Skip to content

Commit 1e7b094

Browse files
gdanielsbegaudeau
authored andcommitted
[5982] Add the ability to trigger context menu entries with key bindings
Bug: #5982 Signed-off-by: Gwendal Daniel <gwendal.daniel@obeosoft.com>
1 parent d3f7980 commit 1e7b094

File tree

49 files changed

+1146
-358
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1146
-358
lines changed

CHANGELOG.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ The `SingleClickOnDiagramElementTool` builder now requires a list of key binding
6161
- https://github.com/eclipse-sirius/sirius-web/issues/6202[#6202] [diagram] Custom node layout handlers must now set `isDragNodeSource`.
6262
An example can be found with `EllipseNodeConverter.ts`.
6363
`useDropNodeStyle` now has a new parameter `isDragNodeSource`.
64+
- https://github.com/eclipse-sirius/sirius-web/issues/5982[#5982] [tree] Add the ability to trigger context menu entries using key bindings.
65+
The `TreeItemContextMenuEntryBuilder` subclasses now require a list of key bindings.
66+
`IKeyBindingConverter` and `KeyBindingConverter` have been moved from their respective packages to `org.eclipse.sirius.components.view.emf`.
6467

6568

6669
=== Dependency update
@@ -109,6 +112,9 @@ The key bindings are visible in the palette next to the tool name.
109112
A key binding needs to contain at least one of `Control`, `Alt`, or `Meta` to be executed by the frontend.
110113
- https://github.com/eclipse-sirius/sirius-web/issues/3488[#3488] [diagram] Restore auto-layout support for diagrams
111114
- https://github.com/eclipse-sirius/sirius-web/issues/5062[#5062] [sirius-web] Add new "views explorer" view
115+
- https://github.com/eclipse-sirius/sirius-web/issues/5982[#5982] [tree] Add the ability to trigger context menu entries using key bindings.
116+
As for diagram tools, it is now possible to add `KeyBinding` instances to context menu entries in trees.
117+
Note that for the moment, only entries that aren't overridden in the frontend can be triggered with key bindings, although it is possible to define key bindings for any entry.
112118

113119

114120
=== Improvements
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Obeo.
3+
* This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Obeo - initial API and implementation
12+
*******************************************************************************/
13+
import { expect, test } from '@playwright/test';
14+
import { PlaywrightExplorer } from '../../helpers/PlaywrightExplorer';
15+
import { PlaywrightProject } from '../../helpers/PlaywrightProject';
16+
17+
test.describe('explorer - key bindings', () => {
18+
let projectId;
19+
let playwrightExplorer;
20+
test.beforeEach(async ({ page, request }) => {
21+
const project = await new PlaywrightProject(request).createProject('Studio', 'blank-studio-template');
22+
projectId = project.projectId;
23+
24+
await page.goto(`/projects/${projectId}/edit/`);
25+
26+
playwrightExplorer = new PlaywrightExplorer(page);
27+
await playwrightExplorer.uploadDocument('studioKeyBindings.xml');
28+
});
29+
30+
test('When clicking on a tree item and entering a valid key binding, then the corresponding entry is executed', async ({
31+
page,
32+
}) => {
33+
// Reload the page to display the explorer selection button (which is only present if a domain model is in the editing context)
34+
page.reload();
35+
await playwrightExplorer.openExplorer('Domain explorer by DSL');
36+
await playwrightExplorer.expand('studioKeyBindings.xml');
37+
await playwrightExplorer.expand('domain');
38+
await playwrightExplorer.select('[Entity] Entity1 {}');
39+
const entity1Item = await playwrightExplorer.getTreeItemLabel('[Entity] Entity1 {}');
40+
entity1Item.press('ControlOrMeta+b');
41+
await expect(page.getByTestId('impact-analysis-dialog')).toBeAttached();
42+
});
43+
});

integration-tests-playwright/playwright/helpers/PlaywrightExplorer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2025 Obeo.
2+
* Copyright (c) 2025, 2026 Obeo.
33
* This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v2.0
55
* which accompanies this distribution, and is available at
@@ -91,4 +91,9 @@ export class PlaywrightExplorer {
9191
await this.explorerLocator.getByTestId(`${treeItemLabel}-more`).click();
9292
await this.page.getByTestId(`push-selection-to-${selectionTargetLabel}`).click();
9393
}
94+
95+
async openExplorer(explorerDescriptionLabel: string) {
96+
await this.page.getByTestId('tree-descriptions-menu-icon').click();
97+
await this.page.getByRole('menuitem').filter({ hasText: explorerDescriptionLabel }).click();
98+
}
9499
}

packages/core/frontend/sirius-components-core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export { useData } from './extension/useData';
3434
export * from './filter/FilterBar';
3535
export type * from './graphql/GQLTypes.types';
3636
export * from './icon/IconOverlay';
37+
export { KeyBinding } from './key-binding/KeyBinding';
38+
export type { GQLKeyBinding, KeyBindingProps } from './key-binding/KeyBinding.types';
3739
export * from './label/StyledLabel';
3840
export * from './label/StyledLabel.type';
3941
export * from './materialui';

packages/palette/frontend/sirius-components-palette/src/key-binding/KeyBinding.tsx renamed to packages/core/frontend/sirius-components-core/src/key-binding/KeyBinding.tsx

File renamed without changes.

packages/palette/frontend/sirius-components-palette/src/key-binding/KeyBinding.types.ts renamed to packages/core/frontend/sirius-components-core/src/key-binding/KeyBinding.types.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@
1111
* Obeo - initial API and implementation
1212
*******************************************************************************/
1313

14-
import { GQLKeyBinding } from '../Palette.types';
15-
1614
export interface KeyBindingProps {
1715
keyBinding: GQLKeyBinding;
1816
'data-testid'?: string;
1917
}
18+
19+
export interface GQLKeyBinding {
20+
isCtrl: boolean;
21+
isMeta: boolean;
22+
isAlt: boolean;
23+
key: string;
24+
}

packages/palette/frontend/sirius-components-palette/src/Palette.types.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* Obeo - initial API and implementation
1212
*******************************************************************************/
1313

14+
import { GQLKeyBinding } from '@eclipse-sirius/sirius-components-core';
1415
import { PaletteExtensionSectionProps } from './PaletteExtensionSection.types';
1516

1617
export interface ContextualPaletteStyleProps {
@@ -60,13 +61,6 @@ export interface GQLSingleClickOnDiagramElementTool extends GQLTool {
6061
keyBindings: GQLKeyBinding[];
6162
}
6263

63-
export interface GQLKeyBinding {
64-
isCtrl: boolean;
65-
isMeta: boolean;
66-
isAlt: boolean;
67-
key: string;
68-
}
69-
7064
export interface GQLPalette {
7165
id: string;
7266
quickAccessTools: GQLTool[];

packages/palette/frontend/sirius-components-palette/src/tool-list-item/ToolListItem.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111
* Obeo - initial API and implementation
1212
*******************************************************************************/
1313

14-
import { IconOverlay } from '@eclipse-sirius/sirius-components-core';
14+
import { IconOverlay, KeyBinding } from '@eclipse-sirius/sirius-components-core';
1515
import ListItemButton from '@mui/material/ListItemButton';
1616
import ListItemIcon from '@mui/material/ListItemIcon';
1717
import ListItemText from '@mui/material/ListItemText';
1818
import Tooltip from '@mui/material/Tooltip';
1919
import { makeStyles } from 'tss-react/mui';
20-
import { KeyBinding } from '../key-binding/KeyBinding';
2120
import { isSingleClickOnDiagramElementTool } from '../Palette';
2221
import { GQLTool } from '../Palette.types';
2322
import { ToolListItemProps } from './ToolListItem.types';

packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/studio/services/representations/DomainViewTreeDescriptionProvider.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.eclipse.sirius.components.view.TextStylePalette;
2828
import org.eclipse.sirius.components.view.View;
2929
import org.eclipse.sirius.components.view.builder.generated.tree.TreeBuilders;
30+
import org.eclipse.sirius.components.view.builder.generated.view.KeyBindingBuilder;
3031
import org.eclipse.sirius.components.view.builder.generated.view.ViewBuilders;
3132
import org.eclipse.sirius.components.view.emf.tree.ITreeIdProvider;
3233
import org.eclipse.sirius.components.view.tree.FetchTreeItemContextMenuEntryKind;
@@ -292,12 +293,29 @@ private List<TreeItemContextMenuEntry> getContextMenuEntries() {
292293
.preconditionExpression(AQL_SELF_IS_AN_ENTITY)
293294
.urlExression("https://eclipse.dev/sirius/sirius-web.html")
294295
.kind(FetchTreeItemContextMenuEntryKind.OPEN)
296+
.keyBindings(new KeyBindingBuilder()
297+
.ctrl(true)
298+
.key("m")
299+
.build(),
300+
new KeyBindingBuilder()
301+
.meta(true)
302+
.key("m")
303+
.build()
304+
)
295305
.build();
296306
this.toggleAbstractMenuEntry = new TreeBuilders().newSingleClickTreeItemContextMenuEntry()
297307
.labelExpression("Toggle abstract")
298308
.preconditionExpression(AQL_SELF_IS_AN_ENTITY)
299309
.body(callService.build())
300310
.withImpactAnalysis(true)
311+
.keyBindings(new KeyBindingBuilder()
312+
.ctrl(true)
313+
.key("b")
314+
.build(),
315+
new KeyBindingBuilder()
316+
.meta(true)
317+
.key("b")
318+
.build())
301319
.build();
302320

303321
var expandAllMenuEntry = new TreeBuilders().newCustomTreeItemContextMenuEntry()

packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerTreeItemContextMenuEntryProvider.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2025 Obeo.
2+
* Copyright (c) 2025, 2026 Obeo.
33
* This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v2.0
55
* which accompanies this distribution, and is available at
@@ -96,7 +96,7 @@ public List<ITreeItemContextMenuEntry> getTreeItemContextMenuEntries(IEditingCon
9696
result.addAll(this.getLibraryRelatedEntries(emfEditingContext, treeItem));
9797
}
9898
if (treeItem.isHasChildren()) {
99-
result.add(new SingleClickTreeItemContextMenuEntry(EXPAND_ALL, "", List.of(), false));
99+
result.add(new SingleClickTreeItemContextMenuEntry(EXPAND_ALL, "", List.of(), false, List.of()));
100100
}
101101
return result;
102102
}
@@ -110,9 +110,9 @@ private List<ITreeItemContextMenuEntry> getDocumentContextMenuEntries(IEMFEditin
110110

111111
List<ITreeItemContextMenuEntry> entries = new ArrayList<>();
112112
if (!this.readOnlyObjectPredicate.test(resource)) {
113-
entries.add(new SingleClickTreeItemContextMenuEntry(NEW_ROOT_OBJECT, "", List.of(), false));
113+
entries.add(new SingleClickTreeItemContextMenuEntry(NEW_ROOT_OBJECT, "", List.of(), false, List.of()));
114114
}
115-
entries.add(new SingleClickTreeItemContextMenuEntry(DOWNLOAD_DOCUMENT, "", List.of(), false));
115+
entries.add(new SingleClickTreeItemContextMenuEntry(DOWNLOAD_DOCUMENT, "", List.of(), false, List.of()));
116116
return entries;
117117
}
118118
return List.of();
@@ -126,9 +126,9 @@ private List<ITreeItemContextMenuEntry> getObjectContextMenuEntries(IEMFEditingC
126126
var object = optionalEObject.get();
127127
if (!this.readOnlyObjectPredicate.test(object)) {
128128
return List.of(
129-
new SingleClickTreeItemContextMenuEntry(NEW_OBJECT, "", List.of(), false),
130-
new SingleClickTreeItemContextMenuEntry(NEW_REPRESENTATION, "", List.of(), false),
131-
new SingleClickTreeItemContextMenuEntry(DUPLICATE_OBJECT, "", List.of(), false)
129+
new SingleClickTreeItemContextMenuEntry(NEW_OBJECT, "", List.of(), false, List.of()),
130+
new SingleClickTreeItemContextMenuEntry(NEW_REPRESENTATION, "", List.of(), false, List.of()),
131+
new SingleClickTreeItemContextMenuEntry(DUPLICATE_OBJECT, "", List.of(), false, List.of())
132132
);
133133
}
134134
}
@@ -141,7 +141,7 @@ private List<ITreeItemContextMenuEntry> getRepresentationContextMenuEntries(IEMF
141141
.map(RepresentationMetadata.class::cast);
142142
if (optionalRepresentationMetadata.isPresent()) {
143143
return List.of(
144-
new SingleClickTreeItemContextMenuEntry(DUPLICATE_REPRESENTATION, "", List.of(), false)
144+
new SingleClickTreeItemContextMenuEntry(DUPLICATE_REPRESENTATION, "", List.of(), false, List.of())
145145
);
146146
}
147147
return List.of();
@@ -171,8 +171,8 @@ private List<ITreeItemContextMenuEntry> getLibraryRelatedEntries(IEMFEditingCont
171171
var libraryMetadataAdapter = optionalLibraryMetadataAdapter.get();
172172
if (this.isDirectDependency(editingContext, libraryMetadataAdapter)) {
173173
// We do not support the update or removal of a transitive dependency for the moment.
174-
result.add(new SingleClickTreeItemContextMenuEntry(UPDATE_LIBRARY, "Update the library", List.of(), true));
175-
result.add(new SingleClickTreeItemContextMenuEntry(REMOVE_LIBRARY, "Remove library", List.of("/icons/remove_library.svg"), true));
174+
result.add(new SingleClickTreeItemContextMenuEntry(UPDATE_LIBRARY, "Update the library", List.of(), true, List.of()));
175+
result.add(new SingleClickTreeItemContextMenuEntry(REMOVE_LIBRARY, "Remove library", List.of("/icons/remove_library.svg"), true, List.of()));
176176
}
177177
}
178178
return result;

0 commit comments

Comments
 (0)