Skip to content

Commit 248600b

Browse files
authored
[Property Editor] Enable Property Editor panel (#8108)
1 parent ec2ec2b commit 248600b

File tree

9 files changed

+216
-57
lines changed

9 files changed

+216
-57
lines changed

flutter-idea/src/io/flutter/dart/DartPlugin.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
package io.flutter.dart;
77

88
import com.intellij.execution.configurations.ConfigurationType;
9+
import com.intellij.ide.plugins.IdeaPluginDescriptor;
10+
import com.intellij.ide.plugins.PluginManagerCore;
11+
import com.intellij.openapi.extensions.PluginId;
912
import com.intellij.openapi.module.Module;
1013
import com.intellij.openapi.project.Project;
1114
import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService;
@@ -61,6 +64,12 @@ public static boolean isDartTestConfiguration(@NotNull ConfigurationType type) {
6164
return type.getId().equals("DartTestRunConfigurationType");
6265
}
6366

67+
public static DartPluginVersion getDartPluginVersion() {
68+
final IdeaPluginDescriptor dartPlugin = PluginManagerCore.getPlugin(PluginId.getId("Dart"));
69+
final String versionString = dartPlugin.getVersion();
70+
return new DartPluginVersion(versionString);
71+
}
72+
6473
/**
6574
* Return the {@link DartAnalysisServerService} instance for the passed {@link Project}.
6675
*/
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025 The Chromium Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style license that can be
4+
* found in the LICENSE file.
5+
*/
6+
package io.flutter.dart;
7+
8+
import com.intellij.openapi.util.Version;
9+
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.Nullable;
11+
12+
public class DartPluginVersion implements Comparable<DartPluginVersion> {
13+
14+
@Nullable
15+
private final String rawVersionString;
16+
17+
@Nullable
18+
private final Version version;
19+
20+
public DartPluginVersion(@Nullable String versionString) {
21+
rawVersionString = versionString;
22+
version = Version.parseVersion(versionString);
23+
}
24+
25+
@Override
26+
public int compareTo(@NotNull DartPluginVersion otherVersion) {
27+
if (rawVersionString == null) return -1;
28+
if (otherVersion.rawVersionString == null) return 1;
29+
return version.compareTo(otherVersion.version);
30+
}
31+
32+
public boolean supportsPropertyEditor() {
33+
if (version == null) return false;
34+
final int major = version.major;
35+
36+
if (major == 243) {
37+
return this.compareTo(new DartPluginVersion("243.26753.1")) >= 0;
38+
}
39+
40+
if (major == 251) {
41+
return this.compareTo(new DartPluginVersion("251.23774.318")) >= 0;
42+
}
43+
44+
return major >= 244;
45+
}
46+
}

flutter-idea/src/io/flutter/propertyeditor/PropertyEditorViewFactory.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@
1212
import io.flutter.FlutterUtils;
1313
import io.flutter.actions.RefreshToolWindowAction;
1414
import io.flutter.bazel.WorkspaceCache;
15+
import io.flutter.dart.DartPlugin;
16+
import io.flutter.dart.DartPluginVersion;
1517
import io.flutter.devtools.DevToolsIdeFeature;
1618
import io.flutter.devtools.DevToolsUrl;
1719
import io.flutter.run.daemon.DevToolsService;
1820
import io.flutter.sdk.FlutterSdk;
1921
import io.flutter.sdk.FlutterSdkVersion;
2022
import io.flutter.utils.AsyncUtils;
23+
import io.flutter.view.ViewUtils;
2124
import kotlin.coroutines.Continuation;
2225
import org.jetbrains.annotations.NotNull;
2326

@@ -27,6 +30,9 @@
2730
public class PropertyEditorViewFactory implements ToolWindowFactory {
2831
@NotNull private static String TOOL_WINDOW_ID = "Flutter Property Editor";
2932

33+
@NotNull
34+
private final ViewUtils viewUtils = new ViewUtils();
35+
3036
@Override
3137
public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) {
3238
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
@@ -39,6 +45,12 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo
3945
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
4046
FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion();
4147

48+
DartPluginVersion dartPluginVersion = DartPlugin.getDartPluginVersion();
49+
if (!dartPluginVersion.supportsPropertyEditor()) {
50+
viewUtils.presentLabel(toolWindow, "Flutter Property Editor requires a newer version of the Dart plugin.");
51+
return;
52+
}
53+
4254
AsyncUtils.whenCompleteUiThread(
4355
DevToolsService.getInstance(project).getDevToolsInstance(),
4456
(instance, error) -> {

flutter-idea/src/io/flutter/sdk/FlutterSdkVersion.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public final class FlutterSdkVersion implements Comparable<FlutterSdkVersion> {
4747
private static final FlutterSdkVersion MIN_SUPPORTS_DTD = new FlutterSdkVersion("3.22.0");
4848

4949
@NotNull
50-
private static final FlutterSdkVersion MIN_SUPPORTS_PROPERTY_EDITOR = new FlutterSdkVersion("3.29.0");
50+
public static final FlutterSdkVersion MIN_SUPPORTS_PROPERTY_EDITOR = new FlutterSdkVersion("3.32.0-0.1.pre");
5151

5252
@Nullable
5353
private final Version version;

flutter-idea/src/io/flutter/view/FlutterView.java

Lines changed: 15 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ public class FlutterView implements PersistentStateComponent<FlutterViewState>,
8686
@NotNull
8787
private final FlutterViewState state = new FlutterViewState();
8888

89+
@VisibleForTesting
90+
@NotNull
91+
public final ViewUtils viewUtils;
92+
8993
@NotNull
9094
private final Project myProject;
9195

@@ -97,15 +101,16 @@ public class FlutterView implements PersistentStateComponent<FlutterViewState>,
97101
private final JxBrowserManager jxBrowserManager;
98102

99103
public FlutterView(@NotNull Project project) {
100-
this(project, JxBrowserManager.getInstance(), new JxBrowserUtils());
104+
this(project, JxBrowserManager.getInstance(), new JxBrowserUtils(), new ViewUtils());
101105
}
102106

103107
@VisibleForTesting
104108
@NonInjectable
105-
protected FlutterView(@NotNull Project project, @NotNull JxBrowserManager jxBrowserManager, JxBrowserUtils jxBrowserUtils) {
109+
protected FlutterView(@NotNull Project project, @NotNull JxBrowserManager jxBrowserManager, JxBrowserUtils jxBrowserUtils, ViewUtils viewUtils) {
106110
myProject = project;
107111
this.jxBrowserUtils = jxBrowserUtils;
108112
this.jxBrowserManager = jxBrowserManager;
113+
this.viewUtils = viewUtils != null ? viewUtils : new ViewUtils();
109114

110115
shouldAutoHorizontalScroll.listen(state::setShouldAutoScroll);
111116
highlightNodesShownInBothTrees.listen(state::setHighlightNodesShownInBothTrees);
@@ -194,7 +199,7 @@ private void addBrowserInspectorViewContent(FlutterApp app,
194199
new LabelInput("The embedded browser failed to load. Error: " + error),
195200
openDevToolsLabel(app, toolWindow, ideFeature)
196201
);
197-
presentClickableLabel(toolWindow, inputs);
202+
viewUtils.presentClickableLabel(toolWindow, inputs);
198203
});
199204
}));
200205
};
@@ -217,7 +222,7 @@ private void addBrowserInspectorViewContent(FlutterApp app,
217222
.getUrlString(),
218223
null
219224
);
220-
presentLabel(toolWindow, "DevTools inspector has been opened in the browser.");
225+
viewUtils.presentLabel(toolWindow, "DevTools inspector has been opened in the browser.");
221226
}
222227
}
223228

@@ -247,7 +252,7 @@ private void presentDevTools(FlutterApp app, ToolWindow toolWindow, boolean isEm
247252
verifyEventDispatchThread();
248253

249254
devToolsInstallCount += 1;
250-
presentLabel(toolWindow, getInstallingDevtoolsLabel());
255+
viewUtils.presentLabel(toolWindow, getInstallingDevtoolsLabel());
251256

252257
openInspectorWithDevTools(app, toolWindow, isEmbedded, ideFeature);
253258

@@ -266,7 +271,7 @@ protected void setUpToolWindowListener(FlutterApp app, ToolWindow toolWindow, bo
266271
}
267272
this.toolWindowListener.updateOnWindowOpen(() -> {
268273
devToolsInstallCount += 1;
269-
presentLabel(toolWindow, getInstallingDevtoolsLabel());
274+
viewUtils.presentLabel(toolWindow, getInstallingDevtoolsLabel());
270275
openInspectorWithDevTools(app, toolWindow, isEmbedded, ideFeature, true);
271276
});
272277
}
@@ -299,12 +304,12 @@ private void openInspectorWithDevTools(FlutterApp app,
299304
// TODO(helinx): Restart DevTools server if there's an error.
300305
if (error != null) {
301306
LOG.error(error);
302-
presentLabel(toolWindow, DEVTOOLS_FAILED_LABEL);
307+
viewUtils.presentLabel(toolWindow, DEVTOOLS_FAILED_LABEL);
303308
return;
304309
}
305310

306311
if (instance == null) {
307-
presentLabel(toolWindow, DEVTOOLS_FAILED_LABEL);
312+
viewUtils.presentLabel(toolWindow, DEVTOOLS_FAILED_LABEL);
308313
return;
309314
}
310315

@@ -402,47 +407,16 @@ else if (latestFailureReason != null && Objects.equals(latestFailureReason.failu
402407
inputs.add(openDevToolsLabel);
403408
}
404409

405-
presentClickableLabel(toolWindow, inputs);
406-
}
407-
408-
protected void presentLabel(ToolWindow toolWindow, String text) {
409-
final JBLabel label = new JBLabel(text, SwingConstants.CENTER);
410-
label.setForeground(UIUtil.getLabelDisabledForeground());
411-
replacePanelLabel(toolWindow, label);
412-
}
413-
414-
protected void presentClickableLabel(ToolWindow toolWindow, List<LabelInput> labels) {
415-
final JPanel panel = new JPanel(new GridLayout(0, 1));
416-
417-
for (LabelInput input : labels) {
418-
if (input.listener == null) {
419-
final JLabel descriptionLabel = new JLabel("<html>" + input.text + "</html>");
420-
descriptionLabel.setBorder(JBUI.Borders.empty(5));
421-
descriptionLabel.setHorizontalAlignment(SwingConstants.CENTER);
422-
panel.add(descriptionLabel, BorderLayout.NORTH);
423-
}
424-
else {
425-
final LinkLabel<String> linkLabel = new LinkLabel<>("<html>" + input.text + "</html>", null);
426-
linkLabel.setBorder(JBUI.Borders.empty(5));
427-
linkLabel.setListener(input.listener, null);
428-
linkLabel.setHorizontalAlignment(SwingConstants.CENTER);
429-
panel.add(linkLabel, BorderLayout.SOUTH);
430-
}
431-
}
432-
433-
final JPanel center = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.CENTER));
434-
center.add(panel);
435-
replacePanelLabel(toolWindow, center);
410+
viewUtils.presentClickableLabel(toolWindow, inputs);
436411
}
437-
438412
protected void presentOpenDevToolsOptionWithMessage(FlutterApp app,
439413
ToolWindow toolWindow,
440414
String message,
441415
DevToolsIdeFeature ideFeature) {
442416
final List<LabelInput> inputs = new ArrayList<>();
443417
inputs.add(new LabelInput(message));
444418
inputs.add(openDevToolsLabel(app, toolWindow, ideFeature));
445-
presentClickableLabel(toolWindow, inputs);
419+
viewUtils.presentClickableLabel(toolWindow, inputs);
446420
}
447421

448422
private void replacePanelLabel(ToolWindow toolWindow, JComponent label) {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2025 The Chromium Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style license that can be
4+
* found in the LICENSE file.
5+
*/
6+
package io.flutter.view;
7+
8+
import com.intellij.openapi.application.ApplicationManager;
9+
import com.intellij.openapi.ui.VerticalFlowLayout;
10+
import com.intellij.openapi.wm.ToolWindow;
11+
import com.intellij.ui.components.JBLabel;
12+
import com.intellij.ui.components.labels.LinkLabel;
13+
import com.intellij.ui.content.Content;
14+
import com.intellij.ui.content.ContentManager;
15+
import com.intellij.util.ui.JBUI;
16+
import com.intellij.util.ui.UIUtil;
17+
import io.flutter.utils.LabelInput;
18+
19+
import javax.swing.*;
20+
import java.awt.*;
21+
import java.util.List;
22+
23+
public class ViewUtils {
24+
public void presentLabel(ToolWindow toolWindow, String text) {
25+
final JBLabel label = new JBLabel(text, SwingConstants.CENTER);
26+
label.setForeground(UIUtil.getLabelDisabledForeground());
27+
replacePanelLabel(toolWindow, label);
28+
}
29+
30+
public void presentClickableLabel(ToolWindow toolWindow, List<LabelInput> labels) {
31+
final JPanel panel = new JPanel(new GridLayout(0, 1));
32+
33+
for (LabelInput input : labels) {
34+
if (input.listener == null) {
35+
final JLabel descriptionLabel = new JLabel("<html>" + input.text + "</html>");
36+
descriptionLabel.setBorder(JBUI.Borders.empty(5));
37+
descriptionLabel.setHorizontalAlignment(SwingConstants.CENTER);
38+
panel.add(descriptionLabel, BorderLayout.NORTH);
39+
}
40+
else {
41+
final LinkLabel<String> linkLabel = new LinkLabel<>("<html>" + input.text + "</html>", null);
42+
linkLabel.setBorder(JBUI.Borders.empty(5));
43+
linkLabel.setListener(input.listener, null);
44+
linkLabel.setHorizontalAlignment(SwingConstants.CENTER);
45+
panel.add(linkLabel, BorderLayout.SOUTH);
46+
}
47+
}
48+
49+
final JPanel center = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.CENTER));
50+
center.add(panel);
51+
replacePanelLabel(toolWindow, center);
52+
}
53+
54+
private void replacePanelLabel(ToolWindow toolWindow, JComponent label) {
55+
ApplicationManager.getApplication().invokeLater(() -> {
56+
final ContentManager contentManager = toolWindow.getContentManager();
57+
if (contentManager.isDisposed()) {
58+
return;
59+
}
60+
61+
final JPanel panel = new JPanel(new BorderLayout());
62+
panel.add(label, BorderLayout.CENTER);
63+
final Content content = contentManager.getFactory().createContent(panel, null, false);
64+
contentManager.removeAllContents(true);
65+
contentManager.addContent(content);
66+
});
67+
}
68+
}

0 commit comments

Comments
 (0)