/* ******************************************************************************
*
* Copyright 2008-2010 Hans Dijkema
*
* JRichTextEditor 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.
*
* JRichTextEditor 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 JRichTextEditor. If not, see <http://www.gnu.org/licenses/>.
*
* ******************************************************************************/
package nl.dykema.jxmlnote.widgets;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.util.Vector;
import java.util.WeakHashMap;
import javax.swing.Icon;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.Position;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.TabSet;
import javax.swing.text.TabStop;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import nl.dykema.jxmlnote.document.XMLNoteDocument;
import nl.dykema.jxmlnote.document.XMLNoteImageIcon;
import nl.dykema.jxmlnote.document.XMLNoteMark;
import nl.dykema.jxmlnote.exceptions.BadStyleException;
import nl.dykema.jxmlnote.exceptions.DefaultXMLNoteErrorHandler;
import nl.dykema.jxmlnote.interfaces.MarkMarkupProvider;
import nl.dykema.jxmlnote.styles.XMLNoteStyleConstants;
public class XMLNoteEditorKit extends StyledEditorKit {
private static final long serialVersionUID = 1L;
private JXMLNotePane _pane=null;
private WeakHashMap<Icon,Icon> _iconMap;
private WeakHashMap<Icon,Double> _zoomMap;
public Icon getZoomedInstance(Icon c) {
Double z=_zoomMap.get(c);
Double zf=_pane.getZoomFactor();
if (z==null || !z.equals(zf)) {
_iconMap.put(c, deriveIconForZoom(c));
_zoomMap.put(c,zf);
} else if (z!=null) {
if (c instanceof XMLNoteImageIcon) {
XMLNoteImageIcon xicn=(XMLNoteImageIcon) c;
Icon cc=_iconMap.get(c);
if (cc!=null && cc instanceof XMLNoteImageIcon) {
XMLNoteImageIcon nicn=(XMLNoteImageIcon) cc;
if (!nicn.equalOrig(xicn)) {
_iconMap.put(c, deriveIconForZoom(c));
_zoomMap.put(c, zf);
}
}
}
}
return _iconMap.get(c);
}
public Icon deriveIconForZoom(Icon c) {
if (c instanceof XMLNoteImageIcon) {
XMLNoteImageIcon xicn=(XMLNoteImageIcon) c;
XMLNoteImageIcon nicn=xicn.getZoomedInstance(_pane,new XMLNoteImageIcon.LoadedListener() {
public void loaded() {
SwingUtilities.invokeLater(new Runnable() {
public void run() { _pane.repaint(); }
});
}
});
return nicn;
} else {
return c;
}
}
public ViewFactory getViewFactory() {
if (_pane==null) {
DefaultXMLNoteErrorHandler.exception(new Exception("Unexpected: get of viewfactory before install of pane"));
return null;
} else {
return new XMLNoteViewFactory(_pane,this);
}
}
public Document createDefaultDocument() {
try {
return new XMLNoteDocument();
} catch (BadStyleException e) {
DefaultXMLNoteErrorHandler.exception(e);
return null;
}
}
public void install(JEditorPane p) {
if (p instanceof JXMLNotePane) {
_pane=(JXMLNotePane) p;
} else {
DefaultXMLNoteErrorHandler.exception(new Exception("Unexpected: install of something else than a JXMLNotePane"));
}
super.install(p);
}
public void deinstall(JEditorPane p) {
_pane=null;
super.deinstall(p);
}
public XMLNoteEditorKit(JXMLNotePane p) {
_pane=p;
_iconMap=new WeakHashMap<Icon,Icon>();
_zoomMap=new WeakHashMap<Icon,Double>();
}
}
class XMLNoteViewFactory implements ViewFactory {
private JXMLNotePane _pane;
private XMLNoteEditorKit _kit;
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new MyLabelView(elem,_pane);
}
else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new MyParagraphView(elem,_pane);
}
else if (kind.equals(AbstractDocument.SectionElementName)) {
//return new ScaledView(elem, View.Y_AXIS);
return new BoxView(elem,View.Y_AXIS);
}
else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
}
else if (kind.equals(StyleConstants.IconElementName)) {
//Element q=getScaledIcon(elem);
return new MyIconView(elem,_kit);
}
}
// default to text display
return new LabelView(elem);
}
public JXMLNotePane getPane() {
return _pane;
}
public XMLNoteViewFactory(JXMLNotePane p,XMLNoteEditorKit kit) {
_pane=p;
_kit=kit;
}
}
class MyIconView extends View {
private XMLNoteEditorKit _kit;
private Icon _icn;
private Icon _orig;
public MyIconView(Element elem,XMLNoteEditorKit kit) {
super(elem);
_kit=kit;
AttributeSet attr = elem.getAttributes();
_orig=StyleConstants.getIcon(attr);
_icn=_kit.getZoomedInstance(_orig);
}
public void paint(Graphics g, Shape a) {
Rectangle alloc = a.getBounds();
_icn=_kit.getZoomedInstance(_orig);
_icn.paintIcon(getContainer(), g, alloc.x, alloc.y);
}
public float getPreferredSpan(int axis) {
_icn=_kit.getZoomedInstance(_orig);
switch (axis) {
case View.X_AXIS:
return _icn.getIconWidth();
case View.Y_AXIS:
return _icn.getIconHeight();
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
public float getAlignment(int axis) {
switch (axis) {
case View.Y_AXIS:
return 1;
default:
return super.getAlignment(axis);
}
}
public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
int p0 = getStartOffset();
int p1 = getEndOffset();
if ((pos >= p0) && (pos <= p1)) {
Rectangle r = a.getBounds();
if (pos == p1) {
r.x += r.width;
}
r.width = 0;
return r;
}
throw new BadLocationException(pos + " not in range " + p0 + "," + p1, pos);
}
public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
Rectangle alloc = (Rectangle) a;
if (x < alloc.x + (alloc.width / 2)) {
bias[0] = Position.Bias.Forward;
return getStartOffset();
}
bias[0] = Position.Bias.Backward;
return getEndOffset();
}
}
class MyParagraphView extends ParagraphView {
JXMLNotePane _pane;
Element _par;
TabSet _tabs;
float _zoom=-1.0f;
protected TabSet getTabSet() {
TabSet ts=StyleConstants.getTabSet(_par.getAttributes());
if (ts!=null) {
float zoom=(float) _pane.getZoomFactor();
if (zoom!=_zoom || _tabs==null) {
TabStop[] stp=new TabStop[ts.getTabCount()];
int i,N;
for(i=0,N=ts.getTabCount();i<N;i++) {
TabStop s=ts.getTab(i);
stp[i]=new TabStop(s.getPosition()*zoom,s.getAlignment(),s.getLeader());
}
_zoom=zoom;
_tabs=new TabSet(stp);
}
return _tabs;
} else {
return null;
}
}
public MyParagraphView(Element p,JXMLNotePane pn) {
super(p);
_pane=pn;
_par=p;
}
}
class MyLabelView extends LabelView {
JXMLNotePane _pane;
Element _elem;
Color _textColor=null;
private Font _myFont=null;
private float _myPoints=-1.0f;
private double _myZoom=-1.0;
private Font _myDerived=null;
public Font getFont() {
Font f=super.getFont();
float ps=f.getSize2D();
double zoom=_pane.getZoomFactor();
if (_myFont==null || ps!=_myPoints || zoom!=_myZoom || _myDerived==null) {
_myFont=f;
_myPoints=ps;
_myZoom=zoom;
_myDerived=_myFont.deriveFont((float) (ps*_myZoom));
}
return _myDerived;
}
// override the foreground colour, if (part of) this text is Marked
protected void setPropertiesFromAttributes() {
super.setPropertiesFromAttributes();
}
public Color getForeground() {
if (_elem!=null) {
AttributeSet set=_elem.getAttributes();
Vector<XMLNoteMark> marks=XMLNoteStyleConstants.getMarks(set);
if (marks!=null && !marks.isEmpty()) {
// catch the first mark. See documentation in XMLNoteDocument.
XMLNoteMark mark=marks.get(0);
MarkMarkupProvider prov=_pane.getMarkMarkupProviderMaker().create(mark.id(), mark.markClass());
_textColor=prov.textColor(mark);
} else {
_textColor=null;
}
}
if (_textColor!=null) {
return _textColor;
} else {
return super.getForeground();
}
}
public MyLabelView(Element e,JXMLNotePane pane) {
super(e);
_elem=e;
_pane=pane;
}
}
/*class MyIconView extends IconView {
JXMLNotePane _pane;
public static Element deriveIconForZoom(Element e,JXMLNotePane p) {
return p.getXMLNoteDoc().deriveIconForZoom(e,p);
}
public MyIconView(Element e,JXMLNotePane pane) {
_pane=pane;
}
}*/
class ScaledView extends BoxView {
public ScaledView(Element elem, int axis) {
super(elem, axis);
}
public double getZoomFactor() {
XMLNoteViewFactory v=(XMLNoteViewFactory) super.getViewFactory();
double scale = v.getPane().getZoomFactor();
return scale;
}
public void paint(Graphics g, Shape allocation) {
Graphics2D g2d = (Graphics2D) g;
double zoomFactor = getZoomFactor();
AffineTransform old = g2d.getTransform();
g2d.scale(zoomFactor, zoomFactor);
super.paint(g2d, allocation);
g2d.setTransform(old);
}
public float getMinimumSpan(int axis) {
float f = super.getMinimumSpan(axis);
f *= getZoomFactor();
return f;
}
public float getMaximumSpan(int axis) {
float f = super.getMaximumSpan(axis);
f *= getZoomFactor();
return f;
}
public float getPreferredSpan(int axis) {
float f = super.getPreferredSpan(axis);
f *= getZoomFactor();
return f;
}
protected void layout(int width, int height) {
double zoom=getZoomFactor();
super.layout((int) (((double) width)/zoom),(int) (((double) height)/zoom));
}
public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
Rectangle alloc;
double zoomFactor = getZoomFactor();
alloc = a.getBounds();
Shape s = super.modelToView(pos, alloc, b);
alloc = s.getBounds();
double x=alloc.getX()*zoomFactor;
double y=alloc.getY()*zoomFactor;
double w=alloc.getWidth()*zoomFactor;
double h=alloc.getHeight()*zoomFactor;
alloc.x=(int) Math.round(x);
alloc.y=(int) Math.round(y);
alloc.width=(int) Math.round(w);
alloc.height=(int) Math.round(h);
return alloc;
}
public int viewToModel(float x, float y, Shape a,Position.Bias[] bias) {
double zoomFactor = getZoomFactor();
Rectangle alloc = a.getBounds();
x /= zoomFactor;
y /= zoomFactor;
double xx=alloc.getX()/zoomFactor;
double yy=alloc.getY()/zoomFactor;
double w=alloc.getWidth()/zoomFactor;
double h=alloc.getHeight()/zoomFactor;
//alloc.x /= zoomFactor;
//alloc.y /= zoomFactor;
//alloc.width /= zoomFactor;
//alloc.height /= zoomFactor;
alloc.x=(int) Math.round(xx);
alloc.y=(int) Math.round(yy);
alloc.width=(int) Math.round(w);
alloc.height=(int) Math.round(h);
return super.viewToModel(x, y, alloc, bias);
}
}