diff --git a/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/ClickModifiers.kt b/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/ClickModifiers.kt new file mode 100644 index 000000000000..27a6af01ae99 --- /dev/null +++ b/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/ClickModifiers.kt @@ -0,0 +1,31 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.notebooks.visualization.ui + +import com.intellij.openapi.client.ClientSystemInfo +import java.awt.event.InputEvent +import java.awt.event.MouseEvent + +data class ClickModifiers( + val isCtrlPressed: Boolean = false, + val isShiftPressed: Boolean = false, + val isAltPressed: Boolean = false, + val mouseButton: Int = MouseEvent.BUTTON1 +) { + companion object { + fun fromEvent(e: MouseEvent) = ClickModifiers( + isCtrlPressed = e.isCtrlPressed(), + isShiftPressed = e.isShiftPressed(), + isAltPressed = e.isAltPressed(), + mouseButton = e.button + ) + } +} + +private fun MouseEvent.isCtrlPressed(): Boolean = + (modifiersEx and if (ClientSystemInfo.isMac()) InputEvent.META_DOWN_MASK else InputEvent.CTRL_DOWN_MASK) != 0 + +private fun MouseEvent.isShiftPressed(): Boolean = + (modifiersEx and InputEvent.SHIFT_DOWN_MASK) != 0 + +private fun MouseEvent.isAltPressed(): Boolean = + (modifiersEx and InputEvent.ALT_DOWN_MASK) != 0 \ No newline at end of file diff --git a/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/DecoratedEditor.kt b/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/DecoratedEditor.kt index aa87f17a3010..ac65040ccc7f 100644 --- a/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/DecoratedEditor.kt +++ b/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/DecoratedEditor.kt @@ -8,14 +8,12 @@ import com.intellij.notebooks.visualization.cellSelectionModel import com.intellij.notebooks.visualization.getCells import com.intellij.notebooks.visualization.ui.EditorLayerController.Companion.EDITOR_LAYER_CONTROLLER_KEY import com.intellij.openapi.Disposable -import com.intellij.openapi.client.ClientSystemInfo import com.intellij.openapi.editor.event.EditorMouseEventArea import com.intellij.openapi.editor.impl.EditorImpl import com.intellij.openapi.observable.properties.AtomicProperty import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.removeUserData import com.intellij.ui.ComponentUtil -import java.awt.event.InputEvent import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import java.awt.event.MouseMotionAdapter @@ -68,7 +66,8 @@ class DecoratedEditor private constructor( if (editorImpl.getMouseEventArea(event) != EditorMouseEventArea.EDITING_AREA) { editorImpl.setMode(NotebookEditorMode.COMMAND) } - updateSelectionAfterClick(hoveredCell.interval, event.isCtrlPressed(), event.isShiftPressed(), event.button) + + updateSelectionAfterClick(hoveredCell.interval, ClickModifiers.fromEvent(event)) } private fun sendMouseEventToNestedScroll(event: MouseEvent) { @@ -83,15 +82,18 @@ class DecoratedEditor private constructor( editorComponentWrapper.addEditorMouseWheelEvent { nestedScrollingSupport.processMouseWheelEvent(it) } } - override fun inlayClicked(clickedCell: NotebookCellLines.Interval, ctrlPressed: Boolean, shiftPressed: Boolean, mouseButton: Int) { + override fun inlayClicked(clickedCell: NotebookCellLines.Interval, clickModifiers: ClickModifiers) { editorImpl.setMode(NotebookEditorMode.COMMAND) - updateSelectionAfterClick(clickedCell, ctrlPressed, shiftPressed, mouseButton) + updateSelectionAfterClick(clickedCell, clickModifiers) } - fun updateSelectionAfterClick(clickedCell: NotebookCellLines.Interval, ctrlPressed: Boolean, shiftPressed: Boolean, mouseButton: Int) { + fun updateSelectionAfterClick( + clickedCell: NotebookCellLines.Interval, + clickModifiers: ClickModifiers + ) { val model = editorImpl.cellSelectionModel!! when { - ctrlPressed -> { + clickModifiers.isCtrlPressed -> { if (model.isSelectedCell(clickedCell)) { model.removeSelection(clickedCell) } @@ -99,7 +101,7 @@ class DecoratedEditor private constructor( model.selectCell(clickedCell, makePrimary = true) } } - shiftPressed -> { + clickModifiers.isShiftPressed -> { // select or deselect all cells from primary to the selected one val primaryCell = model.primarySelectedCell val line1 = primaryCell.lines.first @@ -115,21 +117,28 @@ class DecoratedEditor private constructor( } } else { - for (cell in (affectedSelectedCells - cellsInRange)) { + for (cell in (affectedSelectedCells - cellsInRange.toSet())) { model.removeSelection(cell) } - for (cell in (cellsInRange - affectedSelectedCells)) { + for (cell in (cellsInRange - affectedSelectedCells.toSet())) { model.selectCell(cell) } } } - mouseButton == MouseEvent.BUTTON1 && !model.isSelectedCell(clickedCell) -> { + clickModifiers.isAltPressed -> { + // don't change selection in this case + } + clickModifiers.mouseButton == MouseEvent.BUTTON1 && !model.isSelectedCell(clickedCell) -> { model.selectSingleCell(clickedCell) } } } companion object { + /** lists assumed to be ordered and non-empty */ + private fun hasIntersection(cells: List, others: List): Boolean = + !(cells.last().ordinal < others.first().ordinal || cells.first().ordinal > others.last().ordinal) + fun install(original: EditorImpl, manager: NotebookCellInlayManager) { val decoratedEditor = DecoratedEditor(original, manager) val controller = EditorLayerController( @@ -143,14 +152,5 @@ class DecoratedEditor private constructor( } } } + } - -/** lists assumed to be ordered and non-empty */ -private fun hasIntersection(cells: List, others: List): Boolean = - !(cells.last().ordinal < others.first().ordinal || cells.first().ordinal > others.last().ordinal) - -private fun MouseEvent.isCtrlPressed(): Boolean = - (modifiersEx and if (ClientSystemInfo.isMac()) InputEvent.META_DOWN_MASK else InputEvent.CTRL_DOWN_MASK) != 0 - -private fun MouseEvent.isShiftPressed(): Boolean = - (modifiersEx and InputEvent.SHIFT_DOWN_MASK) != 0 \ No newline at end of file diff --git a/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/NotebookEditor.kt b/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/NotebookEditor.kt index 6f13c2df3dcd..c7b80d6c42ef 100644 --- a/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/NotebookEditor.kt +++ b/notebooks/visualization/src/com/intellij/notebooks/visualization/ui/NotebookEditor.kt @@ -6,7 +6,7 @@ import com.intellij.openapi.observable.properties.AtomicProperty import com.intellij.openapi.util.Key interface NotebookEditor { - fun inlayClicked(clickedCell: NotebookCellLines.Interval, ctrlPressed: Boolean, shiftPressed: Boolean, mouseButton: Int) + fun inlayClicked(clickedCell: NotebookCellLines.Interval, clickModifiers: ClickModifiers) val editorPositionKeeper: NotebookPositionKeeper /** Updated by JupyterAboveCellToolbarManager. When set, we are hiding the cell action toolbar in the top right corner. */