From d1eadfe6a4e9d5fc0fee7b7a539f1ce41851c460 Mon Sep 17 00:00:00 2001 From: Starryi Date: Tue, 24 Aug 2021 12:34:23 +0800 Subject: [PATCH] =?UTF-8?q?REPORT-51919=20=E4=B8=BB=E9=A2=98=E5=88=87?= =?UTF-8?q?=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【问题原因】 新增模版主题功能: 1. 修复主题缩略图模糊有锯齿的问题 2. 修复主题报表背景可能NPE的问题 3. 优化主题读取时的性能: 按需读取,延迟加载 4. 保存和删除主题时使用事务 5. 使用线程池优化主题列表界面的数据加载性能 【改动思路】 同上 --- .../mainframe/theme/TemplateThemeBlock.java | 66 ++++++++++--- .../theme/TemplateThemeListPane.java | 26 ++++-- .../theme/TemplateThemeManagePane.java | 21 +++-- .../theme/TemplateThemePreviewPane.java | 61 +++++++++++- .../theme/TemplateThemeProfilePane.java | 93 ++++++++----------- .../dialog/TemplateThemeManageDialog.java | 4 +- .../dialog/TemplateThemeUsingDialog.java | 3 +- 7 files changed, 192 insertions(+), 82 deletions(-) diff --git a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeBlock.java b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeBlock.java index 3be7e4f090..929b5ef9fb 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeBlock.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeBlock.java @@ -23,11 +23,12 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; +import java.awt.Image; import java.awt.Rectangle; import java.awt.Window; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.awt.image.BufferedImage; +import java.util.concurrent.ExecutorService; /** * @author Starryi @@ -54,12 +55,16 @@ public class TemplateThemeBlock extends JPanel { private boolean hovering = false; private MouseAdapter clickListener; + private final ExecutorService asyncThemeFetcher; - public TemplateThemeBlock(String name, TemplateThemeConfig config, TemplateThemeProfilePane profilePane) { + public TemplateThemeBlock(String name, + TemplateThemeConfig config, + TemplateThemeProfilePane profilePane, + ExecutorService asyncThemeFetcher) { this.name = name; this.config = config; - this.theme = this.config.find(name); this.profilePane = profilePane; + this.asyncThemeFetcher = asyncThemeFetcher; initializePane(); addMouseListener(new MouseAdapter() { @@ -87,7 +92,7 @@ public class TemplateThemeBlock extends JPanel { } }); - refresh(); + fetchTheme(); } private void initializePane() { @@ -148,6 +153,9 @@ public class TemplateThemeBlock extends JPanel { } private void openProfileDialog() { + if (theme == null) { + return; + } Window parent = SwingUtilities.getWindowAncestor(TemplateThemeBlock.this); TemplateThemeProfileDialog profileDialog = new TemplateThemeProfileDialog<>(parent, profilePane); try { @@ -158,16 +166,46 @@ public class TemplateThemeBlock extends JPanel { profileDialog.setVisible(true); } - public void refresh() { - theme = this.config.find(name); - ThemeThumbnail thumbnail = theme.getThumbnail(); - if (thumbnail != null) { - BufferedImage image = thumbnail.getImage(); - if (image != null) { - thumbnailLabel.setIcon(new ImageIcon(thumbnail.getImage())); - } + public void fetchTheme() { + if (asyncThemeFetcher.isShutdown()) { + return; } - repaint(); + asyncThemeFetcher.submit(new Runnable() { + @Override + public void run() { + if (asyncThemeFetcher.isShutdown()) { + return; + } + + theme = null; + // 耗时的同步操作,如远程设计器场景 + theme = config.cachedFetch(name, new TemplateThemeConfig.CacheCondition() { + @Override + public boolean shouldCacheTheme(T theme) { + // 如果Fetcher已经关闭就不放进缓存里了 + // 因为可切换工作目录,所以submit时的工作目录环境与最终获取到主题数据时的工作目录环境可能不是同一个, + // 如果仍然放进缓存中,会污染当前工作目录环境的主题缓存. + // TODO: 除了根据asyncThemeFetch的关闭情况来判断是否缓存主题,也可以更加精细的判断前后的工作目录环境是否时同一个 + // TODO: 后续看情况再优化吧. + return !asyncThemeFetcher.isShutdown(); + } + }); + + if (asyncThemeFetcher.isShutdown()) { + return; + } + if (theme != null) { + ThemeThumbnail thumbnail = theme.getThumbnail(); + if (thumbnail != null) { + Image image = thumbnail.getImage(); + if (image != null) { + thumbnailLabel.setIcon(new ImageIcon(image)); + } + } + } + repaint(); + } + }); } @Override @@ -193,4 +231,4 @@ public class TemplateThemeBlock extends JPanel { GraphHelper.draw(g, rectangle, Constants.LINE_MEDIUM); } } -} +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeListPane.java b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeListPane.java index 7cea43260d..247c373700 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeListPane.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeListPane.java @@ -2,12 +2,14 @@ package com.fr.design.mainframe.theme; import com.fr.base.theme.TemplateTheme; import com.fr.base.theme.TemplateThemeConfig; +import com.fr.concurrent.NamedThreadFactory; import com.fr.design.designer.IntervalConstants; import com.fr.design.dialog.BasicPane; import com.fr.design.event.ChangeEvent; import com.fr.design.event.ChangeListener; import com.fr.design.gui.icontainer.UIScrollPane; import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.module.ModuleContext; import com.fr.stable.StringUtils; import javax.swing.BorderFactory; @@ -20,6 +22,7 @@ import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutorService; /** * @author Starryi @@ -31,6 +34,12 @@ public class TemplateThemeListPane extends BasicPane { public static final int BLOCK_GAP = IntervalConstants.INTERVAL_L1; public static final int CONTENT_WIDTH = 630; + private final ExecutorService asyncThemeFetcher = + ModuleContext.getExecutor().newFixedThreadPool( + 10, + new NamedThreadFactory("TemplateThemeListPane") + ); + protected final TemplateThemeConfig config; private final TemplateThemeProfilePane profilePane; private final JPanel contentListPane; @@ -72,8 +81,7 @@ public class TemplateThemeListPane extends BasicPane { contentListPane.removeAll(); List names = config.getThemeNames(); for (String name: names) { - T theme = config.find(name); - if (theme != null) { + if (config.contains(name)) { TemplateThemeBlock block = createCachedTemplateThemeBlock(name); contentListPane.add(block); if (StringUtils.equals(name, config.getTheme4NewTemplate().getName())) { @@ -92,7 +100,7 @@ public class TemplateThemeListPane extends BasicPane { } private TemplateThemeBlock createNewTemplateThemeBlock(String name) { - final TemplateThemeBlock block = new TemplateThemeBlock<>(name, config, profilePane); + final TemplateThemeBlock block = new TemplateThemeBlock<>(name, config, profilePane, asyncThemeFetcher); block.addClickListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { @@ -138,17 +146,17 @@ public class TemplateThemeListPane extends BasicPane { switch (event.action) { case DEFAULT_THEME_4_NEW_TEMPLATE_UPDATE: { if (block4newTemplate != null) { - block4newTemplate.refresh(); + block4newTemplate.fetchTheme(); } if (existingBlock != null) { - existingBlock.refresh(); + existingBlock.fetchTheme(); } block4newTemplate = existingBlock; break; } case UPDATE: { if (existingBlock != null) { - existingBlock.refresh(); + existingBlock.fetchTheme(); } break; } @@ -186,4 +194,8 @@ public class TemplateThemeListPane extends BasicPane { themeConfigChangeListener = null; } } -} + + public void stopAsyncFetchTheme() { + asyncThemeFetcher.shutdown(); + } +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeManagePane.java b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeManagePane.java index 84aee2d6be..5dab5b7f3a 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeManagePane.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeManagePane.java @@ -22,6 +22,10 @@ import com.fr.design.menu.ToolBarDef; import com.fr.general.IOUtils; import com.fr.log.FineLoggerFactory; import com.fr.stable.StringUtils; +import com.fr.third.checkerframework.checker.nullness.qual.Nullable; +import com.fr.third.guava.util.concurrent.FutureCallback; +import com.fr.transaction.Configurations; +import com.fr.transaction.WorkerFacade; import javax.swing.BorderFactory; import javax.swing.JOptionPane; @@ -213,7 +217,12 @@ public class TemplateThemeManagePane extends BasicPane Toolkit.i18nText("Fine-Design_Template_Theme_Manager_Pane_Delete_Tip", theme.getName()), Toolkit.i18nText("Fine-Design_Basic_Delete"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if (result == JOptionPane.YES_OPTION) { - removeTheme(theme.getName()); + Configurations.modify(new WorkerFacade(config.getClass()) { + @Override + public void run() { + config.removeTheme(theme.getName()); + } + }); } } } @@ -244,10 +253,6 @@ public class TemplateThemeManagePane extends BasicPane } } - private void removeTheme(String name) { - config.removeTheme(name); - } - public static class BottomLineBorder extends LineBorder { private BottomLineBorder(Color color, int thickness) { @@ -280,4 +285,8 @@ public class TemplateThemeManagePane extends BasicPane public void stopListenThemeConfig() { themeListPane.stopListenThemeConfig(); } -} + + public void stopAsyncFetchTheme() { + themeListPane.stopAsyncFetchTheme(); + } +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemePreviewPane.java b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemePreviewPane.java index af46690415..68c858f27e 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemePreviewPane.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemePreviewPane.java @@ -2,9 +2,17 @@ package com.fr.design.mainframe.theme; import com.fr.base.chart.chartdata.CallbackEvent; import com.fr.base.theme.TemplateTheme; +import com.fr.base.theme.settings.ThemeThumbnail; import com.fr.design.mainframe.theme.preview.ThemePreviewed; +import com.fr.log.FineLoggerFactory; import javax.swing.JPanel; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.RenderingHints; +import java.awt.Transparency; +import java.awt.image.BufferedImage; /** * @author Starryi @@ -26,4 +34,55 @@ public abstract class TemplateThemePreviewPane extends this.repaint(); } } -} + + protected Image createThumbnailImage() { + BufferedImage image = null; + + int thumbnailWidth = ThemeThumbnail.WIDTH; + int thumbnailHeight = ThemeThumbnail.HEIGHT; + float thumbnailAspect = 1.0F * thumbnailWidth / thumbnailHeight; + + int width = getWidth(); + int height = getHeight(); + float aspect = 1.0F * width / height; + + if (thumbnailAspect > aspect) { + height = (int) (width / thumbnailAspect); + } else { + width = (int) (height * thumbnailAspect); + } + + try { + // 使用TYPE_INT_RGB和new Color(255, 255, 255, 1)设置有透明背景buffer image, + // 使得创建出来的透明像素是(255, 255, 255, 1),而不是(0, 0, 0, 0) + // 这样不支持透明通道缩略图的旧设计器打开新设计器创建的模版时,就不会创建出拥有黑色背景的缩略图 + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = image.createGraphics(); + // 创建一个支持透明背景的buffer image + image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT); + g2d.dispose(); + g2d = image.createGraphics(); + + // 使用TYPE_INT_RGB和new Color(255, 255, 255, 1)设置有透明背景buffer image, + // 使得创建出来的透明像素是(255, 255, 255, 1),而不是(0, 0, 0, 0) + // 这样不支持透明通道缩略图的旧设计器打开新设计器创建的模版时,就不会创建出拥有黑色背景的缩略图 + g2d.setColor(new Color(255, 255, 255, 1)); + g2d.fillRect(0, 0, width, height); + + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.clipRect(0, 0, width, height); + + paint(g2d); + + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + + if (image != null) { + return image.getScaledInstance(thumbnailWidth, thumbnailHeight, BufferedImage.SCALE_SMOOTH); + } + + return null; + } +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeProfilePane.java b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeProfilePane.java index edbe231515..e0f5c06a2a 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeProfilePane.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeProfilePane.java @@ -25,8 +25,10 @@ import com.fr.design.mainframe.theme.edit.ui.ColorListPane; import com.fr.design.mainframe.theme.edit.ui.LabelUtils; import com.fr.design.mainframe.theme.ui.BorderUtils; import com.fr.design.utils.gui.GUICoreUtils; -import com.fr.log.FineLoggerFactory; import com.fr.stable.StringUtils; +import com.fr.transaction.CallBackAdaptor; +import com.fr.transaction.Configurations; +import com.fr.transaction.WorkerFacade; import javax.swing.BorderFactory; import javax.swing.JComponent; @@ -36,19 +38,17 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; -import java.util.List; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; -import java.awt.Graphics2D; -import java.awt.Transparency; +import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; +import java.util.List; /** * @author Starryi @@ -316,7 +316,7 @@ public abstract class TemplateThemeProfilePane extends String name = theme.getName(); - currentIsNewTheme = config.find(name) == null; + currentIsNewTheme = config.cachedFetch(name) == null; nameTextField.setText(name); nameTextField.setEnabled(StringUtils.isEmpty(name)); @@ -342,7 +342,12 @@ public abstract class TemplateThemeProfilePane extends theme.setName(this.nameTextField.getText()); - theme.setThumbnail(updateThumbnail()); + Image thumbnailImage = themePreviewPane.createThumbnailImage(); + if (thumbnailImage != null) { + ThemeThumbnail thumbnail = new ThemeThumbnail(); + thumbnail.setImage(thumbnailImage); + theme.setThumbnail(thumbnail); + } ThemedCellStyleList cellStyleConfig = this.cellStyleSettingPane.updateBean(); theme.setCellStyleList(cellStyleConfig); @@ -363,42 +368,6 @@ public abstract class TemplateThemeProfilePane extends protected abstract void updateBean(T theme); - protected ThemeThumbnail updateThumbnail() { - BufferedImage image = null; - int imgWidth = ThemeThumbnail.WIDTH; - int imgHeight = ThemeThumbnail.HEIGHT; - - int canvasWidth = themePreviewPane.getWidth(); - int canvasHeight = themePreviewPane.getHeight(); - try { - // 使用TYPE_INT_RGB和new Color(255, 255, 255, 1)设置有透明背景buffer image, - // 使得创建出来的透明像素是(255, 255, 255, 1),而不是(0, 0, 0, 0) - // 这样不支持透明通道缩略图的旧设计器打开新设计器创建的模版时,就不会创建出拥有黑色背景的缩略图 - image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB); - Graphics2D g2d = image.createGraphics(); - // 创建一个支持透明背景的buffer image - image = g2d.getDeviceConfiguration().createCompatibleImage(imgWidth, imgHeight, Transparency.TRANSLUCENT); - g2d.dispose(); - g2d = image.createGraphics(); - - // 使用TYPE_INT_RGB和new Color(255, 255, 255, 1)设置有透明背景buffer image, - // 使得创建出来的透明像素是(255, 255, 255, 1),而不是(0, 0, 0, 0) - // 这样不支持透明通道缩略图的旧设计器打开新设计器创建的模版时,就不会创建出拥有黑色背景的缩略图 - g2d.setColor(new Color(255, 255, 255, 1)); - g2d.fillRect(0, 0, imgWidth, imgHeight); - - float scale = Math.max(1.0F * imgWidth / canvasWidth, 1.0F * imgHeight / canvasHeight); - g2d.scale(scale, scale); - themePreviewPane.paint(g2d); - - } catch (Exception e) { - FineLoggerFactory.getLogger().error(e.getMessage(), e); - } - ThemeThumbnail thumbnail = new ThemeThumbnail(); - thumbnail.setImage(image); - return thumbnail; - } - public UIButton createSaveButton() { saveButton = new UIButton(); saveButton.setText(Toolkit.i18nText("Fine-Design_Basic_Save")); @@ -410,10 +379,20 @@ public abstract class TemplateThemeProfilePane extends boolean canBeSaved = checkThemeCanBeSavedAndUpdateUI(currentIsNewTheme, nameTextField, nameErrorLabel, saveButton); if (canBeSaved && theme != null) { theme.setName(nameTextField.getText()); - config.addTheme(theme, true); - currentIsNewTheme = false; - nameTextField.setEnabled(false); - saveAsButton.setEnabled(true); + Configurations.modify(new WorkerFacade(config.getClass()) { + @Override + public void run() { + config.addTheme(theme, true); + } + }.addCallBack(new CallBackAdaptor() { + @Override + public void afterCommit() { + super.afterCommit(); + currentIsNewTheme = false; + nameTextField.setEnabled(false); + saveAsButton.setEnabled(true); + } + })); } } }); @@ -475,9 +454,19 @@ public abstract class TemplateThemeProfilePane extends theme.setName(nameTextField.getText()); theme.setRemovable(true); theme.setMutable(true); - config.addTheme(theme, true); - exit(); - parent.exit(); + Configurations.modify(new WorkerFacade(config.getClass()) { + @Override + public void run() { + config.addTheme(theme, true); + } + }.addCallBack(new CallBackAdaptor() { + @Override + public void afterCommit() { + super.afterCommit(); + exit(); + parent.exit(); + } + })); } } }); @@ -560,7 +549,7 @@ public abstract class TemplateThemeProfilePane extends return StringUtils.isEmpty(name); } private boolean isThemeNameDuplicated(String name) { - return config.find(name) != null; + return config.cachedFetch(name) != null; } private boolean checkThemeCanBeSavedAndUpdateUI(boolean checkDuplicated, UITextField textField, UILabel errorLabel, UIButton... actionButtons) { String error = StringUtils.EMPTY; @@ -588,4 +577,4 @@ public abstract class TemplateThemeProfilePane extends return valid; } -} +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/mainframe/theme/dialog/TemplateThemeManageDialog.java b/designer-base/src/main/java/com/fr/design/mainframe/theme/dialog/TemplateThemeManageDialog.java index 195771ffc3..3eeee55fe2 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/theme/dialog/TemplateThemeManageDialog.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/theme/dialog/TemplateThemeManageDialog.java @@ -85,7 +85,9 @@ public class TemplateThemeManageDialog extends TemplateThemeDialog { public void exit() { formThemesManagerPane.stopListenThemeConfig(); + formThemesManagerPane.stopAsyncFetchTheme(); reportThemesManagerPane.stopListenThemeConfig(); + reportThemesManagerPane.stopAsyncFetchTheme(); } } -} +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/mainframe/theme/dialog/TemplateThemeUsingDialog.java b/designer-base/src/main/java/com/fr/design/mainframe/theme/dialog/TemplateThemeUsingDialog.java index 86ee48eb47..7d1a998257 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/theme/dialog/TemplateThemeUsingDialog.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/theme/dialog/TemplateThemeUsingDialog.java @@ -146,6 +146,7 @@ public class TemplateThemeUsingDialog extends TemplateT public void exit() { themeListPane.stopListenThemeConfig(); + themeListPane.stopAsyncFetchTheme(); super.exit(); } @@ -157,4 +158,4 @@ public class TemplateThemeUsingDialog extends TemplateT usingCurrentThemeButton.setEnabled(false); } } -} +} \ No newline at end of file