Browse Source
Merge in ~CLOUD.LIU/design from feature/x to research/11.0 * commit '1a584a9a6b57e7f2c0330370ffa8a16d2a6ba79f': (1579 commits) 常量命名规范修复 REPORT-82787回退 REPORT-92147 REPORT-92159问题修复 REPORT-82787 图表空数据提示配置页面,默认图片需补充繁中版 REPORT-92134,REPORT-92120,REPORT-92123 问题修复 REPORT-89253 命名和读取规范化处理,回退右下角异常小标记 REPORT-89253 修正插入悬浮元素图标项文件路径 REPORT-89253 设计器替换高清svg图标第2次提交 REPORT-91610 数据脱敏-设计器内en下面板文案显示不全 【问题原因】en、ja、ko下面板文案显示不全 【改动思路】与产品沟通后,调大这几个语言下弹窗的宽度 【review建议】 REPORT-89253 设计器替换高清svg图标-第1次提交 REPORT-91657 设计器单元格属性-其他面板文本显示截断 【问题原因】不同语言显示长度不同 【改动思路】选项面板水平布局修改为垂直布局 REPORT-91743 设计器通过关闭模板A的方式触发保存,设计器页面显示还是模板A的内容 REPORT-91743 设计器通过关闭模板A的方式触发保存,设计器页面显示还是模板A的内容 REPORT-91486 不打开任何报表进入设计器,选项弹窗触发下保存,模板tab栏会自动弹出来 REPORT-91591 单元格控件的自定义样式设置--代码格式调整 REPORT-91316 不打开任何报表的情况下,编辑工具栏点不动 REPORT-91591 单元格控件的自定义样式设置 REPORT-91591 单元格控件的自定义样式设置 REPORT-80651 模板版本管理重构一期 - 优化代码结构 REPORT-80651 模板版本管理重构一期 - 修改代码 ...research/11.0
Cloud.Liu-刘学真
2 years ago
1743 changed files with 94487 additions and 27062 deletions
@ -0,0 +1,24 @@ |
|||||||
|
package com.fr.base.function; |
||||||
|
|
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
|
||||||
|
/** |
||||||
|
* 可抛出异常的 Runnable |
||||||
|
* |
||||||
|
* created by Harrison on 2022/05/24 |
||||||
|
**/ |
||||||
|
public interface ThrowableRunnable<T extends Exception> { |
||||||
|
|
||||||
|
void run() throws T; |
||||||
|
|
||||||
|
static <T extends Exception> Runnable toRunnable(ThrowableRunnable<T> runnable) { |
||||||
|
|
||||||
|
return () -> { |
||||||
|
try { |
||||||
|
runnable.run(); |
||||||
|
} catch (Exception e) { |
||||||
|
FineLoggerFactory.getLogger().error(e.getMessage(), e); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
package com.fr.base.function; |
||||||
|
|
||||||
|
import com.fr.design.utils.DesignUtils; |
||||||
|
import com.fr.runtime.FineRuntime; |
||||||
|
|
||||||
|
/** |
||||||
|
* UI 终止动作 |
||||||
|
* |
||||||
|
* created by Harrison on 2022/07/14 |
||||||
|
**/ |
||||||
|
public abstract class UITerminator { |
||||||
|
|
||||||
|
public void run() { |
||||||
|
|
||||||
|
// 先执行必须的逻辑
|
||||||
|
FineRuntime.start(); |
||||||
|
DesignUtils.initLookAndFeel(); |
||||||
|
|
||||||
|
// 在执行核心逻辑
|
||||||
|
doRun(); |
||||||
|
} |
||||||
|
|
||||||
|
protected abstract void doRun(); |
||||||
|
} |
@ -1,92 +0,0 @@ |
|||||||
package com.fr.base.svg; |
|
||||||
|
|
||||||
import com.fr.general.IOUtils; |
|
||||||
import org.apache.batik.transcoder.TranscoderException; |
|
||||||
import org.apache.batik.transcoder.TranscoderInput; |
|
||||||
import org.apache.xmlgraphics.java2d.Dimension2DDouble; |
|
||||||
import org.jetbrains.annotations.NotNull; |
|
||||||
import org.jetbrains.annotations.Nullable; |
|
||||||
|
|
||||||
import java.awt.Image; |
|
||||||
import java.io.IOException; |
|
||||||
import java.net.URL; |
|
||||||
|
|
||||||
/** |
|
||||||
* SVG图标加载器 |
|
||||||
* @author Yvan |
|
||||||
* @version 10.0 |
|
||||||
* Created by Yvan on 2020/12/17 |
|
||||||
*/ |
|
||||||
public class SVGLoader { |
|
||||||
public static final int ICON_DEFAULT_SIZE = 16; |
|
||||||
|
|
||||||
public SVGLoader() { |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
public static Image load(@NotNull String url) { |
|
||||||
try { |
|
||||||
URL resource = IOUtils.getResource(url, SVGLoader.class); |
|
||||||
if (resource == null) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
return load(resource, SVGIcon.SYSTEM_SCALE); |
|
||||||
} catch (IOException ignore) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
public static Image load(@NotNull URL url) throws IOException { |
|
||||||
return load(url, SVGIcon.SYSTEM_SCALE); |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
public static Image load(@NotNull URL url, double scale) throws IOException { |
|
||||||
try { |
|
||||||
String svgUri = url.toString(); |
|
||||||
TranscoderInput input = new TranscoderInput(svgUri); |
|
||||||
return SVGTranscoder.createImage(scale, input).getImage(); |
|
||||||
} catch (TranscoderException ignore) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
public static Image load(@NotNull URL url, double scale, Dimension2DDouble dimension) throws IOException { |
|
||||||
try { |
|
||||||
String svgUri = url.toString(); |
|
||||||
TranscoderInput input = new TranscoderInput(svgUri); |
|
||||||
return SVGTranscoder.createImage(scale, input, |
|
||||||
(float) (dimension.getWidth() * scale), (float) (dimension.getHeight() * scale)).getImage(); |
|
||||||
} catch (TranscoderException ignore) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Nullable |
|
||||||
public static Image load(@NotNull URL url, double scale, double overriddenWidth, double overriddenHeight) throws IOException { |
|
||||||
try { |
|
||||||
String svgUri = url.toString(); |
|
||||||
TranscoderInput input = new TranscoderInput(svgUri); |
|
||||||
return SVGTranscoder.createImage(scale, input, (float) (overriddenWidth * scale), (float) (overriddenHeight * scale)).getImage(); |
|
||||||
} catch (TranscoderException ignore) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
public static Image load(@NotNull String url, float width, float height) { |
|
||||||
try { |
|
||||||
URL resource = IOUtils.getResource(url, SVGLoader.class); |
|
||||||
if (resource == null) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
TranscoderInput input = new TranscoderInput(resource.toString()); |
|
||||||
return SVGTranscoder.createImage(SVGIcon.SYSTEM_SCALE, input, -1, -1, width, height).getImage(); |
|
||||||
} catch (TranscoderException ignore) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,181 +0,0 @@ |
|||||||
package com.fr.base.svg; |
|
||||||
|
|
||||||
import com.fr.stable.AssistUtils; |
|
||||||
import com.fr.value.AtomicNotNullLazyValue; |
|
||||||
import org.apache.batik.anim.dom.SAXSVGDocumentFactory; |
|
||||||
import org.apache.batik.anim.dom.SVGOMDocument; |
|
||||||
import org.apache.batik.bridge.BridgeContext; |
|
||||||
import org.apache.batik.bridge.UserAgent; |
|
||||||
import org.apache.batik.transcoder.SVGAbstractTranscoder; |
|
||||||
import org.apache.batik.transcoder.TranscoderException; |
|
||||||
import org.apache.batik.transcoder.TranscoderInput; |
|
||||||
import org.apache.batik.transcoder.TranscoderOutput; |
|
||||||
import org.apache.batik.transcoder.image.ImageTranscoder; |
|
||||||
import org.apache.batik.util.XMLResourceDescriptor; |
|
||||||
import org.jetbrains.annotations.NotNull; |
|
||||||
import org.jetbrains.annotations.Nullable; |
|
||||||
import org.w3c.dom.Element; |
|
||||||
import org.w3c.dom.svg.SVGDocument; |
|
||||||
|
|
||||||
import java.awt.GraphicsDevice; |
|
||||||
import java.awt.GraphicsEnvironment; |
|
||||||
import java.awt.Rectangle; |
|
||||||
import java.awt.geom.AffineTransform; |
|
||||||
import java.awt.image.BufferedImage; |
|
||||||
import java.io.IOException; |
|
||||||
import java.io.StringReader; |
|
||||||
|
|
||||||
/** |
|
||||||
* 可以根据某个缩放倍数scale,将SVG图片转化为Image对象 |
|
||||||
* @author Yvan |
|
||||||
* @version 10.0 |
|
||||||
* Created by Yvan on 2020/12/17 |
|
||||||
*/ |
|
||||||
public class SVGTranscoder extends ImageTranscoder { |
|
||||||
|
|
||||||
private static final float DEFAULT_VALUE = -1.0F; |
|
||||||
public static final float ICON_DEFAULT_SIZE = 16F; |
|
||||||
private float origDocWidth; |
|
||||||
private float origDocHeight; |
|
||||||
@Nullable |
|
||||||
private BufferedImage image; |
|
||||||
private final double scale; |
|
||||||
|
|
||||||
@NotNull |
|
||||||
private static AtomicNotNullLazyValue<Double> iconMaxSize = new AtomicNotNullLazyValue<Double>() { |
|
||||||
@NotNull |
|
||||||
@Override |
|
||||||
protected Double compute() { |
|
||||||
double maxSize = Double.MAX_VALUE; |
|
||||||
if (!GraphicsEnvironment.isHeadless()) { |
|
||||||
GraphicsDevice defaultScreenDevice = GraphicsEnvironment |
|
||||||
.getLocalGraphicsEnvironment() |
|
||||||
.getDefaultScreenDevice(); |
|
||||||
Rectangle bounds = defaultScreenDevice.getDefaultConfiguration().getBounds(); |
|
||||||
AffineTransform tx = defaultScreenDevice |
|
||||||
.getDefaultConfiguration() |
|
||||||
.getDefaultTransform(); |
|
||||||
maxSize = Math.max(bounds.width * tx.getScaleX(), bounds.height * tx.getScaleY()); |
|
||||||
} |
|
||||||
return maxSize; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
public SVGTranscoder(double scale) { |
|
||||||
this.scale = scale; |
|
||||||
this.width = ICON_DEFAULT_SIZE; |
|
||||||
this.height = ICON_DEFAULT_SIZE; |
|
||||||
} |
|
||||||
|
|
||||||
public SVGTranscoder(double scale, float width, float height) { |
|
||||||
this.scale = scale; |
|
||||||
this.width = width; |
|
||||||
this.height = height; |
|
||||||
} |
|
||||||
|
|
||||||
public final float getOrigDocWidth() { |
|
||||||
return this.origDocWidth; |
|
||||||
} |
|
||||||
|
|
||||||
public final void setOrigDocWidth(float origDocWidth) { |
|
||||||
this.origDocWidth = origDocWidth; |
|
||||||
} |
|
||||||
|
|
||||||
public final float getOrigDocHeight() { |
|
||||||
return this.origDocHeight; |
|
||||||
} |
|
||||||
|
|
||||||
public final void setOrigDocHeight(float origDocHeight) { |
|
||||||
this.origDocHeight = origDocHeight; |
|
||||||
} |
|
||||||
|
|
||||||
public static double getIconMaxSize() { |
|
||||||
return iconMaxSize.getValue(); |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
public final BufferedImage getImage() { |
|
||||||
return this.image; |
|
||||||
} |
|
||||||
|
|
||||||
@NotNull |
|
||||||
public static SVGTranscoder createImage(double scale, @NotNull TranscoderInput input) throws TranscoderException { |
|
||||||
return createImage(scale, input, -1, -1); |
|
||||||
} |
|
||||||
|
|
||||||
@NotNull |
|
||||||
public static SVGTranscoder createImage(double scale, @NotNull TranscoderInput input, float overriddenWidth, float overriddenHeight) throws TranscoderException { |
|
||||||
return createImage(scale, input, overriddenWidth, overriddenHeight, ICON_DEFAULT_SIZE, ICON_DEFAULT_SIZE); |
|
||||||
} |
|
||||||
|
|
||||||
@NotNull |
|
||||||
public static SVGTranscoder createImage(double scale, @NotNull TranscoderInput input, float overriddenWidth, float overriddenHeight, float width, float height) throws TranscoderException { |
|
||||||
SVGTranscoder transcoder = new SVGTranscoder(scale, width, height); |
|
||||||
if (!AssistUtils.equals(overriddenWidth, DEFAULT_VALUE)) { |
|
||||||
transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, overriddenWidth); |
|
||||||
} |
|
||||||
|
|
||||||
if (!AssistUtils.equals(overriddenHeight, DEFAULT_VALUE)) { |
|
||||||
transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, overriddenHeight); |
|
||||||
} |
|
||||||
|
|
||||||
double iconMaxSize = SVGTranscoder.iconMaxSize.getValue(); |
|
||||||
transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_MAX_WIDTH, (float) iconMaxSize); |
|
||||||
transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_MAX_HEIGHT, (float) iconMaxSize); |
|
||||||
transcoder.transcode(input, null); |
|
||||||
return transcoder; |
|
||||||
} |
|
||||||
|
|
||||||
private static SVGDocument createFallbackPlaceholder() { |
|
||||||
try { |
|
||||||
String fallbackIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\">\n" + |
|
||||||
" <rect x=\"1\" y=\"1\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"red\" stroke-width=\"2\"/>\n" + |
|
||||||
" <line x1=\"1\" y1=\"1\" x2=\"15\" y2=\"15\" stroke=\"red\" stroke-width=\"2\"/>\n" + |
|
||||||
" <line x1=\"1\" y1=\"15\" x2=\"15\" y2=\"1\" stroke=\"red\" stroke-width=\"2\"/>\n" + |
|
||||||
"</svg>\n"; |
|
||||||
|
|
||||||
SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName()); |
|
||||||
return (SVGDocument) factory.createDocument(null, new StringReader(fallbackIcon)); |
|
||||||
} catch (IOException e) { |
|
||||||
throw new IllegalStateException(e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void setImageSize(float docWidth, float docHeight) { |
|
||||||
super.setImageSize((float) (docWidth * this.scale), (float) (docHeight * this.scale)); |
|
||||||
this.origDocWidth = docWidth; |
|
||||||
this.origDocHeight = docHeight; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
@NotNull |
|
||||||
public BufferedImage createImage(int width, int height) { |
|
||||||
return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void writeImage(@NotNull BufferedImage image, @Nullable TranscoderOutput output) { |
|
||||||
this.image = image; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
@NotNull |
|
||||||
protected UserAgent createUserAgent() { |
|
||||||
return new SVGAbstractTranscoderUserAgent() { |
|
||||||
@Override |
|
||||||
@NotNull |
|
||||||
public SVGDocument getBrokenLinkDocument(@NotNull Element e, @NotNull String url, @NotNull String message) { |
|
||||||
return createFallbackPlaceholder(); |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 开放访问权限 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public BridgeContext createBridgeContext(SVGOMDocument doc) { |
|
||||||
return super.createBridgeContext(doc); |
|
||||||
} |
|
||||||
} |
|
@ -1,101 +0,0 @@ |
|||||||
package com.fr.base.svg; |
|
||||||
|
|
||||||
import com.bulenkov.iconloader.util.UIUtil; |
|
||||||
import com.fr.log.FineLoggerFactory; |
|
||||||
import com.fr.stable.StableUtils; |
|
||||||
import com.fr.stable.os.OperatingSystem; |
|
||||||
import org.jetbrains.annotations.NotNull; |
|
||||||
|
|
||||||
import java.awt.GraphicsConfiguration; |
|
||||||
import java.awt.GraphicsDevice; |
|
||||||
import java.awt.GraphicsEnvironment; |
|
||||||
import java.lang.reflect.Method; |
|
||||||
import java.util.concurrent.atomic.AtomicReference; |
|
||||||
|
|
||||||
/** |
|
||||||
* 获取系统Scale相关的工具类 |
|
||||||
* @author Yvan |
|
||||||
* @version 10.0 |
|
||||||
* Created by Yvan on 2020/12/17 |
|
||||||
*/ |
|
||||||
public class SystemScaleUtils { |
|
||||||
|
|
||||||
private static final AtomicReference<Boolean> JRE_HIDPI = new AtomicReference<>(); |
|
||||||
|
|
||||||
private static final String HI_DPI = "hidpi"; |
|
||||||
|
|
||||||
/** |
|
||||||
* 判断是否支持高清 |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
public static boolean isJreHiDPIEnabled() { |
|
||||||
if (JRE_HIDPI.get() != null) { |
|
||||||
return JRE_HIDPI.get(); |
|
||||||
} |
|
||||||
if (OperatingSystem.isMacos()) { |
|
||||||
// 如果是mac os系统,直接返回true
|
|
||||||
return true; |
|
||||||
} |
|
||||||
if (OperatingSystem.isWindows() && StableUtils.getMajorJavaVersion() <= 8) { |
|
||||||
// 如果是jdk8 + Windows系统,直接返回false
|
|
||||||
return false; |
|
||||||
} |
|
||||||
synchronized (JRE_HIDPI) { |
|
||||||
if (JRE_HIDPI.get() != null) { |
|
||||||
return JRE_HIDPI.get(); |
|
||||||
} |
|
||||||
boolean result = false; |
|
||||||
if (getBooleanProperty(HI_DPI, true)) { |
|
||||||
try { |
|
||||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); |
|
||||||
Class<?> sunGraphicsEnvironmentClass = Class.forName("sun.java2d.SunGraphicsEnvironment"); |
|
||||||
if (sunGraphicsEnvironmentClass.isInstance(ge)) { |
|
||||||
try { |
|
||||||
Method method = sunGraphicsEnvironmentClass.getDeclaredMethod("isUIScaleEnabled"); |
|
||||||
method.setAccessible(true); |
|
||||||
result = (Boolean)method.invoke(ge); |
|
||||||
} |
|
||||||
catch (NoSuchMethodException e) { |
|
||||||
FineLoggerFactory.getLogger().error(e.getMessage()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
catch (Throwable ignore) { |
|
||||||
} |
|
||||||
} |
|
||||||
JRE_HIDPI.set(result); |
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static boolean getBooleanProperty(@NotNull final String key, final boolean defaultValue) { |
|
||||||
final String value = System.getProperty(key); |
|
||||||
return value == null ? defaultValue : Boolean.parseBoolean(value); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 获取系统Scale |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
public static float sysScale() { |
|
||||||
// 如果检测到是retina,直接返回2
|
|
||||||
if (UIUtil.isRetina()) { |
|
||||||
return 2.0f; |
|
||||||
} |
|
||||||
float scale = 1.0f; |
|
||||||
// 先判断是否支持高清,不支持代表此时是Windows + jdk8 的设计器,返回的scale值为1.0
|
|
||||||
if (isJreHiDPIEnabled()) { |
|
||||||
// 获取屏幕图形设备对象
|
|
||||||
GraphicsDevice graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); |
|
||||||
if (graphicsDevice != null) { |
|
||||||
// 获取图形配置对象
|
|
||||||
GraphicsConfiguration configuration = graphicsDevice.getDefaultConfiguration(); |
|
||||||
if (configuration != null && configuration.getDevice().getType() != GraphicsDevice.TYPE_PRINTER) { |
|
||||||
// 获取屏幕缩放率,Windows+jdk11环境下会得到用户设置的dpi值
|
|
||||||
scale = (float) configuration.getDefaultTransform().getScaleX(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return scale; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,12 @@ |
|||||||
|
package com.fr.common.exception; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author hades |
||||||
|
* @version 11.0 |
||||||
|
* Created by hades on 2021/12/7 |
||||||
|
*/ |
||||||
|
public interface ThrowableHandler { |
||||||
|
|
||||||
|
boolean process(Throwable e); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,193 @@ |
|||||||
|
package com.fr.design; |
||||||
|
|
||||||
|
import com.fr.concurrent.NamedThreadFactory; |
||||||
|
import com.fr.general.CloudCenter; |
||||||
|
import com.fr.general.CloudCenterConfig; |
||||||
|
import com.fr.general.http.HttpToolbox; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.stable.ProductConstants; |
||||||
|
import com.fr.stable.StableUtils; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import com.fr.stable.xml.XMLPrintWriter; |
||||||
|
import com.fr.stable.xml.XMLReaderHelper; |
||||||
|
import com.fr.stable.xml.XMLTools; |
||||||
|
import com.fr.stable.xml.XMLable; |
||||||
|
import com.fr.stable.xml.XMLableReader; |
||||||
|
import com.fr.third.javax.xml.stream.XMLStreamException; |
||||||
|
import com.fr.third.org.apache.commons.io.FileUtils; |
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileInputStream; |
||||||
|
import java.io.FileNotFoundException; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.concurrent.ExecutorService; |
||||||
|
import java.util.concurrent.Executors; |
||||||
|
|
||||||
|
/** |
||||||
|
* Created by kerry on 2021/10/22 |
||||||
|
*/ |
||||||
|
public class DesignerCloudURLManager implements XMLable { |
||||||
|
private static final String CLOUD_URL_INFO = "cloudUrl.info"; |
||||||
|
private static final String ROOT_XML_TAG = "CloudUrlInfoList"; |
||||||
|
private static final String CHILD_XML_TAG = "CloudUrlInfo"; |
||||||
|
private final Map<String, String> urlMap = new HashMap<>(); |
||||||
|
|
||||||
|
public static DesignerCloudURLManager getInstance() { |
||||||
|
return DesignerCloudURLManager.HOLDER.singleton; |
||||||
|
} |
||||||
|
|
||||||
|
private final ExecutorService executorService = Executors.newSingleThreadExecutor(new NamedThreadFactory("TestCloudConnectThread")); |
||||||
|
|
||||||
|
private volatile boolean testResult; |
||||||
|
|
||||||
|
|
||||||
|
private static class HOLDER { |
||||||
|
private static final DesignerCloudURLManager singleton = new DesignerCloudURLManager(); |
||||||
|
} |
||||||
|
|
||||||
|
private DesignerCloudURLManager() { |
||||||
|
loadURLXMLFile(); |
||||||
|
} |
||||||
|
|
||||||
|
public String acquireUrlByKind(String key) { |
||||||
|
String url = urlMap.getOrDefault(key, StringUtils.EMPTY); |
||||||
|
if (StringUtils.isEmpty(url)) { |
||||||
|
//本地缓存中为空时,直接从云中心获取,获取完成后异步更新本地缓存文件
|
||||||
|
String latestUrl = CloudCenter.getInstance().acquireConf(key, StringUtils.EMPTY); |
||||||
|
executorService.submit(() -> { |
||||||
|
updateURLXMLFile(key, latestUrl); |
||||||
|
}); |
||||||
|
return latestUrl; |
||||||
|
} |
||||||
|
//本地缓存不为空时,直接返回对应 url,同时异步更新
|
||||||
|
executorService.submit(() -> { |
||||||
|
String latestUrl = CloudCenter.getInstance().acquireConf(key, StringUtils.EMPTY); |
||||||
|
updateURLXMLFile(key, latestUrl); |
||||||
|
}); |
||||||
|
return url; |
||||||
|
} |
||||||
|
|
||||||
|
private synchronized void updateURLXMLFile(String key, String url) { |
||||||
|
if (StringUtils.isNotEmpty(url) && (!urlMap.containsKey(key) || !url.equals(urlMap.get(key)))) { |
||||||
|
urlMap.put(key, url); |
||||||
|
saveURLXMLFile(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public void testConnect() { |
||||||
|
executorService.submit(() -> { |
||||||
|
testResult = isOnline(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isConnected() { |
||||||
|
return testResult; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isOnline() { |
||||||
|
if (CloudCenterConfig.getInstance().isOnline()) { |
||||||
|
String ping = acquireUrlByKind("ping"); |
||||||
|
if (StringUtils.isNotEmpty(ping)) { |
||||||
|
try { |
||||||
|
return StringUtils.isEmpty(HttpToolbox.get(ping)); |
||||||
|
} catch (Exception ignore) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 加载本地 url 管理文件 |
||||||
|
*/ |
||||||
|
private void loadURLXMLFile() { |
||||||
|
if (!getInfoFile().exists()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
XMLableReader reader = null; |
||||||
|
try (InputStream in = new FileInputStream(getInfoFile())) { |
||||||
|
// XMLableReader 还是应该考虑实现 Closable 接口的,这样就能使用 try-with 语句了
|
||||||
|
reader = XMLReaderHelper.createXMLableReader(in, XMLPrintWriter.XML_ENCODER); |
||||||
|
if (reader == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
reader.readXMLObject(this); |
||||||
|
} catch (FileNotFoundException e) { |
||||||
|
// do nothing
|
||||||
|
} catch (XMLStreamException | IOException e) { |
||||||
|
FineLoggerFactory.getLogger().error(e.getMessage(), e); |
||||||
|
} finally { |
||||||
|
try { |
||||||
|
if (reader != null) { |
||||||
|
reader.close(); |
||||||
|
} |
||||||
|
} catch (XMLStreamException e) { |
||||||
|
FineLoggerFactory.getLogger().error(e.getMessage(), e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private File getInfoFile() { |
||||||
|
|
||||||
|
File file = new File(StableUtils.pathJoin(ProductConstants.getEnvHome(), CLOUD_URL_INFO)); |
||||||
|
try { |
||||||
|
if (!file.exists()) { |
||||||
|
file.createNewFile(); |
||||||
|
} |
||||||
|
} catch (Exception ex) { |
||||||
|
FineLoggerFactory.getLogger().error(ex.getMessage(), ex); |
||||||
|
} |
||||||
|
return file; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 保存到本地 URL 管理文件中,存放在 .Finereport110 中 |
||||||
|
*/ |
||||||
|
void saveURLXMLFile() { |
||||||
|
try { |
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
||||||
|
XMLTools.writeOutputStreamXML(this, out); |
||||||
|
out.flush(); |
||||||
|
out.close(); |
||||||
|
String fileContent = new String(out.toByteArray(), StandardCharsets.UTF_8); |
||||||
|
FileUtils.writeStringToFile(getInfoFile(), fileContent, StandardCharsets.UTF_8); |
||||||
|
} catch (Exception ex) { |
||||||
|
FineLoggerFactory.getLogger().error(ex.getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void readXML(XMLableReader reader) { |
||||||
|
String tagName = reader.getTagName(); |
||||||
|
if (tagName.equals(CHILD_XML_TAG)) { |
||||||
|
String key = reader.getAttrAsString("key", StringUtils.EMPTY); |
||||||
|
String value = reader.getAttrAsString("url", StringUtils.EMPTY); |
||||||
|
this.urlMap.put(key, value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeXML(XMLPrintWriter xmlPrintWriter) { |
||||||
|
xmlPrintWriter.startTAG(ROOT_XML_TAG); |
||||||
|
Iterator<Map.Entry<String, String>> iterable = urlMap.entrySet().iterator(); |
||||||
|
while (iterable.hasNext()) { |
||||||
|
Map.Entry<String, String> entry = iterable.next(); |
||||||
|
xmlPrintWriter.startTAG(CHILD_XML_TAG).attr("key", entry.getKey()).attr("url", entry.getValue()).end(); |
||||||
|
} |
||||||
|
xmlPrintWriter.end(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object clone() throws CloneNotSupportedException { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
package com.fr.design.actions.community; |
||||||
|
|
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.login.AbstractDesignerSSO; |
||||||
|
import com.fr.general.CloudCenter; |
||||||
|
|
||||||
|
public class StudyPlanAction extends AbstractDesignerSSO { |
||||||
|
public StudyPlanAction() { |
||||||
|
this.setName(Toolkit.i18nText("Fine-Design_Study_Plan")); |
||||||
|
this.setSmallIcon("/com/fr/design/images/bbs/studyPlan"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getJumpUrl() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind("bbs.studyPlan", "https://edu.fanruan.com/studypath/finereport"); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
package com.fr.design.actions.file; |
||||||
|
|
||||||
|
import com.fr.design.actions.UpdateAction; |
||||||
|
import com.fr.design.dialog.FineJOptionPane; |
||||||
|
import com.fr.design.file.FileOperations; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.mainframe.DesignerContext; |
||||||
|
import com.fr.design.mainframe.DesignerFrameFileDealerPane; |
||||||
|
import com.fr.design.utils.TemplateUtils; |
||||||
|
|
||||||
|
import javax.swing.JOptionPane; |
||||||
|
import java.awt.event.ActionEvent; |
||||||
|
|
||||||
|
import static javax.swing.JOptionPane.WARNING_MESSAGE; |
||||||
|
import static javax.swing.JOptionPane.YES_NO_OPTION; |
||||||
|
|
||||||
|
/* |
||||||
|
* 删除指定文件 |
||||||
|
*/ |
||||||
|
public class DelFileAction extends UpdateAction { |
||||||
|
|
||||||
|
public DelFileAction() { |
||||||
|
|
||||||
|
this.setName(Toolkit.i18nText("Fine-Design_Basic_Remove")); |
||||||
|
this.setSmallIcon("/com/fr/design/standard/remove/remove"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent evt) { |
||||||
|
FileOperations selectedOperation = DesignerFrameFileDealerPane.getInstance().getSelectedOperation(); |
||||||
|
if (!selectedOperation.access()) { |
||||||
|
FineJOptionPane.showMessageDialog(DesignerContext.getDesignerFrame(), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Template_Permission_Denied"), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Alert"), |
||||||
|
WARNING_MESSAGE); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (TemplateUtils.checkSelectedTemplateIsEditing()) { |
||||||
|
if (FineJOptionPane.showConfirmDialog(DesignerContext.getDesignerFrame(), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Template_Is_Editing"), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Alert"), |
||||||
|
YES_NO_OPTION) != JOptionPane.YES_OPTION) { |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
selectedOperation.deleteFile(); |
||||||
|
DesignerFrameFileDealerPane.getInstance().stateChange(); |
||||||
|
DesignerContext.getDesignerFrame().setTitle(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,163 @@ |
|||||||
|
package com.fr.design.actions.file; |
||||||
|
|
||||||
|
import com.fr.design.actions.UpdateAction; |
||||||
|
import com.fr.design.file.HistoryTemplateListCache; |
||||||
|
import com.fr.design.file.TemplateTreePane; |
||||||
|
import com.fr.design.gui.itree.filetree.TemplateFileTree; |
||||||
|
import com.fr.design.gui.itree.refreshabletree.ExpandMutableTreeNode; |
||||||
|
import com.fr.design.gui.itree.refreshabletree.RefreshableJTree; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.mainframe.JTemplate; |
||||||
|
import com.fr.design.mainframe.manager.search.TemplateTreeSearchManager; |
||||||
|
import com.fr.file.filetree.FileNode; |
||||||
|
import com.fr.general.ComparatorUtils; |
||||||
|
import com.fr.stable.CoreConstants; |
||||||
|
import com.fr.stable.StableUtils; |
||||||
|
import com.fr.stable.project.ProjectConstants; |
||||||
|
|
||||||
|
import javax.swing.tree.DefaultTreeModel; |
||||||
|
import javax.swing.tree.TreeNode; |
||||||
|
import javax.swing.tree.TreePath; |
||||||
|
import java.awt.event.ActionEvent; |
||||||
|
import java.io.File; |
||||||
|
|
||||||
|
/** |
||||||
|
* 模板定位功能 |
||||||
|
*/ |
||||||
|
public class LocateAction extends UpdateAction { |
||||||
|
|
||||||
|
public LocateAction() { |
||||||
|
this.setName(Toolkit.i18nText("Fine-Design_Basic_Locate")); |
||||||
|
this.setSmallIcon("/com/fr/design/standard/locate/locate"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
JTemplate<?, ?> current = HistoryTemplateListCache.getInstance().getCurrentEditingTemplate(); |
||||||
|
gotoEditingTemplateLeaf(current.getEditingFILE().getPath(), false); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 在左侧模板树定位到指定模板 |
||||||
|
* |
||||||
|
* @param locatedPath |
||||||
|
*/ |
||||||
|
public static void gotoEditingTemplateLeaf(String locatedPath) { |
||||||
|
gotoEditingTemplateLeaf(locatedPath, true); |
||||||
|
} |
||||||
|
|
||||||
|
private static void gotoEditingTemplateLeaf(String locatedPath, boolean needRefreshMode) { |
||||||
|
if (locatedPath == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (TemplateTreeSearchManager.getInstance().isInSearchMode()) { |
||||||
|
TemplateTreeSearchManager.getInstance().outOfSearchMode(); |
||||||
|
} |
||||||
|
|
||||||
|
DefaultTreeModel model = (DefaultTreeModel) getTemplateFileTree().getModel(); |
||||||
|
ExpandMutableTreeNode treeNode = (ExpandMutableTreeNode) model.getRoot(); |
||||||
|
if (needRefreshMode) { |
||||||
|
treeNode.removeAllChildren(); |
||||||
|
ExpandMutableTreeNode[] childTreeNodes = getTemplateFileTree().loadChildTreeNodes(treeNode); |
||||||
|
treeNode.addChildTreeNodes(childTreeNodes); |
||||||
|
model.reload(treeNode); |
||||||
|
} |
||||||
|
|
||||||
|
recursiveSelectPath(treeNode, locatedPath, model); |
||||||
|
TreePath selectedTreePath = getTemplateFileTree().getSelectionPath(); |
||||||
|
if (selectedTreePath != null) { |
||||||
|
getTemplateFileTree().scrollPathToVisible(selectedTreePath); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static TemplateFileTree getTemplateFileTree() { |
||||||
|
return TemplateTreePane.getInstance().getTemplateFileTree(); |
||||||
|
} |
||||||
|
|
||||||
|
private static void recursiveSelectPath(TreeNode treeNode, String currentPath, DefaultTreeModel m_model) { |
||||||
|
for (int i = 0, len = treeNode.getChildCount(); i < len; i++) { |
||||||
|
TreeNode node = treeNode.getChildAt(i); |
||||||
|
// 取出当前的childTreeNode,并append到searchingPath后面
|
||||||
|
ExpandMutableTreeNode childTreeNode = (ExpandMutableTreeNode) node; |
||||||
|
if (selectFilePath(childTreeNode, ProjectConstants.REPORTLETS_NAME, currentPath, m_model)) { |
||||||
|
break; |
||||||
|
} |
||||||
|
if (!node.isLeaf()) { |
||||||
|
for (int j = 0; j < node.getChildCount(); j++) { |
||||||
|
recursiveSelectPath(node.getChildAt(j), currentPath, m_model); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* 在currentTreeNode下找寻filePath |
||||||
|
* |
||||||
|
* prefix + currentTreeNode.getName() = currentTreeNode所对应的Path |
||||||
|
* |
||||||
|
* 返回currentTreeNode下是否找到了filePath |
||||||
|
*/ |
||||||
|
private static boolean selectFilePath(ExpandMutableTreeNode currentTreeNode, String prefix, String filePath, DefaultTreeModel model) { |
||||||
|
Object userObj = currentTreeNode.getUserObject(); |
||||||
|
if (!(userObj instanceof FileNode)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
FileNode fileNode = (FileNode) userObj; |
||||||
|
String nodePath = fileNode.getName(); |
||||||
|
String currentTreePath = StableUtils.pathJoin(prefix, nodePath); |
||||||
|
boolean result = false; |
||||||
|
|
||||||
|
// 如果equals,说明找到了,不必再找下去了
|
||||||
|
if (ComparatorUtils.equals(new File(currentTreePath), new File(filePath))) { |
||||||
|
getTemplateFileTree().setSelectionPath(new TreePath(model.getPathToRoot(currentTreeNode))); |
||||||
|
result = true; |
||||||
|
} |
||||||
|
// 如果当前路径是currentFilePath的ParentFile,则expandTreeNode,并继续往下找
|
||||||
|
else if (isParentFile(currentTreePath, filePath)) { |
||||||
|
loadPendingChildTreeNode(currentTreeNode); |
||||||
|
prefix = currentTreePath + CoreConstants.SEPARATOR; |
||||||
|
for (int i = 0, len = currentTreeNode.getChildCount(); i < len; i++) { |
||||||
|
ExpandMutableTreeNode childTreeNode = (ExpandMutableTreeNode) currentTreeNode.getChildAt(i); |
||||||
|
|
||||||
|
if (selectFilePath(childTreeNode, prefix, filePath, model)) { |
||||||
|
result = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
protected static void loadPendingChildTreeNode(ExpandMutableTreeNode currentTreeNode) { |
||||||
|
if (currentTreeNode.isLeaf()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// 判断第一个孩子节点.UserObject是不是PENDING,如果是PENDING的话,需要重新加载这个TreeNode
|
||||||
|
ExpandMutableTreeNode flag = (ExpandMutableTreeNode) currentTreeNode.getFirstChild(); |
||||||
|
if (flag == null || !flag.getUserObject().toString().equals(RefreshableJTree.PENDING.toString())) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// 删除所有的节点.
|
||||||
|
currentTreeNode.removeAllChildren(); |
||||||
|
|
||||||
|
ExpandMutableTreeNode[] children = getTemplateFileTree().loadChildTreeNodes(currentTreeNode); |
||||||
|
for (ExpandMutableTreeNode c : children) { |
||||||
|
currentTreeNode.add(c); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isParentFile(String var0, String var1) { |
||||||
|
File var2 = new File(var0); |
||||||
|
File var3 = new File(var1); |
||||||
|
|
||||||
|
while (!ComparatorUtils.equals(var2, var3)) { |
||||||
|
var3 = var3.getParentFile(); |
||||||
|
if (var3 == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,346 @@ |
|||||||
|
package com.fr.design.actions.file; |
||||||
|
|
||||||
|
import com.fr.base.BaseUtils; |
||||||
|
import com.fr.chartx.TwoTuple; |
||||||
|
import com.fr.design.DesignerEnvManager; |
||||||
|
import com.fr.design.actions.UpdateAction; |
||||||
|
import com.fr.design.dialog.FineJOptionPane; |
||||||
|
import com.fr.design.file.FileOperations; |
||||||
|
import com.fr.design.file.HistoryTemplateListCache; |
||||||
|
import com.fr.design.file.MultiTemplateTabPane; |
||||||
|
import com.fr.design.gui.ibutton.UIButton; |
||||||
|
import com.fr.design.gui.ilable.UILabel; |
||||||
|
import com.fr.design.gui.itextfield.UITextField; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.layout.TableLayout; |
||||||
|
import com.fr.design.layout.TableLayoutHelper; |
||||||
|
import com.fr.design.mainframe.DesignerContext; |
||||||
|
import com.fr.design.mainframe.DesignerFrameFileDealerPane; |
||||||
|
import com.fr.design.mainframe.vcs.common.VcsHelper; |
||||||
|
import com.fr.design.utils.TemplateUtils; |
||||||
|
import com.fr.design.utils.gui.GUICoreUtils; |
||||||
|
import com.fr.event.EventDispatcher; |
||||||
|
import com.fr.file.FileNodeFILE; |
||||||
|
import com.fr.file.filetree.FileNode; |
||||||
|
import com.fr.general.ComparatorUtils; |
||||||
|
import com.fr.stable.CoreConstants; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import com.fr.stable.project.ProjectConstants; |
||||||
|
import com.fr.third.org.apache.commons.io.FilenameUtils; |
||||||
|
|
||||||
|
import javax.swing.BorderFactory; |
||||||
|
import javax.swing.JDialog; |
||||||
|
import javax.swing.JPanel; |
||||||
|
import javax.swing.JOptionPane; |
||||||
|
import javax.swing.SwingConstants; |
||||||
|
import javax.swing.event.DocumentEvent; |
||||||
|
import javax.swing.event.DocumentListener; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Component; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.FlowLayout; |
||||||
|
import java.awt.event.ActionEvent; |
||||||
|
import java.awt.event.ActionListener; |
||||||
|
import java.awt.event.KeyAdapter; |
||||||
|
import java.awt.event.KeyEvent; |
||||||
|
import java.awt.event.KeyListener; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import static javax.swing.JOptionPane.DEFAULT_OPTION; |
||||||
|
import static javax.swing.JOptionPane.ERROR_MESSAGE; |
||||||
|
import static javax.swing.JOptionPane.WARNING_MESSAGE; |
||||||
|
import static javax.swing.JOptionPane.YES_NO_OPTION; |
||||||
|
|
||||||
|
/** |
||||||
|
* 模板/目录重命名操作 |
||||||
|
*/ |
||||||
|
public class RenameAction extends UpdateAction { |
||||||
|
|
||||||
|
private FileOperations selectedOperation; |
||||||
|
|
||||||
|
public RenameAction() { |
||||||
|
|
||||||
|
this.setName(Toolkit.i18nText("Fine-Design_Basic_Rename")); |
||||||
|
this.setSmallIcon("/com/fr/design/standard/rename/rename"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent evt) { |
||||||
|
selectedOperation = DesignerFrameFileDealerPane.getInstance().getSelectedOperation(); |
||||||
|
if (!selectedOperation.access()) { |
||||||
|
FineJOptionPane.showMessageDialog(DesignerContext.getDesignerFrame(), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Template_Permission_Denied"), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Alert"), |
||||||
|
WARNING_MESSAGE); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
FileNode node = selectedOperation.getFileNode(); |
||||||
|
String lock = node.getLock(); |
||||||
|
if (lock != null && !lock.equals(node.getUserID())) { |
||||||
|
// 提醒被锁定模板无法重命名
|
||||||
|
FineJOptionPane.showMessageDialog(DesignerContext.getDesignerFrame(), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Unable_Rename_Locked_File"), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Alert"), |
||||||
|
WARNING_MESSAGE); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
new FileRenameDialog(node); |
||||||
|
MultiTemplateTabPane.getInstance().repaint(); |
||||||
|
DesignerFrameFileDealerPane.getInstance().stateChange(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 重命名对话框 |
||||||
|
* 支持快捷键Enter,ESC |
||||||
|
*/ |
||||||
|
private class FileRenameDialog extends JDialog { |
||||||
|
|
||||||
|
private UITextField nameField; |
||||||
|
|
||||||
|
private UILabel warnLabel; |
||||||
|
|
||||||
|
private UIButton confirmButton; |
||||||
|
|
||||||
|
/** |
||||||
|
* 操作的节点 |
||||||
|
*/ |
||||||
|
private FileNodeFILE fnf; |
||||||
|
|
||||||
|
private KeyListener keyListener = new KeyAdapter() { |
||||||
|
@Override |
||||||
|
public void keyPressed(KeyEvent e) { |
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { |
||||||
|
dispose(); |
||||||
|
} else if (e.getKeyCode() == KeyEvent.VK_ENTER) { |
||||||
|
if (confirmButton.isEnabled()) { |
||||||
|
confirmClose(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
private FileRenameDialog(FileNode node) { |
||||||
|
super(DesignerContext.getDesignerFrame(), true); |
||||||
|
if (node == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
fnf = new FileNodeFILE(node); |
||||||
|
String oldName = fnf.getName(); |
||||||
|
String suffix = fnf.isDirectory() ? StringUtils.EMPTY : oldName.substring(oldName.lastIndexOf(CoreConstants.DOT), oldName.length()); |
||||||
|
oldName = StringUtils.replaceLast(oldName, suffix, StringUtils.EMPTY); |
||||||
|
|
||||||
|
initPane(oldName); |
||||||
|
} |
||||||
|
|
||||||
|
private void initPane(String oldName) { |
||||||
|
this.setLayout(new BorderLayout()); |
||||||
|
|
||||||
|
// 输入框前提示
|
||||||
|
UILabel newNameLabel = new UILabel(Toolkit.i18nText( |
||||||
|
fnf.isDirectory() ? |
||||||
|
"Fine-Design_Basic_Enter_New_Folder_Name" : "Fine-Design_Basic_Enter_New_File_Name") |
||||||
|
); |
||||||
|
newNameLabel.setHorizontalAlignment(SwingConstants.RIGHT); |
||||||
|
newNameLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10)); |
||||||
|
//newNameLabel.setPreferredSize(new Dimension(118, 15));
|
||||||
|
|
||||||
|
// 重命名输入框
|
||||||
|
nameField = new UITextField(oldName); |
||||||
|
nameField.getDocument().addDocumentListener(new DocumentListener() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void changedUpdate(DocumentEvent e) { |
||||||
|
validInput(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void insertUpdate(DocumentEvent e) { |
||||||
|
validInput(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void removeUpdate(DocumentEvent e) { |
||||||
|
validInput(); |
||||||
|
} |
||||||
|
}); |
||||||
|
nameField.selectAll(); |
||||||
|
nameField.setPreferredSize(new Dimension(170, 20)); |
||||||
|
|
||||||
|
JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 5)); |
||||||
|
topPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 0, 15)); |
||||||
|
topPanel.add(newNameLabel); |
||||||
|
topPanel.add(nameField); |
||||||
|
|
||||||
|
// 增加enter以及esc快捷键的支持
|
||||||
|
nameField.addKeyListener(keyListener); |
||||||
|
// 重名提示
|
||||||
|
warnLabel = new UILabel(); |
||||||
|
warnLabel.setPreferredSize(new Dimension(300, 50)); |
||||||
|
warnLabel.setHorizontalAlignment(SwingConstants.LEFT); |
||||||
|
warnLabel.setVerticalAlignment(SwingConstants.TOP); |
||||||
|
warnLabel.setForeground(Color.RED); |
||||||
|
warnLabel.setVisible(false); |
||||||
|
|
||||||
|
JPanel midPanel = new JPanel(new BorderLayout()); |
||||||
|
midPanel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 15)); |
||||||
|
midPanel.add(warnLabel, BorderLayout.WEST); |
||||||
|
|
||||||
|
// 确认按钮
|
||||||
|
confirmButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Confirm")); |
||||||
|
confirmButton.addActionListener(new ActionListener() { |
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
confirmClose(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// 取消按钮
|
||||||
|
UIButton cancelButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Cancel")); |
||||||
|
|
||||||
|
cancelButton.addActionListener(new ActionListener() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
dispose(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
JPanel buttonsPane = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0)); |
||||||
|
buttonsPane.setBorder(BorderFactory.createEmptyBorder(10, 15, 10, 10)); |
||||||
|
buttonsPane.add(confirmButton); |
||||||
|
buttonsPane.add(cancelButton); |
||||||
|
|
||||||
|
this.add( |
||||||
|
TableLayoutHelper.createTableLayoutPane( |
||||||
|
new Component[][]{ |
||||||
|
new Component[]{topPanel}, |
||||||
|
new Component[]{midPanel}, |
||||||
|
new Component[]{buttonsPane} |
||||||
|
}, |
||||||
|
new double[]{TableLayout.FILL, TableLayout.PREFERRED, TableLayout.PREFERRED}, |
||||||
|
new double[]{TableLayout.FILL} |
||||||
|
), |
||||||
|
BorderLayout.CENTER); |
||||||
|
|
||||||
|
this.setSize(340, 200); |
||||||
|
this.setTitle(Toolkit.i18nText("Fine-Design_Basic_Rename")); |
||||||
|
this.setResizable(false); |
||||||
|
this.setIconImage(BaseUtils.readImage("/com/fr/base/images/oem/logo.png")); |
||||||
|
this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); |
||||||
|
GUICoreUtils.centerWindow(this); |
||||||
|
this.setVisible(true); |
||||||
|
} |
||||||
|
|
||||||
|
private void confirmClose() { |
||||||
|
|
||||||
|
if (TemplateUtils.checkSelectedTemplateIsEditing()) { |
||||||
|
if (FineJOptionPane.showConfirmDialog(this, |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Template_Is_Editing"), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Alert"), |
||||||
|
YES_NO_OPTION) != JOptionPane.YES_OPTION) { |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
String userInput = nameField.getText().trim(); |
||||||
|
|
||||||
|
String path = FilenameUtils.standard(fnf.getPath()); |
||||||
|
|
||||||
|
String oldName = fnf.getName(); |
||||||
|
String suffix = fnf.isDirectory() ? StringUtils.EMPTY : oldName.substring(oldName.lastIndexOf(CoreConstants.DOT), oldName.length()); |
||||||
|
oldName = StringUtils.replaceLast(oldName, suffix, StringUtils.EMPTY); |
||||||
|
|
||||||
|
// 输入为空或者没有修改
|
||||||
|
if (ComparatorUtils.equals(userInput, oldName)) { |
||||||
|
this.dispose(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
String parentPath = FilenameUtils.standard(fnf.getParent().getPath()); |
||||||
|
|
||||||
|
// 简单执行old new 替换是不可行的,例如 /abc/abc/abc/abc/
|
||||||
|
String newPath = parentPath + CoreConstants.SEPARATOR + userInput + suffix; |
||||||
|
this.dispose(); |
||||||
|
|
||||||
|
//模版重命名
|
||||||
|
boolean success = selectedOperation.rename(fnf, path, newPath); |
||||||
|
|
||||||
|
if (success) { |
||||||
|
afterRename(path, newPath); |
||||||
|
} else { |
||||||
|
FineJOptionPane.showConfirmDialog(DesignerContext.getDesignerFrame(), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Rename_Failure"), |
||||||
|
Toolkit.i18nText("Fine-Design_Basic_Error"), |
||||||
|
DEFAULT_OPTION, |
||||||
|
ERROR_MESSAGE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void afterRename(String path, String newPath) { |
||||||
|
EventDispatcher.fire(DesignerFrameFileDealerPane.TEMPLATE_RENAME, new TwoTuple<>(path, newPath)); |
||||||
|
HistoryTemplateListCache.getInstance().rename(fnf, path, newPath); |
||||||
|
DesignerEnvManager.getEnvManager().replaceRecentOpenedFilePath(fnf.isDirectory(), path, newPath); |
||||||
|
selectedOperation.refreshParent(); |
||||||
|
if (!fnf.isDirectory()) { |
||||||
|
//版本控制:未打开模板时重命名,是个纯文件操作
|
||||||
|
//不借助JTemplate的事件触发机制实现(需要新创建JTemplate,并添加监听,不确定会不会有问题)
|
||||||
|
path = path.replaceFirst(ProjectConstants.REPORTLETS_NAME, StringUtils.EMPTY); |
||||||
|
VcsHelper.getInstance().moveVcs(path, newPath.replaceFirst(ProjectConstants.REPORTLETS_NAME, StringUtils.EMPTY)); |
||||||
|
} |
||||||
|
DesignerContext.getDesignerFrame().setTitle(); |
||||||
|
LocateAction.gotoEditingTemplateLeaf(newPath); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private void validInput() { |
||||||
|
|
||||||
|
String userInput = nameField.getText().trim(); |
||||||
|
|
||||||
|
String oldName = fnf.getName(); |
||||||
|
String suffix = fnf.isDirectory() ? StringUtils.EMPTY : oldName.substring(oldName.lastIndexOf(CoreConstants.DOT), oldName.length()); |
||||||
|
oldName = oldName.replaceAll(suffix, StringUtils.EMPTY); |
||||||
|
|
||||||
|
if (StringUtils.isEmpty(userInput)) { |
||||||
|
confirmButton.setEnabled(false); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (ComparatorUtils.equals(userInput, oldName)) { |
||||||
|
warnLabel.setVisible(false); |
||||||
|
confirmButton.setEnabled(true); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
String errorMsg = doCheck(userInput, suffix, fnf.isDirectory()); |
||||||
|
if (StringUtils.isNotEmpty(errorMsg)) { |
||||||
|
nameField.selectAll(); |
||||||
|
// 如果文件名已存在,则灰掉确认按钮
|
||||||
|
warnLabel.setText(errorMsg); |
||||||
|
warnLabel.setVisible(true); |
||||||
|
confirmButton.setEnabled(false); |
||||||
|
} else { |
||||||
|
warnLabel.setVisible(false); |
||||||
|
confirmButton.setEnabled(true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private String doCheck (String userInput, String suffix, boolean isDirectory) { |
||||||
|
String errorMsg = StringUtils.EMPTY; |
||||||
|
if (selectedOperation.duplicated(userInput, suffix, false)) { |
||||||
|
errorMsg = Toolkit.i18nText(isDirectory ? |
||||||
|
"Fine-Design_Basic_Folder_Name_Duplicate" : |
||||||
|
"Fine-Design_Basic_Template_File_Name_Duplicate", |
||||||
|
userInput); |
||||||
|
} |
||||||
|
if (!Pattern.compile(DesignerFrameFileDealerPane.FILE_NAME_LIMIT).matcher(userInput).matches()) { |
||||||
|
errorMsg = Toolkit.i18nText("Fine-Design_Basic_Template_Name_Illegal"); |
||||||
|
} |
||||||
|
return errorMsg; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,200 @@ |
|||||||
|
package com.fr.design.actions.help.alphafine; |
||||||
|
|
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.general.CloudCenter; |
||||||
|
import com.fr.json.JSONArray; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* 云端变量统一管理 |
||||||
|
* |
||||||
|
* @author Link |
||||||
|
* @version 11.0 |
||||||
|
* Created by Link on 2022/9/21 |
||||||
|
*/ |
||||||
|
public class AlphaFineCloudConstants { |
||||||
|
|
||||||
|
private static final String PLUGIN_SEARCH_API = "plugin.searchAPI"; |
||||||
|
private static final String PLUGIN_ALL_SEARCH_API = "plugin.all.searchAPI"; |
||||||
|
private static final String AF_PLUGIN_INFO = "af.pluginInfo"; |
||||||
|
private static final String AF_REUSE_INFO = "af.reuseInfo"; |
||||||
|
private static final String AF_DOC_VIEW = "af.doc_view"; |
||||||
|
private static final String AF_DOC_SEARCH = "af.doc_search"; |
||||||
|
private static final String AF_DOC_INFO = "af.doc_info"; |
||||||
|
private static final String AF_PLUGIN_IMAGE = "af.plugin_image"; |
||||||
|
private static final String AF_RECORD = "af.record"; |
||||||
|
private static final String AF_CLOUD_SEARCH = "af.cloud_search"; |
||||||
|
private static final String AF_SIMILAR_SEARCH = "af.similar_search"; |
||||||
|
private static final String AF_ADVICE_SEARCH = "af.advice_search"; |
||||||
|
private static final String AF_HOT_SEARCH = "af.hot_search"; |
||||||
|
private static final String AF_GO_FORUM = "af.go_fourm"; |
||||||
|
private static final String AF_GO_WEB = "af.go_web"; |
||||||
|
private static final String AF_PREVIEW = "af.preview"; |
||||||
|
private static final String AF_CID_NEW = "af.cid.new"; |
||||||
|
private static final String AF_CID_USER_GROUP_INFO = "af.cid.user.group.info"; |
||||||
|
private static final String AF_HELP_QUICK_START = "af.help.quick.start"; |
||||||
|
private static final String AF_HELP_REPORT_LEARNING_PATH = "af.help.report.learning.path"; |
||||||
|
private static final String AF_HELP_PARAM_LEARNING_PATH = "af.help.param.learning.path"; |
||||||
|
private static final String AF_HELP_FILL_LEARNING_PATH = "af.help.fill.learning.path"; |
||||||
|
private static final String AF_HELP_API_SUMMARY = "af.help.api.summary"; |
||||||
|
private static final String AF_HELP_MONTHLY_DOCUMENT = "af.help.monthly.document"; |
||||||
|
private static final String AF_RECOMMEND = "af.recommend"; |
||||||
|
|
||||||
|
private static final String LINK_NAME = "name"; |
||||||
|
private static final String LINK_URL = "link"; |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取插件搜索api |
||||||
|
*/ |
||||||
|
public static String getPluginSearchUrl() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(PLUGIN_SEARCH_API); |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* 帆软市场里全部插件api |
||||||
|
*/ |
||||||
|
public static String getSearchAllPluginUrl() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(PLUGIN_ALL_SEARCH_API); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取插件信息api |
||||||
|
*/ |
||||||
|
public static String getPluginUrl() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_PLUGIN_INFO); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取组件信息api |
||||||
|
*/ |
||||||
|
public static String getReuseUrl() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_REUSE_INFO); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取帮助文档url |
||||||
|
*/ |
||||||
|
public static String getDocumentDocUrl() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_DOC_VIEW); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 帮助文档搜索api |
||||||
|
*/ |
||||||
|
public static String getDocumentSearchUrl() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_DOC_SEARCH); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 帮助文档信息api |
||||||
|
*/ |
||||||
|
public static String getDocumentInformationUrl() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_DOC_INFO); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 插件图片api |
||||||
|
*/ |
||||||
|
public static String getPluginImageUrl() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_PLUGIN_IMAGE); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取云端接口,用于上传alphafine搜索记录 |
||||||
|
*/ |
||||||
|
public static String getCloudServerUrl() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_RECORD); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取搜索api,输入搜索词,返回fr的相关功能 |
||||||
|
*/ |
||||||
|
public static String getSearchApi() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_CLOUD_SEARCH); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取模糊搜索api前缀,输入搜索词,返回alphaFine相关内容,插件,文档,功能等 |
||||||
|
*/ |
||||||
|
public static String getSimilarSearchUrlPrefix() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_SIMILAR_SEARCH); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 补全建议搜索结果 api,与AF_SIMILAR_SEARCH接口类似,但是返回的信息更全 |
||||||
|
*/ |
||||||
|
public static String getComplementAdviceSearchUrlPrefix() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_ADVICE_SEARCH); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取热门问题 |
||||||
|
*/ |
||||||
|
public static String getAlphaHotSearch() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_HOT_SEARCH); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 跳转论坛url |
||||||
|
*/ |
||||||
|
public static String getAlphaGoToForum() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_GO_FORUM); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 推荐搜索api,输入搜索词,返回猜你想搜的内容(html格式) |
||||||
|
*/ |
||||||
|
public static String getAlphaGoToWeb() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_GO_WEB); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 帆软智能客服页面url |
||||||
|
*/ |
||||||
|
public static String getAlphaPreview() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_PREVIEW); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* cid系统的产品动态api |
||||||
|
*/ |
||||||
|
public static String getAlphaCid() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_CID_NEW); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* cid系统的 用户组信息api |
||||||
|
*/ |
||||||
|
public static String getAlphaCidUserGroupInfo() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_CID_USER_GROUP_INFO); |
||||||
|
} |
||||||
|
|
||||||
|
private static String getDefaultRecommend() { |
||||||
|
String[][] links = new String[][]{ |
||||||
|
{Toolkit.i18nText("Fine-Design_Report_AlphaFine_Doc_Quick_Start"), CloudCenter.getInstance().acquireUrlByKind(AF_HELP_QUICK_START)}, |
||||||
|
{Toolkit.i18nText("Fine-Design_Report_AlphaFine_Doc_Report_Learning"), CloudCenter.getInstance().acquireUrlByKind(AF_HELP_REPORT_LEARNING_PATH)}, |
||||||
|
{Toolkit.i18nText("Fine-Design_Report_AlphaFine_Doc_Parameter_Learning"), CloudCenter.getInstance().acquireUrlByKind(AF_HELP_PARAM_LEARNING_PATH)}, |
||||||
|
{Toolkit.i18nText("Fine-Design_Report_AlphaFine_Doc_Fill_Learning"), CloudCenter.getInstance().acquireUrlByKind(AF_HELP_FILL_LEARNING_PATH)}, |
||||||
|
{Toolkit.i18nText("Fine-Design_Report_AlphaFine_Doc_Api_Summary"), CloudCenter.getInstance().acquireUrlByKind(AF_HELP_API_SUMMARY)}, |
||||||
|
{Toolkit.i18nText("Fine-Design_Report_AlphaFine_Doc_Monthly_Document"), CloudCenter.getInstance().acquireUrlByKind(AF_HELP_MONTHLY_DOCUMENT)} |
||||||
|
}; |
||||||
|
JSONArray jsonArray = new JSONArray(); |
||||||
|
for (String[] link : links) { |
||||||
|
Map<String, String> map = new HashMap<>(); |
||||||
|
map.put(LINK_NAME, link[0]); |
||||||
|
map.put(LINK_URL, link[1]); |
||||||
|
jsonArray.put(map); |
||||||
|
} |
||||||
|
|
||||||
|
return jsonArray.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取默认推荐帮助文档url |
||||||
|
*/ |
||||||
|
public static String getAlphaHelpRecommend() { |
||||||
|
return CloudCenter.getInstance().acquireUrlByKind(AF_RECOMMEND, getDefaultRecommend()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
package com.fr.design.actions.help.alphafine; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author hades |
||||||
|
* @version 11.0 |
||||||
|
* Created by hades on 2022/4/26 |
||||||
|
*/ |
||||||
|
public class AlphaFineShortCutUtil { |
||||||
|
|
||||||
|
private static final String TYPE = "pressed"; |
||||||
|
private static final String DISPLAY_TYPE = "+"; |
||||||
|
private static final String BACK_SLASH = "BACK_SLASH"; |
||||||
|
private static final String DISPLAY_BACK_SLASH = "\\"; |
||||||
|
private static final String SLASH = "SLASH"; |
||||||
|
private static final String DISPLAY_SLASH = "/"; |
||||||
|
private static final String CONTROL = "CONTROL"; |
||||||
|
private static final String DISPLAY_CONTROL = "ctrl"; |
||||||
|
private static final String OPEN_BRACKET = "OPEN_BRACKET"; |
||||||
|
private static final String DISPLAY_OPEN_BRACKET = "{"; |
||||||
|
private static final String CLOSE_BRACKET = "CLOSE_BRACKET"; |
||||||
|
private static final String DISPLAY_CLOSE_BRACKET = "}"; |
||||||
|
private static final String COMMA = "COMMA"; |
||||||
|
private static final String DISPLAY_COMMA = ","; |
||||||
|
private static final String PERIOD = "PERIOD"; |
||||||
|
private static final String DISPLAY_PERIOD = "."; |
||||||
|
private static final String SEMICOLON = "SEMICOLON"; |
||||||
|
private static final String DISPLAY_SEMICOLON = ";"; |
||||||
|
private static final String QUOTE = "QUOTE"; |
||||||
|
private static final String DISPLAY_QUOTE = "'"; |
||||||
|
private static final String EQUALS = "EQUALS"; |
||||||
|
private static final String DISPLAY_EQUALS = "+"; |
||||||
|
private static final String MINUS = "MINUS"; |
||||||
|
private static final String DISPLAY_MINUS = "-"; |
||||||
|
private static final String COMMAND = "META"; |
||||||
|
private static final String SMALL_COMMAND = "meta"; |
||||||
|
private static final String DISPLAY_COMMAND = "\u2318"; |
||||||
|
|
||||||
|
public static String getDisplayShortCut(String shortCut) { |
||||||
|
return shortCut.replace(TYPE, DISPLAY_TYPE).replace(BACK_SLASH, DISPLAY_BACK_SLASH).replace(SLASH, DISPLAY_SLASH) |
||||||
|
.replace(CONTROL, DISPLAY_CONTROL).replace(OPEN_BRACKET, DISPLAY_OPEN_BRACKET).replace(CLOSE_BRACKET, DISPLAY_CLOSE_BRACKET) |
||||||
|
.replace(COMMA, DISPLAY_COMMA).replace(PERIOD, DISPLAY_PERIOD).replace(SEMICOLON, DISPLAY_SEMICOLON).replace(QUOTE, DISPLAY_QUOTE) |
||||||
|
.replace(EQUALS, DISPLAY_EQUALS).replace(MINUS, DISPLAY_MINUS).replace(COMMAND, DISPLAY_COMMAND).replace(SMALL_COMMAND, DISPLAY_COMMAND); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
package com.fr.design.actions.help.alphafine; |
||||||
|
|
||||||
|
import java.util.Stack; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author hades |
||||||
|
* @version 11.0 |
||||||
|
* Created by hades on 2022/4/23 |
||||||
|
*/ |
||||||
|
public class SizedStack<T> extends Stack<T> { |
||||||
|
|
||||||
|
private final int maxSize; |
||||||
|
|
||||||
|
public SizedStack(int size) { |
||||||
|
super(); |
||||||
|
this.maxSize = size; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public T push(T object) { |
||||||
|
while (this.size() >= maxSize) { |
||||||
|
this.remove(0); |
||||||
|
} |
||||||
|
// 不重复
|
||||||
|
if (this.contains(object)) { |
||||||
|
return object; |
||||||
|
} |
||||||
|
return super.push(object); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,229 @@ |
|||||||
|
package com.fr.design.actions.help.alphafine.component; |
||||||
|
|
||||||
|
import com.fr.base.svg.IconUtils; |
||||||
|
import com.fr.design.actions.help.alphafine.AlphaFineConfigPane; |
||||||
|
import com.fr.design.gui.ibutton.UIButton; |
||||||
|
import com.fr.design.gui.icheckbox.UICheckBox; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
|
||||||
|
import javax.swing.BorderFactory; |
||||||
|
import javax.swing.JPanel; |
||||||
|
import javax.swing.SwingUtilities; |
||||||
|
import javax.swing.plaf.PanelUI; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Component; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.FlowLayout; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
/** |
||||||
|
* alphafine设置 - 搜索范围 - 自定义排序 - 弹出面板 |
||||||
|
* |
||||||
|
* @author Link |
||||||
|
* @version 11.0 |
||||||
|
* Created by Link on 2022/9/18 |
||||||
|
*/ |
||||||
|
public class CustomSortPane extends JPanel { |
||||||
|
|
||||||
|
|
||||||
|
private static final int WIDTH = 147; |
||||||
|
private static final int ITEM_HEIGHT = 23; |
||||||
|
private static final int GAP = 1; |
||||||
|
private static final Color BACKGROUND_COLOR = new Color(0xdadadd); |
||||||
|
|
||||||
|
private UIButton top; |
||||||
|
private UIButton bottom; |
||||||
|
private UIButton up; |
||||||
|
private UIButton down; |
||||||
|
private JPanel toolbarPane; |
||||||
|
private MenuLabelPane sortItemPane; |
||||||
|
private List<UICheckBox> sortItems; |
||||||
|
private MenuLabel selectedLabel; |
||||||
|
private AlphaFineConfigPane parentPane; |
||||||
|
|
||||||
|
public CustomSortPane(List<UICheckBox> items, AlphaFineConfigPane parentPane) { |
||||||
|
this.sortItems = items; |
||||||
|
this.parentPane = parentPane; |
||||||
|
setLayout(new BorderLayout(GAP, GAP)); |
||||||
|
int height = (sortItems.size() + 1) * (ITEM_HEIGHT + GAP) + GAP; |
||||||
|
setPreferredSize(new Dimension(WIDTH, height)); |
||||||
|
initComponent(); |
||||||
|
add(toolbarPane, BorderLayout.NORTH); |
||||||
|
add(sortItemPane, BorderLayout.CENTER); |
||||||
|
revalidate(); |
||||||
|
this.setVisible(true); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setUI(PanelUI ui) { |
||||||
|
super.setUI(ui); |
||||||
|
setBackground(BACKGROUND_COLOR); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private void initComponent() { |
||||||
|
createToolbarPane(); |
||||||
|
createSortItemPane(); |
||||||
|
} |
||||||
|
|
||||||
|
private void createToolbarPane() { |
||||||
|
top = new UIButton(IconUtils.readIcon("com/fr/design/mainframe/alphafine/images/top.svg"), false); |
||||||
|
bottom = new UIButton(IconUtils.readIcon("com/fr/design/mainframe/alphafine/images/bottom.svg"), false); |
||||||
|
up = new UIButton(IconUtils.readIcon("com/fr/design/mainframe/alphafine/images/up.svg"), false); |
||||||
|
down = new UIButton(IconUtils.readIcon("com/fr/design/mainframe/alphafine/images/down.svg"), false); |
||||||
|
top.setDisabledIcon(IconUtils.readIcon("com/fr/design/mainframe/alphafine/images/top_disable.svg")); |
||||||
|
bottom.setDisabledIcon(IconUtils.readIcon("com/fr/design/mainframe/alphafine/images/bottom_disable.svg")); |
||||||
|
up.setDisabledIcon(IconUtils.readIcon("com/fr/design/mainframe/alphafine/images/up_disable.svg")); |
||||||
|
down.setDisabledIcon(IconUtils.readIcon("com/fr/design/mainframe/alphafine/images/down_disable.svg")); |
||||||
|
top.addActionListener(e -> { |
||||||
|
SwingUtilities.invokeLater(() -> { |
||||||
|
sortItemPane.setComponentZOrder(selectedLabel, 0); |
||||||
|
setToolbarEnable(sortItemPane.getComponentZOrder(selectedLabel), sortItemPane.getComponentCount()); |
||||||
|
CustomSortPane.this.revalidate(); |
||||||
|
CustomSortPane.this.repaint(); |
||||||
|
refreshCurrentOrder(); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
bottom.addActionListener(e -> { |
||||||
|
SwingUtilities.invokeLater(() -> { |
||||||
|
sortItemPane.setComponentZOrder(selectedLabel, sortItemPane.getComponentCount() - 1); |
||||||
|
setToolbarEnable(sortItemPane.getComponentZOrder(selectedLabel), sortItemPane.getComponentCount()); |
||||||
|
CustomSortPane.this.revalidate(); |
||||||
|
CustomSortPane.this.repaint(); |
||||||
|
refreshCurrentOrder(); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
up.addActionListener(e -> { |
||||||
|
SwingUtilities.invokeLater(() -> { |
||||||
|
sortItemPane.setComponentZOrder(selectedLabel, sortItemPane.getComponentZOrder(selectedLabel) - 1); |
||||||
|
setToolbarEnable(sortItemPane.getComponentZOrder(selectedLabel), sortItemPane.getComponentCount()); |
||||||
|
CustomSortPane.this.revalidate(); |
||||||
|
CustomSortPane.this.repaint(); |
||||||
|
refreshCurrentOrder(); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
down.addActionListener(e -> { |
||||||
|
SwingUtilities.invokeLater(() -> { |
||||||
|
sortItemPane.setComponentZOrder(selectedLabel, sortItemPane.getComponentZOrder(selectedLabel) + 1); |
||||||
|
setToolbarEnable(sortItemPane.getComponentZOrder(selectedLabel), sortItemPane.getComponentCount()); |
||||||
|
CustomSortPane.this.revalidate(); |
||||||
|
CustomSortPane.this.repaint(); |
||||||
|
refreshCurrentOrder(); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
toolbarPane = new JPanel(new FlowLayout(FlowLayout.TRAILING, GAP, GAP)); |
||||||
|
toolbarPane.setBorder(BorderFactory.createEmptyBorder()); |
||||||
|
toolbarPane.add(top); |
||||||
|
toolbarPane.add(bottom); |
||||||
|
toolbarPane.add(up); |
||||||
|
toolbarPane.add(down); |
||||||
|
} |
||||||
|
|
||||||
|
private void createSortItemPane() { |
||||||
|
String[] currentTabOrder = parentPane.getCurrentOrder(); |
||||||
|
Map<String, Integer> sortMap = new HashMap<>(); |
||||||
|
for (int i = 0; i < currentTabOrder.length; i++) { |
||||||
|
sortMap.put(currentTabOrder[i], i); |
||||||
|
} |
||||||
|
List<MenuLabel> sortLabels = new ArrayList<>(); |
||||||
|
for (UICheckBox item : sortItems) { |
||||||
|
MenuLabel label = new MenuLabel(item.getText(), (Function<MenuLabel, Object>) o -> { |
||||||
|
selectedLabel = o; |
||||||
|
disableButton(); |
||||||
|
return null; |
||||||
|
}); |
||||||
|
sortLabels.add(label); |
||||||
|
} |
||||||
|
sortLabels.sort(Comparator.comparingInt(tab -> sortMap.get(tab.getText()))); |
||||||
|
sortItemPane = new MenuLabelPane(sortLabels); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 如果选中第一个和最后一个,则置灰向上和向下的按钮 |
||||||
|
*/ |
||||||
|
private void disableButton() { |
||||||
|
int order = sortItemPane.getComponentZOrder(selectedLabel); |
||||||
|
if (order == 0) { |
||||||
|
setToolbarEnable(false, false, true, true); |
||||||
|
} else if (order == sortItemPane.getComponentCount() - 1) { |
||||||
|
setToolbarEnable(true, true, false, false); |
||||||
|
} else { |
||||||
|
setToolbarEnable(true, true, true, true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 设置 置顶,上移,下移,置底 按钮的状态 |
||||||
|
* true:启用 |
||||||
|
* false:关闭 |
||||||
|
*/ |
||||||
|
private void setToolbarEnable(boolean top, boolean up, boolean down, boolean bottom) { |
||||||
|
this.top.setEnabled(top); |
||||||
|
this.up.setEnabled(up); |
||||||
|
this.down.setEnabled(down); |
||||||
|
this.bottom.setEnabled(bottom); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据选项当前位置以及菜单大小设置 置顶,上移,下移,置底 按钮的状态 |
||||||
|
*/ |
||||||
|
private void setToolbarEnable(int order, int maxOrder) { |
||||||
|
this.top.setEnabled(true); |
||||||
|
this.up.setEnabled(true); |
||||||
|
this.down.setEnabled(true); |
||||||
|
this.bottom.setEnabled(true); |
||||||
|
// 选项处于顶端,则置灰上移和置顶按钮
|
||||||
|
if (order == 0) { |
||||||
|
this.top.setEnabled(false); |
||||||
|
this.up.setEnabled(false); |
||||||
|
} |
||||||
|
// 选项处于底端,则置灰下移和置底按钮
|
||||||
|
if (order == maxOrder - 1) { |
||||||
|
this.down.setEnabled(false); |
||||||
|
this.bottom.setEnabled(false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void refreshCurrentOrder() { |
||||||
|
String[] currentTabOrder = parentPane.getCurrentOrder(); |
||||||
|
HashSet<String> selectedTab = new HashSet<>(); |
||||||
|
for (UICheckBox item : sortItems) { |
||||||
|
selectedTab.add(item.getText()); |
||||||
|
} |
||||||
|
|
||||||
|
// 未选中的tab,保持原排序不变
|
||||||
|
Map<String, Integer> exTab = new HashMap<>(); |
||||||
|
for (int i = 0; i < currentTabOrder.length; i++) { |
||||||
|
if (!selectedTab.contains(currentTabOrder[i])) { |
||||||
|
exTab.put(currentTabOrder[i], i); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 计算当前排序
|
||||||
|
String[] newOrder = new String[currentTabOrder.length]; |
||||||
|
Component[] components = sortItemPane.getComponents(); |
||||||
|
for (String s : exTab.keySet()) { |
||||||
|
newOrder[exTab.get(s)] = s; |
||||||
|
} |
||||||
|
|
||||||
|
int t = 0; |
||||||
|
for (int i = 0; i < newOrder.length; i++) { |
||||||
|
if (StringUtils.isEmpty(newOrder[i])) { |
||||||
|
newOrder[i] = ((MenuLabel) components[t++]).getText(); |
||||||
|
} |
||||||
|
} |
||||||
|
parentPane.setCurrentOrder(newOrder); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,96 @@ |
|||||||
|
package com.fr.design.actions.help.alphafine.component; |
||||||
|
|
||||||
|
import com.fr.design.gui.ilable.UILabel; |
||||||
|
import com.fr.design.utils.DesignUtils; |
||||||
|
|
||||||
|
import javax.swing.BorderFactory; |
||||||
|
import javax.swing.plaf.LabelUI; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.event.MouseAdapter; |
||||||
|
import java.awt.event.MouseEvent; |
||||||
|
import java.awt.event.MouseListener; |
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
/** |
||||||
|
* 菜单选项label |
||||||
|
* |
||||||
|
* @author Link |
||||||
|
* @version 11.0 |
||||||
|
* Created by Link on 2022/9/18 |
||||||
|
*/ |
||||||
|
public class MenuLabel extends UILabel { |
||||||
|
|
||||||
|
private static final Color BACKGROUND_COLOR = Color.white; |
||||||
|
private static final Color SELECTED_COLOR = new Color(0x419BF9); |
||||||
|
private static final Color HOVERED_COLOR = new Color(0xd9ebfe); |
||||||
|
private static final int HEIGHT = 23; |
||||||
|
private static final int WIDTH = 147; |
||||||
|
|
||||||
|
private MenuLabelPane parentMenu; |
||||||
|
private final Function function; |
||||||
|
private boolean selected; |
||||||
|
|
||||||
|
public MenuLabel(String text, Function function) { |
||||||
|
super(text); |
||||||
|
this.function = function; |
||||||
|
setOpaque(true); |
||||||
|
addMouseListener(createMouseListener()); |
||||||
|
} |
||||||
|
|
||||||
|
public void setParentMenu(MenuLabelPane menu) { |
||||||
|
this.parentMenu = menu; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void setUI(LabelUI ui) { |
||||||
|
super.setUI(ui); |
||||||
|
this.setBackground(BACKGROUND_COLOR); |
||||||
|
this.setBorder(BorderFactory.createEmptyBorder(2, 10, 1, 10)); |
||||||
|
this.setPreferredSize(new Dimension(WIDTH, HEIGHT)); |
||||||
|
this.setFont(DesignUtils.getDefaultGUIFont().applySize(12)); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isSelected() { |
||||||
|
return selected; |
||||||
|
} |
||||||
|
|
||||||
|
public void setSelected(boolean selected) { |
||||||
|
if (selected) { |
||||||
|
parentMenu.setNoneSelected(); |
||||||
|
setBackground(SELECTED_COLOR); |
||||||
|
function.apply(this); |
||||||
|
this.selected = true; |
||||||
|
} else { |
||||||
|
setBackground(BACKGROUND_COLOR); |
||||||
|
this.selected = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
MouseListener createMouseListener() { |
||||||
|
return new MouseAdapter() { |
||||||
|
@Override |
||||||
|
public void mouseClicked(MouseEvent e) { |
||||||
|
super.mouseClicked(e); |
||||||
|
setSelected(true); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void mouseEntered(MouseEvent e) { |
||||||
|
super.mouseEntered(e); |
||||||
|
if (!selected) { |
||||||
|
setBackground(HOVERED_COLOR); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void mouseExited(MouseEvent e) { |
||||||
|
super.mouseExited(e); |
||||||
|
if (!selected) { |
||||||
|
setBackground(BACKGROUND_COLOR); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
package com.fr.design.actions.help.alphafine.component; |
||||||
|
|
||||||
|
import javax.swing.JPanel; |
||||||
|
import java.awt.FlowLayout; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* 简单菜单面板 |
||||||
|
* |
||||||
|
* @author Link |
||||||
|
* @version 11.0 |
||||||
|
* Created by Link on 2022/9/18 |
||||||
|
*/ |
||||||
|
public class MenuLabelPane extends JPanel { |
||||||
|
|
||||||
|
private static final int GAP = 1; |
||||||
|
|
||||||
|
private List<MenuLabel> labels; |
||||||
|
|
||||||
|
public MenuLabelPane(List<MenuLabel> labels) { |
||||||
|
this.labels = labels; |
||||||
|
setLayout(new FlowLayout(FlowLayout.CENTER, GAP, GAP)); |
||||||
|
for (MenuLabel label : labels) { |
||||||
|
label.setParentMenu(this); |
||||||
|
add(label); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void setNoneSelected() { |
||||||
|
for (MenuLabel label : labels) { |
||||||
|
label.setSelected(false); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package com.fr.design.actions.help.replace; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 定义一些底层操作 |
||||||
|
* |
||||||
|
* @author Destiny.Lin |
||||||
|
* @version 11.0 |
||||||
|
* created by Destiny.Lin on 2022-09-27 |
||||||
|
*/ |
||||||
|
public interface ITReplaceOperator { |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 关闭面板 |
||||||
|
*/ |
||||||
|
void close(); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
package com.fr.design.carton; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
|
||||||
|
public class CartonFiles { |
||||||
|
private File easyCheckerFile; |
||||||
|
private File timerCheckerFile; |
||||||
|
|
||||||
|
public CartonFiles() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public File getEasyCheckerFile() { |
||||||
|
return easyCheckerFile; |
||||||
|
} |
||||||
|
|
||||||
|
public void setEasyCheckerFile(File easyCheckerFile) { |
||||||
|
this.easyCheckerFile = easyCheckerFile; |
||||||
|
} |
||||||
|
|
||||||
|
public File getTimerCheckerFile() { |
||||||
|
return timerCheckerFile; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTimerCheckerFile(File timerCheckerFile) { |
||||||
|
this.timerCheckerFile = timerCheckerFile; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,161 @@ |
|||||||
|
package com.fr.design.carton; |
||||||
|
|
||||||
|
import com.fr.base.SimpleDateFormatThreadSafe; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.json.JSONObject; |
||||||
|
|
||||||
|
import java.util.Map; |
||||||
|
import java.util.Timer; |
||||||
|
import java.util.TimerTask; |
||||||
|
import java.util.concurrent.*; |
||||||
|
import java.util.concurrent.atomic.AtomicLong; |
||||||
|
|
||||||
|
|
||||||
|
public class CartonThreadExecutorPool extends ThreadPoolExecutor { |
||||||
|
private static final int MAX_LIVE_TIME = 3000; |
||||||
|
private static final int MAX_WORKER_THREADS = 10; |
||||||
|
/** |
||||||
|
* 开启间隔检测后两次检测的相隔时间ms |
||||||
|
*/ |
||||||
|
private static final long CHECK_INTERVAL_MS = 100; |
||||||
|
private final ThreadLocal<StackTraceElement[]> startReportedStack = new ThreadLocal<>(); |
||||||
|
private volatile static CartonThreadExecutorPool cartonThreadExecutorPool; |
||||||
|
private static final ConcurrentHashMap<Long, ThreadInfo> concurrentHashMap = new ConcurrentHashMap<>(); |
||||||
|
private static final SimpleDateFormatThreadSafe simpleDateFormatThreadSafe = new SimpleDateFormatThreadSafe(); |
||||||
|
private final static AtomicLong hangCount = new AtomicLong(0); |
||||||
|
private Timer timer; |
||||||
|
/** |
||||||
|
* 一个变量,用于控制easy监测模式的开关 |
||||||
|
*/ |
||||||
|
private boolean easyWitch = false; |
||||||
|
|
||||||
|
public boolean isEasyWitch() { |
||||||
|
return easyWitch; |
||||||
|
} |
||||||
|
|
||||||
|
public void setEasyWitch(boolean easyWitch) { |
||||||
|
this.easyWitch = easyWitch; |
||||||
|
} |
||||||
|
|
||||||
|
private static class ThreadInfo { |
||||||
|
private ThreadInfo () { |
||||||
|
hangNumber = hangCount.getAndAdd(1); |
||||||
|
} |
||||||
|
private long hangNumber; |
||||||
|
private final Thread eventThread = Thread.currentThread(); |
||||||
|
private StackTraceElement[] lastReportedStack; |
||||||
|
private final long startTime = System.currentTimeMillis(); |
||||||
|
public void checkForHang() { |
||||||
|
if (timeSoFar() > MAX_LIVE_TIME) { |
||||||
|
examineHang(); |
||||||
|
} |
||||||
|
} |
||||||
|
private long timeSoFar() { |
||||||
|
return (System.currentTimeMillis() - startTime); |
||||||
|
} |
||||||
|
|
||||||
|
private void examineHang() { |
||||||
|
StackTraceElement[] currentStack = eventThread.getStackTrace(); |
||||||
|
|
||||||
|
if (lastReportedStack!=null && EventDispatchThreadHangMonitor.stacksEqual(currentStack, lastReportedStack)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
lastReportedStack = currentStack; |
||||||
|
String stackTrace = EventDispatchThreadHangMonitor.stackTraceToString(currentStack); |
||||||
|
JSONObject jsonObject = new JSONObject(); |
||||||
|
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"), simpleDateFormatThreadSafe.format(System.currentTimeMillis())); |
||||||
|
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"), "swingWorker_" + hangNumber); |
||||||
|
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Duration_Task_Execute"), timeSoFar() + "ms"); |
||||||
|
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Stack_Info"), stackTrace); |
||||||
|
EventDispatchThreadHangMonitor.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.TIMER_CHECK_FLAG); |
||||||
|
EventDispatchThreadHangMonitor.checkForDeadlock(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 来自SwingWorker类 |
||||||
|
*/ |
||||||
|
public static ThreadFactory threadFactory = |
||||||
|
new ThreadFactory() { |
||||||
|
final ThreadFactory defaultFactory = |
||||||
|
Executors.defaultThreadFactory(); |
||||||
|
@Override |
||||||
|
public Thread newThread(final Runnable r) { |
||||||
|
Thread thread = |
||||||
|
defaultFactory.newThread(r); |
||||||
|
thread.setName("SwingWorker-" |
||||||
|
+ thread.getName()); |
||||||
|
thread.setDaemon(true); |
||||||
|
return thread; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
public static CartonThreadExecutorPool getTimerThreadExecutorPool () { |
||||||
|
if (cartonThreadExecutorPool == null) { |
||||||
|
synchronized (CartonThreadExecutorPool.class) { |
||||||
|
if (cartonThreadExecutorPool == null) { |
||||||
|
cartonThreadExecutorPool = |
||||||
|
new CartonThreadExecutorPool(MAX_WORKER_THREADS, MAX_WORKER_THREADS, |
||||||
|
10L, TimeUnit.MINUTES, |
||||||
|
new LinkedBlockingQueue<Runnable>(),threadFactory |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return cartonThreadExecutorPool; |
||||||
|
} |
||||||
|
|
||||||
|
private CartonThreadExecutorPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { |
||||||
|
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); |
||||||
|
simpleDateFormatThreadSafe.applyPattern("yyyy-MM-dd HH:mm:ss"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void beforeExecute(Thread t, Runnable r) { |
||||||
|
super.beforeExecute(t, r); |
||||||
|
startReportedStack.set(t.getStackTrace()); |
||||||
|
concurrentHashMap.put(t.getId(), new ThreadInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void afterExecute(Runnable r, Throwable t) { |
||||||
|
super.afterExecute(r, t); |
||||||
|
long currentThreadId = Thread.currentThread().getId(); |
||||||
|
long runTime = (System.currentTimeMillis() - concurrentHashMap.get(currentThreadId).startTime); |
||||||
|
//加~是为了之后输出的时候换行。
|
||||||
|
if (isEasyWitch() && runTime > MAX_LIVE_TIME) { |
||||||
|
JSONObject jsonObject = new JSONObject(); |
||||||
|
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"), simpleDateFormatThreadSafe.format(System.currentTimeMillis())); |
||||||
|
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"), "swingWorker_" + concurrentHashMap.get(currentThreadId).hangNumber); |
||||||
|
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Start_Time"), simpleDateFormatThreadSafe.format(concurrentHashMap.get(currentThreadId).startTime)); |
||||||
|
jsonObject.put(Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), runTime + "ms"); |
||||||
|
EventDispatchThreadHangMonitor.outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.EASY_CHECK_FLAG); |
||||||
|
|
||||||
|
} |
||||||
|
concurrentHashMap.remove(currentThreadId); |
||||||
|
} |
||||||
|
|
||||||
|
public class Checker extends TimerTask { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
if (cartonThreadExecutorPool == null || concurrentHashMap.isEmpty()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (Map.Entry<Long, ThreadInfo> map : concurrentHashMap.entrySet()) { |
||||||
|
map.getValue().checkForHang(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void initTimer() { |
||||||
|
timer = new Timer("CheckerSwingWorker",true); |
||||||
|
timer.schedule(new Checker(), 0, CHECK_INTERVAL_MS); |
||||||
|
} |
||||||
|
|
||||||
|
public void stopTimer() { |
||||||
|
if (timer != null) { |
||||||
|
timer.cancel(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
package com.fr.design.carton; |
||||||
|
|
||||||
|
|
||||||
|
public class CartonUploadMessage { |
||||||
|
private String hangCount; |
||||||
|
private String slowTime; |
||||||
|
private String threadTime; |
||||||
|
private String info; |
||||||
|
|
||||||
|
public CartonUploadMessage() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public String getHangCount() { |
||||||
|
return hangCount; |
||||||
|
} |
||||||
|
|
||||||
|
public void setHangCount(String hangCount) { |
||||||
|
this.hangCount = hangCount; |
||||||
|
} |
||||||
|
|
||||||
|
public String getSlowTime() { |
||||||
|
return slowTime; |
||||||
|
} |
||||||
|
|
||||||
|
public void setSlowTime(String slowTime) { |
||||||
|
this.slowTime = slowTime; |
||||||
|
} |
||||||
|
|
||||||
|
public String getThreadTime() { |
||||||
|
return threadTime; |
||||||
|
} |
||||||
|
|
||||||
|
public void setThreadTime(String threadTime) { |
||||||
|
this.threadTime = threadTime; |
||||||
|
} |
||||||
|
|
||||||
|
public String getInfo() { |
||||||
|
return info; |
||||||
|
} |
||||||
|
|
||||||
|
public void setInfo(String info) { |
||||||
|
this.info = info; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,408 @@ |
|||||||
|
package com.fr.design.carton; |
||||||
|
|
||||||
|
import com.fr.concurrent.FineExecutors; |
||||||
|
import com.fr.design.ui.util.UIUtil; |
||||||
|
import com.fr.json.JSONObject; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.stable.ArrayUtils; |
||||||
|
import com.fr.stable.ProductConstantsBase; |
||||||
|
import com.fr.stable.StableUtils; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
|
||||||
|
import javax.swing.SwingUtilities; |
||||||
|
import java.awt.EventQueue; |
||||||
|
import java.awt.Toolkit; |
||||||
|
import java.awt.AWTEvent; |
||||||
|
import java.awt.event.WindowEvent; |
||||||
|
import java.io.BufferedWriter; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileWriter; |
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.management.ManagementFactory; |
||||||
|
import java.lang.management.ThreadInfo; |
||||||
|
import java.lang.management.ThreadMXBean; |
||||||
|
import java.text.SimpleDateFormat; |
||||||
|
import java.util.LinkedList; |
||||||
|
import java.util.Timer; |
||||||
|
import java.util.TimerTask; |
||||||
|
import java.util.concurrent.ScheduledExecutorService; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
|
||||||
|
/** |
||||||
|
* 参考自git swinghelper |
||||||
|
* 用于卡顿检测 |
||||||
|
* 主要是两块内容 |
||||||
|
* 1.获取eventQueue中每个事件的执行时间 |
||||||
|
* 2.用一个Timer定时去检测当前执行任务的线程 |
||||||
|
*/ |
||||||
|
|
||||||
|
public final class EventDispatchThreadHangMonitor extends EventQueue { |
||||||
|
/** |
||||||
|
* 日期事件格式 |
||||||
|
*/ |
||||||
|
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
||||||
|
public static final EventDispatchThreadHangMonitor INSTANCE = new EventDispatchThreadHangMonitor(); |
||||||
|
/** |
||||||
|
* 一个timer |
||||||
|
*/ |
||||||
|
private Timer timer; |
||||||
|
/** |
||||||
|
* 开启间隔检测后两次检测的相隔时间ms |
||||||
|
*/ |
||||||
|
private static final long CHECK_INTERVAL_MS = 100; |
||||||
|
/** |
||||||
|
* 最大的事件允许执行时间,超过该时间则打印堆栈等相关信息 |
||||||
|
*/ |
||||||
|
private static final long UNREASONABLE_DISPATCH_DURATION_MS = 1500; |
||||||
|
/** |
||||||
|
* 事件唯一编码,用于方便日志的查看 |
||||||
|
*/ |
||||||
|
private static long hangCount = 0; |
||||||
|
/** |
||||||
|
* 输出日志所在地址 |
||||||
|
*/ |
||||||
|
private static final String JOURNAL_FILE_PATH = StableUtils.pathJoin(ProductConstantsBase.getEnvHome(), "journal_log"); |
||||||
|
/** |
||||||
|
* 类似于一个开关,当该值为默认的false启动时,定时任务在窗口开启前都不会对执行的事件进行检查 |
||||||
|
*/ |
||||||
|
private boolean haveShownSomeComponent = false; |
||||||
|
/** |
||||||
|
* 该链表为主要的实现定时任务的容器,在重写的dispatchEvent中由pre方法将DispatchInfo加入到链表,由post方法remove |
||||||
|
*/ |
||||||
|
private final LinkedList<DispatchInfo> dispatches = new LinkedList<DispatchInfo>(); |
||||||
|
/** |
||||||
|
* 一个变量,用于控制easy监测模式的开关 |
||||||
|
*/ |
||||||
|
private boolean easyWitch = false; |
||||||
|
private static ScheduledExecutorService scheduledExecutorService; |
||||||
|
|
||||||
|
public boolean isEasyWitch() { |
||||||
|
return easyWitch; |
||||||
|
} |
||||||
|
|
||||||
|
public void setEasyWitch(boolean easyWitch) { |
||||||
|
this.easyWitch = easyWitch; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 一个变量,用于记录Timer的开关。 |
||||||
|
*/ |
||||||
|
|
||||||
|
public boolean isTimerWitch() { |
||||||
|
return timerWitch; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTimerWitch(boolean timerWitch) { |
||||||
|
this.timerWitch = timerWitch; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean timerWitch = false; |
||||||
|
|
||||||
|
private synchronized static long getHangCount() { |
||||||
|
return hangCount++; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param a can not be null |
||||||
|
* @param b can not be null |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static boolean stacksEqual(@NotNull StackTraceElement[] a, @NotNull StackTraceElement[] b) { |
||||||
|
|
||||||
|
if (!ArrayUtils.isSameLength(a, b)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
for (int i = 0; i < a.length; ++i) { |
||||||
|
if (!a[i].equals(b[i])) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 用于判断是不是特定的堆栈 |
||||||
|
*/ |
||||||
|
public static boolean stackTraceElementIs(StackTraceElement e, String className, String methodName, boolean isNative) { |
||||||
|
return e.getClassName().equals(className) && e.getMethodName().equals(methodName) && e.isNativeMethod() == isNative; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 用于判断某个堆栈是否在等待另一个事件 |
||||||
|
* 取当前堆栈前三层判断是是不是匹配等待堆栈的格式 |
||||||
|
*/ |
||||||
|
public static boolean isWaitingForNextEvent(StackTraceElement[] currentStack) { |
||||||
|
|
||||||
|
return currentStack != null && currentStack.length >= 3 && |
||||||
|
stackTraceElementIs(currentStack[0], "java.lang.Object", "wait", true) |
||||||
|
&& stackTraceElementIs(currentStack[1], "java.lang.Object", "wait", false) |
||||||
|
&& stackTraceElementIs(currentStack[2], "java.awt.EventQueue", "getNextEvent", false); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* event事件的包装类 |
||||||
|
*/ |
||||||
|
public static class DispatchInfo { |
||||||
|
// 上一次被打印的堆栈ou
|
||||||
|
private StackTraceElement[] lastReportedStack; |
||||||
|
//获取执行该事件的线程
|
||||||
|
private final Thread eventDispatchThread = Thread.currentThread(); |
||||||
|
//在队列中等待执行的事件最后未执行的时间,当有一个事件执行完后就遍历dispatches给该值赋当前时间
|
||||||
|
private long lastDispatchTimeMillis = System.currentTimeMillis(); |
||||||
|
//事件开始的时间
|
||||||
|
private final long startDispatchTimeMillis = System.currentTimeMillis(); |
||||||
|
//事件编号
|
||||||
|
private long hangNumber; |
||||||
|
//构造函数,给当前对象赋一个递增的唯一编号
|
||||||
|
public DispatchInfo() { |
||||||
|
hangNumber = getHangCount(); |
||||||
|
} |
||||||
|
//定时调度任务检测的入口,如果执行时间大于设定的值就进入examineHang()方法
|
||||||
|
public void checkForHang() { |
||||||
|
if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) { |
||||||
|
examineHang(); |
||||||
|
} |
||||||
|
} |
||||||
|
//超时堆栈的具体处理
|
||||||
|
private void examineHang() { |
||||||
|
//获取执行线程的当前堆栈
|
||||||
|
StackTraceElement[] currentStack = eventDispatchThread.getStackTrace(); |
||||||
|
if (isWaitingForNextEvent(currentStack)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
//某个事件执行时间很长,定时处理时可能会连续打很多个堆栈,对同一个事件的相同堆栈只打一次
|
||||||
|
if (lastReportedStack !=null && stacksEqual(lastReportedStack, currentStack)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
String stackTrace = stackTraceToString(currentStack); |
||||||
|
lastReportedStack = currentStack; |
||||||
|
JSONObject jsonObject = new JSONObject(); |
||||||
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"), simpleDateFormat.format(System.currentTimeMillis())); |
||||||
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"), "eventQueue_" + hangNumber); |
||||||
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Duration_Task_Execute"), timeSoFar() + "ms"); |
||||||
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Stack_Info"), stackTrace); |
||||||
|
outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.TIMER_CHECK_FLAG); |
||||||
|
checkForDeadlock(); |
||||||
|
} |
||||||
|
//记录连续运行了多长时间
|
||||||
|
private long timeSoFar() { |
||||||
|
return (System.currentTimeMillis() - lastDispatchTimeMillis); |
||||||
|
} |
||||||
|
//记录一个事件从被分发到结束的总运行时间
|
||||||
|
private long totalTime() { |
||||||
|
return (System.currentTimeMillis() - startDispatchTimeMillis); |
||||||
|
} |
||||||
|
//事件处理完后的时间判断
|
||||||
|
public void dispose() { |
||||||
|
if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) { |
||||||
|
exportCartonLog(true); |
||||||
|
} else if (lastReportedStack != null){ |
||||||
|
exportCartonLog(false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @param flag 判断一下输出日志时要输出哪个时间 |
||||||
|
*/ |
||||||
|
private void exportCartonLog(boolean flag) { |
||||||
|
JSONObject jsonObject = new JSONObject(); |
||||||
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"), simpleDateFormat.format(System.currentTimeMillis())); |
||||||
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"), "eventQueue_" + hangNumber); |
||||||
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Start_Time"), simpleDateFormat.format(startDispatchTimeMillis)); |
||||||
|
if (flag) { |
||||||
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), timeSoFar() + "ms"); |
||||||
|
} else { |
||||||
|
jsonObject.put(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"), totalTime() + "ms"); |
||||||
|
} |
||||||
|
outPutJournalLog(jsonObject.toString(), SwitchForSwingChecker.EASY_CHECK_FLAG); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void outPutJournalLog(String message, int flag) { |
||||||
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); |
||||||
|
String date = simpleDateFormat.format(System.currentTimeMillis()); |
||||||
|
String filename = flag == SwitchForSwingChecker.EASY_CHECK_FLAG ? SwitchForSwingChecker.EASY_CHECKER_FILE_NAME: SwitchForSwingChecker.TIMER_CHECKER_FILE_NAME; |
||||||
|
String[] split = date.split("-"); |
||||||
|
int month = StringUtils.isEmpty(split[1]) ? -1 : Integer.parseInt(split[1]); |
||||||
|
String dirPath = StableUtils.pathJoin(JOURNAL_FILE_PATH, split[0], "month-" + month, date); |
||||||
|
File dirFile = new File(dirPath); |
||||||
|
File file = new File( StableUtils.pathJoin(dirPath, filename)); |
||||||
|
try { |
||||||
|
if (!file.exists()) { |
||||||
|
if (!dirFile.exists()) { |
||||||
|
dirFile.mkdirs(); |
||||||
|
} |
||||||
|
file.createNewFile(); |
||||||
|
} |
||||||
|
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file, true)); |
||||||
|
String outputMessage = new StringBuilder(message.replaceAll("~", "\r\n")).append(",").append("\r\n").toString(); |
||||||
|
bufferedWriter.write(outputMessage); |
||||||
|
bufferedWriter.close(); |
||||||
|
} catch (IOException e) { |
||||||
|
FineLoggerFactory.getLogger().error("output fail", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private EventDispatchThreadHangMonitor() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 参考SwingExplorer,在处理模态框时没有做特殊处理,也不会输出卡顿堆栈 |
||||||
|
* 原因是SwingExplorer窗口一直有一个监听事件,不断的add,remove。 |
||||||
|
* 由于卡顿日志输出的是事件连续执行的时间,所以一个长时间存在的模态框被不断重复的监听事件刷新时间,就不会输出了。 |
||||||
|
* 当检测开关打开后,在这里模拟一下监听事件,给个不耗时的任务就可以。 |
||||||
|
*/ |
||||||
|
public void startFilterModalWindow() { |
||||||
|
scheduledExecutorService = FineExecutors.newSingleThreadScheduledExecutor(); |
||||||
|
scheduledExecutorService.scheduleAtFixedRate(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
SwingUtilities.invokeLater(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
//不用干事,切个片就可以
|
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}, 0, 500, TimeUnit.MILLISECONDS); |
||||||
|
} |
||||||
|
|
||||||
|
public void stopFilterModalWindow() { |
||||||
|
if (scheduledExecutorService != null) { |
||||||
|
scheduledExecutorService.shutdown(); |
||||||
|
} |
||||||
|
} |
||||||
|
/** |
||||||
|
* Sets up a timer to check for hangs frequently. |
||||||
|
* 初始化一个Timer |
||||||
|
*/ |
||||||
|
public void initTimer() { |
||||||
|
final long initialDelayMs = 0; |
||||||
|
final boolean daemon = true; |
||||||
|
timer = new Timer("EventDispatchThreadHangMonitor", daemon); |
||||||
|
timer.schedule(new HangChecker(), initialDelayMs, CHECK_INTERVAL_MS); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* /消除Timer |
||||||
|
*/ |
||||||
|
public void stopTimer() { |
||||||
|
if (timer != null) { |
||||||
|
timer.cancel(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* /定时执行的任务 |
||||||
|
*/ |
||||||
|
public class HangChecker extends TimerTask { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
synchronized (dispatches) { |
||||||
|
//如果链表为空或者窗口还没启开,定时检测就不进行
|
||||||
|
if (dispatches.isEmpty() || !haveShownSomeComponent) { |
||||||
|
return; |
||||||
|
} |
||||||
|
dispatches.getLast().checkForHang(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 将swing中默认的EventQueue换成自己的 |
||||||
|
*/ |
||||||
|
public static void initMonitoring() { |
||||||
|
UIUtil.invokeLaterIfNeeded(() -> Toolkit.getDefaultToolkit().getSystemEventQueue().push(INSTANCE)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Overrides EventQueue.dispatchEvent to call our pre and post hooks either |
||||||
|
* side of the system's event dispatch code. |
||||||
|
* 重写 |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected void dispatchEvent(AWTEvent event) { |
||||||
|
//如果两个开关都没开,那就不走重写方法了
|
||||||
|
if (!isEasyWitch() && !isTimerWitch()) { |
||||||
|
super.dispatchEvent(event); |
||||||
|
} else { |
||||||
|
try { |
||||||
|
preDispatchEvent(); |
||||||
|
super.dispatchEvent(event); |
||||||
|
} finally { |
||||||
|
postDispatchEvent(); |
||||||
|
if (!haveShownSomeComponent && |
||||||
|
event instanceof WindowEvent && event.getID() == WindowEvent.WINDOW_OPENED) { |
||||||
|
haveShownSomeComponent = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Starts tracking a dispatch. |
||||||
|
*/ |
||||||
|
private synchronized void preDispatchEvent() { |
||||||
|
synchronized (dispatches) { |
||||||
|
dispatches.addLast(new DispatchInfo()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Stops tracking a dispatch. |
||||||
|
*/ |
||||||
|
private synchronized void postDispatchEvent() { |
||||||
|
synchronized (dispatches) { |
||||||
|
DispatchInfo justFinishedDispatch = dispatches.removeLast(); |
||||||
|
if (isEasyWitch()) { |
||||||
|
justFinishedDispatch.dispose(); |
||||||
|
} |
||||||
|
//嵌套最深的事件执行完毕后刷新链表中其他事件的lastDispatchTimeMillis
|
||||||
|
Thread currentEventDispatchThread = Thread.currentThread(); |
||||||
|
for (DispatchInfo dispatchInfo : dispatches) { |
||||||
|
if (dispatchInfo.eventDispatchThread == currentEventDispatchThread) { |
||||||
|
dispatchInfo.lastDispatchTimeMillis = System.currentTimeMillis(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 检查死锁 |
||||||
|
*/ |
||||||
|
public static void checkForDeadlock() { |
||||||
|
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); |
||||||
|
long[] threadIds = threadBean.findDeadlockedThreads(); |
||||||
|
if (threadIds == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
FineLoggerFactory.getLogger().warn("deadlock detected involving the following threads:"); |
||||||
|
ThreadInfo[] threadInfos = threadBean.getThreadInfo(threadIds, Integer.MAX_VALUE); |
||||||
|
for (ThreadInfo info : threadInfos) { |
||||||
|
FineLoggerFactory.getLogger().warn("Thread # {} {} ( {} ) waiting on {} held by {} {}", info.getThreadId(), info.getThreadName(), |
||||||
|
info.getThreadState(), info.getLockName(), info.getLockOwnerName(), stackTraceToStringForConsole(info.getStackTrace())); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static String stackTraceToString(StackTraceElement[] stackTrace) { |
||||||
|
StringBuilder result = new StringBuilder(); |
||||||
|
for (StackTraceElement stackTraceElement : stackTrace) { |
||||||
|
String indentation = " "; |
||||||
|
result.append("~").append(indentation).append(stackTraceElement); |
||||||
|
} |
||||||
|
return result.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
public static String stackTraceToStringForConsole(StackTraceElement[] stackTrace) { |
||||||
|
StringBuilder result = new StringBuilder(); |
||||||
|
for (StackTraceElement stackTraceElement : stackTrace) { |
||||||
|
String indentation = " "; |
||||||
|
result.append("\r\n").append(indentation).append(stackTraceElement); |
||||||
|
} |
||||||
|
return result.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,406 @@ |
|||||||
|
package com.fr.design.carton; |
||||||
|
|
||||||
|
import com.fr.decision.webservice.v10.log.download.utils.LogZipUtils; |
||||||
|
import com.fr.design.DesignerEnvManager; |
||||||
|
import com.fr.design.constants.UIConstants; |
||||||
|
import com.fr.design.dialog.FineJOptionPane; |
||||||
|
import com.fr.design.env.DesignerWorkspaceInfo; |
||||||
|
import com.fr.design.gui.date.UIDatePicker; |
||||||
|
import com.fr.design.gui.ibutton.UIButton; |
||||||
|
import com.fr.design.gui.icheckbox.UICheckBox; |
||||||
|
import com.fr.design.gui.ilable.UILabel; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.layout.FRGUIPaneFactory; |
||||||
|
import com.fr.design.mainframe.DesignerContext; |
||||||
|
import com.fr.design.utils.gui.GUICoreUtils; |
||||||
|
import com.fr.env.detect.ui.EnvDetectorDialog; |
||||||
|
import com.fr.file.FILE; |
||||||
|
import com.fr.file.FILEChooserPane; |
||||||
|
import com.fr.file.FILEFactory; |
||||||
|
import com.fr.file.filter.ChooseFileFilter; |
||||||
|
import com.fr.general.GeneralUtils; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.stable.StableUtils; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import org.jetbrains.annotations.Nullable; |
||||||
|
import com.fr.workspace.WorkContext; |
||||||
|
|
||||||
|
|
||||||
|
import javax.swing.BorderFactory; |
||||||
|
import javax.swing.JDialog; |
||||||
|
import javax.swing.JOptionPane; |
||||||
|
import javax.swing.JPanel; |
||||||
|
import javax.swing.UIManager; |
||||||
|
import javax.swing.filechooser.FileSystemView; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Component; |
||||||
|
import java.awt.Cursor; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.FlowLayout; |
||||||
|
import java.awt.Frame; |
||||||
|
import java.awt.event.MouseAdapter; |
||||||
|
import java.awt.event.MouseEvent; |
||||||
|
import java.awt.event.WindowEvent; |
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileInputStream; |
||||||
|
import java.text.ParseException; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
|
||||||
|
public class FeedbackToolboxDialog extends JDialog { |
||||||
|
private UIDatePicker uiDatePicker; |
||||||
|
private JPanel generalSettingPanel = null; |
||||||
|
private UICheckBox easyCheckerButton = null; |
||||||
|
private UICheckBox timerCheckerButton = null; |
||||||
|
private UIButton uploadButton = null; |
||||||
|
private UILabel exportLogLabel = null; |
||||||
|
private final Color backgroundColor = new Color(240, 240, 243, 1); |
||||||
|
private final Color lineColor = new Color(192, 192, 192, 120); |
||||||
|
private JPanel body = null; |
||||||
|
private static final String WORK_SPACE_PATH = "reportlets"; |
||||||
|
private static final int BUFFER_SIZE = 2 * 1024; |
||||||
|
|
||||||
|
public FeedbackToolboxDialog(Frame owner) { |
||||||
|
super(owner, Toolkit.i18nText("Fine-Design_Basic_Carton_Feedback_ToolBox")); |
||||||
|
setResizable(false); |
||||||
|
this.setLayout(FRGUIPaneFactory.createBorderLayout()); |
||||||
|
createBodyPanel(); |
||||||
|
add(body); |
||||||
|
setSize(body.getPreferredSize()); |
||||||
|
setSwitches(!StringUtils.isEmpty(GeneralUtils.objectToString(uiDatePicker.getSelectedItem()))); |
||||||
|
repaint(); |
||||||
|
GUICoreUtils.centerWindow(this); |
||||||
|
} |
||||||
|
|
||||||
|
public void createBodyPanel() { |
||||||
|
JPanel body = FRGUIPaneFactory.createBorderLayout_L_Pane(); |
||||||
|
body.setBackground(backgroundColor); |
||||||
|
JPanel titlePane = createTitlePane(); |
||||||
|
JPanel tailPane = createTailPane(); |
||||||
|
JPanel midPane = createMidPane(); |
||||||
|
JPanel infoPane = createInfoPane(); |
||||||
|
body.add(titlePane, BorderLayout.NORTH); |
||||||
|
body.add(tailPane, BorderLayout.SOUTH); |
||||||
|
body.add(midPane, BorderLayout.CENTER); |
||||||
|
midPane.add(infoPane, BorderLayout.NORTH); |
||||||
|
Dimension dimension = new Dimension(662, 556); |
||||||
|
body.setPreferredSize(dimension); |
||||||
|
this.body = body; |
||||||
|
} |
||||||
|
|
||||||
|
private JPanel createInfoPane() { |
||||||
|
JPanel northPane = FRGUIPaneFactory.createNColumnGridInnerContainer_Pane(2, 10, 10); |
||||||
|
UILabel title = new UILabel(); |
||||||
|
title.setText(" " + Toolkit.i18nText("Fine-Design_Basic_Carton_Record_Lag_Time") + ": "); |
||||||
|
//判断一下当天是否有卡顿日志记录,如果有将日期设置为当天,如果没有设置为空
|
||||||
|
boolean cartonExists = SwitchForSwingChecker.isCartonExists(); |
||||||
|
if (cartonExists) { |
||||||
|
this.uiDatePicker = new UIDatePicker(UIDatePicker.STYLE_CN_DATE1, this); |
||||||
|
} else { |
||||||
|
this.uiDatePicker = new UIDatePicker(UIDatePicker.STYLE_CN_DATE1, null, this); |
||||||
|
} |
||||||
|
Dimension dimension = new Dimension(160, 100); |
||||||
|
uiDatePicker.setPreferredSize(dimension); |
||||||
|
northPane.add(GUICoreUtils.createFlowPane(new Component[]{title, uiDatePicker}, FlowLayout.LEFT)); |
||||||
|
exportLogLabel = new UILabel(); |
||||||
|
exportLogLabel.setText(Toolkit.i18nText("Fine-Design_Basic_Carton_Export_Carton_Log")); |
||||||
|
exportLogLabel.setForeground(UIConstants.FLESH_BLUE); |
||||||
|
exportLogLabel.addMouseListener(new MouseAdapter() { |
||||||
|
@Override |
||||||
|
public void mouseClicked(MouseEvent e) { |
||||||
|
if (exportLogLabel.isEnabled()) { |
||||||
|
exportLogFile(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void mouseEntered(MouseEvent evt) { |
||||||
|
Object source = evt.getSource(); |
||||||
|
if (source instanceof UILabel) { |
||||||
|
((UILabel) source).setCursor(new Cursor(Cursor.HAND_CURSOR)); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
northPane.add(GUICoreUtils.createFlowPane(exportLogLabel, FlowLayout.RIGHT)); |
||||||
|
return northPane; |
||||||
|
} |
||||||
|
|
||||||
|
private void exportLogFile() { |
||||||
|
String selectDate = GeneralUtils.objectToString(uiDatePicker.getSelectedItem()); |
||||||
|
FILEChooserPane fileChooserPane = FILEChooserPane.getInstance(); |
||||||
|
StringBuilder fileName = new StringBuilder(); |
||||||
|
fileName.append(selectDate).append(Toolkit.i18nText("Fine-Design_Basic_Carton_Carton_Log")); |
||||||
|
fileChooserPane.setFileNameTextField(fileName.toString(), " "); |
||||||
|
fileChooserPane.removeAllFilter(); |
||||||
|
fileChooserPane.addChooseFILEFilter(new ChooseFileFilter("zip", Toolkit.i18nText("Fine-Design_Basic_Carton_Compile_File"))); |
||||||
|
//默认选择桌面
|
||||||
|
FILE desktop = FILEFactory.createFILE(FILEFactory.FILE_PREFIX + FileSystemView.getFileSystemView().getHomeDirectory().getPath()); |
||||||
|
fileChooserPane.setCurrentDirectory(desktop); |
||||||
|
int chooseResult = fileChooserPane.showSaveDialog(DesignerContext.getDesignerFrame()); |
||||||
|
if (chooseResult == 0) { |
||||||
|
FILE selectedFile = fileChooserPane.getSelectedFILE(); |
||||||
|
String path = selectedFile.getPath(); |
||||||
|
//selectDate 2002-03-09例子
|
||||||
|
String[] split = selectDate.split("-"); |
||||||
|
int month = Integer.parseInt(split[1]); |
||||||
|
String sourceFilePath = StableUtils.pathJoin(SwitchForSwingChecker.JOURNAL_FILE_PATH, split[0], "month-" + month, selectDate); |
||||||
|
File sourceFile = new File(sourceFilePath); |
||||||
|
if (sourceFile.exists()) { |
||||||
|
exportCartonLog(sourceFile, path, sourceFilePath); |
||||||
|
} |
||||||
|
fileChooserPane.removeAllFilter(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private JPanel createTailPane() { |
||||||
|
JPanel tailPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
||||||
|
tailPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, lineColor)); |
||||||
|
JPanel actionsPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
||||||
|
actionsPanel.setLayout(FRGUIPaneFactory.createM_BorderLayout()); |
||||||
|
{ |
||||||
|
uploadButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Carton_Upload_Carton_Log")); |
||||||
|
uploadButton.addActionListener((e) -> { |
||||||
|
try { |
||||||
|
List<CartonUploadMessage> list = SwitchForSwingChecker.uploadJournalLog(uiDatePicker.getSelectedDate()); |
||||||
|
if (list.isEmpty()) { |
||||||
|
FineJOptionPane.showMessageDialog(null, Toolkit.i18nText("Fine_Design_Basic_Upload_Fail"), UIManager.getString("OptionPane.messageDialogTitle"), JOptionPane.ERROR_MESSAGE); |
||||||
|
} else { |
||||||
|
FineJOptionPane.showMessageDialog(null, Toolkit.i18nText("Fine-Design_Basic_Upload_Success")); |
||||||
|
} |
||||||
|
} catch (ParseException parseException) { |
||||||
|
FineLoggerFactory.getLogger().error("parse error", parseException); |
||||||
|
} |
||||||
|
|
||||||
|
}); |
||||||
|
actionsPanel.add(uploadButton, BorderLayout.WEST); |
||||||
|
|
||||||
|
UIButton cancelButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Cancel")); |
||||||
|
cancelButton.addActionListener((e) -> { |
||||||
|
setVisible(false); |
||||||
|
EnvDetectorDialog envDetectorDialog = new EnvDetectorDialog(DesignerContext.getDesignerFrame()); |
||||||
|
envDetectorDialog.setVisible(true); |
||||||
|
dispose(); |
||||||
|
}); |
||||||
|
actionsPanel.add(cancelButton, BorderLayout.EAST); |
||||||
|
} |
||||||
|
UIButton currencySetButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Carton_General_Settings")); |
||||||
|
currencySetButton.addActionListener((e -> { |
||||||
|
createGeneralSettingPanel(); |
||||||
|
this.remove(body); |
||||||
|
this.add(generalSettingPanel); |
||||||
|
setSize(generalSettingPanel.getPreferredSize()); |
||||||
|
repaint(); |
||||||
|
setVisible(true); |
||||||
|
})); |
||||||
|
tailPanel.add(actionsPanel, BorderLayout.EAST); |
||||||
|
tailPanel.add(currencySetButton, BorderLayout.WEST); |
||||||
|
return tailPanel; |
||||||
|
} |
||||||
|
|
||||||
|
private JPanel createTitlePane() { |
||||||
|
JPanel titlePane = FRGUIPaneFactory.createBorderLayout_M_Pane(); |
||||||
|
titlePane.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, lineColor)); |
||||||
|
UILabel uiLabel = new UILabel(Toolkit.i18nText("Fine-Design_Basic_Carton_Carton_Journal_Record")); |
||||||
|
uiLabel.setForeground(UIConstants.FLESH_BLUE); |
||||||
|
titlePane.add(uiLabel, BorderLayout.WEST); |
||||||
|
return titlePane; |
||||||
|
} |
||||||
|
|
||||||
|
private JPanel createMidPane() { |
||||||
|
JPanel midPanel = FRGUIPaneFactory.createBorderLayout_L_Pane(); |
||||||
|
return midPanel; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 下面是通用设置的面板 |
||||||
|
*/ |
||||||
|
private void createGeneralSettingPanel() { |
||||||
|
JPanel generalSettingPanel = FRGUIPaneFactory.createBorderLayout_L_Pane(); |
||||||
|
JPanel tailPaneInGeneralSettings = createTailPaneInGeneralSettings(); |
||||||
|
generalSettingPanel.add(tailPaneInGeneralSettings, BorderLayout.SOUTH); |
||||||
|
|
||||||
|
JPanel titlePaneInGeneralSettings = createTitlePaneInGeneralSettings(); |
||||||
|
generalSettingPanel.add(titlePaneInGeneralSettings, BorderLayout.NORTH); |
||||||
|
|
||||||
|
JPanel midPanel = FRGUIPaneFactory.createBorderLayout_L_Pane(); |
||||||
|
generalSettingPanel.add(midPanel, BorderLayout.CENTER); |
||||||
|
|
||||||
|
JPanel infoPane = createInfoPanelInGeneralSettings(); |
||||||
|
midPanel.add(infoPane, BorderLayout.NORTH); |
||||||
|
|
||||||
|
Dimension dimension = new Dimension(662, 556); |
||||||
|
generalSettingPanel.setPreferredSize(dimension); |
||||||
|
generalSettingPanel.setBackground(backgroundColor); |
||||||
|
this.generalSettingPanel = generalSettingPanel; |
||||||
|
} |
||||||
|
|
||||||
|
private JPanel createTitlePaneInGeneralSettings() { |
||||||
|
JPanel titlePane = FRGUIPaneFactory.createBorderLayout_L_Pane(); |
||||||
|
titlePane.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, lineColor)); |
||||||
|
UILabel uiLabel = new UILabel(Toolkit.i18nText("Fine-Design_Basic_Carton_Carton_Journal_Record") + "/"); |
||||||
|
uiLabel.addMouseListener(new MouseAdapter() { |
||||||
|
@Override |
||||||
|
public void mouseClicked(MouseEvent e) { |
||||||
|
createBodyPanel(); |
||||||
|
remove(generalSettingPanel); |
||||||
|
add(body); |
||||||
|
setPreferredSize(body.getPreferredSize()); |
||||||
|
setSwitches(!StringUtils.isEmpty(GeneralUtils.objectToString(uiDatePicker.getSelectedItem()))); |
||||||
|
repaint(); |
||||||
|
setVisible(true); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void mouseEntered(MouseEvent evt) { |
||||||
|
Object source = evt.getSource(); |
||||||
|
if (source instanceof UILabel) { |
||||||
|
((UILabel) source).setCursor(new Cursor(Cursor.HAND_CURSOR)); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
UILabel uiCurrentLabel = new UILabel(Toolkit.i18nText("Fine-Design_Basic_Carton_General_Settings")); |
||||||
|
uiCurrentLabel.setForeground(UIConstants.FLESH_BLUE); |
||||||
|
titlePane.add(GUICoreUtils.createFlowPane(new Component[]{uiLabel, uiCurrentLabel}, FlowLayout.LEFT)); |
||||||
|
return titlePane; |
||||||
|
} |
||||||
|
|
||||||
|
private JPanel createTailPaneInGeneralSettings() { |
||||||
|
JPanel tailPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
||||||
|
tailPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, lineColor)); |
||||||
|
JPanel actionsPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
||||||
|
actionsPanel.setLayout(FRGUIPaneFactory.createM_BorderLayout()); |
||||||
|
{ |
||||||
|
UIButton confirmButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Save")); |
||||||
|
confirmButton.addActionListener((e) -> { |
||||||
|
if (easyCheckerButton.isSelected()) { |
||||||
|
SwitchForSwingChecker.startEasyChecker(); |
||||||
|
} else { |
||||||
|
SwitchForSwingChecker.stopEasyChecker(); |
||||||
|
} |
||||||
|
if (timerCheckerButton.isSelected()) { |
||||||
|
SwitchForSwingChecker.startTimerChecker(); |
||||||
|
} else { |
||||||
|
SwitchForSwingChecker.stopTimerChecker(); |
||||||
|
} |
||||||
|
createBodyPanel(); |
||||||
|
remove(generalSettingPanel); |
||||||
|
add(body); |
||||||
|
setPreferredSize(body.getPreferredSize()); |
||||||
|
setSwitches(!StringUtils.isEmpty(GeneralUtils.objectToString(uiDatePicker.getSelectedItem()))); |
||||||
|
repaint(); |
||||||
|
setVisible(true); |
||||||
|
}); |
||||||
|
actionsPanel.add(confirmButton, BorderLayout.WEST); |
||||||
|
|
||||||
|
UIButton cancelButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Cancel")); |
||||||
|
cancelButton.addActionListener((e) -> { |
||||||
|
createBodyPanel(); |
||||||
|
remove(generalSettingPanel); |
||||||
|
add(body); |
||||||
|
setPreferredSize(body.getPreferredSize()); |
||||||
|
repaint(); |
||||||
|
setVisible(true); |
||||||
|
|
||||||
|
}); |
||||||
|
actionsPanel.add(cancelButton, BorderLayout.EAST); |
||||||
|
} |
||||||
|
tailPanel.add(actionsPanel, BorderLayout.EAST); |
||||||
|
return tailPanel; |
||||||
|
} |
||||||
|
|
||||||
|
private JPanel createInfoPanelInGeneralSettings() { |
||||||
|
JPanel infoPane = FRGUIPaneFactory.createNColumnGridInnerContainer_S_Pane(1); |
||||||
|
easyCheckerButton = new UICheckBox(Toolkit.i18nText("Fine-Design_Basic_Carton_Operation_Time_Consuming_Detection")); |
||||||
|
timerCheckerButton = new UICheckBox(Toolkit.i18nText("Fine-Design_Basic_Carton_Carton_Operation_Class_Capture")); |
||||||
|
easyCheckerButton.setSelected(SwitchForSwingChecker.isEasyChecker()); |
||||||
|
timerCheckerButton.setSelected(SwitchForSwingChecker.isCheckerTimerSwitch()); |
||||||
|
infoPane.add(GUICoreUtils.createFlowPane(easyCheckerButton, FlowLayout.LEFT)); |
||||||
|
infoPane.add(GUICoreUtils.createFlowPane(timerCheckerButton, FlowLayout.LEFT)); |
||||||
|
return infoPane; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void processWindowEvent(WindowEvent e) { |
||||||
|
super.processWindowEvent(e); |
||||||
|
if (e.getID() == WindowEvent.WINDOW_CLOSING) { |
||||||
|
EnvDetectorDialog envDetectorDialog = new EnvDetectorDialog(DesignerContext.getDesignerFrame()); |
||||||
|
envDetectorDialog.setVisible(true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 上传和导出卡顿日志的可用化处理,如果没有选择日期就不可用 |
||||||
|
*/ |
||||||
|
public void setSwitches(boolean flag) { |
||||||
|
uploadButton.setEnabled(flag); |
||||||
|
exportLogLabel.setEnabled(flag); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 导出卡顿日志到本地或远程服务器WEB-INF下 |
||||||
|
* |
||||||
|
* @param sourceFile 导出的卡顿日志所在文件夹 |
||||||
|
* @param path 文件需要导出到的路径 |
||||||
|
* @param sourceFilePath 导出的卡顿日志所在文件夹的路径 |
||||||
|
*/ |
||||||
|
private void exportCartonLog(File sourceFile, String path, String sourceFilePath) { |
||||||
|
File[] files = sourceFile.listFiles(); |
||||||
|
if (!Objects.isNull(files)) { |
||||||
|
try { |
||||||
|
if (path.startsWith(WORK_SPACE_PATH)) { |
||||||
|
if (WorkContext.getCurrent().isLocal()) { |
||||||
|
String curEnvName = DesignerEnvManager.getEnvManager().getCurEnvName(); |
||||||
|
DesignerWorkspaceInfo workspaceInfo = DesignerEnvManager.getEnvManager().getWorkspaceInfo(curEnvName); |
||||||
|
String workspaceInfoPath = workspaceInfo.getPath(); |
||||||
|
path = StableUtils.pathJoin(workspaceInfoPath, path); |
||||||
|
LogZipUtils.compress(files, path, false); |
||||||
|
} else { |
||||||
|
String sourceFilePathZip = sourceFilePath + ".zip"; |
||||||
|
LogZipUtils.compress(files, sourceFilePathZip, false); |
||||||
|
byte[] bytesByFile = getBytesByFile(sourceFilePathZip); |
||||||
|
WorkContext.getWorkResource().write(path, bytesByFile); |
||||||
|
LogZipUtils.delDir(sourceFilePathZip); |
||||||
|
} |
||||||
|
} else { |
||||||
|
LogZipUtils.compress(files, path, false); |
||||||
|
} |
||||||
|
FineJOptionPane.showMessageDialog(this, Toolkit.i18nText("Fine-Design_Report_Exported_Successfully")); |
||||||
|
} catch (Exception exception) { |
||||||
|
FineJOptionPane.showMessageDialog(this, Toolkit.i18nText("Fine-Design_Report_Export_Failed"), UIManager.getString("OptionPane.messageDialogTitle"), JOptionPane.ERROR_MESSAGE); |
||||||
|
FineLoggerFactory.getLogger().error("export file fail", exception); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据文件地址将文件转换成byte[] |
||||||
|
* |
||||||
|
* @param pathStr 本地文件目录 |
||||||
|
* @return 本地文件转成的byte[] |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
private static byte[] getBytesByFile(String pathStr) { |
||||||
|
File file = new File(pathStr); |
||||||
|
try { |
||||||
|
FileInputStream fis = new FileInputStream(file); |
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream(BUFFER_SIZE); |
||||||
|
byte[] b = new byte[BUFFER_SIZE]; |
||||||
|
int n; |
||||||
|
while ((n = fis.read(b)) != -1) { |
||||||
|
bos.write(b, 0, n); |
||||||
|
} |
||||||
|
fis.close(); |
||||||
|
byte[] data = bos.toByteArray(); |
||||||
|
bos.close(); |
||||||
|
return data; |
||||||
|
} catch (Exception e) { |
||||||
|
FineLoggerFactory.getLogger().error("reading local file fail", e); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
package com.fr.design.carton; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
|
||||||
|
public class MonthlyCartonFile { |
||||||
|
private File currentMonthFile; |
||||||
|
private File lastMonthFile; |
||||||
|
private File nextMonthFile; |
||||||
|
public MonthlyCartonFile() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public File getCurrentMonthFile() { |
||||||
|
return currentMonthFile; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCurrentMonthFile(File currentMonthFile) { |
||||||
|
this.currentMonthFile = currentMonthFile; |
||||||
|
} |
||||||
|
|
||||||
|
public File getLastMonthFile() { |
||||||
|
return lastMonthFile; |
||||||
|
} |
||||||
|
|
||||||
|
public void setLastMonthFile(File lastMonthFile) { |
||||||
|
this.lastMonthFile = lastMonthFile; |
||||||
|
} |
||||||
|
|
||||||
|
public File getNextMonthFile() { |
||||||
|
return nextMonthFile; |
||||||
|
} |
||||||
|
|
||||||
|
public void setNextMonthFile(File nextMonthFile) { |
||||||
|
this.nextMonthFile = nextMonthFile; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,312 @@ |
|||||||
|
package com.fr.design.carton; |
||||||
|
|
||||||
|
|
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.general.GeneralUtils; |
||||||
|
import com.fr.json.JSON; |
||||||
|
import com.fr.json.JSONArray; |
||||||
|
import com.fr.json.JSONObject; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.stable.ProductConstantsBase; |
||||||
|
import com.fr.stable.StableUtils; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import com.fr.stable.xml.XMLPrintWriter; |
||||||
|
import com.fr.stable.xml.XMLReadable; |
||||||
|
import com.fr.stable.xml.XMLWriter; |
||||||
|
import com.fr.stable.xml.XMLableReader; |
||||||
|
import sun.awt.AppContext; |
||||||
|
|
||||||
|
import javax.swing.SwingWorker; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.File; |
||||||
|
import java.io.BufferedReader; |
||||||
|
import java.io.FileReader; |
||||||
|
import java.text.SimpleDateFormat; |
||||||
|
import java.util.List; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Date; |
||||||
|
import java.util.Calendar; |
||||||
|
|
||||||
|
public class SwitchForSwingChecker implements XMLReadable, XMLWriter { |
||||||
|
/** |
||||||
|
* Designer4Debug类名 |
||||||
|
*/ |
||||||
|
private static final String DEBUG_MAIN_CLASS_NAME = "com.fr.start.Designer4Debug"; |
||||||
|
/** |
||||||
|
* XML标签 |
||||||
|
*/ |
||||||
|
public static final String XML_TAG = "SwitchForSwingChecker"; |
||||||
|
/** |
||||||
|
* 定时任务的开关 |
||||||
|
*/ |
||||||
|
private static boolean checkerTimerSwitch = false; |
||||||
|
/** |
||||||
|
* 简单记录事件执行时间的开关 |
||||||
|
*/ |
||||||
|
private static boolean easyChecker = false; |
||||||
|
/** |
||||||
|
* 一个标识位用于区分耗时任务时长检测(简单检测)和timer检测 |
||||||
|
*/ |
||||||
|
public static final int TIMER_CHECK_FLAG = 0; |
||||||
|
public static final int EASY_CHECK_FLAG = 1; |
||||||
|
|
||||||
|
/** |
||||||
|
* 日志存储地址 |
||||||
|
*/ |
||||||
|
public static final String JOURNAL_FILE_PATH = StableUtils.pathJoin(ProductConstantsBase.getEnvHome(), "journal_log"); |
||||||
|
public static final String EASY_CHECKER_FILE_NAME = "easy_check_log.csv"; |
||||||
|
public static final String TIMER_CHECKER_FILE_NAME = "timer_check_log.csv"; |
||||||
|
public static boolean isCheckerTimerSwitch() { |
||||||
|
return checkerTimerSwitch; |
||||||
|
} |
||||||
|
|
||||||
|
public static boolean isEasyChecker() { |
||||||
|
return easyChecker; |
||||||
|
} |
||||||
|
|
||||||
|
public static volatile SwitchForSwingChecker switchForSwingChecker = new SwitchForSwingChecker(); |
||||||
|
|
||||||
|
public static SwitchForSwingChecker getInstance() { |
||||||
|
return switchForSwingChecker; |
||||||
|
} |
||||||
|
|
||||||
|
public static void startTimerChecker() { |
||||||
|
if (!checkerTimerSwitch) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.initTimer(); |
||||||
|
CartonThreadExecutorPool.getTimerThreadExecutorPool().initTimer(); |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.setTimerWitch(true); |
||||||
|
checkerTimerSwitch = true; |
||||||
|
if (!easyChecker) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.startFilterModalWindow(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void stopTimerChecker() { |
||||||
|
if (checkerTimerSwitch) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.stopTimer(); |
||||||
|
CartonThreadExecutorPool.getTimerThreadExecutorPool().stopTimer(); |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.setTimerWitch(false); |
||||||
|
checkerTimerSwitch = false; |
||||||
|
if (!easyChecker) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.stopFilterModalWindow(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void startEasyChecker() { |
||||||
|
if (!easyChecker) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.setEasyWitch(true); |
||||||
|
CartonThreadExecutorPool.getTimerThreadExecutorPool().setEasyWitch(true); |
||||||
|
easyChecker = true; |
||||||
|
if (!checkerTimerSwitch) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.startFilterModalWindow(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void stopEasyChecker() { |
||||||
|
if (easyChecker) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.setEasyWitch(false); |
||||||
|
CartonThreadExecutorPool.getTimerThreadExecutorPool().setEasyWitch(false); |
||||||
|
easyChecker = false; |
||||||
|
if (!checkerTimerSwitch) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.stopFilterModalWindow(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取文件名字以及判断文件是否存在 |
||||||
|
*/ |
||||||
|
private static CartonFiles getFiles(String date) { |
||||||
|
String[] split = date.split("-"); |
||||||
|
int month = StringUtils.isEmpty(split[1]) ? -1 : Integer.parseInt(split[1]); |
||||||
|
String dirPath = StableUtils.pathJoin(JOURNAL_FILE_PATH, split[0], "month-" + month, date); |
||||||
|
File file1 = new File(StableUtils.pathJoin(dirPath, EASY_CHECKER_FILE_NAME)); |
||||||
|
File file2 = new File(StableUtils.pathJoin(dirPath, TIMER_CHECKER_FILE_NAME)); |
||||||
|
File[] files = new File[2]; |
||||||
|
files[0] = file1; |
||||||
|
files[1] = file2; |
||||||
|
CartonFiles cartonFiles = new CartonFiles(); |
||||||
|
cartonFiles.setEasyCheckerFile(file1); |
||||||
|
cartonFiles.setTimerCheckerFile(file2); |
||||||
|
return cartonFiles; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*处理文件 |
||||||
|
* 一共四种情况, |
||||||
|
* 两个文件都不存在 |
||||||
|
* 文件一存在,文件二不存在 |
||||||
|
* 文件二存在,文件一不存在 |
||||||
|
* 两个文件都存在 |
||||||
|
*/ |
||||||
|
private static List<CartonUploadMessage> getCartonLog(File easyFile, File timerFile) { |
||||||
|
List<CartonUploadMessage> res = new ArrayList<>(); |
||||||
|
List<CartonUploadMessage> easyFileCartonLog = getEasyFileCartonLog(easyFile); |
||||||
|
List<CartonUploadMessage> timerFileCartonLog = getTimerFileCartonLog(timerFile); |
||||||
|
Map<String, CartonUploadMessage> easyFileMap = new HashMap<>(); |
||||||
|
for (CartonUploadMessage cartonUploadMessage : easyFileCartonLog) { |
||||||
|
easyFileMap.put(cartonUploadMessage.getHangCount(), cartonUploadMessage); |
||||||
|
res.add(cartonUploadMessage); |
||||||
|
} |
||||||
|
for (CartonUploadMessage cartonUploadMessage : timerFileCartonLog) { |
||||||
|
String hangCount = cartonUploadMessage.getHangCount(); |
||||||
|
if (easyFileMap.containsKey(hangCount)) { |
||||||
|
cartonUploadMessage.setThreadTime(easyFileMap.get(hangCount).getThreadTime()); |
||||||
|
} |
||||||
|
res.add(cartonUploadMessage); |
||||||
|
} |
||||||
|
return res; |
||||||
|
} |
||||||
|
|
||||||
|
private static List<CartonUploadMessage> getTimerFileCartonLog(File file) { |
||||||
|
List<CartonUploadMessage> res = new ArrayList<>(); |
||||||
|
try { |
||||||
|
StringBuilder stringBuilder = new StringBuilder(); |
||||||
|
stringBuilder.append("["); |
||||||
|
BufferedReader bufferedReader1 = new BufferedReader(new FileReader(file)); |
||||||
|
String line1; |
||||||
|
while ((line1 = bufferedReader1.readLine()) != null) { |
||||||
|
stringBuilder.append(line1); |
||||||
|
} |
||||||
|
bufferedReader1.close(); |
||||||
|
stringBuilder.append("]"); |
||||||
|
JSONArray easyCheckerJSON = JSON.ARRAY.createJSON(GeneralUtils.objectToString(stringBuilder)); |
||||||
|
for (Object jsonObject : easyCheckerJSON) { |
||||||
|
CartonUploadMessage cartonUploadMessage = new CartonUploadMessage(); |
||||||
|
JSONObject x = (JSONObject) jsonObject; |
||||||
|
cartonUploadMessage.setHangCount(x.getString(Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"))); |
||||||
|
cartonUploadMessage.setSlowTime(x.getString(Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"))); |
||||||
|
cartonUploadMessage.setThreadTime("undefined"); |
||||||
|
//这个跟输出到文件中的格式匹配,参考EventDis里的stackTraceToString方法
|
||||||
|
String indentation = " "; |
||||||
|
String logMessage = x.getString(Toolkit.i18nText("Fine-Design_Basic_Carton_Stack_Info")).replaceAll(indentation, "\r\n "); |
||||||
|
cartonUploadMessage.setInfo(logMessage); |
||||||
|
res.add(cartonUploadMessage); |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
FineLoggerFactory.getLogger().error("upload fail", e); |
||||||
|
} |
||||||
|
return res; |
||||||
|
} |
||||||
|
|
||||||
|
private static List<CartonUploadMessage> getEasyFileCartonLog(File file) { |
||||||
|
List<CartonUploadMessage> res = new ArrayList<>(); |
||||||
|
try { |
||||||
|
StringBuilder stringBuilder = new StringBuilder(); |
||||||
|
stringBuilder.append("["); |
||||||
|
BufferedReader bufferedReader1 = new BufferedReader(new FileReader(file)); |
||||||
|
String line1; |
||||||
|
while ((line1 = bufferedReader1.readLine()) != null) { |
||||||
|
stringBuilder.append(line1); |
||||||
|
} |
||||||
|
bufferedReader1.close(); |
||||||
|
stringBuilder.append("]"); |
||||||
|
JSONArray timerCheckerJSON = JSON.ARRAY.createJSON(GeneralUtils.objectToString(stringBuilder)); |
||||||
|
for (Object jsonObject : timerCheckerJSON) { |
||||||
|
JSONObject x = (JSONObject) jsonObject; |
||||||
|
CartonUploadMessage cartonUploadMessage = new CartonUploadMessage(); |
||||||
|
cartonUploadMessage.setHangCount(x.getString(Toolkit.i18nText("Fine-Design_Basic_Carton_Event_Number"))); |
||||||
|
cartonUploadMessage.setSlowTime(x.getString(Toolkit.i18nText("Fine-Design_Basic_Carton_Output_Time"))); |
||||||
|
cartonUploadMessage.setThreadTime(x.getString(Toolkit.i18nText("Fine-Design_Basic_Carton_Task_Total_Time"))); |
||||||
|
cartonUploadMessage.setInfo("undefined"); |
||||||
|
res.add(cartonUploadMessage); |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
FineLoggerFactory.getLogger().error("upload fail", e); |
||||||
|
} |
||||||
|
return res; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* /埋点方法上传卡顿信息入口 |
||||||
|
date为 2022-09-08的格式 |
||||||
|
*/ |
||||||
|
public static List<CartonUploadMessage> uploadJournalLog(Date dateTime) { |
||||||
|
List<CartonUploadMessage> res = new ArrayList<>(); |
||||||
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); |
||||||
|
CartonFiles files = getFiles(simpleDateFormat.format(dateTime)); |
||||||
|
File easyCheckerFile = files.getEasyCheckerFile(); |
||||||
|
File timerCheckerFile = files.getTimerCheckerFile(); |
||||||
|
if (easyCheckerFile.exists() && timerCheckerFile.exists()) { |
||||||
|
return getCartonLog(easyCheckerFile, timerCheckerFile); |
||||||
|
} else if (easyCheckerFile.exists()) { |
||||||
|
return getEasyFileCartonLog(easyCheckerFile); |
||||||
|
} else if (timerCheckerFile.exists()) { |
||||||
|
return getTimerFileCartonLog(timerCheckerFile); |
||||||
|
} else { |
||||||
|
return res; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 初始化监控任务,主要是替换EventQueue以及SwingWorker执行任务的线程池 |
||||||
|
* |
||||||
|
*/ |
||||||
|
public static void initThreadMonitoring () { |
||||||
|
String mainClass = System.getProperty("sun.java.command"); |
||||||
|
//判断一下,如果是以Designer4Debug启动,就不注册代码,不然会覆盖掉SwingExplorer,导致其无法使用
|
||||||
|
if (!StringUtils.equals(mainClass, DEBUG_MAIN_CLASS_NAME)) { |
||||||
|
EventDispatchThreadHangMonitor.initMonitoring(); |
||||||
|
AppContext.getAppContext().put(SwingWorker.class, CartonThreadExecutorPool.getTimerThreadExecutorPool()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 判断是否有指定日期的卡顿日志,没有就返回false |
||||||
|
*/ |
||||||
|
public static boolean isCartonExists(Date date) { |
||||||
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); |
||||||
|
String format = simpleDateFormat.format(date); |
||||||
|
Calendar calendar = Calendar.getInstance(); |
||||||
|
int month = calendar.get(Calendar.MONTH) + 1; |
||||||
|
int year = calendar.get(Calendar.YEAR); |
||||||
|
File file = new File(StableUtils.pathJoin(JOURNAL_FILE_PATH, String.valueOf(year), "month-" + month, format)); |
||||||
|
return file.exists(); |
||||||
|
} |
||||||
|
|
||||||
|
public static boolean isCartonExists() { |
||||||
|
return isCartonExists(new Date()); |
||||||
|
} |
||||||
|
|
||||||
|
private void initSwitchChecker() { |
||||||
|
if (easyChecker) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.setEasyWitch(true); |
||||||
|
CartonThreadExecutorPool.getTimerThreadExecutorPool().setEasyWitch(true); |
||||||
|
} |
||||||
|
if (checkerTimerSwitch) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.initTimer(); |
||||||
|
CartonThreadExecutorPool.getTimerThreadExecutorPool().initTimer(); |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.setTimerWitch(true); |
||||||
|
} |
||||||
|
if (easyChecker || checkerTimerSwitch) { |
||||||
|
EventDispatchThreadHangMonitor.INSTANCE.startFilterModalWindow(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void readXML(XMLableReader reader) { |
||||||
|
if (reader.isAttr()) { |
||||||
|
checkerTimerSwitch = reader.getAttrAsBoolean("checkerTimerSwitch", false); |
||||||
|
easyChecker = reader.getAttrAsBoolean("easyChecker", false); |
||||||
|
} |
||||||
|
try { |
||||||
|
initSwitchChecker(); |
||||||
|
} catch (Throwable t) { |
||||||
|
FineLoggerFactory.getLogger().error("read checker attr fail", t); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeXML(XMLPrintWriter writer) { |
||||||
|
writer.startTAG(XML_TAG); |
||||||
|
writer.attr("checkerTimerSwitch", checkerTimerSwitch); |
||||||
|
writer.attr("easyChecker", easyChecker); |
||||||
|
writer.end(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,180 @@ |
|||||||
|
package com.fr.design.cell; |
||||||
|
|
||||||
|
import com.fr.base.CellBorderSourceFlag; |
||||||
|
import com.fr.base.CellBorderStyle; |
||||||
|
import com.fr.base.Style; |
||||||
|
import com.fr.design.mainframe.theme.utils.DefaultThemedTemplateCellElementCase; |
||||||
|
import com.fr.general.IOUtils; |
||||||
|
import com.fr.report.cell.TemplateCellElement; |
||||||
|
|
||||||
|
import javax.swing.JPanel; |
||||||
|
import java.awt.AlphaComposite; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Composite; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.Graphics; |
||||||
|
import java.awt.Graphics2D; |
||||||
|
import java.awt.GridLayout; |
||||||
|
import java.awt.Rectangle; |
||||||
|
import java.awt.RenderingHints; |
||||||
|
import java.awt.image.BufferedImage; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Starryi |
||||||
|
* @version 1.0 |
||||||
|
* Created by Starryi on 2021/9/3 |
||||||
|
*/ |
||||||
|
public class CellRectangleStylePreviewPane extends JPanel { |
||||||
|
|
||||||
|
private static final BufferedImage transparentBackgroundImage = IOUtils.readImage("/com/fr/design/images/transparent_background.png"); |
||||||
|
private final float transparentBackgroundWidth; |
||||||
|
private final float transparentBackgroundHeight; |
||||||
|
|
||||||
|
private static final int ROW_COUNT = 2; |
||||||
|
private static final int COLUMN_COUNT = 2; |
||||||
|
|
||||||
|
private final TemplateCellElement[][] cellElementGrid = new TemplateCellElement[ROW_COUNT][COLUMN_COUNT]; |
||||||
|
private final int[][] borderSourceFlags = new int[ROW_COUNT][COLUMN_COUNT]; |
||||||
|
private final CellStylePreviewPane[][] cellStylePreviewPaneGrid = new CellStylePreviewPane[ROW_COUNT][COLUMN_COUNT]; |
||||||
|
|
||||||
|
public CellRectangleStylePreviewPane(boolean supportInnerBorder) { |
||||||
|
transparentBackgroundWidth = transparentBackgroundImage.getWidth(null); |
||||||
|
transparentBackgroundHeight = transparentBackgroundImage.getHeight(null); |
||||||
|
|
||||||
|
setLayout(new GridLayout(2, 2)); |
||||||
|
setOpaque(false); |
||||||
|
setBackground(null); |
||||||
|
|
||||||
|
for (int r = 0; r < ROW_COUNT; r++) { |
||||||
|
for (int c = 0; c < COLUMN_COUNT; c++) { |
||||||
|
CellStylePreviewPane pane = new CellStylePreviewPane(c, r, COLUMN_COUNT, ROW_COUNT, false, false); |
||||||
|
TemplateCellElement cellElement = DefaultThemedTemplateCellElementCase.createInstance(c, r); |
||||||
|
int flags = CellBorderSourceFlag.INVALID_BORDER_SOURCE; |
||||||
|
if (supportInnerBorder) { |
||||||
|
flags = CellBorderSourceFlag.ALL_BORDER_SOURCE_OUTER; |
||||||
|
if (r != 0) { |
||||||
|
flags |= CellBorderSourceFlag.TOP_BORDER_SOURCE_INNER; |
||||||
|
} |
||||||
|
if (r != ROW_COUNT - 1) { |
||||||
|
flags |= CellBorderSourceFlag.BOTTOM_BORDER_SOURCE_INNER; |
||||||
|
} |
||||||
|
if (c != 0) { |
||||||
|
flags |= CellBorderSourceFlag.LEFT_BORDER_SOURCE_INNER; |
||||||
|
} |
||||||
|
if (c != COLUMN_COUNT - 1) { |
||||||
|
flags |= CellBorderSourceFlag.RIGHT_BORDER_SOURCE_INNER; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
pane.setStyle(cellElement.getStyle()); |
||||||
|
add(pane); |
||||||
|
|
||||||
|
cellElementGrid[r][c] = cellElement; |
||||||
|
borderSourceFlags[r][c] = flags; |
||||||
|
cellStylePreviewPaneGrid[r][c] = pane; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void setPlainText(String text) { |
||||||
|
cellStylePreviewPaneGrid[0][1].setPaintText(text); |
||||||
|
cellStylePreviewPaneGrid[1][1].setPaintText(text); |
||||||
|
repaint(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setStyle(Style style, CellBorderStyle borderStyle) { |
||||||
|
for (int i = 0; i < ROW_COUNT; i++) { |
||||||
|
for (int j = 0; j < COLUMN_COUNT; j++) { |
||||||
|
CellStylePreviewPane pane = cellStylePreviewPaneGrid[i][j]; |
||||||
|
TemplateCellElement cellElement = cellElementGrid[i][j]; |
||||||
|
int flag = borderSourceFlags[i][j]; |
||||||
|
cellElement.setStyle(CellBorderSourceFlag.deriveBorderedStyle(style, borderStyle, flag)); |
||||||
|
|
||||||
|
pane.setStyle(cellElement.getStyle()); |
||||||
|
} |
||||||
|
} |
||||||
|
repaint(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setPreferredSize(Dimension preferredSize) { |
||||||
|
super.setPreferredSize(preferredSize); |
||||||
|
int hw = preferredSize.width / 2; |
||||||
|
int hh = preferredSize.height / 2; |
||||||
|
cellStylePreviewPaneGrid[0][0].setPreferredSize(new Dimension(hw, hh)); |
||||||
|
cellStylePreviewPaneGrid[0][1].setPreferredSize(new Dimension(hw, hh)); |
||||||
|
cellStylePreviewPaneGrid[1][0].setPreferredSize(new Dimension(hw, hh)); |
||||||
|
cellStylePreviewPaneGrid[1][1].setPreferredSize(new Dimension(hw, hh)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Dimension getPreferredSize() { |
||||||
|
Dimension d00 = cellStylePreviewPaneGrid[0][0].getPreferredSize(); |
||||||
|
Dimension d01 = cellStylePreviewPaneGrid[0][1].getPreferredSize(); |
||||||
|
Dimension d10 = cellStylePreviewPaneGrid[1][0].getPreferredSize(); |
||||||
|
Dimension d11 = cellStylePreviewPaneGrid[1][1].getPreferredSize(); |
||||||
|
|
||||||
|
int width = Math.max(d00.width + d01.width, d10.width + d11.width); |
||||||
|
int height = Math.max(d00.height + d10.height, d01.height + d11.height); |
||||||
|
|
||||||
|
return new Dimension(width, height); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void paint(Graphics g) { |
||||||
|
Graphics2D g2d = (Graphics2D) g; |
||||||
|
g2d.clearRect(0, 0, getWidth(), getHeight()); |
||||||
|
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); |
||||||
|
|
||||||
|
paintTransparentBackground((Graphics2D) g, cellElementGrid[0][0].getStyle()); |
||||||
|
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); |
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); |
||||||
|
|
||||||
|
super.paint(g); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Rectangle getBounds() { |
||||||
|
return super.getBounds(); |
||||||
|
} |
||||||
|
|
||||||
|
private void paintTransparentBackground(Graphics2D g2d, Style style) { |
||||||
|
float alpha = computeTransparentBackgroundAlpha(style); |
||||||
|
|
||||||
|
float scaleWidth = 1.0F * getWidth() / transparentBackgroundWidth; |
||||||
|
float scaleHeight = 1.0F * getHeight() / transparentBackgroundHeight; |
||||||
|
float maxScale = Math.max(scaleWidth, scaleHeight); |
||||||
|
|
||||||
|
if (maxScale <= 1) { |
||||||
|
scaleWidth = scaleHeight = 1; |
||||||
|
} else { |
||||||
|
scaleHeight = scaleWidth = maxScale; |
||||||
|
} |
||||||
|
|
||||||
|
Composite oldComposite = g2d.getComposite(); |
||||||
|
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); |
||||||
|
g2d.drawImage(transparentBackgroundImage, 0, 0, (int) (transparentBackgroundWidth * scaleWidth), (int) (transparentBackgroundHeight * scaleHeight), null); |
||||||
|
g2d.setComposite(oldComposite); |
||||||
|
} |
||||||
|
|
||||||
|
private float computeTextColorBrightness(Style style) { |
||||||
|
Color fontColor = style.getFRFont().getForeground(); |
||||||
|
return fontColor.getRed() * 0.299F + fontColor.getGreen() * 0.587F + fontColor.getBlue() * 0.114F; |
||||||
|
} |
||||||
|
|
||||||
|
private float computeTransparentBackgroundAlpha(Style style) { |
||||||
|
float textBrightness = computeTextColorBrightness(style); |
||||||
|
|
||||||
|
float alpha = 1.0F; |
||||||
|
if (textBrightness < 50) { |
||||||
|
alpha = 0.2F; |
||||||
|
} else if (textBrightness < 160){ |
||||||
|
alpha = 0.5F; |
||||||
|
} |
||||||
|
return alpha; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,148 @@ |
|||||||
|
package com.fr.design.components.loading; |
||||||
|
|
||||||
|
import javax.swing.JComponent; |
||||||
|
import javax.swing.Timer; |
||||||
|
import java.awt.AlphaComposite; |
||||||
|
import java.awt.BasicStroke; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Component; |
||||||
|
import java.awt.Composite; |
||||||
|
import java.awt.Container; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.Graphics; |
||||||
|
import java.awt.Graphics2D; |
||||||
|
import java.awt.LayoutManager; |
||||||
|
import java.awt.RenderingHints; |
||||||
|
import java.awt.event.ActionEvent; |
||||||
|
import java.awt.event.ActionListener; |
||||||
|
import java.awt.event.MouseAdapter; |
||||||
|
import java.awt.event.MouseEvent; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author hades |
||||||
|
* @version 10.0 |
||||||
|
* Created by hades on 2021/4/12 |
||||||
|
*/ |
||||||
|
public class LoadingPane extends JComponent implements ActionListener { |
||||||
|
|
||||||
|
private AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f); |
||||||
|
private volatile boolean mIsRunning; |
||||||
|
private volatile boolean mIsFadingOut; |
||||||
|
private Timer mTimer; |
||||||
|
private int mAngle; |
||||||
|
private int mFadeCount; |
||||||
|
private int mFadeLimit = 15; |
||||||
|
private int lines = 12; |
||||||
|
private int maxAngle = 360; |
||||||
|
private int angleAdd = 30; |
||||||
|
|
||||||
|
public LoadingPane() { |
||||||
|
|
||||||
|
addMouseListener(new MouseAdapter() { |
||||||
|
@Override |
||||||
|
public void mouseClicked(MouseEvent e) { |
||||||
|
// do nothing
|
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
setLayout(getCoverLayout()); |
||||||
|
setBackground(null); |
||||||
|
setOpaque(false); |
||||||
|
} |
||||||
|
|
||||||
|
protected LayoutManager getCoverLayout() { |
||||||
|
return new LayoutManager() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void removeLayoutComponent(Component comp) { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Dimension preferredLayoutSize(Container parent) { |
||||||
|
return parent.getPreferredSize(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Dimension minimumLayoutSize(Container parent) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void layoutContainer(Container parent) { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void addLayoutComponent(String name, Component comp) { |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void paint(Graphics g) { |
||||||
|
int w = this.getWidth(); |
||||||
|
int h = this.getHeight(); |
||||||
|
super.paint(g); |
||||||
|
if (!mIsRunning) { |
||||||
|
return; |
||||||
|
} |
||||||
|
Graphics2D g2 = (Graphics2D) g.create(); |
||||||
|
float fade = (float) mFadeCount / (float) mFadeLimit; |
||||||
|
Composite urComposite = g2.getComposite(); |
||||||
|
g2.setComposite(composite); |
||||||
|
g2.fillRect(0, 0, w, h); |
||||||
|
g2.setComposite(urComposite); |
||||||
|
int s = Math.min(w, h) / 50; |
||||||
|
int cx = w / 2; |
||||||
|
int cy = h / 2; |
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
||||||
|
g2.setStroke(new BasicStroke(s / 4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
||||||
|
g2.setPaint(Color.BLACK); |
||||||
|
g2.rotate(Math.PI * mAngle / 180, cx, cy); |
||||||
|
for (int i = 0; i < lines; i++) { |
||||||
|
float scale = (11.0f - (float) i) / 11.0f; |
||||||
|
g2.drawLine(cx + s, cy, cx + s * 2, cy); |
||||||
|
g2.rotate(-Math.PI / 6, cx, cy); |
||||||
|
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, scale * fade)); |
||||||
|
} |
||||||
|
g2.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
if (mIsRunning) { |
||||||
|
repaint(); |
||||||
|
mAngle += angleAdd; |
||||||
|
if (mAngle >= maxAngle) { |
||||||
|
mAngle = 0; |
||||||
|
} |
||||||
|
if (mIsFadingOut) { |
||||||
|
if (--mFadeCount == 0) { |
||||||
|
mIsRunning = false; |
||||||
|
mTimer.stop(); |
||||||
|
} |
||||||
|
} else if (mFadeCount < mFadeLimit) { |
||||||
|
mFadeCount++; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void start() { |
||||||
|
if (mIsRunning) { |
||||||
|
return; |
||||||
|
} |
||||||
|
mIsRunning = true; |
||||||
|
mIsFadingOut = false; |
||||||
|
mFadeCount = 0; |
||||||
|
int fps = 24; |
||||||
|
int tick = 1000 / fps; |
||||||
|
mTimer = new Timer(tick, this); |
||||||
|
mTimer.start(); |
||||||
|
} |
||||||
|
|
||||||
|
public void stop() { |
||||||
|
mIsRunning = false; |
||||||
|
mIsFadingOut = true; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
package com.fr.design.components.notification; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/05/24 |
||||||
|
**/ |
||||||
|
public interface NotificationAction { |
||||||
|
|
||||||
|
/** |
||||||
|
* 行为名 |
||||||
|
* |
||||||
|
* @return 名称 |
||||||
|
*/ |
||||||
|
String name(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 行为动作 |
||||||
|
* |
||||||
|
* @param args 参数 |
||||||
|
*/ |
||||||
|
void run(Object... args); |
||||||
|
} |
@ -0,0 +1,391 @@ |
|||||||
|
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
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
package com.fr.design.components.notification; |
||||||
|
|
||||||
|
import java.awt.Frame; |
||||||
|
|
||||||
|
/** |
||||||
|
* 通知会话的属性 |
||||||
|
* |
||||||
|
* created by Harrison on 2022/05/24 |
||||||
|
**/ |
||||||
|
public class NotificationDialogProperties { |
||||||
|
|
||||||
|
private Frame owner; |
||||||
|
|
||||||
|
private String title; |
||||||
|
|
||||||
|
private boolean modal; |
||||||
|
|
||||||
|
public NotificationDialogProperties(Frame owner, String title) { |
||||||
|
this.owner = owner; |
||||||
|
this.title = title; |
||||||
|
this.modal = false; |
||||||
|
} |
||||||
|
|
||||||
|
public void setModal(boolean modal) { |
||||||
|
this.modal = modal; |
||||||
|
} |
||||||
|
|
||||||
|
public Frame getOwner() { |
||||||
|
return owner; |
||||||
|
} |
||||||
|
|
||||||
|
public String getTitle() { |
||||||
|
return title; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isModal() { |
||||||
|
return modal; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,88 @@ |
|||||||
|
package com.fr.design.components.notification; |
||||||
|
|
||||||
|
import com.fr.design.utils.LinkStrUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/05/24 |
||||||
|
**/ |
||||||
|
public interface NotificationMessage { |
||||||
|
|
||||||
|
/** |
||||||
|
* 格式化 |
||||||
|
* |
||||||
|
* @return 通知信息 |
||||||
|
*/ |
||||||
|
String format(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 类型 |
||||||
|
* |
||||||
|
* @return 类型 |
||||||
|
*/ |
||||||
|
Type getType(); |
||||||
|
|
||||||
|
enum Type { |
||||||
|
|
||||||
|
/** |
||||||
|
* 简单型 |
||||||
|
*/ |
||||||
|
SIMPLE, |
||||||
|
|
||||||
|
/** |
||||||
|
* 链接 |
||||||
|
*/ |
||||||
|
LINK |
||||||
|
} |
||||||
|
|
||||||
|
class SimpleMessage implements NotificationMessage { |
||||||
|
|
||||||
|
private String text; |
||||||
|
|
||||||
|
public SimpleMessage(String text) { |
||||||
|
this.text = text; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String format() { |
||||||
|
return text; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Type getType() { |
||||||
|
return Type.SIMPLE; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class LinkMessage implements NotificationMessage { |
||||||
|
|
||||||
|
private String text; |
||||||
|
|
||||||
|
private String link; |
||||||
|
|
||||||
|
public LinkMessage(String text, String link) { |
||||||
|
|
||||||
|
this.text = text; |
||||||
|
this.link = link; |
||||||
|
} |
||||||
|
|
||||||
|
public String getText() { |
||||||
|
return text; |
||||||
|
} |
||||||
|
|
||||||
|
public String getLink() { |
||||||
|
return link; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String format() { |
||||||
|
|
||||||
|
return LinkStrUtils.generateHtmlTag(text); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Type getType() { |
||||||
|
|
||||||
|
return Type.LINK; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
package com.fr.design.components.notification; |
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class NotificationModel { |
||||||
|
|
||||||
|
private final NotificationType type; |
||||||
|
private final NotificationMessage[] messages; |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private final NotificationAction action; |
||||||
|
|
||||||
|
public NotificationModel(NotificationType type, @Nullable NotificationAction action, List<NotificationMessage> messages) { |
||||||
|
this(type, action, messages.toArray(new NotificationMessage[0])); |
||||||
|
} |
||||||
|
|
||||||
|
public NotificationModel(NotificationType type, @Nullable NotificationAction action, NotificationMessage... messages) { |
||||||
|
|
||||||
|
this.type = type; |
||||||
|
this.messages = messages; |
||||||
|
this.action = action; |
||||||
|
} |
||||||
|
|
||||||
|
public NotificationType getType() { |
||||||
|
return type; |
||||||
|
} |
||||||
|
|
||||||
|
public NotificationMessage[] getMessages() { |
||||||
|
return messages == null ? new NotificationMessage[0] : messages; |
||||||
|
} |
||||||
|
|
||||||
|
public @Nullable NotificationAction getAction() { |
||||||
|
|
||||||
|
return action; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
package com.fr.design.components.notification; |
||||||
|
|
||||||
|
/** |
||||||
|
* 提醒类型 |
||||||
|
* 决定图标种类 |
||||||
|
* |
||||||
|
* created by Harrison on 2022/05/27 |
||||||
|
**/ |
||||||
|
public enum NotificationType { |
||||||
|
|
||||||
|
INFO, |
||||||
|
|
||||||
|
ERROR, |
||||||
|
|
||||||
|
WARNING |
||||||
|
} |
@ -0,0 +1,78 @@ |
|||||||
|
package com.fr.design.components.page; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/05/26 |
||||||
|
**/ |
||||||
|
public class PageControlModel { |
||||||
|
|
||||||
|
/** |
||||||
|
* 当前索引 |
||||||
|
* |
||||||
|
* = (页数-1) |
||||||
|
*/ |
||||||
|
private int index; |
||||||
|
|
||||||
|
/** |
||||||
|
* 总页数 |
||||||
|
*/ |
||||||
|
private int summary; |
||||||
|
|
||||||
|
public PageControlModel(int index, int summary) { |
||||||
|
this.index = index; |
||||||
|
this.summary = summary; |
||||||
|
} |
||||||
|
|
||||||
|
public PageControlModel() { |
||||||
|
} |
||||||
|
|
||||||
|
public PageControlModel previous() { |
||||||
|
|
||||||
|
this.index--; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public PageControlModel next() { |
||||||
|
|
||||||
|
this.index++; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 页数 |
||||||
|
* index+1 |
||||||
|
* |
||||||
|
* @return 页数 |
||||||
|
*/ |
||||||
|
public int getNumber() { |
||||||
|
return index + 1; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isFirst() { |
||||||
|
return getNumber() == 1; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isLast() { |
||||||
|
return getNumber() == getSummary(); |
||||||
|
} |
||||||
|
|
||||||
|
public int getIndex() { |
||||||
|
return index; |
||||||
|
} |
||||||
|
|
||||||
|
public void setIndex(int index) { |
||||||
|
this.index = index; |
||||||
|
} |
||||||
|
|
||||||
|
public int getSummary() { |
||||||
|
return summary; |
||||||
|
} |
||||||
|
|
||||||
|
public void setSummary(int summary) { |
||||||
|
this.summary = summary; |
||||||
|
} |
||||||
|
|
||||||
|
public String toContent() { |
||||||
|
|
||||||
|
return getNumber() + "/" + this.summary; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,151 @@ |
|||||||
|
package com.fr.design.components.page; |
||||||
|
|
||||||
|
import com.fr.base.svg.IconUtils; |
||||||
|
import com.fr.design.gui.ibutton.UIButton; |
||||||
|
import com.fr.design.gui.ilable.UILabel; |
||||||
|
|
||||||
|
import javax.swing.BorderFactory; |
||||||
|
import javax.swing.JPanel; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.event.ActionEvent; |
||||||
|
import java.awt.event.ActionListener; |
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
/** |
||||||
|
* 翻页组件 |
||||||
|
* 相关使用方式见 <a href="https://kms.fineres.com/pages/viewpage.action?pageId=415212013">翻页组件</a> |
||||||
|
* |
||||||
|
* created by Harrison on 2022/05/26 |
||||||
|
**/ |
||||||
|
public class PageControlPanel extends JPanel { |
||||||
|
|
||||||
|
private static final long serialVersionUID = 8140501834691131305L; |
||||||
|
|
||||||
|
private static final Dimension PAGE_CONTROL_BUTTON_DIMENSION = new Dimension(20, 20); |
||||||
|
|
||||||
|
private UIButton previous; |
||||||
|
private Runnable previousAction; |
||||||
|
|
||||||
|
private UILabel content; |
||||||
|
private PageControlModel model; |
||||||
|
|
||||||
|
private UIButton next; |
||||||
|
private Runnable nextAction; |
||||||
|
|
||||||
|
public PageControlPanel(PageControlModel model) { |
||||||
|
|
||||||
|
this.model = model; |
||||||
|
setBorder(BorderFactory.createEmptyBorder()); |
||||||
|
setLayout(new BorderLayout(6, 0)); |
||||||
|
|
||||||
|
this.previous = new UIButton(); |
||||||
|
previous.setPreferredSize(PAGE_CONTROL_BUTTON_DIMENSION); |
||||||
|
previous.setIcon(IconUtils.readIcon("/com/fr/design/standard/arrow/arrow_enable_left.svg")); |
||||||
|
previous.setDisabledIcon(IconUtils.readIcon("/com/fr/design/standard/arrow/arrow_disable_left.svg")); |
||||||
|
previous.addActionListener(new ActionListener() { |
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
performAction(previousAction); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
this.add(previous, BorderLayout.WEST); |
||||||
|
|
||||||
|
this.content = new UILabel(model.toContent()); |
||||||
|
this.add(content, BorderLayout.CENTER); |
||||||
|
|
||||||
|
next = new UIButton(); |
||||||
|
next.setPreferredSize(PAGE_CONTROL_BUTTON_DIMENSION); |
||||||
|
next.setIcon(IconUtils.readIcon("/com/fr/design/standard/arrow/arrow_enable_right.svg")); |
||||||
|
next.setDisabledIcon(IconUtils.readIcon("/com/fr/design/standard/arrow/arrow_disable_right.svg")); |
||||||
|
next.addActionListener(new ActionListener() { |
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
performAction(nextAction); |
||||||
|
} |
||||||
|
}); |
||||||
|
this.add(next, BorderLayout.EAST); |
||||||
|
|
||||||
|
refresh(); |
||||||
|
} |
||||||
|
|
||||||
|
public PageControlModel performPrevious() { |
||||||
|
|
||||||
|
update(PageControlModel::previous); |
||||||
|
return this.model; |
||||||
|
} |
||||||
|
|
||||||
|
public PageControlModel performNext() { |
||||||
|
|
||||||
|
update(PageControlModel::next); |
||||||
|
return this.model; |
||||||
|
} |
||||||
|
|
||||||
|
public PageControlModel getModel() { |
||||||
|
|
||||||
|
return this.model; |
||||||
|
} |
||||||
|
|
||||||
|
public void update(PageControlModel model) { |
||||||
|
|
||||||
|
this.model.setIndex(model.getIndex()); |
||||||
|
this.model.setSummary(model.getSummary()); |
||||||
|
refresh(); |
||||||
|
} |
||||||
|
|
||||||
|
public void update(Function<PageControlModel, PageControlModel> updateAction) { |
||||||
|
|
||||||
|
PageControlModel newModel = updateAction.apply(this.model); |
||||||
|
update(newModel); |
||||||
|
refresh(); |
||||||
|
} |
||||||
|
|
||||||
|
public void refresh() { |
||||||
|
|
||||||
|
this.content.setText(this.model.toContent()); |
||||||
|
this.content.repaint(); |
||||||
|
|
||||||
|
this.previous.setEnabled(true); |
||||||
|
this.next.setEnabled(true); |
||||||
|
if (model.getNumber() == 1) { |
||||||
|
// 禁用上一个
|
||||||
|
disableButton(this.previous); |
||||||
|
// 禁用next
|
||||||
|
if (model.getNumber() == model.getSummary()) { |
||||||
|
disableButton(this.next); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// 禁用next
|
||||||
|
if (model.getNumber() == model.getSummary()) { |
||||||
|
disableButton(this.next); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void enable(UIButton button) { |
||||||
|
|
||||||
|
button.setEnabled(true); |
||||||
|
} |
||||||
|
|
||||||
|
private void disableButton(UIButton button) { |
||||||
|
|
||||||
|
button.setEnabled(false); |
||||||
|
} |
||||||
|
|
||||||
|
private void performAction(Runnable action) { |
||||||
|
|
||||||
|
if (action != null) { |
||||||
|
action.run(); |
||||||
|
refresh(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void actions(Runnable previousAction, Runnable nextAction) { |
||||||
|
|
||||||
|
this.previousAction = previousAction; |
||||||
|
this.nextAction = nextAction; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,184 @@ |
|||||||
|
package com.fr.design.components.table; |
||||||
|
|
||||||
|
import com.fr.design.gui.ilable.UILabel; |
||||||
|
import com.fr.design.layout.FRGUIPaneFactory; |
||||||
|
import com.fr.design.utils.ColorUtils; |
||||||
|
import com.fr.third.org.apache.commons.lang3.ArrayUtils; |
||||||
|
|
||||||
|
import javax.swing.BorderFactory; |
||||||
|
import javax.swing.JPanel; |
||||||
|
import javax.swing.JSeparator; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Component; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.GridLayout; |
||||||
|
|
||||||
|
/** |
||||||
|
* 表头 |
||||||
|
* 内容 |
||||||
|
* |
||||||
|
* 适用于需要一个表格的 Panel |
||||||
|
* |
||||||
|
* created by Harrison on 2022/05/26 |
||||||
|
**/ |
||||||
|
public class TablePanel extends JPanel { |
||||||
|
|
||||||
|
private static final Color DEFAULT_HEADER_COLOR = new Color(232, 232, 233); |
||||||
|
|
||||||
|
private static final Color DEFAULT_ODD_ROW_COLOR = new Color(245, 245, 247); |
||||||
|
|
||||||
|
private static final Color DEFAULT_EVEN_ROW_COLOR = Color.WHITE; |
||||||
|
|
||||||
|
private JPanel headerPanel; |
||||||
|
|
||||||
|
private JPanel[] headerItemPanels; |
||||||
|
|
||||||
|
private JPanel contentPanel; |
||||||
|
|
||||||
|
private JPanel[][] cellPanels; |
||||||
|
|
||||||
|
public TablePanel(int row, int column) { |
||||||
|
|
||||||
|
setLayout(FRGUIPaneFactory.createBorderLayout()); |
||||||
|
|
||||||
|
/* header 部分 */ |
||||||
|
|
||||||
|
this.headerPanel = new JPanel(); |
||||||
|
headerPanel.setLayout(FRGUIPaneFactory.createNColumnGridLayout(column)); |
||||||
|
headerPanel.setName("header-panel"); |
||||||
|
headerPanel.setPreferredSize(new Dimension(640, 24)); |
||||||
|
|
||||||
|
// border
|
||||||
|
headerPanel.setBorder(BorderFactory.createLineBorder(new Color(218, 218, 221))); |
||||||
|
syncHeaderColor(headerPanel); |
||||||
|
|
||||||
|
headerItemPanels = new JPanel[column]; |
||||||
|
for (int i = 0; i < column; i++) { |
||||||
|
JPanel headerItemWrapper = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
||||||
|
syncHeaderColor(headerItemWrapper); |
||||||
|
headerItemWrapper.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10)); |
||||||
|
|
||||||
|
JPanel headerItemPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
||||||
|
headerItemPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); |
||||||
|
headerItemPanels[i] = headerItemPanel; |
||||||
|
|
||||||
|
UILabel label = new UILabel(); |
||||||
|
syncHeaderColor(label); |
||||||
|
|
||||||
|
headerItemPanel.add(new UILabel(), BorderLayout.CENTER); |
||||||
|
|
||||||
|
headerItemWrapper.add(headerItemPanel, BorderLayout.WEST); |
||||||
|
if (i != column - 1) { |
||||||
|
JSeparator separator = new JSeparator(JSeparator.VERTICAL); |
||||||
|
separator.setBackground(new Color(218, 218, 221)); |
||||||
|
headerItemWrapper.add(separator, BorderLayout.EAST); |
||||||
|
} |
||||||
|
headerPanel.add(headerItemWrapper); |
||||||
|
} |
||||||
|
|
||||||
|
/* content 部分 */ |
||||||
|
|
||||||
|
contentPanel = new JPanel(); |
||||||
|
|
||||||
|
contentPanel.setLayout(new GridLayout(row, 1)); |
||||||
|
contentPanel.setBorder(BorderFactory.createLineBorder(new Color(218, 218, 221))); |
||||||
|
|
||||||
|
cellPanels = new JPanel[row][column]; |
||||||
|
for (int i = 0; i < row; i++) { |
||||||
|
|
||||||
|
JPanel rowPanel = new JPanel(); |
||||||
|
// 获取行号
|
||||||
|
Color rowColor = getRowColorByRowNumber(i + 1); |
||||||
|
rowPanel.setBackground(rowColor); |
||||||
|
rowPanel.setLayout(FRGUIPaneFactory.createNColumnGridLayout(column)); |
||||||
|
rowPanel.setName("row-" + i); |
||||||
|
rowPanel.setBorder(BorderFactory.createEmptyBorder()); |
||||||
|
rowPanel.setPreferredSize(new Dimension(640, 24)); |
||||||
|
|
||||||
|
for (int j = 0; j < column; j++) { |
||||||
|
|
||||||
|
JPanel rowItemPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
||||||
|
rowItemPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); |
||||||
|
rowItemPanel.setName("rowItemPanel-"+ i + "-" + j); |
||||||
|
final UILabel empty = new UILabel(); |
||||||
|
empty.setPreferredSize(new Dimension(210, 24)); |
||||||
|
rowItemPanel.setBackground(rowPanel.getBackground()); |
||||||
|
rowItemPanel.add(empty, BorderLayout.CENTER); |
||||||
|
|
||||||
|
rowPanel.add(rowItemPanel); |
||||||
|
cellPanels[i][j] = rowItemPanel; |
||||||
|
} |
||||||
|
|
||||||
|
contentPanel.add(rowPanel); |
||||||
|
} |
||||||
|
|
||||||
|
add(headerPanel, BorderLayout.NORTH); |
||||||
|
add(contentPanel, BorderLayout.SOUTH); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取行的颜色 |
||||||
|
* |
||||||
|
* @param row 行号 |
||||||
|
* @return 颜色 |
||||||
|
*/ |
||||||
|
private Color getRowColorByRowNumber(int row) { |
||||||
|
|
||||||
|
Color rowColor; |
||||||
|
if (row % 2 != 0) { |
||||||
|
rowColor = DEFAULT_EVEN_ROW_COLOR; |
||||||
|
} else { |
||||||
|
rowColor = DEFAULT_ODD_ROW_COLOR; |
||||||
|
} |
||||||
|
return rowColor; |
||||||
|
} |
||||||
|
|
||||||
|
public void updateHeaders(String[] headers) { |
||||||
|
|
||||||
|
for (int i = 0; i < headers.length; i++) { |
||||||
|
String header = headers[i]; |
||||||
|
UILabel headerContent = new UILabel(header); |
||||||
|
JPanel headerItemPanel = headerItemPanels[i]; |
||||||
|
if (ArrayUtils.getLength(headerItemPanel.getComponents()) == 1) { |
||||||
|
headerItemPanel.remove(0); |
||||||
|
} |
||||||
|
headerItemPanel.add(headerContent); |
||||||
|
syncHeaderColor(headerItemPanel); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void updateCell(int row, int column, Component component) { |
||||||
|
|
||||||
|
int x = row - 1; |
||||||
|
int y = column - 1; |
||||||
|
|
||||||
|
syncCellColor(row, component); |
||||||
|
|
||||||
|
JPanel cellPanel = this.cellPanels[x][y]; |
||||||
|
if (ArrayUtils.getLength(cellPanel.getComponents()) == 1) { |
||||||
|
cellPanel.remove(0); |
||||||
|
} |
||||||
|
cellPanel.add(component); |
||||||
|
} |
||||||
|
|
||||||
|
public void updateCell(int row, int column, String value) { |
||||||
|
|
||||||
|
UILabel cellContent = new UILabel(value); |
||||||
|
syncCellColor(row, cellContent); |
||||||
|
this.updateCell(row, column, cellContent); |
||||||
|
} |
||||||
|
|
||||||
|
private void syncHeaderColor(Component component) { |
||||||
|
|
||||||
|
ColorUtils.syncBackground(component, DEFAULT_HEADER_COLOR); |
||||||
|
} |
||||||
|
|
||||||
|
private void syncCellColor(int row, Component component) { |
||||||
|
|
||||||
|
Color rowColor = getRowColorByRowNumber(row); |
||||||
|
ColorUtils.syncBackground(component, rowColor); |
||||||
|
component.setBackground(rowColor); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
package com.fr.design.components.tooltip; |
||||||
|
|
||||||
|
import com.fr.base.GraphHelper; |
||||||
|
import com.fr.design.gui.itooltip.UIToolTip; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
|
||||||
|
import javax.swing.Icon; |
||||||
|
import javax.swing.JComponent; |
||||||
|
import javax.swing.JToolTip; |
||||||
|
import javax.swing.SwingUtilities; |
||||||
|
import javax.swing.plaf.ToolTipUI; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.FontMetrics; |
||||||
|
import java.awt.Graphics; |
||||||
|
import java.awt.Graphics2D; |
||||||
|
import java.awt.RenderingHints; |
||||||
|
import java.io.BufferedReader; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.StringReader; |
||||||
|
import java.util.Enumeration; |
||||||
|
import java.util.Vector; |
||||||
|
|
||||||
|
/** |
||||||
|
* 现代化的 UIToolTip |
||||||
|
* 见 <a href="https://kms.fineres.com/pages/viewpage.action?pageId=416850313">设计文档</a> |
||||||
|
* |
||||||
|
* created by Harrison on 2022/07/09 |
||||||
|
**/ |
||||||
|
public class ModernToolTip extends UIToolTip { |
||||||
|
|
||||||
|
public ModernToolTip() { |
||||||
|
super(); |
||||||
|
setUI(new ModernToolTipUI()); |
||||||
|
} |
||||||
|
|
||||||
|
private class ModernToolTipUI extends ToolTipUI { |
||||||
|
|
||||||
|
private String[] strs; |
||||||
|
private Icon icon; |
||||||
|
private boolean needPaint; |
||||||
|
public void paint(Graphics g, JComponent c) { |
||||||
|
if (!needPaint) { |
||||||
|
return; |
||||||
|
} |
||||||
|
FontMetrics metrics = GraphHelper.getFontMetrics(c.getFont()); |
||||||
|
Dimension size = c.getSize(); |
||||||
|
int width = size.width; |
||||||
|
int height = size.height; |
||||||
|
Graphics2D g2 = (Graphics2D) g; |
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
||||||
|
g2.setColor(new Color(51, 51, 52, (int) Math.round(0.7 * 255))); |
||||||
|
g2.fillRoundRect(0, 0, width, height, 4, 4); |
||||||
|
|
||||||
|
g2.setColor(Color.WHITE); |
||||||
|
if (strs != null) { |
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT); |
||||||
|
for (int i = 0; i < strs.length; i++) { |
||||||
|
g2.drawString(strs[i], icon.getIconWidth() + 6, (metrics.getHeight()) * (i + 1)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Dimension getPreferredSize(JComponent c) { |
||||||
|
FontMetrics metrics = GraphHelper.getFontMetrics(c.getFont()); |
||||||
|
String tipText = ((JToolTip) c).getTipText(); |
||||||
|
icon = ((UIToolTip)c).getIcon(); |
||||||
|
needPaint = true; |
||||||
|
if (tipText == null) { |
||||||
|
if(icon.getIconWidth() == -1) { |
||||||
|
needPaint = false; |
||||||
|
} |
||||||
|
tipText = " "; |
||||||
|
} |
||||||
|
BufferedReader br = new BufferedReader(new StringReader(tipText)); |
||||||
|
String line; |
||||||
|
int maxWidth = 0; |
||||||
|
Vector<String> v = new Vector<String>(); |
||||||
|
try { |
||||||
|
while ((line = br.readLine()) != null) { |
||||||
|
int width = SwingUtilities.computeStringWidth(metrics, line); |
||||||
|
maxWidth = (maxWidth < width) ? width : maxWidth; |
||||||
|
v.addElement(line); |
||||||
|
} |
||||||
|
} catch (IOException ex) { |
||||||
|
FineLoggerFactory.getLogger().error(ex.getMessage(), ex); |
||||||
|
} |
||||||
|
int lines = v.size(); |
||||||
|
if (lines < 1) { |
||||||
|
strs = null; |
||||||
|
lines = 1; |
||||||
|
} else { |
||||||
|
strs = new String[lines]; |
||||||
|
int i = 0; |
||||||
|
for (Enumeration<String> e = v.elements(); e.hasMoreElements(); i++) { |
||||||
|
strs[i] = e.nextElement(); |
||||||
|
} |
||||||
|
} |
||||||
|
int height = metrics.getHeight() * lines; |
||||||
|
return new Dimension(maxWidth + icon.getIconWidth() + 10, Math.max(height, icon.getIconHeight()) + 6); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
package com.fr.design.config; |
||||||
|
|
||||||
|
import com.fr.general.IOUtils; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.stable.StableUtils; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
|
||||||
|
import java.io.BufferedInputStream; |
||||||
|
import java.io.FileInputStream; |
||||||
|
import java.io.FileNotFoundException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.util.Properties; |
||||||
|
|
||||||
|
public class DesignerProperties { |
||||||
|
private static DesignerProperties holder = null; |
||||||
|
private boolean supportLoginEntry = true; |
||||||
|
|
||||||
|
public DesignerProperties() { |
||||||
|
String filePath = StableUtils.pathJoin(StableUtils.getInstallHome(), "/config/config.properties"); |
||||||
|
InputStream is = null; |
||||||
|
try { |
||||||
|
is = new BufferedInputStream(new FileInputStream(filePath)); |
||||||
|
Properties ps = new Properties(); |
||||||
|
ps.load(is); |
||||||
|
this.initProperties(ps); |
||||||
|
} catch (FileNotFoundException e) { |
||||||
|
// ignore
|
||||||
|
} catch (Exception e) { |
||||||
|
FineLoggerFactory.getLogger().error(e, e.getMessage()); |
||||||
|
} finally { |
||||||
|
IOUtils.close(is); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static DesignerProperties getInstance() { |
||||||
|
if (holder == null) { |
||||||
|
holder = new DesignerProperties(); |
||||||
|
} |
||||||
|
return holder; |
||||||
|
} |
||||||
|
|
||||||
|
private void initProperties(Properties ps) { |
||||||
|
String supportLoginEntry = ps.getProperty("supportLoginEntry"); |
||||||
|
if (StringUtils.isNotEmpty(supportLoginEntry)) { |
||||||
|
this.supportLoginEntry = Boolean.valueOf(supportLoginEntry); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isSupportLoginEntry() { |
||||||
|
return supportLoginEntry; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
package com.fr.design.constants; |
||||||
|
|
||||||
|
import java.awt.Color; |
||||||
|
|
||||||
|
/** |
||||||
|
* 见 <a href="https://www.figma.com/file/G2f40rv7cY8zpDDYbt6pY2/%E8%AE%BE%E8%AE%A1%E5%99%A811.0%E8%A7%84%E8%8C%83%E6%95%B4%E7%90%86">设计器规范</a> |
||||||
|
* 将相关的逻辑抽象过来 |
||||||
|
* 如果后面更改的话, 可以统一修改 |
||||||
|
* 如果换版本,可以换成 v2 这种类推 |
||||||
|
* |
||||||
|
* created by Harrison on 2022/05/26 |
||||||
|
**/ |
||||||
|
public interface DesignerColor { |
||||||
|
|
||||||
|
interface Button { |
||||||
|
|
||||||
|
interface Primary { |
||||||
|
|
||||||
|
Color PRESSED = new Color(29, 122, 220); |
||||||
|
Color HOVER = new Color(84, 165, 249); |
||||||
|
Color NORMAL = new Color(65, 155, 249); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
package com.fr.design.constants; |
||||||
|
|
||||||
|
public class TableDataConstants { |
||||||
|
public static final String SEPARATOR = "_"; |
||||||
|
} |
@ -0,0 +1,93 @@ |
|||||||
|
package com.fr.design.data; |
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
|
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author rinoux |
||||||
|
* @version 10.0 |
||||||
|
* Created by rinoux on 2022/3/28 |
||||||
|
*/ |
||||||
|
public final class MapCompareUtils { |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 对比两个map 查找出相比orig,other中有哪些是新增的、删除的或者被修改的,并分别进行处理 |
||||||
|
* |
||||||
|
* 对比时默认用equals方法来判断是否为 EntryEventKind#UPDATED |
||||||
|
* |
||||||
|
* @param orig 原始map |
||||||
|
* @param other 参考的新map |
||||||
|
* @param eventHandler 有区别时的事件处理器 |
||||||
|
* @param <K> K |
||||||
|
* @param <V> V |
||||||
|
*/ |
||||||
|
public static <K, V> void contrastMapEntries(@NotNull Map<K, V> orig, @NotNull Map<K, V> other, @NotNull EventHandler<K, V> eventHandler) { |
||||||
|
|
||||||
|
contrastMapEntries(orig, other, eventHandler, UpdateRule.DEFAULT); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 对比两个map 查找出相比orig,other中有哪些是新增的、删除的或者被修改的,并分别进行处理 |
||||||
|
* |
||||||
|
* 对比时用自定义的规则来判断是否为 EntryEventKind#UPDATED |
||||||
|
* |
||||||
|
* @param orig 原始map |
||||||
|
* @param other 参考的新map |
||||||
|
* @param eventHandler 有区别时的事件处理器 |
||||||
|
* @param updateRule 自定义的Update事件判定规则 |
||||||
|
* @param <K> |
||||||
|
* @param <V> |
||||||
|
*/ |
||||||
|
public static <K, V> void contrastMapEntries(@NotNull Map<K, V> orig, @NotNull Map<K, V> other, @NotNull EventHandler<K, V> eventHandler, @NotNull UpdateRule<K, V> updateRule) { |
||||||
|
|
||||||
|
Map<K, V> copiedOrig = new LinkedHashMap<>(orig); |
||||||
|
|
||||||
|
other.forEach((k, v) -> { |
||||||
|
V existedV = copiedOrig.remove(k); |
||||||
|
if (existedV != null) { |
||||||
|
if (updateRule.needUpdate(existedV, v)) { |
||||||
|
eventHandler.on(EntryEventKind.UPDATED, k, v); |
||||||
|
} |
||||||
|
} else { |
||||||
|
eventHandler.on(EntryEventKind.ADDED, k, v); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
copiedOrig.forEach((k, v) -> eventHandler.on(EntryEventKind.REMOVED, k, v)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 事件处理器,对应比较后的三种结果的事件处理 |
||||||
|
* @param <K> |
||||||
|
* @param <V> |
||||||
|
*/ |
||||||
|
public interface EventHandler<K, V> { |
||||||
|
void on(EntryEventKind entryEventKind, K k, V v); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据被修改(EntryEventKind.UPDATED) 的判定规则 |
||||||
|
* @param <K> |
||||||
|
* @param <V> |
||||||
|
*/ |
||||||
|
public interface UpdateRule<K, V> { |
||||||
|
|
||||||
|
EntryEventKind eventKind = EntryEventKind.UPDATED; |
||||||
|
|
||||||
|
UpdateRule DEFAULT = new UpdateRule() {}; |
||||||
|
|
||||||
|
default boolean needUpdate(V origin, V v) { |
||||||
|
return !v.equals(origin); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public enum EntryEventKind { |
||||||
|
ADDED, |
||||||
|
REMOVED, |
||||||
|
UPDATED; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,73 @@ |
|||||||
|
package com.fr.design.data.datapane; |
||||||
|
|
||||||
|
import com.fr.data.util.function.AbstractDataFunction; |
||||||
|
import com.fr.data.util.function.AverageFunction; |
||||||
|
import com.fr.data.util.function.CountFunction; |
||||||
|
import com.fr.data.util.function.MaxFunction; |
||||||
|
import com.fr.data.util.function.MinFunction; |
||||||
|
import com.fr.data.util.function.NoneFunction; |
||||||
|
import com.fr.data.util.function.SumFunction; |
||||||
|
import com.fr.design.gui.icombobox.UIComboBox; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.general.ComparatorUtils; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.plugin.chart.base.FirstFunction; |
||||||
|
|
||||||
|
/** |
||||||
|
* 图表数据汇总方式下拉框 |
||||||
|
* |
||||||
|
* 支持首个,最后一个,求和,平均,最大值,最小值,个数 |
||||||
|
* |
||||||
|
*/ |
||||||
|
public class SummaryMethodComboBox extends UIComboBox { |
||||||
|
public static final String[] CALCULATE_ARRAY = {Toolkit.i18nText("Fine-Design_Chart_Data_Function_First"), Toolkit.i18nText("Fine-Design_Chart_Data_Function_Last"), |
||||||
|
Toolkit.i18nText("Fine-Design_Chart_Data_Function_Sum"), Toolkit.i18nText("Fine-Design_Chart_Data_Function_Average"), |
||||||
|
Toolkit.i18nText("Fine-Design_Chart_Data_Function_Max"), Toolkit.i18nText("Fine-Design_Chart_Data_Function_Min"), |
||||||
|
Toolkit.i18nText("Fine-Design_Chart_Data_Function_Count")}; |
||||||
|
public static final Class[] CLASS_ARRAY = {FirstFunction.class, NoneFunction.class, SumFunction.class, AverageFunction.class, |
||||||
|
MaxFunction.class, MinFunction.class, CountFunction.class}; |
||||||
|
|
||||||
|
public SummaryMethodComboBox() { |
||||||
|
super(CALCULATE_ARRAY); |
||||||
|
setSelectedIndex(2); |
||||||
|
} |
||||||
|
|
||||||
|
public void reset() { |
||||||
|
this.setSelectedItem(Toolkit.i18nText("Fine-Design_Chart_Data_Function_Sum")); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 更新公式选择. |
||||||
|
*/ |
||||||
|
public void populateBean(AbstractDataFunction function) { |
||||||
|
if (function != null) { |
||||||
|
for (int i = 0; i < CLASS_ARRAY.length; i++) { |
||||||
|
if (this.getModel() != null && this.getModel().getSize() > i |
||||||
|
&& ComparatorUtils.equals(function.getClass(), CLASS_ARRAY[i])) { |
||||||
|
setSelectedIndex(i); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 返回当前选择的公式 |
||||||
|
*/ |
||||||
|
public AbstractDataFunction updateBean() { |
||||||
|
try { |
||||||
|
int selectIndex = getSelectedIndex(); |
||||||
|
if (selectIndex >= 0 && selectIndex < CLASS_ARRAY.length) { |
||||||
|
return (AbstractDataFunction) CLASS_ARRAY[selectIndex].newInstance(); |
||||||
|
} |
||||||
|
} catch (InstantiationException e) { |
||||||
|
FineLoggerFactory.getLogger().error("Function Error"); |
||||||
|
return null; |
||||||
|
} catch (IllegalAccessException e) { |
||||||
|
FineLoggerFactory.getLogger().error("Function Error"); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
package com.fr.design.data.datapane.auth; |
||||||
|
|
||||||
|
import com.fr.base.TableData; |
||||||
|
import com.fr.data.impl.Connection; |
||||||
|
import com.fr.data.impl.DBTableData; |
||||||
|
import com.fr.data.impl.NameDatabaseConnection; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import com.fr.workspace.WorkContext; |
||||||
|
import com.fr.workspace.server.connection.DBConnectAuth; |
||||||
|
|
||||||
|
import java.util.Collection; |
||||||
|
import java.util.Collections; |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据连接权限相关的工具类 |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataAuthHelper { |
||||||
|
|
||||||
|
/** |
||||||
|
* 编辑数据集时是否需要检查权限 |
||||||
|
* @param tableData |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static boolean needCheckAuthWhenEdit(TableData tableData) { |
||||||
|
// 远程设计下,编辑DBTableData时需要判断权限
|
||||||
|
return !WorkContext.getCurrent().isLocal() && tableData instanceof DBTableData; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取无权限数据连接集合 |
||||||
|
* 远程下需要调用RPC,为耗时操作,谨慎使用 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static Collection<String> getNoAuthConnections() { |
||||||
|
// 获取无权限连接集合
|
||||||
|
Collection<String> noAuthConnections = WorkContext.getCurrent().get(DBConnectAuth.class).getNoAuthConnections(); |
||||||
|
return noAuthConnections == null ? Collections.emptyList() : noAuthConnections; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 通过数据集获取其数据连接的名称 |
||||||
|
* |
||||||
|
* 注意: |
||||||
|
* 1. Connection接口本身是不提供名称的,只有我们内部为了使用方便,将其包装成了NameDataBaseConnection |
||||||
|
* 如果不是NameDataBaseConnection类型,则无名称,因此这里只能用判断类型的方式获取名称 |
||||||
|
* 2. 仅支持DBTableData获取连接名 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static String getConnectionNameByDBTableData(DBTableData tableData) { |
||||||
|
Connection database = tableData.getDatabase(); |
||||||
|
if (database instanceof NameDatabaseConnection) { |
||||||
|
return ((NameDatabaseConnection) database).getName(); |
||||||
|
} |
||||||
|
return StringUtils.EMPTY; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,145 @@ |
|||||||
|
package com.fr.design.data.datapane.connect; |
||||||
|
|
||||||
|
import com.fr.data.impl.JDBCDatabaseConnection; |
||||||
|
import com.fr.data.pool.DBCPConnectionPoolAttr; |
||||||
|
import com.fr.design.dialog.BasicPane; |
||||||
|
import com.fr.design.editor.editor.IntegerEditor; |
||||||
|
import com.fr.design.gui.icombobox.UIComboBox; |
||||||
|
import com.fr.design.gui.ilable.UILabel; |
||||||
|
import com.fr.design.gui.itextfield.UITextField; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.layout.FRGUIPaneFactory; |
||||||
|
import com.fr.design.layout.TableLayout; |
||||||
|
import com.fr.design.layout.TableLayoutHelper; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
|
||||||
|
import javax.swing.JPanel; |
||||||
|
import javax.swing.SwingConstants; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Component; |
||||||
|
import java.awt.event.FocusEvent; |
||||||
|
import java.awt.event.FocusListener; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author xiqiu |
||||||
|
* @date 2021/11/22 |
||||||
|
* @description |
||||||
|
*/ |
||||||
|
public class AdvancePane extends BasicPane { |
||||||
|
private IntegerEditor DBCP_MAX_ACTIVE = new IntegerEditor(); |
||||||
|
private UIComboBox DBCP_TESTONBORROW = new UIComboBox(new String[]{Toolkit.i18nText("Fine-Design_Basic_No"), Toolkit.i18nText("Fine-Design_Basic_Yes")}); |
||||||
|
private IntegerEditor DBCP_MAX_WAIT = new IntegerEditor(); |
||||||
|
private SpecialUITextField DBCP_VALIDATION_QUERY = new SpecialUITextField(); |
||||||
|
|
||||||
|
|
||||||
|
public AdvancePane() { |
||||||
|
JPanel jPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
||||||
|
DBCP_VALIDATION_QUERY.addFocusListener(new JTextFieldHintListener(DBCP_VALIDATION_QUERY)); |
||||||
|
double p = TableLayout.PREFERRED; |
||||||
|
DBCP_VALIDATION_QUERY.setColumns(20); |
||||||
|
double[] rowSizeDbcp = {p, p, p, p}; |
||||||
|
double[] columnDbcp = {190, p}; |
||||||
|
Component[][] comps = { |
||||||
|
{new UILabel(Toolkit.i18nText("Fine-Design_Basic_Dbcp_Max_Active") + ":", SwingConstants.RIGHT), DBCP_MAX_ACTIVE}, |
||||||
|
{new UILabel(Toolkit.i18nText("Fine-Design_Basic_Dbcp_Validation_Query") + ":", SwingConstants.RIGHT), DBCP_VALIDATION_QUERY}, |
||||||
|
{new UILabel(Toolkit.i18nText("Fine-Design_Basic_Dbcp_Test_On_Borrow") + ":", SwingConstants.RIGHT), DBCP_TESTONBORROW}, |
||||||
|
{new UILabel(Toolkit.i18nText("Fine-Design_Basic_Connection_Pool_Max_Wait_Time") + ":", SwingConstants.RIGHT), DBCP_MAX_WAIT} |
||||||
|
}; |
||||||
|
|
||||||
|
JPanel contextPane = TableLayoutHelper.createGapTableLayoutPane(comps, rowSizeDbcp, columnDbcp, 11, 11); |
||||||
|
jPanel.add(contextPane, BorderLayout.CENTER); |
||||||
|
this.add(jPanel); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public void populate(JDBCDatabaseConnection jdbcDatabase) { |
||||||
|
DBCPConnectionPoolAttr dbcpAttr = jdbcDatabase.getDbcpAttr(); |
||||||
|
if (dbcpAttr == null) { |
||||||
|
dbcpAttr = new DBCPConnectionPoolAttr(); |
||||||
|
jdbcDatabase.setDbcpAttr(dbcpAttr); |
||||||
|
} |
||||||
|
this.DBCP_MAX_ACTIVE.setValue(dbcpAttr.getMaxActive()); |
||||||
|
this.DBCP_MAX_WAIT.setValue(dbcpAttr.getMaxWait()); |
||||||
|
this.DBCP_VALIDATION_QUERY.setText(dbcpAttr.getValidationQuery()); |
||||||
|
this.DBCP_TESTONBORROW.setSelectedIndex(dbcpAttr.isTestOnBorrow() ? 1 : 0); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void update(JDBCDatabaseConnection jdbcDatabase) { |
||||||
|
DBCPConnectionPoolAttr dbcpAttr = jdbcDatabase.getDbcpAttr(); |
||||||
|
if (dbcpAttr == null) { |
||||||
|
dbcpAttr = new DBCPConnectionPoolAttr(); |
||||||
|
jdbcDatabase.setDbcpAttr(dbcpAttr); |
||||||
|
} |
||||||
|
dbcpAttr.setMaxActive(this.DBCP_MAX_ACTIVE.getValue().intValue()); |
||||||
|
dbcpAttr.setMaxWait(this.DBCP_MAX_WAIT.getValue().intValue()); |
||||||
|
dbcpAttr.setValidationQuery(this.DBCP_VALIDATION_QUERY.getText()); |
||||||
|
dbcpAttr.setTestOnBorrow(this.DBCP_TESTONBORROW.getSelectedIndex() == 0 ? false : true); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected String title4PopupWindow() { |
||||||
|
return Toolkit.i18nText("Fine-Design_Basic_Advanced_Setup"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private class JTextFieldHintListener implements FocusListener { |
||||||
|
private SpecialUITextField textField; |
||||||
|
|
||||||
|
public JTextFieldHintListener(SpecialUITextField jTextField) { |
||||||
|
this.textField = jTextField; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void focusGained(FocusEvent e) { |
||||||
|
//获取焦点时,清空提示内容
|
||||||
|
String temp = textField.getText(); |
||||||
|
textField.setForeground(Color.BLACK); |
||||||
|
textField.setTextOrigin(temp); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void focusLost(FocusEvent e) { |
||||||
|
//失去焦点时,没有输入内容,显示提示内容
|
||||||
|
String temp = textField.getTextOrigin(); |
||||||
|
textField.setText(temp); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class SpecialUITextField extends UITextField { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getText() { |
||||||
|
String text = super.getText(); |
||||||
|
if (isUseless(text)) { |
||||||
|
return StringUtils.EMPTY; |
||||||
|
} |
||||||
|
return text; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setText(String text) { |
||||||
|
if (isUseless(text)) { |
||||||
|
this.setForeground(Color.GRAY); |
||||||
|
super.setText(Toolkit.i18nText("Fine-Design_Dbcp_Default_Query")); |
||||||
|
} else { |
||||||
|
this.setForeground(Color.BLACK); |
||||||
|
super.setText(text); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public String getTextOrigin() { |
||||||
|
return super.getText(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setTextOrigin(String text) { |
||||||
|
super.setText(text); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isUseless(String text) { |
||||||
|
return text == null || text.isEmpty() || Toolkit.i18nText("Fine-Design_Dbcp_Default_Query").equals(text); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,301 @@ |
|||||||
|
package com.fr.design.data.datapane.connect; |
||||||
|
|
||||||
|
import com.fr.data.impl.JDBCDatabaseConnection; |
||||||
|
import com.fr.data.security.ssh.BaseSsh; |
||||||
|
import com.fr.data.security.ssh.Ssh; |
||||||
|
import com.fr.data.security.ssh.SshException; |
||||||
|
import com.fr.data.security.ssh.SshType; |
||||||
|
import com.fr.data.security.ssh.impl.KeyVerifySsh; |
||||||
|
import com.fr.data.security.ssh.impl.NormalSsh; |
||||||
|
import com.fr.data.security.ssl.SslUtils; |
||||||
|
import com.fr.design.border.UITitledBorder; |
||||||
|
import com.fr.design.constants.UIConstants; |
||||||
|
import com.fr.design.dialog.BasicPane; |
||||||
|
import com.fr.design.editor.editor.NotNegativeIntegerEditor; |
||||||
|
import com.fr.design.gui.ibutton.UIButton; |
||||||
|
import com.fr.design.gui.icheckbox.UICheckBox; |
||||||
|
import com.fr.design.gui.icombobox.UIComboBox; |
||||||
|
import com.fr.design.gui.ilable.UILabel; |
||||||
|
import com.fr.design.gui.ipasswordfield.UIPasswordFieldWithFixedLength; |
||||||
|
import com.fr.design.gui.itextfield.UITextField; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.layout.FRGUIPaneFactory; |
||||||
|
import com.fr.design.layout.TableLayout; |
||||||
|
import com.fr.design.layout.TableLayoutHelper; |
||||||
|
import com.fr.file.FILE; |
||||||
|
import com.fr.file.FILEChooserPane; |
||||||
|
import com.fr.file.filter.ChooseFileFilter; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import com.fr.third.guava.collect.HashBiMap; |
||||||
|
|
||||||
|
import javax.swing.ImageIcon; |
||||||
|
import javax.swing.JPanel; |
||||||
|
import javax.swing.JPasswordField; |
||||||
|
import javax.swing.SwingConstants; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.Component; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.event.ActionEvent; |
||||||
|
import java.awt.event.ActionListener; |
||||||
|
import java.awt.event.KeyAdapter; |
||||||
|
import java.awt.event.KeyEvent; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import static com.fr.design.i18n.Toolkit.i18nText; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author xiqiu |
||||||
|
* @date 2021/12/23 |
||||||
|
* @description |
||||||
|
*/ |
||||||
|
public class SshPane extends BasicPane { |
||||||
|
private static HashBiMap<String, SshType> typeMap; |
||||||
|
|
||||||
|
static { |
||||||
|
typeMap = HashBiMap.create(); |
||||||
|
typeMap.put(Toolkit.i18nText("Fine-Design_Basic_Password"), SshType.NORMAL); |
||||||
|
typeMap.put(Toolkit.i18nText("Fine-Design_Basic_Ssh_Public_Key"), SshType.KEY); |
||||||
|
} |
||||||
|
|
||||||
|
private UICheckBox usingSsh = new UICheckBox(i18nText("Fine-Design_Basic_Ssh_Using")); |
||||||
|
private NotNegativeIntegerEditor port = new NotNegativeIntegerEditor(20); |
||||||
|
private UITextField ip = new UITextField(20); |
||||||
|
private UIComboBox type = new UIComboBox(); |
||||||
|
private UITextField user = new UITextField(20); |
||||||
|
private JPasswordField password = new UIPasswordFieldWithFixedLength(20); |
||||||
|
private JPasswordField secret = new UIPasswordFieldWithFixedLength(20); |
||||||
|
private KeyFileUITextField keyPath = new KeyFileUITextField(18); |
||||||
|
private JPanel contextPane; |
||||||
|
private Component[][] passwordComps; |
||||||
|
private Component[][] keyComps; |
||||||
|
private double p = TableLayout.PREFERRED; |
||||||
|
private double f = TableLayout.FILL; |
||||||
|
private JPanel jPanel; |
||||||
|
private UIButton fileChooserButton = new UIButton(); |
||||||
|
private double[] columnSize = new double[]{195, p}; |
||||||
|
|
||||||
|
public SshPane() { |
||||||
|
fileChooserButton.setIcon(new ImageIcon(UIConstants.ACCESSIBLE_EDITOR_DOT)); |
||||||
|
this.setBorder(UITitledBorder.createBorderWithTitle(Toolkit.i18nText("Fine-Design_Basic_Ssh_Settings"))); |
||||||
|
this.setLayout(FRGUIPaneFactory.createLabelFlowLayout()); |
||||||
|
typeMap.keySet().forEach(key -> type.addItem(key)); |
||||||
|
type.setSelectedItem(typeMap.inverse().get(SshType.KEY)); |
||||||
|
jPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
||||||
|
fileChooserButton.setPreferredSize(new Dimension(20, 20)); |
||||||
|
type.setEditable(false); |
||||||
|
type.setSelectedItem(Toolkit.i18nText("Fine-Design_Basic_Ssh_Private_Key")); |
||||||
|
JPanel filePanel = TableLayoutHelper.createCommonTableLayoutPane(new Component[][]{{keyPath, fileChooserButton}}, new double[]{p}, new double[]{f, 20}, 0); |
||||||
|
Component[] compIp = {new UILabel(Toolkit.i18nText("Fine-Design_Basic_Host") + ":", SwingConstants.RIGHT), ip}; |
||||||
|
Component[] compPort = {new UILabel(Toolkit.i18nText("Fine-Design_Basic_Port") + ":", SwingConstants.RIGHT), port}; |
||||||
|
Component[] compUserName = {new UILabel(Toolkit.i18nText("Fine-Design_Report_UserName") + ":", SwingConstants.RIGHT), user}; |
||||||
|
Component[] compMethod = {new UILabel(Toolkit.i18nText("Fine-Design_Basic_Ssh_Verify_Method") + ":", SwingConstants.RIGHT), type}; |
||||||
|
Component[] compPassword = {new UILabel(Toolkit.i18nText("Fine-Design_Basic_Password") + ":", SwingConstants.RIGHT), password}; |
||||||
|
Component[] compKey = {new UILabel(Toolkit.i18nText("Fine-Design_Basic_Ssh_Private_Key") + ":", SwingConstants.RIGHT), filePanel}; |
||||||
|
Component[] comSecret = {new UILabel(Toolkit.i18nText("Fine-Design_Basic_Ssh_Secret") + ":", SwingConstants.RIGHT), secret}; |
||||||
|
|
||||||
|
passwordComps = new Component[][]{ |
||||||
|
compIp, |
||||||
|
compPort, |
||||||
|
compUserName, |
||||||
|
compMethod, |
||||||
|
compPassword |
||||||
|
}; |
||||||
|
keyComps = new Component[][]{ |
||||||
|
compIp, |
||||||
|
compPort, |
||||||
|
compUserName, |
||||||
|
compMethod, |
||||||
|
compKey, |
||||||
|
comSecret |
||||||
|
}; |
||||||
|
usingSsh.setSelected(true); |
||||||
|
contextPane = TableLayoutHelper.createGapTableLayoutPane(keyComps, new double[]{p, p, p, p, p, p}, columnSize, 11, 11); |
||||||
|
jPanel.add(usingSsh, BorderLayout.NORTH); |
||||||
|
jPanel.add(contextPane, BorderLayout.CENTER); |
||||||
|
this.add(jPanel); |
||||||
|
|
||||||
|
usingSsh.addActionListener(new ActionListener() { |
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
changePane(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
type.addActionListener(new ActionListener() { |
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
changePaneForType(); |
||||||
|
} |
||||||
|
}); |
||||||
|
fileChooserButton.addActionListener(new ActionListener() { |
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
FILEChooserPane fileChooser = FILEChooserPane.getInstanceWithDesignatePath(SslUtils.PREFIX, new ChooseFileFilter(true), SslUtils.CERTIFICATES); |
||||||
|
int type = fileChooser.showOpenDialog(SshPane.this, StringUtils.EMPTY); |
||||||
|
if (type == FILEChooserPane.OK_OPTION) { |
||||||
|
final FILE file = fileChooser.getSelectedFILE(); |
||||||
|
if (file == null) { |
||||||
|
keyPath.setText(StringUtils.EMPTY); |
||||||
|
} else { |
||||||
|
keyPath.setText(file.getPath()); |
||||||
|
} |
||||||
|
} |
||||||
|
fileChooser.removeAllFilter(); |
||||||
|
fileChooser.removeTopPath(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private void changePane() { |
||||||
|
contextPane.setVisible(usingSsh.isSelected()); |
||||||
|
} |
||||||
|
|
||||||
|
private void changePaneForType() { |
||||||
|
contextPane.removeAll(); |
||||||
|
switch (typeMap.get(type.getSelectedItem())) { |
||||||
|
case NORMAL: |
||||||
|
TableLayoutHelper.addComponent2ResultPane(passwordComps, new double[]{p, p, p, p, p}, columnSize, contextPane); |
||||||
|
break; |
||||||
|
case KEY: |
||||||
|
TableLayoutHelper.addComponent2ResultPane(keyComps, new double[]{p, p, p, p, p, p}, columnSize, contextPane); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new SshException("un support ssh type"); |
||||||
|
} |
||||||
|
jPanel.revalidate(); |
||||||
|
jPanel.repaint(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
protected String title4PopupWindow() { |
||||||
|
return Toolkit.i18nText("Fine-Design_Basic_Ssh_Settings"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public void populate(JDBCDatabaseConnection jdbcDatabase) { |
||||||
|
if (jdbcDatabase.getSsh() == null) { |
||||||
|
jdbcDatabase.setSsh(new NormalSsh()); |
||||||
|
} |
||||||
|
Ssh ssh = jdbcDatabase.getSsh(); |
||||||
|
switch (ssh.getSshType()) { |
||||||
|
case KEY: |
||||||
|
type.setSelectedItem(typeMap.inverse().get(ssh.getSshType())); |
||||||
|
KeyVerifySsh keyVerifySsh = (KeyVerifySsh) ssh; |
||||||
|
keyPath.setText(keyVerifySsh.getPrivateKeyPath()); |
||||||
|
secret.setText(keyVerifySsh.getSecret()); |
||||||
|
password.setText(StringUtils.EMPTY); |
||||||
|
setCommonConfig(keyVerifySsh); |
||||||
|
break; |
||||||
|
case NORMAL: |
||||||
|
type.setSelectedItem(typeMap.inverse().get(ssh.getSshType())); |
||||||
|
NormalSsh normalSsh = (NormalSsh) ssh; |
||||||
|
password.setText(normalSsh.getSecret()); |
||||||
|
keyPath.setText(StringUtils.EMPTY); |
||||||
|
secret.setText(StringUtils.EMPTY); |
||||||
|
setCommonConfig(normalSsh); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new SshException("un support ssh type"); |
||||||
|
} |
||||||
|
usingSsh.setSelected(ssh.isUsingSsh()); |
||||||
|
changePane(); |
||||||
|
} |
||||||
|
|
||||||
|
private void setCommonConfig(BaseSsh baseSsh) { |
||||||
|
ip.setText(baseSsh.getIp()); |
||||||
|
port.setValue(baseSsh.getPort()); |
||||||
|
user.setText(baseSsh.getUser()); |
||||||
|
} |
||||||
|
|
||||||
|
public void update(JDBCDatabaseConnection jdbcDatabase) { |
||||||
|
Ssh ssh; |
||||||
|
switch (typeMap.get(type.getSelectedItem())) { |
||||||
|
case NORMAL: |
||||||
|
NormalSsh normalSsh = new NormalSsh(); |
||||||
|
normalSsh.setSecret(new String(password.getPassword()).trim()); |
||||||
|
getCommonConfig(normalSsh); |
||||||
|
ssh = normalSsh; |
||||||
|
break; |
||||||
|
case KEY: |
||||||
|
KeyVerifySsh keyVerifySsh = new KeyVerifySsh(); |
||||||
|
keyVerifySsh.setPrivateKeyPath(keyPath.getText().trim()); |
||||||
|
keyVerifySsh.setSecret(new String(secret.getPassword()).trim()); |
||||||
|
getCommonConfig(keyVerifySsh); |
||||||
|
ssh = keyVerifySsh; |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new SshException("un support ssh type"); |
||||||
|
} |
||||||
|
jdbcDatabase.setSsh(ssh); |
||||||
|
} |
||||||
|
|
||||||
|
private void getCommonConfig(BaseSsh baseSsh) { |
||||||
|
baseSsh.setUsingSsh(usingSsh.isSelected()); |
||||||
|
baseSsh.setIp(ip.getText().trim()); |
||||||
|
baseSsh.setPort(port.getValue()); |
||||||
|
baseSsh.setUser(user.getText().trim()); |
||||||
|
} |
||||||
|
|
||||||
|
public static class KeyFileUITextField extends UITextField { |
||||||
|
private static final Pattern ERROR_START = Pattern.compile("^([/\\\\.]+).*"); |
||||||
|
private static final Pattern MUTI_DOT = Pattern.compile("\\.+"); |
||||||
|
private static final String UPPER = ".."; |
||||||
|
|
||||||
|
public KeyFileUITextField(int columns) { |
||||||
|
this(); |
||||||
|
this.setColumns(columns); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public KeyFileUITextField() { |
||||||
|
super(); |
||||||
|
this.addKeyListener(new KeyAdapter() { |
||||||
|
@Override |
||||||
|
public void keyReleased(KeyEvent e) { |
||||||
|
String text = KeyFileUITextField.this.getTextOrigin(); |
||||||
|
if (!StringUtils.isEmpty(text)) { |
||||||
|
if (text.contains(UPPER)) { |
||||||
|
text = MUTI_DOT.matcher(text).replaceAll("."); |
||||||
|
KeyFileUITextField.this.setTextOrigin(text); |
||||||
|
} |
||||||
|
Matcher matcher = ERROR_START.matcher(text); |
||||||
|
if (matcher.matches()) { |
||||||
|
text = text.substring(matcher.group(1).length()); |
||||||
|
KeyFileUITextField.this.setTextOrigin(text); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public String getTextOrigin() { |
||||||
|
return super.getText(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setTextOrigin(String text) { |
||||||
|
super.setText(text); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getText() { |
||||||
|
// 获取的时候,不为空,给他加上前缀就好了,否则还是空
|
||||||
|
if (!StringUtils.isEmpty(super.getText())) { |
||||||
|
return SslUtils.PREFIX + super.getText(); |
||||||
|
} |
||||||
|
return StringUtils.EMPTY; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setText(String text) { |
||||||
|
// 设置的时候,不为空,说明文件指定了(文件需要是resource下),替换掉前缀
|
||||||
|
if (!StringUtils.isEmpty(text) && text.startsWith(SslUtils.PREFIX)) { |
||||||
|
super.setText(text.replaceFirst(SslUtils.PREFIX, "")); |
||||||
|
} else { |
||||||
|
super.setText(text); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,165 @@ |
|||||||
|
package com.fr.design.data.datapane.connect; |
||||||
|
|
||||||
|
import com.fr.data.impl.JDBCDatabaseConnection; |
||||||
|
import com.fr.data.security.ssl.Ssl; |
||||||
|
import com.fr.data.security.ssl.SslException; |
||||||
|
import com.fr.data.security.ssl.SslType; |
||||||
|
import com.fr.data.security.ssl.SslUtils; |
||||||
|
import com.fr.data.security.ssl.impl.NormalSsl; |
||||||
|
import com.fr.design.border.UITitledBorder; |
||||||
|
import com.fr.design.constants.UIConstants; |
||||||
|
import com.fr.design.data.datapane.connect.SshPane.KeyFileUITextField; |
||||||
|
import com.fr.design.dialog.BasicPane; |
||||||
|
import com.fr.design.gui.ibutton.UIButton; |
||||||
|
import com.fr.design.gui.icheckbox.UICheckBox; |
||||||
|
import com.fr.design.gui.ilable.UILabel; |
||||||
|
import com.fr.design.gui.itextfield.UITextField; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.layout.FRGUIPaneFactory; |
||||||
|
import com.fr.design.layout.TableLayout; |
||||||
|
import com.fr.design.layout.TableLayoutHelper; |
||||||
|
import com.fr.file.FILE; |
||||||
|
import com.fr.file.FILEChooserPane; |
||||||
|
import com.fr.file.filter.ChooseFileFilter; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
|
||||||
|
import javax.swing.ImageIcon; |
||||||
|
import javax.swing.JPanel; |
||||||
|
import javax.swing.SwingConstants; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.Component; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.event.ActionEvent; |
||||||
|
import java.awt.event.ActionListener; |
||||||
|
|
||||||
|
import static com.fr.design.i18n.Toolkit.i18nText; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author xiqiu |
||||||
|
* @date 2022/1/4 |
||||||
|
* @description |
||||||
|
*/ |
||||||
|
public class SslPane extends BasicPane { |
||||||
|
UICheckBox usingSsl = new UICheckBox(i18nText("Fine-Design_Basic_Ssl_Using")); |
||||||
|
private KeyFileUITextField keyPathCa = new KeyFileUITextField(18); |
||||||
|
private UIButton fileChooserButtonCa = new UIButton(); |
||||||
|
private KeyFileUITextField keyPathClientCert = new KeyFileUITextField(18); |
||||||
|
private UIButton fileChooserButtonClientCert = new UIButton(); |
||||||
|
private KeyFileUITextField keyPathClientKey = new KeyFileUITextField(18); |
||||||
|
private UIButton fileChooserButtonClientKey = new UIButton(); |
||||||
|
private UICheckBox verifyCa = new UICheckBox(i18nText("Fine-Design_Basic_Ssl_Verify_Ca")); |
||||||
|
// private UITextField cipher = new UITextField(20);
|
||||||
|
private JPanel jPanel; |
||||||
|
private Component[][] usingComps; |
||||||
|
private double p = TableLayout.PREFERRED; |
||||||
|
private double f = TableLayout.FILL; |
||||||
|
private JPanel contextPane; |
||||||
|
private double[] columnSize = new double[]{195, p}; |
||||||
|
|
||||||
|
public SslPane() { |
||||||
|
fileChooserButtonCa.setIcon(new ImageIcon(UIConstants.ACCESSIBLE_EDITOR_DOT)); |
||||||
|
fileChooserButtonClientCert.setIcon(new ImageIcon(UIConstants.ACCESSIBLE_EDITOR_DOT)); |
||||||
|
fileChooserButtonClientKey.setIcon(new ImageIcon(UIConstants.ACCESSIBLE_EDITOR_DOT)); |
||||||
|
this.setBorder(UITitledBorder.createBorderWithTitle(Toolkit.i18nText("Fine-Design_Basic_Ssl_Settings"))); |
||||||
|
this.setLayout(FRGUIPaneFactory.createLabelFlowLayout()); |
||||||
|
jPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); |
||||||
|
Dimension dimension = new Dimension(20, 20); |
||||||
|
fileChooserButtonCa.setPreferredSize(dimension); |
||||||
|
fileChooserButtonClientCert.setPreferredSize(dimension); |
||||||
|
fileChooserButtonClientKey.setPreferredSize(dimension); |
||||||
|
JPanel filePanelCa = TableLayoutHelper.createCommonTableLayoutPane(new Component[][]{{keyPathCa, fileChooserButtonCa}}, new double[]{p}, new double[]{f, 20}, 0); |
||||||
|
Component[] compCa = {new UILabel(Toolkit.i18nText("Fine-Design_Basic_Ssl_Ca") + ":", SwingConstants.RIGHT), filePanelCa}; |
||||||
|
Component[] compVerifyCa = {null, verifyCa}; |
||||||
|
JPanel filePanelClientKey = TableLayoutHelper.createCommonTableLayoutPane(new Component[][]{{keyPathClientKey, fileChooserButtonClientKey}}, new double[]{p}, new double[]{f, 20}, 0); |
||||||
|
Component[] compClientKey = {new UILabel(Toolkit.i18nText("Fine-Design_Basic_Ssl_Client_Key") + ":", SwingConstants.RIGHT), filePanelClientKey}; |
||||||
|
JPanel filePanelClientCert = TableLayoutHelper.createCommonTableLayoutPane(new Component[][]{{keyPathClientCert, fileChooserButtonClientCert}}, new double[]{p}, new double[]{f, 20}, 0); |
||||||
|
Component[] compClientCert = {new UILabel(Toolkit.i18nText("Fine-Design_Basic_Ssl_Client_Cert") + ":", SwingConstants.RIGHT), filePanelClientCert}; |
||||||
|
// Component[] comCipher = {new UILabel(Toolkit.i18nText("Fine-Design_Basic_Ssl_Cipher") + ":", SwingConstants.RIGHT), cipher};
|
||||||
|
usingComps = new Component[][]{ |
||||||
|
compCa, |
||||||
|
compVerifyCa, |
||||||
|
compClientKey, |
||||||
|
compClientCert |
||||||
|
// comCipher
|
||||||
|
}; |
||||||
|
usingSsl.setSelected(true); |
||||||
|
contextPane = TableLayoutHelper.createGapTableLayoutPane(usingComps, new double[]{p, p, p, p}, columnSize, 11, 11); |
||||||
|
jPanel.add(usingSsl, BorderLayout.NORTH); |
||||||
|
jPanel.add(contextPane, BorderLayout.CENTER); |
||||||
|
this.add(jPanel); |
||||||
|
usingSsl.addActionListener(new ActionListener() { |
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
changePane(); |
||||||
|
} |
||||||
|
}); |
||||||
|
fileChooserButtonCa.addActionListener(new TextFieldActionListener(keyPathCa)); |
||||||
|
fileChooserButtonClientCert.addActionListener(new TextFieldActionListener(keyPathClientCert)); |
||||||
|
fileChooserButtonClientKey.addActionListener(new TextFieldActionListener(keyPathClientKey)); |
||||||
|
} |
||||||
|
|
||||||
|
private void changePane() { |
||||||
|
contextPane.setVisible(usingSsl.isSelected()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
protected String title4PopupWindow() { |
||||||
|
return Toolkit.i18nText("Fine-Design_Basic_Ssl_Settings"); |
||||||
|
} |
||||||
|
|
||||||
|
public void populate(JDBCDatabaseConnection jdbcDatabase) { |
||||||
|
Ssl ssl = jdbcDatabase.getSsl(); |
||||||
|
if (ssl == null) { |
||||||
|
ssl = new NormalSsl(); |
||||||
|
jdbcDatabase.setSsl(ssl); |
||||||
|
} |
||||||
|
if (ssl.getSslType() == SslType.NORMAL) { |
||||||
|
NormalSsl normalSsl = (NormalSsl) ssl; |
||||||
|
keyPathCa.setText(normalSsl.getCaCertificate()); |
||||||
|
keyPathClientCert.setText(normalSsl.getClientCertificate()); |
||||||
|
keyPathClientKey.setText(normalSsl.getClientPrivateKey()); |
||||||
|
verifyCa.setSelected(normalSsl.isVerifyCa()); |
||||||
|
// cipher.setText(normalSsl.getCipher());
|
||||||
|
} else { |
||||||
|
throw new SslException("un support ssl type"); |
||||||
|
} |
||||||
|
usingSsl.setSelected(ssl.isUsingSsl()); |
||||||
|
changePane(); |
||||||
|
} |
||||||
|
|
||||||
|
public void update(JDBCDatabaseConnection jdbcDatabase) { |
||||||
|
NormalSsl normalSsl = new NormalSsl(); |
||||||
|
// normalSsl.setCipher(cipher.getText().trim());
|
||||||
|
normalSsl.setVerifyCa(verifyCa.isSelected()); |
||||||
|
normalSsl.setCaCertificate(keyPathCa.getText().trim()); |
||||||
|
normalSsl.setClientCertificate(keyPathClientCert.getText().trim()); |
||||||
|
normalSsl.setClientPrivateKey(keyPathClientKey.getText().trim()); |
||||||
|
normalSsl.setUsingSsl(usingSsl.isSelected()); |
||||||
|
jdbcDatabase.setSsl(normalSsl); |
||||||
|
} |
||||||
|
|
||||||
|
private class TextFieldActionListener implements ActionListener { |
||||||
|
private UITextField textField; |
||||||
|
|
||||||
|
public TextFieldActionListener(UITextField textField) { |
||||||
|
this.textField = textField; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void actionPerformed(ActionEvent e) { |
||||||
|
FILEChooserPane fileChooser = FILEChooserPane.getInstanceWithDesignatePath(SslUtils.PREFIX, new ChooseFileFilter(true), SslUtils.CERTIFICATES); |
||||||
|
int type = fileChooser.showOpenDialog(SslPane.this, StringUtils.EMPTY); |
||||||
|
if (type == FILEChooserPane.OK_OPTION) { |
||||||
|
final FILE file = fileChooser.getSelectedFILE(); |
||||||
|
if (file == null) { |
||||||
|
textField.setText(StringUtils.EMPTY); |
||||||
|
} else { |
||||||
|
textField.setText(file.getPath()); |
||||||
|
} |
||||||
|
} |
||||||
|
fileChooser.removeAllFilter(); |
||||||
|
fileChooser.removeTopPath(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
package com.fr.design.data.datapane.management.clip; |
||||||
|
|
||||||
|
import com.fr.base.TableData; |
||||||
|
import com.fr.design.data.tabledata.paste.TableDataFollowingPasteUtils; |
||||||
|
import com.fr.design.data.tabledata.wrapper.AbstractTableDataWrapper; |
||||||
|
import com.fr.design.data.tabledata.wrapper.TemplateTableDataWrapper; |
||||||
|
import com.fr.general.NameObject; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* 用于数据集的复制粘贴 |
||||||
|
* |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataTreeClipboard { |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据集名称 - 数据集Wrapper |
||||||
|
*/ |
||||||
|
private Map<String, AbstractTableDataWrapper> clip = new HashMap<>(); |
||||||
|
|
||||||
|
private static class Holder { |
||||||
|
private static final TableDataTreeClipboard INSTANCE = new TableDataTreeClipboard(); |
||||||
|
} |
||||||
|
|
||||||
|
private TableDataTreeClipboard() { |
||||||
|
} |
||||||
|
|
||||||
|
public static TableDataTreeClipboard getInstance() { |
||||||
|
return Holder.INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加选中的数据集数据到剪切板,覆盖原本剪切板内数据 |
||||||
|
* |
||||||
|
* @param copyMap |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public void addToClip(Map<String, AbstractTableDataWrapper> copyMap) { |
||||||
|
this.clip = copyMap; |
||||||
|
} |
||||||
|
|
||||||
|
public Map<String, AbstractTableDataWrapper> transferNameObjectArray2Map(NameObject[] selectedNameObjects) { |
||||||
|
Map<String, AbstractTableDataWrapper> resultMap = new HashMap<>(); |
||||||
|
if (selectedNameObjects == null) { |
||||||
|
return resultMap; |
||||||
|
} |
||||||
|
for (NameObject selectedNameObject : selectedNameObjects) { |
||||||
|
TableData cloned = TableDataFollowingPasteUtils.cloneTableData(((AbstractTableDataWrapper) selectedNameObject.getObject()).getTableData()); |
||||||
|
if (cloned != null) { |
||||||
|
resultMap.put(selectedNameObject.getName(), new TemplateTableDataWrapper(cloned)); |
||||||
|
} |
||||||
|
} |
||||||
|
return resultMap; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 取出剪切板内的所有数据集数据,剪切板不清空 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public Map<String, AbstractTableDataWrapper> takeFromClip() { |
||||||
|
return clip; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 清空剪切板 |
||||||
|
*/ |
||||||
|
public void reset() { |
||||||
|
clip.clear(); |
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue