/**
* DelegateView.java
* (c) Peter Bielik and Radek Burget, 2011-2012
*
* SwingBox 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 3 of the License, or
* (at your option) any later version.
*
* SwingBox 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 SwingBox. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.fit.cssbox.swingbox.view;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.CompositeView;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Position;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import org.fit.cssbox.swingbox.SwingBoxDocument.DelegateElement;
/**
* @author Peter Bielik
* @version 1.0
* @since 1.0 - 3.4.2011
*/
/**
* Root view that acts as a gateway between the component and the View
* hierarchy.
*/
public class DelegateView extends CompositeView
{
private View view;
private View parent;
/**
* Instantiates a new delegate view.
*
* @param elem
* the element
*/
public DelegateView(Element elem)
{
super(elem);
}
/**
* Sets the view parent.
*
* @param parent
* the parent view
*/
@Override
public void setParent(View parent)
{
if (parent == null && view != null) view.setParent(null);
this.parent = parent;
// if set new parent and has some element, try to load children
// this element is a BranchElement ("collection"),
// so we should have some LeafElements ("children")
if ((parent != null) && (getElement() != null))
{
ViewFactory f = getViewFactory();
loadChildren(f);
}
}
@Override
public View getParent()
{
return this.parent;
}
@Override
protected void loadChildren(ViewFactory f)
{
if (f == null) { return; }
Element e = getElement();
if (e.getElementCount() > 0)
{
View[] added = new View[1];
// load children (element) using ViewFactory (a new View)
// elements should contain only 1 LeafElement
added[0] = f.create(e.getElement(0));
replace(0, 1, added);
}
}
@Override
public void replace(int offset, int length, View[] views)
{
// update parent reference on removed views
if (offset < offset + length && views.length > 0)
{
/*
* Actually, we remove old view only, if we have some view to add. We
* can not stay empty (no child, no LeafElement-View), otherwise run
* into troubles...
*/
if (view != null)
{
view.setParent(null);
view = null;
}
}
if (views.length > 0)
{
View tmp = views[0];
String name = getDelegateName();
for (int i = 0; name != null && i < views.length; i++)
{
if (name.equals(views[i].getElement().getName()))
{
if (tmp != views[i]) tmp.setParent(null);
tmp = views[i];
}
else
{
if (tmp != views[i]) views[i].setParent(null);
}
}
view = tmp;
/*
* setParent is guaranteed to be first method called after new
* instance is created.
*/
view.setParent(this);
}
}
/**
* Gets the name of delegate
*
* @return the name. It may not be a name of a Class !
*/
public String getDelegateName()
{
javax.swing.text.Element data = getElement();
if (data instanceof DelegateElement) { return ((DelegateElement) data)
.getDelegateName(); }
return null;
}
/**
* Fetches the attributes to use when rendering. At the root level there are
* no attributes. If an attribute is resolved up the view hierarchy this is
* the end of the line.
*/
@Override
public AttributeSet getAttributes()
{
return null;
}
/**
* Determines the preferred span for this view along an axis.
*
* @param axis
* may be either X_AXIS or Y_AXIS
* @return the span the view would like to be rendered into. Typically the
* view is told to render into the span that is returned, although
* there is no guarantee. The parent may choose to resize or break
* the view.
*/
@Override
public float getPreferredSpan(int axis)
{
if (view != null) { return view.getPreferredSpan(axis); }
return 10;
}
/**
* Determines the minimum span for this view along an axis.
*
* @param axis
* may be either X_AXIS or Y_AXIS
* @return the span the view would like to be rendered into. Typically the
* view is told to render into the span that is returned, although
* there is no guarantee. The parent may choose to resize or break
* the view.
*/
@Override
public float getMinimumSpan(int axis)
{
if (view != null) { return view.getMinimumSpan(axis); }
return 10;
}
/**
* Determines the maximum span for this view along an axis.
*
* @param axis
* may be either X_AXIS or Y_AXIS
* @return the span the view would like to be rendered into. Typically the
* view is told to render into the span that is returned, although
* there is no guarantee. The parent may choose to resize or break
* the view.
*/
@Override
public float getMaximumSpan(int axis)
{
if (view != null) { return view.getMaximumSpan(axis); }
return Integer.MAX_VALUE;
}
/**
* Specifies that a preference has changed. Child views can call this on the
* parent to indicate that the preference has changed. The root view routes
* this to invalidate on the hosting component.
* <p>
* This can be called on a different thread from the event dispatching
* thread and is basically unsafe to propagate into the component. To make
* this safe, the operation is transferred over to the event dispatching
* thread for completion. It is a design goal that all view methods be safe
* to call without concern for concurrency, and this behavior helps make
* that true.
*
* @param child
* the child view
* @param width
* true if the width preference has changed
* @param height
* true if the height preference has changed
*/
@Override
public void preferenceChanged(View child, boolean width, boolean height)
{
if (parent != null)
{
parent.preferenceChanged(child, width, height);
}
}
/**
* Determines the desired alignment for this view along an axis.
*
* @param axis
* may be either X_AXIS or Y_AXIS
* @return the desired alignment, where 0.0 indicates the origin and 1.0 the
* full span away from the origin
*/
@Override
public float getAlignment(int axis)
{
if (view != null) { return view.getAlignment(axis); }
return 0;
}
/**
* Renders the view.
*
* @param g
* the graphics context
* @param allocation
* the region to render into
*/
@Override
public void paint(Graphics g, Shape allocation)
{
if (view != null)
{
view.paint(g, allocation);
}
}
/**
* Returns the number of views in this view. Since this view simply wraps
* the root of the view hierarchy it has exactly one child.
*
* @return the number of views
* @see #getView
*/
@Override
public int getViewCount()
{
return view != null ? 1 : 0;
}
/**
* Gets the n-th view in this container.
*
* @param n
* the number of the view to get
* @return the view
*/
@Override
public View getView(int n)
{
return view;
}
/**
* Returns the child view index representing the given position in the
* model. This is implemented to return the index of the only child.
*
* @param pos
* the position >= 0
* @return index of the view representing the given position, or -1 if no
* view represents that position
* @since 1.3
*/
@Override
public int getViewIndex(int pos, Position.Bias b)
{
return view != null ? 0 : -1;
}
/**
* Fetches the allocation for the given child view. This enables finding out
* where various views are located, without assuming the views store their
* location. This returns the given allocation since this view simply acts
* as a gateway between the view hierarchy and the associated component.
*
* @param index
* the index of the child
* @param a
* the allocation to this view.
* @return the allocation to the child
*/
@Override
public Shape getChildAllocation(int index, Shape a)
{
if (view != null) return view.getChildAllocation(index, a);
return a;
}
/**
* Provides a mapping from the document model coordinate space to the
* coordinate space of the view mapped to it.
*
* @param pos
* the position to convert
* @param a
* the allocated region to render into
* @return the bounding box of the given position
*/
@Override
public Shape modelToView(int pos, Shape a, Position.Bias b)
throws BadLocationException
{
if (view != null) { return view.modelToView(pos, a, b); }
return null;
}
/**
* Provides a mapping from the document model coordinate space to the
* coordinate space of the view mapped to it.
*
* @param p0
* the position to convert >= 0
* @param b0
* the bias toward the previous character or the next character
* represented by p0, in case the position is a boundary of two
* views.
* @param p1
* the position to convert >= 0
* @param b1
* the bias toward the previous character or the next character
* represented by p1, in case the position is a boundary of two
* views.
* @param a
* the allocated region to render into
* @return the bounding box of the given position is returned
* @exception BadLocationException
* if the given position does not represent a valid location
* in the associated document
* @exception IllegalArgumentException
* for an invalid bias argument
* @see View#viewToModel
*/
@Override
public Shape modelToView(int p0, Position.Bias b0, int p1,
Position.Bias b1, Shape a) throws BadLocationException
{
if (view != null) { return view.modelToView(p0, b0, p1, b1, a); }
return null;
}
/**
* Provides a mapping from the view coordinate space to the logical
* coordinate space of the model.
*
* @param x
* x coordinate of the view location to convert
* @param y
* y coordinate of the view location to convert
* @param a
* the allocated region to render into
* @return the location within the model that best represents the given
* point in the view
*/
@Override
public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
{
if (view != null)
{
int retValue = view.viewToModel(x, y, a, bias);
return retValue;
}
return -1;
}
/**
* Provides a way to determine the next visually represented model location
* that one might place a caret. Some views may not be visible, they might
* not be in the same order found in the model, or they just might not allow
* access to some of the locations in the model.
*
* @param pos
* the position to convert >= 0
* @param a
* the allocated region to render into
* @param direction
* the direction from the current position that can be thought of
* as the arrow keys typically found on a keyboard. This may be
* SwingConstants.WEST, SwingConstants.EAST,
* SwingConstants.NORTH, or SwingConstants.SOUTH.
* @return the location within the model that best represents the next
* location visual position.
* @exception BadLocationException
* @exception IllegalArgumentException
* for an invalid direction
*/
@Override
public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
int direction, Position.Bias[] biasRet) throws BadLocationException
{
if (view != null)
{
int nextPos = view.getNextVisualPositionFrom(pos, b, a, direction,
biasRet);
if (nextPos != -1)
{
pos = nextPos;
}
else
{
biasRet[0] = b;
}
}
return pos;
}
/**
* Returns the document model underlying the view.
*
* @return the model
*/
@Override
public Document getDocument()
{
return parent.getDocument();
}
/**
* Returns the starting offset into the model for this view.
*
* @return the starting offset
*/
@Override
public int getStartOffset()
{
if (view != null) { return view.getStartOffset(); }
return getElement().getStartOffset();
}
/**
* Returns the ending offset into the model for this view.
*
* @return the ending offset
*/
@Override
public int getEndOffset()
{
if (view != null) { return view.getEndOffset(); }
return getElement().getEndOffset();
}
/**
* Determines the resizability of the view along the given axis. A value of
* 0 or less is not resizable.
*
* @param axis
* may be either X_AXIS or Y_AXIS
* @return the weight
*/
@Override
public int getResizeWeight(int axis)
{
if (view != null) { return view.getResizeWeight(axis); }
return 0;
}
/**
* Sets the view size.
*
* @param width
* the width
* @param height
* the height
*/
@Override
public void setSize(float width, float height)
{
if (view != null)
{
view.setSize(width, height);
}
}
@Override
public String getToolTipText(float x, float y, Shape allocation)
{
if (view != null)
return view.getToolTipText(x, y, allocation);
else
return super.getToolTipText(x, y, allocation);
}
@Override
protected boolean isBefore(int x, int y, Rectangle alloc)
{
return false;
}
@Override
protected boolean isAfter(int x, int y, Rectangle alloc)
{
return false;
}
@Override
protected View getViewAtPoint(int x, int y, Rectangle alloc)
{
return view;
}
@Override
protected void childAllocation(int index, Rectangle a)
{
//nothing
}
}