📜 ⬆️ ⬇️

Make a UI plug-in in IntelliJ Idea "like a maven'a"

Prehistory


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


How to make something convenient to the person who last GUI painted in Borland Delphi 6.0? Take something already ready and adapt to their needs, well, since the future users are working in IntelliJ Idea, the idea to build a plugin that looks and behaves the way the Maven Integration Plugin does.


image


Under the cut classes and some utilitarian methods that will help to do this.


A few words about the documentation


Documentation for plugins for Idea can be found here or in a series of articles on habre .


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


We are, of course, interested in the plugins / maven folder.


Tips and tricks


Tool window


To add your Tool Window documentation offers to use 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 until Idea completely initializes the project?


Nothing complicated if you find 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, which inherits from JPanel (egg in duck, duck in hare, hare in shock) .
Create a JTree object and save it as content.


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


image


How to fill a tree


Filling is proposed to do 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, simply pass the Comparator as the last argument to the constructor.
Also, it is often useful to update all nodes starting from this:


 myTreeBuilder.addSubtreeToUpdate(node) 

How to fill a tree


By analogy with SimpleTree and SimpleTreeBuilder, we will use SimpleNode. This class is simple, like a penny. There is only one getChildren method that needs to be implemented and which is called every time you need to draw a node (when opening the entire window or when expanding a subtree), and several available fields with obvious names that are easy to find by auto-completion (myName, myClosedIcon, etc.).


This seeming simplicity ends when you begin to look into the details.


How to write the node name in different colors


image


The SimpleNode class is drawn 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 collapse each time I close and open the panel?


This is because all nodes, except the root, are actually re-created. To avoid this, use the CachingSimpleNode class instead of SimpleNode (It also has the only method you need to implement: buildChildren ()).
To manually redraw CachingSimpleNode, you must call the update () or update (PresentationData) method on it.


How to add buttons above the tree


image


The method of adding buttons seemed not quite obvious to me (if you create ToolsWindow itself in code, all the more). They are added in the plugin.xml file in the Actions section, names, icons, tooltips, and everything else are configured there.


 <actions> <action id="HolyProject.ExpandAll" class="ui.ProcessesTreeAction$ExpandAll" text="Expand All" icon="AllIcons.Actions.Expandall"/> <action id="HolyProject.CollapseAll" class="ui.ProcessesTreeAction$CollapseAll" text="Collapse All" icon="AllIcons.Actions.Collapseall"/> <action class="actions.MyToggleAction" id="HolyProject.RefreshProcesses" icon="AllIcons.Nodes.Cvs_roots" text="Refresh HolyProjectProcesses status" description="Refresh HolyProjectProcesses status"/> <action class="actions.CommonAction" id="HolyProject.RecreateProcesses" icon="AllIcons.ToolbarDecorator.Import" text="Recreate Processes List" description="Recreate Processes List"/> <group id="HolyProject.ProcessesToolbar"> <reference id="HolyProject.RefreshProcesses"/> <reference id="HolyProject.RecreateProcesses"/> <separator/> <reference id="HolyProject.ExpandAll"/> <reference id="HolyProject.CollapseAll"/> </group> </actions> 

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


How to trigger an action in a UI Thread from another thread


This is not directly related to the design of the panel, but too often there is a need to trigger an action that must be performed in the UI stream, 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 the githaba .


Good luck!


')

Source: https://habr.com/ru/post/281851/


All Articles