idea插件开发的第六天-开发一个笔记插件

介绍

Demo说明

  • 本文基于maven项目开发,idea版本为2022.3以上,jdk为1.8
  • 本文在JTools插件之上进行开发
  • 本插件目标是做一款笔记插件,用于开发者在开发过程中随时记录信息
  • 仓库地址: jtools-plugins

JTools插件说明

  • Tools插件是一个Idea插件,此插件提供统一Spi规范,极大的降低了idea插件的开发难度,并提供开发者模块,可以极大的为开发者开发此插件提供便利
  • Tools插件安装需要idea2022.3以上版本
  • 插件下载连接: https://download.csdn.net/download/qq_42413011/89702325

成果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

依赖安装

		<dependency>
            <groupId>com.fifesoft</groupId>
            <artifactId>rsyntaxtextarea</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.52</version>
        </dependency>

点击这里动态安装插件sdk
在这里插入图片描述

创建PluginImpl

package com.lhstack.aaa;

import com.lhstack.tools.plugins.Action;
import com.lhstack.tools.plugins.Helper;
import com.lhstack.tools.plugins.IPlugin;
import com.lhstack.tools.plugins.Logger;

import javax.swing.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class PluginImpl implements IPlugin {


    /**
     * 缓存笔记视图,key=project locationHash
     */
    private final Map<String, NotesView> viewMap = new HashMap<>();


    /**
     * 缓存logger,key=project locationHash
     */
    private final Map<String, Logger> loggerMap = new HashMap<>();

    /**
     * 创建笔记视图
     *
     * @param locationHash
     * @return
     */
    @Override
    public JComponent createPanel(String locationHash) {
        return viewMap.computeIfAbsent(locationHash, key -> {
            return new NotesView(locationHash, loggerMap.get(locationHash));
        });
    }

    /**
     * 缓存logger
     *
     * @param projectHash
     * @param logger
     * @param openThisPage
     */
    @Override
    public void openProject(String projectHash, Logger logger, Runnable openThisPage) {
        loggerMap.put(projectHash, logger);
    }

    /**
     * 项目关闭时,清理相关缓存
     *
     * @param projectHash
     */
    @Override
    public void closeProject(String projectHash) {
        NotesView notesView = viewMap.remove(projectHash);
        if (notesView != null) {
            notesView.run();
        }
        loggerMap.remove(projectHash);
    }

    /**
     * 插件卸载,清理缓存
     */
    @Override
    public void unInstall() {
        viewMap.values().forEach(Runnable::run);
        viewMap.clear();
        loggerMap.clear();
    }


    /**
     * 插件面板icon
     *
     * @return
     */
    @Override
    public Icon pluginIcon() {
        return Helper.findIcon("logo.svg", PluginImpl.class);
    }

    /**
     * 插件打开,顶部的tab icon
     *
     * @return
     */
    @Override
    public Icon pluginTabIcon() {
        return Helper.findIcon("logo_tab.svg", PluginImpl.class);
    }

    /**
     * 插件名称
     *
     * @return
     */
    @Override
    public String pluginName() {
        return "笔记";
    }

    /**
     * 插件描述
     *
     * @return
     */
    @Override
    public String pluginDesc() {
        return "这是一个笔记插件";
    }

    /**
     * 插件版本
     *
     * @return
     */
    @Override
    public String pluginVersion() {
        return "0.0.1";
    }


    /**
     * 插件内容tab右侧的按钮
     *
     * @param locationHash
     * @return
     */
    @Override
    public List<Action> swingTabPanelActions(String locationHash) {
        return Arrays.asList(new Action() {
            @Override
            public Icon icon() {
                return Helper.findIcon("icons/home.svg", PluginImpl.class);
            }

            @Override
            public String title() {
                return "主页";
            }

            @Override
            public void actionPerformed() {
                //如果未选中,点击则打开主页面板
                if (!isSelected()) {
                    viewMap.get(locationHash).switchHomeView();
                }
            }

            /**
             * 按钮是否需要选中
             * @return
             */
            @Override
            public boolean isSelected() {
                return viewMap.get(locationHash).isHomeView();
            }
        }, new Action() {
            @Override
            public Icon icon() {
                return Helper.findIcon("icons/content.svg", PluginImpl.class);
            }

            @Override
            public String title() {
                return "内容";
            }

            @Override
            public void actionPerformed() {
                //按钮未选中,则触发
                if (!isSelected()) {
                    //如果不能切换到内容视图,则激活日志面板,打印提示日志
                    if (!viewMap.get(locationHash).switchContentView()) {
                        loggerMap.get(locationHash).activeConsolePanel();
                        loggerMap.get(locationHash).warn("请先选择对应的节点");
                    }
                }
            }

            /**
             * 按钮是否需要选中
             * @return
             */
            @Override
            public boolean isSelected() {
                return viewMap.get(locationHash).isContentView();
            }

        });
    }
}

META-INF/ToolsPlugin.txt

com.lhstack.aaa.PluginImpl

视图代码

package com.lhstack.aaa;

import com.lhstack.tools.plugins.Logger;

import javax.swing.*;
import java.awt.*;

public class NotesView extends JPanel implements Runnable {

    private static final String HOME_PAGE = "HOME";

    private static final String CONTENT_PAGE = "CONTENT";

    private final HomeView homeView;

    private final ContentView contentView;

    private final CardLayout cardLayout;

    private String currentView;

    public NotesView(String locationHash, Logger logger) {
        //笔记主页视图
        this.homeView = new HomeView(locationHash, this, logger);
        //笔记内容视图
        this.contentView = new ContentView(locationHash, this, logger, homeView::getDatas);
        //创建卡片布局
        this.cardLayout = new CardLayout();
        //添加主页视图到布局
        cardLayout.addLayoutComponent(homeView, HOME_PAGE);
        //添加内容视图到布局
        cardLayout.addLayoutComponent(contentView, CONTENT_PAGE);
        //添加视图到容器
        this.add(homeView);
        this.add(contentView);
        //为容器设置卡片布局
        this.setLayout(cardLayout);
        //显示主页视图
        this.cardLayout.show(this, HOME_PAGE);
        //缓存当前显示的视图
        this.currentView = HOME_PAGE;
    }

    public void switchHomeView() {
        //显示主页
        cardLayout.show(this, HOME_PAGE);
        //设置当前显示的视图为主页
        currentView = HOME_PAGE;
    }

    public boolean switchContentView() {
        //切换内容面板,需要判断是否切换成功
        //获取视图面板当前选中的节点,没有就是false
        return this.homeView.getSelectedData().map(data -> {
            //获取到,则将当前节点放入内容视图
            this.contentView.onShow(data);
            //切换到内容视图
            cardLayout.show(this, CONTENT_PAGE);
            //修改当前缓存视图为内容视图
            currentView = CONTENT_PAGE;
            return true;
        }).orElse(false);
    }

    /**
     * 判断当前是否为内容视图,用于按钮选中效果
     *
     * @return
     */
    public boolean isContentView() {
        return currentView.equals(CONTENT_PAGE);
    }

    /**
     * @return
     */
    public boolean isHomeView() {
        return currentView.equals(HOME_PAGE);
    }

    /**
     * 卸载,项目关闭回调
     */
    @Override
    public void run() {
        this.contentView.run();
    }
}

主页视图

在这里插入图片描述

package com.lhstack.aaa;

import com.lhstack.aaa.entity.Data;
import com.lhstack.tools.plugins.Helper;
import com.lhstack.tools.plugins.Logger;

import javax.swing.*;
import javax.swing.border.MatteBorder;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;

public class HomeView extends JPanel {

    private final NotesView notesView;
    private final Logger logger;
    private final String locationHash;
    private JTree tree;
    private List<Data> datas;
    private DefaultTreeModel treeModel;
    private DefaultMutableTreeNode root;

    public HomeView(String locationHash, NotesView notesView, Logger logger) {
        this.notesView = notesView;
        this.logger = logger;
        this.locationHash = locationHash;
        this.setLayout(new BorderLayout());
        this.initMenu();
        this.initContent();
    }

    private void initContent() {
        //加载数据
        this.datas = DataManager.loadData(locationHash);
        //创建root节点
        this.root = new DefaultMutableTreeNode();
        //初始化树
        initTree(root, datas);
        //创建树模型
        this.treeModel = new DefaultTreeModel(root);
        //创建tree
        this.tree = new JTree(treeModel);
        //不显示root节点
        this.tree.setRootVisible(false);
        //设置不可编辑
        this.tree.setEditable(false);
        //创建render,自定义未选中的背景色
        DefaultTreeCellRenderer cellRenderer = new DefaultTreeCellRenderer();
        //设置为透明背景
        cellRenderer.setBackgroundNonSelectionColor(new Color(0, 0, 0, 0));
        this.tree.setCellRenderer(cellRenderer);
        //自定义选择模式
        this.tree.setSelectionModel(new DefaultTreeSelectionModel() {
            @Override
            public void setSelectionPath(TreePath path) {
                super.setSelectionPath(path);
                if (path != null) {
                    tree.scrollPathToVisible(path);
                }
            }
        });
        //设置不支持多选
        this.tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        //设置鼠标监听
        this.tree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {

                // 根据点击的位置获取最近的行
                int row = tree.getClosestRowForLocation(e.getX(), e.getY());

                // 获取行高和树的行数
                int rowHeight = tree.getRowHeight();
                int totalRows = tree.getRowCount();

                // 如果点击的位置超出了树的行数总高度,取消选中
                if (e.getY() > totalRows * rowHeight || row == -1) {
                    tree.clearSelection(); // 如果点击的不是任何行,取消选中
                } else {
                    tree.setSelectionRow(row); // 选中行
                }
                //获取选中的节点
                TreePath treePath = tree.getSelectionPath();
                if (treePath != null) {
                    //右键菜单
                    if (SwingUtilities.isRightMouseButton(e)) {
                        JPopupMenu popupMenu = new JPopupMenu();
                        JMenuItem addNodeItem = new JMenuItem("新增节点", Helper.findIcon("icons/addNode.svg", HomeView.class));
                        addNodeItem.addActionListener(event -> {
                            try {
                                String name = JOptionPane.showInputDialog("请输入节点名称");
                                if (name == null || name.trim().isEmpty()) {
                                    logger.activeConsolePanel();
                                    logger.warn("新增节点,节点名称不能为空");
                                    return;
                                }
                                //当前节点作为父节点
                                DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) treePath.getLastPathComponent();
                                //获取节点数据
                                Data parentData = (Data) parentNode.getUserObject();
                                //创建数据节点
                                Data data = new Data();
                                data.setName(name);
                                //获取父级几点children,如果没有,则初始化
                                List<Data> childrenList = parentData.getChildren();
                                if (childrenList == null) {
                                    childrenList = new ArrayList<>();
                                    parentData.setChildren(childrenList);
                                }
                                //添加数据节点到父节点的children
                                childrenList.add(data);
                                DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(data);
                                parentNode.add(newNode);
                                //更新视图
                                treeModel.insertNodeInto(newNode, parentNode, parentNode.getIndex(newNode));
                                //持久化数据
                                DataManager.storeData(datas, locationHash);
                            } catch (Throwable err) {
                                logger.error("添加节点失败: " + err);
                            }
                        });
                        JMenuItem removeItem = new JMenuItem("删除节点", Helper.findIcon("icons/deleteNode.svg", HomeView.class));
                        removeItem.addActionListener(event -> {
                            int confirm = JOptionPane.showConfirmDialog(null, "你确定要删除节点吗,如果是树节点,子节点的内容会放到这个节点的父级", "警告", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
                            if (confirm == JOptionPane.OK_OPTION) {
                                try {
                                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent();
                                    DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent();
                                    List<Data> parenDataList;
                                    if (parent == null) {
                                        parent = root;
                                        parenDataList = datas;
                                    } else {
                                        Data parentData = (Data) parent.getUserObject();
                                        if (parentData == null) {
                                            parenDataList = datas;
                                        } else {
                                            parenDataList = parentData.getChildren();
                                            if (parenDataList == null) {
                                                parenDataList = new ArrayList<>();
                                                parentData.setChildren(parenDataList);
                                            }
                                        }
                                    }
                                    Enumeration<TreeNode> children = node.children();
                                    parent.remove(node);
                                    parenDataList.remove((Data) node.getUserObject());
                                    if (children != null) {
                                        while (children.hasMoreElements()) {
                                            DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
                                            parent.add(child);
                                            parenDataList.add((Data) child.getUserObject());
                                        }
                                    }
                                    treeModel.reload(parent);
                                    logger.info(datas);
                                    DataManager.storeData(datas, locationHash);
                                } catch (Throwable err) {
                                    logger.error("删除节点错误: " + err);
                                }
                            }
                        });
                        JMenuItem editItem = new JMenuItem("编辑节点", Helper.findIcon("icons/editNode.svg", HomeView.class));
                        editItem.addActionListener(event -> {
                            DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent();
                            Data data = (Data) node.getUserObject();
                            String name = JOptionPane.showInputDialog(null, "请输入节点名称", data.getName());
                            if (name == null || name.trim().isEmpty()) {
                                logger.activeConsolePanel();
                                logger.warn("编辑节点名称不能为空");
                                return;
                            }
                            data.setName(name);
                            treeModel.nodeStructureChanged(node);
                            DataManager.storeData(datas, locationHash);
                        });
                        JMenuItem openContentItem = new JMenuItem("打开内容", Helper.findIcon("icons/open.svg", HomeView.class));
                        openContentItem.addActionListener(event -> {
                            notesView.switchContentView();
                        });

                        popupMenu.add(addNodeItem);
                        popupMenu.add(removeItem);
                        popupMenu.add(editItem);
                        popupMenu.add(openContentItem);


                        DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) treePath.getLastPathComponent();

                        if (treeNode.getChildCount() > 0) {
                            JMenuItem removeDirItem = new JMenuItem("删除目录", Helper.findIcon("icons/deleteNode.svg", HomeView.class));
                            removeDirItem.setToolTipText("删除整个目录和目录下所有的节点");
                            removeDirItem.addActionListener(event -> {
                                DefaultMutableTreeNode parent = (DefaultMutableTreeNode) treeNode.getParent();
                                treeNode.removeFromParent();
                                List<Data> parentDataList;
                                Data data = (Data) parent.getUserObject();
                                if (data == null) {
                                    parentDataList = datas;
                                } else {
                                    parentDataList = data.getChildren();
                                    if (parentDataList == null) {
                                        parentDataList = new ArrayList<>();
                                        data.setChildren(parentDataList);
                                    }
                                }
                                parentDataList.remove((Data) treeNode.getUserObject());
                                treeModel.reload(parent);
                                DataManager.storeData(datas, locationHash);
                            });
                            popupMenu.add(removeDirItem);
                        }

                        popupMenu.show(e.getComponent(), e.getX() + 10, e.getY() + 10);
                    }

                    if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 3) {
                        notesView.switchContentView();
                    }
                }

            }
        });
        Helper.treeSpeedSearch(tree, true, treePath -> {
            DefaultMutableTreeNode lastPathComponent = (DefaultMutableTreeNode) treePath.getLastPathComponent();
            Object userObject = lastPathComponent.getUserObject();
            if (userObject != null) {
                Data data = (Data) userObject;
                return data.getName();
            }
            return "";
        });
        JScrollPane jScrollPane = new JScrollPane(this.tree);
        MatteBorder border = BorderFactory.createMatteBorder(1, 0, 0, 0, Color.gray);
        this.tree.setBorder(border);
        this.add(jScrollPane, BorderLayout.CENTER);
    }


    public Optional<Data> getSelectedData() {

        return Optional.ofNullable(tree.getSelectionPath()).map(TreePath::getLastPathComponent)
                .map(DefaultMutableTreeNode.class::cast).map(DefaultMutableTreeNode::getUserObject).map(Data.class::cast);
    }

    private void initTree(DefaultMutableTreeNode root, List<Data> datas) {
        datas.forEach(data -> {
            DefaultMutableTreeNode node = new DefaultMutableTreeNode(data);
            root.add(node);
            if (data.getChildren() != null && !data.getChildren().isEmpty()) {
                initTree(node, data.getChildren());
            }
        });
    }

    private void initMenu() {
        JPanel panel = new JPanel();
        panel.setBorder(null);
        panel.setLayout(new FlowLayout(FlowLayout.RIGHT));
        panel.add(Helper.actionButton(Helper.findIcon("icons/unfold.svg", PluginImpl.class), "全部展开", str -> {
            expandAll();
        }));
        panel.add(Helper.actionButton(Helper.findIcon("icons/packup.svg", PluginImpl.class), "全部收起", str -> {
            collapseAll();
        }));

        panel.add(Helper.actionButton(Helper.findIcon("icons/newNode.svg", PluginImpl.class), "新增节点", str -> {
            SwingUtilities.invokeLater(() -> {
                try {
                    String name = JOptionPane.showInputDialog("请输入节点名称");
                    if (name == null || name.trim().isEmpty()) {
                        logger.activeConsolePanel();
                        logger.warn("新增节点,节点名称不能为空");
                        return;
                    }
                    TreePath treePath = tree.getSelectionPath();
                    Data data = new Data();
                    data.setName(name);
                    if (treePath != null) {
                        DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) treePath.getLastPathComponent();
                        Data parentData = (Data) parentNode.getUserObject();
                        List<Data> childrenList = parentData.getChildren();
                        if (childrenList == null) {
                            childrenList = new ArrayList<>();
                            parentData.setChildren(childrenList);
                        } else {
                            parentData.getChildren().add(data);
                        }
                        childrenList.add(data);
                        DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(data);
                        parentNode.add(newNode);
                        treeModel.insertNodeInto(newNode, parentNode, parentNode.getIndex(newNode));
                    } else {
                        datas.add(data);
                        DefaultMutableTreeNode node = new DefaultMutableTreeNode(data);
                        root.add(node);
                        if (root.getChildCount() == 1) {
                            treeModel.reload(root);
                        } else {
                            treeModel.insertNodeInto(node, root, root.getIndex(node));
                        }
                    }

                    DataManager.storeData(datas, locationHash);
                } catch (Throwable err) {
                    logger.error("添加节点失败: " + err);
                }
            });
        }));

        this.add(panel, BorderLayout.NORTH);
    }

    private void collapseAll() {
        for (int i = 0; i < tree.getRowCount(); i++) {
            tree.collapseRow(i);
        }
    }

    private void expandAll() {
        for (int i = 0; i < tree.getRowCount(); i++) {
            tree.expandRow(i);
        }
    }

    public List<Data> getDatas() {
        return datas;
    }
}

内容视图

在这里插入图片描述

package com.lhstack.aaa;

import com.lhstack.aaa.entity.Data;
import com.lhstack.tools.plugins.Logger;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rsyntaxtextarea.TextEditorPane;
import org.fife.ui.rtextarea.RTextScrollPane;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import java.awt.*;
import java.util.List;
import java.util.function.Supplier;

public class ContentView extends JPanel implements DocumentListener, Runnable {

    private final TextEditorPane textEditorPane;
    private final JLabel title;
    private final Logger logger;
    private final String locationHash;
    private final Supplier<List<Data>> datas;
    private Data data;

    public ContentView(String locationHash, NotesView notesView, Logger logger, Supplier<List<Data>> datas) {
        this.setLayout(new BorderLayout());
        this.setBorder(null);
        this.logger = logger;
        this.textEditorPane = initTextEditorPane();
        this.title = new JLabel();
        this.title.setFont(new Font("", Font.PLAIN, 16));
        this.add(title, BorderLayout.NORTH);
        RTextScrollPane rTextScrollPane = new RTextScrollPane(this.textEditorPane);
        rTextScrollPane.setBorder(null);
        this.add(rTextScrollPane, BorderLayout.CENTER);
        this.datas = datas;
        this.locationHash = locationHash;
    }

    private TextEditorPane initTextEditorPane() {
        TextEditorPane pane = new TextEditorPane();
        pane.setTabSize(2);
        pane.setLineWrap(true);
        pane.setHighlightCurrentLine(true);
        pane.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_MARKDOWN);
        pane.setCodeFoldingEnabled(true);
        return pane;
    }


    public void onShow(Data data) {
        this.data = data;
        this.title.setText(data.getName());
        this.title.setHorizontalAlignment(JLabel.CENTER);
        Document document = this.textEditorPane.getDocument();
        document.removeDocumentListener(this);
        this.textEditorPane.setText(data.getText());
        document.addDocumentListener(this);
    }

    @Override
    public void insertUpdate(DocumentEvent e) {
        this.data.setText(textEditorPane.getText());
        DataManager.storeData(datas.get(), locationHash);
    }

    @Override
    public void removeUpdate(DocumentEvent e) {
        this.data.setText(textEditorPane.getText());
        DataManager.storeData(datas.get(), locationHash);
    }

    @Override
    public void changedUpdate(DocumentEvent e) {
        this.data.setText(textEditorPane.getText());
        DataManager.storeData(datas.get(), locationHash);
    }

    @Override
    public void run() {
        this.textEditorPane.resetKeyboardActions();
        this.textEditorPane.clearParsers();
        this.textEditorPane.clearMarkAllHighlights();
    }
}

数据加载,持久化管理

package com.lhstack.aaa;

import com.alibaba.fastjson2.JSON;
import com.lhstack.aaa.entity.Data;
import com.lhstack.tools.plugins.Helper;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;

public class DataManager {

    public static File loadFile(String locationHash) {
        try {
            String path = Helper.getProjectBasePath(locationHash);
            File file = new File(path, ".idea/JTools/notes");
            if (!file.exists()) {
                file.mkdirs();
            }
            File dataFile = new File(file, "data.json");
            if (!dataFile.exists()) {
                dataFile.createNewFile();
            }
            return dataFile;
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static void storeData(List<Data> data, String locationHash) {
        try {
            File file = loadFile(locationHash);
            Files.write(file.toPath(), JSON.toJSONBytes(data));
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static List<Data> loadData(String locationHash) {
        try {
            File file = loadFile(locationHash);
            if(file.length() <= 0){
                return new ArrayList<>();
            }
            byte[] bytes = Files.readAllBytes(file.toPath());
            return JSON.parseArray(new String(bytes, StandardCharsets.UTF_8), Data.class);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}

数据结构定义

package com.lhstack.aaa.entity;

import java.util.List;
import java.util.Objects;

public class Data {

    private String name;

    private String text;

    private List<Data> children;

    public String getName() {
        return name;
    }

    public Data setName(String name) {
        this.name = name;
        return this;
    }

    public String getText() {
        return text;
    }

    public Data setText(String text) {
        this.text = text;
        return this;
    }

    public List<Data> getChildren() {
        return children;
    }

    public Data setChildren(List<Data> children) {
        this.children = children;
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Data data = (Data) o;
        return Objects.equals(name, data.name) && Objects.equals(text, data.text) && Objects.equals(children, data.children);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, text, children);
    }

    @Override
    public String toString() {
        return name;
    }
}

操作说明

  1. 选中节点点击添加,会在当前节点新增节点
  2. 未选中节点添加,则在root节点新增节点
  3. 点击节点外部内容,即可取消选中
    在这里插入图片描述
  4. 双击展开树节点
  5. 三击打开内容面板
    在这里插入图片描述
  6. 右键菜单,如果是树,则会多一个删除目录菜单
  7. ``在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值