package tutorial.core.guide;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import tutorial.support.ColorDockable;
import tutorial.support.JTutorialFrame;
import tutorial.support.TextDockable;
import tutorial.support.Tutorial;
import bibliothek.extension.gui.dock.theme.SmoothTheme;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DockFactory;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.layout.LocationEstimationMap;
import bibliothek.gui.dock.layout.PredefinedDockSituation;
import bibliothek.gui.dock.perspective.PerspectiveDockable;
import bibliothek.gui.dock.perspective.PerspectiveElement;
import bibliothek.gui.dock.station.split.SplitDockGrid;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.themes.NoStackTheme;
import bibliothek.util.xml.XElement;
import bibliothek.util.xml.XIO;
@Tutorial(title="Persistent Layout: Global", id="PersistentLayout")
public class PersistentLayoutExample {
/* Assume you have written a big application with many Dockables. But the user complains because the
* layout is reset every time he restarts the application. Of course there is an answer: store
* the layout persistently in a file.
*
* In this example we convert the layout into xml and write the text on the screen. No need for
* a file, this is just a simple example.
*
* If you think this is rather complex: there is a class DockFrontend which does most of the work
* described in this example automatically. We will visit DockFrontend in a later example.
* */
public static void main( String[] args ){
/* Setting up a frame, a controller, a station and some Dockables, as usual */
JTutorialFrame frame = new JTutorialFrame( PersistentLayoutExample.class );
DockController controller = new DockController();
controller.setRootWindow( frame );
frame.destroyOnClose( controller );
controller.setTheme( new NoStackTheme( new SmoothTheme() ));
final SplitDockStation station = new SplitDockStation();
controller.add( station );
frame.add( station );
/* A TextDockable is not much more than a JTextArea */
final TextDockable textDockable = new TextDockable( "Layout" );
/* We use a customized ColorDockable for which we can write a factory */
SplitDockGrid grid = new SplitDockGrid();
grid.addDockable( 0, 0, 50, 100, textDockable );
grid.addDockable( 50, 0, 50, 50, new CustomColorDockable( "Red", Color.RED), new CustomColorDockable( "Green", Color.GREEN ));
grid.addDockable( 50, 50, 25, 50, new CustomColorDockable( "Blue", Color.BLUE ));
grid.addDockable( 75, 50, 25, 50, new CustomColorDockable( "Yellow", Color.YELLOW ));
station.dropTree( grid.toTree() );
/* The algorithms to store and load a layout are defined in the class DockSituation.
* DockSituation contains a map of factories which tell the algorithms how to write
* and read the contents of a DockStation or Dockable.
* DockSituation takes a set of DockStations and writes them into a file. When
* reading the file a new set of new DockStations is created. This is not always
* desirable as we often would like to reuse existing DockStations and/or Dockables.
* The PredefinedDockSituation is an extension of DockSituation and allows to
* reuse existing Objects.
*
* It does not matter when we create "situation" or if we create multiple instances of
* PredefinedDockSituation. We do it now because because its convenient.
* */
final PredefinedDockSituation situation = new PredefinedDockSituation( controller );
/* We are going to reuse "station" and "textDockable". "situation" will not store
* those two elements, rather the identifiers "root" and "layout" are stored. */
situation.put( "root", station );
situation.put( "layout", textDockable );
/* Since we are using instances of CustomColorDockable we need to provide a factory
* for them. Have a look at CustomColorDockableFactory to understand what the
* a factory needs to do. */
situation.add( new CustomColorDockableFactory() );
/* Finally we need some buttons to push in order to save and load the layout */
final JMenuItem saveButton = new JMenuItem( "Save" );
final JMenuItem loadButton = new JMenuItem( "Load" );
loadButton.setEnabled( false );
JMenu menu = new JMenu( "Layout" );
menu.add( loadButton );
menu.add( saveButton );
JMenuBar menuBar = new JMenuBar();
menuBar.add( menu );
frame.setJMenuBar( menuBar );
saveButton.addActionListener( new ActionListener(){
public void actionPerformed( ActionEvent e ){
/* To store a layout we need a map containing all the root-DockStations */
Map<String, DockStation> stations = new HashMap<String, DockStation>();
stations.put( "grid-station", station );
XElement xroot = new XElement( "root" );
situation.writeXML( stations, xroot );
textDockable.setText( xroot.toString() );
loadButton.setEnabled( true );
}
});
loadButton.addActionListener( new ActionListener(){
public void actionPerformed( ActionEvent e ){
try{
/* Here we read the layout. There is no need to check the result of
* "readXML": because we have predefined the root-DockStation, the layout
* already got applied. */
XElement xroot = XIO.read( textDockable.getText() );
situation.readXML( xroot );
}
catch( IOException ex ){
ex.printStackTrace();
}
}
});
frame.setVisible( true );
}
/* Each factory needs a unique identifier, this is the one used by CustomColorDockableFactory */
public static final String CUSTOM_COLOR_DOCKABLE_FACTORY_ID = "color";
/* Our new customized Dockable. */
private static class CustomColorDockable extends ColorDockable{
public CustomColorDockable( String title, Color color ){
super(title, color);
}
/* By implementing "getFactoryID" we tell the DockSituation which factory belongs to
* this Dockable. */
@Override
public String getFactoryID(){
return CUSTOM_COLOR_DOCKABLE_FACTORY_ID;
}
}
/* DockSituation does not write a file directly. It first converts the layout and contents of
* all DockStations and Dockables into an intermediate format. Using an intermediate format
* has several advantages:
* - The layout can be stored in memory without needing much space
* - Different formats for files can be used
* - The algorithm can extract information from the intermediate format: e.g guess the location
* of a Dockable even if there is no real DockStation or Dockable around
*
* Any object can be part of the intermediate format, in our case CustomColorLayout is the
* intermediate representation of CustomColorDockable. */
private static class CustomColorLayout{
private String title;
private Color color;
public CustomColorLayout( String title, Color color ){
this.title = title;
this.color = color;
}
public String getTitle(){
return title;
}
public Color getColor(){
return color;
}
}
/* And we need the factory itself. This factory offers verious methods to convert data
* from one format into another from. In a picture:
*
* ------> XML
* CustomColorDockable <-----> CustomColorLayout <----|
* ------> byte[]
*
* DockFactories are used to handle Dockables and DockStations. We do not need to implement
* the methods handling children because our CustomColorDockable is no DockStation and hence
* will never have any children.
* */
private static class CustomColorDockableFactory implements DockFactory<CustomColorDockable, PerspectiveElement, CustomColorLayout>{
public String getID(){
return CUSTOM_COLOR_DOCKABLE_FACTORY_ID;
}
/* CustomColorDockable ---> CustomColorLayout */
public CustomColorLayout getLayout( CustomColorDockable element, Map<Dockable, Integer> children ){
return new CustomColorLayout( element.getTitleText(), element.getColor() );
}
/* CustomColorDockable <--- CustomColorLayout */
public CustomColorDockable layout( CustomColorLayout layout, Map<Integer, Dockable> children, PlaceholderStrategy placeholders ){
return layout( layout, placeholders );
}
/* CustomColorDockable <--- CustomColorLayout */
public CustomColorDockable layout( CustomColorLayout layout, PlaceholderStrategy placeholders ){
return new CustomColorDockable( layout.getTitle(), layout.getColor() );
}
/* CustomColorDockable <--- CustomColorLayout, using an existing CustomColorDockable (never happens
* in our case) */
public void setLayout( CustomColorDockable element, CustomColorLayout layout, Map<Integer, Dockable> children, PlaceholderStrategy placeholders ){
setLayout( element, layout, placeholders );
}
/* CustomColorDockable <--- CustomColorLayout, using an existing CustomColorDockable (never happens
* in our case) */
public void setLayout( CustomColorDockable element, CustomColorLayout layout, PlaceholderStrategy placeholders ){
element.setTitleText( layout.getTitle() );
element.setColor( layout.getColor() );
}
/* ignore in this example */
public CustomColorLayout getPerspectiveLayout( PerspectiveElement element, Map<PerspectiveDockable, Integer> children ){
return null;
}
/* ignored in this example */
public void layoutPerspective( PerspectiveElement perspective, CustomColorLayout layout, Map<Integer, PerspectiveDockable> children ){
// ignore
}
/* ignored in this example */
public PerspectiveElement layoutPerspective( CustomColorLayout layout, Map<Integer, PerspectiveDockable> children ){
return null;
}
/* CustomColorLayout ----> byte[] */
public void write( CustomColorLayout layout, DataOutputStream out ) throws IOException{
out.writeUTF( layout.getTitle() );
out.writeInt( layout.getColor().getRGB() );
}
/* CustomColorLayout <---- byte[] */
public CustomColorLayout read( DataInputStream in, PlaceholderStrategy placeholders ) throws IOException{
String title = in.readUTF();
Color color = new Color( in.readInt() );
return new CustomColorLayout( title, color );
}
/* CustomColorLayout ----> XML */
public void write( CustomColorLayout layout, XElement element ){
element.addElement( "title" ).setString( layout.getTitle() );
element.addElement( "color" ).setInt( layout.getColor().getRGB() );
}
/* CustomColorLayout <---- XML */
public CustomColorLayout read( XElement element, PlaceholderStrategy placeholders ){
String title = element.getElement( "title" ).getString();
Color color = new Color( element.getElement( "color" ).getInt() );
return new CustomColorLayout( title, color );
}
public void estimateLocations( CustomColorLayout layout, LocationEstimationMap children ){
// ignore
}
}
}