blob: 3f23501083ba497b71d21c7256b0ea5d96bf7253 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pivot.wtk.skin;
import org.apache.pivot.wtk.Bounds;
import org.apache.pivot.wtk.Dimensions;
import org.apache.pivot.wtk.TextPane;
import org.apache.pivot.wtk.text.Element;
/**
* Some of the classes in the text hierarchy are very similar in layout ie. they
* lay their children out vertically. This class groups that functionality.
*/
abstract class TextPaneSkinVerticalElementView extends TextPaneSkinElementView {
public TextPaneSkinVerticalElementView(TextPaneSkin textPaneSkin, Element element) {
super(textPaneSkin, element);
}
@Override
protected void childLayout(int breakWidth) {
// TODO At some point, we may want to optimize this method by deferring layout of
// non-visible views. If so, we should not recycle views but rather recreate them
// (as is done in ParagraphView). This way, we avoid thread contention over the
// existing views (e.g. trying to paint one while modifying its size/location, etc.).
// Any invalid node views are simply replaced (in the queued callback, when the
// thread has finished processing the new ones). This allows the definition of
// validate() to remain as-is. Of course, if we redefine NodeView to implement
// ConstrainedVisual, this may no longer be an issue.
// Note that, if anything happens to invalidate the existence of the new views before
// they are added to the document view, we need to make sure they are disposed (i.e.
// detached).
int width = 0;
int height = 0;
for (TextPaneSkinNodeView nodeView : this) {
nodeView.layout(breakWidth);
nodeView.setLocation(0, height);
width = Math.max(width, nodeView.getWidth());
height += nodeView.getHeight();
}
setSize(width, height);
}
@Override
public Dimensions getPreferredSize(int breakWidth) {
int width = 0;
int height = 0;
for (TextPaneSkinNodeView nodeView : this) {
Dimensions childDimensions = nodeView.getPreferredSize(breakWidth);
width = Math.max(width, childDimensions.width);
height += childDimensions.height;
}
return new Dimensions(width, height);
}
@Override
protected void setSkinLocation(int skinX, int skinY) {
super.setSkinLocation(skinX, skinY);
for (TextPaneSkinNodeView nodeView : this) {
nodeView.setSkinLocation(skinX, skinY + nodeView.getY());
}
}
@Override
public int getInsertionPoint(int x, int y) {
int offset = -1;
for (int i = 0, n = getLength(); i < n; i++) {
TextPaneSkinNodeView nodeView = get(i);
Bounds nodeViewBounds = nodeView.getBounds();
if (y >= nodeViewBounds.y && y < nodeViewBounds.y + nodeViewBounds.height) {
offset = nodeView.getInsertionPoint(x - nodeView.getX(), y - nodeView.getY())
+ nodeView.getOffset();
break;
}
}
return offset;
}
@Override
public int getNextInsertionPoint(int x, int from, TextPane.ScrollDirection direction) {
int offset = -1;
if (getLength() > 0) {
if (from == -1) {
int i = (direction == TextPane.ScrollDirection.DOWN) ? 0 : getLength() - 1;
TextPaneSkinNodeView nodeView = get(i);
offset = nodeView.getNextInsertionPoint(x - nodeView.getX(), -1, direction);
if (offset != -1) {
offset += nodeView.getOffset();
}
} else {
// Find the node view that contains the offset
int n = getLength();
int i = 0;
while (i < n) {
TextPaneSkinNodeView nodeView = get(i);
int nodeViewOffset = nodeView.getOffset();
int characterCount = nodeView.getCharacterCount();
if (from >= nodeViewOffset && from < nodeViewOffset + characterCount) {
break;
}
i++;
}
if (i < n) {
TextPaneSkinNodeView nodeView = get(i);
offset = nodeView.getNextInsertionPoint(x - nodeView.getX(),
from - nodeView.getOffset(), direction);
if (offset == -1) {
// Move to the next or previous node view
if (direction == TextPane.ScrollDirection.DOWN) {
nodeView = (i < n - 1) ? get(i + 1) : null;
} else {
nodeView = (i > 0) ? get(i - 1) : null;
}
if (nodeView != null) {
offset = nodeView.getNextInsertionPoint(x - nodeView.getX(), -1,
direction);
}
}
if (offset != -1 && nodeView != null) {
offset += nodeView.getOffset();
}
}
}
}
return offset;
}
@Override
public int getRowAt(int offset) {
int rowIndex = 0;
for (TextPaneSkinNodeView nodeView : this) {
int nodeViewOffset = nodeView.getOffset();
int characterCount = nodeView.getCharacterCount();
if (offset >= nodeViewOffset && offset < nodeViewOffset + characterCount) {
rowIndex += nodeView.getRowAt(offset - nodeView.getOffset());
break;
}
rowIndex += nodeView.getRowCount();
}
return rowIndex;
}
@Override
public int getRowCount() {
int rowCount = 0;
for (TextPaneSkinNodeView nodeView : this) {
rowCount += nodeView.getRowCount();
}
return rowCount;
}
}