帆软报表设计器源代码。
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

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());
}
}