diff --git a/platform/platform-impl/api-dump.txt b/platform/platform-impl/api-dump.txt index f5c4f3dea411..e1cb22a8cc0e 100644 --- a/platform/platform-impl/api-dump.txt +++ b/platform/platform-impl/api-dump.txt @@ -146,6 +146,9 @@ com.intellij.ide.IdeEventQueue$EventDispatcher - a:dispatch(java.awt.AWTEvent):Z com.intellij.ide.IdeEventQueue$NonLockedEventDispatcher - com.intellij.ide.IdeEventQueue$EventDispatcher +c:com.intellij.ide.IdeTooltip +- isCalloutEnabled():Z +- setShowCallout(Z):com.intellij.ide.IdeTooltip com.intellij.ide.IdeTooltip$Ui - a:getShowingPoint():com.intellij.ui.awt.RelativePoint - a:isInside(com.intellij.ui.awt.RelativePoint):Z diff --git a/platform/platform-impl/src/com/intellij/ide/IdeTooltip.java b/platform/platform-impl/src/com/intellij/ide/IdeTooltip.java index 1c3685a3eed2..c314d02a1dab 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeTooltip.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeTooltip.java @@ -50,6 +50,7 @@ public class IdeTooltip extends ComparableObject.Impl { private int myCalloutShift = 4; private boolean myExplicitClose; + private boolean isCalloutEnabled = true; private int myPositionChangeX; private int myPositionChangeY; @@ -240,6 +241,15 @@ public class IdeTooltip extends ComparableObject.Impl { return this; } + public IdeTooltip setShowCallout(boolean showCallout) { + isCalloutEnabled = showCallout; + return this; + } + + public boolean isCalloutEnabled() { + return isCalloutEnabled; + } + public void setComponent(Component component) { myComponent = component; } diff --git a/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.kt b/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.kt index 4e5efb440497..54f449d11aa8 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.kt +++ b/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.kt @@ -638,7 +638,7 @@ class IdeTooltipManager(coroutineScope: CoroutineScope) : Disposable { .setBorderColor(borderColor) .setBorderInsets(tooltip.borderInsets) .setAnimationCycle(if (animationEnabled) RegistryManager.getInstance().intValue("ide.tooltip.animationCycle") else 0) - .setShowCallout(true) + .setShowCallout(tooltip.isCalloutEnabled) .setPointerSize(tooltip.pointerSize) .setCalloutShift(if (small && tooltip.calloutShift == 0) 2 else tooltip.calloutShift) .setPositionChangeXShift(tooltip.positionChangeX) diff --git a/plugins/github/github-core/src/org/jetbrains/plugins/github/pullrequest/ui/GHPRInlayUtils.kt b/plugins/github/github-core/src/org/jetbrains/plugins/github/pullrequest/ui/GHPRInlayUtils.kt index b1a3ed4b4d9d..7fcb604e2d58 100644 --- a/plugins/github/github-core/src/org/jetbrains/plugins/github/pullrequest/ui/GHPRInlayUtils.kt +++ b/plugins/github/github-core/src/org/jetbrains/plugins/github/pullrequest/ui/GHPRInlayUtils.kt @@ -11,7 +11,8 @@ import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorGutterCon import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorInlaysModel import com.intellij.diff.util.LineRange import com.intellij.diff.util.Side -import com.intellij.ide.HelpTooltip +import com.intellij.ide.IdeTooltip +import com.intellij.ide.IdeTooltipManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.event.EditorMouseEvent import com.intellij.openapi.editor.event.EditorMouseListener @@ -21,6 +22,7 @@ import com.intellij.openapi.editor.markup.HighlighterLayer import com.intellij.openapi.editor.markup.HighlighterTargetArea import com.intellij.openapi.editor.markup.RangeHighlighter import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.popup.Balloon import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.wm.IdeGlassPaneUtil import com.intellij.platform.util.coroutines.childScope @@ -36,9 +38,9 @@ import org.jetbrains.plugins.github.i18n.GithubBundle import org.jetbrains.plugins.github.pullrequest.ui.comment.CommentedCodeFrameRenderer import org.jetbrains.plugins.github.pullrequest.ui.editor.GHPREditorMappedComponentModel import java.awt.* -import java.awt.event.MouseEvent import java.beans.PropertyChangeListener import javax.swing.JComponent +import javax.swing.JLabel import javax.swing.JLayer import javax.swing.SwingUtilities import javax.swing.plaf.LayerUI @@ -57,10 +59,10 @@ internal object GHPRInlayUtils { var activeLineHighlighter: RangeHighlighter? = null val multilineCommentsDisabled = Registry.get("github.pr.new.multiline.comments.disabled").asBoolean() val frameResizer = if (vm is GHPREditorMappedComponentModel.NewComment<*> && !(isUnifiedDiff && multilineCommentsDisabled)) { - val frameResizer = ResizingFrameListener(editor, vm) - editor.addEditorMouseMotionListener(frameResizer) - editor.addEditorMouseListener(frameResizer) - frameResizer + ResizingFrameListener(editor, vm).also { + editor.addEditorMouseMotionListener(it) + editor.addEditorMouseListener(it) + } } else null @@ -112,7 +114,10 @@ internal object GHPRInlayUtils { private const val OUTLINE_OUTSIDE_DETECTION_MARGIN = 3 private const val OUTLINE_DETECTION_LINE_FRACTION = 0.3f - private class ResizingFrameListener(val editor: Editor, val vm: GHPREditorMappedComponentModel.NewComment<*>) : EditorMouseListener, EditorMouseMotionListener { + private class ResizingFrameListener( + private val editor: Editor, + private val vm: GHPREditorMappedComponentModel.NewComment<*>, + ) : EditorMouseListener, EditorMouseMotionListener { private val editorEx = editor as EditorEx private val model: CodeReviewEditorGutterControlsModel.WithMultilineComments? get() = editorEx.getUserData(CodeReviewCommentableEditorModel.KEY) as? CodeReviewEditorGutterControlsModel.WithMultilineComments @@ -134,8 +139,9 @@ internal object GHPRInlayUtils { Cursor.getDefaultCursor() } + private var currentTooltip: IdeTooltip? = null + init { - setupTooltips() SwingUtilities.invokeLater { val gutterComponent = editorEx.gutterComponentEx val gutterMousePos = gutterComponent.mousePosition ?: return@invokeLater @@ -143,52 +149,13 @@ internal object GHPRInlayUtils { if (gutterMousePos.getEdge(yBorders) != null) { gutterComponent.cursor = resizeCursor - forceTooltip(gutterMousePos) } } } - private fun setupTooltips() { - val condition: () -> Boolean = { - val yBorders = getYAxisBorders() - val mousePos = editorEx.gutterComponentEx.mousePosition ?: editor.contentComponent.mousePosition - yBorders != null && mousePos != null && mousePos.getEdge(yBorders) != null - } - listOf(editorEx.gutterComponentEx, editor.contentComponent).forEach { component -> - HelpTooltip() - .setDescription(GithubBundle.message("pull.request.review.new.comment.code.outline.tooltip")) - .setLocation(HelpTooltip.Alignment.CURSOR) - .setInitialDelay(100) - .installOn(component) - - HelpTooltip.setMasterPopupOpenCondition(component, condition) - } - } - - private fun forceTooltip(gutterMousePos: Point) { - val gutterComponent = editorEx.gutterComponentEx - val mouseEvent = MouseEvent( - gutterComponent, - MouseEvent.MOUSE_ENTERED, - System.currentTimeMillis(), - 0, - gutterMousePos.x, - gutterMousePos.y, - 0, - false, - ) - gutterComponent.dispatchEvent(mouseEvent) - } - - private fun hideTooltip() { - HelpTooltip.hide(editor.contentComponent) - HelpTooltip.hide(editorEx.gutterComponentEx) - } - override fun mouseDragged(e: EditorMouseEvent) { if (!isDraggingFrame || edge == null || oldRange == null) return e.consume() // to prevent selecting text while dragging - hideTooltip() val mouseY = e.mouseEvent.y.toFloat() val dragDelta = mouseY - dragStart val direction = if (dragDelta > 0) 1 else -1 @@ -245,19 +212,41 @@ internal object GHPRInlayUtils { isDraggingFrame = true } + override fun mouseEntered(e: EditorMouseEvent) { + if (isDraggingFrame) return + val yBorders = getYAxisBorders() ?: return + val point = e.mouseEvent.point + val onEdge = point.getEdge(yBorders) != null + if (onEdge) { + showTooltip(e.mouseEvent.component, point) + } + } + override fun mouseMoved(e: EditorMouseEvent) { val yBorders = getYAxisBorders() ?: return - val onEdge = e.mouseEvent.point.getEdge(yBorders) != null + val point = e.mouseEvent.point + val onEdge = point.getEdge(yBorders) != null val gutterGlassComp = IdeGlassPaneUtil.find(editorEx.gutterComponentEx) + if (onEdge) { editorEx.setCustomCursor(this, resizeCursor) gutterGlassComp.setCursor(resizeCursor, this) } else { - hideTooltip() editorEx.setCustomCursor(this, null) gutterGlassComp.setCursor(null, this) } + + if (onEdge && !isDraggingFrame) { + showTooltip(e.mouseEvent.component, point) + } + else { + hideTooltip() + } + } + + override fun mouseExited(event: EditorMouseEvent) { + hideTooltip() } private fun Point.getEdge(frameCoords: Pair): Edge? { @@ -284,6 +273,29 @@ internal object GHPRInlayUtils { return topY to bottomY } + private fun showTooltip(component: Component, point: Point) { + val offsetPoint = Point(point.x, point.y + editor.lineHeight) // offset for tooltip placement + currentTooltip?.let { + it.component = component + it.point = offsetPoint + } + + if (currentTooltip == null) { + val label = JLabel(GithubBundle.message("pull.request.review.new.comment.code.outline.tooltip")) + currentTooltip = IdeTooltip(component, offsetPoint, label) + .setPreferredPosition(Balloon.Position.below) + .setShowCallout(false) + } + IdeTooltipManager.getInstance().show(currentTooltip!!, false) + } + + private fun hideTooltip() { + currentTooltip?.let { + IdeTooltipManager.getInstance().hide(it) + } + currentTooltip = null + } + private enum class Edge { TOP, BOTTOM }