Author: rwhitcomb Date: Tue Dec 17 20:17:48 2013 New Revision: 1551679 URL: http://svn.apache.org/r1551679 Log: PIVOT-696: Support expansion of tabs in TextPane in the same ways as with TextArea.
Add new style: tabWidth and property expandTabs. This also required changing the way we get a hold of the TextPane.Skin from within the skin view classes. Also PlainTextSerializer needs the tab settings. Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/TextPane.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkin.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinBlockView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinBulletedListView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinComponentNodeView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinDocumentView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinElementView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinImageNodeView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinListItemView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinListView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinNodeView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinNumberedListView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinParagraphView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinSpanView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinTextNodeView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinVerticalElementView.java pivot/trunk/wtk/src/org/apache/pivot/wtk/text/PlainTextSerializer.java Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/TextPane.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextPane.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/TextPane.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/TextPane.java Tue Dec 17 20:17:48 2013 @@ -17,8 +17,12 @@ package org.apache.pivot.wtk; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; +import java.net.URL; import org.apache.pivot.beans.DefaultProperty; import org.apache.pivot.collections.LinkedList; @@ -96,6 +100,12 @@ public class TextPane extends Container * @return The bounds of the character at the given offset. */ public Bounds getCharacterBounds(int offset); + + /** + * Returns the current setting of the "tabWidth" style (so "setText" + * uses the same value as Ctrl-Tab from user). + */ + public int getTabWidth(); } private interface Edit { @@ -199,6 +209,8 @@ public class TextPane extends Container private int selectionStart = 0; private int selectionLength = 0; + private boolean expandTabs = false; + private boolean editable = true; private boolean undoingHistory = false; private boolean bulkOperation = false; @@ -643,6 +655,8 @@ public class TextPane extends Container try { PlainTextSerializer serializer = new PlainTextSerializer(); StringReader reader = new StringReader(text); + serializer.setExpandTabs(this.expandTabs); + serializer.setTabWidth(((TextPane.Skin) getSkin()).getTabWidth()); documentLocal = serializer.readObject(reader); n = documentLocal.getCharacterCount(); @@ -716,14 +730,78 @@ public class TextPane extends Container /** * Convenience method to create a text-only document consisting of one * paragraph per line of the given text. + * + * @param text */ public void setText(String text) { + if (text == null) { + throw new IllegalArgumentException(); + } + + try { + setText(new StringReader(text)); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + } + + public void setText(URL textURL) throws IOException { + if (textURL == null) { + throw new IllegalArgumentException(); + } + + InputStream inputStream = null; + try { + inputStream = textURL.openStream(); + setText(new InputStreamReader(inputStream)); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + + public void setText(Reader textReader) throws IOException { + if (textReader == null) { + throw new IllegalArgumentException(); + } + + int tabPosition = 0; + int tabWidth = ((TextPane.Skin) getSkin()).getTabWidth(); + Document doc = new Document(); - String[] lines = text.split("\r?\n"); - for (int i = 0; i < lines.length; i++) { - Paragraph paragraph = new Paragraph(lines[i]); + StringBuilder text = new StringBuilder(); + + int c = textReader.read(); + while (c != -1) { + if (c == '\n') { + Paragraph paragraph = new Paragraph(text.toString()); + doc.add(paragraph); + text.setLength(0); + tabPosition = 0; + } else if (c == '\t') { + if (expandTabs) { + int spaces = tabWidth - (tabPosition % tabWidth); + for (int i = 0; i < spaces; i++) { + text.append(' '); + } + tabPosition += spaces; + } else { + text.append('\t'); + } + } else { + text.append((char) c); + tabPosition++; + } + + c = textReader.read(); + } + + if (text.length() != 0) { + Paragraph paragraph = new Paragraph(text.toString()); doc.add(paragraph); } + setDocument(doc); } @@ -876,6 +954,27 @@ public class TextPane extends Container } } + public boolean getExpandTabs() { + return expandTabs; + } + + /** + * Sets whether tab characters (<code>\t</code>) are expanded to an + * appropriate number of spaces during {@link #setText} and + * {@link #paste} operations. Note: doing this for keyboard input + * is handled in the skin. + * + * @param expandTabs <code>true</code> to replace tab characters with space + * characters (depending on the setting of the + * {@link TextPane.Skin#getTabWidth} value) or <code>false</code> to leave + * tabs alone. Note: this only affects tabs encountered during program + * operations; tabs entered via the keyboard by the user are always + * expanded, regardless of this setting. + */ + public void setExpandTabs(boolean expandTabs) { + this.expandTabs = expandTabs; + } + public int getInsertionPoint(int x, int y) { TextPane.Skin textPaneSkin = (TextPane.Skin) getSkin(); return textPaneSkin.getInsertionPoint(x, y); Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkin.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkin.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkin.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkin.java Tue Dec 17 20:17:48 2013 @@ -39,16 +39,9 @@ import org.apache.pivot.wtk.TextPane; import org.apache.pivot.wtk.TextPaneListener; import org.apache.pivot.wtk.TextPaneSelectionListener; import org.apache.pivot.wtk.Theme; -import org.apache.pivot.wtk.text.BulletedList; -import org.apache.pivot.wtk.text.ComponentNode; import org.apache.pivot.wtk.text.Document; -import org.apache.pivot.wtk.text.ImageNode; -import org.apache.pivot.wtk.text.List; import org.apache.pivot.wtk.text.Node; -import org.apache.pivot.wtk.text.NumberedList; import org.apache.pivot.wtk.text.Paragraph; -import org.apache.pivot.wtk.text.TextNode; -import org.apache.pivot.wtk.text.TextSpan; /** * Text pane skin. @@ -145,6 +138,8 @@ public class TextPaneSkin extends Contai private Insets margin = new Insets(4); private boolean wrapText = true; + private int tabWidth = 4; + private boolean acceptsTab = false; private static final int SCROLL_RATE = 30; @@ -171,7 +166,7 @@ public class TextPaneSkin extends Contai Document document = textPane.getDocument(); if (document != null) { - documentView = (TextPaneSkinDocumentView) createNodeView(document); + documentView = (TextPaneSkinDocumentView) TextPaneSkinNodeView.createNodeView(this, document); documentView.attach(); updateSelection(); } @@ -386,6 +381,45 @@ public class TextPaneSkin extends Contai return characterBounds; } + /** + * Gets current value of style that determines the behavior of <tt>TAB</tt> + * and <tt>Ctrl-TAB</tt> characters. + * + * @return <tt>true</tt> if <tt>TAB</tt> inserts an appropriate number of + * spaces, while <tt>Ctrl-TAB</tt> shifts focus to next component. + * <tt>false</tt> (default) means <tt>TAB</tt> shifts focus and + * <tt>Ctrl-TAB</tt> inserts spaces. + */ + public boolean getAcceptsTab() { + return acceptsTab; + } + + /** + * Sets current value of style that determines the behavior of <tt>TAB</tt> + * and <tt>Ctrl-TAB</tt> characters. + * + * @param acceptsTab <tt>true</tt> if <tt>TAB</tt> inserts an appropriate + * number of spaces, while <tt>Ctrl-TAB</tt> shifts focus to next component. + * <tt>false</tt> (default) means <tt>TAB</tt> shifts focus and + * <tt>Ctrl-TAB</tt> inserts spaces. + */ + public void setAcceptsTab(boolean acceptsTab) { + this.acceptsTab = acceptsTab; + } + + @Override + public int getTabWidth() { + return tabWidth; + } + + public void setTabWidth(int tabWidth) { + if (tabWidth < 0) { + throw new IllegalArgumentException("tabWidth is negative."); + } + + this.tabWidth = tabWidth; + } + private void scrollCharacterToVisible(int offset) { TextPane textPane = (TextPane) getComponent(); Bounds characterBounds = getCharacterBounds(offset); @@ -797,6 +831,35 @@ public class TextPaneSkin extends Contai return consumed; } + private int getRowOffset(Document document, int index) { + if (document != null) { + Node node = document.getDescendantAt(index); + while (node != null && !(node instanceof Paragraph)) { + node = node.getParent(); + } + // TODO: doesn't take into account the line wrapping within a paragraph + if (node != null) { + return node.getDocumentOffset(); + } + } + return 0; + } + + private int getRowLength(Document document, int index) { + if (document != null) { + Node node = document.getDescendantAt(index); + while (node != null && !(node instanceof Paragraph)) { + node = node.getParent(); + } + // TODO: doesn't take into account the line wrapping within a paragraph + // Assuming the node is a Paragraph, the count includes the trailing \n, so discount it + if (node != null) { + return node.getCharacterCount() - 1; + } + } + return 0; + } + @Override public boolean keyPressed(final Component component, int keyCode, Keyboard.KeyLocation keyLocation) { @@ -805,25 +868,88 @@ public class TextPaneSkin extends Contai final TextPane textPane = (TextPane) getComponent(); Document document = textPane.getDocument(); + int selectionStart = textPane.getSelectionStart(); + int selectionLength = textPane.getSelectionLength(); + Keyboard.Modifier commandModifier = Platform.getCommandModifier(); + boolean commandPressed = Keyboard.isPressed(commandModifier); + boolean wordNavPressed = Keyboard.isPressed(Platform.getWordNavigationModifier()); + boolean shiftPressed = Keyboard.isPressed(Keyboard.Modifier.SHIFT); + boolean ctrlPressed = Keyboard.isPressed(Keyboard.Modifier.CTRL); + boolean metaPressed = Keyboard.isPressed(Keyboard.Modifier.META); + boolean isEditable = textPane.isEditable(); + if (document != null) { - if (keyCode == Keyboard.KeyCode.ENTER && textPane.isEditable()) { + if (keyCode == Keyboard.KeyCode.ENTER && isEditable) { textPane.insertParagraph(); consumed = true; - } else if (keyCode == Keyboard.KeyCode.DELETE && textPane.isEditable()) { + } else if (keyCode == Keyboard.KeyCode.DELETE && isEditable) { textPane.delete(false); consumed = true; - } else if (keyCode == Keyboard.KeyCode.BACKSPACE && textPane.isEditable()) { + } else if (keyCode == Keyboard.KeyCode.BACKSPACE && isEditable) { textPane.delete(true); consumed = true; - } else if (keyCode == Keyboard.KeyCode.LEFT) { - int selectionStart = textPane.getSelectionStart(); - int selectionLength = textPane.getSelectionLength(); + } else if (keyCode == Keyboard.KeyCode.HOME + || (keyCode == Keyboard.KeyCode.LEFT && metaPressed)) { + int start; + if (commandPressed) { + // Move the caret to the beginning of the text + start = 0; + } else { + // Move the caret to the beginning of the line + start = getRowOffset(document, selectionStart); + } - if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { + if (shiftPressed) { + selectionLength += selectionStart - start; + } else { + selectionLength = 0; + } + + if (selectionStart >= 0) { + textPane.setSelection(start, selectionLength); + scrollCharacterToVisible(start); + + consumed = true; + } + } else if (keyCode == Keyboard.KeyCode.END + || (keyCode == Keyboard.KeyCode.RIGHT && metaPressed)) { + int end; + int index = selectionStart + selectionLength; + + if (commandPressed) { + // Move the caret to end of the text + end = textPane.getCharacterCount() - 1; + } else { + // Move the caret to the end of the line + int rowOffset = getRowOffset(document, index); + int rowLength = getRowLength(document, index); + end = rowOffset + rowLength; + } + + if (shiftPressed) { + selectionLength += end - index; + } else { + selectionStart = end; + if (selectionStart < textPane.getCharacterCount() + && document.getCharacterAt(selectionStart) == '\n') { + selectionStart--; + } + + selectionLength = 0; + } + + if (selectionStart + selectionLength <= textPane.getCharacterCount()) { + textPane.setSelection(selectionStart, selectionLength); + scrollCharacterToVisible(selectionStart + selectionLength); + + consumed = true; + } + } else if (keyCode == Keyboard.KeyCode.LEFT) { + if (shiftPressed) { // Add the previous character to the selection if (selectionStart > 0) { selectionStart--; @@ -862,10 +988,7 @@ public class TextPaneSkin extends Contai consumed = true; } else if (keyCode == Keyboard.KeyCode.RIGHT) { - int selectionStart = textPane.getSelectionStart(); - int selectionLength = textPane.getSelectionLength(); - - if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { + if (shiftPressed) { // Add the next character to the selection if (selectionStart + selectionLength < document.getCharacterCount()) { selectionLength++; @@ -911,8 +1034,6 @@ public class TextPaneSkin extends Contai consumed = true; } else if (keyCode == Keyboard.KeyCode.UP) { - int selectionStart = textPane.getSelectionStart(); - int offset = getNextInsertionPoint(caretX, selectionStart, TextPane.ScrollDirection.UP); @@ -920,10 +1041,8 @@ public class TextPaneSkin extends Contai offset = 0; } - int selectionLength; - if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { - int selectionEnd = selectionStart + textPane.getSelectionLength() - 1; - selectionLength = selectionEnd - offset + 1; + if (shiftPressed) { + selectionLength = selectionStart + selectionLength - offset; } else { selectionLength = 0; } @@ -933,10 +1052,8 @@ public class TextPaneSkin extends Contai consumed = true; } else if (keyCode == Keyboard.KeyCode.DOWN) { - int selectionStart = textPane.getSelectionStart(); - int selectionLength = textPane.getSelectionLength(); - if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { + if (shiftPressed) { int from; int x; if (selectionLength == 0) { @@ -993,27 +1110,42 @@ public class TextPaneSkin extends Contai } consumed = true; - } else if (Keyboard.isPressed(commandModifier) && keyCode == Keyboard.KeyCode.TAB - && textPane.isEditable()) { - textPane.insert("\t"); + } else if (keyCode == Keyboard.KeyCode.TAB + && (acceptsTab != Keyboard.isPressed(Keyboard.Modifier.CTRL)) + && isEditable) { + if (textPane.getExpandTabs()) { + int linePos = selectionStart - getRowOffset(document, selectionStart); + StringBuilder tabBuilder = new StringBuilder(tabWidth); + for (int i = 0; i < tabWidth - (linePos % tabWidth); i++) { + tabBuilder.append(" "); + } + textPane.insert(tabBuilder.toString()); + } else { + textPane.insert("\t"); + } showCaret(true); consumed = true; - } else if (Keyboard.isPressed(commandModifier)) { + } else if (keyCode == Keyboard.KeyCode.INSERT) { + if (shiftPressed && isEditable) { + textPane.paste(); + consumed = true; + } + } else if (commandPressed) { if (keyCode == Keyboard.KeyCode.A) { textPane.setSelection(0, document.getCharacterCount()); consumed = true; - } else if (keyCode == Keyboard.KeyCode.X && textPane.isEditable()) { + } else if (keyCode == Keyboard.KeyCode.X && isEditable) { textPane.cut(); consumed = true; } else if (keyCode == Keyboard.KeyCode.C) { textPane.copy(); consumed = true; - } else if (keyCode == Keyboard.KeyCode.V && textPane.isEditable()) { + } else if (keyCode == Keyboard.KeyCode.V && isEditable) { textPane.paste(); consumed = true; - } else if (keyCode == Keyboard.KeyCode.Z && textPane.isEditable()) { - if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { + } else if (keyCode == Keyboard.KeyCode.Z && isEditable) { + if (shiftPressed) { textPane.redo(); } else { textPane.undo(); @@ -1021,33 +1153,6 @@ public class TextPaneSkin extends Contai consumed = true; } - } else if (keyCode == Keyboard.KeyCode.HOME) { - // Move the caret to the beginning of the text - if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { - textPane.setSelection(0, textPane.getSelectionStart()); - } else { - textPane.setSelection(0, 0); - } - scrollCharacterToVisible(0); - - consumed = true; - } else if (keyCode == Keyboard.KeyCode.END) { - // Move the caret to the end of the text - if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { - int selectionStart = textPane.getSelectionStart(); - textPane.setSelection(selectionStart, textPane.getCharacterCount() - - selectionStart); - } else { - textPane.setSelection(textPane.getCharacterCount() - 1, 0); - } - scrollCharacterToVisible(textPane.getCharacterCount() - 1); - - consumed = true; - } else if (keyCode == Keyboard.KeyCode.INSERT) { - if (Keyboard.isPressed(Keyboard.Modifier.SHIFT) && textPane.isEditable()) { - textPane.paste(); - consumed = true; - } } else { consumed = super.keyPressed(component, keyCode, keyLocation); } @@ -1089,7 +1194,7 @@ public class TextPaneSkin extends Contai Document document = textPane.getDocument(); if (document != null) { - documentView = (TextPaneSkinDocumentView) createNodeView(document); + documentView = (TextPaneSkinDocumentView) TextPaneSkinNodeView.createNodeView(this, document); documentView.attach(); } @@ -1132,35 +1237,6 @@ public class TextPaneSkin extends Contai } } - TextPaneSkinNodeView createNodeView(Node node) { - TextPaneSkinNodeView nodeView = null; - - if (node instanceof Document) { - nodeView = new TextPaneSkinDocumentView(this, (Document) node); - } else if (node instanceof Paragraph) { - nodeView = new TextPaneSkinParagraphView((Paragraph) node); - } else if (node instanceof TextNode) { - nodeView = new TextPaneSkinTextNodeView((TextNode) node); - } else if (node instanceof ImageNode) { - nodeView = new TextPaneSkinImageNodeView((ImageNode) node); - } else if (node instanceof ComponentNode) { - nodeView = new TextPaneSkinComponentNodeView((ComponentNode) node); - } else if (node instanceof TextSpan) { - nodeView = new TextPaneSkinSpanView((TextSpan) node); - } else if (node instanceof NumberedList) { - nodeView = new TextPaneSkinNumberedListView((NumberedList) node); - } else if (node instanceof BulletedList) { - nodeView = new TextPaneSkinBulletedListView((BulletedList) node); - } else if (node instanceof List.Item) { - nodeView = new TextPaneSkinListItemView((List.Item) node); - } else { - throw new IllegalArgumentException("Unsupported node type: " - + node.getClass().getName()); - } - - return nodeView; - } - private void updateSelection() { if (documentView.getCharacterCount() > 0) { TextPane textPane = (TextPane) getComponent(); Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinBlockView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinBlockView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinBlockView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinBlockView.java Tue Dec 17 20:17:48 2013 @@ -22,8 +22,8 @@ import org.apache.pivot.wtk.text.BlockLi abstract class TextPaneSkinBlockView extends TextPaneSkinElementView implements BlockListener { - public TextPaneSkinBlockView(Block block) { - super(block); + public TextPaneSkinBlockView(TextPaneSkin textPaneSkin, Block block) { + super(textPaneSkin, block); } @Override Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinBulletedListView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinBulletedListView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinBulletedListView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinBulletedListView.java Tue Dec 17 20:17:48 2013 @@ -21,8 +21,8 @@ import org.apache.pivot.wtk.text.Bullete class TextPaneSkinBulletedListView extends TextPaneSkinListView implements BulletedListListener { - public TextPaneSkinBulletedListView(BulletedList bulletedList) { - super(bulletedList); + public TextPaneSkinBulletedListView(TextPaneSkin textPaneSkin, BulletedList bulletedList) { + super(textPaneSkin, bulletedList); } @Override Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinComponentNodeView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinComponentNodeView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinComponentNodeView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinComponentNodeView.java Tue Dec 17 20:17:48 2013 @@ -35,8 +35,8 @@ class TextPaneSkinComponentNodeView exte } }; - public TextPaneSkinComponentNodeView(ComponentNode componentNode) { - super(componentNode); + public TextPaneSkinComponentNodeView(TextPaneSkin textPaneSkin, ComponentNode componentNode) { + super(textPaneSkin, componentNode); } @Override Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinDocumentView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinDocumentView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinDocumentView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinDocumentView.java Tue Dec 17 20:17:48 2013 @@ -23,11 +23,8 @@ import org.apache.pivot.wtk.text.Documen */ class TextPaneSkinDocumentView extends TextPaneSkinVerticalElementView { - protected final TextPaneSkin textPaneSkin; - public TextPaneSkinDocumentView(TextPaneSkin textPaneSkin, Document document) { - super(document); - this.textPaneSkin = textPaneSkin; + super(textPaneSkin, document); } @Override @@ -43,8 +40,4 @@ class TextPaneSkinDocumentView extends T textPaneSkin.invalidateComponent(); } - @Override - public TextPaneSkin getTextPaneSkin() { - return textPaneSkin; - } } \ No newline at end of file Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinElementView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinElementView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinElementView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinElementView.java Tue Dec 17 20:17:48 2013 @@ -41,8 +41,8 @@ abstract class TextPaneSkinElementView e private int skinX = 0; private int skinY = 0; - public TextPaneSkinElementView(Element element) { - super(element); + public TextPaneSkinElementView(TextPaneSkin textPaneSkin, Element element) { + super(textPaneSkin, element); } @Override @@ -54,7 +54,7 @@ abstract class TextPaneSkinElementView e // Attach child node views for (Node node : element) { - add(getTextPaneSkin().createNodeView(node)); + add(createNodeView(getTextPaneSkin(), node)); } } @@ -235,7 +235,7 @@ abstract class TextPaneSkinElementView e int nodeViewOffset = nodeView.getOffset(); int characterCount = nodeView.getCharacterCount(); - if (offset >= nodeViewOffset && offset < nodeViewOffset + characterCount) { + if (offset >= nodeViewOffset && offset <= nodeViewOffset + characterCount) { characterBounds = nodeView.getCharacterBounds(offset - nodeViewOffset); if (characterBounds != null) { @@ -255,7 +255,7 @@ abstract class TextPaneSkinElementView e @Override public void nodeInserted(Element element, int index) { - insert(getTextPaneSkin().createNodeView(element.get(index)), index); + insert(createNodeView(getTextPaneSkin(), element.get(index)), index); invalidateUpTree(); } Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinImageNodeView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinImageNodeView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinImageNodeView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinImageNodeView.java Tue Dec 17 20:17:48 2013 @@ -28,8 +28,8 @@ import org.apache.pivot.wtk.text.ImageNo class TextPaneSkinImageNodeView extends TextPaneSkinNodeView implements ImageNodeListener, ImageListener { - public TextPaneSkinImageNodeView(ImageNode imageNode) { - super(imageNode); + public TextPaneSkinImageNodeView(TextPaneSkin textPaneSkin, ImageNode imageNode) { + super(textPaneSkin, imageNode); } @Override Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinListItemView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinListItemView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinListItemView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinListItemView.java Tue Dec 17 20:17:48 2013 @@ -18,6 +18,7 @@ package org.apache.pivot.wtk.skin; import java.util.Iterator; +import org.apache.pivot.wtk.text.List; import org.apache.pivot.wtk.text.TextNode; class TextPaneSkinListItemView extends TextPaneSkinVerticalElementView { @@ -25,8 +26,8 @@ class TextPaneSkinListItemView extends T private TextNode indexTextNode; private TextPaneSkinTextNodeView indexTextNodeView; - public TextPaneSkinListItemView(org.apache.pivot.wtk.text.List.Item listItem) { - super(listItem); + public TextPaneSkinListItemView(TextPaneSkin textPaneSkin, List.Item listItem) { + super(textPaneSkin, listItem); this.indexTextNode = new TextNode(""); } @@ -36,7 +37,7 @@ class TextPaneSkinListItemView extends T super.attach(); // add an extra TextNodeView to render the index text - indexTextNodeView = new TextPaneSkinTextNodeView(indexTextNode); + indexTextNodeView = new TextPaneSkinTextNodeView(getTextPaneSkin(), indexTextNode); indexTextNodeView.setLocation(0, 0); insert(indexTextNodeView, 0); } Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinListView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinListView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinListView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinListView.java Tue Dec 17 20:17:48 2013 @@ -22,8 +22,8 @@ class TextPaneSkinListView extends TextP protected int maxIndexTextWidth; - public TextPaneSkinListView(List list) { - super(list); + public TextPaneSkinListView(TextPaneSkin textPaneSkin, List list) { + super(textPaneSkin, list); } public int getMaxIndexTextWidth() { Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinNodeView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinNodeView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinNodeView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinNodeView.java Tue Dec 17 20:17:48 2013 @@ -18,19 +18,33 @@ package org.apache.pivot.wtk.skin; import java.awt.Graphics2D; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import org.apache.pivot.collections.HashMap; import org.apache.pivot.collections.Sequence; import org.apache.pivot.wtk.Bounds; import org.apache.pivot.wtk.Dimensions; import org.apache.pivot.wtk.Point; import org.apache.pivot.wtk.TextPane; +import org.apache.pivot.wtk.text.BulletedList; +import org.apache.pivot.wtk.text.ComponentNode; +import org.apache.pivot.wtk.text.Document; import org.apache.pivot.wtk.text.Element; +import org.apache.pivot.wtk.text.ImageNode; +import org.apache.pivot.wtk.text.List; import org.apache.pivot.wtk.text.Node; import org.apache.pivot.wtk.text.NodeListener; +import org.apache.pivot.wtk.text.NumberedList; +import org.apache.pivot.wtk.text.Paragraph; +import org.apache.pivot.wtk.text.TextNode; +import org.apache.pivot.wtk.text.TextSpan; /** * Abstract base class for node views. */ abstract class TextPaneSkinNodeView implements NodeListener { + protected final TextPaneSkin textPaneSkin; private Node node = null; private TextPaneSkinElementView parent = null; @@ -42,7 +56,8 @@ abstract class TextPaneSkinNodeView impl private boolean valid = false; - public TextPaneSkinNodeView(Node node) { + public TextPaneSkinNodeView(TextPaneSkin textPaneSkin, Node node) { + this.textPaneSkin = textPaneSkin; this.node = node; } @@ -59,7 +74,7 @@ abstract class TextPaneSkinNodeView impl } protected TextPaneSkin getTextPaneSkin() { - return getParent().getTextPaneSkin(); + return textPaneSkin; } protected void attach() { @@ -232,4 +247,41 @@ abstract class TextPaneSkinNodeView impl // No-op } + private static HashMap<Class<? extends Node>, Class<? extends TextPaneSkinNodeView>> + nodeViewSkinMap = new HashMap<>(); + static { + nodeViewSkinMap.put(Document.class, TextPaneSkinDocumentView.class); + nodeViewSkinMap.put(Paragraph.class, TextPaneSkinParagraphView.class); + nodeViewSkinMap.put(TextNode.class, TextPaneSkinTextNodeView.class); + nodeViewSkinMap.put(ImageNode.class, TextPaneSkinImageNodeView.class); + nodeViewSkinMap.put(ComponentNode.class, TextPaneSkinComponentNodeView.class); + nodeViewSkinMap.put(TextSpan.class, TextPaneSkinSpanView.class); + nodeViewSkinMap.put(NumberedList.class, TextPaneSkinNumberedListView.class); + nodeViewSkinMap.put(BulletedList.class, TextPaneSkinBulletedListView.class); + nodeViewSkinMap.put(List.Item.class, TextPaneSkinListItemView.class); + } + + public static TextPaneSkinNodeView createNodeView(TextPaneSkin textPaneSkin, Node node) { + TextPaneSkinNodeView nodeView = null; + + Class<? extends Node> nodeClass = node.getClass(); + Class<? extends TextPaneSkinNodeView> skinClass = nodeViewSkinMap.get(nodeClass); + if (skinClass != null) { + try { + Constructor<?> constructor = skinClass.getConstructor(TextPaneSkin.class, nodeClass); + nodeView = (TextPaneSkinNodeView)constructor.newInstance(textPaneSkin, node); + } catch (NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException ex) { + throw new RuntimeException("Error instantiating node view for " + + nodeClass.getName(), ex); + } + } + if (nodeView == null) { + throw new IllegalArgumentException("Unsupported node type: " + + nodeClass.getName()); + } + + return nodeView; + } + } Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinNumberedListView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinNumberedListView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinNumberedListView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinNumberedListView.java Tue Dec 17 20:17:48 2013 @@ -59,8 +59,8 @@ class TextPaneSkinNumberedListView exten return (char) ('A' + n - 1) + ""; } - public TextPaneSkinNumberedListView(NumberedList numberedList) { - super(numberedList); + public TextPaneSkinNumberedListView(TextPaneSkin textPaneSkin, NumberedList numberedList) { + super(textPaneSkin, numberedList); } @Override Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinParagraphView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinParagraphView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinParagraphView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinParagraphView.java Tue Dec 17 20:17:48 2013 @@ -54,8 +54,8 @@ class TextPaneSkinParagraphView extends private ArrayList<Row> rows = null; private Bounds terminatorBounds = new Bounds(0, 0, 0, 0); - public TextPaneSkinParagraphView(Paragraph paragraph) { - super(paragraph); + public TextPaneSkinParagraphView(TextPaneSkin textPaneSkin, Paragraph paragraph) { + super(textPaneSkin, paragraph); } @Override Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinSpanView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinSpanView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinSpanView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinSpanView.java Tue Dec 17 20:17:48 2013 @@ -25,8 +25,8 @@ import org.apache.pivot.wtk.text.TextSpa */ class TextPaneSkinSpanView extends TextPaneSkinElementView { - public TextPaneSkinSpanView(TextSpan span) { - super(span); + public TextPaneSkinSpanView(TextPaneSkin textPaneSkin, TextSpan span) { + super(textPaneSkin, span); } @Override Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinTextNodeView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinTextNodeView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinTextNodeView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinTextNodeView.java Tue Dec 17 20:17:48 2013 @@ -47,12 +47,12 @@ class TextPaneSkinTextNodeView extends T private GlyphVector glyphVector = null; private TextPaneSkinTextNodeView next = null; - public TextPaneSkinTextNodeView(TextNode textNode) { - this(textNode, 0); + public TextPaneSkinTextNodeView(TextPaneSkin textPaneSkin, TextNode textNode) { + this(textPaneSkin, textNode, 0); } - public TextPaneSkinTextNodeView(TextNode textNode, int start) { - super(textNode); + public TextPaneSkinTextNodeView(TextPaneSkin textPaneSkin, TextNode textNode, int start) { + super(textPaneSkin, textNode); this.start = start; } @@ -134,7 +134,7 @@ class TextPaneSkinTextNodeView extends T if (end < ci.getEndIndex()) { length = end - start; - next = new TextPaneSkinTextNodeView(textNode, end); + next = new TextPaneSkinTextNodeView(getTextPaneSkin(), textNode, end); next.setParent(getParent()); } else { length = ci.getEndIndex() - start; @@ -473,11 +473,18 @@ class TextPaneSkinTextNodeView extends T @Override public Bounds getCharacterBounds(int offset) { - Shape glyphBounds = glyphVector.getGlyphLogicalBounds(offset); + // If the offest == length, then use the right hand edge of the previous + // offset instead -- this is for positioning the caret at the end of the text + Shape glyphBounds = glyphVector.getGlyphLogicalBounds(offset == length ? offset - 1 : offset); Rectangle2D glyphBounds2D = glyphBounds.getBounds2D(); - return new Bounds((int) Math.floor(glyphBounds2D.getX()), 0, - (int) Math.ceil(glyphBounds2D.getWidth()), getHeight()); + if (offset == length) { + return new Bounds((int) Math.ceil(glyphBounds2D.getX() + glyphBounds2D.getWidth()), 0, + 1, getHeight()); + } else { + return new Bounds((int) Math.floor(glyphBounds2D.getX()), 0, + (int) Math.ceil(glyphBounds2D.getWidth()), getHeight()); + } } @Override Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinVerticalElementView.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinVerticalElementView.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinVerticalElementView.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextPaneSkinVerticalElementView.java Tue Dec 17 20:17:48 2013 @@ -27,8 +27,8 @@ import org.apache.pivot.wtk.text.Element */ abstract class TextPaneSkinVerticalElementView extends TextPaneSkinElementView { - public TextPaneSkinVerticalElementView(Element element) { - super(element); + public TextPaneSkinVerticalElementView(TextPaneSkin textPaneSkin, Element element) { + super(textPaneSkin, element); } @Override Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/text/PlainTextSerializer.java URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/text/PlainTextSerializer.java?rev=1551679&r1=1551678&r2=1551679&view=diff ============================================================================== --- pivot/trunk/wtk/src/org/apache/pivot/wtk/text/PlainTextSerializer.java (original) +++ pivot/trunk/wtk/src/org/apache/pivot/wtk/text/PlainTextSerializer.java Tue Dec 17 20:17:48 2013 @@ -39,6 +39,9 @@ public class PlainTextSerializer impleme public static final String MIME_TYPE = "text/plain"; public static final int BUFFER_SIZE = 2048; + private boolean expandTabs = false; + private int tabWidth = 4; + public PlainTextSerializer() { this(Charset.defaultCharset()); } @@ -55,6 +58,35 @@ public class PlainTextSerializer impleme this.charset = charset; } + public int getTabWidth() { + return tabWidth; + } + + public void setTabWidth(int tabWidth) { + if (tabWidth < 0) { + throw new IllegalArgumentException("tabWidth is negative."); + } + + this.tabWidth = tabWidth; + } + + public boolean getExpandTabs() { + return expandTabs; + } + + /** + * Sets whether tab characters (<code>\t</code>) are expanded to an + * appropriate number of spaces while reading the text. + * + * @param expandTabs <code>true</code> to replace tab characters with space + * characters (depending on the setting of the {@link #getTabWidth} value) + * or <code>false</code> to leave tabs alone. + */ + public void setExpandTabs(boolean expandTabs) { + this.expandTabs = expandTabs; + } + + @Override public Document readObject(InputStream inputStream) throws IOException { Reader reader = new InputStreamReader(inputStream, charset); @@ -70,6 +102,18 @@ public class PlainTextSerializer impleme String line = bufferedReader.readLine(); while (line != null) { + if (expandTabs) { + int ix = 0; + StringBuilder buf = new StringBuilder(line); + while ((ix = buf.indexOf("\t", ix)) >= 0) { + buf.deleteCharAt(ix); + int spaces = tabWidth - (ix % tabWidth); + for (int j = 0; j < spaces; j++) { + buf.insert(ix++, ' '); + } + } + line = buf.toString(); + } document.add(new Paragraph(line)); line = bufferedReader.readLine(); }