Java 如何创建捕捉到面板上特定边界的可拖动JLabel

Java 如何创建捕捉到面板上特定边界的可拖动JLabel,java,swing,draggable,jlabel,venn-diagram,Java,Swing,Draggable,Jlabel,Venn Diagram,我正在用Java开发一个Venn图GUI应用程序,它要求我创建元素(JLabel),这些元素可以在循环的Venn图部分中拖放。目前我有两个(定制)重叠的圆形J面板 出现了两个问题:1)如何创建可以在屏幕上移动的J标签,并最终捕捉到标签的某些有界点,并符合边界2)如何在J面板上创建一个布局,使文本元素只适合某些点,而不重叠 到目前为止,我的GUI是这样的。我对其进行了标记,以向您展示该界面的总体概念,以及我希望该功能如何工作 给你。。。大约463行代码让您开始 public class Final

我正在用Java开发一个Venn图GUI应用程序,它要求我创建元素(JLabel),这些元素可以在循环的Venn图部分中拖放。目前我有两个(定制)重叠的圆形J面板

出现了两个问题:

1)如何创建可以在屏幕上移动的J标签,并最终捕捉到标签的某些有界点,并符合边界

2)如何在J面板上创建一个布局,使文本元素只适合某些点,而不重叠

到目前为止,我的GUI是这样的。我对其进行了标记,以向您展示该界面的总体概念,以及我希望该功能如何工作


给你。。。大约463行代码让您开始

public class Final {

    public static enum MouseEventFlag {
        KEY_ALT, KEY_ALTGRAPH, KEY_CTRL, KEY_META, KEY_SHIFT, BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT;

        public static EnumSet<MouseEventFlag> buttonSetOf(final MouseEvent mevt) {
            final EnumSet<MouseEventFlag> set = EnumSet.noneOf(MouseEventFlag.class);
            if (SwingUtilities.isLeftMouseButton(mevt)) set.add(BUTTON_LEFT);
            if (SwingUtilities.isMiddleMouseButton(mevt)) set.add(BUTTON_MIDDLE);
            if (SwingUtilities.isRightMouseButton(mevt)) set.add(BUTTON_RIGHT);
            return set;
        }
    }

    //Converts EnumSet to mask:
    public static long pack(final EnumSet<?> set) { //Supports Enums with up to 64 values.
        //return set.stream().mapToLong(e -> (1L << e.ordinal())).reduce(0L, (L1, L2) -> L1 | L2);
        long L = 0;
        for (final Enum e: set)
            L = L | (1L << e.ordinal());
        return L;
    }

    //Converts Enums to mask:
    public static <E extends Enum<E>> long pack(final E... ez) { //Supports Enums with up to 64 values.
        long L = 0;
        for (final Enum e: ez)
            L = L | (1L << e.ordinal());
        return L;
    }

    public static Color transparent(final Color c, final int alpha) {
        return new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
    }

    public static void setOperatedSize(final Dimension input, final BiFunction<Integer, Integer, Integer> operator, final Dimension inputOutput) {
        inputOutput.setSize(operator.apply(input.width, inputOutput.width), operator.apply(input.height, inputOutput.height));
    }

    //Prompts user to input some text.
    public static void inputTitle(final Component parent, final String titleID, final Consumer<String> consumer) {
        final String userIn = JOptionPane.showInputDialog(parent, "Enter " + titleID.toLowerCase() + "'s title:", "Enter title", JOptionPane.QUESTION_MESSAGE);
        if (userIn != null) {
            if (userIn.isEmpty())
                JOptionPane.showMessageDialog(parent, titleID + "'s name cannot be empty...", "Oups!", JOptionPane.INFORMATION_MESSAGE);
            else
                consumer.accept(userIn);
        }
    }

    //Applies an action to every child component of the container recursively as well as to the given Container.
    public static void consumeComponentsRecursively(final Container container, final Consumer<Component> consumer) {
        for (final Component child: container.getComponents())
            if (child instanceof Container)
                consumeComponentsRecursively((Container) child, consumer);
            else
                consumer.accept(child);
        consumer.accept(container);
    }

    public static Dimension getGoodEnoughSize(final Component comp, final Dimension defaultSize) {
        final Dimension dim = new Dimension(defaultSize);
        if (comp != null) { // && comp.isVisible()) {
            /*Start with default size, and then listen to max and min
            (if both max and min are set, we prefer the min one):*/
            if (comp.isMaximumSizeSet())
                setOperatedSize(comp.getMaximumSize(), Math::min, dim);
            if (comp.isMinimumSizeSet())
                setOperatedSize(comp.getMinimumSize(), Math::max, dim);
        }
        return dim;
    }

    public static class ManualLayout implements LayoutManager, Serializable {

        public Dimension getLayoutComponentSize(final Component comp) {
            return getGoodEnoughSize(comp, (comp.getWidth() <= 0 && comp.getHeight() <= 0)? comp.getPreferredSize(): comp.getSize());
        }

        @Override public void addLayoutComponent(final String name, final Component comp) { }
        @Override public void removeLayoutComponent(final Component comp) { }
        @Override public Dimension preferredLayoutSize(final Container parent) { return minimumLayoutSize(parent); } //Preferred and minimum coincide for simplicity.

        @Override
        public Dimension minimumLayoutSize(final Container parent) {
            final Component[] comps = parent.getComponents();
            if (comps == null || comps.length <= 0)
                return new Dimension();
            final Rectangle totalBounds = new Rectangle(comps[0].getLocation(), getLayoutComponentSize(comps[0]));
            for (int i = 1; i < comps.length; ++i)
                totalBounds.add(new Rectangle(comps[i].getLocation(), getLayoutComponentSize(comps[i])));
            return new Dimension(totalBounds.x + totalBounds.width, totalBounds.y + totalBounds.height);
        }

        @Override
        public void layoutContainer(final Container parent) {
            for (final Component comp: parent.getComponents())
                comp.setSize(getLayoutComponentSize(comp)); //Just set the size. The locations are taken care by the class's client supposedly.
        }
    }

    public static abstract class RectangularPanel<R extends RectangularShape> extends JPanel {
        private R shape;
        private Area cache; /*Use a cache so as not to have to create new Areas every time we need
        to intersect the clip (see 'createClip' method). Also, the client can modify the current
        shape but not the cache upon which the shape and painting of the panel depend.*/

        public RectangularPanel(final double width, final double height) {
            super();
            super.setOpaque(false);
            cache = new Area(shape = createShape(0, 0, width, height));
            super.addComponentListener(new ComponentAdapter() {
                @Override
                public void componentResized(final ComponentEvent cevt) {
                    cache = new Area(shape = createShape(0, 0, getWidth(), getHeight()));
                    revalidate();
                    repaint();
                }
            });
        }

        protected abstract R createShape(final double x, final double y, final double width, final double height);
        protected abstract double getArcWidth();
        protected abstract double getArcHeight();
        protected final R getShape() { return shape; }
        @Override public boolean contains(final int x, final int y) { return cache.contains(x, y); }

        protected Shape createClip(final Shape originalClip) {
            if (originalClip == null)
                return cache;
            final Area clip = new Area(originalClip);
            clip.intersect(cache);
            return clip;
        }

        @Override
        public void paint(final Graphics g) { //print() and update() rely on paint(), so we only need to override this one...
            g.setClip(createClip(g.getClip()));
            super.paint(g);
        }
    }

    public static class VennTool implements Runnable {

        protected static final Object LAYER_USER_CONTROLS = JLayeredPane.DEFAULT_LAYER, LAYER_VENN_SET = JLayeredPane.PALETTE_LAYER, LAYER_VENN_LABEL = JLayeredPane.MODAL_LAYER;

        public class VennDrawPanel extends JPanel {

            private final JLayeredPane pane;
            private final int drawingOffsetY, drawingOffsetX;
            private final JCheckBox attachMode, collisionMode;
            private final JPanel ctrl;

            public VennDrawPanel(final GraphicsConfiguration gconf) {
                super(new BorderLayout());
                pane = new JLayeredPane();
                super.add(pane, BorderLayout.CENTER);
                final ManualLayout layout = new ManualLayout();
                pane.setLayout(layout);
                final Dimension prefsz = new Dimension(gconf.getBounds().getSize());
                prefsz.width = (2 * prefsz.width) / 3;
                prefsz.height = (2 * prefsz.height) / 3;
                final JButton createLabel = new JButton("Create label");
                final JButton createSet = new JButton("Create set");
                attachMode = new JCheckBox("Attach mode", true);
                collisionMode = new JCheckBox("Collision mode", true);
                ctrl = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 5));
                ctrl.add(createLabel);
                ctrl.add(createSet);
                ctrl.add(attachMode);
                ctrl.add(collisionMode);
                drawingOffsetX = layout.getLayoutComponentSize(ctrl).width;
                prefsz.width = Math.max(prefsz.width, drawingOffsetX);
                drawingOffsetY = prefsz.height / 8;
                pane.setPreferredSize(prefsz);
                pane.add(ctrl, LAYER_USER_CONTROLS);
                createLabel.addActionListener(e -> inputTitle(this, "Label", VennTool.this::createLabel));
                createSet.addActionListener(e -> inputTitle(this, "Set", VennTool.this::createSet));
            }

            protected void setControlsEnabled(final boolean enable) { consumeComponentsRecursively(ctrl, c -> c.setEnabled(enable)); }
            public boolean isAttachModeSelected() { return attachMode.isSelected(); }
            public boolean isCollisionModeSelected() { return collisionMode.isSelected(); }
            protected Point getCreationLocation() { return new Point(drawingOffsetX + 50, drawingOffsetY / 2); }

            public void addSet(final VennSet set) {
                set.setLocation(getCreationLocation());
                pane.add(set, LAYER_VENN_SET, 0);
                pane.revalidate();
                pane.repaint();
            }

            public void addLabel(final VennLabel label) {
                label.setLocation(getCreationLocation());
                pane.add(label, LAYER_VENN_LABEL, 0);
                pane.revalidate();
                pane.repaint();
            }
        }

        protected static class VennBorder extends LineBorder {
            public VennBorder(final int thickness) { super(Color.BLACK, thickness, true); }

            @Override
            public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
                if (c instanceof VennControl) {
                    final VennControl ctrl = (VennControl) c;
                    Graphics2D g2d = (Graphics2D) g.create();
                    try {
                        g2d.setColor(ctrl.getBorderColor());
                        final int t2 = thickness + thickness;
                        final int aw = (int) Math.round(Math.min(width, ctrl.getArcWidth())), ah = (int) Math.round(Math.min(height, ctrl.getArcHeight()));
                        final Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
                        path.append(new RoundRectangle2D.Float(x, y, width, height, aw, ah), false);
                        path.append(new RoundRectangle2D.Double(x + thickness, y + thickness, width - t2, height - t2, aw, ah), false);
                        g2d.fill(path);
                    }
                    finally {
                        g2d.dispose();
                    }
                }
                else
                    super.paintBorder(c, g, x, y, width, height);
            }
        }

        public static <C1 extends VennControl<C1, C2, ?, ?>, C2 extends VennControl<C2, C1, ?, ?>> void attach(final C1 c1, final C2 c2) { //Utility method.
            c1.getAttachments().add(c2);
            c2.getAttachments().add(c1);
        }

        public static <C1 extends VennControl<C1, C2, ?, ?>, C2 extends VennControl<C2, C1, ?, ?>> void detach(final C1 c1, final C2 c2) { //Utility method.
            c1.getAttachments().remove(c2);
            c2.getAttachments().remove(c1);
        }

        protected abstract class VennControl<C1 extends VennControl<C1, C2, R1, R2>, C2 extends VennControl<C2, C1, R2, R1>, R1 extends RectangularShape, R2 extends RectangularShape> extends RectangularPanel<R1> {
            private Color bg;
            private boolean highlighted, selected;
            private final LinkedHashSet<C2> attachments;

            public VennControl(final String title, final double width, final double height) {
                super(width, height);
                super.setLayout(new GridBagLayout());
                super.add(new JLabel(Objects.toString(title), JLabel.CENTER));
                super.setBorder(new VennBorder(2));
                super.setSize((int) Math.ceil(width), (int) Math.ceil(height));
                attachments = new LinkedHashSet<>();
                bg = transparent(Color.LIGHT_GRAY, 127);
                highlighted = selected = false;
                final MouseAdapter relocationMA = new MouseAdapter() {
                    private Point moveAnchor;
                    private LinkedHashSet<C2> intersections;

                    @Override
                    public void mousePressed(final MouseEvent mevt) {
                        if (pack(MouseEventFlag.buttonSetOf(mevt)) == pack(MouseEventFlag.BUTTON_LEFT) && moveAnchor == null && intersections == null) {
                            VennTool.this.drawPanel.setControlsEnabled(false);
                            moveAnchor = mevt.getPoint();
                            setSelected(true);
                            intersections = findIntersections(0, 0);
                            intersections.forEach(c2 -> c2.setHighlighted(true));
                        }
                    }

                    @Override
                    public void mouseDragged(final MouseEvent mevt) {
                        final int dx = mevt.getX() - moveAnchor.x, dy = mevt.getY() - moveAnchor.y;
                        final boolean attach = VennTool.this.drawPanel.isAttachModeSelected(), collisions = VennTool.this.drawPanel.isCollisionModeSelected();
                        final LinkedHashSet<C2> newIntersections = findIntersections(dx, dy);
                        if (MouseEventFlag.buttonSetOf(mevt).contains(MouseEventFlag.BUTTON_LEFT) && moveAnchor != null && intersections != null && (!attach || newIntersections.containsAll(getAttachments())) && (!collisions || !collides(dx, dy))) {
                            setLocation(getX() + dx, getY() + dy);
                            LinkedHashSet<C2> setHighlight = (LinkedHashSet<C2>) intersections.clone();
                            setHighlight.removeAll(newIntersections);
                            if (!attach)
                                setHighlight.forEach(c2 -> detach(c2, (C1) VennControl.this));
                            setHighlight.forEach(c2 -> c2.setHighlighted(false));
                            setHighlight = (LinkedHashSet<C2>) newIntersections.clone();
                            setHighlight.removeAll(intersections);
                            setHighlight.forEach(c2 -> c2.setHighlighted(true));
                            intersections = newIntersections;
                        }
                    }

                    @Override
                    public void mouseReleased(final MouseEvent mevt) {
                        if (pack(MouseEventFlag.buttonSetOf(mevt)) == pack(MouseEventFlag.BUTTON_LEFT) && moveAnchor != null && intersections != null) {
                            intersections.forEach(c2 -> c2.setHighlighted(false));
                            final VennDrawPanel vdp = VennTool.this.drawPanel;
                            if (vdp.isAttachModeSelected())
                                intersections.forEach(c2 -> attach(c2, (C1) VennControl.this));
                            moveAnchor = null;
                            intersections = null;
                            setSelected(false);
                            vdp.setControlsEnabled(true);
                        }
                    }
                };
                super.addMouseListener(relocationMA);
                super.addMouseMotionListener(relocationMA);
            }

            protected LinkedHashSet<C2> findIntersections(final double dx, final double dy) {
                final R1 r1tmp = getShape();
                final R1 r1 = createShape(getX() + dx + r1tmp.getX(), getY() + dy + r1tmp.getY(), r1tmp.getWidth(), r1tmp.getHeight());
                final LinkedHashSet<C2> intersections = new LinkedHashSet<>(), possibilities = getPossibleIntersections();
                possibilities.forEach(c2 -> {
                    final R2 r2tmp = c2.getShape();
                    final R2 r2 = c2.createShape(c2.getX() + r2tmp.getX(), c2.getY() + r2tmp.getY(), r2tmp.getWidth(), r2tmp.getHeight());
                    if (intersect(r1, r2))
                        intersections.add(c2);
                });
                return intersections;
            }

            public LinkedHashSet<C2> getAttachments() { return attachments; }
            protected abstract boolean intersect(final R1 r1, final R2 r2);
            protected abstract LinkedHashSet<C2> getPossibleIntersections();
            protected abstract boolean collides(final double dx, final double dy);

            public void setHighlighted(final boolean highlighted) {
                if (highlighted != this.highlighted) {
                    this.highlighted = highlighted;
                    repaint();
                }
            }

            public void setSelected(final boolean selected) {
                if (selected != this.selected) {
                    this.selected = selected;
                    repaint();
                }
            }

            public void setColor(final Color c) {
                if (!bg.equals(c)) {
                    bg = Objects.requireNonNull(c);
                    repaint();
                }
            }

            public Color getBorderColor() {
                return selected? Color.GREEN: (highlighted? Color.CYAN: Color.BLACK);
            }

            @Override
            protected void paintComponent(final Graphics g) {
                super.paintComponent(g);
                g.setColor(bg);
                g.fillRect(0, 0, getWidth(), getHeight());
            }
        }

        protected class VennLabel extends VennControl<VennLabel, VennSet, Rectangle2D, Ellipse2D> {
            public VennLabel(final String title) { super(title, 0, 0); }
            @Override protected Rectangle2D createShape(double x, double y, double width, double height) { return new Rectangle2D.Double(x, y, width, height); }
            @Override protected double getArcWidth() { return 0; }
            @Override protected double getArcHeight() { return 0; }
            @Override protected boolean intersect(final Rectangle2D r1, final Ellipse2D r2) { return r2.intersects(r1); }
            @Override protected LinkedHashSet<VennSet> getPossibleIntersections() { return VennTool.this.sets; }

            @Override
            protected boolean collides(final double dx, final double dy) {
                Rectangle2D tmp = getShape();
                final Rectangle2D thisShape = createShape(getX() + dx + tmp.getX(), getY() + dy + tmp.getY(), tmp.getWidth(), tmp.getHeight());
                for (final VennLabel label: VennTool.this.labels)
                    if (label != this) {
                        tmp = label.getShape();
                        tmp = label.createShape(label.getX() + tmp.getX(), label.getY() + tmp.getY(), tmp.getWidth(), tmp.getHeight());
                        if (tmp.intersects(thisShape))
                            return true;
                    }
                return false;
            }
        }

        protected class VennSet extends VennControl<VennSet, VennLabel, Ellipse2D, Rectangle2D> {

            public VennSet(final String title, final double radius) {
                super(title, radius + radius, radius + radius);
                final Dimension sz = super.getPreferredSize();
                sz.width = sz.height = Math.max(Math.max(sz.width, super.getWidth()), Math.max(sz.height, super.getHeight()));
                super.setSize(sz);
            }

            @Override protected Ellipse2D createShape(double x, double y, double width, double height) { return new Ellipse2D.Double(x, y, width, height); }
            @Override protected double getArcWidth() { return getWidth(); }
            @Override protected double getArcHeight() { return getHeight(); }
            @Override protected boolean intersect(final Ellipse2D r1, final Rectangle2D r2) { return r1.intersects(r2); }
            @Override protected LinkedHashSet<VennLabel> getPossibleIntersections() { return VennTool.this.labels; }
            @Override protected boolean collides(final double dx, final double dy) { return false; } //Never collides with anything.
        }

        private final JFrame frame;
        private final VennDrawPanel drawPanel;
        private final LinkedHashSet<VennSet> sets;
        private final LinkedHashSet<VennLabel> labels;

        public VennTool(final GraphicsConfiguration gconf) {
            drawPanel = new VennDrawPanel(gconf);
            frame = new JFrame("Collisionless Venn Tool", gconf);
            sets = new LinkedHashSet<>();
            labels = new LinkedHashSet<>();
        }

        public void createSet(final String title) {
            final VennSet set = new VennSet(title, 100);
            sets.add(set);
            drawPanel.addSet(set);
            drawPanel.revalidate();
            drawPanel.repaint();
        }

        public void createLabel(final String title) {
            final VennLabel label = new VennLabel(title);
            labels.add(label);
            drawPanel.addLabel(label);
            drawPanel.revalidate();
            drawPanel.repaint();
        }

        @Override
        public void run() {
            if (SwingUtilities.isEventDispatchThread()) {
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.getContentPane().add(drawPanel);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
            else
                SwingUtilities.invokeLater(this);
        }
    }

    public static void main(final String[] args) {
        final VennTool tool = new VennTool(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration());
        SwingUtilities.invokeLater(() -> {
            tool.createSet("Drag this set!");
            tool.createLabel("Drag this label!");
        });
        tool.run();
    }
}
公开课期末考试{
公共静态枚举MouseEventFlag{
ALT键、ALTGRAPH键、CTRL键、META键、SHIFT键、左键、中键、右键;
公共静态枚举集按钮待办事项(最终鼠标事件mevt){
final EnumSet=EnumSet.noneOf(MouseEventFlag.class);
if(SwingUtilities.isLeftMouseButton(mevt))set.add(按钮左);
if(SwingUtilities.isMiddleMouseButton(mevt))set.add(按钮中间);
if(SwingUtilities.isRightMouseButton(mevt))set.add(按钮右);
返回集;
}
}
//将枚举集转换为掩码:
公共静态长包(最终枚举集){//支持最多64个值的枚举。
//返回set.stream().mapToLong(e->(1L L1 | L2);
长L=0;
用于(最终枚举e:集)

L=L |(1L给你…大约463行代码让你开始

public class Final {

    public static enum MouseEventFlag {
        KEY_ALT, KEY_ALTGRAPH, KEY_CTRL, KEY_META, KEY_SHIFT, BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT;

        public static EnumSet<MouseEventFlag> buttonSetOf(final MouseEvent mevt) {
            final EnumSet<MouseEventFlag> set = EnumSet.noneOf(MouseEventFlag.class);
            if (SwingUtilities.isLeftMouseButton(mevt)) set.add(BUTTON_LEFT);
            if (SwingUtilities.isMiddleMouseButton(mevt)) set.add(BUTTON_MIDDLE);
            if (SwingUtilities.isRightMouseButton(mevt)) set.add(BUTTON_RIGHT);
            return set;
        }
    }

    //Converts EnumSet to mask:
    public static long pack(final EnumSet<?> set) { //Supports Enums with up to 64 values.
        //return set.stream().mapToLong(e -> (1L << e.ordinal())).reduce(0L, (L1, L2) -> L1 | L2);
        long L = 0;
        for (final Enum e: set)
            L = L | (1L << e.ordinal());
        return L;
    }

    //Converts Enums to mask:
    public static <E extends Enum<E>> long pack(final E... ez) { //Supports Enums with up to 64 values.
        long L = 0;
        for (final Enum e: ez)
            L = L | (1L << e.ordinal());
        return L;
    }

    public static Color transparent(final Color c, final int alpha) {
        return new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
    }

    public static void setOperatedSize(final Dimension input, final BiFunction<Integer, Integer, Integer> operator, final Dimension inputOutput) {
        inputOutput.setSize(operator.apply(input.width, inputOutput.width), operator.apply(input.height, inputOutput.height));
    }

    //Prompts user to input some text.
    public static void inputTitle(final Component parent, final String titleID, final Consumer<String> consumer) {
        final String userIn = JOptionPane.showInputDialog(parent, "Enter " + titleID.toLowerCase() + "'s title:", "Enter title", JOptionPane.QUESTION_MESSAGE);
        if (userIn != null) {
            if (userIn.isEmpty())
                JOptionPane.showMessageDialog(parent, titleID + "'s name cannot be empty...", "Oups!", JOptionPane.INFORMATION_MESSAGE);
            else
                consumer.accept(userIn);
        }
    }

    //Applies an action to every child component of the container recursively as well as to the given Container.
    public static void consumeComponentsRecursively(final Container container, final Consumer<Component> consumer) {
        for (final Component child: container.getComponents())
            if (child instanceof Container)
                consumeComponentsRecursively((Container) child, consumer);
            else
                consumer.accept(child);
        consumer.accept(container);
    }

    public static Dimension getGoodEnoughSize(final Component comp, final Dimension defaultSize) {
        final Dimension dim = new Dimension(defaultSize);
        if (comp != null) { // && comp.isVisible()) {
            /*Start with default size, and then listen to max and min
            (if both max and min are set, we prefer the min one):*/
            if (comp.isMaximumSizeSet())
                setOperatedSize(comp.getMaximumSize(), Math::min, dim);
            if (comp.isMinimumSizeSet())
                setOperatedSize(comp.getMinimumSize(), Math::max, dim);
        }
        return dim;
    }

    public static class ManualLayout implements LayoutManager, Serializable {

        public Dimension getLayoutComponentSize(final Component comp) {
            return getGoodEnoughSize(comp, (comp.getWidth() <= 0 && comp.getHeight() <= 0)? comp.getPreferredSize(): comp.getSize());
        }

        @Override public void addLayoutComponent(final String name, final Component comp) { }
        @Override public void removeLayoutComponent(final Component comp) { }
        @Override public Dimension preferredLayoutSize(final Container parent) { return minimumLayoutSize(parent); } //Preferred and minimum coincide for simplicity.

        @Override
        public Dimension minimumLayoutSize(final Container parent) {
            final Component[] comps = parent.getComponents();
            if (comps == null || comps.length <= 0)
                return new Dimension();
            final Rectangle totalBounds = new Rectangle(comps[0].getLocation(), getLayoutComponentSize(comps[0]));
            for (int i = 1; i < comps.length; ++i)
                totalBounds.add(new Rectangle(comps[i].getLocation(), getLayoutComponentSize(comps[i])));
            return new Dimension(totalBounds.x + totalBounds.width, totalBounds.y + totalBounds.height);
        }

        @Override
        public void layoutContainer(final Container parent) {
            for (final Component comp: parent.getComponents())
                comp.setSize(getLayoutComponentSize(comp)); //Just set the size. The locations are taken care by the class's client supposedly.
        }
    }

    public static abstract class RectangularPanel<R extends RectangularShape> extends JPanel {
        private R shape;
        private Area cache; /*Use a cache so as not to have to create new Areas every time we need
        to intersect the clip (see 'createClip' method). Also, the client can modify the current
        shape but not the cache upon which the shape and painting of the panel depend.*/

        public RectangularPanel(final double width, final double height) {
            super();
            super.setOpaque(false);
            cache = new Area(shape = createShape(0, 0, width, height));
            super.addComponentListener(new ComponentAdapter() {
                @Override
                public void componentResized(final ComponentEvent cevt) {
                    cache = new Area(shape = createShape(0, 0, getWidth(), getHeight()));
                    revalidate();
                    repaint();
                }
            });
        }

        protected abstract R createShape(final double x, final double y, final double width, final double height);
        protected abstract double getArcWidth();
        protected abstract double getArcHeight();
        protected final R getShape() { return shape; }
        @Override public boolean contains(final int x, final int y) { return cache.contains(x, y); }

        protected Shape createClip(final Shape originalClip) {
            if (originalClip == null)
                return cache;
            final Area clip = new Area(originalClip);
            clip.intersect(cache);
            return clip;
        }

        @Override
        public void paint(final Graphics g) { //print() and update() rely on paint(), so we only need to override this one...
            g.setClip(createClip(g.getClip()));
            super.paint(g);
        }
    }

    public static class VennTool implements Runnable {

        protected static final Object LAYER_USER_CONTROLS = JLayeredPane.DEFAULT_LAYER, LAYER_VENN_SET = JLayeredPane.PALETTE_LAYER, LAYER_VENN_LABEL = JLayeredPane.MODAL_LAYER;

        public class VennDrawPanel extends JPanel {

            private final JLayeredPane pane;
            private final int drawingOffsetY, drawingOffsetX;
            private final JCheckBox attachMode, collisionMode;
            private final JPanel ctrl;

            public VennDrawPanel(final GraphicsConfiguration gconf) {
                super(new BorderLayout());
                pane = new JLayeredPane();
                super.add(pane, BorderLayout.CENTER);
                final ManualLayout layout = new ManualLayout();
                pane.setLayout(layout);
                final Dimension prefsz = new Dimension(gconf.getBounds().getSize());
                prefsz.width = (2 * prefsz.width) / 3;
                prefsz.height = (2 * prefsz.height) / 3;
                final JButton createLabel = new JButton("Create label");
                final JButton createSet = new JButton("Create set");
                attachMode = new JCheckBox("Attach mode", true);
                collisionMode = new JCheckBox("Collision mode", true);
                ctrl = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 5));
                ctrl.add(createLabel);
                ctrl.add(createSet);
                ctrl.add(attachMode);
                ctrl.add(collisionMode);
                drawingOffsetX = layout.getLayoutComponentSize(ctrl).width;
                prefsz.width = Math.max(prefsz.width, drawingOffsetX);
                drawingOffsetY = prefsz.height / 8;
                pane.setPreferredSize(prefsz);
                pane.add(ctrl, LAYER_USER_CONTROLS);
                createLabel.addActionListener(e -> inputTitle(this, "Label", VennTool.this::createLabel));
                createSet.addActionListener(e -> inputTitle(this, "Set", VennTool.this::createSet));
            }

            protected void setControlsEnabled(final boolean enable) { consumeComponentsRecursively(ctrl, c -> c.setEnabled(enable)); }
            public boolean isAttachModeSelected() { return attachMode.isSelected(); }
            public boolean isCollisionModeSelected() { return collisionMode.isSelected(); }
            protected Point getCreationLocation() { return new Point(drawingOffsetX + 50, drawingOffsetY / 2); }

            public void addSet(final VennSet set) {
                set.setLocation(getCreationLocation());
                pane.add(set, LAYER_VENN_SET, 0);
                pane.revalidate();
                pane.repaint();
            }

            public void addLabel(final VennLabel label) {
                label.setLocation(getCreationLocation());
                pane.add(label, LAYER_VENN_LABEL, 0);
                pane.revalidate();
                pane.repaint();
            }
        }

        protected static class VennBorder extends LineBorder {
            public VennBorder(final int thickness) { super(Color.BLACK, thickness, true); }

            @Override
            public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
                if (c instanceof VennControl) {
                    final VennControl ctrl = (VennControl) c;
                    Graphics2D g2d = (Graphics2D) g.create();
                    try {
                        g2d.setColor(ctrl.getBorderColor());
                        final int t2 = thickness + thickness;
                        final int aw = (int) Math.round(Math.min(width, ctrl.getArcWidth())), ah = (int) Math.round(Math.min(height, ctrl.getArcHeight()));
                        final Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
                        path.append(new RoundRectangle2D.Float(x, y, width, height, aw, ah), false);
                        path.append(new RoundRectangle2D.Double(x + thickness, y + thickness, width - t2, height - t2, aw, ah), false);
                        g2d.fill(path);
                    }
                    finally {
                        g2d.dispose();
                    }
                }
                else
                    super.paintBorder(c, g, x, y, width, height);
            }
        }

        public static <C1 extends VennControl<C1, C2, ?, ?>, C2 extends VennControl<C2, C1, ?, ?>> void attach(final C1 c1, final C2 c2) { //Utility method.
            c1.getAttachments().add(c2);
            c2.getAttachments().add(c1);
        }

        public static <C1 extends VennControl<C1, C2, ?, ?>, C2 extends VennControl<C2, C1, ?, ?>> void detach(final C1 c1, final C2 c2) { //Utility method.
            c1.getAttachments().remove(c2);
            c2.getAttachments().remove(c1);
        }

        protected abstract class VennControl<C1 extends VennControl<C1, C2, R1, R2>, C2 extends VennControl<C2, C1, R2, R1>, R1 extends RectangularShape, R2 extends RectangularShape> extends RectangularPanel<R1> {
            private Color bg;
            private boolean highlighted, selected;
            private final LinkedHashSet<C2> attachments;

            public VennControl(final String title, final double width, final double height) {
                super(width, height);
                super.setLayout(new GridBagLayout());
                super.add(new JLabel(Objects.toString(title), JLabel.CENTER));
                super.setBorder(new VennBorder(2));
                super.setSize((int) Math.ceil(width), (int) Math.ceil(height));
                attachments = new LinkedHashSet<>();
                bg = transparent(Color.LIGHT_GRAY, 127);
                highlighted = selected = false;
                final MouseAdapter relocationMA = new MouseAdapter() {
                    private Point moveAnchor;
                    private LinkedHashSet<C2> intersections;

                    @Override
                    public void mousePressed(final MouseEvent mevt) {
                        if (pack(MouseEventFlag.buttonSetOf(mevt)) == pack(MouseEventFlag.BUTTON_LEFT) && moveAnchor == null && intersections == null) {
                            VennTool.this.drawPanel.setControlsEnabled(false);
                            moveAnchor = mevt.getPoint();
                            setSelected(true);
                            intersections = findIntersections(0, 0);
                            intersections.forEach(c2 -> c2.setHighlighted(true));
                        }
                    }

                    @Override
                    public void mouseDragged(final MouseEvent mevt) {
                        final int dx = mevt.getX() - moveAnchor.x, dy = mevt.getY() - moveAnchor.y;
                        final boolean attach = VennTool.this.drawPanel.isAttachModeSelected(), collisions = VennTool.this.drawPanel.isCollisionModeSelected();
                        final LinkedHashSet<C2> newIntersections = findIntersections(dx, dy);
                        if (MouseEventFlag.buttonSetOf(mevt).contains(MouseEventFlag.BUTTON_LEFT) && moveAnchor != null && intersections != null && (!attach || newIntersections.containsAll(getAttachments())) && (!collisions || !collides(dx, dy))) {
                            setLocation(getX() + dx, getY() + dy);
                            LinkedHashSet<C2> setHighlight = (LinkedHashSet<C2>) intersections.clone();
                            setHighlight.removeAll(newIntersections);
                            if (!attach)
                                setHighlight.forEach(c2 -> detach(c2, (C1) VennControl.this));
                            setHighlight.forEach(c2 -> c2.setHighlighted(false));
                            setHighlight = (LinkedHashSet<C2>) newIntersections.clone();
                            setHighlight.removeAll(intersections);
                            setHighlight.forEach(c2 -> c2.setHighlighted(true));
                            intersections = newIntersections;
                        }
                    }

                    @Override
                    public void mouseReleased(final MouseEvent mevt) {
                        if (pack(MouseEventFlag.buttonSetOf(mevt)) == pack(MouseEventFlag.BUTTON_LEFT) && moveAnchor != null && intersections != null) {
                            intersections.forEach(c2 -> c2.setHighlighted(false));
                            final VennDrawPanel vdp = VennTool.this.drawPanel;
                            if (vdp.isAttachModeSelected())
                                intersections.forEach(c2 -> attach(c2, (C1) VennControl.this));
                            moveAnchor = null;
                            intersections = null;
                            setSelected(false);
                            vdp.setControlsEnabled(true);
                        }
                    }
                };
                super.addMouseListener(relocationMA);
                super.addMouseMotionListener(relocationMA);
            }

            protected LinkedHashSet<C2> findIntersections(final double dx, final double dy) {
                final R1 r1tmp = getShape();
                final R1 r1 = createShape(getX() + dx + r1tmp.getX(), getY() + dy + r1tmp.getY(), r1tmp.getWidth(), r1tmp.getHeight());
                final LinkedHashSet<C2> intersections = new LinkedHashSet<>(), possibilities = getPossibleIntersections();
                possibilities.forEach(c2 -> {
                    final R2 r2tmp = c2.getShape();
                    final R2 r2 = c2.createShape(c2.getX() + r2tmp.getX(), c2.getY() + r2tmp.getY(), r2tmp.getWidth(), r2tmp.getHeight());
                    if (intersect(r1, r2))
                        intersections.add(c2);
                });
                return intersections;
            }

            public LinkedHashSet<C2> getAttachments() { return attachments; }
            protected abstract boolean intersect(final R1 r1, final R2 r2);
            protected abstract LinkedHashSet<C2> getPossibleIntersections();
            protected abstract boolean collides(final double dx, final double dy);

            public void setHighlighted(final boolean highlighted) {
                if (highlighted != this.highlighted) {
                    this.highlighted = highlighted;
                    repaint();
                }
            }

            public void setSelected(final boolean selected) {
                if (selected != this.selected) {
                    this.selected = selected;
                    repaint();
                }
            }

            public void setColor(final Color c) {
                if (!bg.equals(c)) {
                    bg = Objects.requireNonNull(c);
                    repaint();
                }
            }

            public Color getBorderColor() {
                return selected? Color.GREEN: (highlighted? Color.CYAN: Color.BLACK);
            }

            @Override
            protected void paintComponent(final Graphics g) {
                super.paintComponent(g);
                g.setColor(bg);
                g.fillRect(0, 0, getWidth(), getHeight());
            }
        }

        protected class VennLabel extends VennControl<VennLabel, VennSet, Rectangle2D, Ellipse2D> {
            public VennLabel(final String title) { super(title, 0, 0); }
            @Override protected Rectangle2D createShape(double x, double y, double width, double height) { return new Rectangle2D.Double(x, y, width, height); }
            @Override protected double getArcWidth() { return 0; }
            @Override protected double getArcHeight() { return 0; }
            @Override protected boolean intersect(final Rectangle2D r1, final Ellipse2D r2) { return r2.intersects(r1); }
            @Override protected LinkedHashSet<VennSet> getPossibleIntersections() { return VennTool.this.sets; }

            @Override
            protected boolean collides(final double dx, final double dy) {
                Rectangle2D tmp = getShape();
                final Rectangle2D thisShape = createShape(getX() + dx + tmp.getX(), getY() + dy + tmp.getY(), tmp.getWidth(), tmp.getHeight());
                for (final VennLabel label: VennTool.this.labels)
                    if (label != this) {
                        tmp = label.getShape();
                        tmp = label.createShape(label.getX() + tmp.getX(), label.getY() + tmp.getY(), tmp.getWidth(), tmp.getHeight());
                        if (tmp.intersects(thisShape))
                            return true;
                    }
                return false;
            }
        }

        protected class VennSet extends VennControl<VennSet, VennLabel, Ellipse2D, Rectangle2D> {

            public VennSet(final String title, final double radius) {
                super(title, radius + radius, radius + radius);
                final Dimension sz = super.getPreferredSize();
                sz.width = sz.height = Math.max(Math.max(sz.width, super.getWidth()), Math.max(sz.height, super.getHeight()));
                super.setSize(sz);
            }

            @Override protected Ellipse2D createShape(double x, double y, double width, double height) { return new Ellipse2D.Double(x, y, width, height); }
            @Override protected double getArcWidth() { return getWidth(); }
            @Override protected double getArcHeight() { return getHeight(); }
            @Override protected boolean intersect(final Ellipse2D r1, final Rectangle2D r2) { return r1.intersects(r2); }
            @Override protected LinkedHashSet<VennLabel> getPossibleIntersections() { return VennTool.this.labels; }
            @Override protected boolean collides(final double dx, final double dy) { return false; } //Never collides with anything.
        }

        private final JFrame frame;
        private final VennDrawPanel drawPanel;
        private final LinkedHashSet<VennSet> sets;
        private final LinkedHashSet<VennLabel> labels;

        public VennTool(final GraphicsConfiguration gconf) {
            drawPanel = new VennDrawPanel(gconf);
            frame = new JFrame("Collisionless Venn Tool", gconf);
            sets = new LinkedHashSet<>();
            labels = new LinkedHashSet<>();
        }

        public void createSet(final String title) {
            final VennSet set = new VennSet(title, 100);
            sets.add(set);
            drawPanel.addSet(set);
            drawPanel.revalidate();
            drawPanel.repaint();
        }

        public void createLabel(final String title) {
            final VennLabel label = new VennLabel(title);
            labels.add(label);
            drawPanel.addLabel(label);
            drawPanel.revalidate();
            drawPanel.repaint();
        }

        @Override
        public void run() {
            if (SwingUtilities.isEventDispatchThread()) {
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.getContentPane().add(drawPanel);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
            else
                SwingUtilities.invokeLater(this);
        }
    }

    public static void main(final String[] args) {
        final VennTool tool = new VennTool(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration());
        SwingUtilities.invokeLater(() -> {
            tool.createSet("Drag this set!");
            tool.createLabel("Drag this label!");
        });
        tool.run();
    }
}
公开课期末考试{
公共静态枚举MouseEventFlag{
ALT键、ALTGRAPH键、CTRL键、META键、SHIFT键、左键、中键、右键;
公共静态枚举集按钮待办事项(最终鼠标事件mevt){
final EnumSet=EnumSet.noneOf(MouseEventFlag.class);
if(SwingUtilities.isLeftMouseButton(mevt))set.add(按钮左);
if(SwingUtilities.isMiddleMouseButton(mevt))set.add(按钮中间);
if(SwingUtilities.isRightMouseButton(mevt))set.add(按钮右);
返回集;
}
}
//将枚举集转换为掩码:
公共静态长包(最终枚举集){//支持最多64个值的枚举。
//返回set.stream().mapToLong(e->(1L L1 | L2);
长L=0;
用于(最终枚举e:集)

L=L|(1L这种类型的应用程序不是Swing组件的设计目标。我只需要创建一个图形JPanel并在Venn图上绘制所有内容。将Swing组件保留在控件JPanel上。这种类型的应用程序不是Swing组件的设计目标。我只需要创建一个图形JPanel并在Venn图上绘制所有内容。K在控件JPanel上保留Swing组件。Java代码的导入不符合答案。@GilbertLeBlanc这是一个问题吗?它很大吗?我不知道。我是否应该完全不使用泛型?请尽可能详细说明。原始类型可能导致类强制转换异常。在您的代码中,它们可能没有那么大的问题。它们会更复杂如果有人必须定期维护或修改您的代码,这将是一个问题。当我将您的代码放入Eclipse中时,我只是做出了反应,编辑器亮起了许多黄色警告。Java代码的导入不符合答案。@GilbertLeBlanc这是一个问题吗?问题大吗?我不知道。我是否应该在没有泛型的情况下创建它?请详细说明e如果可能的话。原始类型可能会导致类强制转换异常。在您的代码中,它们可能没有那么大的问题。如果有人必须定期维护或修改您的代码,它们将是一个更大的问题。当我将您的代码放入Eclipse时,我只是做出了反应,编辑器亮起了许多黄色警告。