From 07aa14dc814f65dd1b70dc620f8845d6348b5497 Mon Sep 17 00:00:00 2001 From: Joe <825510477@qq.com> Date: Wed, 23 Dec 2020 11:00:19 +0800 Subject: [PATCH] event manager before popup view --- .../manager/core/WorkbookEventManager.java | 6 +- .../event/manager/data/MyCellWidget.java | 51 --- .../event/manager/data/WidgetSource.java | 9 - .../event/manager/ui/CellWidgetPane.java | 295 +++++++++++++++++- .../event/manager/ui/EventConfigPane.java | 12 +- .../manager/ui/WorkbookEventManagerPane.java | 11 +- .../ui/tree/MyComponentCellRenderer.java | 1 - .../manager/ui/tree/MyComponentTree.java | 13 +- 8 files changed, 307 insertions(+), 91 deletions(-) delete mode 100644 src/main/java/com/fr/plugin/event/manager/data/MyCellWidget.java delete mode 100644 src/main/java/com/fr/plugin/event/manager/data/WidgetSource.java diff --git a/src/main/java/com/fr/plugin/event/manager/core/WorkbookEventManager.java b/src/main/java/com/fr/plugin/event/manager/core/WorkbookEventManager.java index 4cb44f4..1e150f5 100644 --- a/src/main/java/com/fr/plugin/event/manager/core/WorkbookEventManager.java +++ b/src/main/java/com/fr/plugin/event/manager/core/WorkbookEventManager.java @@ -5,6 +5,7 @@ import com.fr.design.DesignModelAdapter; import com.fr.design.actions.JWorkBookAction; import com.fr.design.dialog.FineJOptionPane; import com.fr.design.dialog.UIDialog; +import com.fr.design.mainframe.CellWidgetPropertyPane; import com.fr.design.mainframe.DesignerContext; import com.fr.design.mainframe.JWorkBook; import com.fr.main.TemplateWorkBook; @@ -39,13 +40,12 @@ public class WorkbookEventManager extends JWorkBookAction { @Override @ExecuteFunctionRecord public void actionPerformed(ActionEvent e) { - JWorkBook jwb = getEditingComponent(); + final JWorkBook jwb = getEditingComponent(); if (jwb == null) { FineJOptionPane.showMessageDialog(DesignerContext.getDesignerFrame(), "无法获取模板对象!", com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Alert"), JOptionPane.ERROR_MESSAGE); return; } - final TemplateWorkBook workBook = jwb.getTarget(); final EventManagerPane eventManagerPane = initEventManager(workBook); UIDialog dialog = new EventManagerDialog(DesignerContext.getDesignerFrame(), eventManagerPane); @@ -54,8 +54,10 @@ public class WorkbookEventManager extends JWorkBookAction { @Override public void windowClosed(WindowEvent e) { eventManagerPane.update(); + // todo 是否改成windowClosing // 触发正在编辑的模板改变事件 DesignModelAdapter.getCurrentModelAdapter().fireTargetModified(); + jwb.refreshEastPropertiesPane(); super.windowClosed(e); } }); diff --git a/src/main/java/com/fr/plugin/event/manager/data/MyCellWidget.java b/src/main/java/com/fr/plugin/event/manager/data/MyCellWidget.java deleted file mode 100644 index fdd1b93..0000000 --- a/src/main/java/com/fr/plugin/event/manager/data/MyCellWidget.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.fr.plugin.event.manager.data; - -import com.fr.form.ui.Widget; -import com.fr.report.cell.CellElement; - -import javax.swing.Icon; - -/** - * @author Joe - * Created by Joe on 12/13/2020 - */ -public class MyCellWidget extends MyWidget { - private CellElement cellElement; - // 来自普通还是条件属性 - private WidgetSource source; - - public MyCellWidget(CellElement cellElement, Widget widget) { - this(cellElement, widget, WidgetSource.NORMAL); - } - - public MyCellWidget(CellElement cellElement, Widget widget, WidgetSource source) { - super(widget); - this.cellElement = cellElement; - this.source = source; - } - - public Widget getWidget() { - return widget; - } - - public void setWidget(Widget widget) { - this.widget = widget; - } - - public WidgetSource getSource() { - return source; - } - - public void setSource(WidgetSource source) { - this.source = source; - } - - @Override - public String getNodeName() { - if (source == WidgetSource.NORMAL) { - return cellElement.getRow() + " - " + cellElement.getColumn(); - } else { - return cellElement.getRow() + " - " + cellElement.getColumn() + "Cond"; - } - } -} diff --git a/src/main/java/com/fr/plugin/event/manager/data/WidgetSource.java b/src/main/java/com/fr/plugin/event/manager/data/WidgetSource.java deleted file mode 100644 index b5d4cc7..0000000 --- a/src/main/java/com/fr/plugin/event/manager/data/WidgetSource.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fr.plugin.event.manager.data; - -/** - * @author Joe - * Created by Joe on 12/13/2020 - */ -public enum WidgetSource { - NORMAL, HIGHLIGHT -} diff --git a/src/main/java/com/fr/plugin/event/manager/ui/CellWidgetPane.java b/src/main/java/com/fr/plugin/event/manager/ui/CellWidgetPane.java index 6a0bf2e..a3e2453 100644 --- a/src/main/java/com/fr/plugin/event/manager/ui/CellWidgetPane.java +++ b/src/main/java/com/fr/plugin/event/manager/ui/CellWidgetPane.java @@ -1,10 +1,30 @@ package com.fr.plugin.event.manager.ui; +import com.fr.base.GraphHelper; +import com.fr.design.constants.UIConstants; import com.fr.design.dialog.BasicPane; +import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.gui.ibutton.UIButtonUI; +import com.fr.design.gui.ibutton.UIToggleButton; import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.design.utils.gui.GUIPaintUtils; +import com.fr.general.IOUtils; import com.fr.plugin.event.manager.data.MyTree; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JPanel; import java.awt.BorderLayout; + +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; import java.util.List; /** @@ -12,8 +32,20 @@ import java.util.List; * Created by Joe on 12/13/2020 */ public class CellWidgetPane extends BasicPane { + private static final Icon LEFT_ICON = IOUtils.readIcon("com/fr/design/images/sheet/left_normal@1x.png"); + private static final Icon RIGHT_ICON = IOUtils.readIcon("com/fr/design/images/sheet/right_normal@1x.png"); + private static final Icon DISABLED_LEFT_ICON = IOUtils.readIcon("com/fr/design/images/sheet/left_hover@1x.png"); + private static final Icon DISABLED_RIGHT_ICON = IOUtils.readIcon("com/fr/design/images/sheet/right_hover@1x.png"); + + private static final String ELLIPSIS = "..."; + private static final int MAX_WIDTH = 810; + private static final int PREFERRED_HEIGHT = 24; + private static final int FLOW_GAP = 5; + private static final int BUTTON_EXTRA_WIDTH = 20; + private List cellWidgetTrees; private EventConfigPane eventConfigPane; + private SheetChoosePane sheetChoosePane; public CellWidgetPane(List cellWidgetTrees) { this.cellWidgetTrees = cellWidgetTrees; @@ -21,18 +53,10 @@ public class CellWidgetPane extends BasicPane { } private void initComponentPane() { - this.setLayout(FRGUIPaneFactory.createBorderLayout()); - initEventConfigPane(); - this.add(eventConfigPane, BorderLayout.CENTER); - } - - private void initEventConfigPane() { - // 初始化事件面板 - if (cellWidgetTrees.size() > 0) { - eventConfigPane = new EventConfigPane(cellWidgetTrees.get(0)); - } else { - eventConfigPane = new EventConfigPane(new MyTree()); - } + setLayout(new BorderLayout(4, 0)); + eventConfigPane = new EventConfigPane(new MyTree()); + sheetChoosePane = new SheetChoosePane(); + populate(cellWidgetTrees.size() > 0 ? cellWidgetTrees.get(0) : new MyTree()); } public String tabTitle() { @@ -44,7 +68,254 @@ public class CellWidgetPane extends BasicPane { return "cell widget"; } + public void populate(MyTree myTree) { + removeAll(); + eventConfigPane = new EventConfigPane(myTree); + add(eventConfigPane, BorderLayout.CENTER); + add(sheetChoosePane, BorderLayout.NORTH); + repaint(); + revalidate(); + } + public void update() { + eventConfigPane.update(); + } + + private class SheetChoosePane extends JPanel { + // 总sheet按钮数 + private int totalCount; + + // 展示的sheet按钮数 + private int showCount = 0; + + // sheet宽度数组 + private int[] widthArray; + + // 选中的sheet的index + private int selectedIndex = 0; + + // 最左边的sheet的index + private int scrollIndex = 0; + + // button数组 + private List buttonList; + + // 左边的panel + private JPanel sheetFlowPane; + + /** + * 左移和右移按钮 + */ + private UIButton leftButton; + private UIButton rightButton; + + public SheetChoosePane() { + totalCount = cellWidgetTrees.size(); + buttonList = new ArrayList<>(); + initComponentPane(); + } + + private void initComponentPane() { + setLayout(FRGUIPaneFactory.createBorderLayout()); + setBorder(BorderFactory.createEmptyBorder(4, 8, 0, 8)); + // 初始化右边按钮 + add(createButtonPane(), BorderLayout.EAST); + // 初始化左边面板 + initSheetFlowPane(); + add(sheetFlowPane, BorderLayout.CENTER); + } + + private void initSheetFlowPane() { + sheetFlowPane = new JPanel(new FlowLayout(FlowLayout.LEFT)); + initWidthAndButtonArray(); + addButton(); + checkButton(showCount < widthArray.length); + } + private JPanel createButtonPane() { + leftButton = new UIButton(LEFT_ICON) { + @Override + public Dimension getPreferredSize() { + return new Dimension(super.getPreferredSize().width, PREFERRED_HEIGHT); + } + }; + leftButton.setUI(new UIButtonUI() { + @Override + protected void doExtraPainting(UIButton b, Graphics2D g2d, int w, int h, String selectedRoles) { + if (isPressed(b) && b.isPressedPainted()) { + GUIPaintUtils.fillPressed(g2d, 0, 0, w, h, b.isRoundBorder(), b.getRectDirection(), b.isDoneAuthorityEdited(selectedRoles), UIConstants.PROPERTY_PANE_BACKGROUND); + } else if (isRollOver(b)) { + GUIPaintUtils.fillRollOver(g2d, 0, 0, w, h, b.isRoundBorder(), b.getRectDirection(), b.isDoneAuthorityEdited(selectedRoles), b.isPressedPainted(), UIConstants.PROPERTY_PANE_BACKGROUND); + } else if (b.isNormalPainted()) { + GUIPaintUtils.fillNormal(g2d, 0, 0, w, h, b.isRoundBorder(), b.getRectDirection(), b.isDoneAuthorityEdited(selectedRoles), b.isPressedPainted()); + } + } + }); + leftButton.set4ToolbarButton(); + leftButton.setDisabledIcon(DISABLED_LEFT_ICON); + leftButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + moveLeft(); + } + }); + rightButton = new UIButton(RIGHT_ICON) { + @Override + public Dimension getPreferredSize() { + return new Dimension(super.getPreferredSize().width, PREFERRED_HEIGHT); + } + }; + rightButton.setUI(new UIButtonUI() { + @Override + protected void doExtraPainting(UIButton b, Graphics2D g2d, int w, int h, String selectedRoles) { + if (isPressed(b) && b.isPressedPainted()) { + GUIPaintUtils.fillPressed(g2d, 0, 0, w, h, b.isRoundBorder(), b.getRectDirection(), b.isDoneAuthorityEdited(selectedRoles), UIConstants.PROPERTY_PANE_BACKGROUND); + } else if (isRollOver(b)) { + GUIPaintUtils.fillRollOver(g2d, 0, 0, w, h, b.isRoundBorder(), b.getRectDirection(), b.isDoneAuthorityEdited(selectedRoles), b.isPressedPainted(), UIConstants.PROPERTY_PANE_BACKGROUND); + } else if (b.isNormalPainted()) { + GUIPaintUtils.fillNormal(g2d, 0, 0, w, h, b.isRoundBorder(), b.getRectDirection(), b.isDoneAuthorityEdited(selectedRoles), b.isPressedPainted()); + } + } + }); + rightButton.set4ToolbarButton(); + rightButton.setDisabledIcon(DISABLED_RIGHT_ICON); + rightButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + moveRight(); + } + }); + JPanel buttonPane = new JPanel(new BorderLayout(0, 0)); + buttonPane.add(rightButton, BorderLayout.EAST); + buttonPane.add(leftButton, BorderLayout.CENTER); + return buttonPane; + } + + private void moveLeft() { + if (scrollIndex > 0) { + scrollIndex--; + refreshSheetFlowPane(); + } + } + + private void moveRight() { + if (scrollIndex < totalCount - showCount) { + scrollIndex++; + refreshSheetFlowPane(); + } + } + + private void refreshSheetFlowPane() { + sheetFlowPane.removeAll(); + sheetFlowPane.repaint(); + addButton(); + sheetFlowPane.revalidate(); + } + + private void addButton() { + showCount = 0; + int currentWidth = FLOW_GAP; + for (int i = scrollIndex; i < totalCount; i++) { + currentWidth += widthArray[i] + FLOW_GAP; + if (currentWidth > MAX_WIDTH) { + break; + } else { + UIToggleButton button = buttonList.get(i); + if (selectedIndex == i) { + // 如果被选中 + button.setSelected(true); + } + sheetFlowPane.add(button); + showCount++; + } + } + } + + private void checkButton(boolean buttonEnabled) { + leftButton.setEnabled(buttonEnabled); + rightButton.setEnabled(buttonEnabled); + } + + /** + * 根据名字和个数计算出所有tab的宽度和名称 + * + * @return + */ + private void initWidthAndButtonArray() { + widthArray = new int[totalCount]; + for (int i = 0; i < totalCount; i++) { + final int index = i; + String sheetName = cellWidgetTrees.get(i).getData().getNodeName(); + if (getStringWidth(sheetName) > MAX_WIDTH - 2 * FLOW_GAP) { + sheetName = getEllipsisName(sheetName, MAX_WIDTH - 2 * FLOW_GAP); + } + // 按钮除了字符串还有20的宽度 + widthArray[i] = getStringWidth(sheetName) + BUTTON_EXTRA_WIDTH; + UIToggleButton button = new UIToggleButton(sheetName) { + @Override + protected MouseListener getMouseListener() { + return new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + setSelectedStatus(index); + eventConfigPane.update(); + populate(cellWidgetTrees.get(index)); + } + }; + } + }; + buttonList.add(button); + } + } + + private void setSelectedStatus(int index) { + selectedIndex = index; + for (int i = 0; i < buttonList.size(); i++) { + if (i == index) { + buttonList.get(i).setSelected(true); + } else { + buttonList.get(i).setSelected(false); + } + } + } + + private int getStringWidth(String str) { + return GraphHelper.getFontMetrics(this.getFont()).stringWidth(str); + } + + /** + * 判断tab文字的长度大于能装下的最大文字长度,要用省略号 + * + * @param name + * @param maxStringlength + * @return + */ + private String getEllipsisName(String name, int maxStringlength) { + + //若是名字长度大于能显示的长度,那能显示的文字的最大长度还要减去省略号的最大长度 + int ellipsisWidth = getStringWidth(ELLIPSIS); + int leftKeyPoint = 0; + int rightKeyPoint = name.length() - 1; + int leftStrWidth = 0; + int rightStrWidth = 0; + while (leftStrWidth + rightStrWidth + ellipsisWidth < maxStringlength) { + if (leftStrWidth <= rightStrWidth) { + leftKeyPoint++; + } else { + rightKeyPoint--; + } + leftStrWidth = getStringWidth(name.substring(0, leftKeyPoint)); + rightStrWidth = getStringWidth(name.substring(rightKeyPoint)); + + if (leftStrWidth + rightStrWidth + ellipsisWidth > maxStringlength) { + if (leftStrWidth <= rightStrWidth) { + rightKeyPoint++; + } + break; + } + } + + return name.substring(0, leftKeyPoint) + ELLIPSIS + name.substring(rightKeyPoint); + } } } diff --git a/src/main/java/com/fr/plugin/event/manager/ui/EventConfigPane.java b/src/main/java/com/fr/plugin/event/manager/ui/EventConfigPane.java index 7366742..c1e3524 100644 --- a/src/main/java/com/fr/plugin/event/manager/ui/EventConfigPane.java +++ b/src/main/java/com/fr/plugin/event/manager/ui/EventConfigPane.java @@ -17,6 +17,7 @@ import com.fr.plugin.event.manager.ui.tree.MyComponentTree; import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.JSplitPane; +import javax.swing.ScrollPaneConstants; import javax.swing.SwingConstants; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; @@ -66,6 +67,7 @@ public class EventConfigPane extends BasicPane implements TreeSelectionListener private void initComponentPane() { this.setLayout(FRGUIPaneFactory.createBorderLayout()); + this.setBorder(BorderFactory.createEmptyBorder(4, 8, 4, 8)); initRightPane(); // SplitPane // 增加边框 @@ -74,14 +76,13 @@ public class EventConfigPane extends BasicPane implements TreeSelectionListener mainSplitPane.setOneTouchExpandable(true); this.add(mainSplitPane, BorderLayout.CENTER); - // todo 考虑是否弹性 - mainSplitPane.setDividerLocation(230); + mainSplitPane.setDividerLocation(180); } private void initRightPane() { rightPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); - rightPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); eventPane = new WidgetEventPaneNoPop(null); + eventPane.setBorder(BorderFactory.createEmptyBorder()); emptyLabel = new UILabel("当前组件没有设置过事件"); emptyLabel.setHorizontalAlignment(SwingConstants.CENTER); rightPane.add(emptyLabel, BorderLayout.CENTER); @@ -111,6 +112,7 @@ public class EventConfigPane extends BasicPane implements TreeSelectionListener MyComponentTree componentTree = new MyComponentTree(tree, EventConfigPane.this, rootVisible); UIScrollPane scrollPane = new UIScrollPane(componentTree); // 把树添加到滚动框中 + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); leftContentPane.add(scrollPane, BorderLayout.CENTER); } @@ -142,9 +144,9 @@ public class EventConfigPane extends BasicPane implements TreeSelectionListener Widget widget = ((MyWidget) tree.getData()).getWidget(); refreshRightPane(eventPane); populate(widget); - return; + } else { + refreshRightPane(emptyLabel); } - refreshRightPane(emptyLabel); } /** diff --git a/src/main/java/com/fr/plugin/event/manager/ui/WorkbookEventManagerPane.java b/src/main/java/com/fr/plugin/event/manager/ui/WorkbookEventManagerPane.java index 91bd2f7..c45a6e0 100644 --- a/src/main/java/com/fr/plugin/event/manager/ui/WorkbookEventManagerPane.java +++ b/src/main/java/com/fr/plugin/event/manager/ui/WorkbookEventManagerPane.java @@ -7,11 +7,9 @@ import com.fr.form.ui.Widget; import com.fr.general.IOUtils; import com.fr.main.TemplateWorkBook; import com.fr.main.parameter.ReportParameterAttr; -import com.fr.plugin.event.manager.data.MyCellWidget; import com.fr.plugin.event.manager.data.MyNode; import com.fr.plugin.event.manager.data.MyTree; import com.fr.plugin.event.manager.data.MyWidget; -import com.fr.plugin.event.manager.data.WidgetSource; import com.fr.plugin.event.manager.utils.CommonConstants; import com.fr.report.cell.CellElement; import com.fr.report.cell.TemplateCellElement; @@ -59,7 +57,6 @@ public class WorkbookEventManagerPane extends EventManagerPane tabbedPane.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { - // todo 切换tab时需要保存之前配置 JTabbedPane pane = (JTabbedPane) e.getSource(); int selectedIndex = pane.getSelectedIndex(); if (selectedIndex == 0) { @@ -151,7 +148,9 @@ public class WorkbookEventManagerPane extends EventManagerPane Widget widget0 = ((TemplateCellElement) cell).getWidget(); if (widget0.getListenerSize() > 0) { // 控件有事件 - tree.addChild(new MyCellWidget(cell, widget0)); + MyWidget cellWidget = new MyWidget(widget0); + cellWidget.setNodeName(cell.toString()); + tree.addChild(cellWidget); hasWidgetEvent = true; } } @@ -163,7 +162,9 @@ public class WorkbookEventManagerPane extends EventManagerPane for (int k = 0; k < highlight.actionCount(); k++) { if (highlight.getHighlightAction(k) instanceof WidgetHighlightAction) { WidgetHighlightAction highlightAction = (WidgetHighlightAction) highlight.getHighlightAction(k); - tree.addChild(new MyCellWidget(cell, highlightAction.getWidget(), WidgetSource.HIGHLIGHT)); + MyWidget highlightWidget = new MyWidget(highlightAction.getWidget()); + highlightWidget.setNodeName(cell.toString() + "(" + highlight.getName() + ")"); + tree.addChild(highlightWidget); hasWidgetEvent = true; } } diff --git a/src/main/java/com/fr/plugin/event/manager/ui/tree/MyComponentCellRenderer.java b/src/main/java/com/fr/plugin/event/manager/ui/tree/MyComponentCellRenderer.java index 2c1792f..e8ffcd9 100644 --- a/src/main/java/com/fr/plugin/event/manager/ui/tree/MyComponentCellRenderer.java +++ b/src/main/java/com/fr/plugin/event/manager/ui/tree/MyComponentCellRenderer.java @@ -23,7 +23,6 @@ public class MyComponentCellRenderer extends DefaultTreeCellRenderer { super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); if (value instanceof MyTree) { String name = ((MyTree) value).getData().getNodeName(); - // todo 或许要用html使文字换行 https://www.open-open.com/blog/5035948000942327244.html setText(name); Icon icon = ((MyTree) value).getData().getIcon(); if (icon != null) { diff --git a/src/main/java/com/fr/plugin/event/manager/ui/tree/MyComponentTree.java b/src/main/java/com/fr/plugin/event/manager/ui/tree/MyComponentTree.java index 02ea8af..6c9d25e 100644 --- a/src/main/java/com/fr/plugin/event/manager/ui/tree/MyComponentTree.java +++ b/src/main/java/com/fr/plugin/event/manager/ui/tree/MyComponentTree.java @@ -27,13 +27,13 @@ public class MyComponentTree extends JTree { this.configPane = configPane; model = new MyComponentTreeModel(tree); setModel(model); - this.setBackground(new Color(255, 255, 255)); + setBackground(new Color(255, 255, 255)); // 根的可见性 setRootVisible(rootVisible); setCellRenderer(new MyComponentCellRenderer()); getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); - this.setDragEnabled(false); - initListener(); + setDragEnabled(false); + initListeners(); setEditable(false); setUI(uiTreeUI); setBorder(BorderFactory.createEmptyBorder(PADDING_TOP, PADDING_LEFT, 0, 0)); @@ -41,10 +41,11 @@ public class MyComponentTree extends JTree { expandTree(); } - private void initListener() { - this.addTreeSelectionListener(configPane); + private void initListeners() { + addTreeSelectionListener(configPane); } + /** * 最多展开一层 */ @@ -65,7 +66,7 @@ public class MyComponentTree extends JTree { } } - this.expandPath(path); + expandPath(path); } }