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

news2024/11/29 10:47:46

介绍

Demo说明

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

JTools插件说明

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

成果展示

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

依赖安装

		<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. ``在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2189158.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手写mybatis之Mapper XML的解析和注册使用

前言 你是怎么面对功能迭代的&#xff1f; 很多程序员在刚开始做编程或者新加入一家公司时&#xff0c;都没有多少机会可以做一个新项目&#xff0c;大部分时候都是在老项目上不断的迭代更新。在这个过程你可能要学习N个前人留下的各式各样的风格迥异的代码片段&#xff0c;在这…

【杂谈一之概率论】CDF、PDF、PMF和PPF概念解释与分析

一、概念解释 1、CDF&#xff1a;累积分布函数&#xff08;cumulative distribution function&#xff09;&#xff0c;又叫做分布函数&#xff0c;是概率密度函数的积分&#xff0c;能完整描述一个实随机变量X的概率分布 2、PDF&#xff1a;连续型概率密度函数&#xff08;p…

平面电磁波的电场能量磁场能量密度相等,能量密度的体积分等于能量,注意电场能量公式也没有复数形式(和坡印廷类似)

1、电场能量密度和磁场能量密度相等(实数场算的) 下面是电场能量密度和磁场能量密度的公式&#xff0c;注意这可不是坡印廷定理。且电场能量密度没有复数表达式&#xff0c;即不是把E和D换成复数形式就行的。注意&#xff0c;一个矢量可以转化为复数形式&#xff0c;两个矢量做…

数据挖掘-padans初步使用

目录标题 Jupyter Notebook安装启动 Pandas快速入门查看数据验证数据建立索引数据选取⚠️注意&#xff1a;排序分组聚合数据转换增加列绘图line 或 **&#xff08;默认&#xff09;&#xff1a;绘制折线图。bar&#xff1a;绘制条形图。barh&#xff1a;绘制水平条形图。hist&…

Discord:报错:A fatal Javascript error occured(解决办法)

按 Windows 键 R 并输入 %appdata% 选择 discord 文件夹并将其删除。 再次按 Windows 键 R 并输入 %LocalAppData% 选择 discord 文件夹并再次将其删除。 附加&#xff1a; 如果还不行&#xff0c;就通过官网下载吧&#xff0c;这个问题通过epic下载可能会有

图文深入理解Oracle DB企业级集中管理神器-GC的安装和部署

值此国庆佳节&#xff0c;深宅家中&#xff0c;闲来无事&#xff0c;就多写几篇博文。今天继续宅继续写。 本文承接上篇&#xff0c;介绍GC的安装和部署。咱们不急&#xff0c;慢慢来&#xff0c;饭要一口一口地吃才能吃得踏实自然。 限于篇幅&#xff0c;本节将重点介绍关键步…

【ubuntu】apt是什么

目录 1.apt简介 2.常用apt指令 2.1安装 2.2更新列表 2.3更新已经安装的软件包 2.4搜索软件包 2.5显示软件包信息 2.6移除软件包 2.7清理无用的安装包 2.8清理无用的依赖项 3.apt和apt-get 3.1区别 3.2 总结 1.apt简介 apt的全称是advanced package …

JAVA的三大特性-封装、继承、多态

Java作为一种面向对象的编程语言&#xff0c;其核心特性包括封装、继承和多态。这三大特性是Java语言的基石&#xff0c;它们相互关联&#xff0c;共同构成了Java强大的面向对象能力。 封装&#xff08;Encapsulation&#xff09; 封装是面向对象编程的一个重要概念&#xff0c…

Pytorch最最适合研究生的入门教程,Q3 开始训练

文章目录 Pytorch最最适合研究生的入门教程Q3 开始训练3.1 训练的见解3.2 Pytorch基本训练框架work Pytorch最最适合研究生的入门教程 Q3 开始训练 3.1 训练的见解 如何理解深度学习能够完成任务&#xff1f; 考虑如下回归问题 由函数 y f ( x ) yf(x) yf(x)采样得到的100个…

现在的新电脑在任务管理器里又多了个NPU?它是啥?

前言 今年中旬各家品牌的新笔记本感觉上都是很不错&#xff0c;搞得小白自己心痒痒&#xff0c;突然间想要真的买一台Windows笔记本来耍耍了。 但今天这个文章并不是什么商品宣传啥的&#xff0c;而是小白稍微尝试了一下新笔记本之后的一些发现。 在今年的新笔记本上都多了一…

【GESP】C++一级练习BCQM3025,输入-计算-输出-6

题型与BCQM3024一样&#xff0c;计算逻辑上稍微复杂了一点点&#xff0c;代码逻辑没变&#xff0c;仍属于小学3&#xff0c;4年级的题目水平。 题解详见&#xff1a;https://www.coderli.com/gesp-1-bcqm3025/ https://www.coderli.com/gesp-1-bcqm3025/https://www.coderli.c…

数据提取之JSON与JsonPATH

第一章 json 一、json简介 json简单说就是javascript中的对象和数组&#xff0c;所以这两种结构就是对象和数组两种结构&#xff0c;通过这两种结构可以表示各种复杂的结构 > 1. 对象&#xff1a;对象在js中表示为{ }括起来的内容&#xff0c;数据结构为 { key&#xff1…

最新版本SkyWalking【10.1.0】部署

这里写目录标题 前言前置条件启动Skywalking下载解压启动说明 集成Skywalking Agent下载Agent在IDEA中添加agent启动应用并访问SpringBoot接口 说明 前言 基于当前最新版10.1.0搭建skywalking 前置条件 装有JDK11版本的环境了解SpringBoot相关知识 启动Skywalking 下载 地…

浑元换算策略和武德换算策略-《分析模式》漫谈36

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的第3章有这么一句&#xff1a; A conversion, however deterministic, does not follow that faithfully. 2004&#xff08;机械工业出版社&#xff09;中译本…

HTB:Explosion[WriteUP]

目录 连接至HTB服务器并启动靶机 1.What does the 3-letter acronym RDP stand for? 2.What is a 3-letter acronym that refers to interaction with the host through a command line interface? 3.What about graphical user interface interactions? 4.What is the…

【MySQL 08】复合查询

目录 1.准备工作 2.多表查询 笛卡尔积 多表查询案例 3. 自连接 4.子查询 1.单行子查询 2.多行子查询 3.多列子查询 4.在from子句中使用子查询 5.合并查询 1.union 2.union all 1.准备工作 如下三个表&#xff0c;将作为示例&#xff0c;理解复合查询 EMP员工表…

深入探究:在双链表的前面进行插入操作的顺序

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd;惟有主动付出&#xff0c;才有丰富的果…

一次解决Go编译问题的经过

用Go语言编写了一个小的项目&#xff0c;项目开发环境是在本地的Windows环境中&#xff0c;一切单元测试和集成测试通过后&#xff0c;计划将项目部署到VPS服务器上自动运行&#xff0c;但在服务器上执行go run运行时&#xff0c;程序没有任何响应和回显&#xff0c;甚至main函…

有没有一款软件,可以在二楼电脑直接唤醒三楼的电脑?

前言 今天有个小姐姐找到我&#xff0c;咨询能不能在二楼的电脑直接访问到三楼电脑的资料。 这个肯定是可以的啊&#xff01; 其实事情很简单&#xff0c;只需要弄好共享文件夹这个功能&#xff0c;只要手机、平板或者电脑在同个局域网下&#xff0c;就能访问到三楼电脑里的…

深入理解Dubbo源码核心原理-Part4

现在开始研究&#xff0c;消费端真正调用proxy的方法时&#xff0c;走的rpc调用 接下来就要走client&#xff0c;发送request请求了 Dubbo协议是怎样的呢&#xff1f; 具体每个字段什么含义请参照官网 链接&#xff1a;Dubbo协议头含义 编码器按照Dubbo协议来进行编码请求 Ne…