- PVSM.RU - https://www.pvsm.ru -
Встала задача создать для разработчиков и QA удобный способ стартовать порядка 20 серверных приложений, живущих в общем репозитрии (Spring с XML конфигурацией и общим для все частей приложения бутстрап классом).
Как сделать нечто удобное человеку, который последний раз GUI рисовал в Borland Delphi 6.0? Взять что-то уже готовое и приспособить для своих нужд, ну и раз уж будущие пользователи работают в IntelliJ Idea, появилась мысль соорудить плагин, который будет выглядеть и вести себя так, как это делает Maven Integration Plugin.
Под катом классы и некоторые утилитарные методы, которые помогут это сделать.
Документацию по плагинам для Idea можно найти тут [1] или в серии статей на хабре [2].
Единственная проблема этой документации в том, что про UI там нет почти ничего, к счастью, есть код [3], а это ведь лучше любой документации! (особенно если у тебя первый разряд по совковой лопате)
Нас, разумеется, интересует папка plugins/maven.
Чтобы добавить свое Tool Window документация предлагает [4] использовать plugin.xml. Но если нужно, например, контролировать время инициализации окна, то придется все это делать вручную:
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);
}
Ничего сложного, если найти пример кода:
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));
}
}
Перед тем, как выращивать дерево, нужно понять где это делать. Ответ очевиден: JPanel (упомянутый выше HolyProjectPanel является подклассом SimpleToolWindowPanel, который наследует от JPanel (яйцо в утке, утка в зайце, заяц в шоке).
Создаем объект JTree и сохраняем его как контент.
UI Maven plugin'a построено на классе SimpleTree, использование его ничем не отличается от JTree, однако оно добавляет полезные фичи, например, поиск на лету:
Наполнение предлагается делать с помощью SimpleTreeBuilder:
SimpleTreeBuilder myTreeBuilder = new SimpleTreeBuilder(tree, (DefaultTreeModel) tree.getModel(), this, null)
Disposer.register(project, myTreeBuilder);
myTreeBuilder.initRoot();
myTreeBuilder.expand(myRoot, null);
Кроме всего прочего, он позволяет сортировать узлы, достаточно просто передать Comparator как последний аргумент конструктора.
Также, часто полезно обновить все узлы, начиная с данного:
myTreeBuilder.addSubtreeToUpdate(node)
По аналогии с SimpleTree и SimpleTreeBuilder будем использовать SimpleNode. Класс этот прост, как три копейки. Всего один метод getChildren, который нужно реализовать и который вызывается каждый раз, когда надо отрисовать узел (при открытии всего окна или при разворачивании поддерева), и несколько доступных полей с очевидными названиями, которые легко найти автодополнением (myName, myClosedIcon и так далее).
Эта кажущаяся простота кончается, когда начинаешь вглядываться в детали.
Класс SimpleNode отрисовывается с помощью объектов класса PresentationData. его и надо использовать:
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);
}
Кроме прочего, этот же объект надо использовать, чтобы перерисовать узел:
@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);
}
Это происходит потому, что все узлы, кроме корневого, на самом деле создаются заново. Чтобы избежать этого используйте класс CachingSimpleNode вместо SimpleNode (Он так же имеет единственный метод, который необходимо реализовать: buildChildren()).
Чтобы вручную перерисовать CachingSimpleNode необходимо вызвать на нем метод update() или update(PresentationData).
Способ добавления кнопок показался мне не вполне очевидным (если создаешь само ToolsWindow в коде — тем более). Они добавляются в файле plugin.xml в разделе Actions, там же настраиваются названия, иконки, тултипы и все остальное.
<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>
Чтобы добавить Enabled/Disabled кнопку, как "Toggle 'Skip Tests' Mode" необходимо использовать ToggleAction вместо AnAction
Это не относится напрямую к разработке панели, но слишком часто возникает необходимость вызвать действие, которое должно выполняться в UI потоке, откуда-то еще. Для этого есть метод:
AppUIUtil.invokeOnEdt(() -> {/*do smth useful*/});
Вот и все, чем я хотел поделиться, пример плагина есть у меня на гитхабе [5].
Удачи!
Автор: Trans00
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/plugins/118325
Ссылки в тексте:
[1] тут: http://www.jetbrains.org/intellij/sdk/docs/index.html
[2] хабре: https://habrahabr.ru/post/187106/
[3] код: https://github.com/JetBrains/intellij-community
[4] предлагает: http://www.jetbrains.org/intellij/sdk/docs/user_interface_components/tool_windows.html
[5] гитхабе: https://github.com/Trans00/HolyProcessIdeaPlugin
[6] Источник: https://habrahabr.ru/post/281851/
Нажмите здесь для печати.