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

629 lines
26 KiB

package com.fr.startup.ui;
import com.fr.base.svg.IconUtils;
import com.fr.design.components.tooltip.ModernToolTip;
import com.fr.design.gui.icontainer.UIScrollPane;
import com.fr.design.gui.ilable.UILabel;
import com.fr.design.gui.itextfield.FRGraphics2D;
import com.fr.design.i18n.Toolkit;
import com.fr.design.layout.FRGUIPaneFactory;
import com.fr.design.utils.ColorUtils;
import com.fr.start.common.DesignerStartupContext;
import com.fr.startup.metric.DesignerMetrics;
import com.fr.third.guava.collect.Lists;
import org.jetbrains.annotations.NotNull;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JToolTip;
import javax.swing.JViewport;
import javax.swing.RepaintManager;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.EmptyBorder;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.ImageObserver;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static com.fr.startup.ui.StartupPageConstants.ARC_DIAMETER;
/**
* created by Harrison on 2022/07/06
**/
public class StartupPageWorkspacePanel extends JPanel {
/* color */
private static final Color SHALLOW_WHITE_COLOR = new Color(248, 250, 254);
private static final Color HOVER_COLOR = new Color(65, 155, 249);
private static final Color PATH_COLOR = new Color(51, 51, 52, (int) Math.round(255 * 0.5));
/* 长度 */
private static final int SCROLL_BAR_WIDTH = 20;
private static final int CONTENT_WIDTH = StartupPageConstants.CONTENT_WIDTH + SCROLL_BAR_WIDTH;
private static final int BORDER_THIN = 2;
private static final int ITEM_VERTICAL_GAP = 20;
private static final int SINGLE_ITEM_HEIGHT = 72;
private static final int SCROLL_HEIGHT = SINGLE_ITEM_HEIGHT * 4 + ITEM_VERTICAL_GAP * 4;
private static final int NAME_LABEL_SIZE = 15;
private static final int PATH_LABEL_SIZE = 10;
private static final Dimension LABEL_DIMENSION = new Dimension(28, 28);
private static final Dimension PATH_DIMENSION = new Dimension(100, 20);
private static final Dimension SELECT_WORKSPACE_DIMENSION = new Dimension(210, 72);
private static final Dimension SELECT_CREATE_DIMENSION = new Dimension(60, 72);
private static final int COLUMN_LIMIT = 3;
private static final int DOUBLE_CLICK_COUNT = 2;
public static final int PARTITION_LIMIT = 2;
/* model */
private final StartupPageModel pageModel;
private final List<List<StartupWorkspaceBean>> partitions;
private Runnable selectWorkspaceRunnable;
private final Runnable createNewTemplateRunnable;
private final Runnable openEmptyTemplateRunnable;
private JComponent contentPanel;
private JPanel tailPanel;
private boolean showMore = true;
public StartupPageWorkspacePanel(StartupPageModel pageModel) {
this.setLayout(new BorderLayout(0, 0));
this.setBorder(new EmptyBorder(15, 0, 20, 0));
this.pageModel = pageModel;
List<StartupWorkspaceBean> workspaceInfos = pageModel.getWorkspaceInfos();
this.partitions = Lists.partition(workspaceInfos, COLUMN_LIMIT);
this.contentPanel = generateLimitContentPanel(partitions);
this.add(contentPanel, BorderLayout.NORTH);
if (partitions.size() > PARTITION_LIMIT) {
this.tailPanel = generateTailPanel();
this.add(tailPanel, BorderLayout.SOUTH);
}
this.createNewTemplateRunnable = pageModel.getCreateNewTemplateRunnable();
this.openEmptyTemplateRunnable = pageModel.getOpenEmptyTemplateRunnable();
this.repaint();
}
public void showLessContent() {
this.remove(this.contentPanel);
this.contentPanel = generateLimitContentPanel(this.partitions);
this.add(contentPanel, BorderLayout.NORTH);
}
public void showMoreContent() {
this.remove(this.contentPanel);
this.contentPanel = generateUnLimitContentPanel(this.partitions);
this.add(contentPanel, BorderLayout.NORTH);
}
private JComponent generateUnLimitContentPanel(List<List<StartupWorkspaceBean>> partitions) {
JComponent panel = generateUnLimitContentPanel0(partitions);
ColorUtils.transparentBackground(panel);
return panel;
}
private JComponent generateUnLimitContentPanel0(List<List<StartupWorkspaceBean>> partitions) {
JPanel workspaceDescWrapper = new JPanel();
workspaceDescWrapper.setLayout(new BorderLayout(0, 0));
workspaceDescWrapper.setBorder(new EmptyBorder(0, 0, 0, 0));
JPanel workspaceDescPanel = new JPanel();
workspaceDescPanel.setLayout(new GridLayout(partitions.size(), 1, 0, 0));
for (List<StartupWorkspaceBean> partition : partitions) {
JPanel partitionPanel = generatePartitionPanel(partition);
workspaceDescPanel.add(partitionPanel);
}
boolean needScroll = partitions.size() > 4;
if (needScroll) {
return generateScrollUnLimitContentPanel(workspaceDescWrapper, workspaceDescPanel);
}
workspaceDescWrapper.add(workspaceDescPanel, BorderLayout.CENTER);
return workspaceDescWrapper;
}
@NotNull
private JPanel generateScrollUnLimitContentPanel(JPanel workspaceDescWrapper, JPanel workspaceDescPanel) {
// 滚动条
UIScrollPane scrollPane = new UIScrollPane(workspaceDescPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
JViewport viewport = scrollPane.getViewport();
JViewport scrollViewport = new TransparentScrollViewPort();
// 动态画图
scrollViewport.addChangeListener(e -> repaintAll());
scrollViewport.setView(viewport.getView());
scrollPane.setViewport(scrollViewport);
scrollPane.setBorder(new EmptyBorder(10, 0, 0, 0));
scrollPane.setPreferredSize(new Dimension(CONTENT_WIDTH, SCROLL_HEIGHT));
workspaceDescWrapper.add(scrollPane, BorderLayout.CENTER);
return workspaceDescWrapper;
}
private JPanel generateLimitContentPanel(List<List<StartupWorkspaceBean>> partitions) {
JPanel workspaceDescPanel = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, FlowLayout.LEFT, 0, 0);
int limit = 2;
for (int i = 0; i < partitions.size(); i++) {
if (i >= limit) {
break;
}
List<StartupWorkspaceBean> partition = partitions.get(i);
JPanel partitionPanel = generatePartitionPanel(partition);
workspaceDescPanel.add(partitionPanel);
}
ColorUtils.transparentBackground(workspaceDescPanel);
return workspaceDescPanel;
}
@NotNull
private JPanel generateTailPanel() {
AtomicReference<Color> hoverBackColorRef = new AtomicReference<>();
JPanel tailPanel = new JPanel();
{
tailPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
tailPanel.setBorder(new EmptyBorder(0, 0, 0, 20));
JPanel showAllPanel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (hoverBackColorRef.get() != null) {
g.setColor(hoverBackColorRef.get());
Dimension preferredSize = getPreferredSize();
g.fillRoundRect(0, 0, preferredSize.width, preferredSize.height, 5, 5);
}
}
};
showAllPanel.setLayout(new BorderLayout(5, 0));
showAllPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
UILabel fontLabel = new UILabel(Toolkit.i18nText("Fine-Design_Startup_Page_Expand_All"));
fontLabel.setForeground(HOVER_COLOR);
showAllPanel.setBackground(new Color(0, 0, 0, 0));
showAllPanel.add(fontLabel, BorderLayout.WEST);
UILabel iconLabel = new UILabel(IconUtils.readIcon("/com/fr/design/startup/show_more.svg"));
showAllPanel.add(iconLabel, BorderLayout.EAST);
Color showAllBackground = showAllPanel.getBackground();
showAllPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
Color hoverBackColor = new Color(217, 235, 254);
hoverBackColorRef.set(hoverBackColor);
repaintAll();
}
@Override
public void mouseExited(MouseEvent e) {
hoverBackColorRef.set(null);
ColorUtils.syncBackground(showAllPanel, showAllBackground);
repaintAll();
}
@Override
public void mousePressed(MouseEvent e) {
doShowAllAction(fontLabel, iconLabel);
}
});
tailPanel.add(showAllPanel);
Dimension preferredSize = tailPanel.getPreferredSize();
tailPanel.setPreferredSize(new Dimension(CONTENT_WIDTH, (int) preferredSize.getHeight()));
}
return tailPanel;
}
@NotNull
private JPanel generatePartitionPanel(List<StartupWorkspaceBean> partition) {
JPanel partitionPanelWrapper = new JPanel();
partitionPanelWrapper.setBorder(new EmptyBorder(10,0,10,0));
partitionPanelWrapper.setLayout(new BorderLayout());
JPanel partitionPanel = FRGUIPaneFactory.createBoxFlowInnerContainer_S_Pane(0, 20, 0);
partitionPanel.setName("partitionPanel");
for (StartupWorkspaceBean workspaceInfo : partition) {
JPanel workspaceItemDesc = FRGUIPaneFactory.createBorderLayout_S_Pane();
layoutSelectWorkspacePanel(workspaceInfo, workspaceItemDesc);
layoutSelectAndCreatePanel(workspaceInfo, workspaceItemDesc);
partitionPanel.add(workspaceItemDesc);
Dimension preferredSize = partitionPanel.getPreferredSize();
partitionPanel.setPreferredSize(new Dimension(CONTENT_WIDTH, (int) preferredSize.getHeight()));
}
partitionPanelWrapper.add(partitionPanel, BorderLayout.CENTER);
return partitionPanelWrapper;
}
private void layoutSelectWorkspacePanel(StartupWorkspaceBean workspaceInfo, JPanel workspaceItemDesc) {
// 选择工作目录
// 图标 / 分隔符 / 说明-进入
// 选择并新建
AtomicReference<Color> borderColorRef = new AtomicReference<>(null);
JPanel selectWorkspacePanel = new JPanel() {
@Override
public JToolTip createToolTip() {
return new ModernToolTip();
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Color borderColor = borderColorRef.get();
Color backColor = Color.WHITE;
g2d.setColor(backColor);
// 直角和圆角上下叠合在一起
int rectOffset = 10;
int roundOffset = 15;
// 填充一个直角
g2d.fillRoundRect(0, 0, getWidth() - rectOffset, getHeight(), ARC_DIAMETER, ARC_DIAMETER);
// 填充一个圆角
g2d.fillRoundRect(getWidth() - roundOffset, 0, roundOffset, getHeight(), 0, 0);
paintBorderIfHover(g2d, borderColor, backColor);
}
/**
* 当悬浮的时候,将边框展示出来
* 会叠合,然后填充中间
*
* |----【-|--】
* | A 【C| B】
* |----【-|--】
*
* 见上面有两种线,分别是 A-| 和 B-【
* 这里会将 A 和 B 叠在一起,中间则存在 C,然后将 C 的部分扩大一点,然后填充上背景
* 则形成下面这种图形
*
* |----】
* |----】
*
* @param g2d 绘画
* @param borderColor 边框颜色
* @param backColor 背景颜色
*/
private void paintBorderIfHover(Graphics2D g2d, Color borderColor, Color backColor) {
if (borderColor != null) {
g2d.setColor(borderColor);
g2d.setStroke(new BasicStroke(BORDER_THIN));
// 需要一个修正值
int strokeOffset = BORDER_THIN / 2;
// 直角和圆角上下叠合在一起
int rectOffset = 10;
int roundOffset = 15;
// 画一个圆角
int fixRoundWidth = getWidth() - rectOffset;
int fixRoundHeight = getHeight() - BORDER_THIN;
g2d.drawRoundRect(strokeOffset, strokeOffset, fixRoundWidth, fixRoundHeight, ARC_DIAMETER, ARC_DIAMETER);
g2d.setColor(backColor);
// 绘制一个矩形,覆盖住多余的相交线
// 需要考虑上下的线宽
int coverHeight = getHeight() - (BORDER_THIN * 2);
// 偏左一点的 fixedX
int fixedX = getWidth() - roundOffset - BORDER_THIN;
// 圆角和直角相交的区域
int coverWidth = 15;
g2d.fillRect(fixedX, BORDER_THIN, coverWidth, coverHeight);
g2d.setColor(borderColor);
g2d.drawLine(getWidth() / 2, strokeOffset, getWidth(), strokeOffset);
g2d.drawLine(getWidth() / 2, getHeight() - strokeOffset, getWidth(), getHeight() - strokeOffset);
g2d.drawLine(getWidth() - strokeOffset, strokeOffset, getWidth() - strokeOffset, getHeight() - strokeOffset);
}
}
};
selectWorkspacePanel.setLayout(new BorderLayout(0,0));
selectWorkspacePanel.setToolTipText(Toolkit.i18nText("Fine-Design_Startup_Page_Double_Click_Enter_Workspace"));
selectWorkspacePanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
{
JPanel iconPanel = FRGUIPaneFactory.createBorderLayout_L_Pane();
iconPanel.setBorder(new EmptyBorder(0, 10, 0, 10));
Icon icon = StartupPageUtil.getIcon4DescAreaByWorkspace(workspaceInfo);
UILabel label = new UILabel(icon);
label.setPreferredSize(LABEL_DIMENSION);
iconPanel.add(label, BorderLayout.CENTER);
selectWorkspacePanel.add(iconPanel, BorderLayout.WEST);
// desc / >箭头
JPanel descPanel = new JPanel();
descPanel.setLayout(FRGUIPaneFactory.createM_BorderLayout());
descPanel.setBorder(new EmptyBorder(0, 10, 0, 0));
JPanel simpleDescPanelWrapper = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, FlowLayout.CENTER, 0, 0);
JPanel simpleDescPanel = FRGUIPaneFactory.createBorderLayout_S_Pane();
UILabel nameLabel = new UILabel(workspaceInfo.getName());
Font font = nameLabel.getFont();
Font newSizeFont = font.deriveFont(font.getStyle(), NAME_LABEL_SIZE);
nameLabel.setFont(newSizeFont);
nameLabel.setPreferredSize(PATH_DIMENSION);
Color nameForeground = nameLabel.getForeground();
simpleDescPanel.add(nameLabel,BorderLayout.NORTH);
UILabel pathLabel = new UILabel(workspaceInfo.getPath());
pathLabel.setPreferredSize(PATH_DIMENSION);
Font pathFont = pathLabel.getFont();
pathLabel.setFont(pathFont.deriveFont(pathFont.getStyle(), PATH_LABEL_SIZE));
Color pathColor = PATH_COLOR;
pathLabel.setForeground(pathColor);
simpleDescPanel.add(pathLabel, BorderLayout.SOUTH);
simpleDescPanelWrapper.add(simpleDescPanel);
descPanel.add(simpleDescPanelWrapper, BorderLayout.WEST);
MouseAdapter selectWorkspaceMouseListener = new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
Color hoverColor = HOVER_COLOR;
borderColorRef.set(hoverColor);
nameLabel.setForeground(hoverColor);
pathLabel.setForeground(hoverColor );
repaintAll();
}
@Override
public void mouseExited(MouseEvent e) {
borderColorRef.set(Color.WHITE);
nameLabel.setForeground(nameForeground);
pathLabel.setForeground(pathColor);
repaintAll();
}
@Override
public void mousePressed(MouseEvent e) {
int clickCount = e.getClickCount();
if (clickCount == DOUBLE_CLICK_COUNT) {
doOpenEmptyTemplate(workspaceInfo);
return;
}
doSwitchWorkspace(workspaceInfo);
}
};
UILabel arrowLabel = new UILabel(IconUtils.readIcon("/com/fr/design/startup/more.svg")) {
@Override
public JToolTip createToolTip() {
return new ModernToolTip();
}
};
arrowLabel.setToolTipText(Toolkit.i18nText("Fine-Design_Startup_Page_Enter_Workspace"));
arrowLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
arrowLabel.setIcon(IconUtils.readIcon("/com/fr/design/startup/more_hover.svg"));
selectWorkspaceMouseListener.mouseEntered(e);
}
@Override
public void mouseExited(MouseEvent e) {
arrowLabel.setIcon(IconUtils.readIcon("/com/fr/design/startup/more.svg"));
selectWorkspaceMouseListener.mouseExited(e);
}
@Override
public void mousePressed(MouseEvent e) {
doOpenEmptyTemplate(workspaceInfo);
}
});
descPanel.add(arrowLabel, BorderLayout.EAST);
selectWorkspacePanel.add(descPanel, BorderLayout.CENTER);
selectWorkspacePanel.addMouseListener(selectWorkspaceMouseListener);
}
ColorUtils.syncBackground(selectWorkspacePanel, Color.WHITE);
selectWorkspacePanel.setPreferredSize(SELECT_WORKSPACE_DIMENSION);
workspaceItemDesc.add(selectWorkspacePanel, BorderLayout.WEST);
}
private void layoutSelectAndCreatePanel(StartupWorkspaceBean workspaceInfo, JPanel workspaceItemDesc) {
// 选择并新建
AtomicReference<Color> borderColorRef = new AtomicReference<>(null);
JPanel selectAndCreatePanel = new JPanel() {
@Override
public JToolTip createToolTip() {
return new ModernToolTip();
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Color borderColor = borderColorRef.get();
Color backColor = SHALLOW_WHITE_COLOR;
g2d.setColor(backColor);
// 见 layoutSelectWorkspacePanel 部分的分析
// 直角和圆角上下叠合在一起
int rectOffset = 10;
int roundOffset = 15;
int strokeOffset = BORDER_THIN / 2;
int fixedRoundOffset = roundOffset + strokeOffset;
g2d.fillRoundRect(0, 0, getWidth() - rectOffset, getHeight(), 0, 0);
g2d.fillRoundRect(getWidth() - fixedRoundOffset, 0, roundOffset, getHeight(), ARC_DIAMETER, ARC_DIAMETER);
if (borderColor != null) {
g2d.setColor(borderColor);
g2d.setStroke(new BasicStroke(BORDER_THIN));
int borderOffset = BORDER_THIN * 2;
// 画画的笔触需要调整一下
g2d.drawRoundRect(strokeOffset, strokeOffset, getWidth() - borderOffset, getHeight() - BORDER_THIN, ARC_DIAMETER, ARC_DIAMETER);
g2d.setColor(backColor);
int fillWidth = 15;
g2d.fillRect(0, 0, fillWidth, getHeight());
g2d.setColor(borderColor);
g2d.drawLine(strokeOffset, strokeOffset, fillWidth, strokeOffset);
g2d.drawLine(strokeOffset, getHeight() - strokeOffset, fillWidth, getHeight() - strokeOffset);
g2d.drawLine(strokeOffset, strokeOffset, strokeOffset, getHeight() - strokeOffset);
}
}
};
selectAndCreatePanel.setToolTipText(Toolkit.i18nText("Fine-Design_Startup_Page_Enter_Workspace_And_Create"));
selectAndCreatePanel.setBorder(new EmptyBorder(0, 0, 0, 0));
selectAndCreatePanel.setLayout(new BorderLayout());
{
UILabel label = new UILabel(IconUtils.readIcon("/com/fr/design/standard/system/add"));
label.setPreferredSize(new Dimension(ARC_DIAMETER, ARC_DIAMETER));
label.setForeground(HOVER_COLOR);
selectAndCreatePanel.add(label, BorderLayout.CENTER);
selectAndCreatePanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
borderColorRef.set(HOVER_COLOR);
label.setIcon(IconUtils.readIcon("/com/fr/design/standard/system/add_hover.svg"));
repaintAll();
}
@Override
public void mouseExited(MouseEvent e) {
borderColorRef.set(null);
label.setIcon(IconUtils.readIcon("/com/fr/design/standard/system/add"));
repaintAll();
}
@Override
public void mousePressed(MouseEvent e) {
pageModel.setSelectWorkspaceInfo(workspaceInfo);
createNewTemplateRunnable.run();
}
});
}
ColorUtils.syncBackground(selectAndCreatePanel, Color.GREEN);
selectAndCreatePanel.setPreferredSize(SELECT_CREATE_DIMENSION);
workspaceItemDesc.add(selectAndCreatePanel, BorderLayout.EAST);
}
public void setSelectWorkspaceRunnable(Runnable selectWorkspaceRunnable) {
this.selectWorkspaceRunnable = selectWorkspaceRunnable;
}
private void doOpenEmptyTemplate(StartupWorkspaceBean workspaceInfo) {
pageModel.setSelectWorkspaceInfo(workspaceInfo);
openEmptyTemplateRunnable.run();
DesignerMetrics designerMetrics = DesignerStartupContext.getInstance().getDesignerMetrics();
designerMetrics.getStatistic().recordOpenEmptyTemplate();
}
private void doSwitchWorkspace(StartupWorkspaceBean workspaceInfo) {
StartupWorkspaceBean lastWorkspaceInfo = pageModel.getSelectWorkspaceInfo();
// selectWorkspaceRunnable
pageModel.setSelectWorkspaceInfo(workspaceInfo);
selectWorkspaceRunnable.run();
DesignerMetrics designerMetrics = DesignerStartupContext.getInstance().getDesignerMetrics();
designerMetrics.getStatistic().recordSwitchWorkspace(lastWorkspaceInfo, workspaceInfo);
}
private void doShowAllAction(UILabel fontLabel, UILabel iconLabel) {
if (showMore) {
fontLabel.setText(Toolkit.i18nText("Fine-Design_Startup_Page_Collapse_Workspace"));
iconLabel.setIcon(IconUtils.readIcon("/com/fr/design/startup/show_less.svg"));
showMoreContent();
showMore = !showMore;
} else {
fontLabel.setText(Toolkit.i18nText("Fine-Design_Startup_Page_Expand_All"));
iconLabel.setIcon(IconUtils.readIcon("/com/fr/design/startup/show_more.svg"));
showLessContent();
showMore = !showMore;
}
DesignerMetrics designerMetrics = DesignerStartupContext.getInstance().getDesignerMetrics();
designerMetrics.getStatistic().recordShowAllAction();
repaintAll();
}
private void repaintAll() {
this.getRootPane().repaint();
}
/**
* 支持透明的滚动视图
*/
private class TransparentScrollViewPort extends JViewport {
/**
* 从而屏蔽掉 {@link RepaintManager.PaintManager#paintDoubleBuffered(JComponent, Image, Graphics, int, int, int, int)}
*
* @return 创建一个不会实际画图的 Graphics
*/
@Override
public Graphics getGraphics() {
Graphics graphics = super.getGraphics();
return new FRGraphics2D((Graphics2D) graphics) {
@Override
public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
return true;
}
};
}
}
}