summaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/text
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/text')
-rw-r--r--libjava/classpath/javax/swing/text/AbstractWriter.java3
-rw-r--r--libjava/classpath/javax/swing/text/DefaultCaret.java64
-rw-r--r--libjava/classpath/javax/swing/text/DefaultEditorKit.java551
-rw-r--r--libjava/classpath/javax/swing/text/FieldView.java24
-rw-r--r--libjava/classpath/javax/swing/text/GapContent.java469
-rw-r--r--libjava/classpath/javax/swing/text/JTextComponent.java31
-rw-r--r--libjava/classpath/javax/swing/text/PlainView.java233
-rw-r--r--libjava/classpath/javax/swing/text/StyleContext.java119
-rw-r--r--libjava/classpath/javax/swing/text/TextAction.java104
-rw-r--r--libjava/classpath/javax/swing/text/Utilities.java66
-rw-r--r--libjava/classpath/javax/swing/text/html/HTMLDocument.java154
-rw-r--r--libjava/classpath/javax/swing/text/html/HTMLEditorKit.java5
-rw-r--r--libjava/classpath/javax/swing/text/html/ImageView.java441
-rw-r--r--libjava/classpath/javax/swing/text/html/MinimalHTMLWriter.java452
14 files changed, 1978 insertions, 738 deletions
diff --git a/libjava/classpath/javax/swing/text/AbstractWriter.java b/libjava/classpath/javax/swing/text/AbstractWriter.java
index d5fc395e1ac..8d5a6075df4 100644
--- a/libjava/classpath/javax/swing/text/AbstractWriter.java
+++ b/libjava/classpath/javax/swing/text/AbstractWriter.java
@@ -183,7 +183,8 @@ public abstract class AbstractWriter
if (! elt.isLeaf())
throw new BadLocationException("Element is not a leaf",
elt.getStartOffset());
- return document.getText(elt.getStartOffset(), elt.getEndOffset());
+ return document.getText(elt.getStartOffset(),
+ elt.getEndOffset() - elt.getStartOffset());
}
/**
diff --git a/libjava/classpath/javax/swing/text/DefaultCaret.java b/libjava/classpath/javax/swing/text/DefaultCaret.java
index c9369af2089..4ad204c00c9 100644
--- a/libjava/classpath/javax/swing/text/DefaultCaret.java
+++ b/libjava/classpath/javax/swing/text/DefaultCaret.java
@@ -216,13 +216,26 @@ public class DefaultCaret extends Rectangle
*/
public void propertyChange(PropertyChangeEvent e)
{
- if (e.getPropertyName().equals("document"))
+ String name = e.getPropertyName();
+
+ if (name.equals("document"))
{
Document oldDoc = (Document) e.getOldValue();
oldDoc.removeDocumentListener(documentListener);
Document newDoc = (Document) e.getNewValue();
newDoc.addDocumentListener(documentListener);
}
+ else if (name.equals("editable"))
+ {
+ active = (((Boolean) e.getNewValue()).booleanValue()
+ && textComponent.isEnabled());
+ }
+ else if (name.equals("enabled"))
+ {
+ active = (((Boolean) e.getNewValue()).booleanValue()
+ && textComponent.isEditable());
+ }
+
}
}
@@ -281,8 +294,10 @@ public class DefaultCaret extends Rectangle
/**
* The text component in which this caret is installed.
+ *
+ * (Package private to avoid synthetic accessor method.)
*/
- private JTextComponent textComponent;
+ JTextComponent textComponent;
/**
* Indicates if the selection should be visible or not.
@@ -314,6 +329,12 @@ public class DefaultCaret extends Rectangle
* package private to avoid an accessor method.
*/
boolean visible = false;
+
+ /** Indicates whether the text component where the caret is installed is
+ * editable and enabled. If either of these properties is <code>false</code>
+ * the caret is not drawn.
+ */
+ boolean active = true;
/**
* The current highlight entry.
@@ -388,14 +409,23 @@ public class DefaultCaret extends Rectangle
/**
* Moves the caret position when the mouse is dragged over the text
- * component, modifying the selection accordingly.
+ * component, modifying the selectiony.
+ *
+ * <p>When the text component where the caret is installed is disabled,
+ * the selection is not change but you can still scroll the text and
+ * update the caret's location.</p>
*
* @param event the <code>MouseEvent</code> describing the drag operation
*/
public void mouseDragged(MouseEvent event)
{
if (event.getButton() == MouseEvent.BUTTON1)
- moveCaret(event);
+ {
+ if (textComponent.isEnabled())
+ moveCaret(event);
+ else
+ positionCaret(event);
+ }
}
/**
@@ -426,6 +456,10 @@ public class DefaultCaret extends Rectangle
*/
public void mouseClicked(MouseEvent event)
{
+ // Do not modify selection if component is disabled.
+ if (!textComponent.isEnabled())
+ return;
+
int count = event.getClickCount();
if (event.getButton() == MouseEvent.BUTTON1 && count >= 2)
@@ -523,7 +557,7 @@ public class DefaultCaret extends Rectangle
// implemented (in regard to text components):
// - a left-click moves the caret
// - a left-click when shift is held down expands the selection
- // - a right-click or click with any additionaly mouse button
+ // - a right-click or click with any additional mouse button
// on a text component is ignored
// - a middle-click positions the caret and pastes the clipboard
// contents.
@@ -540,6 +574,7 @@ public class DefaultCaret extends Rectangle
else
{
positionCaret(event);
+
textComponent.paste();
}
else
@@ -564,8 +599,11 @@ public class DefaultCaret extends Rectangle
*/
public void focusGained(FocusEvent event)
{
- setVisible(true);
- updateTimerStatus();
+ if (textComponent.isEditable())
+ {
+ setVisible(true);
+ updateTimerStatus();
+ }
}
/**
@@ -575,9 +613,10 @@ public class DefaultCaret extends Rectangle
*/
public void focusLost(FocusEvent event)
{
- if (event.isTemporary() == false)
+ if (textComponent.isEditable() && event.isTemporary() == false)
{
setVisible(false);
+
// Stop the blinker, if running.
if (blinkTimer != null && blinkTimer.isRunning())
blinkTimer.stop();
@@ -670,6 +709,7 @@ public class DefaultCaret extends Rectangle
textComponent.addPropertyChangeListener(propertyChangeListener);
documentListener = new DocumentHandler();
textComponent.getDocument().addDocumentListener(documentListener);
+ active = textComponent.isEditable() && textComponent.isEnabled();
repaint();
}
@@ -872,7 +912,7 @@ public class DefaultCaret extends Rectangle
}
// Now draw the caret on the new position if visible.
- if (visible)
+ if (visible && active)
{
g.setColor(textComponent.getCaretColor());
g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height - 1);
@@ -1013,7 +1053,9 @@ public class DefaultCaret extends Rectangle
this.dot = Math.max(this.dot, 0);
handleHighlight();
+
appear();
+
adjustVisibility(this);
}
}
@@ -1050,7 +1092,9 @@ public class DefaultCaret extends Rectangle
this.mark = this.dot;
clearHighlight();
+
appear();
+
adjustVisibility(this);
}
}
@@ -1104,7 +1148,7 @@ public class DefaultCaret extends Rectangle
*/
public boolean isVisible()
{
- return visible;
+ return visible && active;
}
/**
diff --git a/libjava/classpath/javax/swing/text/DefaultEditorKit.java b/libjava/classpath/javax/swing/text/DefaultEditorKit.java
index 1b686182b6a..8602e69f8e7 100644
--- a/libjava/classpath/javax/swing/text/DefaultEditorKit.java
+++ b/libjava/classpath/javax/swing/text/DefaultEditorKit.java
@@ -52,6 +52,7 @@ import java.io.Reader;
import java.io.Writer;
import javax.swing.Action;
+import javax.swing.SwingConstants;
/**
* The default implementation of {@link EditorKit}. This <code>EditorKit</code>
@@ -60,6 +61,7 @@ import javax.swing.Action;
*
* @author original author unknown
* @author Roman Kennke (roman@kennke.org)
+ * @author Robert Schuster (robertschuster@fsfe.org)
*/
public class DefaultEditorKit extends EditorKit
{
@@ -123,6 +125,122 @@ public class DefaultEditorKit extends EditorKit
}
}
+ static class SelectionBeginWordAction extends TextAction
+ {
+ SelectionBeginWordAction()
+ {
+ super(selectionBeginWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getWordStart(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.moveDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class SelectionEndWordAction extends TextAction
+ {
+ SelectionEndWordAction()
+ {
+ super(selectionEndWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getWordEnd(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.moveDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class BeginWordAction extends TextAction
+ {
+ BeginWordAction()
+ {
+ super(beginWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getWordStart(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class EndWordAction extends TextAction
+ {
+ EndWordAction()
+ {
+ super(endWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getWordEnd(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
static class PreviousWordAction
extends TextAction
{
@@ -258,336 +376,260 @@ public class DefaultEditorKit extends EditorKit
}
}
}
-
- static class SelectionEndLineAction
- extends TextAction
+
+ static class SelectionBeginLineAction
+ extends TextAction
{
- SelectionEndLineAction()
+
+ SelectionBeginLineAction()
{
- super(selectionEndLineAction);
+ super(selectionBeginLineAction);
}
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- try
- {
- Point p = t.modelToView(t.getCaret().getDot()).getLocation();
- int cur = t.getCaretPosition();
- int y = p.y;
- int length = t.getDocument().getLength();
- while (y == p.y && cur < length)
- y = t.modelToView(++cur).getLocation().y;
- if (cur != length)
- cur--;
-
- Caret c = t.getCaret();
- c.moveDot(cur);
- c.setMagicCaretPosition(t.modelToView(cur).getLocation());
- }
- catch (BadLocationException ble)
- {
- // Nothing to do here
- }
+ Caret c = t.getCaret();
+ try
+ {
+ int offs = Utilities.getRowStart(t, c.getDot());
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+
}
}
- static class SelectionBeginLineAction
+ static class SelectionEndLineAction
extends TextAction
{
- SelectionBeginLineAction()
+ SelectionEndLineAction()
{
- super(selectionBeginLineAction);
+ super(selectionEndLineAction);
}
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
-
+ Caret c = t.getCaret();
try
- {
- // TODO: There is a more efficent solution, but
- // viewToModel doesn't work properly.
- Point p = t.modelToView(t.getCaret().getDot()).getLocation();
-
- int cur = t.getCaretPosition();
- int y = p.y;
-
- while (y == p.y && cur > 0)
- y = t.modelToView(--cur).getLocation().y;
- if (cur != 0)
- cur++;
-
- Caret c = t.getCaret();
- c.moveDot(cur);
- c.setMagicCaretPosition(t.modelToView(cur).getLocation());
- }
- catch (BadLocationException ble)
- {
- // Do nothing here.
- }
+ {
+ int offs = Utilities.getRowEnd(t, c.getDot());
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+
}
}
-
- static class SelectionDownAction
- extends TextAction
+
+ static class SelectLineAction extends TextAction
{
- SelectionDownAction()
+ SelectLineAction()
{
- super(selectionDownAction);
+ super(selectLineAction);
}
-
+
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
+ Caret c = t.getCaret();
try
{
- if (t != null)
- {
- Caret c = t.getCaret();
- // The magic caret position may be null when the caret
- // has not moved yet.
- Point mcp = c.getMagicCaretPosition();
- int x = (mcp != null) ? mcp.x : 0;
- int pos = Utilities.getPositionBelow(t, t.getCaretPosition(), x);
-
- if (pos > -1)
- t.moveCaretPosition(pos);
- }
+ int offs1 = Utilities.getRowStart(t, c.getDot());
+ int offs2 = Utilities.getRowEnd(t, c.getDot());
+
+ c.setDot(offs2);
+ c.moveDot(offs1);
+
+ c.setMagicCaretPosition(t.modelToView(offs2).getLocation());
}
- catch(BadLocationException ble)
+ catch(BadLocationException ble)
{
- // FIXME: Swallowing allowed?
+ // Can't happen.
}
}
}
-
- static class SelectionUpAction
- extends TextAction
+
+ static class SelectWordAction extends TextAction
{
- SelectionUpAction()
+ SelectWordAction()
{
- super(selectionUpAction);
+ super(selectWordAction);
}
-
+
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
+ Caret c = t.getCaret();
+ int dot = c.getDot();
+
try
{
- if (t != null)
+ int wordStart = Utilities.getWordStart(t, dot);
+
+ if (dot == wordStart)
{
- Caret c = t.getCaret();
- // The magic caret position may be null when the caret
- // has not moved yet.
- Point mcp = c.getMagicCaretPosition();
- int x = (mcp != null) ? mcp.x : 0;
- int pos = Utilities.getPositionAbove(t, t.getCaretPosition(), x);
+ // Current cursor position is on the first character in a word.
+ c.setDot(wordStart);
+ c.moveDot(Utilities.getWordEnd(t, wordStart));
+ }
+ else
+ {
+ // Current cursor position is not on the first character
+ // in a word.
+ int nextWord = Utilities.getNextWord(t, dot);
+ int previousWord = Utilities.getPreviousWord(t, dot);
+ int previousWordEnd = Utilities.getWordEnd(t, previousWord);
- if (pos > -1)
- t.moveCaretPosition(pos);
+ // Cursor position is in the space between two words. In such a
+ // situation just select the space.
+ if (dot >= previousWordEnd && dot <= nextWord)
+ {
+ c.setDot(previousWordEnd);
+ c.moveDot(nextWord);
+ }
+ else
+ {
+ // Cursor position is inside a word. Just select it then.
+ c.setDot(previousWord);
+ c.moveDot(previousWordEnd);
+ }
}
+
+ // If the position was updated change the magic caret position
+ // as well.
+ if (c.getDot() != dot)
+ c.setMagicCaretPosition(t.modelToView(c.getDot()).getLocation());
+
}
- catch(BadLocationException ble)
+ catch(BadLocationException ble)
{
- // FIXME: Swallowing allowed?
+ // Can't happen.
}
}
}
+ static class SelectionDownAction
+ extends TextAction.VerticalMovementAction
+ {
+ SelectionDownAction()
+ {
+ super(selectionDownAction, SwingConstants.SOUTH);
+ }
+
+ protected void actionPerformedImpl(Caret c, int offs)
+ {
+ c.moveDot(offs);
+ }
+
+ }
+
+ static class SelectionUpAction
+ extends TextAction.VerticalMovementAction
+ {
+ SelectionUpAction()
+ {
+ super(selectionUpAction, SwingConstants.NORTH);
+ }
+
+ protected void actionPerformedImpl(Caret c, int offs)
+ {
+ c.moveDot(offs);
+ }
+
+ }
+
static class SelectionForwardAction
- extends TextAction
+ extends TextAction.HorizontalMovementAction
{
SelectionForwardAction()
{
- super(selectionForwardAction);
+ super(selectionForwardAction, SwingConstants.EAST);
}
- public void actionPerformed(ActionEvent event)
+ protected void actionPerformedImpl(Caret c, int offs)
{
- JTextComponent t = getTextComponent(event);
- if (t != null)
- {
- int offs = t.getCaretPosition() + 1;
-
- if(offs <= t.getDocument().getLength())
- {
- Caret c = t.getCaret();
- c.moveDot(offs);
- try
- {
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
- }
- catch(BadLocationException ble)
- {
- // Can't happen.
- }
- }
- }
+ c.moveDot(offs);
}
}
static class SelectionBackwardAction
- extends TextAction
+ extends TextAction.HorizontalMovementAction
{
SelectionBackwardAction()
{
- super(selectionBackwardAction);
+ super(selectionBackwardAction, SwingConstants.WEST);
}
- public void actionPerformed(ActionEvent event)
+ protected void actionPerformedImpl(Caret c, int offs)
{
- JTextComponent t = getTextComponent(event);
- if (t != null)
- {
- int offs = t.getCaretPosition() - 1;
-
- if(offs >= 0)
- {
- Caret c = t.getCaret();
- c.moveDot(offs);
- try
- {
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
- }
- catch(BadLocationException ble)
- {
- // Can't happen.
- }
- }
- }
+ c.moveDot(offs);
}
}
static class DownAction
- extends TextAction
+ extends TextAction.VerticalMovementAction
{
DownAction()
{
- super(downAction);
+ super(downAction, SwingConstants.SOUTH);
}
- public void actionPerformed(ActionEvent event)
+ protected void actionPerformedImpl(Caret c, int offs)
{
- JTextComponent t = getTextComponent(event);
- try
- {
- if (t != null)
- {
- Caret c = t.getCaret();
- // The magic caret position may be null when the caret
- // has not moved yet.
- Point mcp = c.getMagicCaretPosition();
- int x = (mcp != null) ? mcp.x : 0;
- int pos = Utilities.getPositionBelow(t, t.getCaretPosition(), x);
-
- if (pos > -1)
- t.setCaretPosition(pos);
- }
- }
- catch(BadLocationException ble)
- {
- // FIXME: Swallowing allowed?
- }
+ c.setDot(offs);
}
}
static class UpAction
- extends TextAction
+ extends TextAction.VerticalMovementAction
{
UpAction()
{
- super(upAction);
+ super(upAction, SwingConstants.NORTH);
}
- public void actionPerformed(ActionEvent event)
+ protected void actionPerformedImpl(Caret c, int offs)
{
- JTextComponent t = getTextComponent(event);
- try
- {
- if (t != null)
- {
- Caret c = t.getCaret();
- // The magic caret position may be null when the caret
- // has not moved yet.
- Point mcp = c.getMagicCaretPosition();
- int x = (mcp != null) ? mcp.x : 0;
- int pos = Utilities.getPositionAbove(t, t.getCaretPosition(), x);
-
- if (pos > -1)
- t.setCaretPosition(pos);
- }
- }
- catch(BadLocationException ble)
- {
- // FIXME: Swallowing allowed?
- }
+ c.setDot(offs);
}
+
}
static class ForwardAction
- extends TextAction
+ extends TextAction.HorizontalMovementAction
{
ForwardAction()
{
- super(forwardAction);
+ super(forwardAction, SwingConstants.EAST);
}
- public void actionPerformed(ActionEvent event)
+ protected void actionPerformedImpl(Caret c, int offs)
{
- JTextComponent t = getTextComponent(event);
- if (t != null)
- {
- int offs = t.getCaretPosition() + 1;
- if (offs <= t.getDocument().getLength())
- {
- Caret c = t.getCaret();
- c.setDot(offs);
-
- try
- {
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
- }
- catch (BadLocationException ble)
- {
- // Should not happen.
- }
- }
- }
-
+ c.setDot(offs);
}
+
}
static class BackwardAction
- extends TextAction
+ extends TextAction.HorizontalMovementAction
{
BackwardAction()
{
- super(backwardAction);
+ super(backwardAction, SwingConstants.WEST);
}
- public void actionPerformed(ActionEvent event)
+ protected void actionPerformedImpl(Caret c, int offs)
{
- JTextComponent t = getTextComponent(event);
- if (t != null)
- {
- int offs = t.getCaretPosition() - 1;
- if (offs >= 0)
- {
- Caret c = t.getCaret();
- c.setDot(offs);
-
- try
- {
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
- }
- catch (BadLocationException ble)
- {
- // Should not happen.
- }
- }
- }
+ c.setDot(offs);
}
+
}
static class DeletePrevCharAction
@@ -720,6 +762,55 @@ public class DefaultEditorKit extends EditorKit
}
}
+ static class BeginAction extends TextAction
+ {
+
+ BeginAction()
+ {
+ super(beginAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ Caret c = t.getCaret();
+ c.setDot(0);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(0).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class EndAction extends TextAction
+ {
+
+ EndAction()
+ {
+ super(endAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ int offs = t.getDocument().getLength();
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
/**
* Creates a beep on the PC speaker.
*
@@ -867,8 +958,8 @@ public class DefaultEditorKit extends EditorKit
// first we filter the following events:
// - control characters
// - key events with the ALT modifier (FIXME: filter that too!)
- char c = event.getActionCommand().charAt(0);
- if (Character.isISOControl(c))
+ int cp = event.getActionCommand().codePointAt(0);
+ if (Character.isISOControl(cp))
return;
JTextComponent t = getTextComponent(event);
@@ -1345,8 +1436,6 @@ public class DefaultEditorKit extends EditorKit
* The <code>Action</code>s that are supported by the
* <code>DefaultEditorKit</code>.
*/
- // TODO: All these inner classes look ugly. Maybe work out a better way
- // to handle this.
private static Action[] defaultActions =
new Action[] {
// These classes are public because they are so in the RI.
@@ -1387,9 +1476,21 @@ public class DefaultEditorKit extends EditorKit
new PreviousWordAction(),
new SelectionPreviousWordAction(),
+ new BeginAction(),
new SelectionBeginAction(),
+
+ new EndAction(),
new SelectionEndAction(),
+
+ new BeginWordAction(),
+ new SelectionBeginWordAction(),
+
+ new EndWordAction(),
+ new SelectionEndWordAction(),
+
new SelectAllAction(),
+ new SelectLineAction(),
+ new SelectWordAction()
};
/**
diff --git a/libjava/classpath/javax/swing/text/FieldView.java b/libjava/classpath/javax/swing/text/FieldView.java
index 0c2f0fef156..f41f9013092 100644
--- a/libjava/classpath/javax/swing/text/FieldView.java
+++ b/libjava/classpath/javax/swing/text/FieldView.java
@@ -50,6 +50,7 @@ import java.awt.event.ActionListener;
import javax.swing.BoundedRangeModel;
import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
@@ -241,12 +242,29 @@ public class FieldView extends PlainView
Shape newAlloc = adjustAllocation(s);
- // Set a clip to prevent drawing outside of the allocation area.
- // TODO: Is there a better way to achieve this?
Shape clip = g.getClip();
- g.setClip(s);
+ if (clip != null)
+ {
+ // Reason for this: The allocation area is always determined by the
+ // size of the component (and its insets) regardless of whether
+ // parts of the component are invisible or not (e.g. when the
+ // component is part of a JScrollPane and partly moved out of
+ // the user-visible range). However the clip of the Graphics
+ // instance may be adjusted properly to that condition but
+ // does not handle insets. By calculating the intersection
+ // we get the correct clip to paint the text in all cases.
+ Rectangle r = s.getBounds();
+ Rectangle cb = clip.getBounds();
+ SwingUtilities.computeIntersection(r.x, r.y, r.width, r.height, cb);
+
+ g.setClip(cb);
+ }
+ else
+ g.setClip(s);
+
super.paint(g, newAlloc);
g.setClip(clip);
+
}
public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
diff --git a/libjava/classpath/javax/swing/text/GapContent.java b/libjava/classpath/javax/swing/text/GapContent.java
index 219accb4056..1780d7ddfad 100644
--- a/libjava/classpath/javax/swing/text/GapContent.java
+++ b/libjava/classpath/javax/swing/text/GapContent.java
@@ -39,13 +39,10 @@ exception statement from your version. */
package javax.swing.text;
import java.io.Serializable;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.Iterator;
-import java.util.ListIterator;
+import java.util.Set;
import java.util.Vector;
+import java.util.WeakHashMap;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
@@ -60,8 +57,6 @@ import javax.swing.undo.UndoableEdit;
* minimal (simple array access). The array only has to be shifted around when
* the insertion point moves (then the gap also moves and one array copy is
* necessary) or when the gap is filled up and the buffer has to be enlarged.
- *
- * TODO: Implement UndoableEdit support stuff
*/
public class GapContent
implements AbstractDocument.Content, Serializable
@@ -71,11 +66,14 @@ public class GapContent
* A {@link Position} implementation for <code>GapContent</code>.
*/
private class GapContentPosition
- implements Position, Comparable
+ implements Position
{
- /** The index within the buffer array. */
- int mark;
+ /**
+ * The index to the positionMarks array entry, which in turn holds the
+ * mark into the buffer array.
+ */
+ int index;
/**
* Creates a new GapContentPosition object.
@@ -84,33 +82,20 @@ public class GapContent
*/
GapContentPosition(int mark)
{
- this.mark = mark;
- }
-
- /**
- * Comparable interface implementation. This is used to store all
- * positions in an ordered fashion.
- *
- * @param o the object to be compared to this
- *
- * @return a negative integer if this is less than <code>o</code>, zero
- * if both are equal or a positive integer if this is greater than
- * <code>o</code>
- *
- * @throws ClassCastException if <code>o</code> is not a
- * GapContentPosition or Integer object
- */
- public int compareTo(Object o)
- {
- if (o instanceof Integer)
+ // Try to find the mark in the positionMarks array, and store the index
+ // to it.
+ synchronized (GapContent.this)
{
- int otherMark = ((Integer) o).intValue();
- return mark - otherMark;
- }
- else
- {
- GapContentPosition other = (GapContentPosition) o;
- return mark - other.mark;
+ int i = binarySearch(positionMarks, mark, numMarks);
+ if (i >= 0) // mark found
+ {
+ index = i;
+ }
+ else
+ {
+ index = -i - 1;
+ insertMark(index, mark);
+ }
}
}
@@ -121,14 +106,19 @@ public class GapContent
*/
public int getOffset()
{
- // Check precondition.
- assert mark <= gapStart || mark >= gapEnd : "mark: " + mark
- + ", gapStart: " + gapStart
- + ", gapEnd: " + gapEnd;
- if (mark <= gapStart)
- return mark;
- else
- return mark - (gapEnd - gapStart);
+ synchronized (GapContent.this)
+ {
+ // Fetch the actual mark.
+ int mark = positionMarks[index];
+ // Check precondition.
+ assert mark <= gapStart || mark >= gapEnd : "mark: " + mark
+ + ", gapStart: " + gapStart
+ + ", gapEnd: " + gapEnd;
+ int res = mark;
+ if (mark > gapStart)
+ res -= (gapEnd - gapStart);
+ return res;
+ }
}
}
@@ -209,40 +199,6 @@ public class GapContent
}
- /**
- * Compares WeakReference objects in a List by comparing the referenced
- * objects instead.
- *
- * @author Roman Kennke (kennke@aicas.com)
- */
- private class WeakPositionComparator
- implements Comparator
- {
-
- /**
- * Compares two objects of type WeakReference. The objects are compared
- * using the referenced objects compareTo() method.
- */
- public int compare(Object o1, Object o2)
- {
- // Unwrap references.
- if (o1 instanceof WeakReference)
- o1 = ((WeakReference) o1).get();
- if (o2 instanceof WeakReference)
- o2 = ((WeakReference) o2).get();
-
- GapContentPosition p1 = (GapContentPosition) o1;
- GapContentPosition p2 = (GapContentPosition) o2;
-
- int retVal;
- if (p1 == null || p2 == null)
- retVal = -1;
- else
- retVal = p1.compareTo(p2);
- return retVal;
- }
- }
-
/** The serialization UID (compatible with JDK1.5). */
private static final long serialVersionUID = -6226052713477823730L;
@@ -267,12 +223,26 @@ public class GapContent
*/
int gapEnd;
+ // FIXME: We might want to track GC'ed GapContentPositions and remove their
+ // corresponding marks, or alternativly, perform some regular cleanup of
+ // the positionMarks array.
+
+ /**
+ * Holds the marks for positions. These marks are referenced by the
+ * GapContentPosition instances by an index into this array.
+ */
+ int[] positionMarks;
+
/**
- * The positions generated by this GapContent. They are kept in an ordered
- * fashion, so they can be looked up easily. The value objects will be
- * WeakReference objects that in turn hold GapContentPosition objects.
+ * The number of elements in the positionMarks array. The positionMarks array
+ * might be bigger than the actual number of elements.
*/
- private ArrayList positions;
+ int numMarks;
+
+ /**
+ * (Weakly) Stores the GapContentPosition instances.
+ */
+ WeakHashMap positions;
/**
* Creates a new GapContent object.
@@ -294,7 +264,9 @@ public class GapContent
gapStart = 1;
gapEnd = size;
buffer[0] = '\n';
- positions = new ArrayList();
+ positions = new WeakHashMap();
+ positionMarks = new int[10];
+ numMarks = 0;
}
/**
@@ -483,26 +455,30 @@ public class GapContent
*/
public Position createPosition(final int offset) throws BadLocationException
{
- if (offset < 0 || offset > length())
- throw new BadLocationException("The offset was out of the bounds of this"
- + " buffer", offset);
-
- clearPositionReferences();
-
- // We store the actual array index in the GapContentPosition. The real
- // offset is then calculated in the GapContentPosition.
- int mark = offset;
- if (offset >= gapStart)
- mark += gapEnd - gapStart;
- GapContentPosition pos = new GapContentPosition(mark);
- WeakReference r = new WeakReference(pos);
-
- // Add this into our list in a sorted fashion.
- int index = Collections.binarySearch(positions, r,
- new WeakPositionComparator());
- if (index < 0)
- index = -(index + 1);
- positions.add(index, r);
+ // We try to find a GapContentPosition at the specified offset and return
+ // that. Otherwise we must create a new one.
+ GapContentPosition pos = null;
+ Set positionSet = positions.keySet();
+ for (Iterator i = positionSet.iterator(); i.hasNext();)
+ {
+ GapContentPosition p = (GapContentPosition) i.next();
+ if (p.getOffset() == offset)
+ {
+ pos = p;
+ break;
+ }
+ }
+
+ // If none was found, then create and return a new one.
+ if (pos == null)
+ {
+ int mark = offset;
+ if (mark >= gapStart)
+ mark += (gapEnd - gapStart);
+ pos = new GapContentPosition(mark);
+ positions.put(pos, null);
+ }
+
return pos;
}
@@ -542,7 +518,6 @@ public class GapContent
{
if (newGapStart == gapStart)
return;
-
int newGapEnd = newGapStart + gapEnd - gapStart;
if (newGapStart < gapStart)
{
@@ -583,7 +558,7 @@ public class GapContent
assert newGapStart < gapStart : "The new gap start must be less than the "
+ "old gap start.";
- setPositionsInRange(newGapStart, gapStart - newGapStart, gapStart);
+ setPositionsInRange(newGapStart, gapStart, false);
gapStart = newGapStart;
}
@@ -602,7 +577,7 @@ public class GapContent
assert newGapEnd > gapEnd : "The new gap end must be greater than the "
+ "old gap end.";
- setPositionsInRange(gapEnd, newGapEnd - gapEnd, newGapEnd);
+ setPositionsInRange(gapEnd, newGapEnd, false);
gapEnd = newGapEnd;
}
@@ -688,85 +663,79 @@ public class GapContent
else
res.clear();
- int endOffset = offset + length;
-
- int index1 = Collections.binarySearch(positions,
- new GapContentPosition(offset),
- new WeakPositionComparator());
- if (index1 < 0)
- index1 = -(index1 + 1);
-
- // Search the first index with the specified offset. The binarySearch does
- // not necessarily find the first one.
- while (index1 > 0)
- {
- WeakReference r = (WeakReference) positions.get(index1 - 1);
- GapContentPosition p = (GapContentPosition) r.get();
- if (p != null && p.mark == offset || p == null)
- index1--;
- else
- break;
- }
+ int endOffs = offset + length;
- for (ListIterator i = positions.listIterator(index1); i.hasNext();)
+ Set positionSet = positions.keySet();
+ for (Iterator i = positionSet.iterator(); i.hasNext();)
{
- WeakReference r = (WeakReference) i.next();
- GapContentPosition p = (GapContentPosition) r.get();
- if (p == null)
- continue;
-
- if (p.mark > endOffset)
- break;
- if (p.mark >= offset && p.mark <= endOffset)
+ GapContentPosition p = (GapContentPosition) i.next();
+ int offs = p.getOffset();
+ if (offs >= offset && offs < endOffs)
res.add(p);
}
+
return res;
}
/**
- * Sets the mark of all <code>Position</code>s that are in the range
- * specified by <code>offset</code> and </code>length</code> within
- * the buffer array to <code>value</code>
+ * Crunches all positions in the specified range to either the start or
+ * end of that interval. The interval boundaries are meant to be inclusive
+ * [start, end].
*
- * @param offset the start offset of the range to search
- * @param length the length of the range to search
- * @param value the new value for each mark
+ * @param start the start offset of the range
+ * @param end the end offset of the range
+ * @param toStart a boolean indicating if the positions should be crunched
+ * to the start (true) or to the end (false)
*/
- private void setPositionsInRange(int offset, int length, int value)
+ private void setPositionsInRange(int start, int end, boolean toStart)
{
- int endOffset = offset + length;
-
- int index1 = Collections.binarySearch(positions,
- new GapContentPosition(offset),
- new WeakPositionComparator());
- if (index1 < 0)
- index1 = -(index1 + 1);
-
- // Search the first index with the specified offset. The binarySearch does
- // not necessarily find the first one.
- while (index1 > 0)
+ // We slump together all the GapContentPositions to point to
+ // one mark. So this is implemented as follows:
+ // 1. Remove all the marks in the specified range.
+ // 2. Insert one new mark at the correct location.
+ // 3. Adjust all affected GapContentPosition instances to point to
+ // this new mark.
+
+ synchronized (this)
{
- WeakReference r = (WeakReference) positions.get(index1 - 1);
- GapContentPosition p = (GapContentPosition) r.get();
- if (p != null && p.mark == offset || p == null)
- index1--;
+ int startIndex = binarySearch(positionMarks, start, numMarks);
+ if (startIndex < 0) // Translate to insertion index, if not found.
+ startIndex = - startIndex - 1;
+ int endIndex = binarySearch(positionMarks, end, numMarks);
+ if (endIndex < 0) // Translate to insertion index - 1, if not found.
+ endIndex = - endIndex - 2;
+
+ // Update the marks.
+ // We have inclusive interval bounds, but let one element over for
+ // filling in the new value.
+ int removed = endIndex - startIndex;
+ if (removed <= 0)
+ return;
+ System.arraycopy(positionMarks, endIndex + 1, positionMarks,
+ startIndex + 1, positionMarks.length - endIndex - 1);
+ numMarks -= removed;
+ if (toStart)
+ {
+ positionMarks[startIndex] = start;
+ }
else
- break;
- }
-
- for (ListIterator i = positions.listIterator(index1); i.hasNext();)
- {
- WeakReference r = (WeakReference) i.next();
- GapContentPosition p = (GapContentPosition) r.get();
- if (p == null)
- continue;
-
- if (p.mark > endOffset)
- break;
-
- if (p.mark >= offset && p.mark <= endOffset)
- p.mark = value;
- }
+ {
+ positionMarks[startIndex] = end;
+ }
+
+ // Update all affected GapContentPositions to point to the new index
+ // and all GapContentPositions that come after the interval to
+ // have their index moved by -removed.
+ Set positionSet = positions.keySet();
+ for (Iterator i = positionSet.iterator(); i.hasNext();)
+ {
+ GapContentPosition p = (GapContentPosition) i.next();
+ if (p.index > startIndex || p.index <= endIndex)
+ p.index = startIndex;
+ else if (p.index > endIndex)
+ p.index -= removed;
+ }
+ }
}
/**
@@ -780,39 +749,44 @@ public class GapContent
*/
private void adjustPositionsInRange(int offset, int length, int incr)
{
- int endOffset = offset + length;
+ int endMark = offset + length;
- int index1 = Collections.binarySearch(positions,
- new GapContentPosition(offset),
- new WeakPositionComparator());
- if (index1 < 0)
- index1 = -(index1 + 1);
-
- // Search the first index with the specified offset. The binarySearch does
- // not necessarily find the first one.
- while (index1 > 0)
+ synchronized (this)
{
- WeakReference r = (WeakReference) positions.get(index1 - 1);
- GapContentPosition p = (GapContentPosition) r.get();
- if (p != null && p.mark == offset || p == null)
- index1--;
- else
- break;
+ // Find the start and end indices in the positionMarks array.
+ int startIndex = binarySearch(positionMarks, offset, numMarks);
+ if (startIndex < 0) // Translate to insertion index, if not found.
+ startIndex = - startIndex - 1;
+ int endIndex = binarySearch(positionMarks, endMark, numMarks);
+ if (endIndex < 0) // Translate to insertion index - 1, if not found.
+ endIndex = - endIndex - 2;
+
+ // We must not change the order of the marks, this would have
+ // unpredictable results while binary-searching the marks.
+ assert (startIndex <= 0
+ || positionMarks[startIndex - 1]
+ <= positionMarks [startIndex] + incr)
+ && (endIndex >= numMarks - 1
+ || positionMarks[endIndex + 1]
+ >= positionMarks[endIndex] + incr)
+ : "Adjusting the marks must not change their order";
+
+ // Some debug helper output to determine if the start or end of the
+ // should ever be coalesced together with adjecent marks.
+ if (startIndex > 0 && positionMarks[startIndex - 1]
+ == positionMarks[startIndex] + incr)
+ System.err.println("DEBUG: We could coalesce the start of the region"
+ + " in GapContent.adjustPositionsInRange()");
+ if (endIndex < numMarks - 1 && positionMarks[endIndex + 1]
+ == positionMarks[endIndex] + incr)
+ System.err.println("DEBUG: We could coalesce the end of the region"
+ + " in GapContent.adjustPositionsInRange()");
+
+ // Actually adjust the marks.
+ for (int i = startIndex; i <= endIndex; i++)
+ positionMarks[i] += incr;
}
- for (ListIterator i = positions.listIterator(index1); i.hasNext();)
- {
- WeakReference r = (WeakReference) i.next();
- GapContentPosition p = (GapContentPosition) r.get();
- if (p == null)
- continue;
-
- if (p.mark > endOffset)
- break;
-
- if (p.mark >= offset && p.mark <= endOffset)
- p.mark += incr;
- }
}
/**
@@ -826,7 +800,7 @@ public class GapContent
if (gapStart != 0)
return;
- setPositionsInRange(gapEnd, 0, 0);
+ positionMarks[0] = 0;
}
/**
@@ -866,27 +840,94 @@ public class GapContent
System.err.println();
}
- private void dumpPositions()
+ /**
+ * Prints out the position marks.
+ */
+ private void dumpMarks()
+ {
+ System.err.print("positionMarks: ");
+ for (int i = 0; i < numMarks; i++)
+ System.err.print(positionMarks[i] + ", ");
+ System.err.println();
+ }
+
+ /**
+ * Inserts a mark into the positionMarks array. This must update all the
+ * GapContentPosition instances in positions that come after insertionPoint.
+ *
+ * This is package private to avoid synthetic accessor methods.
+ *
+ * @param insertionPoint the index at which to insert the mark
+ * @param mark the mark to insert
+ */
+ void insertMark(int insertionPoint, int mark)
{
- for (Iterator i = positions.iterator(); i.hasNext();)
+ synchronized (this)
{
- WeakReference r = (WeakReference) i.next();
- GapContentPosition pos = (GapContentPosition) r.get();
- System.err.println("position at: " + pos.mark);
+ // Update the positions.
+ Set positionSet = positions.keySet();
+ for (Iterator i = positionSet.iterator(); i.hasNext();)
+ {
+ GapContentPosition p = (GapContentPosition) i.next();
+ if (p.index >= insertionPoint)
+ p.index++;
+ }
+
+ // Update the position marks.
+ if (positionMarks.length <= numMarks)
+ {
+ int[] newMarks = new int[positionMarks.length + 10];
+ System.arraycopy(positionMarks, 0, newMarks, 0, insertionPoint);
+ newMarks[insertionPoint] = mark;
+ System.arraycopy(positionMarks, insertionPoint, newMarks,
+ insertionPoint + 1,
+ numMarks - insertionPoint);
+ positionMarks = newMarks;
+ }
+ else
+ {
+ System.arraycopy(positionMarks, insertionPoint, positionMarks,
+ insertionPoint + 1,
+ numMarks - insertionPoint);
+ positionMarks[insertionPoint] = mark;
+ }
+ numMarks++;
}
}
/**
- * Clears all GC'ed references in the positions array.
+ * An adaption of {@link java.util.Arrays#binarySearch(int[], int)} to
+ * specify a maximum index up to which the array is searched. This allows
+ * us to have some trailing entries that we ignore.
+ *
+ * This is package private to avoid synthetic accessor methods.
+ *
+ * @param a the array
+ * @param key the key to search for
+ * @param maxIndex the maximum index up to which the search is performed
+ *
+ * @return the index of the found entry, or (-(index)-1) for the
+ * insertion point when not found
+ *
+ * @see java.util.Arrays#binarySearch(int[], int)
*/
- private void clearPositionReferences()
+ int binarySearch(int[] a, int key, int maxIndex)
{
- Iterator i = positions.iterator();
- while (i.hasNext())
+ int low = 0;
+ int hi = maxIndex - 1;
+ int mid = 0;
+ while (low <= hi)
{
- WeakReference r = (WeakReference) i.next();
- if (r.get() == null)
- i.remove();
+ mid = (low + hi) >> 1;
+ final int d = a[mid];
+ if (d == key)
+ return mid;
+ else if (d > key)
+ hi = mid - 1;
+ else
+ // This gets the insertion point right on the last loop.
+ low = ++mid;
}
+ return -mid - 1;
}
}
diff --git a/libjava/classpath/javax/swing/text/JTextComponent.java b/libjava/classpath/javax/swing/text/JTextComponent.java
index 1103de9b473..9de151dfbac 100644
--- a/libjava/classpath/javax/swing/text/JTextComponent.java
+++ b/libjava/classpath/javax/swing/text/JTextComponent.java
@@ -38,6 +38,8 @@ exception statement from your version. */
package javax.swing.text;
+import gnu.classpath.NotImplementedException;
+
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Dimension;
@@ -176,6 +178,7 @@ public abstract class JTextComponent extends JComponent
* @param e - caret event
*/
public void caretUpdate(CaretEvent e)
+ throws NotImplementedException
{
// TODO: fire appropriate event.
dot = e.getDot();
@@ -187,6 +190,7 @@ public abstract class JTextComponent extends JComponent
* @return the accessible state set of this component
*/
public AccessibleStateSet getAccessibleStateSet()
+ throws NotImplementedException
{
AccessibleStateSet state = super.getAccessibleStateSet();
// TODO: Figure out what state must be added here to the super's state.
@@ -237,6 +241,7 @@ public abstract class JTextComponent extends JComponent
* @param e - document event
*/
public void insertUpdate(DocumentEvent e)
+ throws NotImplementedException
{
// TODO
}
@@ -248,6 +253,7 @@ public abstract class JTextComponent extends JComponent
* @param e - document event
*/
public void removeUpdate(DocumentEvent e)
+ throws NotImplementedException
{
// TODO
}
@@ -259,6 +265,7 @@ public abstract class JTextComponent extends JComponent
* @param e - document event
*/
public void changedUpdate(DocumentEvent e)
+ throws NotImplementedException
{
// TODO
}
@@ -271,6 +278,7 @@ public abstract class JTextComponent extends JComponent
* @return the character index, or -1
*/
public int getIndexAtPoint(Point p)
+ throws NotImplementedException
{
return 0; // TODO
}
@@ -289,6 +297,7 @@ public abstract class JTextComponent extends JComponent
* @return the bounding box, may be empty or null.
*/
public Rectangle getCharacterBounds(int index)
+ throws NotImplementedException
{
return null; // TODO
}
@@ -311,6 +320,7 @@ public abstract class JTextComponent extends JComponent
* @return the character's attributes
*/
public AttributeSet getCharacterAttribute(int index)
+ throws NotImplementedException
{
return null; // TODO
}
@@ -324,6 +334,7 @@ public abstract class JTextComponent extends JComponent
* @return the selection of text at that index, or null
*/
public String getAtIndex(int part, int index)
+ throws NotImplementedException
{
return null; // TODO
}
@@ -337,6 +348,7 @@ public abstract class JTextComponent extends JComponent
* @return the selection of text after that index, or null
*/
public String getAfterIndex(int part, int index)
+ throws NotImplementedException
{
return null; // TODO
}
@@ -350,6 +362,7 @@ public abstract class JTextComponent extends JComponent
* @return the selection of text before that index, or null
*/
public String getBeforeIndex(int part, int index)
+ throws NotImplementedException
{
return null; // TODO
}
@@ -361,6 +374,7 @@ public abstract class JTextComponent extends JComponent
* @return the 0-based number of actions
*/
public int getAccessibleActionCount()
+ throws NotImplementedException
{
return 0; // TODO
}
@@ -369,11 +383,11 @@ public abstract class JTextComponent extends JComponent
* Get a description for the specified action. Returns null if out of
* bounds.
*
- * @param i
- * the action to describe, 0-based
+ * @param i the action to describe, 0-based
* @return description of the action
*/
public String getAccessibleActionDescription(int i)
+ throws NotImplementedException
{
// TODO: Not implemented fully
return super.getAccessibleDescription();
@@ -386,6 +400,7 @@ public abstract class JTextComponent extends JComponent
* @return true if the action was performed
*/
public boolean doAccessibleAction(int i)
+ throws NotImplementedException
{
return false; // TODO
}
@@ -396,6 +411,7 @@ public abstract class JTextComponent extends JComponent
* @param s the new text
*/
public void setTextContents(String s)
+ throws NotImplementedException
{
// TODO
}
@@ -407,6 +423,7 @@ public abstract class JTextComponent extends JComponent
* @param s the new text
*/
public void insertTextAtIndex(int index, String s)
+ throws NotImplementedException
{
replaceText(index, index, s);
}
@@ -495,6 +512,7 @@ public abstract class JTextComponent extends JComponent
* @param s the new attribute set for the range
*/
public void setAttributes(int start, int end, AttributeSet s)
+ throws NotImplementedException
{
// TODO
}
@@ -1365,7 +1383,7 @@ public abstract class JTextComponent extends JComponent
{
if (editable == newValue)
return;
-
+
boolean oldValue = editable;
editable = newValue;
firePropertyChange("editable", oldValue, newValue);
@@ -1725,17 +1743,20 @@ public abstract class JTextComponent extends JComponent
public void copy()
{
+ if (isEnabled())
doTransferAction("copy", TransferHandler.getCopyAction());
}
public void cut()
{
- doTransferAction("cut", TransferHandler.getCutAction());
+ if (editable && isEnabled())
+ doTransferAction("cut", TransferHandler.getCutAction());
}
public void paste()
{
- doTransferAction("paste", TransferHandler.getPasteAction());
+ if (editable && isEnabled())
+ doTransferAction("paste", TransferHandler.getPasteAction());
}
private void doTransferAction(String name, Action action)
diff --git a/libjava/classpath/javax/swing/text/PlainView.java b/libjava/classpath/javax/swing/text/PlainView.java
index 18818c0bad3..48fe37ce880 100644
--- a/libjava/classpath/javax/swing/text/PlainView.java
+++ b/libjava/classpath/javax/swing/text/PlainView.java
@@ -437,132 +437,92 @@ public class PlainView extends View implements TabExpander
*/
protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f)
{
- // Return early and do no updates if the allocation area is null
- // (like the RI).
- if (a == null)
- return;
-
- float oldMaxLineLength = maxLineLength;
- Rectangle alloc = a.getBounds();
- Element el = getElement();
- ElementChange ec = changes.getChange(el);
-
- // If ec is null then no lines were added or removed, just
- // repaint the changed line
- if (ec == null)
- {
- int line = el.getElementIndex(changes.getOffset());
-
- // If characters have been removed from the current longest line
- // we have to find out which one is the longest now otherwise
- // the preferred x-axis span will not shrink.
- if (changes.getType() == DocumentEvent.EventType.REMOVE
- && el.getElement(line) == longestLine)
- {
- maxLineLength = -1;
- if (determineMaxLineLength() != alloc.width)
- preferenceChanged(this, true, false);
- }
-
- damageLineRange(line, line, a, getContainer());
- return;
- }
-
- Element[] removed = ec.getChildrenRemoved();
- Element[] newElements = ec.getChildrenAdded();
-
- // If no Elements were added or removed, we just want to repaint
- // the area containing the line that was modified
- if (removed == null && newElements == null)
+ // This happens during initialization.
+ if (metrics == null)
{
- int line = getElement().getElementIndex(changes.getOffset());
-
- damageLineRange(line, line, a, getContainer());
+ updateMetrics();
+ preferenceChanged(null, true, true);
return;
}
- // Check to see if we removed the longest line, if so we have to
- // search through all lines and find the longest one again.
- if (removed != null)
- {
- for (int i = 0; i < removed.length; i++)
- if (removed[i].equals(longestLine))
- {
- // reset maxLineLength and search through all lines for longest one
- maxLineLength = -1;
- if (determineMaxLineLength() != alloc.width)
- preferenceChanged(this, true, removed.length != newElements.length);
-
- ((JTextComponent)getContainer()).repaint();
-
- return;
- }
- }
-
- // If we've reached here, that means we haven't removed the longest line
- if (newElements == null)
- {
- // No lines were added, just repaint the container and exit
- ((JTextComponent)getContainer()).repaint();
-
- return;
- }
+ Element element = getElement();
- // Make sure we have the metrics
- updateMetrics();
-
- // If we've reached here, that means we haven't removed the longest line
- // and we have added at least one line, so we have to check if added lines
- // are longer than the previous longest line
- Segment seg = getLineBuffer();
- float longestNewLength = 0;
- Element longestNewLine = null;
+ // Find longest line if it hasn't been initialized yet.
+ if (longestLine == null)
+ findLongestLine(0, element.getElementCount() - 1);
- // Loop through the added lines to check their length
- for (int i = 0; i < newElements.length; i++)
+ ElementChange change = changes.getChange(element);
+ if (changes.getType() == DocumentEvent.EventType.INSERT)
{
- Element child = newElements[i];
- int start = child.getStartOffset();
- int end = child.getEndOffset() - 1;
- try
- {
- el.getDocument().getText(start, end - start, seg);
- }
- catch (BadLocationException ex)
+ // Handles character/line insertion.
+
+ // Determine if lines have been added. In this case we repaint
+ // differently.
+ boolean linesAdded = true;
+ if (change == null)
+ linesAdded = false;
+
+ // Determine the start line.
+ int start;
+ if (linesAdded)
+ start = change.getIndex();
+ else
+ start = element.getElementIndex(changes.getOffset());
+
+ // Determine the length of the updated region.
+ int length = 0;
+ if (linesAdded)
+ length = change.getChildrenAdded().length - 1;
+
+ // Update the longest line and length.
+ int oldMaxLength = (int) maxLineLength;
+ if (longestLine.getEndOffset() < changes.getOffset()
+ || longestLine.getStartOffset() > changes.getOffset()
+ + changes.getLength())
{
- AssertionError ae = new AssertionError("Unexpected bad location");
- ae.initCause(ex);
- throw ae;
+ findLongestLine(start, start + length);
}
-
- if (seg == null || seg.array == null || seg.count == 0)
- continue;
-
- int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
- if (width > longestNewLength)
+ else
{
- longestNewLine = child;
- longestNewLength = width;
+ findLongestLine(0, element.getElementCount() - 1);
}
+
+ // Trigger a preference change so that the layout gets updated
+ // correctly.
+ preferenceChanged(null, maxLineLength != oldMaxLength, linesAdded);
+
+ // Damage the updated line range.
+ int endLine = start;
+ if (linesAdded)
+ endLine = element.getElementCount() - 1;
+ damageLineRange(start, endLine, a, getContainer());
+
}
-
- // Check if the longest of the new lines is longer than our previous
- // longest line, and if so update our values
- if (longestNewLength > maxLineLength)
+ else
{
- maxLineLength = longestNewLength;
- longestLine = longestNewLine;
+ // Handles character/lines removals.
+
+ // Update the longest line and length and trigger preference changed.
+ int oldMaxLength = (int) maxLineLength;
+ if (change != null)
+ {
+ // Line(s) have been removed.
+ findLongestLine(0, element.getElementCount() - 1);
+ preferenceChanged(null, maxLineLength != oldMaxLength, true);
+ }
+ else
+ {
+ // No line has been removed.
+ int lineNo = getElement().getElementIndex(changes.getOffset());
+ Element line = getElement().getElement(lineNo);
+ if (longestLine == line)
+ {
+ findLongestLine(0, element.getElementCount() - 1);
+ preferenceChanged(null, maxLineLength != oldMaxLength, false);
+ }
+ damageLineRange(lineNo, lineNo, a, getContainer());
+ }
}
-
- // Report any changes to the preferred sizes of the view
- // which may cause the underlying component to be revalidated.
- boolean widthChanged = oldMaxLineLength != maxLineLength;
- boolean heightChanged = removed.length != newElements.length;
- if (widthChanged || heightChanged)
- preferenceChanged(this, widthChanged, heightChanged);
-
- // Repaint the container
- ((JTextComponent)getContainer()).repaint();
}
/**
@@ -648,5 +608,54 @@ public class PlainView extends View implements TabExpander
lineBuffer = new Segment();
return lineBuffer;
}
+
+ /**
+ * Finds and updates the longest line in the view inside an interval of
+ * lines.
+ *
+ * @param start the start of the search interval
+ * @param end the end of the search interval
+ */
+ private void findLongestLine(int start, int end)
+ {
+ for (int i = start; i <= end; i++)
+ {
+ int w = getLineLength(i);
+ if (w > maxLineLength)
+ {
+ maxLineLength = w;
+ longestLine = getElement().getElement(i);
+ }
+ }
+ }
+
+ /**
+ * Determines the length of the specified line.
+ *
+ * @param line the number of the line
+ *
+ * @return the length of the line in pixels
+ */
+ private int getLineLength(int line)
+ {
+ Element lineEl = getElement().getElement(line);
+ Segment buffer = getLineBuffer();
+ try
+ {
+ Document doc = getDocument();
+ doc.getText(lineEl.getStartOffset(),
+ lineEl.getEndOffset() - lineEl.getStartOffset() - 1,
+ buffer);
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError err = new AssertionError("Unexpected bad location");
+ err.initCause(ex);
+ throw err;
+ }
+
+ return Utilities.getTabbedTextWidth(buffer, metrics, 0, this,
+ lineEl.getStartOffset());
+ }
}
diff --git a/libjava/classpath/javax/swing/text/StyleContext.java b/libjava/classpath/javax/swing/text/StyleContext.java
index 8ef34400d29..63df3df6a91 100644
--- a/libjava/classpath/javax/swing/text/StyleContext.java
+++ b/libjava/classpath/javax/swing/text/StyleContext.java
@@ -38,8 +38,6 @@ exception statement from your version. */
package javax.swing.text;
-import gnu.classpath.NotImplementedException;
-
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
@@ -50,7 +48,6 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.EventListener;
-import java.util.HashSet;
import java.util.Hashtable;
import javax.swing.event.ChangeEvent;
@@ -413,7 +410,7 @@ public class StyleContext
/**
* These attribute keys are handled specially in serialization.
*/
- private static HashSet staticAttributeKeys = new HashSet();
+ private static Hashtable staticAttributeKeys = new Hashtable();
EventListenerList listenerList;
Hashtable styleTable;
@@ -708,49 +705,125 @@ public class StyleContext
}
}
-
- // FIXME: there's some sort of quasi-serialization stuff in here which I
- // have left incomplete; I'm not sure I understand the intent properly.
-
+ /**
+ * Gets the object previously registered with registerStaticAttributeKey.
+ *
+ * @param key - the key that was registered.
+ * @return the object previously registered with registerStaticAttributeKey.
+ */
public static Object getStaticAttribute(Object key)
- throws NotImplementedException
{
- throw new InternalError("not implemented");
+ if (key == null)
+ return null;
+ return staticAttributeKeys.get(key);
}
+ /**
+ * Returns the String that key will be registered with
+ * registerStaticAttributeKey.
+ *
+ * @param key - the key that will be registered.
+ * @return the string the key will be registered with.
+ */
public static Object getStaticAttributeKey(Object key)
- throws NotImplementedException
{
- throw new InternalError("not implemented");
+ return key.getClass().getName() + "." + key.toString();
}
+ /**
+ * Reads a set of attributes from the given object input stream. This will
+ * attempt to restore keys that were static objects by considering only the
+ * keys that have were registered with registerStaticAttributeKey. The
+ * attributes retrieved will be placed into the given set.
+ *
+ * @param in - the stream to read from
+ * @param a - the set of attributes
+ * @throws ClassNotFoundException - may be encountered when reading from
+ * stream
+ * @throws IOException - any I/O error
+ */
public static void readAttributeSet(ObjectInputStream in, MutableAttributeSet a)
- throws ClassNotFoundException, IOException, NotImplementedException
+ throws ClassNotFoundException, IOException
{
- throw new InternalError("not implemented");
+ if (in == null || a == null)
+ return;
+
+ Object key = in.readObject();
+ Object val = in.readObject();
+ while (key != null && val != null)
+ {
+ Object staticKey = staticAttributeKeys.get(key);
+ Object staticVal = staticAttributeKeys.get(val);
+
+ if (staticKey != null)
+ key = staticKey;
+ if (staticVal != null)
+ val = staticVal;
+
+ a.addAttribute(key, val);
+ key = in.readObject();
+ val = in.readObject();
+ }
}
+ /**
+ * Serialize an attribute set in a way that is compatible with it
+ * being read in again by {@link #readAttributeSet(ObjectInputStream, MutableAttributeSet)}.
+ * In particular registered static keys are transformed properly.
+ *
+ * @param out - stream to write to
+ * @param a - the attribute set
+ * @throws IOException - any I/O error
+ */
public static void writeAttributeSet(ObjectOutputStream out, AttributeSet a)
- throws IOException, NotImplementedException
+ throws IOException
{
- throw new InternalError("not implemented");
+ Enumeration e = a.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ Object oldKey = e.nextElement();
+ Object newKey = getStaticAttribute(oldKey);
+ Object key = (newKey == null) ? oldKey : newKey;
+
+ out.writeObject(key);
+ out.writeObject(a.getAttribute(oldKey));
+ }
+ out.writeObject(null);
+ out.writeObject(null);
}
+ /**
+ * Handles reading in the attributes.
+ * @see #readAttributeSet(ObjectInputStream, MutableAttributeSet)
+ *
+ * @param in - the stream to read from
+ * @param a - the set of attributes
+ * @throws ClassNotFoundException - may be encountered when reading from stream
+ * @throws IOException - any I/O error
+ */
public void readAttributes(ObjectInputStream in, MutableAttributeSet a)
- throws ClassNotFoundException, IOException, NotImplementedException
+ throws ClassNotFoundException, IOException
{
- throw new InternalError("not implemented");
+ readAttributeSet(in, a);
}
+ /**
+ * Handles writing of the given attributes.
+ * @see #writeAttributeSet(ObjectOutputStream, AttributeSet)
+ *
+ * @param out - stream to write to
+ * @param a - the attribute set
+ * @throws IOException - any I/O error
+ */
public void writeAttributes(ObjectOutputStream out, AttributeSet a)
- throws IOException, NotImplementedException
+ throws IOException
{
- throw new InternalError("not implemented");
+ writeAttributeSet(out, a);
}
/**
* Registers an attribute key as a well-known keys. When an attribute with
- * such a key is written to a stream,, a special syntax is used so that it
+ * such a key is written to a stream, a special syntax is used so that it
* can be recognized when it is read back in. All attribute keys defined
* in <code>StyleContext</code> are registered as static keys. If you define
* additional attribute keys that you want to exist as nonreplicated objects,
@@ -760,6 +833,8 @@ public class StyleContext
*/
public static void registerStaticAttributeKey(Object key)
{
- staticAttributeKeys.add(key);
+ if (key != null)
+ staticAttributeKeys.put(key.getClass().getName() + "." + key.toString(),
+ key);
}
}
diff --git a/libjava/classpath/javax/swing/text/TextAction.java b/libjava/classpath/javax/swing/text/TextAction.java
index 8588e3cd202..144166e9cdb 100644
--- a/libjava/classpath/javax/swing/text/TextAction.java
+++ b/libjava/classpath/javax/swing/text/TextAction.java
@@ -38,12 +38,14 @@ exception statement from your version. */
package javax.swing.text;
+import java.awt.Point;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.HashSet;
import javax.swing.AbstractAction;
import javax.swing.Action;
+import javax.swing.SwingConstants;
/**
* TextAction
@@ -108,4 +110,106 @@ public abstract class TextAction extends AbstractAction
{
return null; // TODO
}
+
+ /** Abstract helper class which implements everything needed for an
+ * Action implementation in <code>DefaultEditorKit</code> which
+ * does horizontal movement (and selection).
+ */
+ abstract static class HorizontalMovementAction extends TextAction
+ {
+ int dir;
+
+ HorizontalMovementAction(String name, int direction)
+ {
+ super(name);
+ dir = direction;
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ try
+ {
+ if (t != null)
+ {
+ int offs
+ = Utilities.getNextVisualPositionFrom(t,
+ t.getCaretPosition(),
+ dir);
+
+ Caret c = t.getCaret();
+
+ actionPerformedImpl(c, offs);
+
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ throw
+ (InternalError) new InternalError("Illegal offset").initCause(ble);
+ }
+
+ }
+
+ protected abstract void actionPerformedImpl(Caret c, int offs)
+ throws BadLocationException;
+ }
+
+ /** Abstract helper class which implements everything needed for an
+ * Action implementation in <code>DefaultEditorKit</code> which
+ * does vertical movement (and selection).
+ */
+ abstract static class VerticalMovementAction extends TextAction
+ {
+ int dir;
+
+ VerticalMovementAction(String name, int direction)
+ {
+ super(name);
+ dir = direction;
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ try
+ {
+ if (t != null)
+ {
+ Caret c = t.getCaret();
+ // The magic caret position may be null when the caret
+ // has not moved yet.
+ Point mcp = c.getMagicCaretPosition();
+
+ int pos;
+ if (mcp != null)
+ {
+ mcp.y = t.modelToView(c.getDot()).y;
+ pos = t.viewToModel(mcp);
+ }
+ else
+ pos = c.getDot();
+
+ pos = Utilities.getNextVisualPositionFrom(t,
+ t.getCaretPosition(),
+ dir);
+
+ if (pos > -1)
+ actionPerformedImpl(c, pos);
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ throw
+ (InternalError) new InternalError("Illegal offset").initCause(ble);
+ }
+ }
+
+ protected abstract void actionPerformedImpl(Caret c, int offs)
+ throws BadLocationException;
+
+ }
+
+
}
diff --git a/libjava/classpath/javax/swing/text/Utilities.java b/libjava/classpath/javax/swing/text/Utilities.java
index f154e55aac0..36361f49796 100644
--- a/libjava/classpath/javax/swing/text/Utilities.java
+++ b/libjava/classpath/javax/swing/text/Utilities.java
@@ -43,6 +43,9 @@ import java.awt.Graphics;
import java.awt.Point;
import java.text.BreakIterator;
+import javax.swing.SwingConstants;
+import javax.swing.text.Position.Bias;
+
/**
* A set of utilities to deal with text. This is used by several other classes
* inside this package.
@@ -337,7 +340,7 @@ public class Utilities
// location or is not whitespace (meaning it is a number or
// punctuation). The first case means that 'last' denotes the
// beginning of a word while the second case means it is the start
- // of some else.
+ // of something else.
if (Character.isLetter(cp)
|| !Character.isWhitespace(cp))
return last;
@@ -346,7 +349,7 @@ public class Utilities
current = wb.next();
}
- throw new BadLocationException("no more word", offs);
+ throw new BadLocationException("no more words", offs);
}
/**
@@ -363,24 +366,36 @@ public class Utilities
public static final int getPreviousWord(JTextComponent c, int offs)
throws BadLocationException
{
- if (offs < 0 || offs > (c.getText().length() - 1))
- throw new BadLocationException("invalid offset specified", offs);
String text = c.getText();
+
+ if (offs <= 0 || offs > text.length())
+ throw new BadLocationException("invalid offset specified", offs);
+
BreakIterator wb = BreakIterator.getWordInstance();
wb.setText(text);
int last = wb.preceding(offs);
int current = wb.previous();
+ int cp;
while (current != BreakIterator.DONE)
{
for (int i = last; i < offs; i++)
{
- if (Character.isLetter(text.codePointAt(i)))
+ cp = text.codePointAt(i);
+
+ // Return the last found bound if there is a letter at the current
+ // location or is not whitespace (meaning it is a number or
+ // punctuation). The first case means that 'last' denotes the
+ // beginning of a word while the second case means it is the start
+ // of some else.
+ if (Character.isLetter(cp)
+ || !Character.isWhitespace(cp))
return last;
}
last = current;
current = wb.previous();
}
+
return 0;
}
@@ -394,14 +409,17 @@ public class Utilities
public static final int getWordStart(JTextComponent c, int offs)
throws BadLocationException
{
- if (offs < 0 || offs >= c.getText().length())
+ String text = c.getText();
+
+ if (offs < 0 || offs > text.length())
throw new BadLocationException("invalid offset specified", offs);
- String text = c.getText();
BreakIterator wb = BreakIterator.getWordInstance();
wb.setText(text);
+
if (wb.isBoundary(offs))
return offs;
+
return wb.preceding(offs);
}
@@ -674,4 +692,38 @@ public class Utilities
else
return offs+1;
}
+
+ /** This is an internal helper method which is used by the
+ * <code>javax.swing.text</code> package. It simply delegates the
+ * call to a method with the same name on the <code>NavigationFilter</code>
+ * of the provided <code>JTextComponent</code> (if it has one) or its UI.
+ *
+ * If the underlying method throws a <code>BadLocationException</code> it
+ * will be swallowed and the initial offset is returned.
+ */
+ static int getNextVisualPositionFrom(JTextComponent t, int offset, int direction)
+ {
+ NavigationFilter nf = t.getNavigationFilter();
+
+ try
+ {
+ return (nf != null)
+ ? nf.getNextVisualPositionFrom(t,
+ offset,
+ Bias.Forward,
+ direction,
+ null)
+ : t.getUI().getNextVisualPositionFrom(t,
+ offset,
+ Bias.Forward,
+ direction,
+ null);
+ }
+ catch (BadLocationException ble)
+ {
+ return offset;
+ }
+
+ }
+
}
diff --git a/libjava/classpath/javax/swing/text/html/HTMLDocument.java b/libjava/classpath/javax/swing/text/html/HTMLDocument.java
index fba6cad12e9..b7a706904a4 100644
--- a/libjava/classpath/javax/swing/text/html/HTMLDocument.java
+++ b/libjava/classpath/javax/swing/text/html/HTMLDocument.java
@@ -40,14 +40,12 @@ package javax.swing.text.html;
import gnu.classpath.NotImplementedException;
+import gnu.javax.swing.text.html.CharacterAttributeTranslator;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Stack;
import java.util.Vector;
-
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.UndoableEditEvent;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
@@ -131,21 +129,6 @@ public class HTMLDocument extends DefaultStyledDocument
}
/**
- * Replaces the contents of the document with the given element
- * specifications. This is called before insert if the loading is done
- * in bursts. This is the only method called if loading the document
- * entirely in one burst.
- *
- * @param data - the date that replaces the content of the document
- */
- protected void create(DefaultStyledDocument.ElementSpec[] data)
- {
- // Once the super behaviour is properly implemented it should be sufficient
- // to simply call super.create(data).
- super.create(data);
- }
-
- /**
* This method creates a root element for the new document.
*
* @return the new default root
@@ -216,41 +199,6 @@ public class HTMLDocument extends DefaultStyledDocument
}
/**
- * Inserts new elements in bulk. This is how elements get created in the
- * document. The parsing determines what structure is needed and creates the
- * specification as a set of tokens that describe the edit while leaving the
- * document free of a write-lock. This method can then be called in bursts by
- * the reader to acquire a write-lock for a shorter duration (i.e. while the
- * document is actually being altered).
- *
- * @param offset - the starting offset
- * @param data - the element data
- * @throws BadLocationException - if the given position does not
- * represent a valid location in the associated document.
- */
- protected void insert(int offset, DefaultStyledDocument.ElementSpec[] data)
- throws BadLocationException
- {
- super.insert(offset, data);
- }
-
- /**
- * Updates document structure as a result of text insertion. This will happen
- * within a write lock. This implementation simply parses the inserted content
- * for line breaks and builds up a set of instructions for the element buffer.
- *
- * @param chng - a description of the document change
- * @param attr - the attributes
- */
- protected void insertUpdate(AbstractDocument.DefaultDocumentEvent chng,
- AttributeSet attr)
- {
- // FIXME: Not implemented
- System.out.println("insertUpdate not implemented");
- super.insertUpdate(chng, attr);
- }
-
- /**
* Returns the parser used by this HTMLDocument to insert HTML.
*
* @return the parser used by this HTMLDocument to insert HTML.
@@ -414,6 +362,7 @@ public class HTMLDocument extends DefaultStyledDocument
}
public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent event)
+ throws NotImplementedException
{
// TODO: Implement this properly.
}
@@ -646,12 +595,16 @@ public class HTMLDocument extends DefaultStyledDocument
{
// Put the old attribute set on the stack.
pushCharacterStyle();
-
- // And create the new one by adding the attributes in <code>a</code>.
- if (a != null)
- charAttr.addAttribute(t, a.copyAttributes());
+
+ // Translate tag.. return if succesful.
+ if(CharacterAttributeTranslator.translateTag(charAttr, t, a))
+ return;
+
+ // Just add the attributes in <code>a</code>.
+ if (a != null)
+ charAttr.addAttribute(t, a.copyAttributes());
}
-
+
/**
* Called when an end tag is seen for one of the types of tags associated
* with this Action.
@@ -724,17 +677,6 @@ public class HTMLDocument extends DefaultStyledDocument
// FIXME: Implement.
print ("IsindexAction.start not implemented");
}
-
- /**
- * Called when an end tag is seen for one of the types of tags associated
- * with this Action.
- */
- public void end(HTML.Tag t)
- throws NotImplementedException
- {
- // FIXME: Implement.
- print ("IsindexAction.end not implemented");
- }
}
public class ParagraphAction extends BlockAction
@@ -745,7 +687,6 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public void start(HTML.Tag t, MutableAttributeSet a)
{
- // FIXME: What else must be done here?
blockOpen(t, a);
}
@@ -755,7 +696,6 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public void end(HTML.Tag t)
{
- // FIXME: What else must be done here?
blockClose(t);
}
}
@@ -771,6 +711,7 @@ public class HTMLDocument extends DefaultStyledDocument
{
// FIXME: Implement.
print ("PreAction.start not implemented");
+ super.start(t, a);
}
/**
@@ -782,6 +723,7 @@ public class HTMLDocument extends DefaultStyledDocument
{
// FIXME: Implement.
print ("PreAction.end not implemented");
+ super.end(t);
}
}
@@ -792,22 +734,9 @@ public class HTMLDocument extends DefaultStyledDocument
* of tags associated with this Action.
*/
public void start(HTML.Tag t, MutableAttributeSet a)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("SpecialAction.start not implemented");
+ addSpecialElement(t, a);
}
-
- /**
- * Called when an end tag is seen for one of the types of tags associated
- * with this Action.
- */
- public void end(HTML.Tag t)
- throws NotImplementedException
- {
- // FIXME: Implement.
- print ("SpecialAction.end not implemented");
- }
}
class AreaAction extends TagAction
@@ -1131,7 +1060,7 @@ public class HTMLDocument extends DefaultStyledDocument
*/
protected void pushCharacterStyle()
{
- charAttrStack.push(charAttr);
+ charAttrStack.push(charAttr.copyAttributes());
}
/**
@@ -1565,8 +1494,8 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public Element getElement(String attrId)
{
- Element root = getDefaultRootElement();
- return getElement(root, HTML.getAttributeKey(attrId) , attrId);
+ return getElement(getDefaultRootElement(), HTML.getAttributeKey(attrId),
+ attrId);
}
/**
@@ -1690,53 +1619,4 @@ public class HTMLDocument extends DefaultStyledDocument
// FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit?
System.out.println("insertAfterStart not implemented");
}
-
- /**
- * This method sets the attributes associated with the paragraph containing
- * offset. If replace is false, s is merged with existing attributes. The
- * length argument determines how many characters are affected by the new
- * attributes. This is often the entire paragraph.
- *
- * @param offset -
- * the offset into the paragraph (must be at least 0)
- * @param length -
- * the number of characters affected (must be at least 0)
- * @param s -
- * the attributes
- * @param replace -
- * whether to replace existing attributes, or merge them
- */
- public void setParagraphAttributes(int offset, int length, AttributeSet s,
- boolean replace)
- throws NotImplementedException
- {
- // FIXME: Not implemented.
- System.out.println("setParagraphAttributes not implemented");
- super.setParagraphAttributes(offset, length, s, replace);
- }
-
- /**
- * This method flags a change in the document.
- *
- * @param e - the Document event
- */
- protected void fireChangedUpdate(DocumentEvent e)
- throws NotImplementedException
- {
- // FIXME: Not implemented.
- System.out.println("fireChangedUpdate not implemented");
- super.fireChangedUpdate(e);
- }
-
- /**
- * This method fires an event intended to be caught by Undo listeners. It
- * simply calls the super version inherited from DefaultStyledDocument. With
- * this method, an HTML editor could easily provide undo support.
- *
- * @param e - the UndoableEditEvent
- */
- protected void fireUndoableEditUpdate(UndoableEditEvent e)
- {
- super.fireUndoableEditUpdate(e);
- }
}
diff --git a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
index 92d9de938eb..adda4922d57 100644
--- a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
+++ b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
@@ -548,6 +548,8 @@ public class HTMLEditorKit
|| tag.equals(HTML.Tag.BLOCKQUOTE)
|| tag.equals(HTML.Tag.PRE))
view = new BlockView(element, View.Y_AXIS);
+ else if (tag.equals(HTML.Tag.IMG))
+ view = new ImageView(element);
// FIXME: Uncomment when the views have been implemented
else if (tag.equals(HTML.Tag.CONTENT))
@@ -558,13 +560,12 @@ public class HTMLEditorKit
view = new HTMLTableView(element);
else if (tag.equals(HTML.Tag.TD))
view = new ParagraphView(element);
+
/*
else if (tag.equals(HTML.Tag.MENU) || tag.equals(HTML.Tag.DIR)
|| tag.equals(HTML.Tag.UL) || tag.equals(HTML.Tag.OL))
view = new ListView(element);
- else if (tag.equals(HTML.Tag.IMG))
- view = new ImageView(element);
else if (tag.equals(HTML.Tag.HR))
view = new HRuleView(element);
else if (tag.equals(HTML.Tag.BR))
diff --git a/libjava/classpath/javax/swing/text/html/ImageView.java b/libjava/classpath/javax/swing/text/html/ImageView.java
new file mode 100644
index 00000000000..84b021070a9
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ImageView.java
@@ -0,0 +1,441 @@
+package javax.swing.text.html;
+
+import gnu.javax.swing.text.html.CombinedAttributes;
+import gnu.javax.swing.text.html.ImageViewIconFactory;
+
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.View;
+import javax.swing.text.Position.Bias;
+import javax.swing.text.html.HTML.Attribute;
+
+/**
+ * A view, representing a single image, represented by the HTML IMG tag.
+ *
+ * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
+ */
+public class ImageView extends View
+{
+ /**
+ * True if the image loads synchronuosly (on demand). By default, the image
+ * loads asynchronuosly.
+ */
+ boolean loadOnDemand;
+
+ /**
+ * The image icon, wrapping the image,
+ */
+ ImageIcon imageIcon;
+
+ /**
+ * The image state.
+ */
+ byte imageState = MediaTracker.LOADING;
+
+ /**
+ * Creates the image view that represents the given element.
+ *
+ * @param element the element, represented by this image view.
+ */
+ public ImageView(Element element)
+ {
+ super(element);
+ }
+
+ /**
+ * Load or reload the image. This method initiates the image reloading. After
+ * the image is ready, the repaint event will be scheduled. The current image,
+ * if it already exists, will be discarded.
+ *
+ * @param itsTime
+ * also load if the "on demand" property is set
+ */
+ void reloadImage(boolean itsTime)
+ {
+ URL url = getImageURL();
+ if (url == null)
+ imageState = (byte) MediaTracker.ERRORED;
+ else if (!(loadOnDemand && !itsTime))
+ imageIcon = new ImageIcon(url);
+ else
+ imageState = (byte) MediaTracker.LOADING;
+ }
+
+ /**
+ * Get the image alignment. This method works handling standart alignment
+ * attributes in the HTML IMG tag (align = top bottom middle left right).
+ * Depending from the parameter, either horizontal or vertical alingment
+ * information is returned.
+ *
+ * @param axis -
+ * either X_AXIS or Y_AXIS
+ */
+ public float getAlignment(int axis)
+ {
+ AttributeSet attrs = getAttributes();
+ Object al = attrs.getAttribute(Attribute.ALIGN);
+
+ // Default is top left aligned.
+ if (al == null)
+ return 0.0f;
+
+ String align = al.toString();
+
+ if (axis == View.X_AXIS)
+ {
+ if (align.equals("middle"))
+ return 0.5f;
+ else if (align.equals("left"))
+ return 0.0f;
+ else if (align.equals("right"))
+ return 1.0f;
+ else
+ return 0.0f;
+ }
+ else if (axis == View.Y_AXIS)
+ {
+ if (align.equals("middle"))
+ return 0.5f;
+ else if (align.equals("top"))
+ return 0.0f;
+ else if (align.equals("bottom"))
+ return 1.0f;
+ else
+ return 0.0f;
+ }
+ else
+ throw new IllegalArgumentException("axis " + axis);
+ }
+
+ /**
+ * Get the text that should be shown as the image replacement and also as the
+ * image tool tip text. The method returns the value of the attribute, having
+ * the name {@link Attribute#ALT}. If there is no such attribute, the image
+ * name from the url is returned. If the URL is not available, the empty
+ * string is returned.
+ */
+ public String getAltText()
+ {
+ Object rt = getAttributes().getAttribute(Attribute.ALT);
+ if (rt != null)
+ return rt.toString();
+ else
+ {
+ URL u = getImageURL();
+ if (u == null)
+ return "";
+ else
+ return u.getFile();
+ }
+ }
+
+ /**
+ * Returns the combination of the document and the style sheet attributes.
+ */
+ public AttributeSet getAttributes()
+ {
+ StyleSheet styles = getStyleSheet();
+ if (styles == null)
+ return super.getAttributes();
+ else
+ return CombinedAttributes.combine(super.getAttributes(),
+ styles.getViewAttributes(this));
+ }
+
+ /**
+ * Get the image to render. May return null if the image is not yet loaded.
+ */
+ public Image getImage()
+ {
+ if (imageIcon == null)
+ return null;
+ else
+ return imageIcon.getImage();
+ }
+
+ /**
+ * Get the URL location of the image to render. If this method returns null,
+ * the "no image" icon is rendered instead. By defaul, url must be present as
+ * the "src" property of the IMG tag. If it is missing, null is returned and
+ * the "no image" icon is rendered.
+ *
+ * @return the URL location of the image to render.
+ */
+ public URL getImageURL()
+ {
+ Object url = getAttributes().getAttribute(Attribute.SRC);
+ if (url == null)
+ return null;
+
+ try
+ {
+ return new URL(url.toString());
+ }
+ catch (MalformedURLException e)
+ {
+ // The URL is malformed - no image.
+ return null;
+ }
+ }
+
+ /**
+ * Get the icon that should be displayed while the image is loading and hence
+ * not yet available.
+ *
+ * @return an icon, showing a non broken sheet of paper with image.
+ */
+ public Icon getLoadingImageIcon()
+ {
+ return ImageViewIconFactory.getLoadingImageIcon();
+ }
+
+ /**
+ * Get the image loading strategy.
+ *
+ * @return false (default) if the image is loaded when the view is
+ * constructed, true if the image is only loaded on demand when
+ * rendering.
+ */
+ public boolean getLoadsSynchronously()
+ {
+ return loadOnDemand;
+ }
+
+ /**
+ * Get the icon that should be displayed when the image is not available.
+ *
+ * @return an icon, showing a broken sheet of paper with image.
+ */
+ public Icon getNoImageIcon()
+ {
+ return ImageViewIconFactory.getNoImageIcon();
+ }
+
+ /**
+ * Get the preferred span of the image along the axis. The image size is first
+ * requested to the attributes {@link Attribute#WIDTH} and
+ * {@link Attribute#HEIGHT}. If they are missing, and the image is already
+ * loaded, the image size is returned. If there are no attributes, and the
+ * image is not loaded, zero is returned.
+ *
+ * @param axis -
+ * either X_AXIS or Y_AXIS
+ * @return either width of height of the image, depending on the axis.
+ */
+ public float getPreferredSpan(int axis)
+ {
+ AttributeSet attrs = getAttributes();
+
+ Image image = getImage();
+
+ if (axis == View.X_AXIS)
+ {
+ Object w = attrs.getAttribute(Attribute.WIDTH);
+ if (w != null)
+ return Integer.parseInt(w.toString());
+ else if (image != null)
+ return image.getWidth(getContainer());
+ else
+ return getNoImageIcon().getIconWidth();
+ }
+ else if (axis == View.Y_AXIS)
+ {
+ Object w = attrs.getAttribute(Attribute.HEIGHT);
+ if (w != null)
+ return Integer.parseInt(w.toString());
+ else if (image != null)
+ return image.getHeight(getContainer());
+ else
+ return getNoImageIcon().getIconHeight();
+ }
+ else
+ throw new IllegalArgumentException("axis " + axis);
+ }
+
+ /**
+ * Get the associated style sheet from the document.
+ *
+ * @return the associated style sheet.
+ */
+ protected StyleSheet getStyleSheet()
+ {
+ Document d = getElement().getDocument();
+ if (d instanceof HTMLDocument)
+ return ((HTMLDocument) d).getStyleSheet();
+ else
+ return null;
+ }
+
+ /**
+ * Get the tool tip text. This is overridden to return the value of the
+ * {@link #getAltText()}. The parameters are ignored.
+ *
+ * @return that is returned by getAltText().
+ */
+ public String getToolTipText(float x, float y, Shape shape)
+ {
+ return getAltText();
+ }
+
+ /**
+ * Paints the image or one of the two image state icons. The image is resized
+ * to the shape bounds. If there is no image available, the alternative text
+ * is displayed besides the image state icon.
+ *
+ * @param g
+ * the Graphics, used for painting.
+ * @param bounds
+ * the bounds of the region where the image or replacing icon must be
+ * painted.
+ */
+ public void paint(Graphics g, Shape bounds)
+ {
+ Rectangle r = bounds.getBounds();
+
+ if (imageIcon == null)
+
+ {
+ // Loading image on demand, rendering the loading icon so far.
+ reloadImage(true);
+
+ // The reloadImage sets the imageIcon, unless the URL is broken
+ // or malformed.
+ if (imageIcon != null)
+ {
+ if (imageIcon.getImageLoadStatus() != MediaTracker.COMPLETE)
+ {
+ // Render "not ready" icon, unless the image is ready
+ // immediately.
+ renderIcon(g, r, getLoadingImageIcon());
+ // Add the listener to repaint when the icon will be ready.
+ imageIcon.setImageObserver(getContainer());
+ return;
+ }
+ }
+ else
+ {
+ renderIcon(g, r, getNoImageIcon());
+ return;
+ }
+ }
+
+ imageState = (byte) imageIcon.getImageLoadStatus();
+
+ switch (imageState)
+ {
+ case MediaTracker.ABORTED:
+ case MediaTracker.ERRORED:
+ renderIcon(g, r, getNoImageIcon());
+ break;
+ case MediaTracker.LOADING:
+ // If the image is not loaded completely, we still render it, as the
+ // partial image may be available.
+ case MediaTracker.COMPLETE:
+ {
+ // Paint the scaled image.
+ Image scaled = imageIcon.getImage().getScaledInstance(
+ r.width,
+ r.height,
+ Image.SCALE_DEFAULT);
+ ImageIcon painter = new ImageIcon(scaled);
+ painter.paintIcon(getContainer(), g, r.x, r.y);
+ }
+ break;
+ }
+ }
+
+ /**
+ * Render "no image" icon and the alternative "no image" text. The text is
+ * rendered right from the icon and is aligned to the icon bottom.
+ */
+ private void renderIcon(Graphics g, Rectangle bounds, Icon icon)
+ {
+ Shape current = g.getClip();
+ try
+ {
+ g.setClip(bounds);
+ if (icon != null)
+ {
+ icon.paintIcon(getContainer(), g, bounds.x, bounds.y);
+ g.drawString(getAltText(), bounds.x + icon.getIconWidth(),
+ bounds.y + icon.getIconHeight());
+ }
+ }
+ finally
+ {
+ g.setClip(current);
+ }
+ }
+
+ /**
+ * Set if the image should be loaded only when needed (synchronuosly). By
+ * default, the image loads asynchronuosly. If the image is not yet ready, the
+ * icon, returned by the {@link #getLoadingImageIcon()}, is displayed.
+ */
+ public void setLoadsSynchronously(boolean load_on_demand)
+ {
+ loadOnDemand = load_on_demand;
+ }
+
+ /**
+ * Update all cached properties from the attribute set, returned by the
+ * {@link #getAttributes}.
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ // In the current implementation, nothing is cached yet, unless the image
+ // itself.
+ imageIcon = null;
+ }
+
+ /**
+ * Maps the picture co-ordinates into the image position in the model. As the
+ * image is not divideable, this is currently implemented always to return the
+ * start offset.
+ */
+ public int viewToModel(float x, float y, Shape shape, Bias[] bias)
+ {
+ return getStartOffset();
+ }
+
+ /**
+ * This is currently implemented always to return the area of the image view,
+ * as the image is not divideable by character positions.
+ *
+ * @param pos character position
+ * @param area of the image view
+ * @param bias bias
+ *
+ * @return the shape, where the given character position should be mapped.
+ */
+ public Shape modelToView(int pos, Shape area, Bias bias)
+ throws BadLocationException
+ {
+ return area;
+ }
+
+ /**
+ * Starts loading the image asynchronuosly. If the image must be loaded
+ * synchronuosly instead, the {@link #setLoadsSynchronously} must be
+ * called before calling this method. The passed parameters are not used.
+ */
+ public void setSize(float width, float height)
+ {
+ if (imageIcon == null)
+ reloadImage(false);
+ }
+
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/MinimalHTMLWriter.java b/libjava/classpath/javax/swing/text/html/MinimalHTMLWriter.java
new file mode 100644
index 00000000000..d42951a05ec
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/MinimalHTMLWriter.java
@@ -0,0 +1,452 @@
+/* MinimalHTMLWriter.java --
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath 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
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text.html;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.AbstractWriter;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultStyledDocument;
+import javax.swing.text.Element;
+import javax.swing.text.ElementIterator;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.Style;
+import javax.swing.text.StyledDocument;
+import java.io.Writer;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Stack;
+import java.awt.Color;
+
+/**
+ * MinimalHTMLWriter,
+ * A minimal AbstractWriter implementation for HTML.
+ *
+ * @author Sven de Marothy
+ */
+public class MinimalHTMLWriter extends AbstractWriter
+{
+ private StyledDocument doc;
+ private Stack tagStack;
+ private boolean inFontTag = false;
+
+ /**
+ * Constructs a MinimalHTMLWriter.
+ * @param w - a Writer, for output.
+ * @param doc - the document
+ */
+ public MinimalHTMLWriter(Writer w, StyledDocument doc)
+ {
+ super(w, doc);
+ this.doc = doc;
+ tagStack = new Stack();
+ }
+
+ /**
+ * Constructs a MinimalHTMLWriter.
+ * @param w - a Writer, for output.
+ * @param doc - the document
+ * @param pos - start position
+ * @param len - length
+ */
+ public MinimalHTMLWriter(Writer w, StyledDocument doc, int pos, int len)
+ {
+ super(w, doc, pos, len);
+ this.doc = doc;
+ tagStack = new Stack();
+ }
+
+ /**
+ * Starts a span tag.
+ */
+ protected void startFontTag(String style) throws IOException
+ {
+ if( inFontTag() )
+ endOpenTags();
+ writeStartTag("<span style=\""+style+"\">");
+ inFontTag = true;
+ }
+
+ /**
+ * Returns whether the writer is within two span tags.
+ */
+ protected boolean inFontTag()
+ {
+ return inFontTag;
+ }
+
+ /**
+ * Ends a span tag.
+ */
+ protected void endFontTag() throws IOException
+ {
+ writeEndTag("</span>");
+ inFontTag = false;
+ }
+
+ /**
+ * Write the entire HTML document.
+ */
+ public synchronized void write() throws IOException, BadLocationException
+ {
+ writeStartTag("<html>");
+ writeHeader();
+ writeBody();
+ writeEndTag("</html>");
+ }
+
+ /**
+ * Write a start tag and increment the indent.
+ */
+ protected void writeStartTag(String tag) throws IOException
+ {
+ indent();
+ write(tag+NEWLINE);
+ incrIndent();
+ }
+
+ /**
+ * Write an ending tag and decrement the indent.
+ */
+ protected void writeEndTag(String endTag) throws IOException
+ {
+ decrIndent();
+ indent();
+ write(endTag+NEWLINE);
+ }
+
+ /**
+ * Write the HTML header.
+ */
+ protected void writeHeader() throws IOException
+ {
+ writeStartTag("<head>");
+ writeStartTag("<style>");
+ writeStartTag("<!--");
+ writeStyles();
+ writeEndTag("-->");
+ writeEndTag("</style>");
+ writeEndTag("</head>");
+ }
+
+ /**
+ * Write a paragraph start tag.
+ */
+ protected void writeStartParagraph(Element elem) throws IOException
+ {
+ indent();
+ write("<p class=default>"+NEWLINE); // FIXME: Class value = ?
+ incrIndent();
+ }
+
+ /**
+ * Write a paragraph end tag, closes any other open tags.
+ */
+ protected void writeEndParagraph() throws IOException
+ {
+ endOpenTags();
+ writeEndTag("</p>");
+ }
+
+ /**
+ * Writes the body of the HTML document.
+ */
+ protected void writeBody() throws IOException, BadLocationException
+ {
+ writeStartTag("<body>");
+
+ ElementIterator ei = getElementIterator();
+ Element e = ei.first();
+ boolean inParagraph = false;
+ do
+ {
+ if( e.isLeaf() )
+ {
+ boolean hasNL = (getText(e).indexOf(NEWLINE) != -1);
+ if( !inParagraph && hasText( e ) )
+ {
+ writeStartParagraph(e);
+ inParagraph = true;
+ }
+
+ if( hasText( e ) )
+ writeContent(e, true);
+
+ if( hasNL && inParagraph )
+ {
+ writeEndParagraph();
+ inParagraph = false;
+ }
+ else
+ endOpenTags();
+ }
+ }
+ while((e = ei.next()) != null);
+
+ writeEndTag("</body>");
+ }
+
+ protected void text(Element elem) throws IOException, BadLocationException
+ {
+ write( getText(elem).trim() );
+ }
+
+ /**
+ * Write bold, indent and underline tags.
+ */
+ protected void writeHTMLTags(AttributeSet attr) throws IOException
+ {
+ if(attr.getAttribute(StyleConstants.Bold) != null)
+ if(((Boolean)attr.getAttribute(StyleConstants.Bold)).booleanValue())
+ {
+ write("<b>");
+ tagStack.push("</b>");
+ }
+ if(attr.getAttribute(StyleConstants.Italic) != null)
+ if(((Boolean)attr.getAttribute(StyleConstants.Italic)).booleanValue())
+ {
+ write("<i>");
+ tagStack.push("</i>");
+ }
+ if(attr.getAttribute(StyleConstants.Underline) != null)
+ if(((Boolean)attr.getAttribute(StyleConstants.Underline)).booleanValue())
+ {
+ write("<u>");
+ tagStack.push("</u>");
+ }
+ }
+
+ /**
+ * Returns whether the element contains text or not.
+ */
+ protected boolean isText(Element elem)
+ {
+ return (elem.getEndOffset() != elem.getStartOffset());
+ }
+
+ /**
+ * Writes the content of an element.
+ */
+ protected void writeContent(Element elem, boolean needsIndenting)
+ throws IOException, BadLocationException
+ {
+ writeNonHTMLAttributes(elem.getAttributes());
+ if(needsIndenting)
+ indent();
+ writeHTMLTags(elem.getAttributes());
+ if( isText(elem) )
+ text(elem);
+ else
+ writeLeaf(elem);
+
+ endOpenTags();
+ }
+
+ /**
+ * Writes a non-text leaf element.
+ */
+ protected void writeLeaf(Element e) throws IOException
+ {
+ // NOTE: Haven't tested if this is correct.
+ if(e.getName().equals(StyleConstants.IconElementName))
+ writeImage(e);
+ else
+ writeComponent(e);
+ }
+
+ /**
+ * Write the HTML attributes which do not have tag equivalents,
+ * e.g. attributes other than bold/italic/underlined.
+ */
+ protected void writeNonHTMLAttributes(AttributeSet attr) throws IOException
+ {
+ String style = "";
+
+ // Alignment? Background?
+
+ if( StyleConstants.getForeground(attr) != null )
+ style = style + "color: " +
+ getColor(StyleConstants.getForeground(attr)) + "; ";
+
+ style = style + "font-size: "+StyleConstants.getFontSize(attr)+"pt; ";
+ style = style + "font-family: "+StyleConstants.getFontFamily(attr);
+
+ startFontTag(style);
+ }
+
+ /**
+ * Write the styles used.
+ */
+ protected void writeStyles() throws IOException
+ {
+ if(doc instanceof DefaultStyledDocument)
+ {
+ Enumeration styles = ((DefaultStyledDocument)doc).getStyleNames();
+ while(styles.hasMoreElements())
+ writeStyle(doc.getStyle((String)styles.nextElement()));
+ }
+ else
+ { // What else to do here?
+ Style s = (Style)doc.getStyle("default");
+ if(s != null)
+ writeStyle( s );
+ }
+ }
+
+ /**
+ * Write a set of attributes.
+ */
+ protected void writeAttributes(AttributeSet attr) throws IOException
+ {
+ Enumeration attribs = attr.getAttributeNames();
+ while(attribs.hasMoreElements())
+ {
+ Object attribName = attribs.nextElement();
+ String name = attribName.toString();
+ String output = getAttribute(name, attr.getAttribute(attribName));
+ if( output != null )
+ {
+ indent();
+ write( output + NEWLINE );
+ }
+ }
+ }
+
+ /**
+ * Deliberately unimplemented, handles component elements.
+ */
+ protected void writeComponent(Element elem) throws IOException
+ {
+ }
+
+ /**
+ * Deliberately unimplemented.
+ * Writes StyleConstants.IconElementName elements.
+ */
+ protected void writeImage(Element elem) throws IOException
+ {
+ }
+
+ // -------------------- Private methods. --------------------------------
+
+ /**
+ * Write a single style attribute
+ */
+ private String getAttribute(String name, Object a) throws IOException
+ {
+ if(name.equals("foreground"))
+ return "foreground:"+getColor((Color)a)+";";
+ if(name.equals("background"))
+ return "background:"+getColor((Color)a)+";";
+ if(name.equals("italic"))
+ return "italic:"+(((Boolean)a).booleanValue() ? "italic;" : ";");
+ if(name.equals("bold"))
+ return "bold:"+(((Boolean)a).booleanValue() ? "bold;" : "normal;");
+ if(name.equals("family"))
+ return "family:" + a + ";";
+ if(name.equals("size"))
+ {
+ int size = ((Integer)a).intValue();
+ int htmlSize;
+ if( size > 24 )
+ htmlSize = 7;
+ else if( size > 18 )
+ htmlSize = 6;
+ else if( size > 14 )
+ htmlSize = 5;
+ else if( size > 12 )
+ htmlSize = 4;
+ else if( size > 10 )
+ htmlSize = 3;
+ else if( size > 8 )
+ htmlSize = 2;
+ else
+ htmlSize = 1;
+
+ return "size:" + htmlSize + ";";
+ }
+
+ return null;
+ }
+
+ /**
+ * Stupid that Color doesn't have a method for this.
+ */
+ private String getColor(Color c)
+ {
+ String r = "00" + Integer.toHexString(c.getRed());
+ r = r.substring(r.length() - 2);
+ String g = "00" + Integer.toHexString(c.getGreen());
+ g = g.substring(g.length() - 2);
+ String b = "00" + Integer.toHexString(c.getBlue());
+ b = b.substring(b.length() - 2);
+ return "#" + r + g + b;
+ }
+
+ /**
+ * Empty the stack of open tags
+ */
+ private void endOpenTags() throws IOException
+ {
+ while(!tagStack.empty())
+ write((String)tagStack.pop());
+
+ if( inFontTag() )
+ {
+ write(""+NEWLINE);
+ endFontTag();
+ }
+ }
+
+ /**
+ * Output a single style
+ */
+ private void writeStyle(Style s) throws IOException
+ {
+ if( s == null )
+ return;
+
+ writeStartTag("p."+s.getName()+" {");
+ writeAttributes(s);
+ writeEndTag("}");
+ }
+
+ private boolean hasText(Element e) throws BadLocationException
+ {
+ return (getText(e).trim().length() > 0);
+ }
+}