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: package javax.swing.text;
018:
019: import java.awt.Component;
020: import java.awt.Graphics;
021: import java.awt.Rectangle;
022: import java.awt.Shape;
023:
024: import javax.swing.SizeRequirements;
025: import javax.swing.event.DocumentEvent;
026: import javax.swing.event.DocumentEvent.ElementChange;
027: import javax.swing.text.Position.Bias;
028:
029: import org.apache.harmony.x.swing.SizeRequirementsHelper;
030: import org.apache.harmony.x.swing.Utilities;
031:
032: import org.apache.harmony.x.swing.internal.nls.Messages;
033:
034: public class BoxView extends CompositeView {
035: private static final int[] EMPTY_INT_ARRAY = new int[0];
036: private static final SizeRequirements[] EMPTY_REQUIREMENTS_ARRAY = new SizeRequirements[0];
037:
038: private int boxHeight;
039: private int boxWidth;
040:
041: private int majorAxis;
042:
043: private boolean majorLayoutValid;
044: private int[] majorOffsets = EMPTY_INT_ARRAY;
045: private SizeRequirements[] majorRequirements = EMPTY_REQUIREMENTS_ARRAY;
046: private boolean majorRequirementsValid;
047: private int[] majorSpans = EMPTY_INT_ARRAY;
048: private final SizeRequirements majorTotalRequirements = new SizeRequirements();
049: private boolean majorTotalRequirementsValid;
050:
051: private boolean minorLayoutValid;
052: private int[] minorOffsets = EMPTY_INT_ARRAY;
053: private SizeRequirements[] minorRequirements = EMPTY_REQUIREMENTS_ARRAY;
054: private boolean minorRequirementsValid;
055: private int[] minorSpans = EMPTY_INT_ARRAY;
056: private final SizeRequirements minorTotalRequirements = new SizeRequirements();
057: private boolean minorTotalRequirementsValid;
058:
059: public BoxView(final Element element, final int axis) {
060: super (element);
061: majorAxis = axis;
062: }
063:
064: @Override
065: public float getAlignment(final int axis) {
066: isAxisValid(axis);
067:
068: return getTotalRequirements(axis).alignment;
069: }
070:
071: public int getAxis() {
072: return majorAxis;
073: }
074:
075: @Override
076: public Shape getChildAllocation(final int index, final Shape shape) {
077: if (shape == null || !isLayoutValid()) {
078: return null;
079: }
080: return super .getChildAllocation(index, shape);
081: }
082:
083: @Override
084: public float getMinimumSpan(final int axis) {
085: isAxisValid(axis);
086:
087: return getTotalRequirements(axis).minimum + getSideInset(axis);
088: }
089:
090: @Override
091: public float getPreferredSpan(final int axis) {
092: isAxisValid(axis);
093:
094: return getTotalRequirements(axis).preferred
095: + getSideInset(axis);
096: }
097:
098: @Override
099: public float getMaximumSpan(final int axis) {
100: isAxisValid(axis);
101:
102: return Utilities.safeIntSum(getTotalRequirements(axis).maximum,
103: getSideInset(axis));
104: }
105:
106: @Override
107: public int getResizeWeight(final int axis) {
108: isAxisValid(axis);
109:
110: final SizeRequirements sr = getTotalRequirements(axis);
111: return sr.minimum == sr.maximum ? 0 : 1;
112: }
113:
114: public void layoutChanged(final int axis) {
115: if (isMajor(axis)) {
116: majorLayoutValid = false;
117: } else {
118: minorLayoutValid = false;
119: }
120: }
121:
122: @Override
123: public void paint(final Graphics g, final Shape shape) {
124: final Rectangle insideAlloc = getInsideAllocation(shape);
125: final Rectangle allocation = new Rectangle();
126: final Rectangle clipBounds = g.getClipBounds();
127:
128: for (int i = 0; i < getViewCount(); i++) {
129: allocation.setBounds(insideAlloc);
130: childAllocation(i, allocation);
131: if (allocation.intersects(clipBounds)) {
132: paintChild(g, allocation, i);
133: }
134: }
135: }
136:
137: @Override
138: public void preferenceChanged(final View child,
139: final boolean width, final boolean height) {
140: invalidateLayout(width, height);
141:
142: super .preferenceChanged(child, width, height);
143: }
144:
145: @Override
146: public void replace(final int index, final int length,
147: final View[] elems) {
148: super .replace(index, length, elems);
149:
150: int toAdd = elems != null ? elems.length : 0;
151: minorOffsets = resizeArray(minorOffsets, index, length, toAdd);
152: majorOffsets = resizeArray(majorOffsets, index, length, toAdd);
153: minorSpans = resizeArray(minorSpans, index, length, toAdd);
154: majorSpans = resizeArray(majorSpans, index, length, toAdd);
155:
156: minorRequirements = resizeArray(minorRequirements, index,
157: length, toAdd);
158: majorRequirements = resizeArray(majorRequirements, index,
159: length, toAdd);
160:
161: invalidateLayout(true, true);
162: }
163:
164: public void setAxis(final int axis) {
165: majorAxis = axis;
166: invalidateLayout(true, true);
167: }
168:
169: @Override
170: public void setSize(final float width, final float height) {
171: layout((int) (width - getSideInset(X_AXIS)),
172: (int) (height - getSideInset(Y_AXIS)));
173: }
174:
175: public int getWidth() {
176: return boxWidth;
177: }
178:
179: public int getHeight() {
180: return boxHeight;
181: }
182:
183: @Override
184: public Shape modelToView(final int pos, final Shape shape,
185: final Bias bias) throws BadLocationException {
186: final Rectangle bounds = shape.getBounds();
187: setSize(bounds.width, bounds.height);
188: return super .modelToView(pos, shape, bias);
189: }
190:
191: @Override
192: public int viewToModel(final float x, final float y,
193: final Shape shape, final Bias[] bias) {
194: final Rectangle bounds = shape.getBounds();
195: setSize(bounds.width, bounds.height);
196: return super .viewToModel(x, y, shape, bias);
197: }
198:
199: protected void baselineLayout(final int targetSpan, final int axis,
200: final int[] offsets, final int[] spans) {
201: SizeRequirements[] srs = getRequirements(axis);
202: SizeRequirementsHelper.calculateAlignedPositions(targetSpan,
203: getTotalRequirements(axis), srs, offsets, spans);
204: }
205:
206: protected SizeRequirements baselineRequirements(final int axis,
207: final SizeRequirements r) {
208: final SizeRequirements[] sr = getRequirements(axis);
209: return SizeRequirementsHelper.getAlignedSizeRequirements(sr, r,
210: true);
211: }
212:
213: protected SizeRequirements calculateMajorAxisRequirements(
214: final int axis, final SizeRequirements r) {
215: final SizeRequirements[] sr = getRequirements(axis);
216: return SizeRequirementsHelper.getTiledSizeRequirements(sr, r);
217: }
218:
219: protected SizeRequirements calculateMinorAxisRequirements(
220: final int axis, final SizeRequirements r) {
221: final SizeRequirements result = r != null ? r
222: : new SizeRequirements();
223: final SizeRequirements[] children = getRequirements(axis);
224:
225: int min = 0;
226: int pref = 0;
227:
228: final int count = getViewCount();
229: for (int i = 0; i < count; i++) {
230: min = Math.max(min, children[i].minimum);
231: pref = Math.max(pref, children[i].preferred);
232: }
233:
234: result.minimum = min;
235: result.preferred = pref;
236: result.maximum = Integer.MAX_VALUE;
237: result.alignment = ALIGN_CENTER;
238:
239: return result;
240: }
241:
242: @Override
243: protected void childAllocation(final int index,
244: final Rectangle alloc) {
245: if (isLayoutValid()) {
246: alloc.x += getOffset(X_AXIS, index);
247: alloc.y += getOffset(Y_AXIS, index);
248: alloc.width = getSpan(X_AXIS, index);
249: alloc.height = getSpan(Y_AXIS, index);
250: } else {
251: alloc.width = 0;
252: alloc.height = 0;
253: }
254: }
255:
256: @Override
257: protected boolean flipEastAndWestAtEnds(final int position,
258: final Bias bias) {
259: if (isMajor(X_AXIS)) {
260: return false;
261: }
262:
263: View child = getView(getViewIndexAtPosition(bias == Bias.Backward ? position - 1
264: : position));
265: return child instanceof CompositeView
266: && ((CompositeView) child).flipEastAndWestAtEnds(
267: position, bias);
268: }
269:
270: @Override
271: protected void forwardUpdate(final ElementChange change,
272: final DocumentEvent event, final Shape shape,
273: final ViewFactory factory) {
274: boolean allocValid = isLayoutValid(majorAxis);
275:
276: super .forwardUpdate(change, event, shape, factory);
277:
278: if (allocValid && !isLayoutValid(majorAxis)) {
279: Component component = getComponent();
280: if (component != null) {
281: int index = getViewIndexAtPosition(event.getOffset());
282:
283: Rectangle rect = getInsideAllocation(shape);
284: int viewOffset = getOffset(majorAxis, index);
285: if (majorAxis == Y_AXIS) {
286: rect.y += viewOffset;
287: rect.height -= viewOffset;
288: } else {
289: rect.x += viewOffset;
290: rect.width -= viewOffset;
291: }
292: component.repaint(rect.x, rect.y, rect.width,
293: rect.height);
294: }
295: }
296: }
297:
298: protected int getOffset(final int axis, final int childIndex) {
299: return isMajor(axis) ? majorOffsets[childIndex]
300: : minorOffsets[childIndex];
301: }
302:
303: protected int getSpan(final int axis, final int childIndex) {
304: return isMajor(axis) ? majorSpans[childIndex]
305: : minorSpans[childIndex];
306: }
307:
308: @Override
309: protected View getViewAtPoint(final int x, final int y,
310: final Rectangle alloc) {
311: final int location = isMajor(Y_AXIS) ? y - alloc.y : x
312: - alloc.x;
313:
314: if (location < 0) {
315: return getViewWithAllocation(0, alloc);
316: }
317:
318: final int lastIndex = getViewCount() - 1;
319: if (location >= getOffset(majorAxis, lastIndex)) {
320: return getViewWithAllocation(lastIndex, alloc);
321: }
322:
323: for (int i = 0; i <= lastIndex; i++) {
324: if (getOffset(majorAxis, i) <= location
325: && location < getOffset(majorAxis, i)
326: + getSpan(majorAxis, i)) {
327:
328: return getViewWithAllocation(i, alloc);
329: }
330: }
331: return null;
332: }
333:
334: @Override
335: protected boolean isBefore(final int x, final int y,
336: final Rectangle innerAlloc) {
337: return isMajor(X_AXIS) ? x < innerAlloc.x : y < innerAlloc.y;
338: }
339:
340: @Override
341: protected boolean isAfter(final int x, final int y,
342: final Rectangle innerAlloc) {
343: return isMajor(X_AXIS) ? x > (innerAlloc.x + innerAlloc.width)
344: : y > (innerAlloc.y + innerAlloc.height);
345: }
346:
347: protected boolean isAllocationValid() {
348: return isLayoutValid();
349: }
350:
351: protected boolean isLayoutValid(final int axis) {
352: return isMajor(axis) ? majorLayoutValid : minorLayoutValid;
353: }
354:
355: /**
356: * This method may cause stack overflow if upon each layout try a child
357: * changes its preferences, i.e. <code>preferenceChanged</code> is called.
358: */
359: protected void layout(final int width, final int height) {
360: final boolean layoutX = !isLayoutValid(X_AXIS)
361: || width != boxWidth;
362: final boolean layoutY = !isLayoutValid(Y_AXIS)
363: || height != boxHeight;
364:
365: boxWidth = width;
366: boxHeight = height;
367:
368: final boolean isMajorX = isMajor(X_AXIS);
369:
370: if (layoutX) {
371: if (isMajorX) {
372: layoutMajorAxis(width, X_AXIS, majorOffsets, majorSpans);
373: } else {
374: layoutMinorAxis(width, X_AXIS, minorOffsets, minorSpans);
375: }
376: }
377:
378: if (layoutY) {
379: if (!isMajorX) {
380: layoutMajorAxis(height, Y_AXIS, majorOffsets,
381: majorSpans);
382: } else {
383: layoutMinorAxis(height, Y_AXIS, minorOffsets,
384: minorSpans);
385: }
386: }
387:
388: majorLayoutValid = true;
389: minorLayoutValid = true;
390:
391: if (layoutX || layoutY) {
392: for (int i = 0; i < getViewCount(); i++) {
393: getView(i).setSize(getSpan(X_AXIS, i),
394: getSpan(Y_AXIS, i));
395: }
396: }
397:
398: // The following may cause stack overflow
399: if (!isLayoutValid()) {
400: layout(width, height);
401: }
402: }
403:
404: protected void layoutMajorAxis(final int targetSpan,
405: final int axis, final int[] offsets, final int[] spans) {
406: SizeRequirementsHelper.calculateTiledPositions(targetSpan,
407: getTotalRequirements(axis), majorRequirements, offsets,
408: spans, true);
409: }
410:
411: protected void layoutMinorAxis(final int targetSpan,
412: final int axis, final int[] offsets, final int[] spans) {
413: final SizeRequirements[] sr = getRequirements(axis);
414:
415: final int count = getViewCount();
416: for (int i = 0; i < count; i++) {
417: if (targetSpan >= sr[i].maximum) {
418: spans[i] = sr[i].maximum;
419: } else if (targetSpan >= sr[i].minimum) {
420: spans[i] = targetSpan;
421: } else {
422: spans[i] = sr[i].minimum;
423: }
424:
425: offsets[i] = (int) (sr[i].alignment * (targetSpan - spans[i]));
426: if (offsets[i] < 0) {
427: offsets[i] = 0;
428: }
429: }
430:
431: }
432:
433: protected void paintChild(final Graphics g, final Rectangle alloc,
434: final int index) {
435: getView(index).paint(g, alloc);
436: }
437:
438: private void fillRequirements(
439: final SizeRequirements[] requirements, final int axis) {
440: for (int i = 0; i < getViewCount(); i++) {
441: View child = getView(i);
442:
443: if (requirements[i] == null) {
444: requirements[i] = new SizeRequirements();
445: }
446:
447: requirements[i].minimum = (int) child.getMinimumSpan(axis);
448: requirements[i].preferred = (int) child
449: .getPreferredSpan(axis);
450: requirements[i].maximum = (int) child.getMaximumSpan(axis);
451: requirements[i].alignment = child.getAlignment(axis);
452: }
453: }
454:
455: private SizeRequirements[] getRequirements(final int axis) {
456: if (isMajor(axis)) {
457: if (!majorRequirementsValid) {
458: fillRequirements(majorRequirements, axis);
459: majorRequirementsValid = true;
460: }
461: return majorRequirements;
462: } else {
463: if (!minorRequirementsValid) {
464: fillRequirements(minorRequirements, axis);
465: minorRequirementsValid = true;
466: }
467: return minorRequirements;
468: }
469: }
470:
471: private SizeRequirements getTotalRequirements(final int axis) {
472: if (isMajor(axis)) {
473: if (!majorTotalRequirementsValid) {
474: calculateMajorAxisRequirements(axis,
475: majorTotalRequirements);
476: majorTotalRequirementsValid = true;
477: }
478: return majorTotalRequirements;
479: } else {
480: if (!minorTotalRequirementsValid) {
481: calculateMinorAxisRequirements(axis,
482: minorTotalRequirements);
483: minorTotalRequirementsValid = true;
484: }
485: return minorTotalRequirements;
486: }
487: }
488:
489: private View getViewWithAllocation(final int index,
490: final Rectangle alloc) {
491: childAllocation(index, alloc);
492: return getView(index);
493: }
494:
495: private void isAxisValid(final int axis) {
496: if (axis != X_AXIS && axis != Y_AXIS) {
497: throw new IllegalArgumentException(Messages
498: .getString("swing.81")); //$NON-NLS-1$
499: }
500: }
501:
502: private boolean isMajor(final int axis) {
503: return majorAxis == axis;
504: }
505:
506: private boolean isLayoutValid() {
507: return isLayoutValid(X_AXIS) && isLayoutValid(Y_AXIS);
508: }
509:
510: private void invalidateLayout(final boolean width,
511: final boolean height) {
512: final boolean majorX = isMajor(X_AXIS);
513:
514: if ((majorX && width) || (!majorX && height)) {
515: majorLayoutValid = false;
516: majorRequirementsValid = false;
517: majorTotalRequirementsValid = false;
518: }
519:
520: if ((majorX && height) || (!majorX && width)) {
521: minorLayoutValid = false;
522: minorRequirementsValid = false;
523: minorTotalRequirementsValid = false;
524: }
525: }
526:
527: /**
528: * Resizes an array of <code>int</code>s.
529: *
530: * @param array the original array
531: * @param index index where elements will be removed and added
532: * @param toRemove the number of items to remove
533: * @param toAdd the number of items to add
534: * @return array with the new size,
535: * may be the same as <code>array</code> parameter
536: */
537: private int[] resizeArray(final int[] array, final int index,
538: final int toRemove, final int toAdd) {
539: if (toRemove == toAdd) {
540: return array;
541: }
542:
543: int size = array.length - toRemove + toAdd;
544: if (size == 0) {
545: return EMPTY_INT_ARRAY;
546: }
547:
548: int[] result = new int[size];
549: System.arraycopy(array, 0, result, 0, index);
550: System.arraycopy(array, index + toRemove, result,
551: index + toAdd, array.length - (index + toRemove));
552: return result;
553: }
554:
555: /**
556: * Resizes an array of size requirements.
557: * <small>(Probably may be optimized to not copy old array contents)</small>
558: *
559: * @param array the array to resize
560: * @param index index where remove and add operations occur
561: * @param toRemove the number of items to remove
562: * @param toAdd the number of items to add
563: * @return array with the new size
564: */
565: private SizeRequirements[] resizeArray(
566: final SizeRequirements[] array, final int index,
567: final int toRemove, final int toAdd) {
568: if (toRemove == toAdd) {
569: return array;
570: }
571:
572: int size = array.length - toRemove + toAdd;
573: if (size == 0) {
574: return EMPTY_REQUIREMENTS_ARRAY;
575: }
576:
577: SizeRequirements[] newArray = new SizeRequirements[size];
578: System.arraycopy(array, 0, newArray, 0, index);
579: System.arraycopy(array, index + toRemove, newArray, index
580: + toAdd, array.length - (index + toRemove));
581: return newArray;
582: }
583:
584: private int getSideInset(final int axis) {
585: if (axis == X_AXIS) {
586: return getLeftInset() + getRightInset();
587: }
588: return getTopInset() + getBottomInset();
589: }
590: }
|