Settings tutorial: Change the implementation to follow the recommended approach (a separate state class)

Also:
- remove unnecessary screenshot
- change labels to follow UX guidelines
This commit is contained in:
Karol Lewandowski 2024-06-20 12:42:04 +02:00
parent 2c20002beb
commit f5db8ea180
9 changed files with 92 additions and 84 deletions

View File

@ -4,8 +4,8 @@
## Quickstart
This project illustrates a custom Application-level Settings through the implementation of:
- `AppSettingsConfigurable` is analogous to a Controller in the MVC model - it interacts with the other two Settings classes and the IntelliJ Platform,
- `AppSettingsState` is like a Model because it stores the Settings persistently,
- `AppSettingsConfigurable` is analogous to a Controller in the MVC model it interacts with the other two Settings classes and the IntelliJ Platform,
- `AppSettings` is like a Model because it stores the Settings persistently,
- `AppSettingsComponent` is similar to a View because it displays and captures edits to the values of the Settings.
### Extension Points
@ -13,7 +13,7 @@ This project illustrates a custom Application-level Settings through the impleme
| Name | Implementation | Extension Point Class |
|----------------------------------------|---------------------------------------------------------|----------------------------|
| `com.intellij.applicationConfigurable` | [AppSettingsConfigurable][file:AppSettingsConfigurable] | `Configurable` |
| `com.intellij.applicationService` | [AppSettingsState][file:AppSettingsState] | `PersistentStateComponent` |
| `com.intellij.applicationService` | [AppSettings][file:AppSettings] | `PersistentStateComponent` |
*Reference: [Plugin Extension Points in IntelliJ SDK Docs][docs:ep]*
@ -23,5 +23,4 @@ This project illustrates a custom Application-level Settings through the impleme
[docs:ep]: https://plugins.jetbrains.com/docs/intellij/plugin-extensions.html
[file:AppSettingsConfigurable]: ./src/main/java/org/intellij/sdk/settings/AppSettingsConfigurable.java
[file:AppSettingsState]: ./src/main/java/org/intellij/sdk/settings/AppSettingsState.java
[file:AppSettings]: ./src/main/java/org/intellij/sdk/settings/AppSettings.java

View File

@ -0,0 +1,49 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.sdk.settings;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
/*
* Supports storing the application settings in a persistent way.
* The {@link com.intellij.openapi.components.State State} and {@link Storage}
* annotations define the name of the data and the filename where these persistent
* application settings are stored.
*/
@State(
name = "org.intellij.sdk.settings.AppSettings",
storages = @Storage("SdkSettingsPlugin.xml")
)
final class AppSettings
implements PersistentStateComponent<AppSettings.State> {
static class State {
@NonNls
public String userId = "John Smith";
public boolean ideaStatus = false;
}
private State myState = new State();
static AppSettings getInstance() {
return ApplicationManager.getApplication()
.getService(AppSettings.class);
}
@Override
public State getState() {
return myState;
}
@Override
public void loadState(@NotNull State state) {
myState = state;
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.sdk.settings;
@ -17,11 +17,11 @@ public class AppSettingsComponent {
private final JPanel myMainPanel;
private final JBTextField myUserNameText = new JBTextField();
private final JBCheckBox myIdeaUserStatus = new JBCheckBox("Do you use IntelliJ IDEA? ");
private final JBCheckBox myIdeaUserStatus = new JBCheckBox("IntelliJ IDEA user");
public AppSettingsComponent() {
myMainPanel = FormBuilder.createFormBuilder()
.addLabeledComponent(new JBLabel("Enter user name: "), myUserNameText, 1, false)
.addLabeledComponent(new JBLabel("User name:"), myUserNameText, 1, false)
.addComponent(myIdeaUserStatus, 1)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();

View File

@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Objects;
/**
* Provides controller functionality for application settings.
@ -14,8 +15,8 @@ final class AppSettingsConfigurable implements Configurable {
private AppSettingsComponent mySettingsComponent;
// A default constructor with no arguments is required because this implementation
// is registered in an applicationConfigurable EP
// A default constructor with no arguments is required because
// this implementation is registered as an applicationConfigurable
@Nls(capitalization = Nls.Capitalization.Title)
@Override
@ -37,24 +38,26 @@ final class AppSettingsConfigurable implements Configurable {
@Override
public boolean isModified() {
AppSettingsState settings = AppSettingsState.getInstance();
boolean modified = !mySettingsComponent.getUserNameText().equals(settings.userId);
modified |= mySettingsComponent.getIdeaUserStatus() != settings.ideaStatus;
return modified;
AppSettings.State state =
Objects.requireNonNull(AppSettings.getInstance().getState());
return !mySettingsComponent.getUserNameText().equals(state.userId) ||
mySettingsComponent.getIdeaUserStatus() != state.ideaStatus;
}
@Override
public void apply() {
AppSettingsState settings = AppSettingsState.getInstance();
settings.userId = mySettingsComponent.getUserNameText();
settings.ideaStatus = mySettingsComponent.getIdeaUserStatus();
AppSettings.State state =
Objects.requireNonNull(AppSettings.getInstance().getState());
state.userId = mySettingsComponent.getUserNameText();
state.ideaStatus = mySettingsComponent.getIdeaUserStatus();
}
@Override
public void reset() {
AppSettingsState settings = AppSettingsState.getInstance();
mySettingsComponent.setUserNameText(settings.userId);
mySettingsComponent.setIdeaUserStatus(settings.ideaStatus);
AppSettings.State state =
Objects.requireNonNull(AppSettings.getInstance().getState());
mySettingsComponent.setUserNameText(state.userId);
mySettingsComponent.setIdeaUserStatus(state.ideaStatus);
}
@Override

View File

@ -1,40 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.sdk.settings;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull;
/**
* Supports storing the application settings in a persistent way.
* The {@link State} and {@link Storage} annotations define the name of the data and the file name where
* these persistent application settings are stored.
*/
@State(
name = "org.intellij.sdk.settings.AppSettingsState",
storages = @Storage("SdkSettingsPlugin.xml")
)
final class AppSettingsState implements PersistentStateComponent<AppSettingsState> {
public String userId = "John Q. Public";
public boolean ideaStatus = false;
static AppSettingsState getInstance() {
return ApplicationManager.getApplication().getService(AppSettingsState.class);
}
@Override
public AppSettingsState getState() {
return this;
}
@Override
public void loadState(@NotNull AppSettingsState state) {
XmlSerializerUtil.copyBean(state, this);
}
}

View File

@ -1,5 +1,4 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<idea-plugin>
@ -35,7 +34,7 @@
<applicationConfigurable parentId="tools" instance="org.intellij.sdk.settings.AppSettingsConfigurable"
id="org.intellij.sdk.settings.AppSettingsConfigurable"
displayName="SDK: Application Settings Example"/>
<applicationService serviceImplementation="org.intellij.sdk.settings.AppSettingsState"/>
<applicationService serviceImplementation="org.intellij.sdk.settings.AppSettings"/>
</extensions>
</idea-plugin>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

View File

@ -13,7 +13,7 @@ Custom Settings are displayed and function just like those native to the IDE.
Using the SDK code sample [`settings`](%gh-sdk-samples-master%/settings), this tutorial illustrates the steps to create custom Application-level Settings.
Many IntelliJ Platform Settings implementations use fewer classes, but the `settings` code sample factors the functionality into three classes for clarity:
* The [`AppSettingsConfigurable`](%gh-sdk-samples-master%/settings/src/main/java/org/intellij/sdk/settings/AppSettingsConfigurable.java) is analogous to a Controller in the MVC model - it interacts with the other two Settings classes and the IntelliJ Platform,
* The [`AppSettingsState`](%gh-sdk-samples-master%/settings/src/main/java/org/intellij/sdk/settings/AppSettingsState.java) is like a Model because it stores the Settings persistently,
* The [`AppSettings`](%gh-sdk-samples-master%/settings/src/main/java/org/intellij/sdk/settings/AppSettings.java) is like a Model because it stores the Settings persistently,
* The [`AppSettingsComponent`](%gh-sdk-samples-master%/settings/src/main/java/org/intellij/sdk/settings/AppSettingsComponent.java) is similar to a View because it displays and captures edits to the values of the Settings.
The structure of the implementation is the same for Project Settings, but there are minor differences in the [`Configurable` implementation](settings_guide.md#constructors) and [extension point (EP) declaration](settings_guide.md#declaring-project-settings).
@ -26,12 +26,12 @@ The structure of the implementation is the same for Project Settings, but there
>
{style="note"}
## The `AppSettingsState` Class
## The `AppSettings` Class
The `AppSettingsState` class persistently stores the custom Settings.
The `AppSettings` class persistently stores the custom Settings.
It is based on the [IntelliJ Platform Persistence Model](persisting_state_of_components.md#using-persistentstatecomponent).
### Declaring `AppSettingsState`
### Declaring `AppSettings`
Given a [Light Service](plugin_services.md#light-services) is not used, the persistent data class must be declared as a [Service](plugin_services.md#declaring-a-service) EP in the <path>[plugin.xml](plugin_configuration_file.md)</path> file.
If these were Project Settings, the `com.intellij.projectService` EP would be used.
@ -40,40 +40,40 @@ However, because these are Application Settings, the `com.intellij.applicationSe
```xml
<extensions defaultExtensionNs="com.intellij">
<applicationService
serviceImplementation="org.intellij.sdk.settings.AppSettingsState"/>
serviceImplementation="org.intellij.sdk.settings.AppSettings"/>
</extensions>
```
### Creating the `AppSettingState` Implementation
### Creating the `AppSettings` Implementation
As discussed in [Implementing the PersistentStateComponent Interface](persisting_state_of_components.md#implementing-the-persistentstatecomponent-interface), `AppSettingsState` uses the pattern of implementing [`PersistentStateComponent`](%gh-ic%/platform/projectModel-api/src/com/intellij/openapi/components/PersistentStateComponent.java) itself:
As discussed in [Implementing the PersistentStateComponent Interface](persisting_state_of_components.md#implementing-the-persistentstatecomponent-interface), `AppSettings` uses the pattern of implementing [`PersistentStateComponent`](%gh-ic%/platform/projectModel-api/src/com/intellij/openapi/components/PersistentStateComponent.java) parameterized by a separate state class:
```java
```
{src="settings/src/main/java/org/intellij/sdk/settings/AppSettingsState.java" include-symbol="AppSettingsState"}
{src="settings/src/main/java/org/intellij/sdk/settings/AppSettings.java" include-symbol="AppSettings"}
#### `@Storage` Annotation
The [`@State`](%gh-ic%/platform/projectModel-api/src/com/intellij/openapi/components/State.java) annotation, located just above the class declaration, [defines the data storage location](persisting_state_of_components.md#defining-the-storage-location).
For `AppSettingsState`, the data `name` parameter is the FQN of the class.
For `AppSettings`, the data `name` parameter is the FQN of the class.
Using FQN is the best practice to follow, and is required if custom data gets stored in the standard project or workspace files.
The `storages` parameter utilizes the [`@Storage`](%gh-ic%/platform/projectModel-api/src/com/intellij/openapi/components/Storage.java) annotation to define a custom file name for the `AppSettingsState` data.
The `storages` parameter utilizes the [`@Storage`](%gh-ic%/platform/projectModel-api/src/com/intellij/openapi/components/Storage.java) annotation to define a custom file name for the `AppSettings` data.
In this case, the file is located in the `options` directory of the [configuration directory](https://www.jetbrains.com/help/idea/tuning-the-ide.html#config-directory) for the IDE.
#### Persistent Data Fields
#### Persistent State Class
The `AppSettingState` implementation has two public fields: a `String` and a `boolean`.
Conceptually, these fields hold the name of a user, and whether that person is an IntelliJ IDEA user, respectively.
The `AppSettings` implementation contains an inner state class with two public fields: a `String` and a `boolean`.
Conceptually, these fields hold the name of a user and whether that person is an IntelliJ IDEA user, respectively.
See [Implementing the State Class](persisting_state_of_components.md#implementing-the-state-class) for more information about how `PersistentStateComponent` serializes public fields.
#### `AppSettingState` Methods
#### `AppSettings` Methods
The fields are so limited and straightforward for this class that encapsulation is not used for simplicity.
All that's needed for functionality is to override the two methods called by the IntelliJ Platform when a new component state is loaded (`PersistentStateComponent.loadState()`), and when a state is saved (`PersistentStateComponent.getState()`).
See [`PersistentStateComponent`](%gh-ic%/platform/projectModel-api/src/com/intellij/openapi/components/PersistentStateComponent.java) for more information about these methods.
One static convenience method has been added - `AppSettingState.getInstance()` - which allows `AppSettingsConfigurable` to easily acquire a reference to `AppSettingState`.
One static convenience method has been added - `AppSettings.getInstance()` - which allows `AppSettingsConfigurable` to easily acquire a reference to `AppSetting`.
## The `AppSettingsComponent` Class
@ -83,7 +83,7 @@ The `AppSettingsComponent` is instantiated by `AppSettingsConfigurable`.
### Creating the `AppSettingsComponent` Implementation
The `AppSettingsComponent` defines a `JPanel` containing a [`JBTextField`](%gh-ic%/platform/platform-api/src/com/intellij/ui/components/JBTextField.java) and a [`JBCheckBox`](%gh-ic%/platform/platform-api/src/com/intellij/ui/components/JBCheckBox.java) to hold and display the data that maps to the [data fields](#persistent-data-fields) of `AppSettingsState`:
The `AppSettingsComponent` defines a `JPanel` containing a [`JBTextField`](%gh-ic%/platform/platform-api/src/com/intellij/ui/components/JBTextField.java) and a [`JBCheckBox`](%gh-ic%/platform/platform-api/src/com/intellij/ui/components/JBCheckBox.java) to hold and display the data that maps to the [data fields of `AppSettings.State`](#persistent-state-class):
```java
```
@ -96,7 +96,7 @@ The rest of the class are simple accessors and mutators to encapsulate the UI co
## The `AppSettingsConfigurable` Class
The methods of [`AppSettingsConfigurable`](%gh-sdk-samples-master%/settings/src/main/java/org/intellij/sdk/settings/AppSettingsConfigurable.java) are called by the IntelliJ Platform, and `AppSettingsConfigurable` in turn interacts with `AppSettingsComponent` and `AppSettingState`.
The methods of [`AppSettingsConfigurable`](%gh-sdk-samples-master%/settings/src/main/java/org/intellij/sdk/settings/AppSettingsConfigurable.java) are called by the IntelliJ Platform, and `AppSettingsConfigurable` in turn interacts with `AppSettingsComponent` and `AppSettings`.
### Declaring the `AppSettingsConfigurable`
@ -134,13 +134,11 @@ After performing the steps described above, compile and run the plugin in a Deve
Open the IDE Settings by selecting <ui-path>Settings | Tools | SDK: Application Settings Example</ui-path>.
The settings are preloaded with the default values:
!["Settings Defaults"](settings_defaults.png){width="600"}
!["Settings Defaults"](settings_defaults.png){width="700"}
Now edit the settings values to "John Doe" and click the checkbox.
Click on the <control>OK</control> button to close the Settings dialog and save the changes.
Exit the Development Instance.
Open the file <path>SdkSettingsPlugin.xml</path> to see the Settings persistently stored.
Open the file <path>code_samples/settings/build/idea-sandbox/config/options/SdkSettingsPlugin.xml</path> to see the Settings persistently stored.
In this demonstration the file resides in <path>code_samples/settings/build/idea-sandbox/config/options/</path>, but see [IDE Development Instances](ide_development_instance.md) for the general Development Instance case, or [Default IDE directories](https://www.jetbrains.com/help/idea/tuning-the-ide.html#default-dirs) if you are testing the `settings` plugin directly in an IDE.
!["Persisted Settings"](settings_persisted.png){width="600"}