package tutorial.common.basics;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import tutorial.support.JTutorialFrame;
import tutorial.support.Tutorial;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.action.ActionType;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.action.view.ActionViewConverter;
import bibliothek.gui.dock.action.view.ViewGenerator;
import bibliothek.gui.dock.action.view.ViewTarget;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CAction;
import bibliothek.gui.dock.common.action.core.CommonDockAction;
import bibliothek.gui.dock.station.flap.button.ButtonContentAction;
import bibliothek.gui.dock.themes.basic.action.BasicTitleViewItem;
import bibliothek.gui.dock.themes.basic.action.dropdown.DropDownViewItem;
import bibliothek.gui.dock.themes.basic.action.menu.MenuViewItem;
import bibliothek.gui.dock.title.DockTitle.Orientation;
@Tutorial(title="Title with a textfield", id="TitleWithTextField")
public class TitleWithTextFieldExample {
public static void main( String[] args ){
/* What if you want to create a CDockable that shows a textfield (or any other Component) in its title?
* The textfield can be implemented as CAction. Implementing the textfield may be a bit tedious, but
* using it will be straight forward. */
/* Before any testing we need a frame and a control */
JTutorialFrame frame = new JTutorialFrame( TitleWithTextFieldExample.class );
CControl control = new CControl( frame );
frame.destroyOnClose( control );
frame.add( control.getContentArea(), BorderLayout.CENTER );
/* As we are defining the textfield as "action", we need to inform the framework of how to
* show the action. The action itself will not be a JComponent, rather it is a model encapsulating
* the data we need for a textfield. The factories we install here will actually create
* a JTextField that shows the text of the action.
*
* In this example we will only create a view for a textfield on a title, there will be no
* textfield in menus. */
ActionViewConverter converter = control.getController().getActionViewConverter();
converter.putDefault( TEXT_FIELD_TYPE, ViewTarget.TITLE, new TextFieldOnTitleGenerator() );
converter.putDefault( TEXT_FIELD_TYPE, ViewTarget.MENU, new TextFieldInMenuGenerator() );
converter.putDefault( TEXT_FIELD_TYPE, ViewTarget.DROP_DOWN, new TextFieldInDropDownGenerator() );
/* This is the action representing a textfield. As you see, it is really easy to create... */
CTextFieldAction sharedAction = new CTextFieldAction();
/* ... and to apply to some Dockables. Note that we forward the same action to both Dockables,
* as a result editing the text on one Dockable will change the text on the other as well. */
DefaultSingleCDockable dockableA = new DefaultSingleCDockable( "a", "Aaaa", sharedAction );
DefaultSingleCDockable dockableB = new DefaultSingleCDockable( "b", "Bbbb", sharedAction );
/* As usual, we need to place our Dockables and make the entire application visible. */
CGrid grid = new CGrid( control );
grid.add( 0, 0, 1, 1, dockableA );
grid.add( 0, 1, 1, 1, dockableB );
control.getContentArea().deploy( grid );
frame.setVisible( true );
}
/* As we are going to introduce a new type of action, we need an identifier for this kind of action.
* The identifier is necessary to find appropriate factories when creating the view of the action. */
public static final ActionType<TextFieldAction> TEXT_FIELD_TYPE =
new ActionType<TitleWithTextFieldExample.TextFieldAction>( "text field" );
/* In the Common API an action is always a "CAction". "CAction" is just a wrapper around a DockAction,
* and in our case we do not need any complex logic inside of the CAction. We can use this wrapper
* to present a nice API to the client. */
public static class CTextFieldAction extends CAction{
private TextFieldAction textFieldAction;
public CTextFieldAction(){
super( null );
/* CActions delegate all their work to a DockAction. The best place to create and set
* this DockAction is in the constructor. */
textFieldAction = new TextFieldAction( this );
init( textFieldAction );
}
public String getText(){
return textFieldAction.getText();
}
public void setText( String text ){
textFieldAction.setText( text );
}
}
/* The DockAction "TextFieldAction" is our model of a textfield. The model of a textfield consists
* of one String, the "text".
*
* Notice the annotation "ButtonContentAction", this will cause a textfield to appear on the button
* of a minimized CDockable. In a real application you would probably remove this annotation. */
@ButtonContentAction
private static class TextFieldAction implements DockAction, CommonDockAction{
/* This is the CAction that delegates all its work to this DockAction */
private CTextFieldAction wrapper;
/* This is the all important data: the text */
private String text = "";
/* And with help of a ChangeListener our view can be informed when the model changes. */
private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
public TextFieldAction( CTextFieldAction wrapper ){
this.wrapper = wrapper;
}
/* There is not much to say about the next few methods: text can be set, and listeners
* are called if the text changes. */
public void addChangeListener( ChangeListener listener ){
listeners.add( listener );
}
public void removeChangeListener( ChangeListener listener ){
listeners.remove( listener );
}
public String getText(){
return text;
}
public void setText( String text ){
this.text = text;
ChangeEvent event = new ChangeEvent( this );
for( ChangeListener listener : listeners ){
listener.stateChanged( event );
}
}
public CAction getAction(){
return wrapper;
}
/* Now this method is interesting. The model is responsible for creating its own view. The proper
* implementation of this method is just to forward the call to the "ActionViewConverter". Subclasses
* may however override this method and tweak the view. */
public <V> V createView( ViewTarget<V> target, ActionViewConverter converter, Dockable dockable ){
return converter.createView( TEXT_FIELD_TYPE, this, target, dockable );
}
/* The methods "bind" and "unbind" inform the action that it is actually shown somewhere.
* As this DockAction does not depend on any outside sources, they are not interesting. */
public void bind( Dockable dockable ){
// ignore
}
public void unbind( Dockable dockable ){
// ignore
}
/* "trigger" is the method usually called when the user clicks a button. For a text field it might be
* called if the user hits "enter", but in this example we are going to ignore it. */
public boolean trigger( Dockable dockable ){
return false;
}
}
/* We have our model, the TextFieldAction, but now we need a way to actually show the text on screen. The
* TextFieldView is a JTextField showing the text of the action. */
private static class TextFieldView implements BasicTitleViewItem<JComponent>, ChangeListener, DocumentListener, ActionListener{
/* This is the component we are placing on the title */
private JTextField textField;
/* This is the Dockable using this action */
private Dockable dockable;
/* And this is the model */
private TextFieldAction action;
/* Some flags to prevent our methods from running into a StackTraceException */
private boolean updatingAction = false;
private boolean updatingTextField = false;
public TextFieldView( Dockable dockable, TextFieldAction action ){
this.dockable = dockable;
this.action = action;
}
/* This method is called if the view is made visible. We create and wire all the
* Components we need at this place. */
public void bind(){
action.addChangeListener( this );
textField = new JTextField();
textField.setColumns( 10 );
textField.getDocument().addDocumentListener( this );
textField.setText( action.getText() );
textField.addActionListener( this );
}
/* And this method is called once this view is no longer visible, we can clean up
* all the listeners. */
public void unbind(){
action.removeChangeListener( this );
textField.removeActionListener( this );
textField.getDocument().removeDocumentListener( this );
textField = null;
}
/* The next few methods deal with changes in the model and on the view:
* - If the user enters some text we forward the new text to the action.
* - If the text of the action changed, we update the text in "textField". */
public void changedUpdate( DocumentEvent e ){
if( !updatingTextField ){
try{
updatingAction = true;
action.setText( textField.getText() );
}
finally {
updatingAction = false;
}
}
}
public void insertUpdate( DocumentEvent e ){
changedUpdate( e );
}
public void removeUpdate( DocumentEvent e ){
changedUpdate( e );
}
public void stateChanged( ChangeEvent e ){
if( !updatingAction ){
try{
updatingTextField = true;
textField.setText( action.getText() );
}
finally{
updatingTextField = false;
}
}
}
public void actionPerformed( ActionEvent e ){
action.trigger( dockable );
}
/* And finally some methods required by the interface BasicTitleViewItem. */
public JComponent getItem(){
return textField;
}
public DockAction getAction(){
return action;
}
public void setOrientation( Orientation orientation ){
// not supported
}
public void setForeground( Color foreground ){
// not supported
}
public void setBackground( Color background ){
// not supported
}
}
/* At the very end we can define the factories which will create new views for our new type of action. */
private static class TextFieldOnTitleGenerator implements ViewGenerator<TextFieldAction, BasicTitleViewItem<JComponent>>{
public BasicTitleViewItem<JComponent> create( ActionViewConverter converter, TextFieldAction action, Dockable dockable ){
return new TextFieldView( dockable, action );
}
}
/* ... although for menus and drop-down-menus we are not creating views. But the factories are required anyway. */
private static class TextFieldInMenuGenerator implements ViewGenerator<TextFieldAction, MenuViewItem<JComponent>>{
public MenuViewItem<JComponent> create( ActionViewConverter converter, TextFieldAction action, Dockable dockable ){
return null;
}
}
private static class TextFieldInDropDownGenerator implements ViewGenerator<TextFieldAction, DropDownViewItem>{
public DropDownViewItem create( ActionViewConverter converter, TextFieldAction action, Dockable dockable ){
return null;
}
}
}