You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
577 lines
21 KiB
577 lines
21 KiB
/* |
|
* Copyright(c) 2001-2010, FineReport Inc, All Rights Reserved. |
|
*/ |
|
package com.fr.design.file; |
|
|
|
import com.fr.base.FRContext; |
|
import com.fr.design.ExtraDesignClassManager; |
|
import com.fr.design.dialog.FineJOptionPane; |
|
import com.fr.design.fun.TemplateTreeDefineProcessor; |
|
import com.fr.design.gui.itree.filetree.TemplateFileTree; |
|
import com.fr.design.gui.itree.refreshabletree.ExpandMutableTreeNode; |
|
import com.fr.design.i18n.Toolkit; |
|
import com.fr.design.layout.FRGUIPaneFactory; |
|
import com.fr.design.lock.LockInfoUtils; |
|
import com.fr.design.mainframe.DesignerContext; |
|
import com.fr.design.mainframe.DesignerFrameFileDealerPane; |
|
import com.fr.design.lock.LockInfoDialog; |
|
import com.fr.design.mainframe.JTemplate; |
|
import com.fr.design.mainframe.manager.search.TemplateTreeSearchManager; |
|
import com.fr.design.mainframe.manager.search.searcher.control.pane.TemplateSearchRemindPane; |
|
import com.fr.design.mainframe.vcs.common.VcsHelper; |
|
import com.fr.file.FILE; |
|
import com.fr.file.FileNodeFILE; |
|
import com.fr.file.filetree.FileNode; |
|
import com.fr.file.filetree.IOFileNodeFilter; |
|
import com.fr.form.fit.web.editpreview.FileLockStateObservable; |
|
import com.fr.general.ComparatorUtils; |
|
import com.fr.general.IOUtils; |
|
import com.fr.log.FineLoggerFactory; |
|
import com.fr.workspace.base.UserInfo; |
|
import com.fr.stable.ArrayUtils; |
|
import com.fr.stable.CoreConstants; |
|
import com.fr.stable.StableUtils; |
|
import com.fr.stable.project.ProjectConstants; |
|
import com.fr.workspace.WorkContext; |
|
|
|
import com.fr.report.lock.LockInfoOperator; |
|
import java.util.UUID; |
|
import javax.swing.JOptionPane; |
|
import javax.swing.JPanel; |
|
import javax.swing.SwingUtilities; |
|
import javax.swing.SwingWorker; |
|
import javax.swing.ToolTipManager; |
|
import javax.swing.tree.DefaultMutableTreeNode; |
|
import javax.swing.tree.TreeNode; |
|
import javax.swing.tree.TreePath; |
|
import java.awt.BorderLayout; |
|
import java.awt.Desktop; |
|
import java.awt.Dimension; |
|
import java.awt.event.KeyEvent; |
|
import java.awt.event.KeyListener; |
|
import java.awt.event.MouseAdapter; |
|
import java.awt.event.MouseEvent; |
|
import java.awt.event.MouseListener; |
|
import java.io.File; |
|
import java.util.ArrayList; |
|
import java.util.Arrays; |
|
import java.util.Collection; |
|
import java.util.Enumeration; |
|
import java.util.Objects; |
|
import java.util.Observable; |
|
import java.util.Observer; |
|
import java.util.Set; |
|
import java.util.concurrent.ExecutionException; |
|
import java.util.stream.Collectors; |
|
|
|
import com.fr.workspace.server.vcs.VcsOperator; |
|
import org.jetbrains.annotations.Nullable; |
|
|
|
|
|
import static javax.swing.JOptionPane.WARNING_MESSAGE; |
|
import static javax.swing.JOptionPane.YES_NO_OPTION; |
|
|
|
public class TemplateTreePane extends JPanel implements FileOperations { |
|
|
|
public static TemplateTreePane getInstance() { |
|
return HOLDER.singleton; |
|
} |
|
|
|
private static class HOLDER { |
|
private static TemplateTreePane singleton = new TemplateTreePane(); |
|
} |
|
|
|
private TemplateFileTree reportletsTree; |
|
private TemplateSearchRemindPane remindPane; |
|
private FileToolbarStateChangeListener toolBarStateChangeListener; |
|
|
|
private TemplateTreePane() { |
|
this.setLayout(FRGUIPaneFactory.createBorderLayout()); |
|
this.setPreferredSize(new Dimension(250, super.getPreferredSize().height)); |
|
|
|
reportletsTree = new TemplateFileTree(); |
|
|
|
remindPane = new TemplateSearchRemindPane(getTemplateFileTree()); |
|
this.add(remindPane, BorderLayout.CENTER); |
|
|
|
ToolTipManager.sharedInstance().registerComponent(reportletsTree); |
|
|
|
FileLockStateObservable.getInstance().addObserver(new Observer() { |
|
@Override |
|
public void update(Observable o, Object arg) { |
|
TemplateTreePane.this.refresh(); |
|
} |
|
}); |
|
|
|
/* |
|
* Tree.MouseAdapter |
|
*/ |
|
MouseListener mouseFileTreeListener = new MouseAdapter() { |
|
|
|
@Override |
|
public void mousePressed(MouseEvent evt) { |
|
if (reportletsTree.getPathForLocation(evt.getX(), evt.getY()) != null && evt.getClickCount() == 2) { |
|
openFile(); |
|
} |
|
} |
|
|
|
@Override |
|
public void mouseClicked(MouseEvent e) { |
|
if (SwingUtilities.isRightMouseButton(e)) { |
|
TemplateTreeDefineProcessor processor = ExtraDesignClassManager.getInstance().getSingle(TemplateTreeDefineProcessor.XML_TAG); |
|
if (processor != null) { |
|
processor.rightClickAction(e); |
|
} else { |
|
DefaultTemplateTreeDefineProcessor.getInstance().rightClickAction(e); |
|
} |
|
} |
|
} |
|
|
|
@Override |
|
public void mouseReleased(MouseEvent e) { |
|
if (toolBarStateChangeListener != null) { |
|
toolBarStateChangeListener.stateChange(); |
|
} |
|
DesignerFrameFileDealerPane.getInstance().refreshRightToolBarBy(reportletsTree.getSelectedFileNode()); |
|
} |
|
}; |
|
// lx: add mouse listener |
|
this.reportletsTree.addMouseListener(mouseFileTreeListener); |
|
this.reportletsTree.addKeyListener(new KeyListener() { |
|
|
|
@Override |
|
public void keyPressed(KeyEvent e) { |
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) { |
|
openFile(); |
|
} |
|
} |
|
|
|
@Override |
|
public void keyReleased(KeyEvent e) { |
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) { |
|
openFile(); |
|
} |
|
if (toolBarStateChangeListener != null) { |
|
toolBarStateChangeListener.stateChange(); |
|
} |
|
} |
|
|
|
@Override |
|
public void keyTyped(KeyEvent e) { |
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) { |
|
openFile(); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
public TemplateFileTree getTemplateFileTree() { |
|
return this.reportletsTree; |
|
} |
|
|
|
|
|
/** |
|
* 选中的路径数 |
|
* |
|
* @return 选中的路径数 |
|
*/ |
|
public int countSelectedPath() { |
|
TreePath[] treePaths = reportletsTree.getSelectionPaths(); |
|
if (treePaths == null) { |
|
return 0; |
|
} |
|
return treePaths.length; |
|
} |
|
|
|
/** |
|
* 选中的文件夹个数 |
|
* |
|
* @return 选中的文件夹个数 |
|
*/ |
|
|
|
public int countSelectedFolder() { |
|
|
|
if (reportletsTree.getSelectedFolderPaths() == null) { |
|
return 0; |
|
} |
|
|
|
return reportletsTree.getSelectedFolderPaths().length; |
|
} |
|
|
|
/** |
|
* 选中的文件个数 |
|
* |
|
* @return 选中的文件 |
|
*/ |
|
public int countSelectedFile() { |
|
|
|
if (reportletsTree.getSelectionPaths() == null) { |
|
return 0; |
|
} |
|
|
|
return reportletsTree.getSelectedTemplatePaths().length; |
|
} |
|
|
|
/** |
|
* 刷新 |
|
*/ |
|
public void refreshDockingView() { |
|
reportletsTree.setFileNodeFilter(new IOFileNodeFilter(FRContext.getFileNodes().getSupportedTypes())); |
|
reportletsTree.refreshEnv(); |
|
} |
|
|
|
|
|
@Override |
|
public boolean mkdir(String path) { |
|
return TemplateResourceManager.getResource().mkdir(path); |
|
} |
|
|
|
/** |
|
* 打开选中的报表文件 |
|
*/ |
|
@Override |
|
public void openFile() { |
|
// 判断是否是远程设计的锁定文件 |
|
FileNode node = reportletsTree.getSelectedFileNode(); |
|
if (node == null) { |
|
return; |
|
} |
|
String reportPath = reportletsTree.getSelectedTemplatePath(); |
|
final String selectedFilePath = StableUtils.pathJoin(ProjectConstants.REPORTLETS_NAME, reportPath); |
|
String lock = node.getLock(); |
|
boolean showLockInfo = needShowLockInfo(lock, selectedFilePath, node); |
|
if (showLockInfo) { |
|
UserInfo userInfo = WorkContext.getCurrent().get(LockInfoOperator.class).getUserInfo(selectedFilePath); |
|
node.setLock(UUID.randomUUID().toString()); |
|
// 对于开发者预览占位锁定 定位到tab中 |
|
checkDevelopForBiddenTemplate(selectedFilePath); |
|
LockInfoDialog.show(userInfo); |
|
return; |
|
} else { |
|
node.setLock(null); |
|
} |
|
DesignerContext.getDesignerFrame().openTemplate(new FileNodeFILE(new FileNode(selectedFilePath, false))); |
|
} |
|
|
|
/** |
|
* 是否需要展示锁定信息 |
|
*/ |
|
public static boolean needShowLockInfo(String lock, String selectedFilePath, FileNode node) { |
|
return LockInfoUtils.isCompatibleOperator() |
|
|| LockInfoUtils.unableGetLockInfo() |
|
|| WorkContext.getCurrent().get(LockInfoOperator.class).isTplUnLocked(selectedFilePath) |
|
? (lock != null && !lock.equals(node.getUserID())) |
|
: WorkContext.getCurrent().get(LockInfoOperator.class).isTplLocked(selectedFilePath); |
|
} |
|
|
|
private void checkDevelopForBiddenTemplate(String selectedFilePath) { |
|
JTemplate<?, ?> template = getOpenedTemplate(selectedFilePath); |
|
if (template != null && template.isForbidden()) { |
|
DesignerContext.getDesignerFrame().openTemplate(new FileNodeFILE(new FileNode(selectedFilePath, false))); |
|
} |
|
} |
|
|
|
@Nullable |
|
private JTemplate<?, ?> getOpenedTemplate(String path) { |
|
for (JTemplate<?, ?> template : HistoryTemplateListCache.getInstance().getHistoryList()) { |
|
if (ComparatorUtils.equals(template.getEditingFILE().getPath(), path)) { |
|
return template; |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* 打开文件夹 |
|
*/ |
|
@Override |
|
public void showInExplorer() { |
|
FileNode fn = TemplateTreePane.this.reportletsTree.getSelectedFileNode(); |
|
String filePath = StableUtils.pathJoin(WorkContext.getCurrent().getPath(), fn.getEnvPath()); |
|
filePath = filePath.substring(0, filePath.lastIndexOf(CoreConstants.SEPARATOR)); |
|
try { |
|
Desktop.getDesktop().open(new File(filePath)); |
|
} catch (Exception e) { |
|
IOUtils.openWindowsFolder(StableUtils.pathJoin(WorkContext.getCurrent().getPath(), fn.getEnvPath())); |
|
} |
|
} |
|
|
|
/** |
|
* 刷新 |
|
*/ |
|
@Override |
|
public void refresh() { |
|
// 刷新远程文件夹权限 |
|
NodeAuthProcessor.getInstance().refresh(); |
|
reportletsTree.refresh(); |
|
DesignerFrameFileDealerPane.getInstance().refreshRightToolBarBy(null); |
|
FineLoggerFactory.getLogger().info(Toolkit.i18nText("Fine-Design_Basic_Template_File_Tree_Refresh_Successfully") + "!"); |
|
} |
|
|
|
/** |
|
* 刷新父目录 |
|
*/ |
|
@Override |
|
public void refreshParent() { |
|
// 刷新远程文件夹权限 |
|
NodeAuthProcessor.getInstance().refresh(); |
|
if (reportletsTree.getSelectionCount() == 0) { |
|
//没选中文件刷新根目录 |
|
reportletsTree.refresh(); |
|
return; |
|
} |
|
reportletsTree.refreshParent(Objects.requireNonNull(reportletsTree.getSelectionPath())); |
|
DesignerFrameFileDealerPane.getInstance().refreshRightToolBarBy(null); |
|
FineLoggerFactory.getLogger().info(Toolkit.i18nText("Fine-Design_Basic_Template_File_Tree_Refresh_Successfully")); |
|
} |
|
|
|
|
|
/** |
|
* 删除文件 |
|
* 文件夹和文件均可删除 |
|
* <p> |
|
* 当文件被锁时不能删除 |
|
* 当文件夹中包含被锁文件时不能删除 |
|
*/ |
|
@Override |
|
public void deleteFile() { |
|
|
|
|
|
ExpandMutableTreeNode[] treeNodes = reportletsTree.getSelectedTreeNodes(); |
|
// 筛选可以删除的文件 |
|
ArrayList<ExpandMutableTreeNode> deletableNodes = new ArrayList<>(); |
|
ArrayList<ExpandMutableTreeNode> lockedNodes = new ArrayList<>(); |
|
for (ExpandMutableTreeNode treeNode : treeNodes) { |
|
FileOperationHelper.getInstance().checkFreeOrLock(treeNode, deletableNodes, lockedNodes); |
|
} |
|
if (lockedNodes.isEmpty()) { |
|
|
|
String tipContent = |
|
countSelectedFolder() > 0 |
|
? Toolkit.i18nText("Fine-Design_Basic_Confirm_Delete_Folder") |
|
: Toolkit.i18nText("Fine-Design_Basic_Confirm_Delete_File"); |
|
|
|
if (FineJOptionPane.showConfirmDialog(DesignerContext.getDesignerFrame(), |
|
tipContent, |
|
Toolkit.i18nText("Fine-Design_Basic_Dialog_Prompt"), |
|
YES_NO_OPTION) |
|
== JOptionPane.YES_OPTION) { |
|
// 删除所有选中的即可 |
|
deleteNodes(Arrays.asList(treeNodes)); |
|
} |
|
} else { |
|
|
|
String tipContent = |
|
countSelectedFolder() > 0 |
|
? Toolkit.i18nText("Fine-Design_Basic_Confirm_Delete_Unlock_File_And_Folder") |
|
: Toolkit.i18nText("Fine-Design_Basic_Confirm_Delete_Unlock_File"); |
|
|
|
if (deletableNodes.isEmpty()) { |
|
// 提醒被锁定模板无法删除 |
|
FineJOptionPane.showMessageDialog(DesignerContext.getDesignerFrame(), |
|
Toolkit.i18nText("Fine-Design_Basic_Unable_Delete_Locked_File"), |
|
Toolkit.i18nText("Fine-Design_Basic_Alert"), |
|
WARNING_MESSAGE); |
|
return; |
|
} |
|
|
|
|
|
if (FineJOptionPane.showConfirmDialog(DesignerContext.getDesignerFrame(), |
|
tipContent, |
|
Toolkit.i18nText("Fine-Design_Basic_Dialog_Prompt"), |
|
YES_NO_OPTION) |
|
== JOptionPane.YES_OPTION) { |
|
// 删除其他 |
|
deleteNodes(deletableNodes); |
|
} |
|
} |
|
} |
|
|
|
private void refreshAfterDelete(Set<FileNode> deletedPaths) { |
|
if (TemplateTreeSearchManager.getInstance().isInSearchMode()) { |
|
TemplateTreeSearchManager.getInstance().deleteMatchedNode(deletedPaths); |
|
TemplateTreeSearchManager.getInstance().updateTemplateTree(); |
|
TemplateTreeSearchManager.getInstance().setRefreshing(false); |
|
} else { |
|
TreePath[] paths = reportletsTree.getSelectionPaths(); |
|
if (paths == null) { |
|
reportletsTree.refresh(); |
|
} else { |
|
// 删除一个节点的情况下,默认选中为该节点的上一级节点 |
|
if (paths.length == 1 && paths[0].getParentPath() != null) { |
|
reportletsTree.setSelectionPath(paths[0].getParentPath()); |
|
} |
|
for (TreePath path : Objects.requireNonNull(paths)) { |
|
reportletsTree.refreshParent(path); |
|
} |
|
} |
|
} |
|
} |
|
|
|
private void deleteNodes(Collection<ExpandMutableTreeNode> nodes) { |
|
new SwingWorker<Boolean,Void>(){ |
|
@Override |
|
protected Boolean doInBackground() throws Exception { |
|
boolean success = true; |
|
for (ExpandMutableTreeNode treeNode : nodes) { |
|
Object node = treeNode.getUserObject(); |
|
if (node instanceof FileNode) { |
|
FileNodeFILE nodeFILE = new FileNodeFILE((FileNode) node); |
|
if (nodeFILE.exists()) { |
|
if (!VcsHelper.getInstance().isLegacyMode()) { |
|
try { |
|
WorkContext.getCurrent().get(VcsOperator.class).recycleVersion(VcsHelper.getInstance().getCurrentUsername(), nodeFILE.getPath()); |
|
} catch (Exception e) { |
|
FineLoggerFactory.getLogger().error("[VcsV2] recycle {} failed", nodeFILE.getName()); |
|
} |
|
} |
|
if (TemplateResourceManager.getResource().delete(nodeFILE)) { |
|
HistoryTemplateListCache.getInstance().deleteFile(nodeFILE); |
|
} else { |
|
success = false; |
|
} |
|
} |
|
} |
|
} |
|
return success; |
|
} |
|
@Override |
|
protected void done() { |
|
try { |
|
if (!get()) { |
|
showErrorDialog(); |
|
} |
|
Set<FileNode> deletedFileNode = nodes.stream().map(treeNode -> (FileNode) treeNode.getUserObject()).collect(Collectors.toSet()); |
|
refreshAfterDelete(deletedFileNode); |
|
} catch (InterruptedException | ExecutionException e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
}.execute(); |
|
} |
|
|
|
private void showErrorDialog() { |
|
FineJOptionPane.showConfirmDialog(DesignerContext.getDesignerFrame(), |
|
Toolkit.i18nText("Fine-Design_Basic_Delete_Failure"), |
|
Toolkit.i18nText("Fine-Design_Basic_Error"), |
|
JOptionPane.DEFAULT_OPTION, |
|
JOptionPane.ERROR_MESSAGE); |
|
} |
|
|
|
|
|
@Override |
|
public void lockFile() { |
|
throw new UnsupportedOperationException("unsupport now"); |
|
} |
|
|
|
@Override |
|
public void unlockFile() { |
|
throw new UnsupportedOperationException("unsupport now"); |
|
} |
|
|
|
|
|
@Override |
|
public String getFilePath() { |
|
return reportletsTree.getSelectedTemplatePath(); |
|
} |
|
|
|
@Override |
|
public boolean access() { |
|
|
|
TreePath[] selectedTreePaths = reportletsTree.getSelectionPaths(); |
|
|
|
if (ArrayUtils.isEmpty(selectedTreePaths)) { |
|
return false; |
|
} |
|
// 选中的是文件夹 |
|
TreePath treePath = selectedTreePaths[0]; |
|
ExpandMutableTreeNode currentTreeNode = (ExpandMutableTreeNode) treePath.getLastPathComponent(); |
|
|
|
ExpandMutableTreeNode parentTreeNode = (ExpandMutableTreeNode) currentTreeNode.getParent(); |
|
|
|
return parentTreeNode != null && parentTreeNode.hasFullAuthority(); |
|
} |
|
|
|
@Override |
|
public boolean rename(FILE tplFile, String from, String to) { |
|
|
|
// 多人协作时判断是否有锁定的文件 |
|
ExpandMutableTreeNode[] treeNodes = reportletsTree.getSelectedTreeNodes(); |
|
// 筛选可以重命名的文件 |
|
ArrayList<ExpandMutableTreeNode> unlockedNodes = new ArrayList<>(); |
|
ArrayList<ExpandMutableTreeNode> lockedNodes = new ArrayList<>(); |
|
for (ExpandMutableTreeNode treeNode : treeNodes) { |
|
FileOperationHelper.getInstance().checkFreeOrLock(treeNode, unlockedNodes, lockedNodes); |
|
} |
|
|
|
if (!lockedNodes.isEmpty()) { |
|
FineJOptionPane.showConfirmDialog(DesignerContext.getDesignerFrame(), |
|
Toolkit.i18nText("Fine-Design_Basic_Warn_Rename_Lock_File"), |
|
Toolkit.i18nText("Fine-Design_Basic_Alert"), |
|
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE); |
|
return true; |
|
} |
|
|
|
try { |
|
// 接收的是WEB-INF下的路径 |
|
return TemplateResourceManager.getResource().rename(from, to); |
|
} catch (Exception e) { |
|
FineLoggerFactory.getLogger().error(e.getMessage(), e); |
|
return false; |
|
} |
|
} |
|
|
|
@Override |
|
public FileNode getFileNode() { |
|
return reportletsTree.getSelectedFileNode(); |
|
} |
|
|
|
public void setToolbarStateChangeListener(FileToolbarStateChangeListener listener) { |
|
this.toolBarStateChangeListener = listener; |
|
} |
|
|
|
|
|
/** |
|
* 仅支持在拥有完整权限的文件夹下进行新建和重命名操作,那么是可以看到改文件夹下所有文件的。 |
|
* |
|
* @param newName 原名 |
|
* @param suffix 后缀名 |
|
* @param baseOnSelf 检验目录时,是基于自身目录的子节点还是基于父目录的子节点进行校验 |
|
* @return 是否有重名的 |
|
*/ |
|
@Override |
|
public boolean duplicated(String newName, String suffix, boolean baseOnSelf) { |
|
|
|
// 选中的节点 |
|
TreePath treePath = reportletsTree.getSelectionPath(); |
|
if (treePath == null) { |
|
return false; |
|
} |
|
boolean result = false; |
|
Enumeration children = getChild(treePath, baseOnSelf); |
|
|
|
while (children.hasMoreElements()) { |
|
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) children.nextElement(); |
|
Object object = childNode.getUserObject(); |
|
if (object instanceof FileNode) { |
|
if (ComparatorUtils.equals(((FileNode) object).getName(), newName + suffix)) { |
|
result = true; |
|
} |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
private Enumeration getChild(TreePath treePath, boolean baseOnSelf) { |
|
DefaultMutableTreeNode currentTreeNode = (DefaultMutableTreeNode) treePath.getLastPathComponent(); |
|
Enumeration children; |
|
//现在可以在目录下创建目录,重命名时是基于所选节点的父目录的子目录进行校验,目录下新建子目录时,基于所选目录的子目录进行校验 |
|
if (reportletsTree.getSelectedFileNode().isDirectory() && baseOnSelf) { |
|
children = currentTreeNode.children(); |
|
} else { |
|
TreeNode parentTreeNode = currentTreeNode.getParent(); |
|
children = parentTreeNode.children(); |
|
} |
|
return children; |
|
} |
|
}
|
|
|