Skip to content

Commit cd9c21b

Browse files
committed
Grouping action for the whole tree
1 parent 14eb995 commit cd9c21b

File tree

11 files changed

+607
-50
lines changed

11 files changed

+607
-50
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.tooling.boot.ls.views;
12+
13+
import org.eclipse.jdt.internal.ui.JavaPluginImages;
14+
import org.eclipse.jface.action.Action;
15+
import org.eclipse.jface.dialogs.IDialogConstants;
16+
import org.eclipse.lsp4e.ui.UI;
17+
18+
class GroupingAction extends Action {
19+
20+
private LogicalStructureView structureView;
21+
22+
@SuppressWarnings("restriction")
23+
public GroupingAction(LogicalStructureView structureView) {
24+
super("Grouping...", JavaPluginImages.DESC_ELCL_FILTER);
25+
this.structureView = structureView;
26+
}
27+
28+
@Override
29+
public void run() {
30+
GroupingDialog dialog = new GroupingDialog(UI.getActiveShell(), structureView::fetchGroups, structureView::getGroupings);
31+
if (dialog.open() == IDialogConstants.OK_ID) {
32+
structureView.setGroupings(dialog.getResult());
33+
structureView.fetchStructure(false);
34+
}
35+
}
36+
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.tooling.boot.ls.views;
12+
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.concurrent.CompletableFuture;
16+
import java.util.function.Supplier;
17+
18+
import org.eclipse.jface.dialogs.IDialogSettings;
19+
import org.eclipse.jface.dialogs.TrayDialog;
20+
import org.eclipse.jface.layout.GridDataFactory;
21+
import org.eclipse.jface.viewers.ICheckStateProvider;
22+
import org.eclipse.jface.viewers.ITreeContentProvider;
23+
import org.eclipse.jface.viewers.LabelProvider;
24+
import org.eclipse.lsp4e.ui.UI;
25+
import org.eclipse.swt.SWT;
26+
import org.eclipse.swt.widgets.Composite;
27+
import org.eclipse.swt.widgets.Control;
28+
import org.eclipse.swt.widgets.Shell;
29+
import org.eclipse.ui.PlatformUI;
30+
import org.eclipse.ui.dialogs.ContainerCheckedTreeViewer;
31+
import org.osgi.framework.FrameworkUtil;
32+
import org.springframework.tooling.boot.ls.views.GroupingDialogModel.GroupItem;
33+
import org.springframework.tooling.boot.ls.views.GroupingDialogModel.ProjectItem;
34+
import org.springframework.tooling.boot.ls.views.GroupingDialogModel.TreeItem;
35+
import org.springframework.tooling.boot.ls.views.StructureClient.Groups;
36+
37+
public class GroupingDialog extends TrayDialog {
38+
39+
private GroupingDialogModel model;
40+
41+
protected GroupingDialog(Shell parentShell, Supplier<CompletableFuture<List<Groups>>> client, Supplier<Map<String, List<String>>> groupings) {
42+
super(parentShell);
43+
this.model = new GroupingDialogModel(client, groupings);
44+
}
45+
46+
@Override
47+
protected Control createDialogArea(Composite parent) {
48+
Composite composite = (Composite) super.createDialogArea(parent);
49+
50+
ContainerCheckedTreeViewer viewer = new ContainerCheckedTreeViewer(composite, SWT.SINGLE);
51+
52+
viewer.setCheckStateProvider(new ICheckStateProvider() {
53+
54+
@Override
55+
public boolean isGrayed(Object element) {
56+
if (element instanceof TreeItem) {
57+
return ((TreeItem) element).getChecked() == null;
58+
}
59+
return false;
60+
}
61+
62+
@Override
63+
public boolean isChecked(Object element) {
64+
if (element instanceof TreeItem) {
65+
Boolean checked = ((TreeItem) element).getChecked();
66+
return checked == null || Boolean.TRUE.equals(checked);
67+
}
68+
return false;
69+
}
70+
});
71+
72+
viewer.setContentProvider(new ITreeContentProvider() {
73+
74+
@Override
75+
public Object[] getElements(Object input) {
76+
if (input instanceof GroupingDialogModel) {
77+
return ((GroupingDialogModel) input).getLiveSet().getValue().toArray();
78+
}
79+
return new Object[0];
80+
}
81+
82+
@Override
83+
public Object[] getChildren(Object p) {
84+
if (p instanceof ProjectItem) {
85+
return ((ProjectItem) p).getGroups().toArray();
86+
}
87+
return new Object[0];
88+
}
89+
90+
@Override
91+
public Object getParent(Object e) {
92+
if (e instanceof GroupItem) {
93+
return ((GroupItem) e).getProjectItem();
94+
}
95+
return null;
96+
}
97+
98+
@Override
99+
public boolean hasChildren(Object e) {
100+
if (e instanceof ProjectItem) {
101+
return !((ProjectItem) e).getGroups().isEmpty();
102+
}
103+
104+
return false;
105+
}
106+
107+
});
108+
109+
viewer.setLabelProvider(new LabelProvider() {
110+
111+
@Override
112+
public String getText(Object e) {
113+
return e instanceof TreeItem ? ((TreeItem) e).getLabel() : null;
114+
}
115+
116+
});
117+
118+
viewer.addCheckStateListener(e -> {
119+
Object o = e.getElement();
120+
if (o instanceof TreeItem) {
121+
((TreeItem) o).setChecked(e.getChecked());
122+
}
123+
});
124+
125+
viewer.setInput(model);
126+
127+
model.getLiveSet().addListener((e, v) -> {
128+
UI.getDisplay().asyncExec(viewer::refresh);
129+
});
130+
131+
model.getLoaded().addListener((e, v) -> {
132+
UI.getDisplay().asyncExec(viewer::expandAll);
133+
});
134+
135+
model.load();
136+
137+
viewer.getControl().setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
138+
139+
return composite;
140+
}
141+
142+
@Override
143+
protected IDialogSettings getDialogBoundsSettings() {
144+
IDialogSettings settings = PlatformUI
145+
.getDialogSettingsProvider(FrameworkUtil.getBundle(getClass()))
146+
.getDialogSettings();
147+
String dialogSettingsId = getClass().getName();
148+
IDialogSettings section = settings.getSection(dialogSettingsId);
149+
if (section == null) {
150+
section = settings.addNewSection(dialogSettingsId);
151+
}
152+
return section;
153+
}
154+
155+
@Override
156+
protected boolean isResizable() {
157+
return true;
158+
}
159+
160+
Map<String, List<String>> getResult() {
161+
return model.getResult();
162+
}
163+
164+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.tooling.boot.ls.views;
12+
13+
import java.util.ArrayList;
14+
import java.util.HashMap;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.concurrent.CompletableFuture;
18+
import java.util.function.Supplier;
19+
20+
import org.springframework.tooling.boot.ls.views.StructureClient.Groups;
21+
import org.springsource.ide.eclipse.commons.livexp.core.LiveSetVariable;
22+
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
23+
24+
class GroupingDialogModel {
25+
26+
interface TreeItem {
27+
Boolean getChecked();
28+
String getLabel();
29+
void setChecked(boolean checked);
30+
}
31+
32+
static class ProjectItem implements TreeItem {
33+
34+
private final String name;
35+
private final List<GroupItem> groups;
36+
37+
public ProjectItem(String name) {
38+
this.name = name;
39+
this.groups = new ArrayList<>();
40+
}
41+
42+
public Boolean getChecked() {
43+
if (groups.isEmpty()) {
44+
return true;
45+
}
46+
boolean checked = groups.get(0).checked;
47+
for (GroupItem g : groups) {
48+
if (g.checked != checked) {
49+
return null;
50+
}
51+
}
52+
return checked;
53+
}
54+
55+
public GroupItem addGroup(String id, String label) {
56+
GroupItem groupItem = new GroupItem(this, id, label, false);
57+
groups.add(groupItem);
58+
return groupItem;
59+
}
60+
61+
public String getLabel() {
62+
return name;
63+
}
64+
65+
public List<GroupItem> getGroups() {
66+
return groups;
67+
}
68+
69+
void apply(List<String> checkedGroups) {
70+
groups.forEach(g -> g.checked = checkedGroups == null || checkedGroups.contains(g.id));
71+
}
72+
73+
List<String> extract() {
74+
List<String> checkedGroups = groups.stream().filter(g -> g.checked).map(g -> g.id).toList();
75+
return checkedGroups.size() == groups.size() ? null : checkedGroups;
76+
}
77+
78+
@Override
79+
public void setChecked(boolean checked) {
80+
groups.forEach(g -> g.setChecked(checked));
81+
}
82+
83+
}
84+
85+
static class GroupItem implements TreeItem {
86+
87+
private final ProjectItem projectItem;
88+
private final String id;
89+
private final String label;
90+
private boolean checked;
91+
92+
private GroupItem(ProjectItem projectItem, String id, String label, boolean checked) {
93+
this.projectItem = projectItem;
94+
this.id = id;
95+
this.label = label;
96+
this.checked = checked;
97+
}
98+
99+
public void setChecked(boolean c) {
100+
this.checked = c;
101+
}
102+
103+
public Boolean getChecked() {
104+
return this.checked;
105+
}
106+
107+
public String getId() {
108+
return id;
109+
}
110+
111+
public String getLabel() {
112+
return label;
113+
}
114+
115+
ProjectItem getProjectItem() {
116+
return projectItem;
117+
}
118+
119+
}
120+
121+
private final Supplier<CompletableFuture<List<Groups>>> client;
122+
123+
private final LiveSetVariable<ProjectItem> projectItems;
124+
125+
private final Supplier<Map<String, List<String>>> groupings;
126+
127+
private final LiveVariable<Boolean> loaded;
128+
129+
public GroupingDialogModel(Supplier<CompletableFuture<List<Groups>>> client, Supplier<Map<String, List<String>>> groupings) {
130+
this.client = client;
131+
this.groupings = groupings;
132+
this.projectItems = new LiveSetVariable<>();
133+
this.loaded = new LiveVariable<>(false);
134+
}
135+
136+
void load() {
137+
loaded.setValue(false);
138+
Map<String, List<String>> groupingsMap = groupings.get();
139+
client.get().thenApply(allGroups -> {
140+
return allGroups.stream().map(groups -> {
141+
String projectName = groups.projectName();
142+
ProjectItem projectItem = new ProjectItem(projectName);
143+
groups.groups().stream().forEach(g -> projectItem.addGroup(g.identifier(), g.displayName()));
144+
projectItem.apply(groupingsMap.get(projectName));
145+
return projectItem;
146+
}).toList();
147+
}).thenAccept(items -> {
148+
projectItems.replaceAll(items);
149+
loaded.setValue(true);
150+
});
151+
}
152+
153+
LiveSetVariable<ProjectItem> getLiveSet() {
154+
return projectItems;
155+
}
156+
157+
LiveVariable<Boolean> getLoaded() {
158+
return loaded;
159+
}
160+
161+
public Map<String, List<String>> getResult() {
162+
// Cannot use `Collectors.toMap()` due to NPE with null values
163+
Map<String, List<String>> res = new HashMap<>();
164+
for (ProjectItem p : projectItems.getValue()) {
165+
res.put(p.getLabel(), p.extract());
166+
}
167+
return res;
168+
}
169+
170+
}

0 commit comments

Comments
 (0)