diff --git a/designer-base/src/main/java/com/fr/design/actions/file/PreferencePane.java b/designer-base/src/main/java/com/fr/design/actions/file/PreferencePane.java index 8ac946f423..093d3079f4 100644 --- a/designer-base/src/main/java/com/fr/design/actions/file/PreferencePane.java +++ b/designer-base/src/main/java/com/fr/design/actions/file/PreferencePane.java @@ -14,6 +14,7 @@ import com.fr.design.dialog.DialogActionAdapter; import com.fr.design.dialog.DialogActionListener; import com.fr.design.editor.editor.IntegerEditor; import com.fr.design.file.SaveSomeTemplatePane; +import com.fr.design.gui.frpane.FineTabbedPane; import com.fr.design.gui.frpane.UITabbedPane; import com.fr.design.gui.ibutton.UIButton; import com.fr.design.gui.ibutton.UIColorButton; @@ -53,6 +54,7 @@ import com.fr.io.attr.ImageExportAttr; import com.fr.locale.InterProviderFactory; import com.fr.log.FineLoggerFactory; import com.fr.report.ReportConfigManager; +import com.fr.stable.ArrayUtils; import com.fr.stable.Constants; import com.fr.stable.os.OperatingSystem; import com.fr.third.apache.logging.log4j.Level; @@ -284,8 +286,6 @@ public class PreferencePane extends BasicPane { protected void initComponents() { JPanel contentPane = this; contentPane.setLayout(FRGUIPaneFactory.createBorderLayout()); - UITabbedPane tabPane = new UITabbedPane(); - contentPane.add(tabPane, BorderLayout.CENTER); // 常用面板 JPanel generalPane = column(SETTING_V_GAP, // 功能设置 @@ -300,7 +300,6 @@ public class PreferencePane extends BasicPane { cell(createStartupPagePane()) ).weight(1).getComponent(); UIScrollPane generalScrollPane = patchScroll(generalPane); - tabPane.addTab(i18nText("Fine-Design_Basic_General"), generalScrollPane); // 高级面板 JPanel advancePane = column(SETTING_V_GAP, @@ -326,10 +325,9 @@ public class PreferencePane extends BasicPane { cell(createTplPreviewPane()), // 设计器启动选项 cell(createDesignerStartupPane()) - ).getComponent(); + ).weight(1).getComponent(); useUniverseDBMCheckbox = new UICheckBox(i18nText("Fine-Design_Basic_Use_Universe_Database_Manager")); UIScrollPane adviceScrollPane = patchScroll(advancePane); - tabPane.addTab(i18nText("Fine-Design_Basic_Advanced"), adviceScrollPane); // 版本管理面板 //初始化vcs总面板 @@ -344,7 +342,14 @@ public class PreferencePane extends BasicPane { UIScrollPane vcsScrollPane = patchScroll(vcsPane); //配置面板作为vcs总面板的一张卡片 vcsParentPane.add(vcsScrollPane, VcsMovePanel.SETTING); - tabPane.addTab(i18nText("Fine-Design_Vcs_Title"), vcsParentPane); + + FineTabbedPane tabbedPane = FineTabbedPane.builder() + .addTab(i18nText("Fine-Design_Basic_General"), generalScrollPane) + .addTab(i18nText("Fine-Design_Basic_Advanced"), adviceScrollPane) + .addTab(i18nText("Fine-Design_Vcs_Title"), vcsParentPane) + .build(); + contentPane.add(tabbedPane, BorderLayout.CENTER); + } private Component createDesignerStartupPane() { @@ -669,13 +674,13 @@ public class PreferencePane extends BasicPane { } }); JPanel editPanel = column(10, - cell(supportStringToFormulaBox), - row( - cell(defaultStringToFormulaBox), - fix(10), - cell(shortCutInfoLabel), - cell(shortCutLabel) - ) + cell(supportStringToFormulaBox), + row( + cell(defaultStringToFormulaBox), + fix(10), + cell(shortCutInfoLabel), + cell(shortCutLabel) + ) ).getComponent(); return FineUIUtils.wrapComponentWithTitle(editPanel, i18nText("Fine-Design_Basic_Editor_Preference")); } @@ -809,7 +814,7 @@ public class PreferencePane extends BasicPane { languageComboBox.setFont(FRFont.getInstance("Dialog", Font.PLAIN, 12));//为了在中文系统中显示韩文 return languageComboBox; } - + private Component createStartupPagePane() { startupPageEnabledCheckBox = new UICheckBox(Toolkit.i18nText("Fine-Design_Startup_Page_Config_Check_Text")); UILabel descLabel = FRWidgetFactory.createLineWrapLabel(Toolkit.i18nText("Fine-Design_Startup_Page_Config_Desc"), PREFERENCE_LABEL_MAX_WIDTH); diff --git a/designer-base/src/main/java/com/fr/design/gui/frpane/FineTabbedPane.java b/designer-base/src/main/java/com/fr/design/gui/frpane/FineTabbedPane.java new file mode 100644 index 0000000000..54b01f2d48 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/gui/frpane/FineTabbedPane.java @@ -0,0 +1,166 @@ +package com.fr.design.gui.frpane; + +import com.fine.swing.ui.layout.Column; +import com.fine.theme.utils.FineUIStyle; +import com.formdev.flatlaf.util.ScaledEmptyBorder; +import com.fr.design.gui.ibutton.UIButtonGroup; + +import javax.swing.JPanel; +import javax.swing.event.ChangeListener; +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Component; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import static com.fine.swing.ui.layout.Layouts.cell; +import static com.fine.swing.ui.layout.Layouts.fix; +import static com.fine.swing.ui.layout.Layouts.flex; +import static com.fine.swing.ui.layout.Layouts.row; + +/** + * Tab面板组件 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/05/14 + */ +public class FineTabbedPane extends Column { + + private CardLayout cards; + private JPanel centerPane; + private final float headRatio; + private final UIButtonGroup tabGroup; + private final Map tabComponents; + + private FineTabbedPane(Map tabComponents, float headRatio, int[] tabLayout) { + this.headRatio = headRatio; + this.tabComponents = tabComponents; + + String[] titleArray = tabComponents.keySet().toArray(new String[0]); + this.tabGroup = new UIButtonGroup<>(titleArray, titleArray, tabLayout); + + initLayout(); + initListeners(); + } + + /** + * 建造者模式创建TabPane + * + * @return TabPaneBuilder + */ + public static TabPaneBuilder builder() { + return new TabPaneBuilder(); + } + + /** + * FineTabbedPane建造者 + */ + public static class TabPaneBuilder { + private int[] tabLayout; + private float headRatio = 0.5f; + private final Map tabComponents = new LinkedHashMap<>(); + + /** + * 设置头部居中比例,0-1之间 + * + * @param headRatio 头部居中比例 + * @return TabPaneBuilder + */ + public TabPaneBuilder withHeadRatio(float headRatio) { + this.headRatio = headRatio; + return this; + } + + /** + * 设置Tab布局,形如[3,4]即为首行3个组件,次行4个组件 + * + * @param tabLayout Tab头部布局 + * @return TabPaneBuilder + */ + public TabPaneBuilder withTabLayout(int[] tabLayout) { + this.tabLayout = tabLayout; + return this; + } + + /** + * 添加tab标签 + * + * @param title tab标签标头 + * @param component 组件 + * @return TabPaneBuilder + */ + public TabPaneBuilder addTab(String title, Component component) { + JPanel wrapperPane = new JPanel(new BorderLayout()); + wrapperPane.add(component, BorderLayout.CENTER); + this.tabComponents.put(title, wrapperPane); + return this; + } + + /** + * 构造 + * + * @return FineTabbedPane + */ + public FineTabbedPane build() { + if (headRatio > 1) { + throw new IllegalArgumentException("illegal headRatio argument!"); + } + if (tabLayout != null && Arrays.stream(tabLayout).sum() != tabComponents.size()) { + throw new IllegalArgumentException("illegal tab layout argument!"); + } + return new FineTabbedPane(tabComponents, headRatio, tabLayout); + } + } + + private void initLayout() { + cards = new CardLayout(); + centerPane = new JPanel(cards); + tabComponents.forEach((key, value) -> centerPane.add(value, key)); + float flexRatio = (1 - headRatio) / 2; + add( + row( + flex(flexRatio), cell(tabGroup).weight(headRatio), flex(flexRatio) + ), + fix(5), + cell(centerPane).with(it -> FineUIStyle.setStyle(it, FineUIStyle.LIGHT_GREY)) + ); + setBorder(new ScaledEmptyBorder(10, 10, 10, 10)); + } + + private void initListeners() { + tabGroup.addChangeListener((e) -> { + cards.show(centerPane, String.valueOf(tabGroup.getSelectedItem())); + }); + tabGroup.setSelectedIndex(0); + cards.show(centerPane, String.valueOf(tabGroup.getSelectedItem())); + } + + /** + * 添加事件监听 + * + * @param l 监听器 + */ + public void addChangeListener(ChangeListener l) { + listenerList.add(ChangeListener.class, l); + } + + /** + * 移除事件监听 + * + * @param l 监听器 + */ + public void removeChangeListener(ChangeListener l) { + listenerList.remove(ChangeListener.class, l); + } + + /** + * 获取当前选中的Tab组件 + * + * @return Tab组件 + */ + public Component getSelectedComponent() { + return tabComponents.get(String.valueOf(tabGroup.getSelectedItem())); + } +} diff --git a/designer-base/src/main/java/com/fr/design/gui/ibutton/UIButtonGroup.java b/designer-base/src/main/java/com/fr/design/gui/ibutton/UIButtonGroup.java index bd31f513e1..1468e9255a 100644 --- a/designer-base/src/main/java/com/fr/design/gui/ibutton/UIButtonGroup.java +++ b/designer-base/src/main/java/com/fr/design/gui/ibutton/UIButtonGroup.java @@ -50,6 +50,7 @@ public class UIButtonGroup extends Column implements GlobalNameObserver, UIOb private GlobalNameListener globalNameListener = null; private String buttonGroupName = StringUtils.EMPTY; private boolean isClick; + private int[] customCols; private UIObserverListener uiObserverListener; private boolean autoFireStateChanged = true; @@ -144,10 +145,11 @@ public class UIButtonGroup extends Column implements GlobalNameObserver, UIOb initLayout(getCols(), inToolbar); } - public UIButtonGroup(String[] textArray, T[] objects) { + public UIButtonGroup(String[] textArray, T[] objects, int[] customCols) { if (!ArrayUtils.isEmpty(objects) && textArray.length == objects.length) { this.objectList = Arrays.asList(objects); } + this.customCols = customCols; labelButtonList = new ArrayList<>(textArray.length); totalButtonSize = textArray.length; for (int i = 0; i < textArray.length; i++) { @@ -180,17 +182,24 @@ public class UIButtonGroup extends Column implements GlobalNameObserver, UIOb initLayout(getCols()); } + public UIButtonGroup(String[] textArray, T[] objects) { + this(textArray, objects, null); + } + @Override public String getUIClassID() { return UI_CLASS_ID; } /** - * 计算按钮组的列布局 + * 计算按钮组的列布局;支持自定义布局 * * @return 列布局,形如[5,3] 即为两行,首行5个组件,次行3个组件 */ protected int[] getCols() { + if (customCols != null) { + return customCols; + } return new int[]{totalButtonSize}; } diff --git a/designer-base/src/test/java/com/fr/design/gui/storybook/components/TabbedPaneStoryBoard.java b/designer-base/src/test/java/com/fr/design/gui/storybook/components/TabbedPaneStoryBoard.java new file mode 100644 index 0000000000..196b77615f --- /dev/null +++ b/designer-base/src/test/java/com/fr/design/gui/storybook/components/TabbedPaneStoryBoard.java @@ -0,0 +1,63 @@ +package com.fr.design.gui.storybook.components; + +import com.formdev.flatlaf.util.ScaledEmptyBorder; +import com.fr.design.gui.frpane.FineTabbedPane; +import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.gui.storybook.StoryBoard; + +import java.awt.Component; + +import static com.fine.swing.ui.layout.Layouts.cell; +import static com.fine.swing.ui.layout.Layouts.column; + +/** + * @author Levy.Xie + * @since 11.0 + * Created on 2024/05/22 + */ +public class TabbedPaneStoryBoard extends StoryBoard { + public TabbedPaneStoryBoard() { + super("切换标题面板"); + add( + cell(createSampleTabPane()), + cell(createSampleTabPaneWithRatio(0.8f)), + cell(createMultiRowTab()) + ); + } + + private FineTabbedPane createSampleTabPane() { + + return FineTabbedPane.builder() + .addTab("测试1", getSampleCell(1)) + .addTab("测试2", getSampleCell(2)) + .build(); + } + + private FineTabbedPane createSampleTabPaneWithRatio(float ratio) { + + return FineTabbedPane.builder() + .addTab("测试1", getSampleCell(1)) + .addTab("测试2", getSampleCell(2)) + .withHeadRatio(ratio) + .build(); + } + + private FineTabbedPane createMultiRowTab() { + + return FineTabbedPane.builder() + .addTab("测试1", getSampleCell(1)) + .addTab("测试2", getSampleCell(2)) + .addTab("测试3", getSampleCell(3)) + .addTab("测试4", getSampleCell(4)) + .addTab("测试5", getSampleCell(5)) + .withTabLayout(new int[]{2,3}) + .build(); + } + + private Component getSampleCell(int seq) { + return column(10, + cell(new UIButton(seq + "-按钮1")), cell(new UIButton(seq + "-按钮2")) + ).with(it -> it.setBorder(new ScaledEmptyBorder(10, 10, 10, 10))).getComponent(); + } + +} diff --git a/designer-realize/src/main/java/com/fr/design/dscolumn/DSColumnPane.java b/designer-realize/src/main/java/com/fr/design/dscolumn/DSColumnPane.java index 3f0add29ba..d3378b8956 100644 --- a/designer-realize/src/main/java/com/fr/design/dscolumn/DSColumnPane.java +++ b/designer-realize/src/main/java/com/fr/design/dscolumn/DSColumnPane.java @@ -2,7 +2,7 @@ package com.fr.design.dscolumn; import com.fr.data.TableDataSource; import com.fr.design.dialog.BasicPane; -import com.fr.design.gui.frpane.UITabbedPane; +import com.fr.design.gui.frpane.FineTabbedPane; import com.fr.design.layout.FRGUIPaneFactory; import com.fr.design.mainframe.ElementCasePane; import com.fr.design.mainframe.theme.utils.DefaultThemedTemplateCellElementCase; @@ -29,7 +29,7 @@ public class DSColumnPane extends BasicPane { public static final Dimension DEFAULT_DIMENSION = new Dimension(700, 600); private TableDataSource tplEC; - private UITabbedPane tabbedPane; + private FineTabbedPane tabbedPane; private DSColumnBasicPane basicPane = null; private DSColumnConditionsPane conditionPane = null; private DSColumnAdvancedPane advancedPane = null; @@ -90,22 +90,23 @@ public class DSColumnPane extends BasicPane { JPanel contentPane = this; contentPane.setLayout(FRGUIPaneFactory.createBorderLayout()); - //peter:中心Panel. - tabbedPane = new UITabbedPane(); - tabbedPane.addChangeListener(appliedWizardTabChangeListener); - - contentPane.add(tabbedPane, BorderLayout.CENTER); - //_denny: 数据列面板 basicPane = new DSColumnBasicPane(setting); basicPane.addPropertyChangeListener("cellElement", myPropertyChangeListener); - tabbedPane.addTab(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Report_Basic"), basicPane); conditionPane = new DSColumnConditionsPane(setting); - tabbedPane.addTab(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Report_Filter"), conditionPane); advancedPane = new DSColumnAdvancedPane(setting); - tabbedPane.addTab(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Report_Advanced"), advancedPane); + + //peter:中心Panel. + tabbedPane = FineTabbedPane.builder() + .addTab(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Report_Basic"), basicPane) + .addTab(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Report_Filter"), conditionPane) + .addTab(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Report_Advanced"), advancedPane) + .build(); + tabbedPane.addChangeListener(appliedWizardTabChangeListener); + + contentPane.add(tabbedPane, BorderLayout.CENTER); this.setPreferredSize(new Dimension(610, 400)); }