diff --git a/designer-base/src/main/java/com/fr/design/data/BasicTableDataTreePane.java b/designer-base/src/main/java/com/fr/design/data/BasicTableDataTreePane.java index 530b46693..f45e84f20 100644 --- a/designer-base/src/main/java/com/fr/design/data/BasicTableDataTreePane.java +++ b/designer-base/src/main/java/com/fr/design/data/BasicTableDataTreePane.java @@ -378,9 +378,7 @@ public abstract class BasicTableDataTreePane extends DockingView implements Resp } protected boolean isDsNameRepeaded(String name) { - if (allDSNames == null) { - allDSNames = DesignTableDataManager.getAllDSNames(tc.getBook()); - } + allDSNames = DesignTableDataManager.getAllDSNames(tc.getBook()); for (int i = 0; i < allDSNames.length; i++) { if (ComparatorUtils.equals(name, allDSNames[i])) { return true; diff --git a/designer-base/src/main/java/com/fr/design/data/DesignTableDataManager.java b/designer-base/src/main/java/com/fr/design/data/DesignTableDataManager.java index f7eb9bacc..468982d96 100644 --- a/designer-base/src/main/java/com/fr/design/data/DesignTableDataManager.java +++ b/designer-base/src/main/java/com/fr/design/data/DesignTableDataManager.java @@ -308,6 +308,17 @@ public abstract class DesignTableDataManager { return resMap; } + /** + * 不根据过滤设置,返回当前模板数据集,是有顺序的 + */ + public static java.util.Map getTemplateDataSet(TableDataSource source) { + java.util.Map resMap = new java.util.LinkedHashMap(); + // 模板数据集 + addTemplateData(resMap, source); + + return resMap; + } + public static java.util.Map getAllDataSetIncludingProcedure(java.util.Map resMap) { java.util.LinkedHashMap dsMap = new java.util.LinkedHashMap(); Iterator> entryIt = resMap.entrySet().iterator(); diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/TableDataTree.java b/designer-base/src/main/java/com/fr/design/data/datapane/TableDataTree.java index 80471b9e6..c7dffd63f 100644 --- a/designer-base/src/main/java/com/fr/design/data/datapane/TableDataTree.java +++ b/designer-base/src/main/java/com/fr/design/data/datapane/TableDataTree.java @@ -2,6 +2,7 @@ package com.fr.design.data.datapane; import com.fr.base.BaseUtils; import com.fr.design.constants.UIConstants; +import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; import com.fr.design.data.tabledata.wrapper.TableDataWrapper; import com.fr.design.gui.itree.refreshabletree.ExpandMutableTreeNode; import com.fr.design.gui.itree.refreshabletree.UserObjectRefreshJTree; @@ -17,6 +18,8 @@ import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import java.awt.Color; import java.awt.Component; +import java.util.ArrayList; +import java.util.List; /** * TableData Tree @@ -89,7 +92,7 @@ public class TableDataTree extends UserObjectRefreshJTree { return; } boolean refreshall = childName.isEmpty(); - ExpandMutableTreeNode[] new_nodes = loadChildTreeNodes(eTreeNode); + ExpandMutableTreeNode[] newNodes = loadChildTreeNodes(eTreeNode); java.util.List childTreeNodeList = new java.util.ArrayList(); for (int i = 0, len = eTreeNode.getChildCount(); i < len; i++) { @@ -102,30 +105,30 @@ public class TableDataTree extends UserObjectRefreshJTree { eTreeNode.removeAllChildren(); - for (int ci = 0; ci < new_nodes.length; ci++) { - Object cUserObject = new_nodes[ci].getUserObject(); + for (int ci = 0; ci < newNodes.length; ci++) { + Object cUserObject = newNodes[ci].getUserObject(); ExpandMutableTreeNode cTreeNode = null; for (int ni = 0, nlen = childTreeNodeList.size(); ni < nlen; ni++) { cTreeNode = (ExpandMutableTreeNode) childTreeNodeList.get(ni); if (ComparatorUtils.equals(cTreeNode.getUserObject(), cUserObject)) { if (!refreshall && !ComparatorUtils.equals(childName, ((NameObject) cUserObject).getName())) { - new_nodes[ci] = cTreeNode; + newNodes[ci] = cTreeNode; break; } - new_nodes[ci].setExpanded(cTreeNode.isExpanded()); + newNodes[ci].setExpanded(cTreeNode.isExpanded()); // REPORT-41299 如果建立的是错误的数据集(没有Child的情况)且这个错误数据集处于isExpanded状态,会在后面的if语句中调用getFirstChild()产生异常,因此这里判断一下 - if (cTreeNode.isExpanded() && cTreeNode.getChildCount() == 0) { - new_nodes[ci].setExpanded(false); + if (cTreeNode.getChildCount() == 0) { + newNodes[ci].setExpanded(false); break; } if (cTreeNode.getFirstChild() instanceof ExpandMutableTreeNode && cTreeNode.isExpanded()) { - checkChildNodes(cTreeNode, new_nodes[ci]); + checkChildNodes(cTreeNode, newNodes[ci]); } break; } } - eTreeNode.add(new_nodes[ci]); + eTreeNode.add(newNodes[ci]); } } @@ -140,9 +143,7 @@ public class TableDataTree extends UserObjectRefreshJTree { for (int k = 0; k < nodes.length; k++) { newChild.add(nodes[k]); } - if (newChild.getChildCount() > 1 && ((ExpandMutableTreeNode) newChild.getFirstChild()).getUserObject() == PENDING) { - newChild.remove(0); - } + removePending(newChild); if (ComparatorUtils.equals(oldChild.getUserObject(), newChild.getUserObject())) { newChild.setExpanded(oldChild.isExpanded()); } @@ -150,6 +151,70 @@ public class TableDataTree extends UserObjectRefreshJTree { } } + private void removePending(ExpandMutableTreeNode treeNode) { + if (treeNode.getChildCount() > 1 && ((ExpandMutableTreeNode) treeNode.getFirstChild()).getUserObject() == PENDING) { + treeNode.remove(0); + } + } + + @Override + public void refresh4TreeSearch() { + ExpandMutableTreeNode root = (ExpandMutableTreeNode) this.getModel().getRoot(); + refreshTreeNode4TreeSearch(root); + ((DefaultTreeModel) this.getModel()).reload(root); + root.expandCurrentTreeNode(this); + } + + /** + * 主要是处理节点是否应该添加为搜索结果,以及节点是否需要展开 + * + * @param root + */ + private void refreshTreeNode4TreeSearch(ExpandMutableTreeNode root) { + if (interceptRefresh(root)) { + return; + } + // 获取数据集子节点 + ExpandMutableTreeNode[] dsTreeNodes = loadChildTreeNodes(root); + root.removeAllChildren(); + for (ExpandMutableTreeNode dsTreeNode : dsTreeNodes) { + if (TableDataTreeSearchManager.getInstance().nodeMatches(dsTreeNode)) { + // 加载数据列节点 + loadAndAddChildTreeChild(dsTreeNode); + // 处理子节点的展开 + TableDataTreeSearchManager.getInstance().dealWithNodeExpand(dsTreeNode); + // 添加数据集子节点 + root.add(dsTreeNode); + } + } + } + + /** + * 加载所有子节点,并添加到父节点中 + * + * @param treeNode + * @return + */ + private ExpandMutableTreeNode loadAndAddChildTreeChild(ExpandMutableTreeNode treeNode) { + if (TableDataTreeSearchManager.getInstance().isTreeNodeStoreProcedure(treeNode)) { + // 如果是存储过程,则再加载一次其子表节点,这里比较坑的就是存储过程不能使用loadChildTreeNodes + int tableChildCounts = treeNode.getChildCount(); + ExpandMutableTreeNode[] childs = new ExpandMutableTreeNode[tableChildCounts]; + for (int i = 0; i < tableChildCounts; i++) { + ExpandMutableTreeNode tableChild = (ExpandMutableTreeNode) treeNode.getChildAt(i); + loadAndAddChildTreeChild(tableChild); + childs[i] = tableChild; + removePending(tableChild); + } + treeNode.addChildTreeNodes(childs); + } else { + ExpandMutableTreeNode[] expandMutableTreeNodes = loadChildTreeNodes(treeNode); + treeNode.addChildTreeNodes(expandMutableTreeNodes); + } + removePending(treeNode); + return treeNode; + } + /* * p:获得选中的NameObject = name + tabledata. */ @@ -182,6 +247,39 @@ public class TableDataTree extends UserObjectRefreshJTree { } + /** + * 获得选中的NameObject的list,只会返回数据集节点的NameObject + * 当多选了数据集或数据列时,也只返回选中的数据集 + */ + public NameObject[] getSelectedNameObjects() { + TreePath[] selectedTreePaths = this.getSelectionPaths(); + if (selectedTreePaths == null) { + return new NameObject[0]; + } + List result = new ArrayList<>(); + for (TreePath selectedTreePath : selectedTreePaths) { + if (selectedTreePath == null) { + continue; + } + ExpandMutableTreeNode selectedTreeNode = (ExpandMutableTreeNode) selectedTreePath.getLastPathComponent(); + Object selectedUserObject = selectedTreeNode.getUserObject(); + // 只考虑数据集节点 + if (selectedUserObject instanceof NameObject && ((NameObject) selectedUserObject).getObject() instanceof TableDataWrapper) { + result.add((NameObject) selectedUserObject); + } + } + return result.toArray(new NameObject[0]); + } + + /** + * 获取选中的数据集数量,选中数据列则不计入 + * + * @return + */ + public int getSelectedDsCounts() { + return getSelectedNameObjects().length; + } + public TableDataWrapper[] getSelectedDatas() { TreePath[] selectedTreePaths = this.getSelectionPaths(); if (selectedTreePaths == null || selectedTreePaths.length == 0) { diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/TableDataTreePane.java b/designer-base/src/main/java/com/fr/design/data/datapane/TableDataTreePane.java index 5a6366602..45897f7ac 100644 --- a/designer-base/src/main/java/com/fr/design/data/datapane/TableDataTreePane.java +++ b/designer-base/src/main/java/com/fr/design/data/datapane/TableDataTreePane.java @@ -13,6 +13,12 @@ import com.fr.design.data.BasicTableDataTreePane; import com.fr.design.data.BasicTableDataUtils; import com.fr.design.data.DesignTableDataManager; import com.fr.design.data.StrategyConfigAttrUtils; +import com.fr.design.data.datapane.management.clip.TableDataTreeClipboard; +import com.fr.design.data.datapane.management.search.pane.TableDataSearchRemindPane; +import com.fr.design.data.datapane.management.search.pane.TreeSearchToolbarPane; +import com.fr.design.data.datapane.management.search.searcher.TableDataSearchMode; +import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; +import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; import com.fr.design.data.tabledata.StoreProcedureWorkerListener; import com.fr.design.data.tabledata.tabledatapane.AbstractTableDataPane; import com.fr.design.data.tabledata.tabledatapane.DBTableDataPane; @@ -20,12 +26,14 @@ import com.fr.design.data.tabledata.wrapper.AbstractTableDataWrapper; import com.fr.design.data.tabledata.wrapper.TableDataWrapper; import com.fr.design.data.tabledata.wrapper.TemplateTableDataWrapper; import com.fr.design.dialog.BasicDialog; +import com.fr.design.dialog.BasicPane; import com.fr.design.dialog.DialogActionAdapter; -import com.fr.design.dialog.FineJOptionPane; import com.fr.design.file.HistoryTemplateListCache; import com.fr.design.fun.TableDataPaneProcessor; import com.fr.design.gui.ibutton.UIHeadGroup; import com.fr.design.gui.icontainer.UIScrollPane; +import com.fr.design.gui.ilist.CheckBoxList; +import com.fr.design.gui.imenu.UIPopupMenu; import com.fr.design.gui.itextfield.UITextField; import com.fr.design.gui.itoolbar.UIToolbar; import com.fr.design.gui.itree.refreshabletree.ExpandMutableTreeNode; @@ -37,6 +45,7 @@ import com.fr.design.menu.LineSeparator; import com.fr.design.menu.MenuDef; import com.fr.design.menu.SeparatorDef; import com.fr.design.menu.ToolBarDef; +import com.fr.design.utils.gui.GUICoreUtils; import com.fr.esd.core.strategy.config.StrategyConfig; import com.fr.esd.core.strategy.config.StrategyConfigHelper; import com.fr.esd.event.DSMapping; @@ -61,7 +70,6 @@ import org.jetbrains.annotations.NotNull; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JLabel; -import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; @@ -69,9 +77,12 @@ import javax.swing.ToolTipManager; import javax.swing.tree.TreePath; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Dimension; import java.awt.GridLayout; import java.awt.dnd.DnDConstants; import java.awt.event.ActionEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; @@ -107,13 +118,20 @@ public class TableDataTreePane extends BasicTableDataTreePane { private TableDataSourceOP op; private TableDataTree tableDataTree; + private UIPopupMenu popupMenu; private EditAction editAction; private RemoveAction removeAction; + private CopyAction copyAction; + private PasteAction pasteAction; private EsdOnAction esdAction; private EsdOffAction esdOffAction; + private SwitchAction switchAction; private PreviewTableDataAction previewTableDataAction; private JPanel serverDatasetAuthTipJPanel = new JPanel(); + private TableDataSearchRemindPane remindPane; + private TreeSearchToolbarPane toolbarPane; + private TableDataTreePane() { initPane(); } @@ -122,13 +140,76 @@ public class TableDataTreePane extends BasicTableDataTreePane { this.setLayout(new BorderLayout(4, 0)); this.setBorder(null); - //TableDataTree - tableDataTree = new TableDataTree(); + initTableDataTree(); + toolbarPane = initToolBarPane(); + JPanel treePane = initTreePane(); + dealWithTableDataTree(); + this.add(toolbarPane, BorderLayout.NORTH); + this.add(treePane, BorderLayout.CENTER); + checkButtonEnabled(); + } + + /** + * 处理TableDataTree的监听等 + */ + private void dealWithTableDataTree() { + // tooltip ToolTipManager.sharedInstance().registerComponent(tableDataTree); ToolTipManager.sharedInstance().setDismissDelay(3000); ToolTipManager.sharedInstance().setInitialDelay(0); + // 右键菜单 + popupMenu = new UIPopupMenu(); + popupMenu.add(editAction.createMenuItem()); + popupMenu.add(previewTableDataAction.createMenuItem()); + popupMenu.addSeparator(); + popupMenu.add(copyAction.createMenuItem()); + popupMenu.add(pasteAction.createMenuItem()); + popupMenu.add(removeAction.createMenuItem()); + popupMenu.addSeparator(); + // 监听 + tableDataTree.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + // 服务器暂不支持右键菜单 + if (SwingUtilities.isRightMouseButton(e) && op.getDataMode() != TableDataSourceOP.SERVER_TABLE_DATA) { + GUICoreUtils.showPopupMenu(popupMenu, e.getComponent(), e.getX(), e.getY()); + } + checkButtonEnabled(); + } + }); + tableDataTree.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + //F2重命名先屏蔽了, 有bug没时间弄 + if (e.getKeyCode() == KeyEvent.VK_F2) { + return; + } + super.keyPressed(e); + checkButtonEnabled(); + } + + @Override + public void keyReleased(KeyEvent e) { + super.keyReleased(e); + checkButtonEnabled(); + } + }); + // TreeCellEditor + tableDataTree.setEditable(true); + TableDataTreeCellEditor treeCellEditor = new TableDataTreeCellEditor(new UITextField(), tableDataTree, this); + treeCellEditor.addCellEditorListener(treeCellEditor); + tableDataTree.setCellEditor(treeCellEditor); + new TableDataTreeDragSource(tableDataTree, DnDConstants.ACTION_COPY); + } + /** + * 工具栏面板 + * + * @return + */ + private TreeSearchToolbarPane initToolBarPane() { + // toolbar addMenuDef = new MenuDef(Toolkit.i18nText("Fine-Design_Basic_Action_Add")); addMenuDef.setIconPath(IconPathConstants.ADD_POPMENU_ICON_PATH); createAddMenuDef(); @@ -136,49 +217,54 @@ public class TableDataTreePane extends BasicTableDataTreePane { createPluginListener(); editAction = new EditAction(); + copyAction = new CopyAction(); + pasteAction = new PasteAction(); removeAction = new RemoveAction(); previewTableDataAction = new PreviewTableDataAction(tableDataTree); connectionTableAction = new ConnectionTableAction(); esdAction = new EsdOnAction(); esdOffAction = new EsdOffAction(); - + switchAction = new SwitchAction(); toolbarDef = new ToolBarDef(); - toolbarDef.addShortCut(addMenuDef, SeparatorDef.DEFAULT, editAction, removeAction, SeparatorDef.DEFAULT, previewTableDataAction, connectionTableAction, esdAction, esdOffAction); + toolbarDef.addShortCut(addMenuDef, SeparatorDef.DEFAULT, editAction, removeAction, SeparatorDef.DEFAULT, previewTableDataAction, connectionTableAction, esdAction, esdOffAction, switchAction); UIToolbar toolBar = ToolBarDef.createJToolBar(); toolBar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, UIConstants.TOOLBAR_BORDER_COLOR)); toolBar.setBorderPainted(true); toolbarDef.updateToolBar(toolBar); - JPanel toolbarPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); - toolbarPane.add(toolBar, BorderLayout.CENTER); - this.add(toolbarPane, BorderLayout.NORTH); + TreeSearchToolbarPane searchLayerdPane = new TreeSearchToolbarPane(toolBar); + searchLayerdPane.setPreferredSize(new Dimension(this.getWidth(), 23)); - UIScrollPane scrollPane = new UIScrollPane(tableDataTree); - scrollPane.setBorder(null); + return searchLayerdPane; + } + + /** + * 数据集树面板 + * + * @return + */ + private JPanel initTreePane() { + JPanel treePane = new JPanel(new BorderLayout(0, 6)); + // north + JPanel northPane = new JPanel(FRGUIPaneFactory.createBorderLayout()); initServerDatasetAuthTipJPanel(); initButtonGroup(); - JPanel jPanel = new JPanel(new BorderLayout(0, 0)); - JPanel buttonPane = new JPanel(FRGUIPaneFactory.createBorderLayout()); - buttonPane.add(buttonGroup, BorderLayout.CENTER); - buttonPane.add(serverDatasetAuthTipJPanel, BorderLayout.SOUTH); - jPanel.add(buttonPane, BorderLayout.NORTH); - jPanel.add(scrollPane, BorderLayout.CENTER); - this.add(jPanel, BorderLayout.CENTER); - tableDataTree.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - checkButtonEnabled(); - } - }); - tableDataTree.addKeyListener(getTableTreeNodeListener(editAction, previewTableDataAction, removeAction, op, tableDataTree)); - // TreeCellEditor - tableDataTree.setEditable(true); - TableDataTreeCellEditor treeCellEditor = new TableDataTreeCellEditor(new UITextField(), tableDataTree, this); - treeCellEditor.addCellEditorListener(treeCellEditor); - tableDataTree.setCellEditor(treeCellEditor); - new TableDataTreeDragSource(tableDataTree, DnDConstants.ACTION_COPY); - checkButtonEnabled(); + northPane.add(buttonGroup, BorderLayout.CENTER); + northPane.add(serverDatasetAuthTipJPanel, BorderLayout.SOUTH); + // center + remindPane = new TableDataSearchRemindPane(getDataTree()); + + treePane.add(northPane, BorderLayout.NORTH); + treePane.add(remindPane, BorderLayout.CENTER); + return treePane; + } + + /** + * 初始化 TableDataTree + */ + private void initTableDataTree() { + tableDataTree = new TableDataTree(); } private void initServerDatasetAuthTipJPanel() { @@ -471,6 +557,17 @@ public class TableDataTreePane extends BasicTableDataTreePane { ((TableDataSourceDependent) td).setTableDataSource(tds); } String tdName = nPanel.getObjectName(); + + //处理缓存策略配置 + if (uPanel instanceof DBTableDataPane) { + StrategyConfig editingConfig = ((DBTableDataPane) uPanel).updateStrategyConfig(); + if (editingConfig != null) { + editingConfig.setDsName(tdName); + StrategyConfigAttrUtils.addStrategyConfig(editingConfig); + } + ((DBTableData) td).setDsName(tdName); + } + tds.putTableData(tdName, td); Map map = new HashMap(); if (!ComparatorUtils.equals(paneName, tdName)) { @@ -491,6 +588,9 @@ public class TableDataTreePane extends BasicTableDataTreePane { private void populate(TableDataSourceOP op) { this.op = op; + if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() == TreeSearchStatus.SEARCHING) { + TableDataTreeSearchManager.getInstance().switchBackToolBar(); + } tableDataTree.populate(op); checkButtonEnabled(); } @@ -501,11 +601,61 @@ public class TableDataTreePane extends BasicTableDataTreePane { this.createAddMenuDef(); } + /** + * 感觉这里,把一堆Action和Op之类的送到抽象类里去检查,很奇怪,抽象类本身定义的Action只有add和connection + * 另外因为改动了数据集树节点的选中逻辑,所以这边改成自己类内部实现 + * 不直接改抽象类是怕影响到部分插件兼容 + */ private void checkButtonEnabled() { - super.checkButtonEnabled(editAction, previewTableDataAction, removeAction, op, tableDataTree); + // 检查添加与定义数据连接操作 + this.checkAddAndConnectionEnabled(); + // 检查编辑、预览、复制、粘贴、删除等基本操作 + this.checkBasicButtonEnabled(); + // 检查esd相关操作 this.checkESDComponentsEnabled(); } + private void checkAddAndConnectionEnabled() { + connectionTableAction.setEnabled(WorkContext.getCurrent() != null && WorkContext.getCurrent().isRoot()); + addMenuDef.setEnabled(!(op == null || op.interceptButtonEnabled() || op.getDataMode() == SERVER_TABLE_DATA)); + } + + private void checkBasicButtonEnabled() { + // 设置下各个button的基本状态,避免代码重复 + editAction.setEnabled(false); + copyAction.setEnabled(false); + pasteAction.setEnabled(false); + removeAction.setEnabled(false); + previewTableDataAction.setEnabled(false); + if (op == null || op.interceptButtonEnabled()) { + // 保持false状态 + return; + } + // 获取选中的数据集数量 + int selectioncount = getDataTree().getSelectedDsCounts(); + if (op.getDataMode() == SERVER_TABLE_DATA) { + // 服务器数据集下,选中数据集数量为1时,可以预览 + if (selectioncount == 1) { + previewTableDataAction.setEnabled(true); + } + // 其它保持false状态 + return; + } + // 模板数据集时,粘贴可用 + pasteAction.setEnabled(true); + if (selectioncount == 0) { + // 其它保持false状态 + return; + } + if (selectioncount == 1) { + // 选中单个数据集时,才可以编译、预览 + editAction.setEnabled(true); + previewTableDataAction.setEnabled(true); + } + removeAction.setEnabled(true); + copyAction.setEnabled(true); + } + private void checkESDComponentsEnabled() { if (buttonGroup.getSelectedIndex() == 1) { @@ -863,37 +1013,120 @@ public class TableDataTreePane extends BasicTableDataTreePane { @Override public void actionPerformed(ActionEvent e) { - NameObject selectedNO = tableDataTree.getSelectedNameObject(); - - if (selectedNO == null) { + NameObject[] selectedNameObjects = tableDataTree.getSelectedNameObjects(); + if (selectedNameObjects == null || selectedNameObjects.length == 0) { + FineLoggerFactory.getLogger().error("Table Data to remove is null or not selected"); return; } + CheckBoxList checkBoxList = new CheckBoxList(selectedNameObjects, CheckBoxList.SelectedState.ALL, Toolkit.i18nText("Fine-Design_Basic_Remove_All_Selected")); + UIScrollPane scrollPane = new UIScrollPane(checkBoxList); + BasicPane basicPane = new BasicPane() { + @Override + protected String title4PopupWindow() { + return Toolkit.i18nText("Fine-Design_Basic_Remove"); + } + }; + basicPane.setLayout(new BorderLayout()); + basicPane.add(scrollPane, BorderLayout.CENTER); + BasicDialog basicDialog = basicPane.showSmallWindow(SwingUtilities.getWindowAncestor(TableDataTreePane.this), new DialogActionAdapter() { + @Override + public void doOk() { + Object[] selectedValues = checkBoxList.getSelectedValues(); + for (Object toRemove : selectedValues) { + doRemove((NameObject) toRemove); + } + } - int returnVal = FineJOptionPane.showConfirmDialog(DesignerContext.getDesignerFrame(), Toolkit.i18nText("Fine-Design_Basic_Utils_Are_You_Sure_To_Remove_The_Selected_Item") + ":" + selectedNO.getName() + "?", - Toolkit.i18nText("Fine-Design_Basic_Remove"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); - if (returnVal == JOptionPane.OK_OPTION) { - // richer:这个地方为什么要在DataSourceTree里面去remove呢?多此一举吧 - op.removeAction(selectedNO.getName()); - tableDataTree.refresh(); - // Richie:默认最后一行获得焦点. - tableDataTree.requestFocus(); - tableDataTree.setSelectionRow(tableDataTree.getRowCount() - 1); - fireDSChanged(); - checkButtonEnabled(); + @Override + public void doCancel() { + super.doCancel(); + } + }); + basicDialog.setVisible(true); + } + + private void doRemove(NameObject selectedNO) { + // richer:这个地方为什么要在DataSourceTree里面去remove呢?多此一举吧 + op.removeAction(selectedNO.getName()); + tableDataTree.refresh(); + // Richie:默认最后一行获得焦点. + tableDataTree.requestFocus(); + tableDataTree.setSelectionRow(tableDataTree.getRowCount() - 1); + fireDSChanged(); + checkButtonEnabled(); + + //删掉缓存配置 + StrategyConfigAttrUtils.removeStrategyConfig(selectedNO.getName()); + + // 如果一个模版是平台开启,这个数据集的配置不会存xml,预览模版时直接从全局配置copy,这样 + // 导致删除的时候StrategyConfigsAttrSavedHook没有通过前后配置比较感知数据集被删除,因此不会发出事件让其失效 + // 这里额外发出一次数据集修改事件 + StrategyEventsNotifier.modifyDataSet(new DSMapping(getTplPath(), new DsNameTarget(selectedNO.getName()))); + DesignTableDataManager.removeSelectedColumnNames(selectedNO.getName()); + DesignModelAdapter.getCurrentModelAdapter().removeTableDataParameters(selectedNO.getName()); + } + } - //删掉缓存配置 - StrategyConfigAttrUtils.removeStrategyConfig(selectedNO.getName()); + private class CopyAction extends UpdateAction { + + public CopyAction() { + this.setName(Toolkit.i18nText("Fine-Design_Basic_Copy")); + this.setMnemonic('C'); + this.setSmallIcon("/com/fr/design/images/m_edit/copy"); + } - // 如果一个模版是平台开启,这个数据集的配置不会存xml,预览模版时直接从全局配置copy,这样 - // 导致删除的时候StrategyConfigsAttrSavedHook没有通过前后配置比较感知数据集被删除,因此不会发出事件让其失效 - // 这里额外发出一次数据集修改事件 - StrategyEventsNotifier.modifyDataSet(new DSMapping(getTplPath(), new DsNameTarget(selectedNO.getName()))); - DesignTableDataManager.removeSelectedColumnNames(selectedNO.getName()); - DesignModelAdapter.getCurrentModelAdapter().removeTableDataParameters(selectedNO.getName()); + @Override + public void actionPerformed(ActionEvent e) { + NameObject[] selectedNameObjects = tableDataTree.getSelectedNameObjects(); + Map dataWrapperMap = TableDataTreeClipboard.getInstance().transferNameObjectArray2Map(selectedNameObjects); + TableDataTreeClipboard.getInstance().addToClip(dataWrapperMap); + } + } + + private class PasteAction extends UpdateAction { + + public PasteAction() { + this.setName(Toolkit.i18nText("Fine-Design_Basic_Action_Paste_Name")); + this.setMnemonic('P'); + this.setSmallIcon("/com/fr/design/images/m_edit/paste"); + } + + @Override + public void actionPerformed(ActionEvent e) { + Map dataWrapperMap = TableDataTreeClipboard.getInstance().takeFromClip(); + for (Map.Entry dataWrapperEntry : dataWrapperMap.entrySet()) { + // 处理数据集名称 + String dsName = getNoRepeatedDsName4Paste(dataWrapperEntry.getKey()); + AbstractTableDataWrapper wrapper = dataWrapperEntry.getValue(); + AbstractTableDataPane tableDataPane = wrapper.creatTableDataPane(); + addDataPane(tableDataPane, dsName); } } } + public String getNoRepeatedDsName4Paste(String oldName) { + while (isDsNameRepeaded(oldName)) { + oldName = oldName + Toolkit.i18nText("Fine-Design_Table_Data_Copy_Of_Table_Data"); + } + return oldName; + } + + private class SwitchAction extends UpdateAction { + + public SwitchAction() { + this.setName(Toolkit.i18nText("Fine-Design_Basic_Search")); + this.setMnemonic('S'); + this.setSmallIcon("/com/fr/design/images/data/search"); + } + + @Override + public void actionPerformed(ActionEvent e) { + // 交换层级 + toolbarPane.switchPane(TreeSearchToolbarPane.SEARCH_PANE); + TableDataTreeSearchManager.getInstance().switchToSearch(TableDataSearchMode.match(buttonGroup.getSelectedIndex()), DesignTableDataManager.getEditingTableDataSource()); + } + } + @Override public void checkEnable() { this.checkButtonEnabled(); diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/clip/TableDataTreeClipboard.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/clip/TableDataTreeClipboard.java new file mode 100644 index 000000000..44ae90b2c --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/clip/TableDataTreeClipboard.java @@ -0,0 +1,68 @@ +package com.fr.design.data.datapane.management.clip; + +import com.fr.design.data.tabledata.wrapper.AbstractTableDataWrapper; +import com.fr.general.NameObject; + +import java.util.HashMap; +import java.util.Map; + +/** + * 用于数据集的复制粘贴 + * + * @author Yvan + */ +public class TableDataTreeClipboard { + + /** + * 数据集名称 - 数据集Wrapper + */ + private Map 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 copyMap) { + this.clip = copyMap; + } + + public Map transferNameObjectArray2Map(NameObject[] selectedNameObjects) { + Map resultMap = new HashMap<>(); + if (selectedNameObjects == null) { + return resultMap; + } + for (NameObject selectedNameObject : selectedNameObjects) { + resultMap.put(selectedNameObject.getName(), (AbstractTableDataWrapper) selectedNameObject.getObject()); + } + return resultMap; + } + + /** + * 取出剪切板内的所有数据集数据,剪切板不清空 + * + * @return + */ + public Map takeFromClip() { + return clip; + } + + /** + * 清空剪切板 + */ + public void reset() { + clip.clear(); + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/TableDataTreeSearchManager.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/TableDataTreeSearchManager.java new file mode 100644 index 000000000..afc28e59f --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/TableDataTreeSearchManager.java @@ -0,0 +1,267 @@ +package com.fr.design.data.datapane.management.search; + +import com.fr.data.TableDataSource; +import com.fr.data.impl.storeproc.StoreProcedure; +import com.fr.design.DesignModelAdapter; +import com.fr.design.data.datapane.TableDataTree; +import com.fr.design.data.datapane.TableDataTreePane; +import com.fr.design.data.datapane.management.search.event.TreeSearchStatusChangeEvent; +import com.fr.design.data.datapane.management.search.event.TreeSearchStatusChangeListener; +import com.fr.design.data.datapane.management.search.searcher.TableDataSearchMode; +import com.fr.design.data.datapane.management.search.searcher.TableDataTreeSearcher; +import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; +import com.fr.design.data.datapane.management.search.time.TableDataSearchTimer; +import com.fr.design.data.datapane.management.search.view.TreeSearchRendererHelper; +import com.fr.design.data.tabledata.wrapper.TableDataWrapper; +import com.fr.design.gui.itree.refreshabletree.ExpandMutableTreeNode; +import com.fr.general.NameObject; +import com.fr.stable.StringUtils; + +import javax.swing.SwingUtilities; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 数据集树搜索管理器 + * + * @author Yvan + */ +public class TableDataTreeSearchManager { + + /** + * 数据集树搜索器 + */ + private TableDataTreeSearcher treeSearcher; + + /** + * 搜索任务的状态 + */ + private TreeSearchStatus treeSearchStatus; + + /** + * 缓存上次搜索文本,避免重复搜索 + */ + private String lastSearchText; + + /** + * 存储与复原 原本数据集树的UI + */ + private TreeSearchRendererHelper rendererHelper; + + /** + * 取数计数器 + */ + private AtomicInteger count; + + /** + * 搜索状态变化监听 + */ + private List listeners = new ArrayList<>(); + + private TableDataTreeSearchManager() { + init(); + } + + private void init() { + this.treeSearchStatus = TreeSearchStatus.SEARCH_NOT_BEGIN; + } + + private static class Holder { + private static final TableDataTreeSearchManager INSTANCE = new TableDataTreeSearchManager(); + } + + public static TableDataTreeSearchManager getInstance() { + return Holder.INSTANCE; + } + + public TreeSearchStatus getTreeSearchStatus() { + return treeSearchStatus; + } + + public void setTreeSearchStatus(TreeSearchStatus treeSearchStatus) { + this.treeSearchStatus = treeSearchStatus; + // 每次设置搜索状态,都触发下监听,让页面跟随变化 + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + for (TreeSearchStatusChangeListener listener : listeners) { + listener.updateTreeSearchChange(new TreeSearchStatusChangeEvent(treeSearchStatus)); + } + } + }); + } + + public void registerTreeSearchStatusChangeListener(TreeSearchStatusChangeListener listener) { + listeners.add(listener); + } + + /** + * 工具栏处切换到搜索面板 + * + * @param searchMode + * @param tableDataSource + */ + public void switchToSearch(TableDataSearchMode searchMode, TableDataSource tableDataSource) { + setTreeSearchStatus(TreeSearchStatus.SEARCH_NOT_BEGIN); + rendererHelper = new TreeSearchRendererHelper(); + rendererHelper.save(getCurrentTableDataTree()); + treeSearcher = new TableDataTreeSearcher(); + treeSearcher.beforeSearch(searchMode, tableDataSource); + } + + /** + * 获取当前的tableDataTree + * + * @return + */ + private TableDataTree getCurrentTableDataTree() { + DesignModelAdapter currentModelAdapter = DesignModelAdapter.getCurrentModelAdapter(); + TableDataTreePane tableDataTreePane = (TableDataTreePane) TableDataTreePane.getInstance(currentModelAdapter); + return tableDataTreePane.getDataTree(); + } + + public boolean isMatchSetsEmpty() { + return treeSearcher.isMatchSetsEmpty(); + } + + /** + * 开始搜索 + * + * @param searchText + */ + public void startSearch(String searchText) { + if (isRepeatSearch(searchText)) { + return; + } + setTreeSearchStatus(TreeSearchStatus.SEARCHING); + rendererHelper.replaceTreeRenderer(getCurrentTableDataTree(), searchText); + count = new AtomicInteger(treeSearcher.getNotCalculatedSetsSize()); + // 计时开始 + TableDataSearchTimer.getInstance().startClock(); + treeSearcher.startSearch(searchText); + } + + /** + * 计数-1 + */ + public void decreaseCount() { + if (count == null) { + return; + } + int cunrrentCount = count.decrementAndGet(); + // 减到0后判断状态 + if (cunrrentCount == 0 && getTreeSearchStatus() == TreeSearchStatus.SEARCHING) { + completeSearch(); + } + } + + private boolean isRepeatSearch(String searchText) { + if (StringUtils.isEmpty(lastSearchText)) { + lastSearchText = searchText; + return false; + } + return StringUtils.equals(lastSearchText, searchText); + } + + /** + * 刷新树,更新搜索的结果 + */ + public void updateTableDataTree() { + getCurrentTableDataTree().refresh4TreeSearch(); + } + + /** + * 中断搜索 + */ + public void stopSearch() { + setTreeSearchStatus(TreeSearchStatus.SEARCH_STOPPED); + TableDataSearchTimer.getInstance().stopClock(); + count = null; + treeSearcher.stopSearch(); + } + + /** + * 搜索完成 + */ + public void completeSearch() { + setTreeSearchStatus(TreeSearchStatus.SEARCH_COMPLETED); + TableDataSearchTimer.getInstance().stopClock(); + count = null; + treeSearcher.completeSearch(); + } + + /** + * 切换回工具栏 + */ + public void switchBackToolBar() { + setTreeSearchStatus(TreeSearchStatus.SEARCH_NOT_BEGIN); + lastSearchText = null; + treeSearcher.afterSearch(); + rendererHelper.restore(getCurrentTableDataTree()); + } + + /** + * 节点是否应该添加到搜索结果树的根节点中 + * 只针对数据集节点 + * + * @param treeNode 数据集节点 + * @return + */ + public boolean nodeMatches(ExpandMutableTreeNode treeNode) { + String nodeName = treeNode.getUserObject().toString(); + return treeSearcher.nodeMatches(nodeName); + } + + /** + * 节点是否应该展开 + * + * @param treeNode + * @return + */ + public boolean nodeCanExpand(ExpandMutableTreeNode treeNode) { + String dsName = treeNode.getUserObject().toString(); + return treeSearcher.nodeCanExpand(dsName); + } + + /** + * 处理节点的展开,如果此节点是存储过程,还会处理其子表节点的展开 + * 只针对数据集节点 + * + * @param treeNode + * @return + */ + public ExpandMutableTreeNode dealWithNodeExpand(ExpandMutableTreeNode treeNode) { + String tableDataName = treeNode.getUserObject().toString(); + // 主要还是处理存储过程 + if (isTreeNodeStoreProcedure(treeNode)) { + int childCount = treeNode.getChildCount(); + for (int i = 0; i < childCount; i++) { + ExpandMutableTreeNode child = (ExpandMutableTreeNode) treeNode.getChildAt(i); + if (treeSearcher.nodeCanExpand(tableDataName + "_" + child.getUserObject().toString())) { + child.setExpanded(true); + } + } + } + if (nodeCanExpand(treeNode)) { + treeNode.setExpanded(true); + } + return treeNode; + } + + /** + * 判断此节点是否为存储过程 + * + * @param treeNode + * @return + */ + public boolean isTreeNodeStoreProcedure(ExpandMutableTreeNode treeNode) { + Object userObject = treeNode.getUserObject(); + if (userObject instanceof NameObject) { + NameObject nameObject = (NameObject) userObject; + TableDataWrapper tableDataWrapper = (TableDataWrapper) nameObject.getObject(); + return tableDataWrapper.getTableData() instanceof StoreProcedure; + } + return false; + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/TreeSearchCallback.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/TreeSearchCallback.java new file mode 100644 index 000000000..3122bd207 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/TreeSearchCallback.java @@ -0,0 +1,13 @@ +package com.fr.design.data.datapane.management.search.control; + +import com.fr.design.data.datapane.management.search.searcher.TreeSearcher; + +/** + * 搜索任务回调 + * + * @author Yvan + */ +public interface TreeSearchCallback { + + void done(TreeSearchResult treeSearchResult); +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/TreeSearchResult.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/TreeSearchResult.java new file mode 100644 index 000000000..320eca947 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/TreeSearchResult.java @@ -0,0 +1,37 @@ +package com.fr.design.data.datapane.management.search.control; + +import java.util.List; + +/** + * @author Yvan + */ +public interface TreeSearchResult { + + /** + * 任务结果是否成功 + * + * @return + */ + boolean isSuccess(); + + /** + * 数据集名称匹配或者列名匹配时,需要将数据集名称添加到匹配结果集中 + * + * @return + */ + List getAddToMatch(); + + /** + * 数据集有列名匹配时,需要添加到展开结果集中 + * + * @return + */ + List getAddToExpand(); + + /** + * 数据集完成计算后,需要添加到完成结果集中 + * + * @return + */ + List getAddToCalculated(); +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/TreeSearchTask.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/TreeSearchTask.java new file mode 100644 index 000000000..695c7d3c2 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/TreeSearchTask.java @@ -0,0 +1,10 @@ +package com.fr.design.data.datapane.management.search.control; + +/** + * @author Yvan + */ +public interface TreeSearchTask extends Runnable { + + @Override + void run(); +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/common/TableDataSearchCallBack.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/common/TableDataSearchCallBack.java new file mode 100644 index 000000000..c544921c5 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/common/TableDataSearchCallBack.java @@ -0,0 +1,54 @@ +package com.fr.design.data.datapane.management.search.control.common; + +import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; +import com.fr.design.data.datapane.management.search.control.TreeSearchCallback; +import com.fr.design.data.datapane.management.search.control.TreeSearchResult; +import com.fr.design.data.datapane.management.search.searcher.TableDataTreeSearcher; +import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; + +import javax.swing.SwingUtilities; + +/** + * @author Yvan + */ +public class TableDataSearchCallBack implements TreeSearchCallback { + + protected TableDataTreeSearcher treeSearcher; + + public TableDataSearchCallBack(TableDataTreeSearcher treeSearcher) { + this.treeSearcher = treeSearcher; + } + + @Override + public void done(TreeSearchResult treeSearchResult) { + if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() != TreeSearchStatus.SEARCHING) { + return; + } + if (treeSearchResult.isSuccess()) { + // 添加结果 + addToTreeSearcher(treeSearchResult); + // 处理UI + updateTableDataTree(); + } + } + + protected void updateTableDataTree() { + SwingUtilities.invokeLater(() -> { + if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() != TreeSearchStatus.SEARCHING) { + return; + } + TableDataTreeSearchManager.getInstance().updateTableDataTree(); + // todo 没想清楚为啥会这么快结束,暂时取消搜索计数 +// TableDataTreeSearchManager.getInstance().decreaseCount(); + }); + } + + protected void addToTreeSearcher(TreeSearchResult treeSearchResult) { + // 添加到已计算结果集 + treeSearcher.addToCalculatedSets(treeSearchResult.getAddToCalculated()); + // 添加到匹配结果集 + treeSearcher.addToMatchSets(treeSearchResult.getAddToMatch()); + // 添加到展开结果集 + treeSearcher.addToCanExpandSets(treeSearchResult.getAddToExpand()); + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/common/TableDataSearchResult.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/common/TableDataSearchResult.java new file mode 100644 index 000000000..387933df5 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/common/TableDataSearchResult.java @@ -0,0 +1,105 @@ +package com.fr.design.data.datapane.management.search.control.common; + +import com.fr.design.data.datapane.management.search.control.TreeSearchResult; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Yvan + */ +public class TableDataSearchResult implements TreeSearchResult { + + private boolean success; + + private List addToMatch; + + private List addToExpand; + + private List addToCalculated; + + protected TableDataSearchResult(Builder builder) { + this.success = builder.success; + this.addToMatch = builder.addToMatch; + this.addToExpand = builder.addToExpand; + this.addToCalculated = builder.addToCalculated; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public void setAddToMatch(List addToMatch) { + this.addToMatch = addToMatch; + } + + public void setAddToExpand(List addToExpand) { + this.addToExpand = addToExpand; + } + + public void setAddToCalculated(List addToCalculated) { + this.addToCalculated = addToCalculated; + } + + @Override + public boolean isSuccess() { + return this.success; + } + + @Override + public List getAddToMatch() { + return this.addToMatch; + } + + @Override + public List getAddToExpand() { + return this.addToExpand; + } + + @Override + public List getAddToCalculated() { + return this.addToCalculated; + } + + public static class Builder { + + private boolean success; + + private List addToMatch; + + private List addToExpand; + + private List addToCalculated; + + public Builder() { + this.success = false; + this.addToMatch = new ArrayList<>(); + this.addToExpand = new ArrayList<>(); + this.addToCalculated = new ArrayList<>(); + } + + public Builder buildSuccess(boolean success) { + this.success = success; + return this; + } + + public Builder buildAddToMatch(List addToMatch) { + this.addToMatch = addToMatch; + return this; + } + + public Builder buildAddToExpand(List addToExpand) { + this.addToExpand = addToExpand; + return this; + } + + public Builder buildAddToCalculated(List addToCalculated) { + this.addToCalculated = addToCalculated; + return this; + } + + public TableDataSearchResult build() { + return new TableDataSearchResult(this); + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/common/TableDataSearchTask.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/common/TableDataSearchTask.java new file mode 100644 index 000000000..fcfec0b8d --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/common/TableDataSearchTask.java @@ -0,0 +1,147 @@ +package com.fr.design.data.datapane.management.search.control.common; + +import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; +import com.fr.design.data.datapane.management.search.control.TreeSearchCallback; +import com.fr.design.data.datapane.management.search.control.TreeSearchResult; +import com.fr.design.data.datapane.management.search.control.TreeSearchTask; +import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; +import com.fr.design.data.tabledata.wrapper.StoreProcedureDataWrapper; +import com.fr.design.data.tabledata.wrapper.TableDataWrapper; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Yvan + */ +public class TableDataSearchTask implements TreeSearchTask { + + /** + * 用户搜索的文本 + */ + private String searchText; + + private TableDataWrapper tableDataWrapper; + + private TreeSearchCallback callback; + + public TableDataSearchTask(String searchText, TableDataWrapper tableDataWrapper, TreeSearchCallback callback) { + this.searchText = searchText; + this.tableDataWrapper = tableDataWrapper; + this.callback = callback; + } + + @Override + public void run() { + TreeSearchResult result; + try { + if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() != TreeSearchStatus.SEARCHING) { + return; + } + if (isTableDataStoreProcedure(tableDataWrapper)) { + dealWithStoreProcedureTableDataWrapper((StoreProcedureDataWrapper) tableDataWrapper); + } else { + dealWithCommonTableDataWrapper(tableDataWrapper); + } + } catch (Throwable e) { + FineLoggerFactory.getLogger().error(e, e.getMessage()); + dealWithErrorTableDataWrapper(tableDataWrapper); + } + } + + /** + * 处理错误情况 + * + * @param tableDataWrapper + */ + private void dealWithErrorTableDataWrapper(TableDataWrapper tableDataWrapper) { + callback.done(new TableDataSearchResult.Builder().buildSuccess(false).build()); + } + + /** + * 处理普通数据集的搜索与匹配 + * + * @param tableDataWrapper + */ + private void dealWithCommonTableDataWrapper(TableDataWrapper tableDataWrapper) { + String tableDataName = tableDataWrapper.getTableDataName(); + boolean isTableDataNameMatch = isMatchSearch(tableDataName, searchText); + List columnNameList = tableDataWrapper.calculateColumnNameList(); + boolean isColumnMatch = columnNameList.stream().anyMatch(columnName -> isMatchSearch(columnName, searchText)); + TableDataSearchResult result = new TableDataSearchResult.Builder() + .buildSuccess(true) + .buildAddToMatch(isTableDataNameMatch || isColumnMatch ? Arrays.asList(tableDataName) : new ArrayList<>()) + .buildAddToExpand(isColumnMatch ? Arrays.asList(tableDataName) : new ArrayList<>()) + .buildAddToCalculated(Arrays.asList(tableDataName)) + .build(); + callback.done(result); + } + + /** + * 处理存储过程的搜索与匹配 + * + * @param procedureDataWrapper + */ + private void dealWithStoreProcedureTableDataWrapper(StoreProcedureDataWrapper procedureDataWrapper) { + // 存储过程数据集名称,例如 Proc1_Table1 + String tableDataName = procedureDataWrapper.getTableDataName(); + // 存储过程名称,例如 Proc1 + String storeProcedureName = procedureDataWrapper.getStoreprocedureName(); + // 存储过程子表名称,例如 Table1 + String tableName = tableDataName.replaceFirst(storeProcedureName, StringUtils.EMPTY).replaceFirst("_", StringUtils.EMPTY); + boolean isStoreProcedureNameMatch = isMatchSearch(storeProcedureName, searchText); + boolean isTableNameMatch = isMatchSearch(tableName, searchText); + // 再处理子表的columns + List columnNameList = tableDataWrapper.calculateColumnNameList(); + boolean isColumnMatch = columnNameList.stream().anyMatch(columnName -> isMatchSearch(columnName, searchText)); + Set addToMatch = new HashSet<>(); + Set addToExpand = new HashSet<>(); + Set addToCalculated = new HashSet<>(); + if (isStoreProcedureNameMatch) { + addToMatch.add(storeProcedureName); + } + if (isTableNameMatch) { + addToMatch.add(storeProcedureName); + addToExpand.add(storeProcedureName); + } + if (isColumnMatch) { + addToMatch.add(storeProcedureName); + addToExpand.add(storeProcedureName); + // 这里有重名风险,所以要添加 “Proc1_Table1”,在结果树展示的时候再去处理 + addToExpand.add(tableDataName); + } + addToCalculated.add(tableDataName); + TableDataSearchResult result = new TableDataSearchResult.Builder() + .buildSuccess(true) + .buildAddToMatch(new ArrayList<>(addToMatch)) + .buildAddToExpand(new ArrayList<>(addToExpand)) + .buildAddToCalculated(new ArrayList<>(addToCalculated)) + .build(); + callback.done(result); + } + + /** + * 判断TableDataWrapper内的TableData是否为存储过程 + * + * @param tableDataWrapper + * @return + */ + private boolean isTableDataStoreProcedure(TableDataWrapper tableDataWrapper) { + return tableDataWrapper instanceof StoreProcedureDataWrapper; + } + + /** + * 判断是否匹配搜索文本,不区分大小写 + * + * @param str + * @return + */ + private boolean isMatchSearch(String str, String searchText) { + return str.toUpperCase().contains(searchText.toUpperCase()); + } +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/pre/TableDataPreSearchCallBack.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/pre/TableDataPreSearchCallBack.java new file mode 100644 index 000000000..a4826918d --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/pre/TableDataPreSearchCallBack.java @@ -0,0 +1,17 @@ +package com.fr.design.data.datapane.management.search.control.pre; + +import com.fr.design.data.datapane.management.search.control.common.TableDataSearchCallBack; +import com.fr.design.data.datapane.management.search.searcher.TableDataTreeSearcher; + +/** + * 预取数任务回调 + * + * @author Yvan + */ +public class TableDataPreSearchCallBack extends TableDataSearchCallBack { + + + public TableDataPreSearchCallBack(TableDataTreeSearcher treeSearcher) { + super(treeSearcher); + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/pre/TableDataPreSearchResult.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/pre/TableDataPreSearchResult.java new file mode 100644 index 000000000..6ebdb33ab --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/pre/TableDataPreSearchResult.java @@ -0,0 +1,40 @@ +package com.fr.design.data.datapane.management.search.control.pre; + +import com.fr.design.data.datapane.management.search.control.TreeSearchResult; + +import java.util.ArrayList; +import java.util.List; + +/** + * 预取数任务结果,不需要回调,因此空实现即可 + * + * @author Yvan + */ +public class TableDataPreSearchResult implements TreeSearchResult { + + private boolean success; + + public TableDataPreSearchResult(boolean success) { + this.success = success; + } + + @Override + public boolean isSuccess() { + return this.success; + } + + @Override + public List getAddToMatch() { + return new ArrayList<>(); + } + + @Override + public List getAddToExpand() { + return new ArrayList<>(); + } + + @Override + public List getAddToCalculated() { + return new ArrayList<>(); + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/pre/TableDataPreSearchTask.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/pre/TableDataPreSearchTask.java new file mode 100644 index 000000000..009f02765 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/control/pre/TableDataPreSearchTask.java @@ -0,0 +1,42 @@ +package com.fr.design.data.datapane.management.search.control.pre; + +import com.fr.design.data.datapane.management.search.control.TreeSearchTask; +import com.fr.design.data.datapane.management.search.control.common.TableDataSearchResult; +import com.fr.design.data.datapane.management.search.control.TreeSearchCallback; +import com.fr.design.data.datapane.management.search.control.TreeSearchResult; +import com.fr.design.data.tabledata.wrapper.TableDataWrapper; +import com.fr.log.FineLoggerFactory; + +/** + * 预取数任务 + * + * @author Yvan + */ +public class TableDataPreSearchTask implements TreeSearchTask { + + private TreeSearchCallback callback; + + private TableDataWrapper tableDataWrapper; + + public TableDataPreSearchTask(TreeSearchCallback callback, TableDataWrapper tableDataWrapper) { + this.callback = callback; + this.tableDataWrapper = tableDataWrapper; + } + + @Override + public void run() { + TreeSearchResult result; + try { + tableDataWrapper.calculateColumnNameList(); + result = new TableDataSearchResult.Builder() + .buildSuccess(true) + .build(); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e, "calculate table data {} failed", tableDataWrapper.getTableDataName()); + result = new TableDataSearchResult.Builder() + .buildSuccess(false) + .build(); + } + callback.done(result); + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/event/TreeSearchStatusChangeEvent.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/event/TreeSearchStatusChangeEvent.java new file mode 100644 index 000000000..9d28e2d73 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/event/TreeSearchStatusChangeEvent.java @@ -0,0 +1,22 @@ +package com.fr.design.data.datapane.management.search.event; + +import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; + +import java.util.EventObject; + +/** + * @author Yvan + */ +public class TreeSearchStatusChangeEvent extends EventObject { + + private TreeSearchStatus status; + + public TreeSearchStatusChangeEvent(Object source) { + super(source); + this.status = (TreeSearchStatus) source; + } + + public TreeSearchStatus getTreeSearchStatus() { + return status; + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/event/TreeSearchStatusChangeListener.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/event/TreeSearchStatusChangeListener.java new file mode 100644 index 000000000..63dc4e698 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/event/TreeSearchStatusChangeListener.java @@ -0,0 +1,11 @@ +package com.fr.design.data.datapane.management.search.event; + +import java.util.EventListener; + +/** + * @author Yvan + */ +public interface TreeSearchStatusChangeListener extends EventListener { + + void updateTreeSearchChange(TreeSearchStatusChangeEvent event); +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/pane/TableDataSearchRemindPane.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/pane/TableDataSearchRemindPane.java new file mode 100644 index 000000000..7d62f876b --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/pane/TableDataSearchRemindPane.java @@ -0,0 +1,212 @@ +package com.fr.design.data.datapane.management.search.pane; + +import com.fr.base.svg.IconUtils; +import com.fr.design.constants.UIConstants; +import com.fr.design.data.datapane.TableDataTree; +import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; +import com.fr.design.data.datapane.management.search.event.TreeSearchStatusChangeEvent; +import com.fr.design.data.datapane.management.search.event.TreeSearchStatusChangeListener; +import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; +import com.fr.design.gui.icontainer.UIScrollPane; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.i18n.Toolkit; +import com.fr.design.layout.FRGUIPaneFactory; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +/** + * @author Yvan + */ +public class TableDataSearchRemindPane extends JPanel implements TreeSearchStatusChangeListener { + + private RemindPane remindPane; + private TreePane treePane; + + public TableDataSearchRemindPane(TableDataTree tableDataTree) { + this.setLayout(new BorderLayout()); + remindPane = new RemindPane(); + treePane = new TreePane(tableDataTree); + // 初始状态 + this.add(remindPane, BorderLayout.NORTH); + this.add(treePane, BorderLayout.CENTER); + TableDataTreeSearchManager.getInstance().registerTreeSearchStatusChangeListener(this); + } + + /** + * 根据搜索状态变化,来调整自身面板的显示 + * + * @param event + */ + @Override + public void updateTreeSearchChange(TreeSearchStatusChangeEvent event) { + TreeSearchStatus status = event.getTreeSearchStatus(); + if (status == TreeSearchStatus.SEARCH_NOT_BEGIN) { + remindPane.onNotBegin(); + treePane.onNotBegin(); + } else if (status == TreeSearchStatus.SEARCHING) { + remindPane.onInSearching(); + treePane.onInSearching(); + } else if (status == TreeSearchStatus.SEARCH_STOPPED) { + remindPane.onStoppedSearching(); + treePane.onStoppedSearching(); + } else { + boolean matchSetsEmpty = TableDataTreeSearchManager.getInstance().isMatchSetsEmpty(); + // 代表是否搜索出结果 + remindPane.onDoneSearching(matchSetsEmpty); + treePane.onDoneSearching(matchSetsEmpty); + } + this.revalidate(); + } + + private interface TreeSearchStatusChange { + + void onNotBegin(); + + void onInSearching(); + + void onStoppedSearching(); + + void onDoneSearching(boolean matchSetsEmpty); + } + + private class TreePane extends JPanel implements TreeSearchStatusChange { + + private UIScrollPane scrollPane; + + private JPanel notFoundPane; + + private CardLayout cardLayout; + + private static final String SCROLL_PANE = "scrollPane"; + + private static final String NOT_FOUND_PANE = "notFoundPane"; + + public TreePane(TableDataTree tableDataTree) { + init(tableDataTree); + } + + private void init(TableDataTree tableDataTree) { + + scrollPane = new UIScrollPane(tableDataTree); + scrollPane.setBorder(null); + + notFoundPane = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, FlowLayout.LEADING, 0, 5); + UILabel emptyPicLabel = new UILabel(); + emptyPicLabel.setIcon(IconUtils.readIcon("com/fr/base/images/share/no_match_icon.png")); + emptyPicLabel.setHorizontalAlignment(SwingConstants.CENTER); + emptyPicLabel.setPreferredSize(new Dimension(240, 100)); + UILabel textLabel = new UILabel(Toolkit.i18nText("Fine-Design_Tree_Search_Not_Match"), SwingConstants.CENTER); + textLabel.setForeground(Color.gray); + textLabel.setHorizontalAlignment(SwingConstants.CENTER); + textLabel.setPreferredSize(new Dimension(240, 20)); + notFoundPane.add(emptyPicLabel); + notFoundPane.add(textLabel); + notFoundPane.setBorder(BorderFactory.createEmptyBorder(80, 0, 0, 0)); + + cardLayout = new CardLayout(); + this.setLayout(cardLayout); + this.add(scrollPane, SCROLL_PANE); + this.add(notFoundPane, NOT_FOUND_PANE); + cardLayout.show(this, SCROLL_PANE); + } + + @Override + public void onNotBegin() { + switchPane(SCROLL_PANE); + } + + @Override + public void onInSearching() { + switchPane(SCROLL_PANE); + } + + @Override + public void onStoppedSearching() { + switchPane(SCROLL_PANE); + } + + @Override + public void onDoneSearching(boolean matchSetsEmpty) { + if (matchSetsEmpty) { + switchPane(NOT_FOUND_PANE); + } + } + + private void switchPane(String paneName) { + cardLayout.show(this, paneName); + } + } + + private class RemindPane extends JPanel implements TreeSearchStatusChange { + + private static final String IN_SEARCHING = "Fine-Design_Tree_Search_In_Searching"; + private static final String STOP_SEARCHING = "Fine-Design_Tree_Search_Stop_Search"; + private static final String SEARCHING_STOPPED = "Fine-Design_Tree_Search_Search_Stopped"; + private static final String DONE_SEARCHING = "Fine-Design_Tree_Search_Search_Completed"; + + private UILabel textLabel; + + private UILabel stopLabel; + + private MouseListener stopSearch; + + public RemindPane() { + init(); + } + + private void init() { + this.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 0)); + // 初始情况下为Not_Begin + textLabel = new UILabel(); + stopLabel = new UILabel(); + stopLabel.setForeground(UIConstants.NORMAL_BLUE); + stopSearch = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + TableDataTreeSearchManager.getInstance().stopSearch(); + } + }; + stopLabel.addMouseListener(stopSearch); + this.add(textLabel); + this.add(stopLabel); + onNotBegin(); + } + + @Override + public void onNotBegin() { + this.setVisible(false); + } + + @Override + public void onInSearching() { + this.textLabel.setText(IN_SEARCHING); + this.stopLabel.setText(STOP_SEARCHING); + this.stopLabel.setVisible(true); + this.setVisible(true); + } + + @Override + public void onStoppedSearching() { + this.textLabel.setText(SEARCHING_STOPPED); + this.stopLabel.setVisible(false); + this.setVisible(true); + } + + @Override + public void onDoneSearching(boolean matchSetsEmpty) { + this.textLabel.setText(DONE_SEARCHING); + this.stopLabel.setVisible(false); + this.setVisible(true); + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/pane/TreeSearchToolbarPane.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/pane/TreeSearchToolbarPane.java new file mode 100644 index 000000000..454009bf2 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/pane/TreeSearchToolbarPane.java @@ -0,0 +1,192 @@ +package com.fr.design.data.datapane.management.search.pane; + +import com.fr.base.svg.IconUtils; +import com.fr.design.DesignModelAdapter; +import com.fr.design.constants.UIConstants; +import com.fr.design.data.datapane.TableDataTreePane; +import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; +import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.gui.itextfield.UITextField; +import com.fr.design.gui.itoolbar.UIToolbar; +import com.fr.design.i18n.Toolkit; +import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.stable.StringUtils; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Panel; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +/** + * @author Yvan + */ +public class TreeSearchToolbarPane extends Panel { + + public static final String TOOLBAR_PANE = "toolbarPane"; + + public static final String SEARCH_PANE = "searchPane"; + + /** + * 工具栏 + */ + private UIToolbar toolbar; + + /** + * 工具栏面板 + */ + private JPanel toolbarPane; + + /** + * 搜索面板 + */ + private JPanel searchPane; + + /** + * 搜索输入框 + */ + private UITextField searchTextField; + + /** + * 内容面板 + */ + private JPanel contentPane; + + /** + * 卡片布局管理器 + */ + private CardLayout cardLayout; + + private final KeyAdapter enterPressed = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + TableDataTreeSearchManager.getInstance().startSearch(searchTextField.getText()); + } + } + }; + + public TreeSearchToolbarPane(UIToolbar toolbar) { + this.toolbar = toolbar; + this.setLayout(FRGUIPaneFactory.createBorderLayout()); + initToolbarPane(); + initSearchPane(); + initContentPane(); + add(contentPane, BorderLayout.CENTER); + setPreferredSize(new Dimension(240, 30)); + } + + private void initContentPane() { + cardLayout = new CardLayout(); + contentPane = new JPanel(cardLayout); + contentPane.add(searchPane, SEARCH_PANE); + contentPane.add(toolbarPane, TOOLBAR_PANE); + cardLayout.show(contentPane, TOOLBAR_PANE); + } + + private void initSearchPane() { + searchPane = new JPanel(FRGUIPaneFactory.createBorderLayout()); + searchPane.setBorder(BorderFactory.createLineBorder(UIConstants.TOOLBAR_BORDER_COLOR)); + searchPane.setBackground(Color.WHITE); + // 左侧搜索图标 + UILabel searchLabel = new UILabel(IconUtils.readIcon("/com/fr/design/images/data/search")); + searchLabel.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); + searchLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + // do nothing + } + }); + // 中间输入框 + searchTextField = new UITextField(); + searchTextField.setBorderPainted(false); + searchTextField.setPlaceholder(Toolkit.i18nText("Fine-Design_Tree_Search_Press_Enter_For_Search")); + searchTextField.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + searchPane.setBorder(BorderFactory.createLineBorder(UIConstants.NORMAL_BLUE)); + searchPane.repaint(); + } + + @Override + public void focusLost(FocusEvent e) { + searchPane.setBorder(BorderFactory.createLineBorder(UIConstants.TOOLBAR_BORDER_COLOR)); + searchPane.repaint(); + } + }); + this.searchTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + } + + @Override + public void removeUpdate(DocumentEvent e) { + dealWithTextChange(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + } + }); + // 右侧返回图标 + UILabel returnLabel = new UILabel(IconUtils.readIcon("/com/fr/design/images/data/clear")); + returnLabel.setToolTipText(Toolkit.i18nText("Fine-Design_Tree_Search_Return")); + returnLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + returnLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + searchTextField.setText(StringUtils.EMPTY); + TableDataTreeSearchManager.getInstance().switchBackToolBar(); + switchPane(TOOLBAR_PANE); + } + }); + + searchPane.add(searchLabel, BorderLayout.WEST); + searchPane.add(searchTextField, BorderLayout.CENTER); + searchPane.add(returnLabel, BorderLayout.EAST); + } + + private void dealWithTextChange() { + // 判断搜索是否正在进行 + if (StringUtils.isEmpty(searchTextField.getText()) && TableDataTreeSearchManager.getInstance().getTreeSearchStatus() != TreeSearchStatus.SEARCH_NOT_BEGIN) { + TableDataTreeSearchManager.getInstance().setTreeSearchStatus(TreeSearchStatus.SEARCH_NOT_BEGIN); + DesignModelAdapter currentModelAdapter = DesignModelAdapter.getCurrentModelAdapter(); + TableDataTreePane tableDataTreePane = (TableDataTreePane) TableDataTreePane.getInstance(currentModelAdapter); + tableDataTreePane.refreshDockingView(); + } + } + + private void initToolbarPane() { + toolbarPane = new JPanel(); + toolbarPane.setLayout(FRGUIPaneFactory.createBorderLayout()); + toolbarPane.add(toolbar, BorderLayout.CENTER); + } + + /** + * 交换当前面板层级 + */ + public void switchPane(String name) { + if (StringUtils.equals(name, TOOLBAR_PANE)) { + searchTextField.removeKeyListener(enterPressed); + searchTextField.setText(StringUtils.EMPTY); + } else if (StringUtils.equals(name, SEARCH_PANE)) { + searchTextField.addKeyListener(enterPressed); + } + cardLayout.show(contentPane, name); + } + + public void setPlaceHolder(String placeHolder) { + this.searchTextField.setPlaceholder(placeHolder); + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TableDataSearchMode.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TableDataSearchMode.java new file mode 100644 index 000000000..d68455db5 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TableDataSearchMode.java @@ -0,0 +1,38 @@ +package com.fr.design.data.datapane.management.search.searcher; + +/** + * 搜索模式 + * + * @author Yvan + */ +public enum TableDataSearchMode { + + /** + * 搜索模板数据集 + */ + TEMPLATE_TABLE_DATA(0), + + /** + * 搜索服务器数据集 + */ + SERVER_TABLE_DATA(1); + + private final int mode; + + TableDataSearchMode(int mode) { + this.mode = mode; + } + + public int getMode() { + return mode; + } + + public static TableDataSearchMode match(int mode) { + for (TableDataSearchMode searchMode : TableDataSearchMode.values()) { + if (searchMode.getMode() == mode) { + return searchMode; + } + } + return TEMPLATE_TABLE_DATA; + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TableDataTreeSearcher.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TableDataTreeSearcher.java new file mode 100644 index 000000000..7569491e9 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TableDataTreeSearcher.java @@ -0,0 +1,151 @@ +package com.fr.design.data.datapane.management.search.searcher; + +import com.fr.concurrent.NamedThreadFactory; +import com.fr.data.TableDataSource; +import com.fr.design.data.DesignTableDataManager; +import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; +import com.fr.design.data.datapane.management.search.control.common.TableDataSearchCallBack; +import com.fr.design.data.datapane.management.search.control.common.TableDataSearchTask; +import com.fr.design.data.datapane.management.search.control.pre.TableDataPreSearchCallBack; +import com.fr.design.data.datapane.management.search.control.pre.TableDataPreSearchTask; +import com.fr.design.data.tabledata.wrapper.TableDataWrapper; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +/** + * @author Yvan + */ +public class TableDataTreeSearcher implements TreeSearcher { + + private ExecutorService executorService; + + private Map allWrappers = new ConcurrentHashMap<>(); + + private final Set calculatedSets = new HashSet<>(); + + private final Set notCalculatedSets = new HashSet<>(); + + private final Set matchSets = new HashSet<>(); + + private final Set canExpandSets = new HashSet<>(); + + public TableDataTreeSearcher() { + } + + public boolean isMatchSetsEmpty() { + return matchSets.isEmpty(); + } + + public int getNotCalculatedSetsSize() { + return notCalculatedSets.size(); + } + + public synchronized void addToCalculatedSets(List tableDataNames) { + for (String tableDataName : tableDataNames) { + TableDataWrapper tableDataWrapper = allWrappers.get(tableDataName); + if (tableDataWrapper == null) { + return; + } + notCalculatedSets.remove(tableDataName); + calculatedSets.add(tableDataName); + } + } + + public synchronized void addToMatchSets(List matchNodeNames) { + matchSets.addAll(matchNodeNames); + } + + public synchronized void addToCanExpandSets(List canExpandNodeNames) { + canExpandSets.addAll(canExpandNodeNames); + } + + + /** + * 正式搜索前,预加载一下数据集列名 + * + * @param searchMode + * @param tableDataSource + */ + public void beforeSearch(TableDataSearchMode searchMode, TableDataSource tableDataSource) { + executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new NamedThreadFactory(TableDataTreeSearchManager.class)); + collectTableDataWrappers(searchMode, tableDataSource); + preCalculateColumns(); + } + + /** + * 预先对数据集进行取列名的操作,提升用户搜索体验 + */ + private void preCalculateColumns() { + for (String notCalculatedSet : notCalculatedSets) { + TableDataWrapper tableDataWrapper = allWrappers.get(notCalculatedSet); + if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() == TreeSearchStatus.SEARCH_NOT_BEGIN) { + executorService.execute(new TableDataPreSearchTask(new TableDataPreSearchCallBack(this), tableDataWrapper)); + } + } + } + + /** + * 收集下此次搜索需要进行取数的TableDataWrapper + * + * @param searchSubject + * @param tableDataSource + */ + private void collectTableDataWrappers(TableDataSearchMode searchSubject, TableDataSource tableDataSource) { + Map dataSet = searchSubject == TableDataSearchMode.TEMPLATE_TABLE_DATA ? + DesignTableDataManager.getTemplateDataSet(tableDataSource) : + DesignTableDataManager.getGlobalDataSet(); + // 转化一下存储过程 + Map setIncludingProcedure = DesignTableDataManager.getAllDataSetIncludingProcedure(dataSet); + notCalculatedSets.addAll(setIncludingProcedure.keySet()); + allWrappers.putAll(setIncludingProcedure); + } + + @Override + public void startSearch(String searchText) { + for (String notCalculatedSet : notCalculatedSets) { + TableDataWrapper tableDataWrapper = allWrappers.get(notCalculatedSet); + if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() == TreeSearchStatus.SEARCHING) { + executorService.execute(new TableDataSearchTask(searchText, tableDataWrapper, new TableDataSearchCallBack(this))); + } + } + } + + @Override + public void stopSearch() { + reset(); + } + + @Override + public void completeSearch() { + reset(); + } + + private void reset() { + matchSets.clear(); + canExpandSets.clear(); + calculatedSets.clear(); + notCalculatedSets.addAll(allWrappers.keySet()); + } + + public void afterSearch() { + allWrappers.clear(); + executorService.shutdownNow(); + } + + public boolean nodeMatches(String dsName) { + return matchSets.contains(dsName); + } + + public boolean nodeCanExpand(String dsName) { + return canExpandSets.contains(dsName); + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TreeSearchStatus.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TreeSearchStatus.java new file mode 100644 index 000000000..fd59f60ea --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TreeSearchStatus.java @@ -0,0 +1,24 @@ +package com.fr.design.data.datapane.management.search.searcher; + +/** + * @author Yvan + */ +public enum TreeSearchStatus { + + /** + * 搜索未开始 + */ + SEARCH_NOT_BEGIN, + /** + * 搜索中 + */ + SEARCHING, + /** + * 搜索已停止 + */ + SEARCH_STOPPED, + /** + * 搜索已完成 + */ + SEARCH_COMPLETED; +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TreeSearcher.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TreeSearcher.java new file mode 100644 index 000000000..b6f694470 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/searcher/TreeSearcher.java @@ -0,0 +1,27 @@ +package com.fr.design.data.datapane.management.search.searcher; + + +/** + * 用于搜索RefreshableJTree数据的搜索器 + * + * @author Yvan + */ +public interface TreeSearcher { + + /** + * 开始搜索 + * + * @param text + */ + void startSearch(String text); + + /** + * 停止搜索 + */ + void stopSearch(); + + /** + * 搜索完成 + */ + void completeSearch(); +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/time/TableDataSearchTimer.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/time/TableDataSearchTimer.java new file mode 100644 index 000000000..88c305e98 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/time/TableDataSearchTimer.java @@ -0,0 +1,54 @@ +package com.fr.design.data.datapane.management.search.time; + +import com.fr.concurrent.NamedThreadFactory; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * 搜索任务定时器 + * + * @author Yvan + */ +public class TableDataSearchTimer { + + /** + * 定时器 + */ + private ScheduledExecutorService scheduler; + + /** + * 定时任务 + */ + private TableDataSearchTimerTask timeTask; + + /** + * 最大单次startSearch的时间 + */ + public static final long MAX_SEARCH_TIME = 5000; + + private TableDataSearchTimer() { + } + + private static class Holder { + private static final TableDataSearchTimer INSTANCE = new TableDataSearchTimer(); + } + + public static TableDataSearchTimer getInstance() { + return Holder.INSTANCE; + } + + public void startClock() { + this.scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory(TableDataSearchTimer.class)); + this.timeTask = new TableDataSearchTimerTask(); + scheduler.schedule(timeTask, MAX_SEARCH_TIME, TimeUnit.MILLISECONDS); + } + + public void stopClock() { + this.timeTask = null; + if (this.scheduler != null) { + this.scheduler.shutdownNow(); + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/time/TableDataSearchTimerTask.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/time/TableDataSearchTimerTask.java new file mode 100644 index 000000000..65c2a0458 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/time/TableDataSearchTimerTask.java @@ -0,0 +1,21 @@ +package com.fr.design.data.datapane.management.search.time; + +import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; +import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; + +/** + * @author Yvan + */ +public class TableDataSearchTimerTask implements Runnable { + + public TableDataSearchTimerTask() { + } + + @Override + public void run() { + // 最大单次搜索时间过后,将结束搜索 + if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() == TreeSearchStatus.SEARCHING) { + TableDataTreeSearchManager.getInstance().completeSearch(); + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/datapane/management/search/view/TreeSearchRendererHelper.java b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/view/TreeSearchRendererHelper.java new file mode 100644 index 000000000..cf348f128 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/datapane/management/search/view/TreeSearchRendererHelper.java @@ -0,0 +1,82 @@ +package com.fr.design.data.datapane.management.search.view; + +import com.fr.design.data.datapane.TableDataTree; + +import javax.swing.JTree; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeCellRenderer; +import java.awt.Component; +import java.util.regex.Pattern; + +/** + * @author Yvan + */ +public class TreeSearchRendererHelper { + + /** + * 缓存下原来的渲染器 + */ + private TreeCellRenderer originTreeCellRenderer; + + public TreeSearchRendererHelper() { + } + + public TreeCellRenderer getOriginTreeCellRenderer() { + return originTreeCellRenderer; + } + + public void setOriginTreeCellRenderer(TreeCellRenderer originTreeCellRenderer) { + this.originTreeCellRenderer = originTreeCellRenderer; + } + + public void replaceTreeRenderer(TableDataTree tableDataTree, String searchText) { + tableDataTree.setCellRenderer(getNewTreeCellRenderer(searchText)); + } + + public void save(TableDataTree tableDataTree) { + if (getOriginTreeCellRenderer() == null) { + setOriginTreeCellRenderer(tableDataTree.getTableDataTreeCellRenderer()); + } + } + + public void restore(TableDataTree tableDataTree) { + if (getOriginTreeCellRenderer() != null) { + tableDataTree.setCellRenderer(getOriginTreeCellRenderer()); + } + } + + /** + * 获取新树渲染器,也就是搜索结果树的TreeCellRenderer,主要是为了文本高亮 + * + * @param searchText + * @return + */ + private TreeCellRenderer getNewTreeCellRenderer(String searchText) { + return new DefaultTreeCellRenderer() { + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + Component treeCellRendererComponent = getOriginTreeCellRenderer().getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + if (treeCellRendererComponent instanceof DefaultTreeCellRenderer) { + DefaultTreeCellRenderer defaultTreeCellRenderer = (DefaultTreeCellRenderer) treeCellRendererComponent; + String text = defaultTreeCellRenderer.getText(); + defaultTreeCellRenderer.setText(getHighlightText(text, searchText)); + } + return treeCellRendererComponent; + } + }; + } + + private String getHighlightText(String text, String textToHighlight) { + String highLightTemplate = "$1"; + if (textToHighlight.length() == 0) { + return text; + } + try { + text = text.replaceAll("(?i)(" + Pattern.quote(textToHighlight) + ")", highLightTemplate); + } catch (Exception e) { + return text; + } + text = "" + text + ""; + return text; + } +} diff --git a/designer-base/src/main/java/com/fr/design/data/tabledata/paste/TableDataFollowingPasteUtils.java b/designer-base/src/main/java/com/fr/design/data/tabledata/paste/TableDataFollowingPasteUtils.java new file mode 100644 index 000000000..ffeb9e5f6 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/data/tabledata/paste/TableDataFollowingPasteUtils.java @@ -0,0 +1,149 @@ +package com.fr.design.data.tabledata.paste; + +import com.fr.base.TableData; +import com.fr.data.TableDataSource; +import com.fr.design.DesignModelAdapter; +import com.fr.design.data.DesignTableDataManager; +import com.fr.design.data.datapane.TableDataTreePane; +import com.fr.design.data.tabledata.tabledatapane.AbstractTableDataPane; +import com.fr.design.data.tabledata.wrapper.AbstractTableDataWrapper; +import com.fr.design.data.tabledata.wrapper.TableDataWrapper; +import com.fr.design.data.tabledata.wrapper.TemplateTableDataWrapper; +import com.fr.form.data.DataBinding; +import com.fr.form.data.DataTableConfig; +import com.fr.form.ui.DataControl; +import com.fr.form.ui.DictionaryContainer; +import com.fr.form.ui.Widget; +import com.fr.form.ui.concept.data.ValueInitializer; +import com.fr.report.cell.tabledata.ElementUsedTableDataProvider; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * 数据集跟随复制粘贴的工具类 + * + * @author Yvan + */ +public class TableDataFollowingPasteUtils { + + /** + * 粘贴所有Map中的tabledata到当前模板 + * + * @param tableDataWrapperMap + */ + public static void paste(Map tableDataWrapperMap) { + if (tableDataWrapperMap == null) { + return; + } + // 获取当前的TableDataTreePane + DesignModelAdapter currentModelAdapter = DesignModelAdapter.getCurrentModelAdapter(); + TableDataTreePane tableDataTreePane = (TableDataTreePane) TableDataTreePane.getInstance(currentModelAdapter); + // 粘贴(添加)数据集 + for (Map.Entry dataWrapperEntry : tableDataWrapperMap.entrySet()) { + String oldName = dataWrapperEntry.getKey(); + // 处理名称重复情况 + String dsName = tableDataTreePane.getNoRepeatedDsName4Paste(oldName); + AbstractTableDataWrapper tableDataWrapper = new TemplateTableDataWrapper(dataWrapperEntry.getValue(), dsName); + AbstractTableDataPane tableDataPane = tableDataWrapper.creatTableDataPane(); + tableDataTreePane.addDataPane(tableDataPane, dsName); + } + } + + /** + * 处理 ElementUsedTableDataProvider,从中获取数据集名称 - 数据集Wrapper 的Map + * + * @param providers + * @return + */ + public static Map transferProvider2TableDataMap(ElementUsedTableDataProvider... providers) { + if (providers == null) { + return new HashMap<>(); + } + // 获取当前的所有模板数据集 + Map templateTableData = getCurrentTemplateTableDataWrapper(); + Map resultMap = new HashMap<>(); + for (ElementUsedTableDataProvider tableDataProvider : providers) { + Set usedTableDataNames = tableDataProvider.getElementUsedTableDataNames(); + for (String usedTableDataName : usedTableDataNames) { + if (templateTableData.containsKey(usedTableDataName)) { + resultMap.put(usedTableDataName, templateTableData.get(usedTableDataName).getTableData()); + } + } + } + return resultMap; + } + + /** + * 提取控件内使用的数据集,转化成Map返回 + * + * @param widgets + * @return + */ + public static Map transferWidgetArray2TableDataMap(Widget... widgets) { + if (widgets == null) { + return new HashMap<>(); + } + // 获取当前的所有模板数据集 + Map templateTableData = getCurrentTemplateTableDataWrapper(); + Map resultMap = new HashMap<>(); + for (Widget widget : widgets) { + collectTableDataInDictionary(templateTableData, resultMap, widget); + collectTableDataInWidgetValue(templateTableData, resultMap, widget); + } + return resultMap; + } + + /** + * 收集控件值中的TableData + * + * @param templateTableData + * @param resultMap + * @param widget + */ + private static void collectTableDataInWidgetValue(Map templateTableData, Map resultMap, Widget widget) { + if (widget instanceof DataControl && ((DataControl) widget).getWidgetValue() != null) { + ValueInitializer widgetValue = ((DataControl) widget).getWidgetValue(); + Object value = widgetValue.getValue(); + if (value instanceof DataBinding) { + String dataSourceName = ((DataBinding) value).getDataSourceName(); + if (templateTableData.containsKey(dataSourceName)) { + resultMap.put(dataSourceName, templateTableData.get(dataSourceName).getTableData()); + } + } + if (value instanceof DataTableConfig) { + String tableDataName = ((DataTableConfig) value).getTableDataName(); + if (templateTableData.containsKey(tableDataName)) { + resultMap.put(tableDataName, templateTableData.get(tableDataName).getTableData()); + } + } + } + } + + /** + * 收集控件-数据字典中的TableData + * + * @param templateTableData + * @param resultMap + * @param widget + */ + private static void collectTableDataInDictionary(Map templateTableData, Map resultMap, Widget widget) { + if (widget instanceof DictionaryContainer) { + Set usedTableDataSets = ((DictionaryContainer) widget).getUsedTableDataSets(); + for (String usedTableDataSet : usedTableDataSets) { + if (templateTableData.containsKey(usedTableDataSet)) { + resultMap.put(usedTableDataSet, templateTableData.get(usedTableDataSet).getTableData()); + } + } + } + } + + private static Map getCurrentTemplateTableDataWrapper() { + TableDataSource tableDataSource = DesignTableDataManager.getEditingTableDataSource(); + List> editingDataSet = DesignTableDataManager.getEditingDataSet(tableDataSource); + return editingDataSet.get(0); + } + +} diff --git a/designer-base/src/main/java/com/fr/design/gui/itree/refreshabletree/RefreshableJTree.java b/designer-base/src/main/java/com/fr/design/gui/itree/refreshabletree/RefreshableJTree.java index a22f2a302..161c1c7c7 100644 --- a/designer-base/src/main/java/com/fr/design/gui/itree/refreshabletree/RefreshableJTree.java +++ b/designer-base/src/main/java/com/fr/design/gui/itree/refreshabletree/RefreshableJTree.java @@ -154,6 +154,13 @@ public abstract class RefreshableJTree extends CheckBoxTree { refresh((ExpandMutableTreeNode) this.getModel().getRoot(), childName); } + /** + * 刷新树,用于搜索结果的展示 + */ + public void refresh4TreeSearch() { + + } + /* * 刷新expandRoot节点下所有已打开的节点的UserObject,并打开isExpanded为true的TreeNode */ diff --git a/designer-base/src/main/resources/com/fr/design/images/data/back_normal.svg b/designer-base/src/main/resources/com/fr/design/images/data/back_normal.svg new file mode 100644 index 000000000..8b22bc182 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/images/data/back_normal.svg @@ -0,0 +1,9 @@ + + + icon_返回_normal + + + + diff --git a/designer-base/src/main/resources/com/fr/design/images/data/clear_normal.svg b/designer-base/src/main/resources/com/fr/design/images/data/clear_normal.svg new file mode 100644 index 000000000..11f60a553 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/images/data/clear_normal.svg @@ -0,0 +1,10 @@ + + + icon_关闭_normal + + + + diff --git a/designer-base/src/main/resources/com/fr/design/images/data/search_normal.svg b/designer-base/src/main/resources/com/fr/design/images/data/search_normal.svg new file mode 100644 index 000000000..dc036ac53 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/images/data/search_normal.svg @@ -0,0 +1,9 @@ + + + icon_搜索_normal + + + + diff --git a/designer-base/src/test/java/com/fr/design/data/datapane/management/clip/TableDataTreeClipboardTest.java b/designer-base/src/test/java/com/fr/design/data/datapane/management/clip/TableDataTreeClipboardTest.java new file mode 100644 index 000000000..98871158d --- /dev/null +++ b/designer-base/src/test/java/com/fr/design/data/datapane/management/clip/TableDataTreeClipboardTest.java @@ -0,0 +1,43 @@ +package com.fr.design.data.datapane.management.clip; + +import com.fr.data.impl.EmbeddedTableData; +import com.fr.design.data.tabledata.wrapper.AbstractTableDataWrapper; +import com.fr.design.data.tabledata.wrapper.TemplateTableDataWrapper; +import junit.framework.TestCase; +import org.junit.Assert; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Yvan + */ +public class TableDataTreeClipboardTest extends TestCase { + + public void testAddAndTake() { + Map testMap = new HashMap<>(); + testMap.put("ds1", new TemplateTableDataWrapper(new EmbeddedTableData())); + testMap.put("ds2", new TemplateTableDataWrapper(new EmbeddedTableData())); + + Map anotherTestMap = new HashMap<>(); + anotherTestMap.put("ds3", new TemplateTableDataWrapper(new EmbeddedTableData())); + + Map clip; + TableDataTreeClipboard.getInstance().addToClip(testMap); + clip = TableDataTreeClipboard.getInstance().takeFromClip(); + Assert.assertEquals(2, clip.size()); + Assert.assertTrue(clip.containsKey("ds1")); + Assert.assertTrue(clip.containsKey("ds2")); + + // 验证多次取出 + clip = TableDataTreeClipboard.getInstance().takeFromClip(); + Assert.assertEquals(2, clip.size()); + Assert.assertTrue(clip.containsKey("ds1")); + Assert.assertTrue(clip.containsKey("ds2")); + + TableDataTreeClipboard.getInstance().addToClip(anotherTestMap); + clip = TableDataTreeClipboard.getInstance().takeFromClip(); + Assert.assertEquals(1, clip.size()); + Assert.assertTrue(clip.containsKey("ds3")); + } +} \ No newline at end of file diff --git a/designer-base/src/test/java/com/fr/design/data/tabledata/paste/TableDataFollowingPasteUtilsTest.java b/designer-base/src/test/java/com/fr/design/data/tabledata/paste/TableDataFollowingPasteUtilsTest.java new file mode 100644 index 000000000..2435f363f --- /dev/null +++ b/designer-base/src/test/java/com/fr/design/data/tabledata/paste/TableDataFollowingPasteUtilsTest.java @@ -0,0 +1,201 @@ +package com.fr.design.data.tabledata.paste; + +import com.fr.base.TableData; +import com.fr.data.Dictionary; +import com.fr.data.TableDataSource; +import com.fr.data.impl.EmbeddedTableData; +import com.fr.design.data.DesignTableDataManager; +import com.fr.design.data.tabledata.wrapper.TableDataWrapper; +import com.fr.design.data.tabledata.wrapper.TemplateTableDataWrapper; +import com.fr.form.data.DataBinding; +import com.fr.form.data.DataTableConfig; +import com.fr.form.ui.AbstractDataControl; +import com.fr.form.ui.DictionaryContainer; +import com.fr.form.ui.Widget; +import com.fr.form.ui.WidgetValue; +import com.fr.report.cell.tabledata.ElementUsedTableDataProvider; +import com.fr.script.Calculator; +import com.fr.stable.script.CalculatorProvider; +import com.fr.stable.script.NameSpace; +import com.fr.web.core.TemplateSessionIDInfo; +import junit.framework.TestCase; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.powermock.api.easymock.PowerMock; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Yvan + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({DesignTableDataManager.class}) +public class TableDataFollowingPasteUtilsTest extends TestCase { + + @Before + public void beforeTest() { + Map templateDataMap = new LinkedHashMap(); + Map serverDataMap = new LinkedHashMap(); + Map storeProcedureMap = new LinkedHashMap(); + templateDataMap.put("ds1", new TemplateTableDataWrapper(new EmbeddedTableData())); + templateDataMap.put("ds2", new TemplateTableDataWrapper(new EmbeddedTableData())); + templateDataMap.put("ds3", new TemplateTableDataWrapper(new EmbeddedTableData())); + templateDataMap.put("ds4", new TemplateTableDataWrapper(new EmbeddedTableData())); + templateDataMap.put("ds5", new TemplateTableDataWrapper(new EmbeddedTableData())); + + List> list = new ArrayList>(); + list.add(templateDataMap); + list.add(serverDataMap); + list.add(storeProcedureMap); + + TableDataSource tableDataSource = EasyMock.mock(TableDataSource.class); + PowerMock.mockStatic(DesignTableDataManager.class); + EasyMock.expect(DesignTableDataManager.getEditingTableDataSource()).andReturn(tableDataSource).anyTimes(); + EasyMock.expect(DesignTableDataManager.getEditingDataSet(tableDataSource)).andReturn(list).anyTimes(); + PowerMock.replayAll(); + } + + public void testTransferProvider2TableDataMap() { + ElementUsedTableDataProvider[] providers = generateElementUsedTableDataProvider(); + Map tableDataMap = TableDataFollowingPasteUtils.transferProvider2TableDataMap(providers); + Assert.assertEquals(2, tableDataMap.size()); + Assert.assertTrue(tableDataMap.containsKey("ds1")); + Assert.assertTrue(tableDataMap.containsKey("ds2")); + } + + private ElementUsedTableDataProvider[] generateElementUsedTableDataProvider() { + ElementUsedTableDataProvider elementUsedTableDataProvider1 = new ElementUsedTableDataProvider() { + @Override + public Set getElementUsedTableDataNames() { + Set set = new HashSet<>(); + set.add("ds1"); + return set; + } + }; + ElementUsedTableDataProvider elementUsedTableDataProvider2 = new ElementUsedTableDataProvider() { + @Override + public Set getElementUsedTableDataNames() { + Set set = new HashSet<>(); + set.add("ds2"); + return set; + } + }; + return new ElementUsedTableDataProvider[]{elementUsedTableDataProvider1, elementUsedTableDataProvider2}; + } + + public void testTransferWidgetArray2TableDataMap() { + Widget[] widgets = generateWidgetArray(); + Map tableDataMap = TableDataFollowingPasteUtils.transferWidgetArray2TableDataMap(widgets); + Assert.assertEquals(3, tableDataMap.size()); + Assert.assertTrue(tableDataMap.containsKey("ds3")); + Assert.assertTrue(tableDataMap.containsKey("ds4")); + Assert.assertTrue(tableDataMap.containsKey("ds5")); + } + + private Widget[] generateWidgetArray() { + Set set = new HashSet<>(); + set.add("ds3"); + MockWidget widget1 = EasyMock.mock(MockWidget.class); + EasyMock.expect(widget1.getUsedTableDataSets()).andReturn(set).anyTimes(); + EasyMock.replay(widget1); + + DataBinding dataBinding = new DataBinding("ds4", ""); + WidgetValue widgetValue2 = new WidgetValue(); + widgetValue2.setValue(dataBinding); + AbstractDataControl widget2 = EasyMock.mock(AbstractDataControl.class); + EasyMock.expect(widget2.getWidgetValue()).andReturn(widgetValue2).anyTimes(); + EasyMock.replay(widget2); + + DataTableConfig dataTableConfig = EasyMock.mock(DataTableConfig.class); + EasyMock.expect(dataTableConfig.getTableDataName()).andReturn("ds5").anyTimes(); + WidgetValue widgetValue3 = new WidgetValue(); + widgetValue3.setValue(dataTableConfig); + AbstractDataControl widget3 = EasyMock.mock(AbstractDataControl.class); + EasyMock.expect(widget3.getWidgetValue()).andReturn(widgetValue3).anyTimes(); + EasyMock.replay(dataTableConfig, widget3); + + Widget[] widgets = new Widget[3]; + widgets[0] = widget1; + widgets[1] = widget2; + widgets[2] = widget3; + return widgets; + } + + private class MockWidget extends Widget implements DictionaryContainer { + + @Override + public String[] supportedEvents() { + return new String[0]; + } + + @Override + public void setDictionary(Dictionary model) { + + } + + @Override + public Dictionary getDictionary() { + return null; + } + + @Override + public Object getViewValue(Object value, Calculator c, TemplateSessionIDInfo sessionIDInfor, HttpServletRequest req) { + return null; + } + + @Override + public Object getModuleValue(Object text, Calculator c, TemplateSessionIDInfo sessionIDInfor, HttpServletRequest req) { + return null; + } + + @Override + public Object getViewValue(Object value, Calculator c, TemplateSessionIDInfo sessionIDInfor, HttpServletRequest req, NameSpace dependenceNameSpace) { + return null; + } + + @Override + public Object getModuleValue(Object text, Calculator c, TemplateSessionIDInfo sessionIDInfor, HttpServletRequest req, NameSpace dependenceNameSpace) { + return null; + } + + @Override + public boolean isValueAllInDictionary(Object value, Calculator c, TemplateSessionIDInfo sessionIDInfor, HttpServletRequest req, NameSpace dependenceNameSpace) { + return false; + } + + @Override + public String getXType() { + return null; + } + + @Override + public boolean isEditor() { + return false; + } + + @Override + public void setDependenceMap(Map dependenceMap) { + + } + + @Override + public Map getDependenceMap() { + return null; + } + + @Override + public String[] dependence(CalculatorProvider calculatorProvider) { + return new String[0]; + } + } +} \ No newline at end of file diff --git a/designer-form/src/main/java/com/fr/design/designer/beans/models/SelectionModel.java b/designer-form/src/main/java/com/fr/design/designer/beans/models/SelectionModel.java index 31bb62531..6aa47474a 100644 --- a/designer-form/src/main/java/com/fr/design/designer/beans/models/SelectionModel.java +++ b/designer-form/src/main/java/com/fr/design/designer/beans/models/SelectionModel.java @@ -3,6 +3,7 @@ package com.fr.design.designer.beans.models; import com.fr.common.inputevent.InputEventBaseOnOS; import com.fr.design.ExtraDesignClassManager; import com.fr.design.base.clipboard.ClipboardFilter; +import com.fr.design.data.tabledata.paste.TableDataFollowingPasteUtils; import com.fr.design.designer.beans.AdapterBus; import com.fr.design.designer.beans.LayoutAdapter; import com.fr.design.designer.beans.events.DesignerEvent; @@ -187,6 +188,8 @@ public class SelectionModel { } else { //已选 selectedPaste(); + // 粘贴剪切板控件中的数据集 + pasteTableDataFromWidget(pasteSelection); } } else { Toolkit.getDefaultToolkit().beep(); @@ -194,6 +197,10 @@ public class SelectionModel { return false; } + private void pasteTableDataFromWidget(FormSelection pasteSelection) { + TableDataFollowingPasteUtils.paste(pasteSelection.getSelectionUsedTablaData()); + } + public FormSelection getSelection() { return selection; } diff --git a/designer-form/src/main/java/com/fr/design/mainframe/FormSelection.java b/designer-form/src/main/java/com/fr/design/mainframe/FormSelection.java index ee7918e50..1df1b5d2f 100644 --- a/designer-form/src/main/java/com/fr/design/mainframe/FormSelection.java +++ b/designer-form/src/main/java/com/fr/design/mainframe/FormSelection.java @@ -1,5 +1,7 @@ package com.fr.design.mainframe; +import com.fr.base.TableData; +import com.fr.design.data.tabledata.paste.TableDataFollowingPasteUtils; import com.fr.design.designer.beans.AdapterBus; import com.fr.design.designer.beans.LayoutAdapter; import com.fr.design.designer.beans.adapters.layout.FRAbsoluteLayoutAdapter; @@ -7,12 +9,10 @@ import com.fr.design.designer.beans.location.Direction; import com.fr.design.designer.creator.XComponent; import com.fr.design.designer.creator.XCreator; import com.fr.design.designer.creator.XCreatorUtils; -import com.fr.design.designer.creator.XElementCase; import com.fr.design.designer.creator.XLayoutContainer; import com.fr.design.designer.creator.XWAbsoluteLayout; import com.fr.design.designer.creator.XWFitLayout; import com.fr.design.designer.creator.XWParameterLayout; -import com.fr.design.designer.creator.XWTitleLayout; import com.fr.design.designer.creator.cardlayout.XWCardMainBorderLayout; import com.fr.design.designer.creator.cardlayout.XWCardTagLayout; import com.fr.design.designer.creator.cardlayout.XWTabFitLayout; @@ -22,11 +22,12 @@ import com.fr.design.utils.gui.LayoutUtils; import com.fr.form.ui.Widget; import com.fr.log.FineLoggerFactory; -import java.awt.Component; import java.awt.LayoutManager; import java.awt.Rectangle; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedList; +import java.util.Map; public class FormSelection { @@ -35,6 +36,7 @@ public class FormSelection { private ArrayList recs = new ArrayList(); // 选中的组件外层嵌套的tab块 head->tail 由内向外 private LinkedList tabList = new LinkedList<>(); + private Map selectionUsedTablaData = new HashMap<>(); public FormSelection() { selection = new ArrayList(); @@ -362,8 +364,13 @@ public class FormSelection { public void cut2ClipBoard(FormSelection clipBoard) { clipBoard.reset(); clipBoard.selection.addAll(selection); - for (XCreator creator : selection) { + try { + // 剪切时,添加剪切组件的数据集到usedTablaDataMap中 + clipBoard.addUsedTablaData((Widget) creator.toData().clone()); + } catch (CloneNotSupportedException e) { + FineLoggerFactory.getLogger().error(e.getMessage()); + } removeCreatorFromContainer(creator); } reset(); @@ -382,15 +389,26 @@ public class FormSelection { continue; } try { - XCreator creator = XCreatorUtils.createXCreator((Widget) root.toData().clone()); + Widget clone = (Widget) root.toData().clone(); + XCreator creator = XCreatorUtils.createXCreator(clone); creator.setBounds(root.getBounds()); clipBoard.selection.add(creator); + // 复制时,添加剪切组件的数据集到usedTablaDataMap中 + clipBoard.addUsedTablaData(clone); } catch (CloneNotSupportedException e) { FineLoggerFactory.getLogger().error(e.getMessage(), e); } } } + public void addUsedTablaData(Widget... widget) { + this.selectionUsedTablaData.putAll(TableDataFollowingPasteUtils.transferWidgetArray2TableDataMap(widget)); + } + + public Map getSelectionUsedTablaData() { + return selectionUsedTablaData; + } + public LinkedList getTabList() { return tabList; } diff --git a/designer-realize/src/main/java/com/fr/design/cell/clipboard/CellElementsClip.java b/designer-realize/src/main/java/com/fr/design/cell/clipboard/CellElementsClip.java index 8bc66e172..b54c871e0 100644 --- a/designer-realize/src/main/java/com/fr/design/cell/clipboard/CellElementsClip.java +++ b/designer-realize/src/main/java/com/fr/design/cell/clipboard/CellElementsClip.java @@ -3,7 +3,9 @@ */ package com.fr.design.cell.clipboard; +import com.fr.base.TableData; import com.fr.design.base.clipboard.ClipboardHelper; +import com.fr.design.data.tabledata.paste.TableDataFollowingPasteUtils; import com.fr.grid.selection.CellSelection; import com.fr.log.FineLoggerFactory; import com.fr.report.cell.CellElement; @@ -17,6 +19,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Map; /** * The clip of CellElement. @@ -27,6 +30,7 @@ public class CellElementsClip implements Cloneable, java.io.Serializable { private FU[] columnWidth; private FU[] rowHeight; private TemplateCellElement[] clips; + private Map elementUsedTableDatas; public CellElementsClip(int columnSpan, int rowSpan, FU[] columnWidth, FU[] rowHeight, TemplateCellElement[] clips) { this.columnSpan = columnSpan; @@ -34,12 +38,14 @@ public class CellElementsClip implements Cloneable, java.io.Serializable { this.columnWidth = columnWidth; this.rowHeight = rowHeight; this.clips = clips; + this.elementUsedTableDatas = TableDataFollowingPasteUtils.transferProvider2TableDataMap(clips); } public CellElementsClip(int columnSpan, int rowSpan, TemplateCellElement[] clips) { this.columnSpan = columnSpan; this.rowSpan = rowSpan; this.clips = clips; + this.elementUsedTableDatas = TableDataFollowingPasteUtils.transferProvider2TableDataMap(clips); } public int getColumnSpan() { @@ -136,6 +142,8 @@ public class CellElementsClip implements Cloneable, java.io.Serializable { if (this.columnWidth != null && this.rowHeight != null) { pasteWidthAndHeight(ec, column, row, columnSpan, rowSpan); } + // 粘贴数据集 + TableDataFollowingPasteUtils.paste(elementUsedTableDatas); return new CellSelection(column, row, columnSpan, rowSpan); } @@ -163,6 +171,8 @@ public class CellElementsClip implements Cloneable, java.io.Serializable { } ec.addCellElement(cellElement); + // 跟随粘贴数据集 + TableDataFollowingPasteUtils.paste(elementUsedTableDatas); } } @@ -182,4 +192,4 @@ public class CellElementsClip implements Cloneable, java.io.Serializable { return cloned; } -} \ No newline at end of file +} diff --git a/designer-realize/src/main/java/com/fr/design/cell/clipboard/FloatElementsClip.java b/designer-realize/src/main/java/com/fr/design/cell/clipboard/FloatElementsClip.java index 7dc7fd80b..3c1f54e33 100644 --- a/designer-realize/src/main/java/com/fr/design/cell/clipboard/FloatElementsClip.java +++ b/designer-realize/src/main/java/com/fr/design/cell/clipboard/FloatElementsClip.java @@ -3,7 +3,9 @@ */ package com.fr.design.cell.clipboard; +import com.fr.base.TableData; import com.fr.design.cell.FloatElementsProvider; +import com.fr.design.data.tabledata.paste.TableDataFollowingPasteUtils; import com.fr.general.ComparatorUtils; import com.fr.grid.selection.FloatSelection; import com.fr.log.FineLoggerFactory; @@ -14,21 +16,25 @@ import com.fr.stable.unit.FU; import com.fr.stable.unit.OLDPIX; import java.util.Iterator; +import java.util.Map; /** * The clip of Float Element. */ -public class FloatElementsClip implements Cloneable, java.io.Serializable,FloatElementsProvider { +public class FloatElementsClip implements Cloneable, java.io.Serializable, FloatElementsProvider { private FloatElement floatEl; - public FloatElementsClip(FloatElement floatEl) { - this.floatEl = floatEl; - } - - /** - * 悬浮元素的粘贴 - * - * @param ec 单元格 + private Map elementUsedTableDatas; + + public FloatElementsClip(FloatElement floatEl) { + this.floatEl = floatEl; + this.elementUsedTableDatas = TableDataFollowingPasteUtils.transferProvider2TableDataMap(floatEl); + } + + /** + * 悬浮元素的粘贴 + * + * @param ec 单元格 * @return 粘贴的悬浮元素 */ public FloatSelection pasteAt(TemplateElementCase ec) { @@ -46,7 +52,7 @@ public class FloatElementsClip implements Cloneable, java.io.Serializable,FloatE while (ec.getFloatElement(ret.getName()) != null) { ret.setName(ret.getName() + "-Copy"); } - + while (true) { if (isContainSameBoundFloatElement(ec, ret)) { ret.setTopDistance(FU.getInstance(ret.getTopDistance().toFU() + new OLDPIX(50).toFU())); @@ -55,11 +61,12 @@ public class FloatElementsClip implements Cloneable, java.io.Serializable,FloatE break; } } - + ec.addFloatElement(ret); - + // 跟随粘贴数据集 + TableDataFollowingPasteUtils.paste(elementUsedTableDatas); return new FloatSelection(ret.getName()); - } + } /** * Contain same location and bounds FloatElement. @@ -100,4 +107,4 @@ public class FloatElementsClip implements Cloneable, java.io.Serializable,FloatE return cloned; } -} \ No newline at end of file +}