src/Tools/jEdit/src/pretty_tooltip.scala
author wenzelm
Thu, 13 Feb 2025 16:19:48 +0100
changeset 82156 5d2ed7e56a49
parent 82142 508a673c87ac
permissions -rw-r--r--
tuned signature: more operations;
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
49702
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
     1
/*  Title:      Tools/jEdit/src/pretty_tooltip.scala
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
     2
    Author:     Makarius
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
     3
53247
bd595338ca18 uniform use of isabelle.jEdit.Popup, based on generic screen location operations;
wenzelm
parents: 53230
diff changeset
     4
Tooltip based on Pretty_Text_Area.
49702
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
     5
*/
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
     6
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
     7
package isabelle.jedit
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
     8
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
     9
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
    10
import isabelle._
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
    11
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
    12
import java.awt.{Color, Point, BorderLayout, Dimension}
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
    13
import java.awt.event.{FocusAdapter, FocusEvent}
53778
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
    14
import javax.swing.{JPanel, JComponent, SwingUtilities, JLayeredPane}
49702
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
    15
import javax.swing.border.LineBorder
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
    16
49725
f8eeff667076 explicit close button;
wenzelm
parents: 49712
diff changeset
    17
import scala.swing.{FlowPanel, Label}
f8eeff667076 explicit close button;
wenzelm
parents: 49712
diff changeset
    18
import scala.swing.event.MouseClicked
f8eeff667076 explicit close button;
wenzelm
parents: 49712
diff changeset
    19
49702
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
    20
import org.gjt.sp.jedit.View
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
    21
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
    22
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
    23
object Pretty_Tooltip {
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
    24
  /* tooltip hierarchy */
51449
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
    25
57612
990ffb84489b clarified module name: facilitate alternative GUI frameworks;
wenzelm
parents: 56930
diff changeset
    26
  // owned by GUI thread
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
    27
  private var stack: List[Pretty_Tooltip] = Nil
51449
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
    28
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
    29
  private def hierarchy(
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
    30
    pretty_tooltip: Pretty_Tooltip
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
    31
  ): Option[(List[Pretty_Tooltip], List[Pretty_Tooltip])] = {
57612
990ffb84489b clarified module name: facilitate alternative GUI frameworks;
wenzelm
parents: 56930
diff changeset
    32
    GUI_Thread.require {}
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
    33
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
    34
    if (stack.contains(pretty_tooltip)) Some(stack.span(_ != pretty_tooltip))
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
    35
    else None
51449
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
    36
  }
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
    37
56497
0c63f3538639 dismiss popups more carefully (amending 7e8c11011fdf), notably allow mouse dragging within some tooltip, e.g. for text selection;
wenzelm
parents: 56341
diff changeset
    38
  private def descendant(parent: JComponent): Option[Pretty_Tooltip] =
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
    39
    GUI_Thread.require {
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
    40
      stack.find(pretty_tooltip => pretty_tooltip.original_parent == parent)
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
    41
    }
56497
0c63f3538639 dismiss popups more carefully (amending 7e8c11011fdf), notably allow mouse dragging within some tooltip, e.g. for text selection;
wenzelm
parents: 56341
diff changeset
    42
51449
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
    43
  def apply(
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
    44
    view: View,
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
    45
    parent: JComponent,
53247
bd595338ca18 uniform use of isabelle.jEdit.Popup, based on generic screen location operations;
wenzelm
parents: 53230
diff changeset
    46
    location: Point,
64621
7116f2634e32 clarified module name;
wenzelm
parents: 58767
diff changeset
    47
    rendering: JEdit_Rendering,
51449
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
    48
    results: Command.Results,
81398
f92ea68473f2 clarified signature with subtle change of semantics: output consists of individual messages that are formatted (and separated) internally;
wenzelm
parents: 81340
diff changeset
    49
    output: List[XML.Elem]
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
    50
  ): Unit = {
57612
990ffb84489b clarified module name: facilitate alternative GUI frameworks;
wenzelm
parents: 56930
diff changeset
    51
    GUI_Thread.require {}
51449
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
    52
52497
2dd4e4a368e3 tuned signature;
wenzelm
parents: 52496
diff changeset
    53
    stack match {
81398
f92ea68473f2 clarified signature with subtle change of semantics: output consists of individual messages that are formatted (and separated) internally;
wenzelm
parents: 81340
diff changeset
    54
      case top :: _ if top.results == results && top.output == output =>
52497
2dd4e4a368e3 tuned signature;
wenzelm
parents: 52496
diff changeset
    55
      case _ =>
53778
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
    56
        GUI.layered_pane(parent) match {
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
    57
          case None =>
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
    58
          case Some(layered) =>
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
    59
            val (old, rest) =
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
    60
              GUI.ancestors(parent).collectFirst({ case x: Pretty_Tooltip => x }) match {
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
    61
                case Some(pretty_tooltip) => hierarchy(pretty_tooltip).getOrElse((stack, Nil))
53778
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
    62
                case None => (stack, Nil)
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
    63
              }
76487
304ae1a6e160 tuned signature, following hints by IntelliJ IDEA;
wenzelm
parents: 75393
diff changeset
    64
            old.foreach(_.hide_popup())
52497
2dd4e4a368e3 tuned signature;
wenzelm
parents: 52496
diff changeset
    65
53778
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
    66
            val loc = SwingUtilities.convertPoint(parent, location, layered)
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
    67
            val pretty_tooltip =
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
    68
              new Pretty_Tooltip(view, layered, parent, loc, rendering, results, output)
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
    69
            stack = pretty_tooltip :: rest
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
    70
            pretty_tooltip.show_popup()
53778
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
    71
        }
52496
8188e5286662 avoid repeated window popup when the mouse is moved over the same content (again, see also cb677987b7e3 and 0a1db0d02628);
wenzelm
parents: 52494
diff changeset
    72
    }
51449
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
    73
  }
51452
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
    74
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
    75
57612
990ffb84489b clarified module name: facilitate alternative GUI frameworks;
wenzelm
parents: 56930
diff changeset
    76
  /* pending event and active state */  // owned by GUI thread
51452
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
    77
52494
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    78
  private var pending: Option[() => Unit] = None
51452
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
    79
  private var active = true
52494
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    80
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    81
  private val pending_delay =
71704
b9a5eb0f3b43 clarified modules;
wenzelm
parents: 71601
diff changeset
    82
    Delay.last(PIDE.options.seconds("jedit_tooltip_delay"), gui = true) {
52494
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    83
      pending match {
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    84
        case Some(body) => pending = None; body()
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    85
        case None =>
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    86
      }
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    87
    }
51452
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
    88
52494
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    89
  def invoke(body: () => Unit): Unit =
57612
990ffb84489b clarified module name: facilitate alternative GUI frameworks;
wenzelm
parents: 56930
diff changeset
    90
    GUI_Thread.require {
52494
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    91
      if (active) {
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    92
        pending = Some(body)
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    93
        pending_delay.invoke()
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    94
      }
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    95
    }
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
    96
52664
e99a0a43720b deactivate/revoke mouse event more thoroughly, to avoid "Implicit change of text area buffer";
wenzelm
parents: 52548
diff changeset
    97
  def revoke(): Unit =
57612
990ffb84489b clarified module name: facilitate alternative GUI frameworks;
wenzelm
parents: 56930
diff changeset
    98
    GUI_Thread.require {
52664
e99a0a43720b deactivate/revoke mouse event more thoroughly, to avoid "Implicit change of text area buffer";
wenzelm
parents: 52548
diff changeset
    99
      pending = None
e99a0a43720b deactivate/revoke mouse event more thoroughly, to avoid "Implicit change of text area buffer";
wenzelm
parents: 52548
diff changeset
   100
      pending_delay.revoke()
e99a0a43720b deactivate/revoke mouse event more thoroughly, to avoid "Implicit change of text area buffer";
wenzelm
parents: 52548
diff changeset
   101
    }
e99a0a43720b deactivate/revoke mouse event more thoroughly, to avoid "Implicit change of text area buffer";
wenzelm
parents: 52548
diff changeset
   102
51452
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
   103
  private lazy val reactivate_delay =
71704
b9a5eb0f3b43 clarified modules;
wenzelm
parents: 71601
diff changeset
   104
    Delay.last(PIDE.options.seconds("jedit_tooltip_delay"), gui = true) {
51452
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
   105
      active = true
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
   106
    }
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
   107
52494
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
   108
  private def deactivate(): Unit =
57612
990ffb84489b clarified module name: facilitate alternative GUI frameworks;
wenzelm
parents: 56930
diff changeset
   109
    GUI_Thread.require {
52664
e99a0a43720b deactivate/revoke mouse event more thoroughly, to avoid "Implicit change of text area buffer";
wenzelm
parents: 52548
diff changeset
   110
      revoke()
52494
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
   111
      active = false
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
   112
      reactivate_delay.invoke()
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
   113
    }
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   114
52494
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
   115
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
   116
  /* dismiss */
51452
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
   117
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
   118
  private lazy val focus_delay =
76610
6e2383488a55 clarified signature: proper scopes and types;
wenzelm
parents: 76487
diff changeset
   119
    Delay.last(PIDE.session.input_delay, gui = true) { dismiss_unfocused() }
56341
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   120
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
   121
  def dismiss_unfocused(): Unit = {
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   122
    stack.span(pretty_tooltip => !pretty_tooltip.pretty_text_area.isFocusOwner) match {
56341
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   123
      case (Nil, _) =>
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   124
      case (unfocused, rest) =>
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   125
        deactivate()
76487
304ae1a6e160 tuned signature, following hints by IntelliJ IDEA;
wenzelm
parents: 75393
diff changeset
   126
        unfocused.foreach(_.hide_popup())
56341
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   127
        stack = rest
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   128
    }
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   129
  }
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   130
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   131
  def dismiss(pretty_tooltip: Pretty_Tooltip): Unit = {
52494
a1e09340c0f4 clarified tooltip timing of pending event and active state;
wenzelm
parents: 52493
diff changeset
   132
    deactivate()
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   133
    hierarchy(pretty_tooltip) match {
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   134
      case Some((old, _ :: rest)) =>
52498
d802431fe356 clarified focus after dismiss;
wenzelm
parents: 52497
diff changeset
   135
        rest match {
76487
304ae1a6e160 tuned signature, following hints by IntelliJ IDEA;
wenzelm
parents: 75393
diff changeset
   136
          case top :: _ => top.request_focus()
66592
cc93f86e005f tuned signature;
wenzelm
parents: 66591
diff changeset
   137
          case Nil => JEdit_Lib.request_focus_view()
52498
d802431fe356 clarified focus after dismiss;
wenzelm
parents: 52497
diff changeset
   138
        }
76487
304ae1a6e160 tuned signature, following hints by IntelliJ IDEA;
wenzelm
parents: 75393
diff changeset
   139
        old.foreach(_.hide_popup())
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   140
        pretty_tooltip.hide_popup()
54376
3eb84b6b0353 transfer focus before closing old component -- avoid intermediate focus switch to root component, which is actually visible e.g. on Windows;
wenzelm
parents: 53779
diff changeset
   141
        stack = rest
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   142
      case _ =>
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   143
    }
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   144
  }
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   145
56497
0c63f3538639 dismiss popups more carefully (amending 7e8c11011fdf), notably allow mouse dragging within some tooltip, e.g. for text selection;
wenzelm
parents: 56341
diff changeset
   146
  def dismiss_descendant(parent: JComponent): Unit =
71601
97ccf48c2f0c misc tuning based on hints by IntelliJ IDEA;
wenzelm
parents: 67547
diff changeset
   147
    descendant(parent).foreach(dismiss)
56497
0c63f3538639 dismiss popups more carefully (amending 7e8c11011fdf), notably allow mouse dragging within some tooltip, e.g. for text selection;
wenzelm
parents: 56341
diff changeset
   148
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
   149
  def dismissed_all(): Boolean = {
52664
e99a0a43720b deactivate/revoke mouse event more thoroughly, to avoid "Implicit change of text area buffer";
wenzelm
parents: 52548
diff changeset
   150
    deactivate()
52548
a1a8248a4677 some attempts to avoid sandwiching of actions stemming from single ESCAPE key event, to avoid potential conflict with ongoing text selection;
wenzelm
parents: 52498
diff changeset
   151
    if (stack.isEmpty) false
a1a8248a4677 some attempts to avoid sandwiching of actions stemming from single ESCAPE key event, to avoid potential conflict with ongoing text selection;
wenzelm
parents: 52498
diff changeset
   152
    else {
66592
cc93f86e005f tuned signature;
wenzelm
parents: 66591
diff changeset
   153
      JEdit_Lib.request_focus_view()
76487
304ae1a6e160 tuned signature, following hints by IntelliJ IDEA;
wenzelm
parents: 75393
diff changeset
   154
      stack.foreach(_.hide_popup())
52548
a1a8248a4677 some attempts to avoid sandwiching of actions stemming from single ESCAPE key event, to avoid potential conflict with ongoing text selection;
wenzelm
parents: 52498
diff changeset
   155
      stack = Nil
a1a8248a4677 some attempts to avoid sandwiching of actions stemming from single ESCAPE key event, to avoid potential conflict with ongoing text selection;
wenzelm
parents: 52498
diff changeset
   156
      true
a1a8248a4677 some attempts to avoid sandwiching of actions stemming from single ESCAPE key event, to avoid potential conflict with ongoing text selection;
wenzelm
parents: 52498
diff changeset
   157
    }
52664
e99a0a43720b deactivate/revoke mouse event more thoroughly, to avoid "Implicit change of text area buffer";
wenzelm
parents: 52548
diff changeset
   158
  }
51449
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
   159
}
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
   160
8d6e478934dc explicit handling of tooltip window stack -- avoid memory leak due to not-so-weak references to disposed windows (via event handlers and other aux. components);
wenzelm
parents: 51440
diff changeset
   161
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   162
class Pretty_Tooltip private(
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   163
  view: View,
53778
29eaacff4078 proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
wenzelm
parents: 53712
diff changeset
   164
  layered: JLayeredPane,
56497
0c63f3538639 dismiss popups more carefully (amending 7e8c11011fdf), notably allow mouse dragging within some tooltip, e.g. for text selection;
wenzelm
parents: 56341
diff changeset
   165
  val original_parent: JComponent,
53247
bd595338ca18 uniform use of isabelle.jEdit.Popup, based on generic screen location operations;
wenzelm
parents: 53230
diff changeset
   166
  location: Point,
64621
7116f2634e32 clarified module name;
wenzelm
parents: 58767
diff changeset
   167
  rendering: JEdit_Rendering,
52496
8188e5286662 avoid repeated window popup when the mouse is moved over the same content (again, see also cb677987b7e3 and 0a1db0d02628);
wenzelm
parents: 52494
diff changeset
   168
  private val results: Command.Results,
81398
f92ea68473f2 clarified signature with subtle change of semantics: output consists of individual messages that are formatted (and separated) internally;
wenzelm
parents: 81340
diff changeset
   169
  private val output: List[XML.Elem]
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
   170
) extends JPanel(new BorderLayout) {
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   171
  pretty_tooltip =>
49705
036c9a028dbd close tooltip window on escape;
wenzelm
parents: 49703
diff changeset
   172
57612
990ffb84489b clarified module name: facilitate alternative GUI frameworks;
wenzelm
parents: 56930
diff changeset
   173
  GUI_Thread.require {}
49725
f8eeff667076 explicit close button;
wenzelm
parents: 49712
diff changeset
   174
f8eeff667076 explicit close button;
wenzelm
parents: 49712
diff changeset
   175
f8eeff667076 explicit close button;
wenzelm
parents: 49712
diff changeset
   176
  /* controls */
f8eeff667076 explicit close button;
wenzelm
parents: 49712
diff changeset
   177
49727
2fe56b600698 make buttons closer to original mouse position;
wenzelm
parents: 49726
diff changeset
   178
  private val close = new Label {
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   179
    icon = rendering.tooltip_close_icon
49727
2fe56b600698 make buttons closer to original mouse position;
wenzelm
parents: 49726
diff changeset
   180
    tooltip = "Close tooltip window"
2fe56b600698 make buttons closer to original mouse position;
wenzelm
parents: 49726
diff changeset
   181
    listenTo(mouse.clicks)
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   182
    reactions += { case _: MouseClicked => Pretty_Tooltip.dismiss(pretty_tooltip) }
49727
2fe56b600698 make buttons closer to original mouse position;
wenzelm
parents: 49726
diff changeset
   183
  }
2fe56b600698 make buttons closer to original mouse position;
wenzelm
parents: 49726
diff changeset
   184
49726
2074197dc274 detach tooltip as dockable window;
wenzelm
parents: 49725
diff changeset
   185
  private val detach = new Label {
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   186
    icon = rendering.tooltip_detach_icon
49726
2074197dc274 detach tooltip as dockable window;
wenzelm
parents: 49725
diff changeset
   187
    tooltip = "Detach tooltip window"
2074197dc274 detach tooltip as dockable window;
wenzelm
parents: 49725
diff changeset
   188
    listenTo(mouse.clicks)
2074197dc274 detach tooltip as dockable window;
wenzelm
parents: 49725
diff changeset
   189
    reactions += {
50501
6f41f1646617 more careful handling of Dialog_Result, with active area and color feedback;
wenzelm
parents: 50206
diff changeset
   190
      case _: MouseClicked =>
81398
f92ea68473f2 clarified signature with subtle change of semantics: output consists of individual messages that are formatted (and separated) internally;
wenzelm
parents: 81340
diff changeset
   191
        Info_Dockable(view, rendering.snapshot, results, output)
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   192
        Pretty_Tooltip.dismiss(pretty_tooltip)
49726
2074197dc274 detach tooltip as dockable window;
wenzelm
parents: 49725
diff changeset
   193
    }
2074197dc274 detach tooltip as dockable window;
wenzelm
parents: 49725
diff changeset
   194
  }
2074197dc274 detach tooltip as dockable window;
wenzelm
parents: 49725
diff changeset
   195
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   196
  private val controls = new FlowPanel(FlowPanel.Alignment.Left)(close, detach) {
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   197
    background = rendering.tooltip_color
49709
3062937b45b3 tuned window position to fit on screen;
wenzelm
parents: 49708
diff changeset
   198
  }
51452
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
   199
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
   200
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   201
  /* text area */
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   202
71601
97ccf48c2f0c misc tuning based on hints by IntelliJ IDEA;
wenzelm
parents: 67547
diff changeset
   203
  val pretty_text_area: Pretty_Text_Area =
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   204
    new Pretty_Text_Area(view, () => Pretty_Tooltip.dismiss(pretty_tooltip), true) {
76487
304ae1a6e160 tuned signature, following hints by IntelliJ IDEA;
wenzelm
parents: 75393
diff changeset
   205
      override def get_background(): Option[Color] = Some(rendering.tooltip_color)
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   206
    }
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   207
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   208
  pretty_text_area.addFocusListener(new FocusAdapter {
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
   209
    override def focusGained(e: FocusEvent): Unit = {
52493
ee7218d28159 less intrusive border, notably on windows;
wenzelm
parents: 52492
diff changeset
   210
      tip_border(true)
56341
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   211
      Pretty_Tooltip.focus_delay.invoke()
52491
d435febab327 visually explicit focus (behaviour dependent on platform and look-and-feel);
wenzelm
parents: 52484
diff changeset
   212
    }
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
   213
    override def focusLost(e: FocusEvent): Unit = {
56341
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   214
      tip_border(false)
bfd13102eb54 observe focus change for all tooltips, e.g. relevant for focus change to unrelated components;
wenzelm
parents: 55825
diff changeset
   215
      Pretty_Tooltip.focus_delay.invoke()
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   216
    }
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   217
  })
51452
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
   218
55825
694833e3e4a0 tuned signature -- separate module Font_Info;
wenzelm
parents: 54376
diff changeset
   219
  pretty_text_area.resize(Font_Info.main(PIDE.options.real("jedit_popup_font_scale")))
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   220
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   221
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   222
  /* main content */
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   223
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
   224
  def tip_border(has_focus: Boolean): Unit = {
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   225
    pretty_tooltip.setBorder(new LineBorder(if (has_focus) Color.BLACK else Color.GRAY))
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   226
    pretty_tooltip.repaint()
52491
d435febab327 visually explicit focus (behaviour dependent on platform and look-and-feel);
wenzelm
parents: 52484
diff changeset
   227
  }
52493
ee7218d28159 less intrusive border, notably on windows;
wenzelm
parents: 52492
diff changeset
   228
  tip_border(true)
52491
d435febab327 visually explicit focus (behaviour dependent on platform and look-and-feel);
wenzelm
parents: 52484
diff changeset
   229
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   230
  override def getFocusTraversalKeysEnabled = false
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   231
  pretty_tooltip.setBackground(rendering.tooltip_color)
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   232
  pretty_tooltip.add(controls.peer, BorderLayout.NORTH)
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   233
  pretty_tooltip.add(pretty_text_area)
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   234
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   235
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   236
  /* popup */
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   237
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   238
  private val popup: Popup = {
72974
3afd293347cc clarified modules;
wenzelm
parents: 71704
diff changeset
   239
    val screen = GUI.screen_location(layered, location)
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 73367
diff changeset
   240
    val size = {
64621
7116f2634e32 clarified module name;
wenzelm
parents: 58767
diff changeset
   241
      val bounds = JEdit_Rendering.popup_bounds
57849
6d8f97d555d8 more robust popup geometry vs. formatted margin;
wenzelm
parents: 57612
diff changeset
   242
6d8f97d555d8 more robust popup geometry vs. formatted margin;
wenzelm
parents: 57612
diff changeset
   243
      val w_max = layered.getWidth min (screen.bounds.width * bounds).toInt
6d8f97d555d8 more robust popup geometry vs. formatted margin;
wenzelm
parents: 57612
diff changeset
   244
      val h_max = layered.getHeight min (screen.bounds.height * bounds).toInt
6d8f97d555d8 more robust popup geometry vs. formatted margin;
wenzelm
parents: 57612
diff changeset
   245
52492
0f0f80e41581 tighten tooltip size;
wenzelm
parents: 52491
diff changeset
   246
      val painter = pretty_text_area.getPainter
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   247
      val geometry = JEdit_Lib.window_geometry(pretty_tooltip, painter)
81340
30f7eb65d679 clarified signature;
wenzelm
parents: 80456
diff changeset
   248
      val metric = JEdit_Lib.font_metric(painter)
57849
6d8f97d555d8 more robust popup geometry vs. formatted margin;
wenzelm
parents: 57612
diff changeset
   249
      val margin =
81417
964b85e04f1f clarified margin operations (again, reverting 4794576828df);
wenzelm
parents: 81412
diff changeset
   250
        Rich_Text.make_margin(metric, rendering.tooltip_margin,
81403
5f401c2f7d33 more accurate pretty_margin for proportional fonts;
wenzelm
parents: 81402
diff changeset
   251
          limit = ((w_max - geometry.deco_width) / metric.average_width).toInt)
52492
0f0f80e41581 tighten tooltip size;
wenzelm
parents: 52491
diff changeset
   252
81433
c3793899b880 performance tuning: cache for Rich_Text.format, notably for incremental tracing;
wenzelm
parents: 81428
diff changeset
   253
      val formatted = Rich_Text.format(output, margin, metric, cache = PIDE.cache)
81428
257ec066b360 clarified signature and modules;
wenzelm
parents: 81417
diff changeset
   254
      val lines = Rich_Text.formatted_lines(formatted)
52492
0f0f80e41581 tighten tooltip size;
wenzelm
parents: 52491
diff changeset
   255
67546
2b30e03a7229 clarified lines: like split_lines;
wenzelm
parents: 66592
diff changeset
   256
      val h = painter.getLineHeight * lines + geometry.deco_height
52492
0f0f80e41581 tighten tooltip size;
wenzelm
parents: 52491
diff changeset
   257
      val margin1 =
81428
257ec066b360 clarified signature and modules;
wenzelm
parents: 81417
diff changeset
   258
        if (h <= h_max) Rich_Text.formatted_margin(metric, formatted)
81403
5f401c2f7d33 more accurate pretty_margin for proportional fonts;
wenzelm
parents: 81402
diff changeset
   259
        else margin.toDouble
81405
c519a14bd3f6 tuned GUI: avoid wasting space with proportional fonts;
wenzelm
parents: 81403
diff changeset
   260
      val w = (metric.unit * (margin1 + 1)).round.toInt + geometry.deco_width
53184
5d6ffb87ee08 confine popup to parent component, to avoid javax.swing.PopupFactory$HeavyWeightPopup and its problems with Linux window management and Mac OS X key handling;
wenzelm
parents: 53019
diff changeset
   261
57849
6d8f97d555d8 more robust popup geometry vs. formatted margin;
wenzelm
parents: 57612
diff changeset
   262
      new Dimension(w min w_max, h min h_max)
52492
0f0f80e41581 tighten tooltip size;
wenzelm
parents: 52491
diff changeset
   263
    }
81399
6b805c746e3b tuned signature;
wenzelm
parents: 81398
diff changeset
   264
    new Popup(layered, pretty_tooltip, screen.relative(layered, size), size)
51452
14e6d761ba1c extra tooltip_delay after window.dismiss operation, to avoid flickering of quick reactivation;
wenzelm
parents: 51451
diff changeset
   265
  }
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   266
76487
304ae1a6e160 tuned signature, following hints by IntelliJ IDEA;
wenzelm
parents: 75393
diff changeset
   267
  private def show_popup(): Unit = {
52484
23a09c639700 avoid potential race condition of focusLost/dismiss vs. popup.show;
wenzelm
parents: 52483
diff changeset
   268
    popup.show
73367
77ef8bef0593 clarified signature --- fewer warnings;
wenzelm
parents: 73359
diff changeset
   269
    pretty_text_area.requestFocus()
81398
f92ea68473f2 clarified signature with subtle change of semantics: output consists of individual messages that are formatted (and separated) internally;
wenzelm
parents: 81340
diff changeset
   270
    pretty_text_area.update(rendering.snapshot, results, output)
52484
23a09c639700 avoid potential race condition of focusLost/dismiss vs. popup.show;
wenzelm
parents: 52483
diff changeset
   271
  }
52478
0a1db0d02628 manage popup windows via PopupFactory, which prefers light-weight JComponent, but might fall back on JWindow (despite JPanel of 0f88591478e6);
wenzelm
parents: 52472
diff changeset
   272
76487
304ae1a6e160 tuned signature, following hints by IntelliJ IDEA;
wenzelm
parents: 75393
diff changeset
   273
  private def hide_popup(): Unit = popup.hide
52498
d802431fe356 clarified focus after dismiss;
wenzelm
parents: 52497
diff changeset
   274
76487
304ae1a6e160 tuned signature, following hints by IntelliJ IDEA;
wenzelm
parents: 75393
diff changeset
   275
  private def request_focus(): Unit = pretty_text_area.requestFocus()
49702
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
   276
}
696e91c0bc80 separate module Pretty_Tooltip;
wenzelm
parents:
diff changeset
   277