Разработка плагина IntelliJ IDEA. Часть 7

Original author: JetBrains
  • Translation
В этой части: компоненты пользовательского интерфейса. Предыдущая часть тут.


IntelliJ IDEA включает в себя большое количество пользовательских Swing-компонентов. Использование этих компонентов в ваших плагинах гарантирует, что они будут выглядеть и работать согласовано с остальным пользовательским интерфейсом IDE и часто позволяет уменьшить размер кода, по сравнению с использованием стандартных Swing-компонентов.

Меню и панели инструментов


Меню и панели инструментов (тулбары) строятся с использованием системы действий (как уже было описано во второй части).

Окна инструментов


Окна инструментов — это панели, которые отображаются слева, снизу и справа от главного окна IntelliJ IDEA. На каждой стороне содержится по две группы окон инструментов — первичное и вторичное, причем в один и тот же момент активной может быть только одна группа.

Каждое окно инструментов может содержать несколько вкладок (в API называются «contents»). Например, toolwindow «Run» показывает вкладку для каждой активной конфигурации запуска, а «Changes» отображает фиксированный набор вкладок в зависимости от системы управления версиями, используемой в проекте.

Существует два основных сценария для использования окон инструментов в плагинах. В первом сценарии (используется, например, в плагинах Ant и Commander) кнопка окна инструментов отображается всегда, следовательно пользователь может активировать и взаимодействовать с функциональностью плагина в любое время. Во втором сценарии (используется действием «Analyze Dependencies») окно инструментов создается для того, чтобы показать результаты конкретной операции и может быть закрыто пользователем сразу после завершения операции.

В первом сценарии окно регистрируется в файле plugin.xml, используя точку расширения
<<toolWindow>>
настроек. Атрибуты точки расширения содержат данные, которые используются для отображения кнопки toolwindow:
  • Атрибут «id» (обязательный) — соответствует тексту, отображаемому на кнопке toolwindow;
  • Атрибут «anchor» (обязательный) — задает сторону экрана, к которой привязывается окно инструментов («left», «right» или «bottom»);
  • Логический атрибут «secondary» (необязательный) — указывает, будет ли окно инструментов принадлежать вторичной группе;
  • «icon» (необязательный) — иконка, которая будет отображаться на кнопке (13 x 13 пикселей).

В дополнение к этому следует указать фабричный класс (в атрибуте «factoryClass»), реализующий интерфейс ToolWindowFactory. Когда пользователь кликает на кнопке окна инструмента, вызывается метод createToolWindowContent() и инициализирует пользовательский интерфейс. Эта процедура гарантирует, что неиспользуемые окна инструментов не вызывают каких-либо накладных расходов в использовании памяти или времени запуска: если пользователь не взаимодействует с toolwindow вашего плагина, то никакой код не будет загружен или выполнен.

Если окно инструментов не требуется для всех типов проектов, можно указать необязательный атрибут conditionClass, содержащий полное имя класса, реализующего интерфейс Condition (это может быть тот же класс, что реализует фабрику). Если метод value() возвращает «false», окно инструментов не будет отображаться. Обратите внимание, что условие вычисляется только один раз при загрузке проекта. Если вы хотите показывать и скрывать окна инструментов динамически, во время работы с проектом, вам нужно использовать второй сценарий регистрации окна инструментов.

Пример конфигурации
<extensions defaultExtensionNs="com.intellij">
     <toolWindow id="My Sample Tool Window" icon="/myPackage/icon.png" anchor="right" factoryClass="myPackage.MyToolWindowFactory" />
 </extensions>

Второй сценарий заключается в обычном вызове метода ToolWindowManager.registerToolWindow() из кода плагина. Этот метод имеет несколько перегрузок, которые могут использоваться в зависимости от ваших задач. Если вы используете перегрузку, которая принимает Swing-компонент, то он становится первой вкладкой, отображаемой в окне инструмента.

Отображение содержимого многих toolwindows требует получение доступа к индексам. В силу этого окна выключаются при построении индексов, но оно останется активным, если вы передадите «true» в качестве значения параметра canWorkInDumbMode функции registerToolWindow().

Как упоминалось ранее, окна инструментов могут содержать несколько вкладок. Для управлением содержимым окна, вы можете вызвать ToolWindow.getContentManager(). Чтобы добавить вкладку («content»), необходимо сначала создать ее путем вызова ContentManager.getFactory().createContent(), а затем добавить в окно инструментов, с помощью ContentManager.addContent().

Вы можете определить, разрешено ли пользователю закрывать вкладки, или глобально, или для каждой в отдельности. Последнее осуществляется путем определения параметра canCloseContents функции registerToolWindow(), или указав
<canCloseContents="true">
в файле plugin.xml. Даже если закрытие вкладок разрешено глобально, его можно отключить для конкретной вкладки путем вызова
<Content.setCloseable(false)>
.

Диалоги


DialogWrapper — является базовым классом для всех модальных (и некоторых не модальных) диалоговых окон, использующихся в плагине IntelliJ IDEA. Он предоставляет следующие возможности:
  • Расположение кнопок (платформо-специфичный порядок кнопок «ОК/Отмена», Mac OS-специфичная кнопка «Справка»);
  • Контекстная помощь;
  • Запоминание размера диалогового окна;
  • Валидация данных (и отображение сообщения об ошибке, если данные, введенные в диалоговом окне некорректные);
  • Сочетания клавиш: Esc для закрытия диалогового окна, влево/вправо для переключения между кнопками и Y/N для ответов «Да/нет» если они присутствуют в диалоговом окне;
  • Необязательный флажок «Do not ask again».

При использовании класса DialogWrapper для собственного диалогового окна, необходимо выполнить следующие действия:
  • Вызвать конструктор базового класса и передать проект в рамках которого будет отображаться диалоговое окно или родительский компонент для диалогового окна;
  • Вызвать метод init() из конструктора класса диалогового окна;
  • Вызвать метод setTitle(), чтобы задать заголовок для диалогового окна;
  • Реализовать метод createCenterPanel() для возврата компонента, отвечающего за основное содержание диалогового окна;
  • Необязательно: Переопределите метод getPreferredFocusedComponent(), возвращающий компонент, который окажется в фокусе, когда диалоговое окно отобразится;
  • Дополнительно: Переопределите метод getDimensionServiceKey() для определения идентификатора, который будет использоваться для сохранения размера диалогового окна;
  • Дополнительно: Переопределите метод getHelpId() для задания контекста справки, связанного с диалоговым окном.

Класс DialogWrapper часто используется совместно с формами UI Designer. Для того чтобы связать форму и ваш класс, расширяющий DialogWrapper, привяжите корневую панель формы к полю и возвратите его из метода createCenterPanel().

Для отображения диалогового окна, вызовите метод show() и затем используйте метод getExitCode() для проверки, как было закрыто диалоговое окно.

Для настройки кнопок, отображаемых в диалоговом окне (т.е. замены стандартного набора кнопок ОК/Отмена/Помощь), можно переопределить методы createActions() или createLeftActions(). Оба этих метода возвращают массив объектов Swing Action. Если кнопка, которую вы добавляете закрывает диалоговое окно, можно использовать DialogWrapperExitAction в качестве базового класса для действия.

Чтобы проверить данные, введенные в диалоговом окне, можно переопределить метод doValidate(). Метод будет вызываться автоматически по таймеру. Если в настоящее время введенные данные допустимы, вам нужно возвратить null. В противном случае, возвратите объект ValidationInfo, который инкапсулирует сообщение об ошибке и, дополнительно, компонент, связанный с неправильными данными. Если указать компонент, рядом с ним будет отображаться значок ошибки и он получит фокус, когда пользователь попытается нажать кнопку ОК.

Всплывающие окна


Пользовательский интерфейс IntelliJ IDEA широко использует всплывающие окна — полу-модальные окна, которые не имеют кнопки явного закрытия и автоматически исчезают при потере фокуса. Использование этих элементов управления в вашем плагине обеспечивает единый пользовательский интерфейс между плагином и остальной частью интегрированной среды разработки.

Всплывающие окна могут иметь заголовок, при необходимости могут быть перемещаемыми и позволять изменение размера (поддерживается запоминание размера), они могут быть вложенными (разрешено показывать дополнительные всплывающее окна когда выбран элемент).

Интерфейс JBPopupFactory позволяет вам создавать всплывающие окна, отображающие различные виды Swing-компонентов, в зависимости от ваших конкретных потребностей. Наиболее часто используемыми методами являются:
  • createComponentPopupBuilder() — наиболее универсальный способ показать любой Swing-компонента внутри popup.
  • createListPopupBuilder() — создает всплывающее окно для выбора одного или нескольких элементов из Swing JList.
  • createConfirmation() — создает всплывающее окно для выбора между двумя вариантами и выполнения различных действий в зависимости от выбранного.
  • createActionGroupPopup() — создает popup, который показывает группу действий и выполняет то, которое выбрано пользователем.

Всплывающие окна для группы действий поддерживают различные способы выбора действия с помощью клавиатуры, в дополнение к обычным клавишам со стрелками. Передавая значение одной из констант в перечисление ActionSelectionAid, вы можете выбрать можно ли будет выбрать действие, нажав цифровую клавишу, соответствующую его порядковому номеру, введя часть его текста (ускоренный поиск) или нажав символ-мнемонику. Для всплывающих окон с фиксированным набором элементов рекомендуется выбрать метод порядковой нумерации; для всплывающих окон с переменным и потенциально большим количеством элементов — ускоренный поиск, как правило, работает лучше всего.

Если вам нужно создать списочный всплывающий диалог, но обычный JList не устраивает — например, нежелательно собирать действия в группу, вы можете работать непосредственно с интерфейсом ListPopupStep и методом JBPopupFactory.createListPopup(). Обычно не требуется реализовывать весь интерфейс, вместо этого можно унаследоваться от класса BaseListPopupStep. Основные методы для переопределения это: getTextFor() (возвращается текст, отображаемый для элемента) и onChosen() (вызывается при выборе элемента). Возвращая новый PopupStep из метода onChosen(), можно реализовать иерархические (вложенные) всплывающие окна.

После того как вы создали окно, нужно отобразить его путем вызова одного из методов show(). Вы можете позволить IntelliJ IDEA автоматически выбрать позицию на основе контекста, путем вызова showInBestPositionFor(), или явно указать позицию через такие методы как showUnderneathOf() и showInCenterOf(). Обратите внимание, что методы show() немедленно возвращают управление, а не ждут, когда всплывающее окно закроется. Если вам нужно выполнить некоторые действия при закрытии окна, то можно привязать к нему слушателя с помощью метода addListener(); или переопределить метод, подобный PopupStep.onChosen(); или присоединить обработчик событий к соответствующему компоненту внутри всплывающего окна.

Уведомления


Один из ведущих принципов дизайна в последних версиях IntelliJ IDEA является предотвращение использования модальных окон для уведомления пользователя об ошибках и других ситуациях, которые могут потребовать его внимания. В качестве замены IntelliJ IDEA предоставляет на выбор несколько немодальных типов уведомлений.

Диалоги

При работе с модальным диалоговым окном, вместо проверки введенных данных при нажатии на кнопку ОК и уведомления пользователя о недопустимых данных в еще одном модальном окне, рекомендуется использовать метод DialogBuilder.doValidate(), который был описан ранее.

Подсказки редактора

Для действий, вызываемых из редактора (например, рефакторинг, навигации и тому подобное) лучший способ уведомления пользователя о невозможности выполнения действий — это использование класса HintManager. Его метод showErrorHint() отображает плавающее над редактором окно, которое скрывается автоматически, когда пользователь начинает выполнение другого действия в редакторе. Остальные методы HintManager можно использоваться для отображения прочих видов немодальных уведомлений в виде подсказок над редактором.

Высокоуровневые уведомления

Наиболее общий способ для отображения немодальных уведомлений является использование класса Notifications. Он имеет два основных преимущества:
  • пользователь может управлять каждый типом уведомлений, перечисленных в разделе «Settings | Notifications»;
  • все отображенные уведомления, собираются в окне инструментов «Event Log» и могут быть просмотрены позднее.

Главный метод, используемый для отображения уведомления — это Notifications.Bus.notify(). Текст уведомления могут содержать HTML-теги. Можно разрешить пользователю взаимодействовать с уведомлением, включив гиперссылки и передав экземпляр NotificationListener в конструктор класса Notification.

Параметр GroupDisplayId конструктора Notication указывает тип уведомления; пользователь может выбрать тип отображения, соответствующий типу каждого уведомления в разделе Settings | Notifications. Чтобы указать предпочтительный тип, необходимо вызвать Notifications.Bus.register() перед показом уведомления.

Выбор классов и файлов


Выбор файла

Чтобы позволить пользователю выбрать файл, каталог или несколько файлов, используйте метод FileChooser.chooseFiles(). Этот метод имеет несколько перегрузок, одна из которых возвращает void и получает функцию обратного вызова, принимающую список выбранных файлов в качестве параметра. Это единственная перегрузка, которая будет отображать родное диалоговое окно на Mac OS X.

Класс FileChooserDescriptor позволяет вам проконтролировать, какие файлы могут быть выбраны. Параметры конструктора определяют можно ли выбрать файлы и/или каталоги, допускается ли выбор нескольких элементов. Для более точного контроля можно перегрузить метод isFileSelectable(). Вы также можете настроить отображение файлов путем перегрузки методов getIcon(), getName() и getComment() класса FileChooserDescriptor. Обратите внимание, что родной диалог выбора файлов Mac OS X не поддерживает большую часть настроек, так что если вы полагаетесь на них, необходимо использовать перегрузку метода chooseFiles(), которая отображает стандартное диалоговое окно IntelliJ IDEA.

Очень распространенный способ выбора файла — использовать текстовое поле для ввода пути с многоточием ("...") для отображения диалога выбора файлов. Для создания такого элемента управления, используйте компонент TextFieldWithBrowseButton и вызовите его метод addBrowseFolderListener(), чтобы настроить средство выбора файлов. В качестве дополнительного бонуса это даст возможность автозавершения имени файла при вводе пути в текстовом поле вручную.

Альтернативный UI для выбора файлов, который идеально подходит, когда необходим поиск файла по названию, доступен через класс TreeFileChooserFactory. Диалоговое окно, используемое этим API имеет две вкладки: одна показывает структуру проекта, другая — список файлов, аналогичный используемому в «Goto File». Для отображения диалогового окна, вызовите showDialog() на объекте, возвращенном из createFileChooser() и затем вызовите getSelectedFile() для получения пользовательского выбора.

Выбор класса и пакета

Если вы хотите предложить пользователю возможность выбора класса Java, можно использовать класс TreeClassChooserFactory. Его различные методы позволяют указать область поиска, чтобы ограничить выбор потомками определенного класса или реализациями интерфейса, а также включить или исключить внутренние классы из списка.

Для выбора пакета Java, можно использовать класс PackageChooserDialog.

Компоненты редактора


По сравнению со Swing JTextArea, компонент редактора IntelliJ IDEA имеет массу преимуществ — подсветка синтаксиса, автозавершение кода, сворачивание кода и многое другое. Редакторы в IntelliJ IDEA обычно отображаются в виде вкладок редактора, но они могут быть внедрены в диалоговые окна или окна инструментов. Это позволяет компонент EditorTextField.

При создании EditorTextField, можно указать следующие атрибуты:
  • Тип файла, согласно которому выполняется синтаксический анализ текста в поле;
  • Будет ли текстовое поле только для чтения;
  • Является ли текстовое поле однострочным или многострочным.

Одним из распространенных вариантов использования для EditorTextField является редактирование имени Java-класса или пакета. Это может быть достигнуто с помощью следующих шагов:
  • Используйте JavaCodeFragmentFactory.getInstance().createReferenceCodeFragment() для создания фрагмента кода, представляющего имя класса или пакета;
  • Вызовите PsiDocumentManager.getInstance().getDocument() чтобы получить документ, соответствующий фрагменту кода;
  • Передайте полученный документ в конструктор EditorTextField или его метод setDocument().

Списки и деревья


JBList и Tree

Всякий раз, когда вы планируете задействовать стандартный Swing компонент JList, рассмотрите возможность использования его альтернативы JBList, который поддерживает следующие дополнительные функции:
  • Отрисовка всплывающей подсказки, содержащей полный текст элемента, если он не помещается в поле по ширине;
  • Отрисовка серого текстового сообщение посередине списка, когда он не содержит элементов (текст можно настроить путем вызова getEmptyText().setText());
  • Отрисовка иконки «Занят» в правом верхнем углу списка, когда выполняется фоновая операция (включается вызовом setPaintBusy()).

Аналогично JBList, существует класс com.intellij.ui.treeStructure.Tree предоставляющий собой замену для стандартного класса JTree. В дополнение к функциям JBList он поддерживает отрисовку в стиле Mac и авто-прокрутку для drag & drop.

ColoredListCellRenderer и ColoredTreeCellRenderer

Когда вам нужно настроить представление элементов в поле со списком или дереве, рекомендуется использовать классы ColoredListCellRenderer или ColoredTreeCellRenderer в качестве средства визуализации ячеек. Эти классы позволяют собирать представления из нескольких фрагментов текста с разными атрибутами (посредством вызова append()) и применять дополнительный значок для элемента (путем вызова setIcon()). Система визуализации автоматически берет на себя настройку правильного цвета текста для выбранных элементов и многие другие платформозависимые детали рендеринга.

ListSpeedSearch и TreeSpeedSearch

Для облегчения выбора элементов с помощью клавиатуры в списке или дереве, к ним можно применить обработчик быстрого поиска. Это можно сделать простым вызовом
<new ListSpeedSeach(list)>
или
<new TreeSpeedSearch(tree)>
. Если вам нужно настроить текст, который используется для поиска элемента, можно переопределить метод getElementText(). Кроме того, можно передать функцию для преобразования элементов в строки (как elementTextDelegate в конструктор ListSpeedSearch или как метод toString() в конструктор TreeSpeedSearch).

ToolbarDecorator

Весьма распространенная задача при разработке плагина — отображение списка или дерева, где пользователь мог бы бы добавлять, удалять, редактировать или переупорядочить элементы. Осуществление этой задачи значительно облегчаются классом ToolbarDecorator. Этот класс содержит панель инструментов с действиями, привязанными к элементам и автоматически позволяет drag & drop изменение порядка элементов в списках, если это поддерживает нижележащая модель списка. Положение панели инструментов (выше или ниже списка) зависит от платформы, на которой выполняется IntelliJ IDEA.

Чтобы использовать декоратор панели инструментов:
  • Если вам нужна поддержка удаления и изменения порядка элементов в списке, убедитесь, что модель вашего списка реализует интерфейс EditableModel. CollectionListModel — это удобный класс, реализующий этот интерфейс.
  • Вызовите ToolbarDecorator.createDecorator для создания экземпляра декоратора.
  • Если вам нужна поддержка добавления и/или удаления элементов, вызовите setAddAction() и/или setRemoveAction().
  • Если вам нужны другие кнопки в дополнение к стандартным, вызовите addExtraAction() или setActionGroup().
  • Вызовите createPanel() и добавьте возвращенный компонент к вашей панели.

Прочие Swing-компоненты


Сообщения

Класс Messages предоставляет способ показать простое сообщение, диалог ввода (модальный диалог с текстовым поле) и диалог выбора (модальное диалоговое окно со списком). Функции различных методов класса должно быть ясны из их названия. При запуске на Mac OS X окна сообщений используют родной интерфейс.

Функция showCheckboxMessageDialog() обеспечивает простой способ реализации флажка «Do not show this again».

Рекомендуется использовать немодальные уведомления вместо модальных окон сообщений, всякий раз, когда это уместно. Уведомления были рассмотрены в одном из предыдущих разделов.

JBSplitter

Класс JBSplitter является заменой для стандартного класса JSplitPane. В отличие от некоторых других, улучшенных JetBrains Swing-компонентов это не равноценная замена, т.к. он имеет другой API. Однако, чтобы добиться единообразия в пользовательском интерфейсе, рекомендуется использовать JBSplitter вместо стандартного JSplitPane.
Чтобы добавить компоненты в разделитель, вызовите методы setFirstComponent() и setSecondComponent().

JBSplitter поддерживает автоматическое запоминание пропорции. Для того, чтобы включить его, вызовите метод setSplitterProportionKey() и передайте идентификатор, под которым будет храниться пропорция.

JBTabs

Класс JBTabs является реализацией вкладок от JetBrains, используемой в редакторе и некоторых других компонентах. Она имеет значительно другой look & feel, по сравнению со стандартными вкладками Swing и выглядит более инородно на платформе Mac OS X, так что оставляем за вами выбор, какая реализация вкладок будет более подходящей для вашего плагина.

Продолжение следует...

Все статьи цикла: 1, 2, 3, 4, 5, 6, 7.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 3

    +5
    Я думаю к 30 части будет завершено создание собственной IntelliJ IDEA
      +2
      Мне кажется, вы могли бы назвать статьи более разумным образом. Название разработка плагина для идеи можно было бы дать лишь первой статье. Остальные статьи можно было бы назвать более адекватно, эту, например, утилиты для swing компонентов в идее.
        0
        А вы не думали вынести эти статьи в индексированный справочник?
        Думаю, если Вы осилите это дело, Ваш ресурс будет очень и очень популярным. :-)
        P.S. Хотелось бы еще разделения между официальным API и особенностями реализации на текущий момент, не входящими в официальный API. Для написания кросс-версионных плагинов это достаточно важный пункт.

        Only users with full accounts can post comments. Log in, please.