|
|
|
@ -9,6 +9,7 @@ import com.fr.stable.StringUtils;
|
|
|
|
|
|
|
|
|
|
import javax.swing.Icon; |
|
|
|
|
import javax.swing.JTextPane; |
|
|
|
|
import javax.swing.text.MutableAttributeSet; |
|
|
|
|
import javax.swing.text.SimpleAttributeSet; |
|
|
|
|
import javax.swing.text.StyleConstants; |
|
|
|
|
import javax.swing.text.StyledDocument; |
|
|
|
@ -18,7 +19,6 @@ import java.awt.Container;
|
|
|
|
|
import java.awt.Dimension; |
|
|
|
|
import java.awt.Font; |
|
|
|
|
import java.awt.FontMetrics; |
|
|
|
|
import java.awt.Graphics2D; |
|
|
|
|
import java.awt.Insets; |
|
|
|
|
import java.awt.LayoutManager; |
|
|
|
|
import java.awt.Rectangle; |
|
|
|
@ -32,13 +32,21 @@ import java.text.AttributedCharacterIterator;
|
|
|
|
|
import java.text.AttributedString; |
|
|
|
|
|
|
|
|
|
public class BubbleWithClose extends Bubble { |
|
|
|
|
private static final Font FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 14); |
|
|
|
|
private static final Font TITLE_FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 14); |
|
|
|
|
private static final Font CONTENT_FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 12); |
|
|
|
|
private static final int TITLE_LINE_HEIGHT = 22; |
|
|
|
|
private static final int CONTENT_LINE_HEIGHT = 18; |
|
|
|
|
|
|
|
|
|
private static final Icon ICON = IOUtils.readIcon("/com/fr/design/mainframe/guide/close.png"); |
|
|
|
|
private static final int HEADER_HEIGHT = 24; |
|
|
|
|
private static final Color HEADER_COLOR = new Color(245, 245, 246); |
|
|
|
|
private static final Color TITLE_COLOR = new Color(51, 51, 52); |
|
|
|
|
private static final Color CONTENT_COLOR = new Color(51,51,52,128); |
|
|
|
|
private static final Insets DEFAULT_INSET = new Insets(10, 15, 10, 15); |
|
|
|
|
private static final Color HIGHLIGHT_COLOR = new Color(65, 155, 249); |
|
|
|
|
private static final Insets DEFAULT_INSET = new Insets(15, 15, 15, 15); |
|
|
|
|
private static final int MIN_WIDTH = 140; |
|
|
|
|
private static final int MAX_WIDTH = 275; |
|
|
|
|
private static final int ICON_GAP = 10; |
|
|
|
|
private static final int ICON_SIZE = 10; |
|
|
|
|
private static final int TEXT_GAP = 5; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String title; |
|
|
|
@ -46,6 +54,8 @@ public class BubbleWithClose extends Bubble {
|
|
|
|
|
private UITextPane titleTextArea; |
|
|
|
|
private UITextPane contentTextArea; |
|
|
|
|
private UIButton closeButton; |
|
|
|
|
private Dimension titleSize; |
|
|
|
|
private Dimension contentSize; |
|
|
|
|
|
|
|
|
|
public BubbleWithClose(String title, String content) { |
|
|
|
|
this(title, content, TailDirection.LEFT, 0.5f); |
|
|
|
@ -87,23 +97,17 @@ public class BubbleWithClose extends Bubble {
|
|
|
|
|
@Override |
|
|
|
|
public void componentResized(ComponentEvent e) { |
|
|
|
|
Rectangle rectBounds = getRectBounds(); |
|
|
|
|
Dimension buttonSize = closeButton.getPreferredSize(); |
|
|
|
|
|
|
|
|
|
closeButton.setBounds(getWidth() - 10 - buttonSize.width, rectBounds.y + (HEADER_HEIGHT - buttonSize.height) / 2, buttonSize.width, buttonSize.height); |
|
|
|
|
|
|
|
|
|
Dimension titleSize = new Dimension(0,0); |
|
|
|
|
Dimension contentSize = new Dimension(0, 0); |
|
|
|
|
closeButton.setBounds(rectBounds.x + rectBounds.width - DEFAULT_INSET.right - ICON_SIZE, rectBounds.y + 21, ICON_SIZE, ICON_SIZE); |
|
|
|
|
if (titleTextArea != null) { |
|
|
|
|
titleSize = calTextSize(titleTextArea, getTextAreaMaxWidth()); |
|
|
|
|
int x = rectBounds.x + (rectBounds.width - getHorizontalInsets() - titleSize.width) / 2 + DEFAULT_INSET.left; |
|
|
|
|
int y = rectBounds.y + HEADER_HEIGHT + DEFAULT_INSET.top; |
|
|
|
|
int x = rectBounds.x + DEFAULT_INSET.left; |
|
|
|
|
int y = rectBounds.y + DEFAULT_INSET.top; |
|
|
|
|
titleTextArea.setBounds(x, y, titleSize.width, titleSize.height); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (contentTextArea != null) { |
|
|
|
|
contentSize = calTextSize(contentTextArea, getTextAreaMaxWidth()); |
|
|
|
|
int x = rectBounds.x + (rectBounds.width - getHorizontalInsets() - contentSize.width) / 2 + DEFAULT_INSET.left; |
|
|
|
|
int y = rectBounds.y = HEADER_HEIGHT + DEFAULT_INSET.top + (titleSize.height == 0 ? 0 : titleSize.height + 10); |
|
|
|
|
int x = rectBounds.x + DEFAULT_INSET.left; |
|
|
|
|
int y = rectBounds.y + DEFAULT_INSET.top + (titleSize.height == 0 ? 0 : titleSize.height + getTextGap()); |
|
|
|
|
contentTextArea.setBounds(x,y, contentSize.width, contentSize.height); |
|
|
|
|
} |
|
|
|
|
setSize(getPreferredSize().width, getPreferredSize().height); |
|
|
|
@ -116,7 +120,7 @@ public class BubbleWithClose extends Bubble {
|
|
|
|
|
closeButton = new UIButton(); |
|
|
|
|
closeButton.setIcon(ICON); |
|
|
|
|
closeButton.set4ToolbarButton(); |
|
|
|
|
closeButton.setPreferredSize(new Dimension(12, 12)); |
|
|
|
|
closeButton.setPreferredSize(new Dimension(ICON_SIZE, ICON_SIZE)); |
|
|
|
|
closeButton.setRolloverEnabled(false); |
|
|
|
|
closeButton.setPressedPainted(false); |
|
|
|
|
this.add(closeButton); |
|
|
|
@ -138,44 +142,84 @@ public class BubbleWithClose extends Bubble {
|
|
|
|
|
|
|
|
|
|
private void createTitleTextArea() { |
|
|
|
|
if (StringUtils.isNotEmpty(title)) { |
|
|
|
|
titleTextArea = createTextArea(title, FONT, TITLE_COLOR); |
|
|
|
|
titleTextArea = createTextArea(title, TITLE_FONT, TITLE_LINE_HEIGHT, TITLE_COLOR); |
|
|
|
|
this.add(titleTextArea); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
titleSize = calTextSize(titleTextArea, TITLE_LINE_HEIGHT, getTextAreaWidth(MAX_WIDTH), getTextAreaWidth(MIN_WIDTH)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void createContentTextArea() { |
|
|
|
|
if (StringUtils.isNotEmpty(content)) { |
|
|
|
|
contentTextArea = createTextArea(content, FONT, CONTENT_COLOR); |
|
|
|
|
contentTextArea = createTextArea(content, CONTENT_FONT, CONTENT_LINE_HEIGHT, CONTENT_COLOR); |
|
|
|
|
this.add(contentTextArea); |
|
|
|
|
} |
|
|
|
|
contentSize = calTextSize(contentTextArea, CONTENT_LINE_HEIGHT, getTextAreaWidth(MAX_WIDTH), getTextAreaWidth(MIN_WIDTH)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private UITextPane createTextArea(String str, Font font, Color foreground) { |
|
|
|
|
private UITextPane createTextArea(String str, Font font, int lineHeight, Color foreground) { |
|
|
|
|
int lineSpace = lineHeight - font.getSize(); |
|
|
|
|
UITextPane textArea= new UITextPane(){ |
|
|
|
|
@Override |
|
|
|
|
public Insets getInsets() { |
|
|
|
|
return new Insets(0, 0, 0, 0); |
|
|
|
|
return new Insets(lineSpace / 2 - 1, 0, lineSpace / 2, 0); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
textArea.setText(str); |
|
|
|
|
textArea.setEnabled(false); |
|
|
|
|
textArea.setFont(font); |
|
|
|
|
changeLineSpacing(textArea, lineHeight, true); |
|
|
|
|
textArea.setEditable(false); |
|
|
|
|
textArea.setForeground(foreground); |
|
|
|
|
textArea.setDisabledTextColor(foreground); |
|
|
|
|
textArea.setBorder(null); |
|
|
|
|
textArea.setFont(font); |
|
|
|
|
textArea.setOpaque(false); |
|
|
|
|
textArea.setForeground(foreground); |
|
|
|
|
StyledDocument doc = textArea.getStyledDocument(); |
|
|
|
|
SimpleAttributeSet center = new SimpleAttributeSet(); |
|
|
|
|
StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER); |
|
|
|
|
doc.setParagraphAttributes(0, doc.getLength(), center, false); |
|
|
|
|
textArea.setText(str); |
|
|
|
|
highlightText(textArea); |
|
|
|
|
return textArea; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void changeLineSpacing(JTextPane pane, int lineHeight, boolean replace) { |
|
|
|
|
pane.selectAll(); |
|
|
|
|
MutableAttributeSet set = new SimpleAttributeSet(pane.getParagraphAttributes()); |
|
|
|
|
FontMetrics fontMetrics = GraphHelper.getFontMetrics(pane.getFont()); |
|
|
|
|
StyleConstants.setLineSpacing(set, (float)(lineHeight - fontMetrics.getHeight()) / fontMetrics.getHeight()); |
|
|
|
|
pane.setParagraphAttributes(set, replace); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void highlightText(JTextPane textArea) { |
|
|
|
|
SimpleAttributeSet sas = new SimpleAttributeSet(); |
|
|
|
|
StyleConstants.setForeground(sas, HIGHLIGHT_COLOR); |
|
|
|
|
StyledDocument doc = textArea.getStyledDocument(); |
|
|
|
|
String text = textArea.getText(); |
|
|
|
|
int startPos = -1; |
|
|
|
|
for (int i = 0; i < text.length(); i ++) { |
|
|
|
|
char ch = text.charAt(i); |
|
|
|
|
if (ch == '【') { |
|
|
|
|
startPos = i; |
|
|
|
|
} |
|
|
|
|
if (ch == '】') { |
|
|
|
|
if (startPos > 0) { |
|
|
|
|
try { |
|
|
|
|
doc.setCharacterAttributes(startPos, (i - startPos + 1), sas, false); |
|
|
|
|
startPos = -1; |
|
|
|
|
} catch (Exception e) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected int getDefaultWidth() { |
|
|
|
|
return Math.max(titleSize.width, contentSize.width) + getHorizontalInsets() + ICON_GAP + ICON_SIZE + (isTailHorizontal() ? TAIL_HEIGHT : 0) ; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected int getDefaultHeight() { |
|
|
|
|
Dimension titleSize = calTextSize(titleTextArea, getDefaultTextAreaMaxWidth()); |
|
|
|
|
Dimension contentSize = calTextSize(contentTextArea, getDefaultTextAreaMaxWidth()); |
|
|
|
|
return (isTailVertical() ? TAIL_HEIGHT : 0) + HEADER_HEIGHT + titleSize.height + (titleSize.height == 0 ? 0 : 5) + contentSize.height + getVerticalInsets(); |
|
|
|
|
return titleSize.height + contentSize.height + getTextGap() + getVerticalInsets() + (isTailVertical() ? TAIL_HEIGHT : 0); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private int getTextGap() { |
|
|
|
|
return (titleSize.height != 0 && contentSize.height != 0) ? TEXT_GAP : 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private LayoutManager getLayoutManager() { |
|
|
|
@ -206,18 +250,6 @@ public class BubbleWithClose extends Bubble {
|
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected void paintBubbleBg(Graphics2D g2) { |
|
|
|
|
super.paintBubbleBg(g2); |
|
|
|
|
paintHeaderBg(g2); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void paintHeaderBg(Graphics2D g2) { |
|
|
|
|
g2.setColor(HEADER_COLOR); |
|
|
|
|
Rectangle bounds = getRectBounds(); |
|
|
|
|
g2.fillRoundRect(bounds.x, bounds.y, bounds.width, HEADER_HEIGHT, 10, 10); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private int countLines(JTextPane textArea, int max_width) { |
|
|
|
|
AttributedString text = new AttributedString(textArea.getText()); |
|
|
|
|
text.addAttribute(TextAttribute.FONT, textArea.getFont()); |
|
|
|
@ -234,28 +266,26 @@ public class BubbleWithClose extends Bubble {
|
|
|
|
|
return lines; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Dimension calTextSize(JTextPane textArea, int maxWidth) { |
|
|
|
|
private Dimension calTextSize(JTextPane textArea, int lineHeight, int maxWidth, int minWidth) { |
|
|
|
|
if (textArea == null) { |
|
|
|
|
return new Dimension(0, 0); |
|
|
|
|
return new Dimension(minWidth, 0); |
|
|
|
|
} |
|
|
|
|
FontMetrics fontMetrics = GraphHelper.getFontMetrics(textArea.getFont()); |
|
|
|
|
int line = countLines(textArea, maxWidth); |
|
|
|
|
int width = maxWidth; |
|
|
|
|
if (line == 1) { |
|
|
|
|
width = fontMetrics.stringWidth(textArea.getText()); |
|
|
|
|
width = Math.max(fontMetrics.stringWidth(textArea.getText()), minWidth); |
|
|
|
|
} |
|
|
|
|
int height = fontMetrics.getHeight() * line; |
|
|
|
|
int height = lineHeight * line; |
|
|
|
|
return new Dimension(width, height); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private int getTextAreaMaxWidth() { |
|
|
|
|
return getWidth() - (isTailHorizontal() ? TAIL_HEIGHT : 0) -getHorizontalInsets(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private int getDefaultTextAreaMaxWidth() { |
|
|
|
|
return BUBBLE_WIDTH - getHorizontalInsets(); |
|
|
|
|
private int getTextAreaWidth(int bubbleWidth) { |
|
|
|
|
return bubbleWidth - getHorizontalInsets() - ICON_SIZE - ICON_GAP; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private int getHorizontalInsets() { |
|
|
|
|
return DEFAULT_INSET.left + DEFAULT_INSET.right; |
|
|
|
|
} |
|
|
|
|