From 7818b5a4d4bc0dda4928d9062617dd12c753f2c2 Mon Sep 17 00:00:00 2001 From: Zhanying <2446962908@qq.com> Date: Wed, 25 Dec 2024 17:57:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20FRM=E5=BC=95=E5=AF=BC=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8FVS=20#REPORT-136793?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fr/design/mainframe/FormArea.java | 50 ++++ .../design/mainframe/guide/FvsGuidePane.java | 260 ++++++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 designer-form/src/main/java/com/fr/design/mainframe/guide/FvsGuidePane.java diff --git a/designer-form/src/main/java/com/fr/design/mainframe/FormArea.java b/designer-form/src/main/java/com/fr/design/mainframe/FormArea.java index 94886f9d46..314851b0e9 100644 --- a/designer-form/src/main/java/com/fr/design/mainframe/FormArea.java +++ b/designer-form/src/main/java/com/fr/design/mainframe/FormArea.java @@ -17,6 +17,7 @@ import com.fr.design.gui.itextfield.UINumberField; import com.fr.design.layout.FRGUIPaneFactory; import com.fr.design.layout.TableLayout; import com.fr.design.layout.TableLayoutHelper; +import com.fr.design.mainframe.guide.FvsGuidePane; import com.fr.design.mainframe.share.ui.base.PopupMenuItem; import com.fr.design.scrollruler.BaseRuler; import com.fr.design.scrollruler.HorizontalRuler; @@ -30,6 +31,7 @@ import com.fr.form.main.mobile.FormMobileAttr; import com.fr.form.ui.container.WBodyLayoutType; import com.fr.form.ui.container.WBorderLayout; import com.fr.form.ui.container.WFitLayout; +import com.fr.general.ComparatorUtils; import com.fr.general.FRScreen; import com.fr.general.IOUtils; import com.fr.stable.AssistUtils; @@ -105,6 +107,7 @@ public class FormArea extends JComponent implements ScrollRulerComponent { if (useScrollBar) { this.setLayout(new FormRulerLayout()); designer.setBorder(new LineBorder(new Color(198, 198, 198))); + this.add(FormRulerLayout.FVS_GUIDE, new FvsGuidePane(this)); this.add(FormRulerLayout.CENTER, designer); addFormSize(); this.add(FormRulerLayout.VERTICAL, verScrollBar); @@ -812,6 +815,7 @@ public class FormArea extends JComponent implements ScrollRulerComponent { } private class FormRulerLayout extends RulerLayout { + public static final String FVS_GUIDE = "FvsGuide"; private static final int DESIGNER_WIDTH = 960; private static final int DESIGNER_HEIGHT = 540; private static final int TOPGAP = 8; @@ -819,10 +823,20 @@ public class FormArea extends JComponent implements ScrollRulerComponent { private int DESIGNERWIDTH = DESIGNER_WIDTH; private int DESIGNERHEIGHT = DESIGNER_HEIGHT; + private FvsGuidePane fvsGuidePane; + public FormRulerLayout() { super(); } + @Override + public void addLayoutComponent(String name, Component comp) { + super.addLayoutComponent(name, comp); + if (isValid && ComparatorUtils.equals(name, FVS_GUIDE)) { + fvsGuidePane = (FvsGuidePane) comp; + } + } + /** * 表单用的layout,当前不需要标尺 */ @@ -860,6 +874,12 @@ public class FormArea extends JComponent implements ScrollRulerComponent { DESIGNERWIDTH = right - vbarPreferredSize.width; int designerLeft = left + (verScrollBar.getX() - DESIGNERWIDTH) / 2; int designerTop = top + (horScrollBar.getY() - DESIGNERHEIGHT) / 2; + if (fvsGuidePane != null) { + Dimension fvsGuidePreferredSize = fvsGuidePane.getPreferredSize(); + fvsGuidePane.setBounds(left, top, right - BARSIZE - 2, fvsGuidePreferredSize.height); + DESIGNERHEIGHT -= fvsGuidePreferredSize.height; + designerTop += fvsGuidePreferredSize.height; + } rec = new Rectangle(designerLeft, designerTop, DESIGNERWIDTH, DESIGNERHEIGHT); } // designer是整个表单设计界面中的面板部分,目前只放自适应布局和参数界面。 @@ -867,6 +887,36 @@ public class FormArea extends JComponent implements ScrollRulerComponent { } } + @Override + public void removeLayoutComponent(Component comp) { + super.removeLayoutComponent(comp); + if (isValid && ComparatorUtils.equals(comp, fvsGuidePane)) { + fvsGuidePane = null; + } + } + + @Override + public Dimension preferredLayoutSize(Container target) { + synchronized (target.getTreeLock()) { + Dimension dim = super.preferredLayoutSize(target); + if (isValid && fvsGuidePane != null) { + dim.height += fvsGuidePane.getPreferredSize().height; + } + return dim; + } + } + + @Override + public Dimension minimumLayoutSize(Container target) { + synchronized (target.getTreeLock()) { + Dimension dim = super.minimumLayoutSize(target); + if (isValid && fvsGuidePane != null) { + Dimension dim1 = fvsGuidePane.getMinimumSize(); + dim.height += dim1.height; + } + return dim; + } + } } public boolean isFixLayout(){ diff --git a/designer-form/src/main/java/com/fr/design/mainframe/guide/FvsGuidePane.java b/designer-form/src/main/java/com/fr/design/mainframe/guide/FvsGuidePane.java new file mode 100644 index 0000000000..ee6f0d3dc7 --- /dev/null +++ b/designer-form/src/main/java/com/fr/design/mainframe/guide/FvsGuidePane.java @@ -0,0 +1,260 @@ +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"); + // 引导URL + private static final String GUIDE_URL = "https://help.fanruan.com/finereport/doc-view-4222.html?source=3"; + 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 = "\n
\n %s\n\n"; + private final JComponent parent; + private JTextPane textPane; + + public FvsGuidePane(JComponent parent) { + super(); + this.parent = parent; + 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 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("