I'm using RSyntaxTextArea for a minimized IDE I'm working on, Everything seems to be really working smoothly except for the line numbering, which I couldn't really make it show:
RSyntaxTextArea textArea = new RSyntaxTextArea(20, 60);
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_C);
textArea.setCodeFoldingEnabled(true);
textArea.setAntiAliasingEnabled(true);
RTextScrollPane sp = new RTextScrollPane(textArea);
sp.setLineNumbersEnabled(true);
sp.setFoldIndicatorEnabled(true);
if ( sp.getLineNumbersEnabled() )
{
System.out.println("Enabled"); // it prints the line but it's not showing
}
contentPane.add(/*textEditorScrollPane*/ textArea, BorderLayout.CENTER);
I can't figure out why it's not showing the line numbers..
It's not showing the scrollbars either, right? Assuming that contentPane is where you want your components, you need to add the RTextScrollPane instance to the contentPane, not the RSyntaxTextArea instance. The Gutter, which displays line numbers, is a part of the RTextScrollPane - an extended JScrollPane.
If you don't add a scroll pane to your GUI, it will not be shown, nor will you be able to scroll around. :P
So try the following:
contentPane.add(sp, BorderLayout.CENTER);
Alternatively, you can use the following class:
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
/**
* This class will display line numbers for a related text component. The text
* component must use the same line height for each line. TextLineNumber
* supports wrapped lines and will highlight the line number of the current
* line in the text component.
* <p>
* This class was designed to be used as a component added to the row header
* of a JScrollPane.
*/
public class TextLineNumber extends JPanel
implements CaretListener, DocumentListener, PropertyChangeListener
{
public final static float LEFT = 0.0f;
public final static float CENTER = 0.5f;
public final static float RIGHT = 1.0f;
private final static Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY);
private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
// Text component this TextTextLineNumber component is in sync with
private JTextComponent component;
// Properties that can be changed
private boolean updateFont;
private int borderGap;
private Color currentLineForeground;
private float digitAlignment;
private int minimumDisplayDigits;
// Keep history information to reduce the number of times the component
// needs to be repainted
private int lastDigits;
private int lastHeight;
private int lastLine;
private HashMap<String, FontMetrics> fonts;
/**
* Create a line number component for a text component. This minimum
* display width will be based on 3 digits.
*
* #param component the related text component
*/
public TextLineNumber(JTextComponent component)
{
this(component, 3);
}
/**
* Create a line number component for a text component.
*
* #param component the related text component
* #param minimumDisplayDigits the number of digits used to calculate
* the minimum width of the component
*/
public TextLineNumber(JTextComponent component, int minimumDisplayDigits)
{
this.component = component;
setFont(component.getFont());
setBorderGap(5);
setCurrentLineForeground(Color.RED);
setDigitAlignment(RIGHT);
setMinimumDisplayDigits(minimumDisplayDigits);
component.getDocument().addDocumentListener(this);
component.addCaretListener(this);
component.addPropertyChangeListener("font", this);
}
/**
* Gets the update font property
*
* #return the update font property
*/
public boolean getUpdateFont()
{
return updateFont;
}
/**
* Set the update font property. Indicates whether this Font should be
* updated automatically when the Font of the related text component
* is changed.
*
* #param updateFont when true update the Font and repaint the line
* numbers, otherwise just repaint the line numbers.
*/
public void setUpdateFont(boolean updateFont)
{
this.updateFont = updateFont;
}
/**
* Gets the border gap
*
* #return the border gap in pixels
*/
public int getBorderGap()
{
return borderGap;
}
/**
* The border gap is used in calculating the left and right insets of the
* border. Default value is 5.
*
* #param borderGap the gap in pixels
*/
public void setBorderGap(int borderGap)
{
this.borderGap = borderGap;
Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
setBorder(new CompoundBorder(OUTER, inner));
lastDigits = 0;
setPreferredWidth();
}
/**
* Gets the current line rendering Color
*
* #return the Color used to render the current line number
*/
public Color getCurrentLineForeground()
{
return currentLineForeground == null ? getForeground() : currentLineForeground;
}
/**
* The Color used to render the current line digits. Default is Coolor.RED.
*
* #param currentLineForeground the Color used to render the current line
*/
public void setCurrentLineForeground(Color currentLineForeground)
{
this.currentLineForeground = currentLineForeground;
}
/**
* Gets the digit alignment
*
* #return the alignment of the painted digits
*/
public float getDigitAlignment()
{
return digitAlignment;
}
/**
* Specify the horizontal alignment of the digits within the component.
* Common values would be:
* <ul>
* <li>TextLineNumber.LEFT
* <li>TextLineNumber.CENTER
* <li>TextLineNumber.RIGHT (default)
* </ul>
*
* #param currentLineForeground the Color used to render the current line
*/
public void setDigitAlignment(float digitAlignment)
{
this.digitAlignment =
digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
}
/**
* Gets the minimum display digits
*
* #return the minimum display digits
*/
public int getMinimumDisplayDigits()
{
return minimumDisplayDigits;
}
/**
* Specify the mimimum number of digits used to calculate the preferred
* width of the component. Default is 3.
*
* #param minimumDisplayDigits the number digits used in the preferred
* width calculation
*/
public void setMinimumDisplayDigits(int minimumDisplayDigits)
{
this.minimumDisplayDigits = minimumDisplayDigits;
setPreferredWidth();
}
/**
* Calculate the width needed to display the maximum line number
*/
private void setPreferredWidth()
{
Element root = component.getDocument().getDefaultRootElement();
int lines = root.getElementCount();
int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits);
// Update sizes when number of digits in the line number changes
if (lastDigits != digits)
{
lastDigits = digits;
FontMetrics fontMetrics = getFontMetrics(getFont());
int width = fontMetrics.charWidth('0') * digits;
Insets insets = getInsets();
int preferredWidth = insets.left + insets.right + width;
Dimension d = getPreferredSize();
d.setSize(preferredWidth, HEIGHT);
setPreferredSize(d);
setSize(d);
}
}
/**
* Draw the line numbers
*/
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Determine the width of the space available to draw the line number
FontMetrics fontMetrics = component.getFontMetrics(component.getFont());
Insets insets = getInsets();
int availableWidth = getSize().width - insets.left - insets.right;
// Determine the rows to draw within the clipped bounds.
Rectangle clip = g.getClipBounds();
int rowStartOffset = component.viewToModel(new Point(0, clip.y));
int endOffset = component.viewToModel(new Point(0, clip.y + clip.height));
while (rowStartOffset <= endOffset)
{
try
{
if (isCurrentLine(rowStartOffset))
{
g.setColor(getCurrentLineForeground());
} else
{
g.setColor(getForeground());
}
// Get the line number as a string and then determine the
// "X" and "Y" offsets for drawing the string.
String lineNumber = getTextLineNumber(rowStartOffset);
int stringWidth = fontMetrics.stringWidth(lineNumber);
int x = getOffsetX(availableWidth, stringWidth) + insets.left;
int y = getOffsetY(rowStartOffset, fontMetrics);
g.drawString(lineNumber, x, y);
// Move to the next row
rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
} catch (Exception e)
{
break;
}
}
}
/*
* We need to know if the caret is currently positioned on the line we
* are about to paint so the line number can be highlighted.
*/
private boolean isCurrentLine(int rowStartOffset)
{
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
if (root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition))
{
return true;
} else
{
return false;
}
}
/*
* Get the line number to be drawn. The empty string will be returned
* when a line of text has wrapped.
*/
protected String getTextLineNumber(int rowStartOffset)
{
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
if (line.getStartOffset() == rowStartOffset)
{
return String.valueOf(index + 1);
} else
{
return "";
}
}
/*
* Determine the X offset to properly align the line number when drawn
*/
private int getOffsetX(int availableWidth, int stringWidth)
{
return (int) ((availableWidth - stringWidth) * digitAlignment);
}
/*
* Determine the Y offset for the current row
*/
private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics)
throws BadLocationException
{
// Get the bounding rectangle of the row
Rectangle r = component.modelToView(rowStartOffset);
int lineHeight = fontMetrics.getHeight();
int y = r.y + r.height;
int descent = 0;
// The text needs to be positioned above the bottom of the bounding
// rectangle based on the descent of the font(s) contained on the row.
if (r.height == lineHeight) // default font is being used
{
descent = fontMetrics.getDescent();
} else // We need to check all the attributes for font changes
{
if (fonts == null)
{
fonts = new HashMap<>();
}
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
for (int i = 0; i < line.getElementCount(); i++)
{
Element child = line.getElement(i);
AttributeSet as = child.getAttributes();
String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
String key = fontFamily + fontSize;
FontMetrics fm = fonts.get(key);
if (fm == null)
{
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
fm = component.getFontMetrics(font);
fonts.put(key, fm);
}
descent = Math.max(descent, fm.getDescent());
}
}
return y - descent;
}
//
// Implement CaretListener interface
//
#Override
public void caretUpdate(CaretEvent e)
{
// Get the line the caret is positioned on
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
int currentLine = root.getElementIndex(caretPosition);
// Need to repaint so the correct line number can be highlighted
if (lastLine != currentLine)
{
repaint();
lastLine = currentLine;
}
}
//
// Implement DocumentListener interface
//
#Override
public void changedUpdate(DocumentEvent e)
{
documentChanged();
}
#Override
public void insertUpdate(DocumentEvent e)
{
documentChanged();
}
#Override
public void removeUpdate(DocumentEvent e)
{
documentChanged();
}
/*
* A document change may affect the number of displayed lines of text.
* Therefore the lines numbers will also change.
*/
private void documentChanged()
{
// View of the component has not been updated at the time
// the DocumentEvent is fired
SwingUtilities.invokeLater(() ->
{
try
{
int endPos = component.getDocument().getLength();
Rectangle rect = component.modelToView(endPos);
if (rect != null && rect.y != lastHeight)
{
setPreferredWidth();
repaint();
lastHeight = rect.y;
}
} catch (BadLocationException ex)
{ /* nothing to do */ }
});
}
//
// Implement PropertyChangeListener interface
//
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getNewValue() instanceof Font)
{
if (updateFont)
{
Font newFont = (Font) evt.getNewValue();
setFont(newFont);
lastDigits = 0;
setPreferredWidth();
} else
{
repaint();
}
}
}
}
Then line numbers can be added like this:
TextLineNumber textLineNumber = new TextLineNumber(sourceCodeArea);
sourceCodeAreaScrollPane.setRowHeaderView(textLineNumber);
Related
I currently have a canvas where I will first draw an image before all the other canvas mouse-draw functions come in (to draw the boxes). I also have an undo function which will remove the last drawn box, clear the entire canvas and redraw all the remaining boxes back onto the canvas. However, after undoing, the image will somehow appear on top of the boxes and covering them, instead of below as it should be. I was able to fix this using z-index in HTML5 previously, but do not know how to do so using the Java way.
This is my Canvas.java (undo method is towards the end):
package com.vaadin.starter.beveragebuddy.ui.components;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.ElementFactory;
import com.vaadin.flow.shared.Registration;
import com.vaadin.starter.beveragebuddy.backend.MainLayout;
import elemental.json.JsonObject;
import java.util.ArrayList;
import java.util.List;
/**
* Canvas component that you can draw shapes and images on. It's a Java wrapper
* for the
* <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">HTML5
* canvas</a>.
* <p>
* Use {#link #getContext()} to get API for rendering shapes and images on the
* canvas.
* <p>
*/
#Tag("canvas")
#SuppressWarnings("serial")
public class Canvas extends Component implements HasStyle, HasSize {
private static CanvasRenderingContext2D context;
private Element element;
private boolean mouseSelect = false;
private boolean mouseIsDown = false;
private double endX;
private double endY;
public static int boxCount = 0;
public static boolean undoCalled = false;
public static ArrayList <BoundingBox> arrayBoxes = new ArrayList<BoundingBox>();
public static ArrayList <MousePosition> mousePosArray = new ArrayList<MousePosition>();
public static ArrayList <SelectBox> selectBoxes = new ArrayList<SelectBox>();
private List<Runnable> mouseMoveListeners = new ArrayList<>(0);
public static ArrayList<BoundingBox> getArrayBoxes() {
return arrayBoxes;
}
public static ArrayList<MousePosition> getMousePosArray() {
return mousePosArray;
}
public static void setMousePosArray(ArrayList<MousePosition> mousePosArray) {
Canvas.mousePosArray = mousePosArray;
}
/**
* Creates a new canvas component with the given size.
* <p>
* Use the API provided by {#link #getContext()} to render graphics on the
* canvas.
* <p>
* The width and height parameters will be used for the canvas' coordinate
* system. They will determine the size of the component in pixels, unless
* you explicitly set the component's size with {#link #setWidth(String)} or
* {#link #setHeight(String)}.
*
// * #param width
// * the width of the canvas
// * #param height
// * the height of the canvas
// */
public Registration addMouseMoveListener(Runnable listener) {
mouseMoveListeners.add(listener);
return () -> mouseMoveListeners.remove(listener);
}
public Canvas(int width, int height) {
context = new CanvasRenderingContext2D(this);
context.drawImage("https://freedesignfile.com/upload/2016/10/Red-Clouds-and-Prairie-Background.jpg", 0, 0);
element = getElement();
element.getStyle().set("border", "1px solid");
getElement().setAttribute("width", String.valueOf(width));
getElement().setAttribute("height", String.valueOf(height));
element.addEventListener("mousedown", event -> { // Retrieve Starting Position on MouseDown
Element boundingBoxResult = ElementFactory.createDiv();
element.appendChild(boundingBoxResult);
JsonObject evtData = event.getEventData();
double xBox = evtData.getNumber("event.x");
double yBox = evtData.getNumber("event.y");
boundingBoxResult.setAttribute("data-x", String.format("%f", xBox));
boundingBoxResult.setAttribute("data-y", String.format("%f", yBox));
BoundingBox newBox = new BoundingBox("","", xBox, yBox, 0.0, 0.0);
arrayBoxes.add(newBox);
SelectBox select = new SelectBox(xBox, 0.0, yBox, 0.0);
selectBoxes.add(0, select);
mouseIsDown=true;
mouseMoveListeners.forEach(Runnable::run);
}).addEventData("event.x").addEventData("event.y");
element.addEventListener("mouseup", event -> { // Draw Box on MouseUp
Element boundingBoxResult2 = ElementFactory.createDiv();
element.appendChild(boundingBoxResult2);
JsonObject evtData2 = event.getEventData();
endX = evtData2.getNumber("event.x");
endY = evtData2.getNumber("event.y");
boundingBoxResult2.setAttribute("end-x", String.format("%f", endX));
boundingBoxResult2.setAttribute("end-y", String.format("%f", endY));
// System.out.println(endX);
// System.out.println(endY);
double xcoordi = 0;
double ycoordi = 0;
double boxWidth = 0;
double boxHeight = 0;
// for (int i = 0; i < arrayBoxes.size(); i++) {
System.out.println(endX);
System.out.println(endY);
arrayBoxes.get(boxCount).setWidth(endX, arrayBoxes.get(boxCount).xcoordi);
arrayBoxes.get(boxCount).setHeight(endY, arrayBoxes.get(boxCount).ycoordi);
xcoordi = arrayBoxes.get(boxCount).getXcoordi();
ycoordi = arrayBoxes.get(boxCount).getYcoordi();
boxWidth = arrayBoxes.get(boxCount).getWidth();
boxHeight = arrayBoxes.get(boxCount).getHeight();
boxCount++;
mouseIsDown=false;
context.beginPath();
context.setStrokeStyle("green");
context.setLineWidth(2);
context.strokeRect(xcoordi, ycoordi, boxWidth, boxHeight);
context.stroke();
context.fill();
SelectBox select = new SelectBox(endX, 0.0, endY, 0.0);
selectBoxes.add(1, select);
// if (selectBoxes.get(1).getSelectEndX() == selectBoxes.get(0).getSelectStartX()){
// mouseSelect = true;
// context.beginPath();
// context.setStrokeStyle("yellow");
// context.setLineWidth(2);
// context.strokeRect(arrayBoxes.get(i).xcoordi, arrayBoxes.get(i).ycoordi, arrayBoxes.get(i).boxWidth, arrayBoxes.get(i).boxHeight);
// context.fill();
// }
System.out.println(arrayBoxes.toString());
//
// for (int i = 0; i < arrayBoxes.size(); i++){
// if(arrayBoxes.get(i).xcoordi)
// if (endX > arrayBoxes.get(i).xcoordi){
// if (endX < arrayBoxes.get(i).endY)
// }
// }
mouseMoveListeners.forEach(Runnable::run);
}).addEventData("event.x").addEventData("event.y");
element.addEventListener("mousemove", event -> { // Retrieve Mouse Position when Moving
JsonObject mousePos = event.getEventData();
double mouseX = mousePos.getNumber("event.x");
double mouseY = mousePos.getNumber("event.y");
MousePosition currentPos = new MousePosition(mouseX, mouseY);
mousePosArray.add(0, currentPos);
setMousePosArray(mousePosArray);
mouseMoveListeners.forEach(Runnable::run);
}).addEventData("event.x").addEventData("event.y");
}
public static void undoLast() {
undoCalled = true;
if (arrayBoxes.size() > 0) {
arrayBoxes.remove(arrayBoxes.size() - 1);
}
System.out.println(arrayBoxes.toString());
System.out.println(arrayBoxes.size());
context.clearRect(0, 0, 1580, 700);
context.drawImage("https://freedesignfile.com/upload/2016/10/Red-Clouds-and-Prairie-Background.jpg", 0, 0);
for (int i = 0; i < arrayBoxes.size(); i++){
context.beginPath();
context.setStrokeStyle("green");
context.setLineWidth(2);
context.strokeRect(arrayBoxes.get(i).xcoordi, arrayBoxes.get(i).ycoordi, arrayBoxes.get(i).boxWidth, arrayBoxes.get(i).boxHeight);
context.fill();
}
boxCount--;
System.out.println("Box Count: " + boxCount);
}
/**
* Gets the context for rendering shapes and images in the canvas.
* <p>
* It is a Java wrapper for the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">same
* client-side API</a>.
*
* #return the 2D rendering context of this canvas
*/
public CanvasRenderingContext2D getContext() {
return context;
}
/**
* {#inheritDoc}
* <p>
* <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
* drawing, and it uses the width and height provided in the constructor.
* This coordinate system is independent of the component's size. Changing
* the component's size with this method may scale/stretch the rendered
* graphics.
*/
#Override
public void setWidth(String width) {
HasSize.super.setWidth(width);
}
/**
* {#inheritDoc}
* <p>
* <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
* drawing, and it uses the width and height provided in the constructor.
* This coordinate system is independent of the component's size. Changing
* the component's size with this method may scale/stretch the rendered
* graphics.
*/
#Override
public void setHeight(String height) {
HasSize.super.setHeight(height);
}
/**
* {#inheritDoc}
* <p>
* <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
* drawing, and it uses the width and height provided in the constructor.
* This coordinate system is independent of the component's size. Changing
* the component's size with this method may scale/stretch the rendered
* graphics.
*/
#Override
public void setSizeFull() {
HasSize.super.setSizeFull();
}
public void addComponent(Label label) {
}
}
Any help is much appreciated, thank you!
I believe your issue is due to one or both of these issues
When drawing the image, you correctly wait for it to load before actually drawing it to the canvas (image.onload = ...). This means that your code might start loading the image, then it draws all the boxes, and then the image loads so that it is drawn on top.
You run the image drawing script on beforeClientResponse, which means it might get called after all the code for drawing the boxes is called.
The simplest solution, if you always want to have an image as the background, is to use two canvases on top of each other (with absolute positioning, for example). This way, you can always draw the images to the background canvas, and all boxes to the foreground canvas. A bonus of this is that you don't have to redraw the background even if you clear the foreground canvas.
I need to create a custom border for a JPanel that contains a JButton in the top left corner of the border (like the title in a titled border). Is there any way to do this in Java?
ComponentBorder might fit the bill. I successfully used it in the past, with minor tweaking, to make a TitledBorder with a CheckBox.
(copied from the website):
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
/**
* The ComponentBorder class allows you to place a real component in
* the space reserved for painting the Border of a component.
*
* This class takes advantage of the knowledge that all Swing components are
* also Containers. By default the layout manager is null, so we should be
* able to place a child component anywhere in the parent component. In order
* to prevent the child component from painting over top of the parent
* component a Border is added to the parent componet such that the insets of
* the Border will reserve space for the child component to be painted without
* affecting the parent component.
*/
public class ComponentBorder implements Border
{
public enum Edge
{
TOP,
LEFT,
BOTTOM,
RIGHT;
}
public static final float LEADING = 0.0f;
public static final float CENTER = 0.5f;
public static final float TRAILING = 1.0f;
private JComponent parent;
private JComponent component;
private Edge edge;
private float alignment;
private int gap = 5;
private boolean adjustInsets = true;
private Insets borderInsets = new Insets(0, 0, 0, 0);
/**
* Convenience constructor that uses the default edge (Edge.RIGHT) and
* alignment (CENTER).
*
* #param component the component to be added in the Border area
*/
public ComponentBorder(JComponent component)
{
this(component, Edge.RIGHT);
}
/**
* Convenience constructor that uses the default alignment (CENTER).
*
* #param component the component to be added in the Border area
* #param edge a valid Edge enum of TOP, LEFT, BOTTOM, RIGHT
*/
public ComponentBorder(JComponent component, Edge edge)
{
this(component, edge, CENTER);
}
/**
* Main constructor to create a ComponentBorder.
*
* #param component the component to be added in the Border area
* #param edge a valid Edge enum of TOP, LEFT, BOTTOM, RIGHT
* #param alignment the alignment of the component along the
* specified Edge. Must be in the range 0 - 1.0.
*/
public ComponentBorder(JComponent component, Edge edge, float alignment )
{
this.component = component;
component.setSize( component.getPreferredSize() );
component.setCursor(Cursor.getDefaultCursor());
setEdge( edge );
setAlignment( alignment );
}
public boolean isAdjustInsets()
{
return adjustInsets;
}
public void setAdjustInsets(boolean adjustInsets)
{
this.adjustInsets = adjustInsets;
}
/**
* Get the component alignment along the Border Edge
*
* #return the alignment
*/
public float getAlignment()
{
return alignment;
}
/**
* Set the component alignment along the Border Edge
*
* #param alignment a value in the range 0 - 1.0. Standard values would be
* CENTER (default), LEFT and RIGHT.
*/
public void setAlignment(float alignment)
{
this.alignment = alignment > 1.0f ? 1.0f : alignment < 0.0f ? 0.0f : alignment;
}
/**
* Get the Edge the component is positioned along
*
* #return the Edge
*/
public Edge getEdge()
{
return edge;
}
/**
* Set the Edge the component is positioned along
*
* #param edge the Edge the component is position on.
*/
public void setEdge(Edge edge)
{
this.edge = edge;
}
/**
* Get the gap between the border component and the parent component
*
* #return the gap in pixels.
*/
public int getGap()
{
return gap;
}
/**
* Set the gap between the border component and the parent component
*
* #param gap the gap in pixels (default is 5)
*/
public void setGap(int gap)
{
this.gap = gap;
}
//
// Implement the Border interface
//
public Insets getBorderInsets(Component c)
{
return borderInsets;
}
public boolean isBorderOpaque()
{
return false;
}
/**
* In this case a real component is to be painted. Setting the location
* of the component will cause it to be painted at that location.
*/
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
float x2 = (width - component.getWidth()) * component.getAlignmentX() + x;
float y2 = (height - component.getHeight()) * component.getAlignmentY() + y;
component.setLocation((int)x2, (int)y2);
}
/*
* Install this Border on the specified component by replacing the
* existing Border with a CompoundBorder containing the original Border
* and our ComponentBorder
*
* This method should only be invoked once all the properties of this
* class have been set. Installing the Border more than once will cause
* unpredictable results.
*/
public void install(JComponent parent)
{
this.parent = parent;
determineInsetsAndAlignment();
// Add this Border to the parent
Border current = parent.getBorder();
if (current == null)
{
parent.setBorder(this);
}
else
{
CompoundBorder compound = new CompoundBorder(current, this);
parent.setBorder(compound);
}
// Add component to the parent
parent.add(component);
}
/**
* The insets need to be determined so they are included in the preferred
* size of the component the Border is attached to.
*
* The alignment of the component is determined here so it doesn't need
* to be recalculated every time the Border is painted.
*/
private void determineInsetsAndAlignment()
{
borderInsets = new Insets(0, 0, 0, 0);
// The insets will only be updated for the edge the component will be
// diplayed on.
//
// The X, Y alignment of the component is controlled by both the edge
// and alignment parameters
if (edge == Edge.TOP)
{
borderInsets.top = component.getPreferredSize().height + gap;
component.setAlignmentX(alignment);
component.setAlignmentY(0.0f);
}
else if (edge == Edge.BOTTOM)
{
borderInsets.bottom = component.getPreferredSize().height + gap;
component.setAlignmentX(alignment);
component.setAlignmentY(1.0f);
}
else if (edge == Edge.LEFT)
{
borderInsets.left = component.getPreferredSize().width + gap;
component.setAlignmentX(0.0f);
component.setAlignmentY(alignment);
}
else if (edge == Edge.RIGHT)
{
borderInsets.right = component.getPreferredSize().width + gap;
component.setAlignmentX(1.0f);
component.setAlignmentY(alignment);
}
if (adjustInsets)
adjustBorderInsets();
}
/*
* The complimentary edges of the Border may need to be adjusted to allow
* the component to fit completely in the bounds of the parent component.
*/
private void adjustBorderInsets()
{
Insets parentInsets = parent.getInsets();
// May need to adust the height of the parent component to fit
// the component in the Border
if (edge == Edge.RIGHT || edge == Edge.LEFT)
{
int parentHeight = parent.getPreferredSize().height - parentInsets.top - parentInsets.bottom;
int diff = component.getHeight() - parentHeight;
if (diff > 0)
{
int topDiff = (int)(diff * alignment);
int bottomDiff = diff - topDiff;
borderInsets.top += topDiff;
borderInsets.bottom += bottomDiff;
}
}
// May need to adust the width of the parent component to fit
// the component in the Border
if (edge == Edge.TOP || edge == Edge.BOTTOM)
{
int parentWidth = parent.getPreferredSize().width - parentInsets.left - parentInsets.right;
int diff = component.getWidth() - parentWidth;
if (diff > 0)
{
int leftDiff = (int)(diff * alignment);
int rightDiff = diff - leftDiff;
borderInsets.left += leftDiff;
borderInsets.right += rightDiff;
}
}
}
}
I have a class that extends EditText, on this class i'm trying to make the text to set setEllipsize when the text is to long. For some reason all my tryies didn't work.
It seems that that i can scroll the text horizontal inside the text view...
Can anybody advice how to make it work.
(So far i've tryed many combination of those functions and
that's my part in the code:
public class LMEditTextAutoSize extends EditText {
private boolean autoSize;
public LMEditTextAutoSize(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
mMaxTextSize = getTextSize();
}
public LMEditTextAutoSize(Context context, AttributeSet attrs) {
super(context, attrs);
mTextSize = getTextSize();
mMaxTextSize = getTextSize();
}
public LMEditTextAutoSize(Context context) {
super(context);
mTextSize = getTextSize();
mMaxTextSize = getTextSize();
}
public void setAutoSize(boolean autoSize) {
this.autoSize = autoSize;
resetTextSize();
}
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 50;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
private int widthLimit;
private int heightLimit;
/**
* When text changes, set the force resize flag to true and reset the text size.
*/
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
*
* #param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
#Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
*
* #param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
*
* #return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
*
* #param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
*
* #return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add ellipsis to text that overflows at the smallest text size
*
* #param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add ellipsis to text that overflows at the smallest text size
*
* #return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
if (autoSize) {
if (mTextSize > 0) {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaxTextSize);
mTextSize = mMaxTextSize;
}
}
}
/**
* Resize text after measuring
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed || mNeedsResize) {
widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with specified width and height
*
* #param width
* #param height
*/
public void resizeText(int width, int height) {
if (autoSize) {
CharSequence text = getText();
int oneLineWidth = width;
int lineCount = getLineCount();
int minTextHeight = getTextHeight("oo", getPaint(), width, mMinTextSize);
int maxLineInHeight = height / minTextHeight;
lineCount = lineCount > maxLineInHeight ? maxLineInHeight : lineCount;
if (lineCount > 1) {
width = width * lineCount;
}
// Do not resize if the view does not have dimensions or there is no text
if (text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
return;
}
// Get the text view's paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while (textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
TextPaint paintCopy = new TextPaint(textPaint);
paintCopy.setTextSize(targetTextSize);
float textWidte = paintCopy.measureText(text, 0, text.length());
while (textWidte > width && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
paintCopy.setTextSize(targetTextSize);
textWidte = paintCopy.measureText(text, 0, text.length());
}
if (lineCount > 1) {
int textHeightforLine = getTextHeight(text, textPaint, oneLineWidth, targetTextSize);
while (textHeightforLine > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeightforLine = getTextHeight(text, textPaint, oneLineWidth, targetTextSize);
}
}
// If we had reached our minimum text size and still don't fit, append an ellipsis
if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
// modified: use a copy of TextPaint for measuring
TextPaint paint = new TextPaint(textPaint);
paint.setTextSize(mMinTextSize);
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if (layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if (lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the ellipsis
while (width < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
}
if (end != 0) {
setInputType(InputType.TYPE_CLASS_TEXT);
setSingleLine(true);
setLines(1);
setMaxLines(1);
setEllipsize(TextUtils.TruncateAt.END);
setSelected(true);
setText(text);
}
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if (mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
}
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
TextPaint paintCopy = new TextPaint(paint);
paintCopy.setTextSize(textSize);
StaticLayout layout = new StaticLayout(source, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
}
remove setHorizontallyScrolling(true) it is contradicting with setEllipsize(TextUtils.TruncateAt.END); Refer to the android doc for each ones function
https://developer.android.com/reference/android/widget/TextView.html
I tried to set JTextArea to JPanel as
JPanel panel=new JPanel(new BorderLayout());
JTextArea ta=new JTextArea();
ta.setColumns(20);
ta.setEditable(false);
ta.setLineWrap(true);
ta.setRows(5);
ta.setWrapStyleWord(true);
panel.add(ta,BorderLayout.CENTER);
JPanel panel1=new JPanel();
panel1.setLayout(new VerticalLayout(5,VerticalLayout.BOTH));
panel1.add(panel);
JFrame frame=new JFrame();
frame.getContentPane().add(panel1);
...
The thing is... when the panel1 is re-sized wider and then back narrower the JTextArea becomes cut off. I mean its rows do not restore to 5 but hold as 1 and so all its text is in one line , of course, the WrapStyleWord is inactive :S
So my question is how to make the JTextArea restore to its original scale on frame re-sized?
Here is the VerticalLayout code.
...
Well, based on Guillaume Polet snippet, I tried to write some kind of vertical panels list but the previously mentioned problem takes place :( Here is the code
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.util.Hashtable;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class TestTextArea {
private void initUI() {
JFrame frame = new JFrame("test");
JPanel listPanel = new JPanel();
listPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH));
JPanel mainPanel=new JPanel(new GridLayout());
JScrollPane sp=new JScrollPane();
sp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
sp.setViewportView(listPanel);
mainPanel.add(sp);
listPanel.add(new MyPanel());
listPanel.add(new MyPanel());
listPanel.add(new MyPanel());
listPanel.add(new MyPanel());
listPanel.add(new MyPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestTextArea().initUI();
}
});
}
class MyPanel extends JPanel
{
public MyPanel(){
this.setLayout(new BorderLayout());
JTextArea ta = new JTextArea();
ta.setText("Hello world Hello world Hello world Hello world " + "Hello world Hello world Hello world Hello world "
+ "Hello world Hello world Hello world Hello world " + "Hello world Hello world Hello world Hello world ");
ta.setColumns(20);
ta.setEditable(false);
ta.setLineWrap(true);
ta.setRows(5);
ta.setWrapStyleWord(true);
this.add(ta, BorderLayout.CENTER);
}
}
public static class VerticalLayout implements LayoutManager {
/**
* The horizontal alignment constant that designates centering. Also used to designate center anchoring.
*/
public final static int CENTER = 0;
/**
* The horizontal alignment constant that designates right justification.
*/
public final static int RIGHT = 1;
/**
* The horizontal alignment constant that designates left justification.
*/
public final static int LEFT = 2;
/**
* The horizontal alignment constant that designates stretching the component horizontally.
*/
public final static int BOTH = 3;
/**
* The anchoring constant that designates anchoring to the top of the display area
*/
public final static int TOP = 1;
/**
* The anchoring constant that designates anchoring to the bottom of the display area
*/
public final static int BOTTOM = 2;
private int vgap; // the vertical vgap between components...defaults to 5
private int alignment; // LEFT, RIGHT, CENTER or BOTH...how the components are justified
private int anchor; // TOP, BOTTOM or CENTER ...where are the components positioned in an overlarge space
private Hashtable comps;
// Constructors
/**
* Constructs an instance of VerticalLayout with a vertical vgap of 5 pixels, horizontal centering and anchored to the top of the
* display area.
*/
public VerticalLayout() {
this(5, CENTER, TOP);
}
/**
* Constructs a VerticalLayout instance with horizontal centering, anchored to the top with the specified vgap
*
* #param vgap
* An int value indicating the vertical seperation of the components
*/
public VerticalLayout(int vgap) {
this(vgap, CENTER, TOP);
}
/**
* Constructs a VerticalLayout instance anchored to the top with the specified vgap and horizontal alignment
*
* #param vgap
* An int value indicating the vertical seperation of the components
* #param alignment
* An int value which is one of <code>RIGHT, LEFT, CENTER, BOTH</code> for the horizontal alignment.
*/
public VerticalLayout(int vgap, int alignment) {
this(vgap, alignment, TOP);
}
/**
* Constructs a VerticalLayout instance with the specified vgap, horizontal alignment and anchoring
*
* #param vgap
* An int value indicating the vertical seperation of the components
* #param alignment
* An int value which is one of <code>RIGHT, LEFT, CENTER, BOTH</code> for the horizontal alignment.
* #param anchor
* An int value which is one of <code>TOP, BOTTOM, CENTER</code> indicating where the components are to appear if the
* display area exceeds the minimum necessary.
*/
public VerticalLayout(int vgap, int alignment, int anchor) {
this.vgap = vgap;
this.alignment = alignment;
this.anchor = anchor;
}
// ----------------------------------------------------------------------------
private Dimension layoutSize(Container parent, boolean minimum) {
Dimension dim = new Dimension(0, 0);
Dimension d;
synchronized (parent.getTreeLock()) {
int n = parent.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = parent.getComponent(i);
if (c.isVisible()) {
d = minimum ? c.getMinimumSize() : c.getPreferredSize();
dim.width = Math.max(dim.width, d.width);
dim.height += d.height;
if (i > 0) {
dim.height += vgap;
}
}
}
}
Insets insets = parent.getInsets();
dim.width += insets.left + insets.right;
dim.height += insets.top + insets.bottom + vgap + vgap;
return dim;
}
// -----------------------------------------------------------------------------
/**
* Lays out the container.
*/
#Override
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
synchronized (parent.getTreeLock()) {
int n = parent.getComponentCount();
Dimension pd = parent.getSize();
int y = 0;
// work out the total size
for (int i = 0; i < n; i++) {
Component c = parent.getComponent(i);
Dimension d = c.getPreferredSize();
y += d.height + vgap;
}
y -= vgap; // otherwise there's a vgap too many
// Work out the anchor paint
if (anchor == TOP) {
y = insets.top;
} else if (anchor == CENTER) {
y = (pd.height - y) / 2;
} else {
y = pd.height - y - insets.bottom;
}
// do layout
for (int i = 0; i < n; i++) {
Component c = parent.getComponent(i);
Dimension d = c.getPreferredSize();
int x = insets.left;
int wid = d.width;
if (alignment == CENTER) {
x = (pd.width - d.width) / 2;
} else if (alignment == RIGHT) {
x = pd.width - d.width - insets.right;
} else if (alignment == BOTH) {
wid = pd.width - insets.left - insets.right;
}
c.setBounds(x, y, wid, d.height);
y += d.height + vgap;
}
}
}
// -----------------------------------------------------------------------------
#Override
public Dimension minimumLayoutSize(Container parent) {
return layoutSize(parent, false);
}
// -----------------------------------------------------------------------------
#Override
public Dimension preferredLayoutSize(Container parent) {
return layoutSize(parent, false);
}
// ----------------------------------------------------------------------------
/**
* Not used by this class
*/
#Override
public void addLayoutComponent(String name, Component comp) {
}
// -----------------------------------------------------------------------------
/**
* Not used by this class
*/
#Override
public void removeLayoutComponent(Component comp) {
}
// -----------------------------------------------------------------------------
#Override
public String toString() {
return getClass().getName() + "[vgap=" + vgap + " align=" + alignment + " anchor=" + anchor + "]";
}
}
}
I am not pretty sure how to make JTextArea come to its original width? Or maybe there is some more optimal way?
It seems like you are having trouble posting an SSCCE, so maybe you can get started from this one, possibly change it, and then edit your question showing us the problem you are having.
EDIT: (Changed SSCCE according to updated question)
The problem is that the ViewPortView of the scrollpane does not implements Scrollable. Try the code below with the dedicated class ScrollablePanel which is added as the ViewPortView.
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.util.Hashtable;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class TestTextArea {
private void initUI() {
JFrame frame = new JFrame("test");
class ScrollablePanel extends JPanel implements Scrollable {
/**
* Returns the preferred size of the viewport for a view component. This is implemented to do the default behavior of returning
* the preferred size of the component.
*
* #return the <code>preferredSize</code> of a <code>JViewport</code> whose view is this <code>Scrollable</code>
*/
#Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
/**
* Components that display logical rows or columns should compute the scroll increment that will completely expose one new row
* or column, depending on the value of orientation. Ideally, components should handle a partially exposed row or column by
* returning the distance required to completely expose the item.
* <p>
* The default implementation of this is to simply return 10% of the visible area. Subclasses are likely to be able to provide a
* much more reasonable value.
*
* #param visibleRect
* the view area visible within the viewport
* #param orientation
* either <code>SwingConstants.VERTICAL</code> or <code>SwingConstants.HORIZONTAL</code>
* #param direction
* less than zero to scroll up/left, greater than zero for down/right
* #return the "unit" increment for scrolling in the specified direction
* #exception IllegalArgumentException
* for an invalid orientation
* #see JScrollBar#setUnitIncrement
*/
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
switch (orientation) {
case SwingConstants.VERTICAL:
return visibleRect.height / 10;
case SwingConstants.HORIZONTAL:
return visibleRect.width / 10;
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
/**
* Components that display logical rows or columns should compute the scroll increment that will completely expose one block of
* rows or columns, depending on the value of orientation.
* <p>
* The default implementation of this is to simply return the visible area. Subclasses will likely be able to provide a much
* more reasonable value.
*
* #param visibleRect
* the view area visible within the viewport
* #param orientation
* either <code>SwingConstants.VERTICAL</code> or <code>SwingConstants.HORIZONTAL</code>
* #param direction
* less than zero to scroll up/left, greater than zero for down/right
* #return the "block" increment for scrolling in the specified direction
* #exception IllegalArgumentException
* for an invalid orientation
* #see JScrollBar#setBlockIncrement
*/
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
switch (orientation) {
case SwingConstants.VERTICAL:
return visibleRect.height;
case SwingConstants.HORIZONTAL:
return visibleRect.width;
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
/**
* Returns true if a viewport should always force the width of this <code>Scrollable</code> to match the width of the viewport.
* For example a normal text view that supported line wrapping would return true here, since it would be undesirable for wrapped
* lines to disappear beyond the right edge of the viewport. Note that returning true for a <code>Scrollable</code> whose
* ancestor is a <code>JScrollPane</code> effectively disables horizontal scrolling.
* <p>
* Scrolling containers, like <code>JViewport</code>, will use this method each time they are validated.
*
* #return true if a viewport should force the <code>Scrollable</code>s width to match its own
*/
#Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
/**
* Returns true if a viewport should always force the height of this <code>Scrollable</code> to match the height of the
* viewport. For example a columnar text view that flowed text in left to right columns could effectively disable vertical
* scrolling by returning true here.
* <p>
* Scrolling containers, like <code>JViewport</code>, will use this method each time they are validated.
*
* #return true if a viewport should force the Scrollables height to match its own
*/
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
JPanel listPanel = new ScrollablePanel();
listPanel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH));
JPanel mainPanel = new JPanel(new GridLayout());
JScrollPane sp = new JScrollPane();
sp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
sp.setViewportView(listPanel);
mainPanel.add(sp);
listPanel.add(new MyPanel());
listPanel.add(new MyPanel());
listPanel.add(new MyPanel());
listPanel.add(new MyPanel());
listPanel.add(new MyPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestTextArea().initUI();
}
});
}
class MyPanel extends JPanel {
public MyPanel() {
this.setLayout(new BorderLayout());
JTextArea ta = new JTextArea();
ta.setText("Hello world Hello world Hello world Hello world " + "Hello world Hello world Hello world Hello world "
+ "Hello world Hello world Hello world Hello world " + "Hello world Hello world Hello world Hello world ");
ta.setColumns(20);
ta.setEditable(false);
ta.setLineWrap(true);
ta.setRows(5);
ta.setWrapStyleWord(true);
this.add(ta, BorderLayout.CENTER);
}
}
public static class VerticalLayout implements LayoutManager {
/**
* The horizontal alignment constant that designates centering. Also used to designate center anchoring.
*/
public final static int CENTER = 0;
/**
* The horizontal alignment constant that designates right justification.
*/
public final static int RIGHT = 1;
/**
* The horizontal alignment constant that designates left justification.
*/
public final static int LEFT = 2;
/**
* The horizontal alignment constant that designates stretching the component horizontally.
*/
public final static int BOTH = 3;
/**
* The anchoring constant that designates anchoring to the top of the display area
*/
public final static int TOP = 1;
/**
* The anchoring constant that designates anchoring to the bottom of the display area
*/
public final static int BOTTOM = 2;
private int vgap; // the vertical vgap between components...defaults to 5
private int alignment; // LEFT, RIGHT, CENTER or BOTH...how the components are justified
private int anchor; // TOP, BOTTOM or CENTER ...where are the components positioned in an overlarge space
private Hashtable comps;
// Constructors
/**
* Constructs an instance of VerticalLayout with a vertical vgap of 5 pixels, horizontal centering and anchored to the top of the
* display area.
*/
public VerticalLayout() {
this(5, CENTER, TOP);
}
/**
* Constructs a VerticalLayout instance with horizontal centering, anchored to the top with the specified vgap
*
* #param vgap
* An int value indicating the vertical seperation of the components
*/
public VerticalLayout(int vgap) {
this(vgap, CENTER, TOP);
}
/**
* Constructs a VerticalLayout instance anchored to the top with the specified vgap and horizontal alignment
*
* #param vgap
* An int value indicating the vertical seperation of the components
* #param alignment
* An int value which is one of <code>RIGHT, LEFT, CENTER, BOTH</code> for the horizontal alignment.
*/
public VerticalLayout(int vgap, int alignment) {
this(vgap, alignment, TOP);
}
/**
* Constructs a VerticalLayout instance with the specified vgap, horizontal alignment and anchoring
*
* #param vgap
* An int value indicating the vertical seperation of the components
* #param alignment
* An int value which is one of <code>RIGHT, LEFT, CENTER, BOTH</code> for the horizontal alignment.
* #param anchor
* An int value which is one of <code>TOP, BOTTOM, CENTER</code> indicating where the components are to appear if the
* display area exceeds the minimum necessary.
*/
public VerticalLayout(int vgap, int alignment, int anchor) {
this.vgap = vgap;
this.alignment = alignment;
this.anchor = anchor;
}
// ----------------------------------------------------------------------------
private Dimension layoutSize(Container parent, boolean minimum) {
Dimension dim = new Dimension(0, 0);
Dimension d;
synchronized (parent.getTreeLock()) {
int n = parent.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = parent.getComponent(i);
if (c.isVisible()) {
d = minimum ? c.getMinimumSize() : c.getPreferredSize();
dim.width = Math.max(dim.width, d.width);
dim.height += d.height;
if (i > 0) {
dim.height += vgap;
}
}
}
}
Insets insets = parent.getInsets();
dim.width += insets.left + insets.right;
dim.height += insets.top + insets.bottom + vgap + vgap;
return dim;
}
// -----------------------------------------------------------------------------
/**
* Lays out the container.
*/
#Override
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
synchronized (parent.getTreeLock()) {
int n = parent.getComponentCount();
Dimension pd = parent.getSize();
int y = 0;
// work out the total size
for (int i = 0; i < n; i++) {
Component c = parent.getComponent(i);
Dimension d = c.getPreferredSize();
y += d.height + vgap;
}
y -= vgap; // otherwise there's a vgap too many
// Work out the anchor paint
if (anchor == TOP) {
y = insets.top;
} else if (anchor == CENTER) {
y = (pd.height - y) / 2;
} else {
y = pd.height - y - insets.bottom;
}
// do layout
for (int i = 0; i < n; i++) {
Component c = parent.getComponent(i);
Dimension d = c.getPreferredSize();
int x = insets.left;
int wid = d.width;
if (alignment == CENTER) {
x = (pd.width - d.width) / 2;
} else if (alignment == RIGHT) {
x = pd.width - d.width - insets.right;
} else if (alignment == BOTH) {
wid = pd.width - insets.left - insets.right;
}
c.setBounds(x, y, wid, d.height);
y += d.height + vgap;
}
}
}
// -----------------------------------------------------------------------------
#Override
public Dimension minimumLayoutSize(Container parent) {
return layoutSize(parent, false);
}
// -----------------------------------------------------------------------------
#Override
public Dimension preferredLayoutSize(Container parent) {
return layoutSize(parent, false);
}
// ----------------------------------------------------------------------------
/**
* Not used by this class
*/
#Override
public void addLayoutComponent(String name, Component comp) {
}
// -----------------------------------------------------------------------------
/**
* Not used by this class
*/
#Override
public void removeLayoutComponent(Component comp) {
}
// -----------------------------------------------------------------------------
#Override
public String toString() {
return getClass().getName() + "[vgap=" + vgap + " align=" + alignment + " anchor=" + anchor + "]";
}
}
}
I don't know any reason why not use JScrollPane, LineWrap and WordWrap works correctly in the JScrollPane, then for JPanel will be better to use another LayoutManager
put JTextArea to the JScrollPane
JPanel required (I'm quite to ignore used VerticalLayout) to change default LayoutManager (FlowLayout) to the BorderLayout
put JScrollPane(myTextArea) to the BorderLayout.CENTER
In our application that has a JTabbedPane with unlimited tabs, when the width of the tabs exceeds the tabbed pane's width, the tabs start wrapping into several rows. When you then click on a tab in one of the upper rows, the complete row comes down and to the foreground. For users who click around between several tabs, it's highly confusing as it's not possible to keep track of the tab order.
How can I either
- nail the tabs to fixed positions while bringing their contents to the front (though this would optically corrupt the tab metaphor but I don't care), or
- limit the number of rows to one (so the tabs get very narrow instead of wrapping)?
After some hours of research I have finally found a clean solution.
First, figure out which UI class you are using. Put the following code after you initialize the L&F with UIManager.setLookAndFeel():
for (Map.Entry<Object, Object> entry : UIManager.getDefaults().entrySet()) {
boolean isStringKey = entry.getKey().getClass() == String.class ;
String key = isStringKey ? ((String) entry.getKey()):"";
if (key.equals("TabbedPaneUI")) {
System.out.println(entry.getValue());
}
}
In my case, it prints com.sun.java.swing.plaf.windows.WindowsTabbedPaneUI. If you are using a different L&F, this may be another class (or even if you are using another OS, if you take the OS default).
Next, just instantiate that class (which extends BasicTabbedPaneUI), and override the problematic method:
WindowsTabbedPaneUI jtpui = new WindowsTabbedPaneUI() {
#Override protected boolean shouldRotateTabRuns(int i) {
return false;
}
};
If eclipse does not recognize the class, and gives you an "access restriction" error if you type the full name of the class, see this question: Access restriction: The type 'Application' is not API (restriction on required library rt.jar)
Finally, just set that UI for your JTabbedPane:
JTabbedPane jtp = new JTabbedPane();
jtp.setUI(jtpui);
However, there is a problem: some L&F don't take into account the non-scrolling of tabs rows, and it turns out ugly.
To fix this (I only tested on the Windows L&F), add the following immediately after initializing the L&F:
UIManager.getDefaults().put("TabbedPane.tabRunOverlay", 0);
Really quick and dirty (definitely needs improvements and changes), but I would imagine that something like that could work for you (but is not a JTabbePane):
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class Test {
/**
* FlowLayout subclass that fully supports wrapping of components.
*/
public static class WrapLayout extends FlowLayout {
private Dimension preferredLayoutSize;
/**
* Constructs a new <code>WrapLayout</code> with a left alignment and a
* default 5-unit horizontal and vertical gap.
*/
public WrapLayout() {
super();
}
/**
* Constructs a new <code>FlowLayout</code> with the specified alignment
* and a default 5-unit horizontal and vertical gap. The value of the
* alignment argument must be one of <code>WrapLayout</code>,
* <code>WrapLayout</code>, or <code>WrapLayout</code>.
*
* #param align
* the alignment value
*/
public WrapLayout(int align) {
super(align);
}
/**
* Creates a new flow layout manager with the indicated alignment and
* the indicated horizontal and vertical gaps.
* <p>
* The value of the alignment argument must be one of
* <code>WrapLayout</code>, <code>WrapLayout</code>, or
* <code>WrapLayout</code>.
*
* #param align
* the alignment value
* #param hgap
* the horizontal gap between components
* #param vgap
* the vertical gap between components
*/
public WrapLayout(int align, int hgap, int vgap) {
super(align, hgap, vgap);
}
/**
* Returns the preferred dimensions for this layout given the
* <i>visible</i> components in the specified target container.
*
* #param target
* the component which needs to be laid out
* #return the preferred dimensions to lay out the subcomponents of the
* specified container
*/
#Override
public Dimension preferredLayoutSize(Container target) {
return layoutSize(target, true);
}
/**
* Returns the minimum dimensions needed to layout the <i>visible</i>
* components contained in the specified target container.
*
* #param target
* the component which needs to be laid out
* #return the minimum dimensions to lay out the subcomponents of the
* specified container
*/
#Override
public Dimension minimumLayoutSize(Container target) {
Dimension minimum = layoutSize(target, false);
minimum.width -= getHgap() + 1;
return minimum;
}
/**
* Returns the minimum or preferred dimension needed to layout the
* target container.
*
* #param target
* target to get layout size for
* #param preferred
* should preferred size be calculated
* #return the dimension to layout the target container
*/
private Dimension layoutSize(Container target, boolean preferred) {
synchronized (target.getTreeLock()) {
// Each row must fit with the width allocated to the containter.
// When the container width = 0, the preferred width of the
// container
// has not yet been calculated so lets ask for the maximum.
int targetWidth = target.getSize().width;
if (targetWidth == 0) {
targetWidth = Integer.MAX_VALUE;
}
int hgap = getHgap();
int vgap = getVgap();
Insets insets = target.getInsets();
int horizontalInsetsAndGap = insets.left + insets.right + hgap * 2;
int maxWidth = targetWidth - horizontalInsetsAndGap;
// Fit components into the allowed width
Dimension dim = new Dimension(0, 0);
int rowWidth = 0;
int rowHeight = 0;
int nmembers = target.getComponentCount();
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
// Can't add the component to current row. Start a new
// row.
if (rowWidth + d.width > maxWidth) {
addRow(dim, rowWidth, rowHeight);
rowWidth = 0;
rowHeight = 0;
}
// Add a horizontal gap for all components after the
// first
if (rowWidth != 0) {
rowWidth += hgap;
}
rowWidth += d.width;
rowHeight = Math.max(rowHeight, d.height);
}
}
addRow(dim, rowWidth, rowHeight);
dim.width += horizontalInsetsAndGap;
dim.height += insets.top + insets.bottom + vgap * 2;
// When using a scroll pane or the DecoratedLookAndFeel we need
// to
// make sure the preferred size is less than the size of the
// target containter so shrinking the container size works
// correctly. Removing the horizontal gap is an easy way to do
// this.
Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
if (scrollPane != null) {
dim.width -= hgap + 1;
}
return dim;
}
}
/*
* A new row has been completed. Use the dimensions of this row
* to update the preferred size for the container.
*
* #param dim update the width and height when appropriate
* #param rowWidth the width of the row to add
* #param rowHeight the height of the row to add
*/
private void addRow(Dimension dim, int rowWidth, int rowHeight) {
dim.width = Math.max(dim.width, rowWidth);
if (dim.height > 0) {
dim.height += getVgap();
}
dim.height += rowHeight;
}
}
public static class MyTabbedPane extends JPanel {
private JPanel buttonPanel;
private JPanel currentview;
private Tab currentTab;
private class Tab {
String name;
JComponent component;
}
private List<Tab> tabs = new ArrayList<Tab>();
public MyTabbedPane() {
super(new BorderLayout());
buttonPanel = new JPanel(new WrapLayout());
currentview = new JPanel();
add(buttonPanel, BorderLayout.NORTH);
add(currentview);
}
public void addTab(String name, JComponent tabView, int index) {
if (index < 0 || index > tabs.size()) {
throw new IllegalArgumentException("Index out of bounds");
}
final Tab tab = new Tab();
tab.component = tabView;
tab.name = name;
tabs.add(index, tab);
JButton b = new JButton(name);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setCurrentTab(tab);
}
});
buttonPanel.add(b, index);
buttonPanel.validate();
}
public void removeTab(int i) {
Tab tab = tabs.remove(i);
if (tab == currentTab) {
if (tabs.size() > 0) {
if (i < tabs.size()) {
setCurrentTab(tabs.get(i));
} else {
setCurrentTab(tabs.get(i - 1));
}
} else {
setCurrentTab(null);
}
}
buttonPanel.remove(index);
}
void setCurrentTab(final Tab tab) {
if (currentTab == tab) {
return;
}
if (currentTab != null) {
currentview.remove(currentTab.component);
}
if (tab != null) {
currentview.add(tab.component);
}
currentTab = tab;
currentview.validate();
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
MyTabbedPane tabbedPane = new MyTabbedPane();
for (int i = 0; i < 100; i++) {
tabbedPane.addTab("Button " + (i + 1), new JLabel("Dummy Label " + (i + 1)), i);
}
frame.add(tabbedPane);
frame.pack();
frame.setSize(new Dimension(1000, 800));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
The WrapLayout was taken from another post on SO.