You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
587 lines
19 KiB
587 lines
19 KiB
package com.fr.design.mainframe; |
|
|
|
import com.fr.design.constants.UIConstants; |
|
import com.fr.design.designer.creator.XCreator; |
|
import com.fr.design.designer.creator.XCreatorUtils; |
|
import com.fr.design.designer.creator.XLayoutContainer; |
|
import com.fr.design.designer.creator.XWAbsoluteLayout; |
|
import com.fr.design.designer.creator.XWTitleLayout; |
|
import com.fr.design.designer.creator.cardlayout.XWCardMainBorderLayout; |
|
import com.fr.design.designer.treeview.ComponentTreeCellRenderer; |
|
import com.fr.design.designer.treeview.ComponentTreeModel; |
|
import com.fr.design.file.HistoryTemplateListCache; |
|
import com.fr.design.gui.itree.UITreeUI; |
|
import com.fr.design.utils.ComponentUtils; |
|
import com.fr.design.utils.gui.GUICoreUtils; |
|
import com.fr.stable.StringUtils; |
|
import java.awt.Rectangle; |
|
import java.util.Stack; |
|
import java.util.function.Consumer; |
|
import javax.swing.BorderFactory; |
|
import javax.swing.DropMode; |
|
import javax.swing.JPanel; |
|
import javax.swing.JPopupMenu; |
|
import javax.swing.JTree; |
|
import javax.swing.SwingUtilities; |
|
import javax.swing.tree.TreeCellRenderer; |
|
import javax.swing.tree.TreePath; |
|
import javax.swing.tree.TreeSelectionModel; |
|
import java.awt.BorderLayout; |
|
import java.awt.Color; |
|
import java.awt.Component; |
|
import java.awt.Container; |
|
import java.awt.Dimension; |
|
import java.awt.Graphics; |
|
import java.awt.Point; |
|
import java.awt.event.MouseAdapter; |
|
import java.awt.event.MouseEvent; |
|
import java.awt.image.BufferedImage; |
|
import java.io.IOException; |
|
import java.util.ArrayList; |
|
import java.util.Enumeration; |
|
import java.util.HashMap; |
|
import java.util.List; |
|
import java.util.Map; |
|
|
|
public class ComponentTree extends JTree { |
|
|
|
private FormDesigner designer; |
|
private ComponentTreeModel model; |
|
private UITreeUI uiTreeUI = new UITreeUI(); |
|
private PopupPreviewPane previewPane; |
|
|
|
private static final Map<String, List<TreePath>> treePathCache = new HashMap<>(); |
|
private static final int PADDING_LEFT = 5; |
|
private static final int PADDING_TOP = 5; |
|
|
|
public ComponentTree(FormDesigner designer) { |
|
this.designer = designer; |
|
this.setBackground(UIConstants.TREE_BACKGROUND); |
|
setRootVisible(true); |
|
setCellRenderer(new ComponentTreeCellRenderer()); |
|
getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); |
|
this.setDragEnabled(false); |
|
this.setDropMode(DropMode.ON_OR_INSERT); |
|
this.setTransferHandler(new TreeTransferHandler()); |
|
this.refreshTreeRoot(); |
|
initListeners(); |
|
setEditable(true); |
|
setUI(uiTreeUI); |
|
setBorder(BorderFactory.createEmptyBorder(PADDING_TOP, PADDING_LEFT, 0, 0)); |
|
} |
|
|
|
private void initListeners() { |
|
this.addTreeSelectionListener(designer); |
|
ComponetTreeMouseListener componetTreeMouseListener = new ComponetTreeMouseListener(this); |
|
this.addMouseMotionListener(componetTreeMouseListener); |
|
this.addMouseListener(componetTreeMouseListener); |
|
} |
|
|
|
public FormDesigner getDesigner() { |
|
return designer; |
|
} |
|
|
|
/** |
|
* 构造函数 |
|
* |
|
* @param designer 设计界面组件 |
|
* @param model 构造JTree的model |
|
*/ |
|
public ComponentTree(FormDesigner designer, ComponentTreeModel model) { |
|
this(designer); |
|
this.setModel(model); |
|
expandTree(); |
|
} |
|
|
|
public void setSelectionPath(TreePath path) { |
|
// 不管点击哪一项,都要先退出编辑状态(图表、报表块、绝对布局、tab块) |
|
Object widget = path.getLastPathComponent(); |
|
if(widget == null ||!((XCreator)widget).getXCreatorBaseOperate().supportSelected()){ |
|
return; |
|
} |
|
designer.stopEditing(path); |
|
super.setSelectionPath(path); |
|
} |
|
|
|
@Override |
|
public void removeAll() { |
|
if (previewPane != null) { |
|
previewPane.setVisible(false); |
|
previewPane.removeAll(); |
|
} |
|
super.removeAll(); |
|
} |
|
|
|
|
|
/** |
|
* 是否可编辑 |
|
* |
|
* @param path 树路径 |
|
* @return 是则返回true |
|
*/ |
|
@Override |
|
public boolean isPathEditable(TreePath path) { |
|
return false; |
|
} |
|
|
|
/** |
|
* 将值转换为文本 |
|
* |
|
* @param value 值 |
|
* @param selected 是否选中 |
|
* @param expanded 扩展 |
|
* @param leaf 是否叶子 |
|
* @param row 行 |
|
* @param hasFocus 是否焦点 |
|
* @return 返回文本 |
|
*/ |
|
@Override |
|
public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { |
|
if (value != null && value instanceof XCreator) { |
|
return ((XCreator) value).toData().getWidgetName(); |
|
} else { |
|
return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus); |
|
} |
|
} |
|
|
|
public void setAndScrollSelectionPath(TreePath[] treepath) { |
|
setSelectionPaths(treepath); |
|
if (treepath.length > 0) { |
|
scrollPathToVisible(treepath[0]); |
|
} |
|
} |
|
|
|
@Override |
|
public void paint(Graphics g) { |
|
super.paint(g); |
|
designer.getSelectionModel(); // 否则参数一个一个加会导致参数面板和body结构出问题 |
|
} |
|
|
|
/** |
|
* 刷新 |
|
*/ |
|
public void refreshUI() { |
|
updateUI(); |
|
setUI(uiTreeUI); |
|
} |
|
|
|
|
|
public TreePath[] getSelectedTreePath() { |
|
XCreator[] creators = designer.getSelectionModel().getSelection().getSelectedCreators(); |
|
TreePath[] paths = new TreePath[creators.length]; |
|
for (int i = 0; i < paths.length; i++) { |
|
paths[i] = buildTreePath(creators[i]); |
|
} |
|
return paths; |
|
} |
|
|
|
|
|
/** |
|
* 搜索指定名称的路径 |
|
* |
|
* @param text 名称 |
|
* @return 树路径 |
|
*/ |
|
public TreePath[] search(String text) { |
|
if (StringUtils.isNotEmpty(text)) { |
|
text = text.toLowerCase(); |
|
} |
|
ArrayList<XCreator> searchList = new ArrayList<XCreator>(); |
|
XLayoutContainer root = designer.getRootComponent(); |
|
searchFormRoot(root, searchList, text); |
|
TreePath[] paths = new TreePath[searchList.size()]; |
|
|
|
for (int i = 0; i < paths.length; i++) { |
|
paths[i] = buildTreePath(searchList.get(i)); |
|
} |
|
if (paths.length > 0) { |
|
setAndScrollSelectionPath(paths); |
|
} else { |
|
setSelectionPath(); |
|
} |
|
return paths; |
|
} |
|
|
|
@Override |
|
protected void setExpandedState(TreePath path, boolean state) { |
|
super.setExpandedState(path, state); |
|
saveTreePath(); |
|
} |
|
|
|
/** |
|
* 删除key对应的缓存展开路径 |
|
*/ |
|
public void removeTreePath(String key) { |
|
if (StringUtils.isNotEmpty(key)) { |
|
treePathCache.remove(key); |
|
} |
|
} |
|
|
|
private void setSelectionPath() { |
|
|
|
/** |
|
* 不让传null参数,所以只有自己定义 |
|
*/ |
|
super.setSelectionPath(null); |
|
clearSelection(); |
|
} |
|
|
|
private void searchFormRoot(XLayoutContainer container, ArrayList<XCreator> searchList, String text) { |
|
if (StringUtils.isEmpty(text)) { |
|
return; |
|
} |
|
for (int i = 0, size = container.getXCreatorCount(); i < size; i++) { |
|
XCreator creator = container.getXCreator(i); |
|
String xName = creator.toData().getWidgetName(); |
|
if (xName.toLowerCase().contains(text)) { |
|
searchList.add(creator); |
|
} |
|
if (creator instanceof XLayoutContainer) { |
|
searchFormRoot((XLayoutContainer) creator, searchList, text); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* 触发 |
|
*/ |
|
public void fireTreeChanged() { |
|
designer.refreshDesignerUI(); |
|
} |
|
|
|
/** |
|
* 刷新 |
|
*/ |
|
public void refreshTreeRoot() { |
|
model = new ComponentTreeModel(designer, designer.getTopContainer()); |
|
setModel(model); |
|
expandTree(); |
|
setDragEnabled(false); |
|
setDropMode(DropMode.ON_OR_INSERT); |
|
setTransferHandler(new TreeTransferHandler()); |
|
repaint(); |
|
} |
|
|
|
/** |
|
* 从缓存中获取展开路径并进行展开 |
|
*/ |
|
public void expandTree() { |
|
expandTree(loadTreePath()); |
|
} |
|
|
|
/** |
|
* 按照传入参数展开组件树 |
|
*/ |
|
private void expandTree(List<TreePath> list) { |
|
if (list == null) { |
|
return; |
|
} |
|
for (TreePath treePath : list) { |
|
expandPath(treePath); |
|
} |
|
} |
|
|
|
/** |
|
* 获得树的展开路径 |
|
*/ |
|
private List<TreePath> getExpandTreePaths() { |
|
List<TreePath> result = new ArrayList<>(); |
|
TreePath rootTreePath = buildTreePath(designer.getTopContainer()); |
|
Enumeration<TreePath> enumeration = getExpandedDescendants(rootTreePath); |
|
if (enumeration != null) { |
|
while (enumeration.hasMoreElements()) { |
|
result.add(enumeration.nextElement()); |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
private void saveTreePath() { |
|
JTemplate<?, ?> jt = HistoryTemplateListCache.getInstance().getCurrentEditingTemplate(); |
|
if (jt instanceof JForm && jt.getTarget() == designer.getTarget()) { |
|
String key = jt.getEditingFILE().getPath() + "-" + designer.getTarget().getTemplateID(); |
|
treePathCache.put(key, getExpandTreePaths()); |
|
} |
|
} |
|
|
|
private List<TreePath> loadTreePath() { |
|
List<TreePath> result = null; |
|
JTemplate<?, ?> jt = HistoryTemplateListCache.getInstance().getCurrentEditingTemplate(); |
|
if (jt instanceof JForm) { |
|
String key = jt.getEditingFILE().getPath() + "-" + designer.getTarget().getTemplateID(); |
|
result = treePathCache.get(key); |
|
} |
|
return result == null ? new ArrayList<>() : result; |
|
} |
|
|
|
private TreePath buildTreePath(Component comp) { |
|
ArrayList<Component> path = new ArrayList<Component>(); |
|
Component parent = comp; |
|
|
|
while (parent != null) { |
|
XCreator creator = (XCreator) parent; |
|
path.add(0, parent); |
|
if (creator != comp) { |
|
creator.notShowInComponentTree(path); |
|
} |
|
parent = creator.getParentShow(); |
|
} |
|
Object[] components = path.toArray(); |
|
return new TreePath(components); |
|
} |
|
|
|
|
|
private void popupPreviewPane(int popupPosYOnScreen, XCreator comp) { |
|
if (previewPane == null) { |
|
previewPane = new PopupPreviewPane(); |
|
} |
|
if (previewPane.isVisible() && previewPane.getLocationOnScreen().y != popupPosYOnScreen) { |
|
previewPane.setVisible(false); |
|
} |
|
|
|
if (!previewPane.isVisible() && comp.getWidth() != 0 && comp.getHeight() != 0) { |
|
previewPane.setComp(comp); |
|
int popupPosY = popupPosYOnScreen - FormHierarchyTreePane.getInstance().getLocationOnScreen().y; |
|
GUICoreUtils.showPopupMenu(previewPane, FormHierarchyTreePane.getInstance(), -previewPane.getPreferredSize().width, popupPosY); |
|
} |
|
} |
|
|
|
private void hidePreviewPane() { |
|
if (previewPane != null && previewPane.isVisible()) { |
|
previewPane.setVisible(false); |
|
} |
|
} |
|
|
|
private final class ComponetTreeMouseListener extends MouseAdapter { |
|
private final JTree tree; |
|
private XCreator selectedCreator; |
|
|
|
private ComponetTreeMouseListener(JTree tree) { |
|
this.tree = tree; |
|
} |
|
|
|
@Override |
|
public void mouseMoved(MouseEvent e) { |
|
Point p = e.getPoint(); |
|
int selRow = tree.getRowForLocation(p.x, p.y); |
|
TreeCellRenderer r = tree.getCellRenderer(); |
|
if (selRow != -1 && r != null) { |
|
TreePath path = tree.getPathForRow(selRow); |
|
Point point = tree.getPathBounds(path).getLocation(); |
|
SwingUtilities.convertPointToScreen(point, tree); |
|
XCreator comp = (XCreator) path.getLastPathComponent(); |
|
if (comp.getXCreatorBaseOperate().supportSelected()) { |
|
popupPreviewPane(point.y, comp); |
|
return; |
|
} |
|
} |
|
hidePreviewPane(); |
|
} |
|
|
|
@Override |
|
public void mouseExited(MouseEvent e) { |
|
hidePreviewPane(); |
|
} |
|
|
|
@Override |
|
public void mouseClicked(MouseEvent e) { |
|
onMouseEvent(e, new Consumer<XCreator>() { |
|
@Override |
|
public void accept(XCreator creator) { |
|
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1 && !designer.isFormParaDesigner() |
|
&&creator.getXCreatorBaseOperate().supportSelected()) { |
|
startEditing(creator); |
|
} |
|
} |
|
}); |
|
|
|
} |
|
|
|
@Override |
|
public void mousePressed(MouseEvent e) { |
|
onMouseEvent(e, new Consumer<XCreator>() { |
|
@Override |
|
public void accept(XCreator creator) { |
|
if (e.getButton() == MouseEvent.BUTTON1 && creator.getXCreatorBaseOperate().supportSelected()) { |
|
selectedCreator = creator; |
|
} |
|
} |
|
}); |
|
} |
|
|
|
@Override |
|
public void mouseReleased(MouseEvent e) { |
|
if (e.isControlDown() || e.isShiftDown()) { |
|
return; |
|
} |
|
XCreator currentCreator = designer.getSelectionModel().getSelection().getSelectedCreator(); |
|
// 以当前选中的为准 |
|
if (currentCreator != selectedCreator) { |
|
selectedCreator = currentCreator; |
|
} |
|
if (e.getButton() == MouseEvent.BUTTON1 && selectedCreator != null) { |
|
showSelectedPopup(selectedCreator); |
|
} |
|
} |
|
|
|
private void showSelectedPopup(XCreator comp) { |
|
Rectangle rectangle = getRelativeBounds(comp); |
|
comp.showSelectedPopup(designer, rectangle, comp.acceptType(XWTitleLayout.class, XWCardMainBorderLayout.class, XWAbsoluteLayout.class)); |
|
comp.setSelected(true); |
|
designer.repaint(); |
|
} |
|
|
|
/** |
|
* 响应鼠标事件并解析选中的组件 |
|
* |
|
* @param e |
|
* @param consumer |
|
*/ |
|
private void onMouseEvent(final MouseEvent e, Consumer<XCreator> consumer) { |
|
if (e.isControlDown() || e.isShiftDown()) { |
|
return; |
|
} |
|
Point p = e.getPoint(); |
|
// 解析组件树路径 获取选中的组件 |
|
int selRow = tree.getRowForLocation(p.x, p.y); |
|
TreePath path = tree.getPathForRow(selRow); |
|
Rectangle bounds = tree.getPathBounds(path); |
|
if (bounds != null) { |
|
Point point = bounds.getLocation(); |
|
SwingUtilities.convertPointToScreen(point, tree); |
|
XCreator comp = (XCreator) path.getLastPathComponent(); |
|
consumer.accept(comp); |
|
} |
|
} |
|
|
|
/** |
|
* 组件进入编辑状态 |
|
* |
|
* @param comp |
|
*/ |
|
private void startEditing(XCreator comp) { |
|
designer.getSelectionModel().selectACreator(comp); |
|
XCreator creator = comp.getEditingChildCreator(); |
|
responseEditing(creator); |
|
// 放到事件尾部执行 |
|
SwingUtilities.invokeLater(new Runnable() { |
|
@Override |
|
public void run() { |
|
//处理下tab块的选中 |
|
if (comp.acceptType(XWCardMainBorderLayout.class)) { |
|
XCreator xCreator = designer.getSelectionModel().getSelection().getSelectedCreator(); |
|
if (xCreator != null) { |
|
showSelectedPopup(xCreator); |
|
} |
|
} |
|
} |
|
}); |
|
|
|
} |
|
|
|
/** |
|
* 自父容器到子组件 每一层的都响应click事件 |
|
* |
|
* |
|
* @param creator |
|
*/ |
|
private void responseEditing(XCreator creator) { |
|
Stack<XCreator> stack = new Stack<>(); |
|
stack.push(creator); |
|
while (creator.getParent() instanceof XCreator) { |
|
creator = (XCreator) creator.getParent(); |
|
stack.push(creator); |
|
} |
|
while (!stack.isEmpty()) { |
|
stack.pop().startEditing(); |
|
} |
|
} |
|
|
|
|
|
/** |
|
* 获取组件范围坐标 |
|
* |
|
* @param creator |
|
* @return |
|
*/ |
|
private Rectangle getRelativeBounds(XCreator creator) { |
|
Rectangle bounds = creator.getBounds(); |
|
XLayoutContainer parent = XCreatorUtils.getParentXLayoutContainer(creator); |
|
if (parent == null) { |
|
return bounds; |
|
} |
|
Rectangle rec = ComponentUtils.getRelativeBounds(parent); |
|
bounds.x += rec.x; |
|
bounds.y += rec.y; |
|
return bounds; |
|
} |
|
} |
|
|
|
private class PopupPreviewPane extends JPopupMenu { |
|
private Container contentPane; |
|
private BufferedImage compImage; |
|
private static final int MAX_WIDTH = 360; |
|
private static final int MAX_HEIGHT = 280; |
|
|
|
PopupPreviewPane() { |
|
contentPane = new JPanel(); |
|
contentPane.setBackground(Color.white); |
|
this.setLayout(new BorderLayout()); |
|
this.add(contentPane, BorderLayout.CENTER); |
|
this.setOpaque(false); |
|
setPreferredSize(new Dimension(MAX_WIDTH, MAX_HEIGHT)); |
|
setBorder(BorderFactory.createLineBorder(UIConstants.LINE_COLOR)); |
|
} |
|
|
|
public void setComp(XCreator comp) { |
|
try { |
|
this.compImage = componentToImage(comp); |
|
this.updateSize(); |
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
@Override |
|
public void paint(Graphics g) { |
|
super.paint(g); |
|
if (compImage != null) { |
|
g.drawImage(compImage, 0, 0, getWidth(), getHeight(), null); |
|
} |
|
} |
|
|
|
@Override |
|
public void setVisible(boolean visible) { |
|
super.setVisible(visible); |
|
} |
|
|
|
public void menuSelectionChanged(boolean isIncluded) { |
|
} |
|
|
|
public Container getContentPane() { |
|
return contentPane; |
|
} |
|
|
|
private BufferedImage componentToImage(Component comp) throws IOException { |
|
BufferedImage im = new BufferedImage(comp.getWidth(), comp.getHeight(), BufferedImage.TYPE_INT_ARGB); |
|
comp.paint(im.getGraphics()); |
|
return im; |
|
} |
|
|
|
// 根据控件内容,更新弹出框大小 |
|
private void updateSize() { |
|
int width = compImage.getWidth(); |
|
int height = compImage.getHeight(); |
|
double aspectRatio = (double) width / height; |
|
if (width > MAX_WIDTH) { |
|
width = MAX_WIDTH; |
|
height = (int) (width / aspectRatio); |
|
} |
|
if (height > MAX_HEIGHT) { |
|
height = MAX_HEIGHT; |
|
width = (int) (height * aspectRatio); |
|
} |
|
this.setPreferredSize(new Dimension(width, height)); |
|
} |
|
} |
|
} |