mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 12:18:00 +00:00
Added a reflowing label UI component. Distributed under the MIT license.
Created a licenses folder to store all third-party license information.
This commit is contained in:
7
.gitattributes
vendored
7
.gitattributes
vendored
@@ -6819,10 +6819,11 @@ res/lib/miglayout-3.7.3.1-swing.jar -text svneol=unset#unset
|
|||||||
res/lib/napkinlaf-1.2.jar -text svneol=unset#unset
|
res/lib/napkinlaf-1.2.jar -text svneol=unset#unset
|
||||||
res/lib/nimrodlf.jar -text svneol=unset#unset
|
res/lib/nimrodlf.jar -text svneol=unset#unset
|
||||||
res/lib/substance.jar -text svneol=unset#unset
|
res/lib/substance.jar -text svneol=unset#unset
|
||||||
res/lib/xpp3-license.txt svneol=native#text/plain
|
|
||||||
res/lib/xpp3_min-1.1.4c.jar -text svneol=unset#unset
|
res/lib/xpp3_min-1.1.4c.jar -text svneol=unset#unset
|
||||||
res/lib/xstream-1.3.1.jar -text svneol=unset#unset
|
res/lib/xstream-1.3.1.jar -text svneol=unset#unset
|
||||||
res/lib/xstream-license.txt svneol=native#text/plain
|
res/licenses/multiline-label-license.txt svneol=native#text/plain
|
||||||
|
res/licenses/xpp3-license.txt svneol=native#text/plain
|
||||||
|
res/licenses/xstream-license.txt svneol=native#text/plain
|
||||||
res/main.properties svneol=native#text/plain
|
res/main.properties svneol=native#text/plain
|
||||||
res/mtg-data.txt svneol=native#text/plain
|
res/mtg-data.txt svneol=native#text/plain
|
||||||
res/pics/BookIcon.png -text svneol=unset#image/png
|
res/pics/BookIcon.png -text svneol=unset#image/png
|
||||||
@@ -7418,6 +7419,8 @@ src/forge/error/ExceptionHandler.java -text svneol=native#text/plain
|
|||||||
src/forge/gui/ForgeAction.java svneol=native#text/plain
|
src/forge/gui/ForgeAction.java svneol=native#text/plain
|
||||||
src/forge/gui/GuiUtils.java svneol=native#text/plain
|
src/forge/gui/GuiUtils.java svneol=native#text/plain
|
||||||
src/forge/gui/ListChooser.java svneol=native#text/plain
|
src/forge/gui/ListChooser.java svneol=native#text/plain
|
||||||
|
src/forge/gui/MultiLineLabel.java svneol=native#text/plain
|
||||||
|
src/forge/gui/MultiLineLabelUI.java svneol=native#text/plain
|
||||||
src/forge/gui/game/CardDetailPanel.java svneol=native#text/plain
|
src/forge/gui/game/CardDetailPanel.java svneol=native#text/plain
|
||||||
src/forge/gui/game/CardPanel.java svneol=native#text/plain
|
src/forge/gui/game/CardPanel.java svneol=native#text/plain
|
||||||
src/forge/gui/game/CardPicturePanel.java svneol=native#text/plain
|
src/forge/gui/game/CardPicturePanel.java svneol=native#text/plain
|
||||||
|
|||||||
22
res/licenses/multiline-label-license.txt
Normal file
22
res/licenses/multiline-label-license.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2009 Samuel Sjoberg
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
120
src/forge/gui/MultiLineLabel.java
Executable file
120
src/forge/gui/MultiLineLabel.java
Executable file
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009 Samuel Sjoberg
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package forge.gui;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link JLabel} with support for multi-line text that wraps when the line
|
||||||
|
* doesn't fit in the available width. Multi-line text support is handled by the
|
||||||
|
* {@link MultiLineLabelUI}, the default UI delegate of this component. The text
|
||||||
|
* in the label can be horizontally and vertically aligned, relative to the
|
||||||
|
* bounds of the component.
|
||||||
|
*
|
||||||
|
* @author Samuel Sjoberg, http://samuelsjoberg.com
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public class MultiLineLabel extends JLabel {
|
||||||
|
|
||||||
|
/** Default serial version UID. */
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** Horizontal text alignment. */
|
||||||
|
private int halign = LEFT;
|
||||||
|
|
||||||
|
/** Vertical text alignment. */
|
||||||
|
private int valign = CENTER;
|
||||||
|
|
||||||
|
/** Cache to save heap allocations. */
|
||||||
|
private Rectangle bounds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new empty label.
|
||||||
|
*/
|
||||||
|
public MultiLineLabel() {
|
||||||
|
super();
|
||||||
|
setUI(MultiLineLabelUI.labelUI);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new label with <code>text</code> value.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* the value of the label
|
||||||
|
*/
|
||||||
|
public MultiLineLabel(String text) {
|
||||||
|
this();
|
||||||
|
setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public Rectangle getBounds() {
|
||||||
|
if (bounds == null) {
|
||||||
|
bounds = new Rectangle();
|
||||||
|
}
|
||||||
|
return super.getBounds(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the vertical text alignment.
|
||||||
|
*
|
||||||
|
* @param alignment
|
||||||
|
* vertical alignment
|
||||||
|
*/
|
||||||
|
public void setVerticalTextAlignment(int alignment) {
|
||||||
|
firePropertyChange("verticalTextAlignment", valign, alignment);
|
||||||
|
valign = alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the horizontal text alignment.
|
||||||
|
*
|
||||||
|
* @param alignment
|
||||||
|
* horizontal alignment
|
||||||
|
*/
|
||||||
|
public void setHorizontalTextAlignment(int alignment) {
|
||||||
|
firePropertyChange("horizontalTextAlignment", halign, alignment);
|
||||||
|
halign = alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the vertical text alignment.
|
||||||
|
*
|
||||||
|
* @return vertical text alignment
|
||||||
|
*/
|
||||||
|
public int getVerticalTextAlignment() {
|
||||||
|
return valign;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the horizontal text alignment.
|
||||||
|
*
|
||||||
|
* @return horizontal text alignment
|
||||||
|
*/
|
||||||
|
public int getHorizontalTextAlignment() {
|
||||||
|
return halign;
|
||||||
|
}
|
||||||
|
}
|
||||||
592
src/forge/gui/MultiLineLabelUI.java
Executable file
592
src/forge/gui/MultiLineLabelUI.java
Executable file
@@ -0,0 +1,592 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009 Samuel Sjoberg
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package forge.gui;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.plaf.ComponentUI;
|
||||||
|
import javax.swing.plaf.LabelUI;
|
||||||
|
import javax.swing.plaf.basic.BasicLabelUI;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ComponentEvent;
|
||||||
|
import java.awt.event.ComponentListener;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label UI delegate that supports multiple lines and line wrapping. Hard line
|
||||||
|
* breaks (<code>\n</code>) are preserved. If the dimensions of the label is too
|
||||||
|
* small to fit all content, the string will be clipped and "..." appended to
|
||||||
|
* the end of the visible text (similar to the default behavior of
|
||||||
|
* <code>JLabel</code>). If used in conjunction with a {@link MultiLineLabel},
|
||||||
|
* text alignment (horizontal and vertical) is supported. The UI delegate can be
|
||||||
|
* used on a regular <code>JLabel</code> if text alignment isn't required. The
|
||||||
|
* default alignment, left and vertically centered, will then be used.
|
||||||
|
* <p>
|
||||||
|
* Example of usage:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* JLabel myLabel = new JLabel();
|
||||||
|
* myLabel.setUI(MultiLineLabelUI.labelUI);
|
||||||
|
* myLabel.setText("A long label that will wrap automatically.");
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The line and wrapping support is implemented without using a
|
||||||
|
* <code>View</code> to make it easy for subclasses to add custom text effects
|
||||||
|
* by overriding {@link #paintEnabledText(JLabel, Graphics, String, int, int)}
|
||||||
|
* and {@link #paintDisabledText(JLabel, Graphics, String, int, int)}. This
|
||||||
|
* class is designed to be easily extended by subclasses.
|
||||||
|
*
|
||||||
|
* @author Samuel Sjoberg, http://samuelsjoberg.com
|
||||||
|
* @version 1.3.0
|
||||||
|
*/
|
||||||
|
public class MultiLineLabelUI extends BasicLabelUI implements ComponentListener {
|
||||||
|
|
||||||
|
/** Shared instance of the UI delegate. */
|
||||||
|
public static LabelUI labelUI = new MultiLineLabelUI();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client property key used to store the calculated wrapped lines on the
|
||||||
|
* JLabel.
|
||||||
|
*/
|
||||||
|
public static final String PROPERTY_KEY = "WrappedText";
|
||||||
|
|
||||||
|
// Static references to avoid heap allocations.
|
||||||
|
protected static Rectangle paintIconR = new Rectangle();
|
||||||
|
protected static Rectangle paintTextR = new Rectangle();
|
||||||
|
protected static Rectangle paintViewR = new Rectangle();
|
||||||
|
protected static Insets paintViewInsets = new Insets(0, 0, 0, 0);
|
||||||
|
|
||||||
|
/** Font metrics of the JLabel being rendered. */
|
||||||
|
protected FontMetrics metrics;
|
||||||
|
|
||||||
|
/** Default size of the lines list. */
|
||||||
|
protected static int defaultSize = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the shared UI instance.
|
||||||
|
*
|
||||||
|
* @param c
|
||||||
|
* the component about to be installed
|
||||||
|
* @return the shared UI delegate instance
|
||||||
|
*/
|
||||||
|
public static ComponentUI createUI(JComponent c) {
|
||||||
|
return labelUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
protected void uninstallDefaults(JLabel c) {
|
||||||
|
super.uninstallDefaults(c);
|
||||||
|
clearCache(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
protected void installListeners(JLabel c) {
|
||||||
|
super.installListeners(c);
|
||||||
|
c.addComponentListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
protected void uninstallListeners(JLabel c) {
|
||||||
|
super.uninstallListeners(c);
|
||||||
|
c.removeComponentListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the wrapped line cache.
|
||||||
|
*
|
||||||
|
* @param l
|
||||||
|
* the label containing a cached value
|
||||||
|
*/
|
||||||
|
protected void clearCache(JLabel l) {
|
||||||
|
l.putClientProperty(PROPERTY_KEY, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public void propertyChange(PropertyChangeEvent e) {
|
||||||
|
super.propertyChange(e);
|
||||||
|
final String name = e.getPropertyName();
|
||||||
|
if (name.equals("text") || "font".equals(name)) {
|
||||||
|
clearCache((JLabel) e.getSource());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the paint rectangles for the icon and text for the passed
|
||||||
|
* label.
|
||||||
|
*
|
||||||
|
* @param l
|
||||||
|
* a label
|
||||||
|
* @param fm
|
||||||
|
* the font metrics to use, or <code>null</code> to get the font
|
||||||
|
* metrics from the label
|
||||||
|
* @param width
|
||||||
|
* label width
|
||||||
|
* @param height
|
||||||
|
* label height
|
||||||
|
*/
|
||||||
|
protected void updateLayout(JLabel l, FontMetrics fm, int width, int height) {
|
||||||
|
if (fm == null) {
|
||||||
|
fm = l.getFontMetrics(l.getFont());
|
||||||
|
}
|
||||||
|
metrics = fm;
|
||||||
|
|
||||||
|
String text = l.getText();
|
||||||
|
Icon icon = l.getIcon();
|
||||||
|
Insets insets = l.getInsets(paintViewInsets);
|
||||||
|
|
||||||
|
paintViewR.x = insets.left;
|
||||||
|
paintViewR.y = insets.top;
|
||||||
|
paintViewR.width = width - (insets.left + insets.right);
|
||||||
|
paintViewR.height = height - (insets.top + insets.bottom);
|
||||||
|
|
||||||
|
paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
|
||||||
|
paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
|
||||||
|
|
||||||
|
layoutCL(l, fm, text, icon, paintViewR, paintIconR, paintTextR);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void prepareGraphics(Graphics g) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public void paint(Graphics g, JComponent c) {
|
||||||
|
|
||||||
|
// parent's update method fills the background
|
||||||
|
prepareGraphics(g);
|
||||||
|
|
||||||
|
JLabel label = (JLabel) c;
|
||||||
|
String text = label.getText();
|
||||||
|
Icon icon = (label.isEnabled()) ? label.getIcon() : label
|
||||||
|
.getDisabledIcon();
|
||||||
|
|
||||||
|
if ((icon == null) && (text == null)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontMetrics fm = g.getFontMetrics();
|
||||||
|
|
||||||
|
updateLayout(label, fm, c.getWidth(), c.getHeight());
|
||||||
|
|
||||||
|
if (icon != null) {
|
||||||
|
icon.paintIcon(c, g, paintIconR.x, paintIconR.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != null) {
|
||||||
|
View v = (View) c.getClientProperty("html");
|
||||||
|
if (v != null) {
|
||||||
|
// HTML view disables multi-line painting.
|
||||||
|
v.paint(g, paintTextR);
|
||||||
|
} else {
|
||||||
|
// Paint the multi line text
|
||||||
|
paintTextLines(g, label, fm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paint the wrapped text lines.
|
||||||
|
*
|
||||||
|
* @param g
|
||||||
|
* graphics component to paint on
|
||||||
|
* @param label
|
||||||
|
* the label being painted
|
||||||
|
* @param fm
|
||||||
|
* font metrics for current font
|
||||||
|
*/
|
||||||
|
protected void paintTextLines(Graphics g, JLabel label, FontMetrics fm) {
|
||||||
|
List<String> lines = getTextLines(label);
|
||||||
|
|
||||||
|
// Available component height to paint on.
|
||||||
|
int height = getAvailableHeight(label);
|
||||||
|
|
||||||
|
int textHeight = lines.size() * fm.getHeight();
|
||||||
|
while (textHeight > height) {
|
||||||
|
// Remove one line until no. of visible lines is found.
|
||||||
|
textHeight -= fm.getHeight();
|
||||||
|
}
|
||||||
|
paintTextR.height = Math.min(textHeight, height);
|
||||||
|
paintTextR.y = alignmentY(label, fm, paintTextR);
|
||||||
|
|
||||||
|
int textX = paintTextR.x;
|
||||||
|
int textY = paintTextR.y;
|
||||||
|
|
||||||
|
for (Iterator<String> it = lines.iterator(); it.hasNext()
|
||||||
|
&& paintTextR.contains(textX, textY + getAscent(fm)); textY += fm
|
||||||
|
.getHeight()) {
|
||||||
|
|
||||||
|
String text = it.next().trim();
|
||||||
|
|
||||||
|
if (it.hasNext()
|
||||||
|
&& !paintTextR.contains(textX, textY + fm.getHeight()
|
||||||
|
+ getAscent(fm))) {
|
||||||
|
// The last visible row, add a clip indication.
|
||||||
|
text = clip(text, fm, paintTextR);
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = alignmentX(label, fm, text, paintTextR);
|
||||||
|
|
||||||
|
if (label.isEnabled()) {
|
||||||
|
paintEnabledText(label, g, text, x, textY);
|
||||||
|
} else {
|
||||||
|
paintDisabledText(label, g, text, x, textY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the available height to paint text on. This is the height of the
|
||||||
|
* passed component with insets subtracted.
|
||||||
|
*
|
||||||
|
* @param l
|
||||||
|
* a component
|
||||||
|
* @return the available height
|
||||||
|
*/
|
||||||
|
protected int getAvailableHeight(JLabel l) {
|
||||||
|
l.getInsets(paintViewInsets);
|
||||||
|
return l.getHeight() - paintViewInsets.top - paintViewInsets.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a clip indication to the string. It is important that the string
|
||||||
|
* length does not exceed the length or the original string.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* the to be painted
|
||||||
|
* @param fm
|
||||||
|
* font metrics
|
||||||
|
* @param bounds
|
||||||
|
* the text bounds
|
||||||
|
* @return the clipped string
|
||||||
|
*/
|
||||||
|
protected String clip(String text, FontMetrics fm, Rectangle bounds) {
|
||||||
|
// Fast and lazy way to insert a clip indication is to simply replace
|
||||||
|
// the last characters in the string with the clip indication.
|
||||||
|
// A better way would be to use metrics and calculate how many (if any)
|
||||||
|
// characters that need to be replaced.
|
||||||
|
if (text.length() < 3) {
|
||||||
|
return "...";
|
||||||
|
}
|
||||||
|
return text.substring(0, text.length() - 3) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establish the vertical text alignment. The default alignment is to center
|
||||||
|
* the text in the label.
|
||||||
|
*
|
||||||
|
* @param label
|
||||||
|
* the label to paint
|
||||||
|
* @param fm
|
||||||
|
* font metrics
|
||||||
|
* @param bounds
|
||||||
|
* the text bounds rectangle
|
||||||
|
* @return the vertical text alignment, defaults to CENTER.
|
||||||
|
*/
|
||||||
|
protected int alignmentY(JLabel label, FontMetrics fm, Rectangle bounds) {
|
||||||
|
final int height = getAvailableHeight(label);
|
||||||
|
int textHeight = bounds.height;
|
||||||
|
|
||||||
|
if (label instanceof MultiLineLabel) {
|
||||||
|
int align = ((MultiLineLabel) label).getVerticalTextAlignment();
|
||||||
|
switch (align) {
|
||||||
|
case JLabel.TOP:
|
||||||
|
return getAscent(fm) + paintViewInsets.top;
|
||||||
|
case JLabel.BOTTOM:
|
||||||
|
return getAscent(fm) + height - paintViewInsets.top
|
||||||
|
+ paintViewInsets.bottom - textHeight;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center alignment
|
||||||
|
int textY = paintViewInsets.top + (height - textHeight) / 2
|
||||||
|
+ getAscent(fm);
|
||||||
|
return Math.max(textY, getAscent(fm) + paintViewInsets.top);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getAscent(FontMetrics fm) {
|
||||||
|
return fm.getAscent() + fm.getLeading();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establish the horizontal text alignment. The default alignment is left
|
||||||
|
* aligned text.
|
||||||
|
*
|
||||||
|
* @param label
|
||||||
|
* the label to paint
|
||||||
|
* @param fm
|
||||||
|
* font metrics
|
||||||
|
* @param s
|
||||||
|
* the string to paint
|
||||||
|
* @param bounds
|
||||||
|
* the text bounds rectangle
|
||||||
|
* @return the x-coordinate to use when painting for proper alignment
|
||||||
|
*/
|
||||||
|
protected int alignmentX(JLabel label, FontMetrics fm, String s,
|
||||||
|
Rectangle bounds) {
|
||||||
|
if (label instanceof MultiLineLabel) {
|
||||||
|
int align = ((MultiLineLabel) label).getHorizontalTextAlignment();
|
||||||
|
switch (align) {
|
||||||
|
case JLabel.RIGHT:
|
||||||
|
return bounds.x + paintViewR.width - fm.stringWidth(s);
|
||||||
|
case JLabel.CENTER:
|
||||||
|
return bounds.x + paintViewR.width / 2 - fm.stringWidth(s) / 2;
|
||||||
|
default:
|
||||||
|
return bounds.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bounds.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the given string to see if it should be rendered as HTML. Code
|
||||||
|
* based on implementation found in
|
||||||
|
* <code>BasicHTML.isHTMLString(String)</code> in future JDKs.
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
* the string
|
||||||
|
* @return <code>true</code> if string is HTML, otherwise <code>false</code>
|
||||||
|
*/
|
||||||
|
private static boolean isHTMLString(String s) {
|
||||||
|
if (s != null) {
|
||||||
|
if ((s.length() >= 6) && (s.charAt(0) == '<')
|
||||||
|
&& (s.charAt(5) == '>')) {
|
||||||
|
String tag = s.substring(1, 5);
|
||||||
|
return tag.equalsIgnoreCase("html");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public Dimension getPreferredSize(JComponent c) {
|
||||||
|
Dimension d = super.getPreferredSize(c);
|
||||||
|
JLabel label = (JLabel) c;
|
||||||
|
|
||||||
|
if (isHTMLString(label.getText())) {
|
||||||
|
return d; // HTML overrides everything and we don't need to process
|
||||||
|
}
|
||||||
|
|
||||||
|
// Width calculated by super is OK. The preferred width is the width of
|
||||||
|
// the unwrapped content as long as it does not exceed the width of the
|
||||||
|
// parent container.
|
||||||
|
|
||||||
|
if (c.getParent() != null) {
|
||||||
|
// Ensure that preferred width never exceeds the available width
|
||||||
|
// (including its border insets) of the parent container.
|
||||||
|
Insets insets = c.getParent().getInsets();
|
||||||
|
Dimension size = c.getParent().getSize();
|
||||||
|
if (size.width > 0) {
|
||||||
|
// If width isn't set component shouldn't adjust.
|
||||||
|
d.width = size.width - insets.left - insets.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLayout(label, null, d.width, d.height);
|
||||||
|
|
||||||
|
// The preferred height is either the preferred height of the text
|
||||||
|
// lines, or the height of the icon.
|
||||||
|
d.height = Math.max(d.height, getPreferredHeight(label));
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The preferred height of the label is the height of the lines with added
|
||||||
|
* top and bottom insets.
|
||||||
|
*
|
||||||
|
* @param label
|
||||||
|
* the label
|
||||||
|
* @return the preferred height of the wrapped lines.
|
||||||
|
*/
|
||||||
|
protected int getPreferredHeight(JLabel label) {
|
||||||
|
int numOfLines = getTextLines(label).size();
|
||||||
|
Insets insets = label.getInsets(paintViewInsets);
|
||||||
|
return numOfLines * metrics.getHeight() + insets.top + insets.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the lines of text contained in the text label. The prepared lines is
|
||||||
|
* cached as a client property, accessible via {@link #PROPERTY_KEY}.
|
||||||
|
*
|
||||||
|
* @param l
|
||||||
|
* the label
|
||||||
|
* @return the text lines of the label.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected List<String> getTextLines(JLabel l) {
|
||||||
|
List<String> lines = (List<String>) l.getClientProperty(PROPERTY_KEY);
|
||||||
|
if (lines == null) {
|
||||||
|
lines = prepareLines(l);
|
||||||
|
l.putClientProperty(PROPERTY_KEY, lines);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public void componentHidden(ComponentEvent e) {
|
||||||
|
// Don't care
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public void componentMoved(ComponentEvent e) {
|
||||||
|
// Don't care
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public void componentResized(ComponentEvent e) {
|
||||||
|
clearCache((JLabel) e.getSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public void componentShown(ComponentEvent e) {
|
||||||
|
// Don't care
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the text lines for rendering. The lines are wrapped to fit in the
|
||||||
|
* current available space for text. Explicit line breaks are preserved.
|
||||||
|
*
|
||||||
|
* @param l
|
||||||
|
* the label to render
|
||||||
|
* @return a list of text lines to render
|
||||||
|
*/
|
||||||
|
protected List<String> prepareLines(JLabel l) {
|
||||||
|
List<String> lines = new ArrayList<String>(defaultSize);
|
||||||
|
String text = l.getText();
|
||||||
|
if (text == null) {
|
||||||
|
return null; // Null guard
|
||||||
|
}
|
||||||
|
PlainDocument doc = new PlainDocument();
|
||||||
|
try {
|
||||||
|
doc.insertString(0, text, null);
|
||||||
|
} catch (BadLocationException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Element root = doc.getDefaultRootElement();
|
||||||
|
for (int i = 0, j = root.getElementCount(); i < j; i++) {
|
||||||
|
wrap(lines, root.getElement(i));
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If necessary, wrap the text into multiple lines.
|
||||||
|
*
|
||||||
|
* @param lines
|
||||||
|
* line array in which to store the wrapped lines
|
||||||
|
* @param elem
|
||||||
|
* the document element containing the text content
|
||||||
|
*/
|
||||||
|
protected void wrap(List<String> lines, Element elem) {
|
||||||
|
int p1 = elem.getEndOffset();
|
||||||
|
Document doc = elem.getDocument();
|
||||||
|
for (int p0 = elem.getStartOffset(); p0 < p1;) {
|
||||||
|
int p = calculateBreakPosition(doc, p0, p1);
|
||||||
|
try {
|
||||||
|
lines.add(doc.getText(p0, p - p0));
|
||||||
|
} catch (BadLocationException e) {
|
||||||
|
throw new Error("Can't get line text. p0=" + p0 + " p=" + p);
|
||||||
|
}
|
||||||
|
p0 = (p == p0) ? p1 : p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the position on which to break (wrap) the line.
|
||||||
|
*
|
||||||
|
* @param doc
|
||||||
|
* the document
|
||||||
|
* @param p0
|
||||||
|
* start position
|
||||||
|
* @param p1
|
||||||
|
* end position
|
||||||
|
* @return the actual end position, will be <code>p1</code> if content does
|
||||||
|
* not need to wrap, otherwise it will be less than <code>p1</code>.
|
||||||
|
*/
|
||||||
|
protected int calculateBreakPosition(Document doc, int p0, int p1) {
|
||||||
|
Segment segment = SegmentCache.getSegment();
|
||||||
|
try {
|
||||||
|
doc.getText(p0, p1 - p0, segment);
|
||||||
|
} catch (BadLocationException e) {
|
||||||
|
throw new Error("Can't get line text");
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = paintTextR.width;
|
||||||
|
int p = p0
|
||||||
|
+ Utilities.getBreakLocation(segment, metrics, 0, width, null,
|
||||||
|
p0);
|
||||||
|
SegmentCache.releaseSegment(segment);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static singleton {@link Segment} cache.
|
||||||
|
*
|
||||||
|
* @see javax.swing.text.SegmentCache
|
||||||
|
*
|
||||||
|
* @author Samuel Sjoberg
|
||||||
|
*/
|
||||||
|
protected static final class SegmentCache {
|
||||||
|
|
||||||
|
/** Reused segments. */
|
||||||
|
private ArrayList<Segment> segments = new ArrayList<Segment>(2);
|
||||||
|
|
||||||
|
/** Singleton instance. */
|
||||||
|
private static SegmentCache cache = new SegmentCache();
|
||||||
|
|
||||||
|
/** Private constructor. */
|
||||||
|
private SegmentCache() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a <code>Segment</code>. When done, the <code>Segment</code>
|
||||||
|
* should be recycled by invoking {@link #releaseSegment(Segment)}.
|
||||||
|
*
|
||||||
|
* @return a <code>Segment</code>.
|
||||||
|
*/
|
||||||
|
public static Segment getSegment() {
|
||||||
|
int size = cache.segments.size();
|
||||||
|
if (size > 0) {
|
||||||
|
return cache.segments.remove(size - 1);
|
||||||
|
}
|
||||||
|
return new Segment();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases a <code>Segment</code>. A segment should not be used after
|
||||||
|
* it is released, and a segment should never be released more than
|
||||||
|
* once.
|
||||||
|
*/
|
||||||
|
public static void releaseSegment(Segment segment) {
|
||||||
|
segment.array = null;
|
||||||
|
segment.count = 0;
|
||||||
|
cache.segments.add(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user