001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Alexey A. Ivanov
019: * @version $Revision$
020: */package javax.swing.text;
021:
022: import java.awt.Color;
023: import java.awt.Component;
024: import java.awt.Container;
025: import java.awt.Graphics;
026: import java.awt.Rectangle;
027: import java.awt.Shape;
028: import java.text.AttributedString;
029: import java.util.Arrays;
030: import java.util.Iterator;
031: import java.util.LinkedList;
032: import java.util.List;
033:
034: import javax.swing.SwingConstants;
035: import javax.swing.event.DocumentEvent;
036: import javax.swing.event.DocumentEvent.ElementChange;
037: import javax.swing.event.DocumentEvent.EventType;
038: import javax.swing.text.Position.Bias;
039: import javax.swing.text.TextInterval.TextIntervalPainter;
040:
041: import org.apache.harmony.awt.text.ComposedTextParams;
042: import org.apache.harmony.awt.text.TextKit;
043: import org.apache.harmony.awt.text.TextUtils;
044:
045: public abstract class View implements SwingConstants {
046:
047: private class TextPainter implements TextIntervalPainter {
048: private final TextPaintParams tpp;
049:
050: TextPainter(final TextPaintParams tpp) {
051: this .tpp = tpp;
052: }
053:
054: public int paintSelected(final Graphics g, final int start,
055: final int end, final int x, final int y)
056: throws BadLocationException {
057:
058: return drawSelectedText(g, x, y, start, end);
059: }
060:
061: public int paintUnselected(final Graphics g, final int start,
062: final int end, final int x, final int y)
063: throws BadLocationException {
064:
065: return drawUnselectedText(g, x, y, start, end);
066: }
067:
068: public int paintComposed(final Graphics g, final int start,
069: final int end, final int x, final int y)
070: throws BadLocationException {
071:
072: return drawComposedText(g, tpp.color, tpp.composedText, x,
073: y);
074: }
075: }
076:
077: public static final int BadBreakWeight = 0;
078: public static final int GoodBreakWeight = 1000;
079: public static final int ExcellentBreakWeight = 2000;
080: public static final int ForcedBreakWeight = 3000;
081:
082: public static final int X_AXIS = 0;
083: public static final int Y_AXIS = 1;
084:
085: static final float ALIGN_LEFT = 0.0f;
086: static final float ALIGN_CENTER = 0.5f;
087: static final float ALIGN_RIGHT = 1.0f;
088:
089: Component component;
090:
091: private Element element;
092: private View parent;
093:
094: public View(final Element element) {
095: this .element = element;
096: }
097:
098: public abstract float getPreferredSpan(final int axis);
099:
100: public abstract Shape modelToView(final int pos, final Shape shape,
101: final Bias bias) throws BadLocationException;
102:
103: public abstract int viewToModel(final float x, final float y,
104: final Shape shape, final Bias[] biasReturn);
105:
106: public abstract void paint(final Graphics g, final Shape shape);
107:
108: public View breakView(final int axis, final int offset,
109: final float pos, final float len) {
110: return this ;
111: }
112:
113: public View createFragment(final int startOffset,
114: final int endOffset) {
115: return this ;
116: }
117:
118: public float getAlignment(final int axis) {
119: return ALIGN_CENTER;
120: }
121:
122: public AttributeSet getAttributes() {
123: return getElement().getAttributes();
124: }
125:
126: public int getBreakWeight(final int axis, final float pos,
127: final float len) {
128: return getPreferredSpan(axis) < len ? GoodBreakWeight
129: : BadBreakWeight;
130: }
131:
132: public Shape getChildAllocation(final int index, final Shape shape) {
133: return null;
134: }
135:
136: public Container getContainer() {
137: return getParent() != null ? getParent().getContainer() : null;
138: }
139:
140: public Document getDocument() {
141: return getElement().getDocument();
142: }
143:
144: public Element getElement() {
145: return element;
146: }
147:
148: public int getStartOffset() {
149: return getElement().getStartOffset();
150: }
151:
152: public int getEndOffset() {
153: return getElement().getEndOffset();
154: }
155:
156: public Graphics getGraphics() {
157: return getComponent().getGraphics();
158: }
159:
160: /**
161: * If view is not resizable, returns <code>getPreferredSpan(axis)</code>.
162: * Otherwise (resizable), returns 0.
163: */
164: public float getMinimumSpan(final int axis) {
165: return getResizeWeight(axis) <= 0 ? getPreferredSpan(axis) : 0;
166: }
167:
168: /**
169: * If view is not resizable, returns <code>getPreferredSpan(axis)</code>.
170: * Otherwise (resizable), returns Integer.MAX_VALUE.
171: */
172: public float getMaximumSpan(final int axis) {
173: return getResizeWeight(axis) <= 0 ? getPreferredSpan(axis)
174: : Integer.MAX_VALUE;
175: }
176:
177: public int getNextVisualPositionFrom(final int pos,
178: final Bias bias, final Shape shape, final int direction,
179: final Bias[] biasRet) throws BadLocationException {
180:
181: return TextUtils.getNextVisualPositionFrom(getTextKit(), this ,
182: pos, bias, shape, direction, biasRet);
183: }
184:
185: public View getParent() {
186: return parent;
187: }
188:
189: public int getResizeWeight(final int axis) {
190: return 0;
191: }
192:
193: public String getToolTipText(final float x, final float y,
194: final Shape shape) {
195: final int index = getViewIndex(x, y, shape);
196: if (index == -1) {
197: return null;
198: }
199:
200: final View view = getView(index);
201: return view != null ? view.getToolTipText(x, y,
202: getChildAllocation(index, shape)) : null;
203: }
204:
205: public View getView(final int index) {
206: return null;
207: }
208:
209: public int getViewCount() {
210: return 0;
211: }
212:
213: public ViewFactory getViewFactory() {
214: return getParent() != null ? getParent().getViewFactory()
215: : null;
216: }
217:
218: public int getViewIndex(final float x, final float y,
219: final Shape shape) {
220: if (shape == null) {
221: return -1;
222: }
223:
224: final int count = getViewCount();
225: for (int i = 0; i < count; i++) {
226: final Shape childAllocation = getChildAllocation(i, shape);
227: if (childAllocation != null
228: && childAllocation.contains(x, y)) {
229: return i;
230: }
231: }
232: return -1;
233: }
234:
235: public int getViewIndex(final int pos, final Bias bias) {
236: return -1;
237: }
238:
239: public void changedUpdate(final DocumentEvent event,
240: final Shape shape, final ViewFactory factory) {
241: updateView(event, shape, factory);
242: }
243:
244: public void insertUpdate(final DocumentEvent event,
245: final Shape shape, final ViewFactory factory) {
246: updateView(event, shape, factory);
247: }
248:
249: public void removeUpdate(final DocumentEvent event,
250: final Shape shape, final ViewFactory factory) {
251: updateView(event, shape, factory);
252: }
253:
254: public boolean isVisible() {
255: return true;
256: }
257:
258: public void preferenceChanged(final View child,
259: final boolean width, final boolean height) {
260: if (getParent() != null) {
261: getParent().preferenceChanged(this , width, height);
262: }
263: }
264:
265: public void append(final View view) {
266: replace(getViewCount(), 0, new View[] { view });
267: }
268:
269: public void insert(final int index, final View view) {
270: replace(index, 0, new View[] { view });
271: }
272:
273: public void remove(final int index) {
274: replace(index, 1, null);
275: }
276:
277: public void removeAll() {
278: replace(0, getViewCount(), null);
279: }
280:
281: public void replace(final int index, final int length,
282: final View[] views) {
283: }
284:
285: public void setParent(final View parent) {
286: this .parent = parent;
287: if (parent == null) {
288: for (int i = 0; i < getViewCount(); i++) {
289: getView(i).setParent(null);
290: }
291: }
292: }
293:
294: public void setSize(final float width, final float height) {
295: }
296:
297: public Shape modelToView(final int p1, final Bias b1, final int p2,
298: final Bias b2, final Shape shape)
299: throws BadLocationException {
300: Rectangle r1 = modelToView(p1, shape, b1).getBounds();
301: Rectangle r2 = modelToView(p2, shape, b2).getBounds();
302: return r1.union(r2);
303: }
304:
305: /**
306: * @deprecated
307: */
308: public Shape modelToView(final int pos, final Shape shape)
309: throws BadLocationException {
310:
311: return modelToView(pos, shape, Bias.Forward);
312: }
313:
314: /**
315: * @deprecated
316: */
317: public int viewToModel(final float x, final float y,
318: final Shape shape) {
319: return viewToModel(x, y, shape, new Bias[] { Bias.Forward });
320: }
321:
322: /**
323: * The event is forwarded to all child views that lie in the range of
324: * the change, i.e. <code>event.getOffset()</code> up to
325: * <code>(event.getOffset() + event.getLength())</code>.
326: * <p>
327: * If <code>event.getOffset()</code> is boundary of children, then
328: * the previous child is included to the update range.
329: * <p>
330: * If <code>change</code> is not <code>null</code>, children that
331: * have just been added by <code>updateChildren</code> are excluded
332: * from the update range.
333: * <p>
334: * <code>change</code> can be <code>null</code> if the element this view
335: * represents has not changed, or if its children represent portions of
336: * elements.
337: *
338: * @param change is always <code>null</code>
339: * if <code>updateChildren</code> returned <code>false</code>,
340: * otherwise it has the value returned by
341: * <code>event.getChange(getElement())</code>.
342: */
343: protected void forwardUpdate(
344: final DocumentEvent.ElementChange change,
345: final DocumentEvent event, final Shape shape,
346: final ViewFactory factory) {
347: final int offset = event.getOffset();
348: int start = getViewIndex(offset, Bias.Forward);
349: if (start < 0) {
350: start = 0;
351: }
352: int end = event.getType() == EventType.REMOVE ? start
353: : getViewIndex(offset + event.getLength(), Bias.Forward);
354: if (end < 0) {
355: end = getViewCount() - 1;
356: }
357:
358: if (start > 0 && getView(start - 1).getEndOffset() == offset) {
359: --start;
360: }
361:
362: if (change != null) {
363: end -= change.getChildrenAdded().length;
364: }
365:
366: for (int i = start; i <= end; i++) {
367: forwardUpdateToView(getView(i), event, getChildAllocation(
368: i, shape), factory);
369: }
370: }
371:
372: protected void forwardUpdateToView(final View view,
373: final DocumentEvent event, final Shape shape,
374: final ViewFactory factory) {
375: final DocumentEvent.EventType type = event.getType();
376:
377: if (type == DocumentEvent.EventType.INSERT) {
378: view.insertUpdate(event, shape, factory);
379: } else if (type == DocumentEvent.EventType.REMOVE) {
380: view.removeUpdate(event, shape, factory);
381: } else if (type == DocumentEvent.EventType.CHANGE) {
382: view.changedUpdate(event, shape, factory);
383: }
384: assert false : "Unknown document event type. "
385: + "Valid values are EventType.INSERT, REMOVE, CHANGE";
386: }
387:
388: protected boolean updateChildren(
389: final DocumentEvent.ElementChange change,
390: final DocumentEvent event, final ViewFactory factory) {
391: final Element[] added = change.getChildrenAdded();
392: View[] views = null;
393: if (added != null && added.length > 0) {
394: views = new View[added.length];
395: for (int i = 0; i < added.length; i++) {
396: views[i] = factory.create(added[i]);
397: }
398: }
399: replace(change.getIndex(), change.getChildrenRemoved().length,
400: views);
401: return true;
402: }
403:
404: protected void updateLayout(
405: final DocumentEvent.ElementChange change,
406: final DocumentEvent event, final Shape shape) {
407: if (change != null) {
408: preferenceChanged(null, true, true);
409: final Component c = getComponent();
410: if (c != null) {
411: c.repaint();
412: }
413: }
414: }
415:
416: final int drawComposedText(final Graphics g, final Color color,
417: final ComposedTextParams composedTextParams, final int x,
418: final int y) {
419: final Color oldColor = g.getColor();
420: g.setColor(color);
421: AttributedString text = composedTextParams.getComposedText();
422: int result = text == null ? x : TextUtils.drawComposedText(
423: getTextKit(), text, g, x, y);
424: g.setColor(oldColor);
425: return result;
426: }
427:
428: void drawLine(final TextPaintParams tpp, final int start,
429: final int end, final Graphics g, final int x, final int y) {
430:
431: TextPainter painter = new TextPainter(tpp);
432: UnselectedTextInterval ui = new UnselectedTextInterval(start,
433: end, painter);
434: SelectedTextInterval si = new SelectedTextInterval(
435: tpp.selStart, tpp.selEnd, painter);
436:
437: TextInterval[] uisi = ui.dissect(si);
438: List intervals;
439:
440: if (tpp.composedText != null) {
441: intervals = new LinkedList();
442: ComposedTextInterval ci = new ComposedTextInterval(
443: tpp.composedStart, tpp.composedEnd, painter);
444: for (int i = 0; i < uisi.length; i++) {
445: TextInterval[] uisici = uisi[i].dissect(ci);
446: for (int j = 0; j < uisici.length; j++) {
447: if (!intervals.contains(uisici[j])) {
448: intervals.add(uisici[j]);
449: }
450: }
451: }
452: } else {
453: intervals = Arrays.asList(uisi);
454: }
455:
456: try {
457: int nextX = x;
458: for (Iterator it = intervals.iterator(); it.hasNext();) {
459: TextInterval interval = (TextInterval) it.next();
460: nextX = interval.paint(g, nextX, y);
461: }
462: } catch (BadLocationException e) {
463: }
464: }
465:
466: int drawSelectedText(final Graphics g, final int x, final int y,
467: final int start, final int end) throws BadLocationException {
468:
469: return 0;
470: }
471:
472: final int drawText(final Graphics g, final Color color,
473: final TextPaintParams tpp, final int x, final int y,
474: final int start, final int end) throws BadLocationException {
475:
476: getDocument().getText(start, end - start, tpp.buffer);
477:
478: final Color oldColor = g.getColor();
479: g.setColor(color);
480: int result = TextUtils.drawTabbedText(tpp.buffer, x, y, g,
481: (TabExpander) tpp.view, start);
482: g.setColor(oldColor);
483: return result;
484:
485: }
486:
487: int drawUnselectedText(final Graphics g, final int x, final int y,
488: final int start, final int end) throws BadLocationException {
489:
490: return 0;
491: }
492:
493: Component getComponent() {
494: final Component comp = getContainer();
495: if (comp == null) {
496: return getParent() != null ? getParent().getComponent()
497: : null;
498: } else {
499: return comp;
500: }
501: }
502:
503: final TextKit getTextKit() {
504: final Component c = getComponent();
505: return c != null ? TextUtils.getTextKit(getComponent()) : null;
506: }
507:
508: private void updateView(final DocumentEvent event,
509: final Shape shape, final ViewFactory factory) {
510: if (getViewCount() == 0) {
511: return;
512: }
513:
514: ElementChange change = event.getChange(getElement());
515: if (change != null && !updateChildren(change, event, factory)) {
516: // updateChildren returned false, then forwardUpdate and
517: // updateLayout must be passed null as change despite
518: // change is not null
519: change = null;
520: }
521:
522: forwardUpdate(change, event, shape, factory);
523: updateLayout(change, event, shape);
524: }
525: }
|