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.
391 lines
14 KiB
391 lines
14 KiB
package com.fr.design.components.notification; |
|
|
|
import com.fr.base.function.ThrowableRunnable; |
|
import com.fr.base.svg.IconUtils; |
|
import com.fr.design.components.page.PageControlModel; |
|
import com.fr.design.components.page.PageControlPanel; |
|
import com.fr.design.dialog.link.MessageWithLink; |
|
import com.fr.design.gui.ibutton.UIButton; |
|
import com.fr.design.gui.ilable.UILabel; |
|
import com.fr.design.i18n.Toolkit; |
|
import com.fr.design.layout.FRGUIPaneFactory; |
|
import com.fr.design.layout.VerticalFlowLayout; |
|
import com.fr.design.utils.LinkStrUtils; |
|
import com.fr.env.detect.base.EnvDetectorConfig; |
|
|
|
import javax.swing.BorderFactory; |
|
import javax.swing.Icon; |
|
import javax.swing.JComponent; |
|
import javax.swing.JDialog; |
|
import javax.swing.JPanel; |
|
import javax.swing.JScrollPane; |
|
import javax.swing.ScrollPaneConstants; |
|
import java.awt.BorderLayout; |
|
import java.awt.Color; |
|
import java.awt.Container; |
|
import java.awt.Desktop; |
|
import java.awt.Dimension; |
|
import java.awt.event.ActionEvent; |
|
import java.awt.event.ActionListener; |
|
import java.awt.event.MouseAdapter; |
|
import java.awt.event.MouseEvent; |
|
import java.net.URI; |
|
import java.util.Arrays; |
|
import java.util.List; |
|
import java.util.function.Function; |
|
import java.util.function.Supplier; |
|
import java.util.stream.Collectors; |
|
|
|
/** |
|
* 右下角的提醒 <a href="https://kms.fineres.com/pages/viewpage.action?pageId=388333688">异常提醒</a> |
|
* 相关使用方式见 <a href="https://kms.fineres.com/pages/viewpage.action?pageId=415212013">提醒组件</a> |
|
* |
|
* created by Harrison on 2022/05/24 |
|
**/ |
|
public class NotificationDialog extends JDialog { |
|
|
|
/** |
|
* 通知框的内部高度 |
|
*/ |
|
private static final int CONTENT_INNER_HEIGHT = 60; |
|
/** |
|
* 通知框如果出现滚动条后的内部宽度 |
|
*/ |
|
private static final int CONTENT_SCROLL_WIDTH = 280; |
|
|
|
private static final int CONTENT_WIDTH = 300; |
|
private static final int CONTENT_HEIGHT = 100; |
|
/** |
|
* 通知框的外部宽高 |
|
*/ |
|
private static final Dimension CONTENT_SIZE = new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT); |
|
private static final Dimension BUTTON_DIMENSION = new Dimension(68, 20); |
|
|
|
/** |
|
* 标记 LABEL, 没有作用 |
|
*/ |
|
private static final UILabel SIGN_LABEL = new UILabel("#"); |
|
/** |
|
* 确认一个 LABEL 的宽高 |
|
*/ |
|
private static final Dimension SIGN_LABEL_DIMENSION = SIGN_LABEL.getPreferredSize(); |
|
|
|
private NotificationDialogProperties properties; |
|
|
|
/* 数据 model */ |
|
|
|
private List<NotificationModel> notificationModels; |
|
private PageControlModel pageControlModel; |
|
|
|
private JPanel body; |
|
private JPanel headerPanel; |
|
private JPanel contentPanel; |
|
private JPanel tailPanel; |
|
|
|
public NotificationDialog(NotificationDialogProperties properties, List<NotificationModel> notificationModels) { |
|
|
|
super(properties.getOwner()); |
|
setTitle(properties.getTitle()); |
|
this.properties = properties; |
|
|
|
this.notificationModels = notificationModels; |
|
this.pageControlModel = new PageControlModel(0, this.notificationModels.size()); |
|
|
|
initComponents(); |
|
} |
|
|
|
public void initComponents() { |
|
|
|
//UI 配置 |
|
configProperties(); |
|
|
|
this.body = FRGUIPaneFactory.createBorderLayout_L_Pane(); |
|
body.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); |
|
|
|
//首行 |
|
layoutHeaderPanel(); |
|
|
|
//消息内容 |
|
layoutContentPanel(); |
|
|
|
//查看详情 |
|
layoutTailPanel(); |
|
|
|
add(body); |
|
|
|
Dimension dimension = body.getPreferredSize(); |
|
setSize(dimension.width, dimension.height); |
|
|
|
Container parent = getParent(); |
|
setLocation((parent.getWidth() - dimension.width - 30 + parent.getX()), |
|
parent.getY() + parent.getHeight() - dimension.height - 30); |
|
|
|
} |
|
|
|
public void open() { |
|
|
|
setVisible(true); |
|
} |
|
|
|
private void configProperties() { |
|
|
|
setModal(properties.isModal()); |
|
setFocusable(false); |
|
setAutoRequestFocus(false); |
|
setResizable(false); |
|
} |
|
|
|
protected JPanel createHeaderPanel() { |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* 内容 |
|
* |
|
* @return 内容面板 |
|
*/ |
|
protected JPanel createContentPanel() { |
|
|
|
JPanel contentPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
|
contentPanel.setBorder(BorderFactory.createEmptyBorder(8, 10, 8, 10)); |
|
contentPanel.setName("contentPanel"); |
|
|
|
NotificationModel model = getCurrentModel(); |
|
|
|
UILabel icon = new UILabel(getIconForType(model.getType())); |
|
icon.setPreferredSize(new Dimension(16, 16)); |
|
JPanel iconPanel = FRGUIPaneFactory.createBorderLayout_L_Pane(); |
|
iconPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 10, 8)); |
|
iconPanel.add(icon, BorderLayout.NORTH); |
|
|
|
contentPanel.add(iconPanel, BorderLayout.WEST); |
|
|
|
JPanel centerPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
|
centerPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 5)); |
|
|
|
NotificationMessage[] messages = model.getMessages(); |
|
List<? extends JComponent> messageComponents = Arrays.stream(messages) |
|
.map((messageModel) -> { |
|
if (messageModel.getType() == NotificationMessage.Type.LINK) { |
|
NotificationMessage.LinkMessage linkMessage = (NotificationMessage.LinkMessage) messageModel; |
|
return new MessageWithLink(linkMessage.format(), ThrowableRunnable.toRunnable(() -> { |
|
Desktop.getDesktop().browse(URI.create(linkMessage.getLink())); |
|
})); |
|
} |
|
return new UILabel(LinkStrUtils.generateHtmlTag(messageModel.format())); |
|
}) |
|
.collect(Collectors.toList()); |
|
|
|
// 当高度 大于 60 时,就会出现滚动条。 |
|
// 当出现滚动条时,需要将内部的宽度限制为 280, 否则会展示不出来 |
|
Function<Double, Integer> calStandardWidth = height -> height > CONTENT_INNER_HEIGHT ? CONTENT_SCROLL_WIDTH : CONTENT_WIDTH; |
|
|
|
int widthUnit = messageComponents.stream() |
|
.map((component) -> { |
|
Dimension preferredSize = component.getPreferredSize(); |
|
double width = preferredSize.getWidth(); |
|
double widthFactor = Math.ceil(width / CONTENT_WIDTH); |
|
// 这里的高度是没有限制宽度的,如果限制宽度,高度会变更,所以这里需要加上宽度的影响 |
|
return preferredSize.getHeight() + widthFactor * SIGN_LABEL_DIMENSION.getHeight(); |
|
}) |
|
.reduce(Double::sum) |
|
.map(calStandardWidth) |
|
.orElse(CONTENT_WIDTH); |
|
|
|
messageComponents = messageComponents.stream() |
|
.peek((component) -> { |
|
Dimension preferredSize = component.getPreferredSize(); |
|
double componentWidth = preferredSize.getWidth(); |
|
double componentHeight = preferredSize.getHeight(); |
|
double heightFactor = Math.ceil(componentHeight / SIGN_LABEL_DIMENSION.getHeight()); |
|
double widthFactor = Math.ceil(componentWidth / widthUnit); |
|
int realHeight = (int)Math.ceil(heightFactor + widthFactor - 1) * (int)(Math.ceil(SIGN_LABEL_DIMENSION.getHeight())); |
|
component.setPreferredSize(new Dimension(widthUnit, realHeight)); |
|
}) |
|
.collect(Collectors.toList()); |
|
|
|
// 竖向排列 |
|
JPanel messageSummaryPanel = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, VerticalFlowLayout.TOP, 0, 0); |
|
messageComponents.forEach(messageSummaryPanel::add); |
|
|
|
JScrollPane jScrollPane = new JScrollPane(messageSummaryPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); |
|
jScrollPane.setBorder(BorderFactory.createEmptyBorder()); |
|
|
|
centerPanel.add(jScrollPane, BorderLayout.CENTER); |
|
centerPanel.setPreferredSize(CONTENT_SIZE); |
|
|
|
contentPanel.add(centerPanel, BorderLayout.CENTER); |
|
|
|
return contentPanel; |
|
} |
|
|
|
/** |
|
* 行动 |
|
* |
|
* UI布局 |
|
* /翻页/不再提醒/提醒行为/我知道了 |
|
* |
|
* @return 行动面板 |
|
*/ |
|
protected JPanel createTailPanel() { |
|
|
|
JPanel tailPanel = FRGUIPaneFactory.createBorderLayout_L_Pane(); |
|
tailPanel.setName("tailPanel"); |
|
|
|
// 翻页按钮效果 |
|
PageControlPanel pageControlPanel = new PageControlPanel(pageControlModel); |
|
|
|
pageControlPanel.actions(new Runnable() { |
|
@Override |
|
public void run() { |
|
pageControlModel = pageControlPanel.performPrevious(); |
|
refresh(); |
|
} |
|
}, new Runnable() { |
|
@Override |
|
public void run() { |
|
pageControlModel = pageControlPanel.performNext(); |
|
refresh(); |
|
} |
|
}); |
|
|
|
tailPanel.add(pageControlPanel, BorderLayout.WEST); |
|
|
|
// 行为效果 |
|
JPanel actionsPanel = FRGUIPaneFactory.createBorderLayout_M_Pane(); |
|
|
|
{ |
|
actionsPanel.setBorder(BorderFactory.createEmptyBorder()); |
|
actionsPanel.setName("actionsPanel"); |
|
|
|
UILabel notReminder = new UILabel(); |
|
notReminder.setText(Toolkit.i18nText("Fine-Design_Basic_Not_Reminder")); |
|
notReminder.addMouseListener(new MouseAdapter() { |
|
@Override |
|
public void mouseClicked(MouseEvent e) { |
|
// 配置处理 |
|
EnvDetectorConfig.getInstance().setEnabled(false); |
|
// 点击事件 |
|
destroy(); |
|
} |
|
}); |
|
Color color = new Color(65, 155, 249); |
|
notReminder.setForeground(color); |
|
actionsPanel.add(notReminder, BorderLayout.WEST); |
|
|
|
JPanel buttonPanel = FRGUIPaneFactory.createBorderLayout_M_Pane(); |
|
buttonPanel.setBorder(BorderFactory.createEmptyBorder()); |
|
|
|
// real-action |
|
NotificationModel currentModel = getCurrentModel(); |
|
NotificationAction action = currentModel.getAction(); |
|
if (action != null) { |
|
UIButton actionButton = new UIButton(action.name()); |
|
actionButton.setPreferredSize(BUTTON_DIMENSION); |
|
actionButton.addActionListener(new ActionListener() { |
|
@Override |
|
public void actionPerformed(ActionEvent e) { |
|
action.run(); |
|
} |
|
}); |
|
buttonPanel.add(actionButton, BorderLayout.WEST); |
|
} |
|
|
|
UIButton knowButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Know")); |
|
knowButton.setPreferredSize(BUTTON_DIMENSION); |
|
knowButton.addActionListener(new ActionListener() { |
|
@Override |
|
public void actionPerformed(ActionEvent e) { |
|
if (pageControlModel.isLast()) { |
|
destroy(); |
|
return; |
|
} |
|
pageControlModel = pageControlPanel.performNext(); |
|
refresh(); |
|
} |
|
}); |
|
buttonPanel.add(knowButton, BorderLayout.EAST); |
|
|
|
actionsPanel.add(buttonPanel, BorderLayout.EAST); |
|
} |
|
|
|
tailPanel.add(actionsPanel, BorderLayout.EAST); |
|
|
|
return tailPanel; |
|
} |
|
|
|
private void refresh() { |
|
|
|
layoutContentPanel(); |
|
|
|
layoutTailPanel(); |
|
|
|
this.repaint(); |
|
} |
|
|
|
private void layoutHeaderPanel() { |
|
|
|
this.headerPanel = layoutPanel(this.headerPanel, this::createHeaderPanel, BorderLayout.NORTH); |
|
} |
|
|
|
private void layoutTailPanel() { |
|
|
|
this.tailPanel = layoutPanel(this.tailPanel, this::createTailPanel, BorderLayout.SOUTH); |
|
} |
|
|
|
private void layoutContentPanel() { |
|
|
|
this.contentPanel = layoutPanel(this.contentPanel, this::createContentPanel, BorderLayout.CENTER); |
|
} |
|
|
|
private JPanel layoutPanel(JPanel oldPanel, Supplier<JPanel> supplier, Object constraints){ |
|
|
|
if (oldPanel != null) { |
|
this.body.remove(oldPanel); |
|
} |
|
JPanel newPanel = supplier.get(); |
|
if (newPanel != null) { |
|
this.body.add(newPanel, constraints); |
|
} |
|
return newPanel; |
|
} |
|
|
|
private NotificationModel getCurrentModel() { |
|
|
|
int index = pageControlModel.getIndex(); |
|
return notificationModels.get(index); |
|
} |
|
|
|
protected Icon getIconForType(NotificationType type) { |
|
|
|
String iconPath; |
|
switch (type) { |
|
case ERROR: |
|
iconPath = "/com/fr/design/standard/reminder/reminder_error.svg"; |
|
break; |
|
case INFO: |
|
iconPath = "/com/fr/design/standard/reminder/reminder_success.svg"; |
|
break; |
|
case WARNING: |
|
iconPath = "/com/fr/design/standard/reminder/reminder_warning.svg"; |
|
break; |
|
default: |
|
return null; |
|
} |
|
return IconUtils.readIcon(iconPath); |
|
} |
|
|
|
private void destroy() { |
|
|
|
setVisible(false); |
|
dispose(); |
|
} |
|
|
|
@Override |
|
public void dispose() { |
|
|
|
super.dispose(); |
|
// todo |
|
} |
|
}
|
|
|