You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
9.9 KiB
268 lines
9.9 KiB
package com.fr.design.mainframe.guide; |
|
|
|
import com.fr.base.svg.IconUtils; |
|
import com.fr.design.gui.ilable.UILabel; |
|
import com.fr.design.i18n.Toolkit; |
|
import com.fr.design.layout.FRGUIPaneFactory; |
|
import com.fr.design.ui.util.UIUtil; |
|
import com.fr.log.FineLoggerFactory; |
|
import com.fr.stable.StringUtils; |
|
|
|
import javax.swing.BorderFactory; |
|
import javax.swing.Icon; |
|
import javax.swing.JButton; |
|
import javax.swing.JComponent; |
|
import javax.swing.JPanel; |
|
import javax.swing.JTextPane; |
|
import javax.swing.UIManager; |
|
import javax.swing.border.LineBorder; |
|
import javax.swing.event.AncestorEvent; |
|
import javax.swing.event.AncestorListener; |
|
import javax.swing.event.HyperlinkEvent; |
|
import java.awt.BorderLayout; |
|
import java.awt.Color; |
|
import java.awt.Desktop; |
|
import java.awt.Dimension; |
|
import java.awt.Font; |
|
import java.awt.FontMetrics; |
|
import java.awt.Insets; |
|
import java.awt.event.ActionEvent; |
|
import java.awt.event.ComponentAdapter; |
|
import java.awt.event.ComponentEvent; |
|
|
|
/** |
|
* FRM引导提示使用FVS |
|
* |
|
* @author Zhanying |
|
* @since 11.0 |
|
* Created on 2024/12/20 |
|
*/ |
|
public class FvsGuidePane extends JPanel { |
|
private static final String OMIT_TEXT = "..."; |
|
private static final Color BACKGROUND_COLOR = new Color(255, 251, 230); |
|
private static final Color BORDER_COLOR = new Color(255, 229, 143); |
|
private static final Icon TIP_ICON = UIManager.getIcon("OptionPane.circularWarningIcon"); |
|
private static final Icon CLOSE_ICON = IconUtils.readIcon("/com/fr/design/standard/close/close"); |
|
private static final int MAX_PANE_HEIGHT = 80; |
|
private static final int MAX_CONTENT_HEIGHT = 60; |
|
private static final int LINE_HEIGHT = 20; |
|
// 计算文本框最多能容纳的行数 = 最大高度 / 行高,向下取整 |
|
private static final int MAX_LINES = (int) Math.floor((double) MAX_CONTENT_HEIGHT / LINE_HEIGHT); |
|
private static final String CONTENT_FORMAT = "<html>\n<body style=\"font-family: %s; font-size: %spt;\">\n %s\n</body>\n</html>"; |
|
private final JComponent parent; |
|
private JTextPane textPane; |
|
// 是否显示超链跳转 |
|
private final boolean showGuideLink; |
|
|
|
public FvsGuidePane(JComponent parent) { |
|
super(); |
|
this.parent = parent; |
|
showGuideLink = FvsGuideConfig.getInstance().isShowGuideLink(); |
|
initUI(); |
|
} |
|
|
|
private void initUI() { |
|
this.setLayout(FRGUIPaneFactory.createBorderLayout()); |
|
this.setLayout(new BorderLayout()); |
|
this.setBorder(new LineBorder(BORDER_COLOR)); |
|
this.setBackground(BACKGROUND_COLOR); |
|
this.setMaximumSize(new Dimension(Integer.MAX_VALUE, MAX_PANE_HEIGHT)); |
|
JPanel wrapPane = new JPanel(new BorderLayout(10, 0)); |
|
wrapPane.setBorder(BorderFactory.createEmptyBorder(8, 13, 8, 13)); |
|
wrapPane.setBackground(BACKGROUND_COLOR); |
|
wrapPane.add(this.createIconPane(), BorderLayout.WEST); |
|
wrapPane.add(this.createContentPane(), BorderLayout.CENTER); |
|
wrapPane.add(this.createCloseBtnPane(), BorderLayout.EAST); |
|
this.add(wrapPane, BorderLayout.CENTER); |
|
addListener(); |
|
} |
|
|
|
private void addListener() { |
|
this.textPane.addComponentListener(new ComponentAdapter() { |
|
@Override |
|
public void componentResized(ComponentEvent e) { |
|
refreshText(); |
|
} |
|
}); |
|
this.textPane.addAncestorListener(new AncestorListener() { |
|
@Override |
|
public void ancestorAdded(AncestorEvent event) { |
|
UIUtil.invokeLaterIfNeeded(FvsGuidePane.this::refreshText); |
|
} |
|
|
|
@Override |
|
public void ancestorRemoved(AncestorEvent event) { |
|
|
|
} |
|
|
|
@Override |
|
public void ancestorMoved(AncestorEvent event) { |
|
|
|
} |
|
}); |
|
} |
|
|
|
private JPanel createIconPane() { |
|
// 图标 |
|
JPanel panel = new JPanel(new BorderLayout()); |
|
panel.setBorder(BorderFactory.createEmptyBorder(6, 0, 0, 0)); |
|
panel.setBackground(BACKGROUND_COLOR); |
|
UILabel iconLabel = new UILabel(TIP_ICON); |
|
panel.add(iconLabel, BorderLayout.NORTH); |
|
return panel; |
|
} |
|
|
|
private JPanel createCloseBtnPane() { |
|
JPanel panel = new JPanel(new BorderLayout()); |
|
panel.setBackground(BACKGROUND_COLOR); |
|
JButton closeButton = new JButton(this.closeBtnText(), CLOSE_ICON); |
|
closeButton.setOpaque(false); |
|
closeButton.setBorderPainted(false); |
|
closeButton.setContentAreaFilled(false); |
|
closeButton.addActionListener(this::close); |
|
panel.add(closeButton, BorderLayout.NORTH); |
|
return panel; |
|
} |
|
|
|
private JPanel createContentPane() { |
|
JPanel contentPane = new JPanel(new BorderLayout()); |
|
contentPane.setBackground(BACKGROUND_COLOR); |
|
textPane = new JTextPane(); |
|
// 设置文本格式为 HTML |
|
textPane.setContentType("text/html"); |
|
textPane.setEditable(false); |
|
textPane.setBackground(BACKGROUND_COLOR); |
|
|
|
// 添加超链接监听器 |
|
textPane.addHyperlinkListener(e -> { |
|
if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) { |
|
// 用户点击了超链接,处理跳转 |
|
try { |
|
Desktop.getDesktop().browse(e.getURL().toURI()); |
|
} catch (Exception ex) { |
|
FineLoggerFactory.getLogger().error(ex.getMessage(), ex); |
|
} |
|
} |
|
}); |
|
contentPane.add(textPane, BorderLayout.CENTER); |
|
return contentPane; |
|
} |
|
|
|
private void refreshText() { |
|
int textPaneWidth = textPane.getWidth(); |
|
if (textPaneWidth <= 0) { |
|
return; |
|
} |
|
// 获取可用宽度:减去可能的左右边距 |
|
Insets insets = textPane.getInsets(); |
|
int availableWidth = textPaneWidth - insets.left - insets.right - 15;// 留点冗余 |
|
// 组装html |
|
String buildHtml = buildHtml(textPane.getFont(), autoChangeLineAndOmit(availableWidth)); |
|
textPane.setText(buildHtml); |
|
} |
|
|
|
private String tipContent() { |
|
return Toolkit.i18nText("Fine-Design_Form_Guide_Use_Fvs_Tips"); |
|
} |
|
|
|
private String linkContent() { |
|
return Toolkit.i18nText("Fine-Design_Form_Guide_Use_Fvs_Link_Tips"); |
|
} |
|
|
|
private String guideUrl() { |
|
return Toolkit.i18nText("Fine-Design_Form_Guide_Use_Fvs_Link_Url"); |
|
} |
|
|
|
private void close(ActionEvent e) { |
|
parent.remove(this); |
|
parent.revalidate(); |
|
parent.repaint(); |
|
} |
|
|
|
private String closeBtnText() { |
|
return Toolkit.i18nText("Fine-Design_Form_Guide_Use_Fvs_Close_Tips"); |
|
} |
|
|
|
private String buildHtml(Font font, String text) { |
|
return String.format(CONTENT_FORMAT, font.getFamily(), font.getSize(), text); |
|
} |
|
|
|
private String autoChangeLineAndOmit(double width) { |
|
StringBuilder htmlBuilder = new StringBuilder(); |
|
// 获取字体的大小 |
|
FontMetrics fontMetrics = textPane.getFontMetrics(textPane.getFont()); |
|
// 计算行间距 |
|
int lineSpace = LINE_HEIGHT - fontMetrics.getHeight(); |
|
String divStart = String.format("<div style=\"margin-bottom: %spt; margin-top: %spt;\">", lineSpace / 2, lineSpace / 2); |
|
htmlBuilder.append(divStart); |
|
|
|
// 计算省略号的长度 |
|
int omitLength = getStringWidth(OMIT_TEXT, fontMetrics); |
|
|
|
String linkContent = showGuideLink ? linkContent() : StringUtils.EMPTY; |
|
// 计算超链的长度 |
|
int linkLength = showGuideLink ? getStringWidth(linkContent, fontMetrics) : 0; |
|
|
|
String tipContent = tipContent(); |
|
|
|
// 宽度已经小于省略号+超链的宽度 |
|
boolean widthTooSmall = width < omitLength + linkLength; |
|
if (StringUtils.isNotEmpty(tipContent)) { |
|
char[] chars = tipContent.toCharArray(); |
|
|
|
int row = 1; |
|
int off = 0; |
|
for (int i = 0; i < chars.length; i++) { |
|
// 当前行占的宽度 |
|
int currRowWidth = fontMetrics.charsWidth(chars, off, i - off); |
|
// 到最后一行 |
|
if (row == MAX_LINES) { |
|
// 计算省略号的位置 |
|
if (currRowWidth + omitLength + linkLength > width) { |
|
//拼上省略号 |
|
htmlBuilder.append(OMIT_TEXT); |
|
break; |
|
} |
|
} |
|
|
|
if (currRowWidth > width) { |
|
// 换行 |
|
htmlBuilder.append("</div>").append(divStart); |
|
off = i; |
|
row++; |
|
} else if (widthTooSmall && simulateIfLastLineOverflows(fontMetrics, currRowWidth, row, (OMIT_TEXT + linkContent).toCharArray(), width)) { |
|
//模拟到最后一行是否溢出;其实这么窄展示也没什么意义了 |
|
//拼上省略号 |
|
htmlBuilder.append(OMIT_TEXT); |
|
break; |
|
} |
|
htmlBuilder.append(chars[i]); |
|
} |
|
} |
|
if (showGuideLink) { |
|
htmlBuilder.append("<a href=\"").append(guideUrl()).append("\">").append(linkContent).append("</a>"); |
|
} |
|
htmlBuilder.append("</div>"); |
|
return htmlBuilder.toString(); |
|
} |
|
|
|
private boolean simulateIfLastLineOverflows(FontMetrics fontMetrics, int currRowWidth, int row, char[] omitAndLink, double width) { |
|
int off = 0; |
|
for (int i = 0; i < omitAndLink.length; i++) { |
|
boolean lastRow = row == MAX_LINES; |
|
if (currRowWidth + fontMetrics.charsWidth(omitAndLink, off, i - off) > (lastRow ? width - 30 : width)) { // 最后一行留点冗余 |
|
if (lastRow) { |
|
return true; |
|
} |
|
off = i; |
|
currRowWidth = 0; |
|
row++; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
private int getStringWidth(String str, FontMetrics fontMetrics) { |
|
return fontMetrics.charsWidth(str.toCharArray(), 0, str.length()); |
|
} |
|
}
|
|
|