Making the plugin UI in IntelliJ Idea “like maven’s”

Background


The task was to create for developers and QA a convenient way to start about 20 server applications living in a common repository (Spring with XML configuration and a common bootstrap class for all parts of the application).


How to make something convenient for the person who last painted the GUI in Borland Delphi 6.0? Take something ready-made and tailor-made for your needs, well, since future users work in IntelliJ Idea, the idea was to build a plug-in that would look and behave like the Maven Integration Plugin does.


image


There are classes and some utilitarian methods to help you do this.


A few words about documentation


The documentation on plug-ins for Idea can be found here or in a series of articles on Habr .


The only problem with this documentation is that there is almost nothing about the UI, fortunately there is code , which is better than any documentation!(especially if you have the first discharge on a shovel)


Of course, we are interested in the plugins / maven folder .


Tips and tricks


Tool window


To add your Tool Window, the documentation suggests using plugin.xml. But if you need, for example, to control the window initialization time, you will have to do all this manually:


private void initToolWindow() {
        final ToolWindowManagerEx manager = ToolWindowManagerEx.getInstanceEx(myProject);
        ToolWindowEx myToolWindow = (ToolWindowEx) manager.registerToolWindow(HolyProjectPanel.ID, false, ToolWindowAnchor.RIGHT, myProject, true);
        myToolWindow.setIcon(IconLoader.findIcon("/icons/jesterhat.png"));
        final ContentFactory contentFactory = ServiceManager.getService(ContentFactory.class);
        final Content content = contentFactory.createContent(panel, "", false);
        ContentManager contentManager = myToolWindow.getContentManager();
        contentManager.addContent(content);
    }

How to wait while Idea completely initializes the project?


Nothing complicated if you find the sample code:


    public static void runWhenInitialized(final Project project, final Runnable r) {
        if (project.isDisposed()) return;
        if (!project.isInitialized()) {
            StartupManager.getInstance(project).registerPostStartupActivity(DisposeAwareRunnable.create(r, project));
            return;
        }
        runDumbAware(project, r);
    }
   public static void runDumbAware(final Project project, final Runnable r) {
        if (DumbService.isDumbAware(r)) {
            r.run();
        }
        else {
            DumbService.getInstance(project).runWhenSmart(DisposeAwareRunnable.create(r, project));
        }
    }

How to grow a tree


Before you grow a tree, you need to understand where to do it. The answer is obvious: JPanel (the HolyProjectPanel mentioned above is a subclass of SimpleToolWindowPanel that inherits from JPanel(egg in a duck, duck in a hare, hare in shock).
Create a JTree object and save it as content.


The Maven plugin UI is built on the SimpleTree class, using it is no different from JTree, however it adds useful features, for example, on-the-fly search:


image


How to fill a tree


Filling is proposed to be done using SimpleTreeBuilder:


    SimpleTreeBuilder myTreeBuilder = new SimpleTreeBuilder(tree, (DefaultTreeModel) tree.getModel(), this, null)
        Disposer.register(project, myTreeBuilder);
        myTreeBuilder.initRoot();
        myTreeBuilder.expand(myRoot, null);

Among other things, it allows you to sort the nodes, just pass the Comparator as the last argument to the constructor.
Also, it is often useful to update all nodes, starting with this one:


myTreeBuilder.addSubtreeToUpdate(node)

How to fill a tree


By analogy with SimpleTree and SimpleTreeBuilder we will use SimpleNode. This class is as simple as three pennies. Just one getChildren method that needs to be implemented and that is called every time you need to draw a node (when you open the whole window or when the subtree is expanded), and several available fields with obvious names that are easy to find by auto-completion (myName, myClosedIcon, and so on).


This apparent simplicity ends when you begin to peer into the details.


How to write a node name in different colors


image


The SimpleNode class is rendered using the objects of the PresentationData class. it should be used:


    private void updatePresentation() {
        PresentationData presentation = getPresentation();
        presentation.clear();
        presentation.addText(myName, SimpleTextAttributes.REGULAR_ATTRIBUTES);
        presentation.addText(" Red", new SimpleTextAttributes(Font.PLAIN, Color.RED));
        presentation.addText(" Level2Node", SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
    }

Among other things, the same object must be used to redraw the node:


        @Override
        public void handleDoubleClickOrEnter(SimpleTree tree, InputEvent inputEvent) {
            if(Color.RED.equals(myColor)){
                myColor = Color.BLUE;
            } else {
                myColor = Color.RED;
            }
            updatePresentation();
        }
       private void updatePresentation() {
            PresentationData presentation = getPresentation();
            presentation.clear();
            presentation.addText(myName, SimpleTextAttributes.REGULAR_ATTRIBUTES);
            presentation.addText(" Red", new SimpleTextAttributes(Font.PLAIN, myColor));
            presentation.addText(" Level2Node", SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
        }

image


Why does the tree fold every time I close and open the panel?


This is because all nodes except the root node are actually recreated. To avoid this, use the CachingSimpleNode class instead of SimpleNode (It also has a single method that needs to be implemented: buildChildren ()).
To manually redraw the CachingSimpleNode, you must call the update () or update (PresentationData) method on it.


How to add buttons above a tree


image


The way to add buttons seemed to me not quite obvious (if you create the ToolsWindow itself in the code - even more so). They are added in the plugin.xml file in the Actions section, names, icons, tooltips and everything else are also configured there.



To add an Enabled / Disabled button, like "Toggle 'Skip Tests' Mode" you must use ToggleAction instead of AnAction


How to call some action in UI Thread from another thread


This does not directly relate to the development of the panel, but too often there is a need to call an action that must be performed in a UI thread, from somewhere else. There is a method for this:


AppUIUtil.invokeOnEdt(() -> {/*do smth useful*/});

That's all I wanted to share, I have an example of a plugin on a github .


Good luck


Also popular now: