Order not kept for dockables in stack dock station with 2 dockables and place holder grouping


#1

When there are two dockables in a stack dock station (red, and green for example), each with grouping set to a PlaceholderGrouping with placeholders custom.red and custom.green respectively, if one closes the first dockable (red), and adds a new dockable with the same placeholder as the dockable that was closed, the new dockable will be placed after the green one, instead of going back to where red was.
With three or more dockables this problem does not happen (red, green, blue, close red, and open red again).

I include an example program that demonstrates this:

/*
 * Bibliothek - DockingFrames
 * Library built on Java/Swing, allows the user to "drag and drop"
 * panels containing any Swing-Component the developer likes to add.
 * 
 * Copyright (C) 2015 Benjamin Sigg
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Benjamin Sigg
 * benjamin_sigg@gmx.ch
 * CH - Switzerland
 */
package com.teste.testedockingframes;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;

import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.ColorMap;
import bibliothek.gui.dock.common.DefaultMultipleCDockable;
import bibliothek.gui.dock.common.MultipleCDockableFactory;
import bibliothek.gui.dock.common.MultipleCDockableLayout;
import bibliothek.gui.dock.common.grouping.PlaceholderGrouping;
import bibliothek.util.Colors;
import bibliothek.util.Path;
import bibliothek.util.xml.XElement;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.swing.SwingUtilities;

/*
 * This example shows how to:
 * 1. use the perspective API to put some "placeholders" into the layout
 * 2. use the grouping mechanism to put real dockables at the location of the placeholders
 * 
 * It also demonstrates how dockables that are associated with the same group tend to stick together.
 */
@Tutorial(title = "Grouping Dockables", id = "GroupingDockables")
public class GroupingDockablesExample {

    private static Map<String, ActionListener> actionsMap = new HashMap<>();

    public GroupingDockablesExample() {
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(GroupingDockablesExample::start);
    }

    private static void start() {
        /* We need a frame to show stuff... */
        JTutorialFrame frame = new JTutorialFrame(GroupingDockablesExample.class);

        /* ... and we need a CControl to place stuff. */
        CControl control = new CControl(frame);

        /* The factory is just here because we want to use MultipleCDockables in this example. */
        final MyFactory factory = new MyFactory();
        control.addMultipleDockableFactory("factory", factory);

        /* Adding a CContentArea to the left, and some buttons to the right side of the frame. */
        frame.setLayout(new GridBagLayout());
        frame.add(control.getContentArea(), new GridBagConstraints(0, 0, 1, 1, 100.0, 100.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
        frame.add(buildActions(control, factory), new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));

        actionsMap.values().forEach((ActionListener addDockableAction) -> {
//            addDockableAction.actionPerformed(null);
        });

        /* After setting up the JComponents, we mark some locations with placeholders. */
//        initialLayout( control );
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                try {
                    File f = getFile();
                    control.writeXML(f);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                control.destroy();
            }

        });
        File layoutFile = getFile();
        if (layoutFile.exists()) {
            try {
                control.readXML(layoutFile);
            } catch (IOException ex) {
                ex.printStackTrace(System.err);
            }
        } else {
            CGrid grid = new CGrid(control);
            grid.add(0, 0, 1, 1, new MyDockable(factory, control, "red", Color.RED));
            grid.add(0, 0, 1, 1, new MyDockable(factory, control, "green", Color.GREEN));
            control.getContentArea().deploy(grid);
        }

        frame.setVisible(true);
    }

    private static File getFile() {
        File layoutFile = new File(GroupingDockablesExample.class.getSimpleName() + ".xml");
        return layoutFile;
    }

    private static JComponent buildActions(CControl control, MyFactory factory) {
        /* Just creating a planel with some buttons. Each button adds Dockables for another group. */
        JPanel panel = new JPanel(new GridLayout(6, 1));
        panel.add(createButton("Red", createAction(control, factory, Color.RED, "red")));
        panel.add(createButton("Green", createAction(control, factory, Color.GREEN, "green")));
        panel.add(createButton("Blue", createAction(control, factory, Color.BLUE, "blue")));
        panel.add(createButton("Yellow", createAction(control, factory, Color.YELLOW, "yellow")));
        panel.add(createButton("Magenta", createAction(control, factory, Color.MAGENTA, "magenta")));
        panel.add(createButton("Cyan", createAction(control, factory, Color.CYAN, "cyan")));
        return panel;
    }

    private static ActionListener createAction(CControl control, MyFactory factory, Color color, String name) {
        final ActionListener actionListener = (ActionEvent e) -> {
            addDock(factory, control, name, color);
        };

        actionsMap.put(name, actionListener);
        return actionListener;
    }

    private static JComponent createButton(String name, ActionListener action) {
        JButton button = new JButton("Add " + name);
        button.addActionListener(action);
        return button;
    }

    private static class MyLayout implements MultipleCDockableLayout {

        private String name;

        public MyLayout() {
        }

        public MyLayout(String name) {
            this.name = name;
        }

        @Override
        public void writeStream(DataOutputStream out) throws IOException {
            out.writeUTF(name);
        }

        @Override
        public void readStream(DataInputStream in) throws IOException {
            setName(in.readUTF());
        }

        @Override
        public void writeXML(XElement element) {
            element.addElement("name").setString(name);

        }

        @Override
        public void readXML(XElement element) {
            setName(element.getElement("name").getString());
        }

        public String getName() {
            return name;
        }

        private void setName(String name) {
            this.name = name;
        }

    }

    private static class MyFactory implements MultipleCDockableFactory<MyDockable, MyLayout> {

        @Override
        public MyLayout create() {
            return new MyLayout();
        }

        @Override
        public boolean match(MyDockable dockable, MyLayout layout) {
            return Objects.equals(dockable.getName(), layout.getName());
        }

        @Override
        public MyDockable read(MyLayout layout) {
            return null;
        }

        @Override
        public MyLayout write(MyDockable dockable) {
            return new MyLayout(dockable.getName());
        }

    }

    static void addDock(MyFactory factory, CControl control, String name, Color color) {
        MyDockable dockable = new MyDockable(factory, control, name, color);

        control.addDockable(dockable);
        dockable.setVisible(true);
    }

    private static class MyDockable extends DefaultMultipleCDockable {

        private final String name;

        private static Map<String, Integer> nameToCountMap = new HashMap<>();

        public MyDockable(MyFactory factory, CControl control, String name, Color color) {
            super(factory);
            this.name = name;
            int number = nameToCountMap.compute(name, (k, v) -> {
                if (v == null) {
                    return 0;
                }
                return v + 1;
            });
            setTitleText(name + " " + String.valueOf(number));
            setCloseable(true);
            setRemoveOnClose(true);
            getColors().setColor(ColorMap.COLOR_KEY_TITLE_BACKGROUND, Colors.brighter(color, 0.75));
            getColors().setColor(ColorMap.COLOR_KEY_TITLE_BACKGROUND_FOCUSED, color);

            /* We define the group of "dockable". It will now show up at the location where "placeholder" was inserted into the layout,
             * or at the location of other dockables that have the same group. */
            setGrouping(new PlaceholderGrouping(control, new Path("custom", name)));
        }

        public String getName() {
            return name;
        }
    }
}

GroupingDockablesExample.txt (9.6 KB)