package com.fanruan.api.design.work; import com.fanruan.api.design.DesignKit; import com.fanruan.api.design.ui.component.UIButton; import com.fanruan.api.design.ui.component.UIComboBox; import com.fanruan.api.design.ui.component.UIToggleButton; import com.fanruan.api.log.LogKit; import com.fanruan.api.util.GeneralKit; import com.fanruan.api.util.IOKit; import com.fr.base.BaseFormula; import com.fr.design.dialog.BasicPane; import com.fr.design.dialog.DialogActionAdapter; import com.fr.design.formula.FormulaFactory; import com.fr.design.formula.UIFormula; import com.fr.design.gui.style.FRFontPane; import com.fr.design.mainframe.DesignerContext; import com.fr.design.style.color.UIToolbarColorButton; import com.fr.general.FRFont; import com.fr.report.cell.cellattr.core.RichTextConverter; import com.fr.stable.Constants; import javax.swing.*; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Element; import javax.swing.text.MutableAttributeSet; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.math.BigDecimal; /** * @author richie * @version 10.0 * Created by richie on 2019/10/25 * 富文本编辑器工具栏 */ public class RichTextToolBar extends BasicPane { private static final Dimension BUTTON_SIZE = new Dimension(24, 20); private static final FRFont DEFAULT_FONT = FRFont.getInstance().applySize(13); private static final int NOT_INITIALIZED = -1; private static final int UPDATING = -2; private int inputStart = NOT_INITIALIZED; private UIComboBox fontNameComboBox; private UIComboBox fontSizeComboBox; private UIToggleButton bold; private UIToggleButton italic; private UIToggleButton underline; private UIToolbarColorButton colorSelectPane; private UIToggleButton superPane; private UIToggleButton subPane; private UIToggleButton formulaPane; //外部传进来的 private RichTextEditingPane textPane; public RichTextToolBar() { this.initComponents(); } public RichTextToolBar(RichTextEditingPane textPane) { this.textPane = textPane; this.initComponents(); } @Override protected String title4PopupWindow() { return DesignKit.i18nText("Fine-Design_Form_Font"); } protected void initComponents() { //初始化并设置所有按钮样式 initAllButton(); //添加到工具栏 addToToolBar(); } private void initAllButton() { fontNameComboBox = new UIComboBox<>(GeneralKit.getAvailableFontFamilyNames()); fontNameComboBox.setPreferredSize(new Dimension(144, 20)); fontSizeComboBox = new UIComboBox<>(FRFontPane.getFontSizes()); colorSelectPane = new UIToolbarColorButton(IOKit.readIcon("/com/fr/design/images/gui/color/foreground.png")); colorSelectPane.set4Toolbar(); bold = new UIToggleButton(IOKit.readIcon("/com/fr/design/images/m_format/cellstyle/bold.png")); italic = new UIToggleButton(IOKit.readIcon("/com/fr/design/images/m_format/cellstyle/italic.png")); underline = new UIToggleButton(IOKit.readIcon("/com/fr/design/images/m_format/cellstyle/underline.png")); superPane = new UIToggleButton(IOKit.readIcon("/com/fr/design/images/m_format/cellstyle/sup.png")); subPane = new UIToggleButton(IOKit.readIcon("/com/fr/design/images/m_format/cellstyle/sub.png")); formulaPane = new UIToggleButton(IOKit.readIcon("/com/fr/design/images/m_insert/formula.png")); //名字 initAllNames(); //悬浮提示 setToolTips(); //样式 setAllButtonStyle(); //绑定监听器 bindListener(); } private void setAllButtonStyle() { setButtonStyle(bold); setButtonStyle(italic); setButtonStyle(underline); setButtonStyle(subPane); setButtonStyle(superPane); setButtonStyle(formulaPane); } private void setButtonStyle(UIButton button) { button.setNormalPainted(false); button.setBackground(null); button.setOpaque(false); button.setPreferredSize(BUTTON_SIZE); button.setBorderPaintedOnlyWhenPressed(true); } private void addToToolBar() { this.setLayout(new FlowLayout(FlowLayout.LEFT)); this.add(fontNameComboBox); this.add(fontSizeComboBox); this.add(bold); this.add(italic); this.add(underline); this.add(colorSelectPane); this.add(superPane); this.add(subPane); this.add(formulaPane); } private void bindListener() { FRFont defaultFont = (this.textPane != null) ? FRFont.getInstance(this.textPane.getFont()) : DEFAULT_FONT; fontNameComboBox.addItemListener(fontNameItemListener); fontNameComboBox.setSelectedItem(defaultFont.getFontName()); fontSizeComboBox.addItemListener(fontSizeItemListener); fontSizeComboBox.setSelectedItem(scaleDown(defaultFont.getSize())); bold.addActionListener(blodChangeAction); italic.addActionListener(itaChangeAction); underline.addActionListener(underlineChangeAction); subPane.addActionListener(subChangeAction); superPane.addActionListener(superChangeAction); colorSelectPane.addColorChangeListener(colorChangeAction); formulaPane.addActionListener(formulaActionListener); //选中文字的监听器 textPane.addCaretListener(textCareListener); textPane.addMouseListener(setMouseCurrentStyle); textPane.getDocument().addDocumentListener(inputListener); } private void initAllNames() { fontNameComboBox.setGlobalName(DesignKit.i18nText("Fine-Design_Report_Font_Family")); fontSizeComboBox.setGlobalName(DesignKit.i18nText("Fine-Design_Form_Font_Size")); italic.setGlobalName(DesignKit.i18nText("Fine-Design_Report_Italic")); bold.setGlobalName(DesignKit.i18nText("Fine-Design_Report_Bold")); underline.setGlobalName(DesignKit.i18nText("Fine-Design_Report_Underline")); superPane.setGlobalName(DesignKit.i18nText("Fine-Design_Report_Super_Script")); subPane.setGlobalName(DesignKit.i18nText("Fine-Design_Report_Sub_Script")); } private void setToolTips() { colorSelectPane.setToolTipText(DesignKit.i18nText("Fine-Design_Report_Foreground")); italic.setToolTipText(DesignKit.i18nText("Fine-Design_Report_Italic")); bold.setToolTipText(DesignKit.i18nText("Fine-Design_Report_Bold")); underline.setToolTipText(DesignKit.i18nText("Fine-Design_Report_Underline")); superPane.setToolTipText(DesignKit.i18nText("Fine-Design_Report_Super_Script")); subPane.setToolTipText(DesignKit.i18nText("Fine-Design_Report_Sub_Script")); formulaPane.setToolTipText(DesignKit.i18nText("Fine-Design_Basic_Formula")); } /** * 移除输入监听 * 用于populate时, 插入字符串, 那时不需要插入监听 */ public void removeInputListener() { this.textPane.getDocument().removeDocumentListener(inputListener); } /** * 增加输入监听事件 */ public void addInputListener() { this.textPane.getDocument().addDocumentListener(inputListener); } private ActionListener blodChangeAction = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { boolean isBold = RichTextToolBar.this.bold.isSelected(); // 调用setCharacterAttributes函数设置文本区选择文本的字体 MutableAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setBold(attr, !isBold); setCharacterAttributes(RichTextToolBar.this.textPane, attr, false); } }; private ActionListener itaChangeAction = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { boolean isIta = RichTextToolBar.this.italic.isSelected(); // 调用setCharacterAttributes函数设置文本区选择文本的字体 MutableAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setItalic(attr, !isIta); setCharacterAttributes(RichTextToolBar.this.textPane, attr, false); } }; private ActionListener underlineChangeAction = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { boolean isUnder = RichTextToolBar.this.underline.isSelected(); // 调用setCharacterAttributes函数设置文本区选择文本的字体 MutableAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setUnderline(attr, !isUnder); setCharacterAttributes(RichTextToolBar.this.textPane, attr, false); } }; private ActionListener subChangeAction = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { boolean isSub = RichTextToolBar.this.subPane.isSelected(); // 调用setCharacterAttributes函数设置文本区选择文本的字体 MutableAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setSubscript(attr, !isSub); setCharacterAttributes(RichTextToolBar.this.textPane, attr, false); } }; private ActionListener superChangeAction = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { boolean isSuper = RichTextToolBar.this.superPane.isSelected(); // 调用setCharacterAttributes函数设置文本区选择文本的字体 MutableAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setSuperscript(attr, !isSuper); setCharacterAttributes(RichTextToolBar.this.textPane, attr, false); } }; private ChangeListener colorChangeAction = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { Color color = RichTextToolBar.this.colorSelectPane.getColor(); color = color == null ? Color.BLACK : color; // 调用setCharacterAttributes函数设置文本区选择文本的字体 MutableAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setForeground(attr, color); setCharacterAttributes(RichTextToolBar.this.textPane, attr, false); } }; // 设置文本区选择文本的样式 private void setCharacterAttributes(JEditorPane editor, AttributeSet attr, boolean replace) { //注意不要失焦 textPane.requestFocus(); // 取得选择文本的起始位置和结束位置 int start = editor.getSelectionStart(); int end = editor.getSelectionEnd(); // 如果选中文本,设置选中文本的样式 if (start != end) { StyledDocument doc = (StyledDocument) textPane.getDocument(); // 将所选文本设置为新的样式,replace为false表示不覆盖原有的样式 doc.setCharacterAttributes(start, end - start, attr, replace); } } private ItemListener fontSizeItemListener = new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { int fontSize = (Integer) RichTextToolBar.this.fontSizeComboBox.getSelectedItem(); fontSize = scaleUp(fontSize); // 调用setCharacterAttributes函数设置文本区选择文本的字体 MutableAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setFontSize(attr, fontSize); setCharacterAttributes(RichTextToolBar.this.textPane, attr, false); } }; private ItemListener fontNameItemListener = new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { String fontName = (String) RichTextToolBar.this.fontNameComboBox.getSelectedItem(); // 调用setCharacterAttributes函数设置文本区选择文本的字体 MutableAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setFontFamily(attr, fontName); setCharacterAttributes(RichTextToolBar.this.textPane, attr, false); } }; private ActionListener formulaActionListener = new ActionListener() { public void actionPerformed(ActionEvent evt) { final UIFormula formulaPane = FormulaFactory.createFormulaPane(); formulaPane.populate(BaseFormula.createFormulaBuilder().build()); formulaPane.showLargeWindow(DesignerContext.getDesignerFrame(), new DialogActionAdapter() { @Override public void doOk() { StyledDocument doc = (StyledDocument) textPane.getDocument(); BaseFormula fm = formulaPane.update(); String content = RichTextConverter.asFormula(fm.getContent()); int start = textPane.getSelectionStart(); AttributeSet attrs = start > 0 ? doc.getCharacterElement(start - 1).getAttributes() : new SimpleAttributeSet(); try { doc.insertString(start, content, attrs); } catch (BadLocationException e) { LogKit.error(e.getMessage(), e); } } }).setVisible(true); } }; private int roundUp(double num) { String numStr = Double.toString(num); numStr = new BigDecimal(numStr).setScale(0, BigDecimal.ROUND_HALF_UP).toString(); return Integer.parseInt(numStr); } private CaretListener textCareListener = new CaretListener() { //根据选中部分的文字样式, 来动态显示工具栏上按钮的状态 private void setSelectedCharStyle(int start, int end, StyledDocument doc) { boolean isBold = true; boolean isItalic = true; boolean isUnderline = true; boolean isSubscript = true; boolean isSuperscript = true; String fontName_1st = null; int fontSize_1st = 0; Color fontColor_1st = null; for (int i = start; i < end; i++) { Element ele = doc.getCharacterElement(i); AttributeSet attrs = ele.getAttributes(); //粗体 isBold = isBold && StyleConstants.isBold(attrs); //斜体 isItalic = isItalic && StyleConstants.isItalic(attrs); //下划线 isUnderline = isUnderline && StyleConstants.isUnderline(attrs); //下标 isSubscript = isSubscript && StyleConstants.isSubscript(attrs); //上标 isSuperscript = isSuperscript && StyleConstants.isSuperscript(attrs); if (i == start) { fontName_1st = (String) attrs.getAttribute(StyleConstants.FontFamily); fontSize_1st = (Integer) attrs.getAttribute(StyleConstants.FontSize); fontColor_1st = (Color) attrs.getAttribute(StyleConstants.Foreground); fontColor_1st = fontColor_1st == null ? Color.BLACK : fontColor_1st; } } setButtonSelected(isBold, isItalic, isUnderline, isSubscript, isSuperscript, fontName_1st, fontSize_1st, fontColor_1st); } //动态显示工具栏上按钮的状态 private void setButtonSelected(boolean isBold, boolean isItalic, boolean isUnderline, boolean isSubscript, boolean isSuperscript, String fontName_1st, int fontSize_1st, Color fontColor_1st) { bold.setSelected(isBold); italic.setSelected(isItalic); underline.setSelected(isUnderline); subPane.setSelected(isSubscript); superPane.setSelected(isSuperscript); //为什么字体名称, 大小, 颜色, 不需要去判断是否全相同呢 //因为如果全相同, 则设置为第一个字符的样式, 如果不全相同, 那么默认也设置成第一个字符的样式. fontNameComboBox.setSelectedItem(fontName_1st); fontSizeComboBox.removeItemListener(fontSizeItemListener); fontSizeComboBox.setSelectedItem(scaleDown(fontSize_1st)); fontSizeComboBox.addItemListener(fontSizeItemListener); selectColorPane(fontColor_1st); } private void selectColorPane(Color color) { colorSelectPane.removeColorChangeListener(colorChangeAction); colorSelectPane.setColor(color); colorSelectPane.addColorChangeListener(colorChangeAction); } @Override public void caretUpdate(CaretEvent e) { StyledDocument doc = (StyledDocument) textPane.getDocument(); // 取得选择文本的起始位置和结束位置 int start = textPane.getSelectionStart(); int end = textPane.getSelectionEnd(); //如果没有选定字符 if (end == start) { return; } setSelectedCharStyle(start, end, doc); } }; //设置当前光标位样式 private MouseListener setMouseCurrentStyle = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { StyledDocument doc = (StyledDocument) textPane.getDocument(); // 取得选择文本的起始位置和结束位置 int start = textPane.getSelectionStart(); int end = textPane.getSelectionEnd(); if (start != end) { return; } setToLastCharStyle(end, doc); } //如果默认不选字符, 那么设置为最后一个字符的样式 private void setToLastCharStyle(int end, StyledDocument doc) { if (textPane.isUpdating()) { return; } //取前一个字符的样式 Element ele = doc.getCharacterElement(end - 1); AttributeSet attrs = ele.getAttributes(); populateToolBar(attrs); } }; /** * 从样式中更新工具栏上的按钮状态 * * @param attrs 样式 * @since 2015-1-5-下午5:12:33 */ public void populateToolBar(AttributeSet attrs) { int size = scaleDown(StyleConstants.getFontSize(attrs)); fontNameComboBox.setSelectedItem(StyleConstants.getFontFamily(attrs)); fontSizeComboBox.setSelectedItem(size); bold.setSelected(StyleConstants.isBold(attrs)); italic.setSelected(StyleConstants.isItalic(attrs)); underline.setSelected(StyleConstants.isUnderline(attrs)); subPane.setSelected(StyleConstants.isSubscript(attrs)); superPane.setSelected(StyleConstants.isSuperscript(attrs)); Color foreGround = StyleConstants.getForeground(attrs); foreGround = foreGround == null ? Color.BLACK : foreGround; colorSelectPane.setColor(foreGround); colorSelectPane.repaint(); } //pt转为px =*4/3 private int scaleUp(int fontSize) { return scale(fontSize, true); } //px转pt = *3/4 private int scaleDown(int fontSize) { return scale(fontSize, false); } private int scale(int fontSize, boolean isUp) { double dpi96 = Constants.FR_PAINT_RESOLUTION; double dpi72 = Constants.DEFAULT_FONT_PAINT_RESOLUTION; double scale = isUp ? (dpi96 / dpi72) : (dpi72 / dpi96); return roundUp(fontSize * scale); } private DocumentListener inputListener = new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { } @Override public void insertUpdate(DocumentEvent e) { //标志正在更新内容 textPane.startUpdating(); final MutableAttributeSet attr = updateStyleFromToolBar(); final int start = textPane.getSelectionStart(); int end = textPane.getSelectionEnd(); if (start != end) { textPane.finishUpdating(); return; } //放到SwingWorker里, 是因为在documentListener里不能动态改变doc内容 SwingUtilities.invokeLater(new Runnable() { @Override public void run() { changeContentStyle(start, attr); } }); } //根据Style来显示populate按钮 private void changeContentStyle(int start, MutableAttributeSet attr) { changeContentStyle(start, attr, 1); } private void changeContentStyle(int start, MutableAttributeSet attr, int contentLength) { // 将所选文本设置为新的样式,replace为false表示不覆盖原有的样式 StyledDocument doc = (StyledDocument) textPane.getDocument(); doc.setCharacterAttributes(start, contentLength, attr, false); textPane.finishUpdating(); } //将界面上的设置赋值给输入的字符 private MutableAttributeSet updateStyleFromToolBar() { final boolean isBold = bold.isSelected(); final boolean isItalic = italic.isSelected(); final boolean isSub = subPane.isSelected(); final boolean isSuper = superPane.isSelected(); final boolean isUnderLine = underline.isSelected(); final String fontName = (String) fontNameComboBox.getSelectedItem(); final int fontSize = scaleUp((Integer) fontSizeComboBox.getSelectedItem()); final Color foreGround = colorSelectPane.getColor() == null ? Color.BLACK : colorSelectPane.getColor(); MutableAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setBold(attr, isBold); StyleConstants.setItalic(attr, isItalic); StyleConstants.setSubscript(attr, isSub); StyleConstants.setSuperscript(attr, isSuper); StyleConstants.setUnderline(attr, isUnderLine); StyleConstants.setForeground(attr, foreGround); StyleConstants.setFontFamily(attr, fontName); StyleConstants.setFontSize(attr, fontSize); return attr; } @Override public void changedUpdate(DocumentEvent e) { } private void setContentStyle(final int inputLen) { //缓存下Start, 下面要用来设置样式 final int _start = inputStart; final MutableAttributeSet attr = updateStyleFromToolBar(); //放到SwingWorker里, 是因为在documentListener里不能动态改变doc内容 SwingUtilities.invokeLater(new Runnable() { @Override public void run() { //防止触发死循环change事件 startUpdating(); //Start-1 是因为中文输入法会用空格占1位 changeContentStyle(_start, attr, inputLen); resetFlag(); } }); } private boolean isUpdating() { return inputStart == UPDATING; } private void startUpdating() { inputStart = UPDATING; } //初始标记状态, 用于记录中文输入法多个字符同时输入的问题 private void initFlag(StyledDocument doc) { if (inputStart != NOT_INITIALIZED) { return; } inputStart = textPane.getSelectionStart() - 1; } //重置标记状态 private void resetFlag() { inputStart = NOT_INITIALIZED; } }; }