blob: c0f0dd6f88a0000a39c92780c5594c9b47043f36 [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 java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.util.Iterator;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.util.ImmutableIterator;
import org.apache.pivot.wtk.Bounds;
import org.apache.pivot.wtk.text.Element;
import org.apache.pivot.wtk.text.ElementListener;
import org.apache.pivot.wtk.text.Node;
/**
* Abstract base class for element views.
*/
abstract class TextPaneSkinElementView extends TextPaneSkinNodeView implements
Sequence<TextPaneSkinNodeView>, Iterable<TextPaneSkinNodeView>, ElementListener {
private ArrayList<TextPaneSkinNodeView> nodeViews = new ArrayList<>();
private int skinX = 0;
private int skinY = 0;
public TextPaneSkinElementView(TextPaneSkin textPaneSkin, Element element) {
super(textPaneSkin, element);
}
@Override
protected void attach() {
super.attach();
Element element = (Element) getNode();
element.getElementListeners().add(this);
// Attach child node views
for (Node node : element) {
add(createNodeView(getTextPaneSkin(), node));
}
}
@Override
protected void detach() {
Element element = (Element) getNode();
element.getElementListeners().remove(this);
// Detach child node views
for (TextPaneSkinNodeView nodeView : this) {
nodeView.detach();
}
super.detach();
}
@Override
public void invalidateDownTree() {
super.invalidateDownTree();
for (TextPaneSkinNodeView child : this) {
child.invalidateDownTree();
}
}
@Override
public int add(TextPaneSkinNodeView nodeView) {
int index = getLength();
insert(nodeView, index);
return index;
}
@Override
public void insert(TextPaneSkinNodeView nodeView, int index) {
nodeView.setParent(this);
nodeView.attach();
nodeViews.insert(nodeView, index);
}
@Override
public TextPaneSkinNodeView update(int index, TextPaneSkinNodeView nodeView) {
throw new UnsupportedOperationException();
}
@Override
public int remove(TextPaneSkinNodeView nodeView) {
int index = indexOf(nodeView);
if (index != -1) {
remove(index, 1);
}
return index;
}
@Override
public Sequence<TextPaneSkinNodeView> remove(int index, int count) {
Sequence<TextPaneSkinNodeView> removed = nodeViews.remove(index, count);
for (int i = 0, n = removed.getLength(); i < n; i++) {
TextPaneSkinNodeView nodeView = removed.get(i);
nodeView.setParent(null);
nodeView.detach();
}
return removed;
}
@Override
public TextPaneSkinNodeView get(int index) {
return nodeViews.get(index);
}
@Override
public int indexOf(TextPaneSkinNodeView nodeView) {
return nodeViews.indexOf(nodeView);
}
@Override
public int getLength() {
return nodeViews.getLength();
}
@Override
public int getBaseline() {
int baseline = -1;
for (TextPaneSkinNodeView nodeView : nodeViews) {
baseline = Math.max(baseline, nodeView.getBaseline());
}
return baseline;
}
@Override
protected void setSkinLocation(int skinX, int skinY) {
this.skinX = skinX;
this.skinY = skinY;
}
@Override
public void paint(Graphics2D graphics) {
// Determine the paint bounds
Bounds paintBounds = new Bounds(0, 0, getWidth(), getHeight());
Rectangle clipBounds = graphics.getClipBounds();
if (clipBounds != null) {
paintBounds = paintBounds.intersect(clipBounds);
}
for (TextPaneSkinNodeView nodeView : nodeViews) {
paintChild(graphics, paintBounds, nodeView);
}
}
protected final void paintChild(Graphics2D graphics, Bounds paintBounds,
TextPaneSkinNodeView nodeView) {
Bounds nodeViewBounds = nodeView.getBounds();
// Only paint node views that intersect the current clip rectangle
if (nodeViewBounds.intersects(paintBounds)) {
// Create a copy of the current graphics context and
// translate to the node view's coordinate system
Graphics2D nodeViewGraphics = (Graphics2D) graphics.create();
Color styledBackgroundColor = getStyledBackgroundColor();
if (styledBackgroundColor != null) {
// don't paint over the selection background
Area selection = getTextPaneSkin().getSelection();
if (selection != null) {
Area fillArea = new Area(new Rectangle(nodeViewBounds.x, nodeViewBounds.y,
nodeViewBounds.width, nodeViewBounds.height));
selection = selection.createTransformedArea(AffineTransform.getTranslateInstance(
-skinX, -skinY));
fillArea.subtract(selection);
nodeViewGraphics.setColor(styledBackgroundColor);
nodeViewGraphics.fill(fillArea);
} else {
nodeViewGraphics.setColor(styledBackgroundColor);
nodeViewGraphics.fillRect(nodeViewBounds.x, nodeViewBounds.y,
nodeViewBounds.width, nodeViewBounds.height);
}
}
nodeViewGraphics.translate(nodeViewBounds.x, nodeViewBounds.y);
// NOTE We don't clip here because views should generally
// not overlap and clipping would impose an unnecessary
// performance penalty
// Paint the node view
nodeView.paint(nodeViewGraphics);
// Dispose of the node views's graphics
nodeViewGraphics.dispose();
}
}
private Color getStyledBackgroundColor() {
Color backgroundColor = null;
Node node = getNode();
// run up the tree until we find a Element's style to apply
while (node != null) {
if (node instanceof Element) {
backgroundColor = ((Element) node).getBackgroundColor();
if (backgroundColor != null) {
break;
}
}
node = node.getParent();
}
return backgroundColor;
}
@Override
public Bounds getCharacterBounds(int offset) {
Bounds characterBounds = null;
for (int i = 0, n = nodeViews.getLength(); i < n; i++) {
TextPaneSkinNodeView nodeView = nodeViews.get(i);
int nodeViewOffset = nodeView.getOffset();
int characterCount = nodeView.getCharacterCount();
if (offset >= nodeViewOffset && offset < nodeViewOffset + characterCount) {
characterBounds = nodeView.getCharacterBounds(offset - nodeViewOffset);
if (characterBounds != null) {
characterBounds = characterBounds.translate(nodeView.getX(), nodeView.getY());
}
break;
}
}
if (characterBounds != null) {
characterBounds = characterBounds.intersect(0, 0, getWidth(), getHeight());
}
return characterBounds;
}
@Override
public void nodeInserted(Element element, int index) {
insert(createNodeView(getTextPaneSkin(), element.get(index)), index);
invalidateUpTree();
}
@Override
public void nodesRemoved(Element element, int index, Sequence<Node> nodes) {
remove(index, nodes.getLength());
invalidateUpTree();
}
@Override
public void fontChanged(Element element, Font previousFont) {
// because children may depend on parents for their style information,
// we need to invalidate the whole tree
// TODO, we don't need to invalidate the whole tree, just the sub-tree
// from here down
getTextPaneSkin().invalidateNodeViewTree();
}
@Override
public void backgroundColorChanged(Element element, Color previousBackgroundColor) {
repaint();
}
@Override
public void foregroundColorChanged(Element element, Color previousForegroundColor) {
// Because children may depend on parents for their style information,
// we need to invalidate the whole tree.
// TODO we don't need to invalidate the whole tree, just the sub-tree
// from here down.
getTextPaneSkin().invalidateNodeViewTree();
}
@Override
public void underlineChanged(Element element) {
// Because children may depend on parents for their style information,
// we need to invalidate the whole tree.
// TODO we don't need to invalidate the whole tree, just the sub-tree
// from here down.
getTextPaneSkin().invalidateNodeViewTree();
}
@Override
public void strikethroughChanged(Element element) {
// Because children may depend on parents for their style information,
// we need to invalidate the whole tree.
// TODO we don't need to invalidate the whole tree, just the sub-tree
// from here down.
getTextPaneSkin().invalidateNodeViewTree();
}
@Override
public Iterator<TextPaneSkinNodeView> iterator() {
return new ImmutableIterator<>(nodeViews.iterator());
}
}