Frames | No Frames |
1: /* CompositeView.java -- An abstract view that manages child views 2: Copyright (C) 2005, 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.text; 40: 41: import java.awt.Rectangle; 42: import java.awt.Shape; 43: 44: import javax.swing.SwingConstants; 45: 46: /** 47: * An abstract base implementation of {@link View} that manages child 48: * <code>View</code>s. 49: * 50: * @author Roman Kennke (roman@kennke.org) 51: */ 52: public abstract class CompositeView 53: extends View 54: { 55: 56: /** 57: * The child views of this <code>CompositeView</code>. 58: */ 59: private View[] children; 60: 61: /** 62: * The number of child views. 63: */ 64: private int numChildren; 65: 66: /** 67: * The allocation of this <code>View</code> minus its insets. This is 68: * initialized in {@link #getInsideAllocation} and reused and modified in 69: * {@link #childAllocation(int, Rectangle)}. 70: */ 71: private final Rectangle insideAllocation = new Rectangle(); 72: 73: /** 74: * The insets of this <code>CompositeView</code>. This is initialized 75: * in {@link #setInsets}. 76: */ 77: private short top; 78: private short bottom; 79: private short left; 80: private short right; 81: 82: /** 83: * Creates a new <code>CompositeView</code> for the given 84: * <code>Element</code>. 85: * 86: * @param element the element that is rendered by this CompositeView 87: */ 88: public CompositeView(Element element) 89: { 90: super(element); 91: children = new View[0]; 92: top = 0; 93: bottom = 0; 94: left = 0; 95: right = 0; 96: } 97: 98: /** 99: * Loads the child views of this <code>CompositeView</code>. This method 100: * is called from {@link #setParent} to initialize the child views of 101: * this composite view. 102: * 103: * @param f the view factory to use for creating new child views 104: * 105: * @see #setParent 106: */ 107: protected void loadChildren(ViewFactory f) 108: { 109: if (f != null) 110: { 111: Element el = getElement(); 112: int count = el.getElementCount(); 113: View[] newChildren = new View[count]; 114: for (int i = 0; i < count; ++i) 115: { 116: Element child = el.getElement(i); 117: View view = f.create(child); 118: newChildren[i] = view; 119: } 120: // I'd have called replace(0, getViewCount(), newChildren) here 121: // in order to replace all existing views. However according to 122: // Harmony's tests this is not what the RI does. 123: replace(0, 0, newChildren); 124: } 125: } 126: 127: /** 128: * Sets the parent of this <code>View</code>. 129: * In addition to setting the parent, this calls {@link #loadChildren}, if 130: * this <code>View</code> does not already have its children initialized. 131: * 132: * @param parent the parent to set 133: */ 134: public void setParent(View parent) 135: { 136: super.setParent(parent); 137: if (parent != null && numChildren == 0) 138: loadChildren(getViewFactory()); 139: } 140: 141: /** 142: * Returns the number of child views. 143: * 144: * @return the number of child views 145: */ 146: public int getViewCount() 147: { 148: return numChildren; 149: } 150: 151: /** 152: * Returns the child view at index <code>n</code>. 153: * 154: * @param n the index of the requested child view 155: * 156: * @return the child view at index <code>n</code> 157: */ 158: public View getView(int n) 159: { 160: return children[n]; 161: } 162: 163: /** 164: * Replaces child views by some other child views. If there are no views to 165: * remove (<code>length == 0</code>), the result is a simple insert, if 166: * there are no children to add (<code>view == null</code>) the result 167: * is a simple removal. 168: * 169: * @param offset the start offset from where to remove children 170: * @param length the number of children to remove 171: * @param views the views that replace the removed children 172: */ 173: public void replace(int offset, int length, View[] views) 174: { 175: // Make sure we have an array. The Harmony testsuite indicates that we 176: // have to do something like this. 177: if (views == null) 178: views = new View[0]; 179: 180: // First we set the parent of the removed children to null. 181: int endOffset = offset + length; 182: for (int i = offset; i < endOffset; ++i) 183: { 184: if (children[i].getParent() == this) 185: children[i].setParent(null); 186: children[i] = null; 187: } 188: 189: // Update the children array. 190: int delta = views.length - length; 191: int src = offset + length; 192: int numMove = numChildren - src; 193: int dst = src + delta; 194: if (numChildren + delta > children.length) 195: { 196: // Grow array. 197: int newLength = Math.max(2 * children.length, numChildren + delta); 198: View[] newChildren = new View[newLength]; 199: System.arraycopy(children, 0, newChildren, 0, offset); 200: System.arraycopy(views, 0, newChildren, offset, views.length); 201: System.arraycopy(children, src, newChildren, dst, numMove); 202: children = newChildren; 203: } 204: else 205: { 206: // Patch existing array. 207: System.arraycopy(children, src, children, dst, numMove); 208: System.arraycopy(views, 0, children, offset, views.length); 209: } 210: numChildren += delta; 211: 212: // Finally we set the parent of the added children to this. 213: for (int i = 0; i < views.length; ++i) 214: views[i].setParent(this); 215: } 216: 217: /** 218: * Returns the allocation for the specified child <code>View</code>. 219: * 220: * @param index the index of the child view 221: * @param a the allocation for this view 222: * 223: * @return the allocation for the specified child <code>View</code> 224: */ 225: public Shape getChildAllocation(int index, Shape a) 226: { 227: Rectangle r = getInsideAllocation(a); 228: childAllocation(index, r); 229: return r; 230: } 231: 232: /** 233: * Maps a position in the document into the coordinate space of the View. 234: * The output rectangle usually reflects the font height but has a width 235: * of zero. 236: * 237: * @param pos the position of the character in the model 238: * @param a the area that is occupied by the view 239: * @param bias either {@link Position.Bias#Forward} or 240: * {@link Position.Bias#Backward} depending on the preferred 241: * direction bias. If <code>null</code> this defaults to 242: * <code>Position.Bias.Forward</code> 243: * 244: * @return a rectangle that gives the location of the document position 245: * inside the view coordinate space 246: * 247: * @throws BadLocationException if <code>pos</code> is invalid 248: * @throws IllegalArgumentException if b is not one of the above listed 249: * valid values 250: */ 251: public Shape modelToView(int pos, Shape a, Position.Bias bias) 252: throws BadLocationException 253: { 254: boolean backward = bias == Position.Bias.Backward; 255: int testpos = backward ? Math.max(0, pos - 1) : pos; 256: 257: Shape ret = null; 258: if (! backward || testpos >= getStartOffset()) 259: { 260: int childIndex = getViewIndexAtPosition(testpos); 261: if (childIndex != -1 && childIndex < getViewCount()) 262: { 263: View child = getView(childIndex); 264: if (child != null && testpos >= child.getStartOffset() 265: && testpos < child.getEndOffset()) 266: { 267: Shape childAlloc = getChildAllocation(childIndex, a); 268: if (childAlloc != null) 269: { 270: ret = child.modelToView(pos, childAlloc, bias); 271: // Handle corner case. 272: if (ret == null && child.getEndOffset() == pos) 273: { 274: childIndex++; 275: if (childIndex < getViewCount()) 276: { 277: child = getView(childIndex); 278: childAlloc = getChildAllocation(childIndex, a); 279: ret = child.modelToView(pos, childAlloc, bias); 280: } 281: } 282: } 283: } 284: } 285: } 286: 287: if (ret == null) 288: throw new BadLocationException("Position " + pos 289: + " is not represented by view.", pos); 290: 291: return ret; 292: } 293: 294: /** 295: * Maps a region in the document into the coordinate space of the View. 296: * 297: * @param p1 the beginning position inside the document 298: * @param b1 the direction bias for the beginning position 299: * @param p2 the end position inside the document 300: * @param b2 the direction bias for the end position 301: * @param a the area that is occupied by the view 302: * 303: * @return a rectangle that gives the span of the document region 304: * inside the view coordinate space 305: * 306: * @throws BadLocationException if <code>p1</code> or <code>p2</code> are 307: * invalid 308: * @throws IllegalArgumentException if b1 or b2 is not one of the above 309: * listed valid values 310: */ 311: public Shape modelToView(int p1, Position.Bias b1, 312: int p2, Position.Bias b2, Shape a) 313: throws BadLocationException 314: { 315: // TODO: This is most likely not 100% ok, figure out what else is to 316: // do here. 317: return super.modelToView(p1, b1, p2, b2, a); 318: } 319: 320: /** 321: * Maps coordinates from the <code>View</code>'s space into a position 322: * in the document model. 323: * 324: * @param x the x coordinate in the view space, x >= 0 325: * @param y the y coordinate in the view space, y >= 0 326: * @param a the allocation of this <code>View</code> 327: * @param b the bias to use 328: * 329: * @return the position in the document that corresponds to the screen 330: * coordinates <code>x, y</code> >= 0 331: */ 332: public int viewToModel(float x, float y, Shape a, Position.Bias[] b) 333: { 334: if (x >= 0 && y >= 0) 335: { 336: Rectangle r = getInsideAllocation(a); 337: View view = getViewAtPoint((int) x, (int) y, r); 338: return view.viewToModel(x, y, r, b); 339: } 340: return 0; 341: } 342: 343: /** 344: * Returns the next model location that is visible in eiter north / south 345: * direction or east / west direction. This is used to determine the placement 346: * of the caret when navigating around the document with the arrow keys. This 347: * is a convenience method for {@link #getNextNorthSouthVisualPositionFrom} 348: * and {@link #getNextEastWestVisualPositionFrom}. 349: * 350: * @param pos 351: * the model position to start search from 352: * @param b 353: * the bias for <code>pos</code> 354: * @param a 355: * the allocated region for this view 356: * @param direction 357: * the direction from the current position, can be one of the 358: * following: 359: * <ul> 360: * <li>{@link SwingConstants#WEST}</li> 361: * <li>{@link SwingConstants#EAST}</li> 362: * <li>{@link SwingConstants#NORTH}</li> 363: * <li>{@link SwingConstants#SOUTH}</li> 364: * </ul> 365: * @param biasRet 366: * the bias of the return value gets stored here 367: * @return the position inside the model that represents the next visual 368: * location 369: * @throws BadLocationException 370: * if <code>pos</code> is not a valid location inside the document 371: * model 372: * @throws IllegalArgumentException 373: * if <code>direction</code> is invalid 374: */ 375: public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, 376: int direction, Position.Bias[] biasRet) 377: throws BadLocationException 378: { 379: int retVal = -1; 380: switch (direction) 381: { 382: case SwingConstants.WEST: 383: case SwingConstants.EAST: 384: retVal = getNextEastWestVisualPositionFrom(pos, b, a, direction, 385: biasRet); 386: break; 387: case SwingConstants.NORTH: 388: case SwingConstants.SOUTH: 389: retVal = getNextNorthSouthVisualPositionFrom(pos, b, a, direction, 390: biasRet); 391: break; 392: default: 393: throw new IllegalArgumentException("Illegal value for direction."); 394: } 395: return retVal; 396: } 397: 398: /** 399: * Returns the index of the child view that represents the specified 400: * model location. 401: * 402: * @param pos the model location for which to determine the child view index 403: * @param b the bias to be applied to <code>pos</code> 404: * 405: * @return the index of the child view that represents the specified 406: * model location 407: */ 408: public int getViewIndex(int pos, Position.Bias b) 409: { 410: if (b == Position.Bias.Backward) 411: pos -= 1; 412: int i = -1; 413: if (pos >= getStartOffset() && pos < getEndOffset()) 414: i = getViewIndexAtPosition(pos); 415: return i; 416: } 417: 418: /** 419: * Returns <code>true</code> if the specified point lies before the 420: * given <code>Rectangle</code>, <code>false</code> otherwise. 421: * 422: * "Before" is typically defined as being to the left or above. 423: * 424: * @param x the X coordinate of the point 425: * @param y the Y coordinate of the point 426: * @param r the rectangle to test the point against 427: * 428: * @return <code>true</code> if the specified point lies before the 429: * given <code>Rectangle</code>, <code>false</code> otherwise 430: */ 431: protected abstract boolean isBefore(int x, int y, Rectangle r); 432: 433: /** 434: * Returns <code>true</code> if the specified point lies after the 435: * given <code>Rectangle</code>, <code>false</code> otherwise. 436: * 437: * "After" is typically defined as being to the right or below. 438: * 439: * @param x the X coordinate of the point 440: * @param y the Y coordinate of the point 441: * @param r the rectangle to test the point against 442: * 443: * @return <code>true</code> if the specified point lies after the 444: * given <code>Rectangle</code>, <code>false</code> otherwise 445: */ 446: protected abstract boolean isAfter(int x, int y, Rectangle r); 447: 448: /** 449: * Returns the child <code>View</code> at the specified location. 450: * 451: * @param x the X coordinate 452: * @param y the Y coordinate 453: * @param r the inner allocation of this <code>BoxView</code> on entry, 454: * the allocation of the found child on exit 455: * 456: * @return the child <code>View</code> at the specified location 457: */ 458: protected abstract View getViewAtPoint(int x, int y, Rectangle r); 459: 460: /** 461: * Computes the allocation for a child <code>View</code>. The parameter 462: * <code>a</code> stores the allocation of this <code>CompositeView</code> 463: * and is then adjusted to hold the allocation of the child view. 464: * 465: * @param index the index of the child <code>View</code> 466: * @param a the allocation of this <code>CompositeView</code> before the 467: * call, the allocation of the child on exit 468: */ 469: protected abstract void childAllocation(int index, Rectangle a); 470: 471: /** 472: * Returns the child <code>View</code> that contains the given model 473: * position. The given <code>Rectangle</code> gives the parent's allocation 474: * and is changed to the child's allocation on exit. 475: * 476: * @param pos the model position to query the child <code>View</code> for 477: * @param a the parent allocation on entry and the child allocation on exit 478: * 479: * @return the child view at the given model position 480: */ 481: protected View getViewAtPosition(int pos, Rectangle a) 482: { 483: View view = null; 484: int i = getViewIndexAtPosition(pos); 485: if (i >= 0 && i < getViewCount() && a != null) 486: { 487: view = getView(i); 488: childAllocation(i, a); 489: } 490: return view; 491: } 492: 493: /** 494: * Returns the index of the child <code>View</code> for the given model 495: * position. 496: * 497: * @param pos the model position for whicht the child <code>View</code> is 498: * queried 499: * 500: * @return the index of the child <code>View</code> for the given model 501: * position 502: */ 503: protected int getViewIndexAtPosition(int pos) 504: { 505: // We have a 1:1 mapping of elements to views here, so we forward 506: // this to the element. 507: Element el = getElement(); 508: return el.getElementIndex(pos); 509: } 510: 511: /** 512: * Returns the allocation that is given to this <code>CompositeView</code> 513: * minus this <code>CompositeView</code>'s insets. 514: * 515: * Also this translates from an immutable allocation to a mutable allocation 516: * that is typically reused and further narrowed, like in 517: * {@link #childAllocation}. 518: * 519: * @param a the allocation given to this <code>CompositeView</code> 520: * 521: * @return the allocation that is given to this <code>CompositeView</code> 522: * minus this <code>CompositeView</code>'s insets or 523: * <code>null</code> if a was <code>null</code> 524: */ 525: protected Rectangle getInsideAllocation(Shape a) 526: { 527: if (a == null) 528: return null; 529: 530: // Try to avoid allocation of Rectangle here. 531: Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); 532: 533: // Initialize the inside allocation rectangle. This is done inside 534: // a synchronized block in order to avoid multiple threads creating 535: // this instance simultanously. 536: Rectangle inside = insideAllocation; 537: inside.x = alloc.x + getLeftInset(); 538: inside.y = alloc.y + getTopInset(); 539: inside.width = alloc.width - getLeftInset() - getRightInset(); 540: inside.height = alloc.height - getTopInset() - getBottomInset(); 541: return inside; 542: } 543: 544: /** 545: * Sets the insets defined by attributes in <code>attributes</code>. This 546: * queries the attribute keys {@link StyleConstants#SpaceAbove}, 547: * {@link StyleConstants#SpaceBelow}, {@link StyleConstants#LeftIndent} and 548: * {@link StyleConstants#RightIndent} and calls {@link #setInsets} to 549: * actually set the insets on this <code>CompositeView</code>. 550: * 551: * @param attributes the attributes from which to query the insets 552: */ 553: protected void setParagraphInsets(AttributeSet attributes) 554: { 555: top = (short) StyleConstants.getSpaceAbove(attributes); 556: bottom = (short) StyleConstants.getSpaceBelow(attributes); 557: left = (short) StyleConstants.getLeftIndent(attributes); 558: right = (short) StyleConstants.getRightIndent(attributes); 559: } 560: 561: /** 562: * Sets the insets of this <code>CompositeView</code>. 563: * 564: * @param t the top inset 565: * @param l the left inset 566: * @param b the bottom inset 567: * @param r the right inset 568: */ 569: protected void setInsets(short t, short l, short b, short r) 570: { 571: top = t; 572: left = l; 573: bottom = b; 574: right = r; 575: } 576: 577: /** 578: * Returns the left inset of this <code>CompositeView</code>. 579: * 580: * @return the left inset of this <code>CompositeView</code> 581: */ 582: protected short getLeftInset() 583: { 584: return left; 585: } 586: 587: /** 588: * Returns the right inset of this <code>CompositeView</code>. 589: * 590: * @return the right inset of this <code>CompositeView</code> 591: */ 592: protected short getRightInset() 593: { 594: return right; 595: } 596: 597: /** 598: * Returns the top inset of this <code>CompositeView</code>. 599: * 600: * @return the top inset of this <code>CompositeView</code> 601: */ 602: protected short getTopInset() 603: { 604: return top; 605: } 606: 607: /** 608: * Returns the bottom inset of this <code>CompositeView</code>. 609: * 610: * @return the bottom inset of this <code>CompositeView</code> 611: */ 612: protected short getBottomInset() 613: { 614: return bottom; 615: } 616: 617: /** 618: * Returns the next model location that is visible in north or south 619: * direction. 620: * This is used to determine the 621: * placement of the caret when navigating around the document with 622: * the arrow keys. 623: * 624: * @param pos the model position to start search from 625: * @param b the bias for <code>pos</code> 626: * @param a the allocated region for this view 627: * @param direction the direction from the current position, can be one of 628: * the following: 629: * <ul> 630: * <li>{@link SwingConstants#NORTH}</li> 631: * <li>{@link SwingConstants#SOUTH}</li> 632: * </ul> 633: * @param biasRet the bias of the return value gets stored here 634: * 635: * @return the position inside the model that represents the next visual 636: * location 637: * 638: * @throws BadLocationException if <code>pos</code> is not a valid location 639: * inside the document model 640: * @throws IllegalArgumentException if <code>direction</code> is invalid 641: */ 642: protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b, 643: Shape a, int direction, 644: Position.Bias[] biasRet) 645: throws BadLocationException 646: { 647: // TODO: It is unknown to me how this method has to be implemented and 648: // there is no specification telling me how to do it properly. Therefore 649: // the implementation was done for cases that are known. 650: // 651: // If this method ever happens to act silly for your particular case then 652: // it is likely that it is a cause of not knowing about your case when it 653: // was implemented first. You are free to fix the behavior. 654: // 655: // Here are the assumptions that lead to the implementation: 656: // If direction is NORTH chose the View preceding the one that contains the 657: // offset 'pos' (imagine the views are stacked on top of each other where 658: // the top is 0 and the bottom is getViewCount()-1. 659: // Consecutively when the direction is SOUTH the View following the one 660: // the offset 'pos' lies in is questioned. 661: // 662: // This limitation is described as PR 27345. 663: int index = getViewIndex(pos, b); 664: View v = null; 665: 666: if (index == -1) 667: return pos; 668: 669: switch (direction) 670: { 671: case NORTH: 672: // If we cannot calculate a proper offset return the one that was 673: // provided. 674: if (index <= 0) 675: return pos; 676: 677: v = getView(index - 1); 678: break; 679: case SOUTH: 680: // If we cannot calculate a proper offset return the one that was 681: // provided. 682: if (index >= getViewCount() - 1) 683: return pos; 684: 685: v = getView(index + 1); 686: break; 687: default: 688: throw new IllegalArgumentException(); 689: } 690: 691: return v.getNextVisualPositionFrom(pos, b, a, direction, biasRet); 692: } 693: 694: /** 695: * Returns the next model location that is visible in east or west 696: * direction. 697: * This is used to determine the 698: * placement of the caret when navigating around the document with 699: * the arrow keys. 700: * 701: * @param pos the model position to start search from 702: * @param b the bias for <code>pos</code> 703: * @param a the allocated region for this view 704: * @param direction the direction from the current position, can be one of 705: * the following: 706: * <ul> 707: * <li>{@link SwingConstants#EAST}</li> 708: * <li>{@link SwingConstants#WEST}</li> 709: * </ul> 710: * @param biasRet the bias of the return value gets stored here 711: * 712: * @return the position inside the model that represents the next visual 713: * location 714: * 715: * @throws BadLocationException if <code>pos</code> is not a valid location 716: * inside the document model 717: * @throws IllegalArgumentException if <code>direction</code> is invalid 718: */ 719: protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b, 720: Shape a, int direction, 721: Position.Bias[] biasRet) 722: throws BadLocationException 723: { 724: // TODO: It is unknown to me how this method has to be implemented and 725: // there is no specification telling me how to do it properly. Therefore 726: // the implementation was done for cases that are known. 727: // 728: // If this method ever happens to act silly for your particular case then 729: // it is likely that it is a cause of not knowing about your case when it 730: // was implemented first. You are free to fix the behavior. 731: // 732: // Here are the assumptions that lead to the implementation: 733: // If direction is EAST increase the offset by one and ask the View to 734: // which that index belong to calculate the 'next visual position'. 735: // If the direction is WEST do the same with offset 'pos' being decreased 736: // by one. 737: // This behavior will fail in a right-to-left or bidi environment! 738: // 739: // This limitation is described as PR 27346. 740: int index; 741: 742: View v = null; 743: 744: switch (direction) 745: { 746: case EAST: 747: index = getViewIndex(pos + 1, b); 748: // If we cannot calculate a proper offset return the one that was 749: // provided. 750: if (index == -1) 751: return pos; 752: 753: v = getView(index); 754: break; 755: case WEST: 756: index = getViewIndex(pos - 1, b); 757: // If we cannot calculate a proper offset return the one that was 758: // provided. 759: if (index == -1) 760: return pos; 761: 762: v = getView(index); 763: break; 764: default: 765: throw new IllegalArgumentException(); 766: } 767: 768: return v.getNextVisualPositionFrom(pos, 769: b, 770: a, 771: direction, 772: biasRet); 773: } 774: 775: /** 776: * Determines if the next view in horinzontal direction is located to 777: * the east or west of the view at position <code>pos</code>. Usually 778: * the <code>View</code>s are laid out from the east to the west, so 779: * we unconditionally return <code>false</code> here. Subclasses that 780: * support bidirectional text may wish to override this method. 781: * 782: * @param pos the position in the document 783: * @param bias the bias to be applied to <code>pos</code> 784: * 785: * @return <code>true</code> if the next <code>View</code> is located 786: * to the EAST, <code>false</code> otherwise 787: */ 788: protected boolean flipEastAndWestAtEnds(int pos, Position.Bias bias) 789: { 790: return false; 791: } 792: }