package org.freeplane.view.swing.map;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicLabelUI;
import javax.swing.text.View;
import org.freeplane.core.ui.components.html.ScaledHTML;
import org.freeplane.core.util.TextUtils;
/*
* Freeplane - mind map editor
* Copyright (C) 2009 Dimitry
*
* This file author is Dimitry
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Dimitry Polivaev
* 23.08.2009
*/
public class ZoomableLabelUI extends BasicLabelUI {
private boolean isPainting = false;
static ZoomableLabelUI labelUI = new ZoomableLabelUI();
private Rectangle iconR = new Rectangle();
private Rectangle textR = new Rectangle();
private Rectangle viewR = new Rectangle();
private int maximumWidth = Integer.MAX_VALUE;
public Dimension getPreferredSize(final ZoomableLabel c, int maximumWidth) {
try{
this.maximumWidth = maximumWidth;
final Dimension preferredSize = getPreferredSize(c);
return preferredSize;
}
finally{
this.maximumWidth = Integer.MAX_VALUE;
}
}
@Override
public Dimension getPreferredSize(final JComponent c) {
final Dimension preferredSize = super.getPreferredSize(c);
final int fontHeight = ((ZoomableLabel) c).getFontMetrics().getHeight();
final Insets insets = c.getInsets();
preferredSize.width = Math.max(preferredSize.width, fontHeight/2 + insets.left + insets.right);
preferredSize.height = Math.max(preferredSize.height, fontHeight + insets.top + insets.bottom);
final float zoom = ((ZoomableLabel) c).getZoom();
if (zoom != 1f) {
preferredSize.width = (int) (Math.ceil(zoom * preferredSize.width));
preferredSize.height = (int) (Math.ceil(zoom * preferredSize.height));
}
return preferredSize;
}
public static ComponentUI createUI(final JComponent c) {
return labelUI;
}
@Override
protected String layoutCL(final JLabel label, final FontMetrics fontMetrics, final String text, final Icon icon,
final Rectangle viewR, final Rectangle iconR, final Rectangle textR) {
final ZoomableLabel zLabel = (ZoomableLabel) label;
if (isPainting) {
final Insets insets = zLabel.getInsets();
final int width = zLabel.getWidth();
final int height = zLabel.getHeight();
final float zoom = zLabel.getZoom();
viewR.x = insets.left;
viewR.y = insets.top;
viewR.width = (int) (width / zoom) - (insets.left + insets.right);
viewR.height = (int)(height / zoom) - (insets.top + insets.bottom);
if(viewR.width < 0)
viewR.width = 0;
ScaledHTML.Renderer v = (ScaledHTML.Renderer) label.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
float preferredWidth = v.getPreferredSpan(View.X_AXIS);
int textWidth = viewR.width;
if(icon != null)
textWidth -= icon.getIconWidth() + label.getIconTextGap();
if(preferredWidth < textWidth){
v.setSize(textWidth, 1);
super.layoutCL(zLabel, zLabel.getFontMetrics(), text, icon, viewR, iconR, textR);
v.setSize(textR.width, textR.height);
return text;
}
}
}
else if(maximumWidth != Integer.MAX_VALUE){
final Insets insets = label.getInsets();
viewR.width = maximumWidth - insets.left - insets.right;
if(viewR.width < 0)
viewR.width = 0;
ScaledHTML.Renderer v = (ScaledHTML.Renderer) label.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
v.resetSize();
float preferredWidth = v.getPreferredSpan(View.X_AXIS);
float minimumWidth = v.getMinimumSpan(View.X_AXIS);
int textWidth = viewR.width;
if(icon != null)
textWidth -= icon.getIconWidth() + label.getIconTextGap();
if(preferredWidth > textWidth){
if(minimumWidth > textWidth){
viewR.width += minimumWidth - textWidth;
textWidth = (int) minimumWidth;
}
v.setSize(textWidth, 1);
super.layoutCL(zLabel, zLabel.getFontMetrics(), text, icon, viewR, iconR, textR);
v.setSize(textR.width, textR.height);
return text;
}
}
}
Icon textRenderingIcon = getTextRenderingIcon(zLabel);
if(textRenderingIcon != null){
layoutLabelWithTextIcon(textRenderingIcon, icon, viewR, iconR, textR, zLabel);
}
else
super.layoutCL(zLabel, zLabel.getFontMetrics(), text, icon, viewR, iconR, textR);
return text;
}
static private void layoutLabelWithTextIcon(final Icon textRenderingIcon, final Icon icon,
final Rectangle viewR, final Rectangle iconR,
final Rectangle textR, final ZoomableLabel zLabel) {
JComponent c = (JComponent) zLabel;
int horizontalAlignment = zLabel.getHorizontalAlignment();
int horizontalTextPosition = zLabel.getHorizontalTextPosition();
boolean orientationIsLeftToRight = true;
int hAlign = horizontalAlignment;
int hTextPos = horizontalTextPosition;
if (c != null) {
if (!(c.getComponentOrientation().isLeftToRight())) {
orientationIsLeftToRight = false;
}
}
// Translate LEADING/TRAILING values in horizontalAlignment
// to LEFT/RIGHT values depending on the components orientation
switch (horizontalAlignment) {
case SwingUtilities.LEADING:
hAlign = (orientationIsLeftToRight) ? SwingUtilities.LEFT : SwingUtilities.RIGHT;
break;
case SwingUtilities.TRAILING:
hAlign = (orientationIsLeftToRight) ? SwingUtilities.RIGHT : SwingUtilities.LEFT;
break;
}
// Translate LEADING/TRAILING values in horizontalTextPosition
// to LEFT/RIGHT values depending on the components orientation
switch (horizontalTextPosition) {
case SwingUtilities.LEADING:
hTextPos = (orientationIsLeftToRight) ? SwingUtilities.LEFT : SwingUtilities.RIGHT;
break;
case SwingUtilities.TRAILING:
hTextPos = (orientationIsLeftToRight) ? SwingUtilities.RIGHT : SwingUtilities.LEFT;
break;
}
int verticalAlignment = zLabel.getVerticalAlignment();
int verticalTextPosition = zLabel.getVerticalTextPosition();
if (icon != null) {
iconR.width = icon.getIconWidth();
iconR.height = icon.getIconHeight();
}
else {
iconR.width = iconR.height = 0;
}
/* Initialize the text bounds rectangle textR. If a null
* or and empty String was specified we substitute "" here
* and use 0,0,0,0 for textR.
*/
int lsb = 0;
int rsb = 0;
/* Unless both text and icon are non-null, we effectively ignore
* the value of textIconGap.
*/
int gap;
int availTextWidth;
gap = (icon == null) ? 0 : zLabel.getIconTextGap();
if (hTextPos == SwingUtilities.CENTER) {
availTextWidth = viewR.width;
}
else {
availTextWidth = viewR.width - (iconR.width + gap);
}
textR.width = Math.min(availTextWidth, textRenderingIcon.getIconWidth());
textR.height = textRenderingIcon.getIconHeight();
/* Compute textR.x,y given the verticalTextPosition and
* horizontalTextPosition properties
*/
if (verticalTextPosition == SwingUtilities.TOP) {
if (hTextPos != SwingUtilities.CENTER) {
textR.y = 0;
}
else {
textR.y = -(textR.height + gap);
}
}
else if (verticalTextPosition == SwingUtilities.CENTER) {
textR.y = (iconR.height / 2) - (textR.height / 2);
}
else { // (verticalTextPosition == BOTTOM)
if (hTextPos != SwingUtilities.CENTER) {
textR.y = iconR.height - textR.height;
}
else {
textR.y = (iconR.height + gap);
}
}
if (hTextPos == SwingUtilities.LEFT) {
textR.x = -(textR.width + gap);
}
else if (hTextPos == SwingUtilities.CENTER) {
textR.x = (iconR.width / 2) - (textR.width / 2);
}
else { // (horizontalTextPosition == RIGHT)
textR.x = (iconR.width + gap);
}
/* labelR is the rectangle that contains iconR and textR.
* Move it to its proper position given the labelAlignment
* properties.
*
* To avoid actually allocating a Rectangle, Rectangle.union
* has been inlined below.
*/
int labelR_x = Math.min(iconR.x, textR.x);
int labelR_width = Math.max(iconR.x + iconR.width,
textR.x + textR.width) - labelR_x;
int labelR_y = Math.min(iconR.y, textR.y);
int labelR_height = Math.max(iconR.y + iconR.height,
textR.y + textR.height) - labelR_y;
int dx, dy;
if (verticalAlignment == SwingUtilities.TOP) {
dy = viewR.y - labelR_y;
}
else if (verticalAlignment == SwingUtilities.CENTER) {
dy = (viewR.y + (viewR.height / 2)) - (labelR_y + (labelR_height / 2));
}
else { // (verticalAlignment == BOTTOM)
dy = (viewR.y + viewR.height) - (labelR_y + labelR_height);
}
if (hAlign == SwingUtilities.LEFT) {
dx = viewR.x - labelR_x;
}
else if (hAlign == SwingUtilities.RIGHT) {
dx = (viewR.x + viewR.width) - (labelR_x + labelR_width);
}
else { // (horizontalAlignment == CENTER)
dx = (viewR.x + (viewR.width / 2)) -
(labelR_x + (labelR_width / 2));
}
/* Translate textR and glypyR by dx,dy.
*/
textR.x += dx;
textR.y += dy;
iconR.x += dx;
iconR.y += dy;
if (lsb < 0) {
// lsb is negative. Shift the x location so that the text is
// visually drawn at the right location.
textR.x -= lsb;
textR.width += lsb;
}
if (rsb > 0) {
textR.width -= rsb;
}
}
@Override
public void paint(final Graphics g, final JComponent label) {
final ZoomableLabel mainView = (ZoomableLabel) label;
if (!mainView.useFractionalMetrics()) {
try {
isPainting = true;
superPaintSafe(g, mainView);
}
finally {
isPainting = false;
}
return;
}
final Graphics2D g2 = (Graphics2D) g;
final Object oldRenderingHintFM = g2.getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS);
final Object newRenderingHintFM = RenderingHints.VALUE_FRACTIONALMETRICS_ON;
if (oldRenderingHintFM != newRenderingHintFM) {
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, newRenderingHintFM);
}
final AffineTransform transform = g2.getTransform();
final float zoom = mainView.getZoom() * 0.97f;
g2.scale(zoom, zoom);
final boolean htmlViewSet = null != label.getClientProperty(BasicHTML.propertyKey);
try {
isPainting = true;
if(htmlViewSet){
GlyphPainterMetricResetter.resetPainter();
}
superPaintSafe(g, mainView);
}
finally {
isPainting = false;
if(htmlViewSet){
GlyphPainterMetricResetter.resetPainter();
}
}
g2.setTransform(transform);
if (oldRenderingHintFM != newRenderingHintFM) {
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, oldRenderingHintFM != null ? oldRenderingHintFM
: RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
}
}
// Workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7126361
private void superPaintSafe(final Graphics g, final ZoomableLabel label) {
try {
Icon textRenderingIcon = getTextRenderingIcon(label);
if(textRenderingIcon != null)
paintIcons(g, label, textRenderingIcon);
else
super.paint(g, label);
} catch (ClassCastException e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.setText(TextUtils.format("html_problem", label.getText()));
}
});
}
}
private void paintIcons(Graphics g, ZoomableLabel label, Icon textRenderingIcon) {
Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon();
Rectangle paintViewR = new Rectangle();
Rectangle paintIconR = new Rectangle();
Rectangle paintTextR = new Rectangle();
layoutCL(label, null, null, icon, paintViewR, paintIconR, paintTextR);
if (icon != null) {
icon.paintIcon(label, g, paintIconR.x, paintIconR.y);
}
textRenderingIcon.paintIcon(label, g, paintTextR.x, paintTextR.y);
}
@Override
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if (name == "text" || "font" == name || "foreground" == name) {
JLabel lbl = ((JLabel) e.getSource());
if(getTextRenderingIcon(lbl) != null){
ScaledHTML.updateRenderer(lbl, "");
}
else{
String text = lbl.getText();
GlyphPainterMetricResetter.resetPainter();
try {
ScaledHTML.updateRenderer(lbl, text);
}
finally{
GlyphPainterMetricResetter.resetPainter();
}
View v = (View) lbl.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
lbl.putClientProperty("preferredWidth", v.getPreferredSpan(View.X_AXIS));
}
}
}
else
super.propertyChange(e);
}
private Icon getTextRenderingIcon(JLabel lbl) {
return (Icon) lbl.getClientProperty(ZoomableLabel.TEXT_RENDERING_ICON);
}
@Override
protected void installComponents(JLabel c) {
ScaledHTML.updateRenderer(c, c.getText());
c.setInheritsPopupMenu(true);
}
public Rectangle getIconR(ZoomableLabel label) {
layout(label);
return iconR;
}
public Rectangle getTextR(ZoomableLabel label) {
layout(label);
return textR;
}
private void layout(ZoomableLabel label) {
String text = label.getText();
if(text == null || text.equals(""))
text = "!";
Icon icon = (label.isEnabled()) ? label.getIcon() :
label.getDisabledIcon();
boolean wasPainting = isPainting;
try{
isPainting = true;
iconR.x = iconR.y = iconR.width = iconR.height = 0;
textR.x = textR.y = textR.width = textR.height = 0;
layoutCL(label, label.getFontMetrics(), text, icon, viewR, iconR,textR);
final float zoom = label.getZoom();
iconR.x = (int)(iconR.x * zoom);
iconR.y = (int)(iconR.y * zoom);
iconR.width = (int)(iconR.width * zoom);
iconR.height = (int)(iconR.height * zoom);
textR.x = (int)(textR.x * zoom);
textR.y = (int)(textR.y * zoom);
textR.width = (int)(textR.width * zoom);
textR.height = (int)(textR.height * zoom);
}
finally{
isPainting = wasPainting;
}
}
}