Completion popup based on javax.swing.PopupFactory, which has better cross-platform chances than JWindow (cf. org/gjt/jedit/gui/CompletionPopup.java);
authorwenzelm
Tue, 13 Aug 2013 22:25:24 +0200
changeset 53023 f127e949389f
parent 53022 5f4703de4140
child 53024 e0968e1f6fe9
Completion popup based on javax.swing.PopupFactory, which has better cross-platform chances than JWindow (cf. org/gjt/jedit/gui/CompletionPopup.java);
src/Tools/jEdit/lib/Tools/jedit
src/Tools/jEdit/src/completion_popup.scala
--- a/src/Tools/jEdit/lib/Tools/jedit	Tue Aug 13 20:35:45 2013 +0200
+++ b/src/Tools/jEdit/lib/Tools/jedit	Tue Aug 13 22:25:24 2013 +0200
@@ -9,6 +9,7 @@
 
 declare -a SOURCES=(
   "src/active.scala"
+  "src/completion_popup.scala"
   "src/dockable.scala"
   "src/document_model.scala"
   "src/document_view.scala"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Tools/jEdit/src/completion_popup.scala	Tue Aug 13 22:25:24 2013 +0200
@@ -0,0 +1,195 @@
+/*  Title:      Tools/jEdit/src/completion_popup.scala
+    Author:     Makarius
+
+Completion popup based on javax.swing.PopupFactory.
+*/
+
+package isabelle.jedit
+
+
+import isabelle._
+
+import java.awt.{Point, BorderLayout, Dimension}
+import java.awt.event.{KeyEvent, KeyAdapter, MouseEvent, MouseAdapter, FocusAdapter, FocusEvent}
+import javax.swing.{JPanel, JComponent, PopupFactory}
+
+import scala.swing.{ListView, ScrollPane}
+import scala.swing.event.MouseClicked
+
+import org.gjt.sp.jedit.View
+
+
+object Completion_Popup
+{
+  sealed case class Item(name: String, text: String)
+  { override def toString: String = text }
+
+  def apply(
+    opt_view: Option[View],
+    parent: JComponent,
+    screen_point: Point,
+    items: List[Item],
+    complete: Item => Unit): Completion_Popup =
+  {
+    Swing_Thread.require()
+
+    require(!items.isEmpty)
+
+    val completion = new Completion_Popup(opt_view, parent, screen_point, items, complete)
+    completion.show_popup()
+    completion
+  }
+}
+
+
+class Completion_Popup private(
+  opt_view: Option[View],
+  parent: JComponent,
+  screen_point: Point,
+  items: List[Completion_Popup.Item],
+  complete: Completion_Popup.Item => Unit) extends JPanel(new BorderLayout)
+{
+  completion =>
+
+  Swing_Thread.require()
+
+
+  /* list view */
+
+  private val list_view = new ListView(items)
+  {
+    font = parent.getFont
+  }
+  list_view.selection.intervalMode = ListView.IntervalMode.Single
+  list_view.peer.setFocusTraversalKeysEnabled(false)
+  list_view.peer.setVisibleRowCount(items.length min 8)
+  list_view.peer.setSelectedIndex(0)
+
+  private def complete_selected(): Boolean =
+  {
+    list_view.selection.items.toList match {
+      case item :: _ => complete(item); true
+      case _ => false
+    }
+  }
+
+  private def move_items(n: Int)
+  {
+    val i = list_view.peer.getSelectedIndex
+    val j = ((i + n) min (items.length - 1)) max 0
+    if (i != j) {
+      list_view.peer.setSelectedIndex(j)
+      list_view.peer.ensureIndexIsVisible(j)
+    }
+  }
+
+  private def move_pages(n: Int)
+  {
+    val page_size = list_view.peer.getVisibleRowCount - 1
+    move_items(page_size * n)
+  }
+
+
+  /* event handling */
+
+  private val key_handler = new KeyAdapter {
+    override def keyPressed(e: KeyEvent)
+    {
+      if (!e.isConsumed) {
+        e.getKeyCode match {
+          case KeyEvent.VK_TAB =>
+            if (complete_selected()) e.consume
+            hide_popup()
+          case KeyEvent.VK_ESCAPE =>
+            hide_popup()
+            e.consume
+          case KeyEvent.VK_UP => move_items(-1); e.consume
+          case KeyEvent.VK_DOWN => move_items(1); e.consume
+          case KeyEvent.VK_PAGE_UP => move_pages(-1); e.consume
+          case KeyEvent.VK_PAGE_DOWN => move_pages(1); e.consume
+          case _ =>
+            if (e.isActionKey || e.isAltDown || e.isMetaDown || e.isControlDown)
+              hide_popup()
+        }
+      }
+      if (!e.isConsumed) pass_to_view(e)
+    }
+
+    override def keyTyped(e: KeyEvent)
+    {
+      if (!e.isConsumed) pass_to_view(e)
+    }
+  }
+
+  private def pass_to_view(e: KeyEvent)
+  {
+    opt_view match {
+      case Some(view) if view.getKeyEventInterceptor == key_handler =>
+        try {
+          view.setKeyEventInterceptor(null)
+          view.getInputHandler().processKeyEvent(e, View.ACTION_BAR, false)
+        }
+        finally { view.setKeyEventInterceptor(key_handler) }
+      case _ =>
+    }
+  }
+
+  list_view.peer.addKeyListener(key_handler)
+
+  list_view.peer.addMouseListener(new MouseAdapter {
+    override def mouseClicked(e: MouseEvent) {
+      if (complete_selected()) e.consume
+      hide_popup()
+    }
+  })
+
+  list_view.peer.addFocusListener(new FocusAdapter {
+    override def focusLost(e: FocusEvent) { hide_popup() }
+  })
+
+
+  /* main content */
+
+  override def getFocusTraversalKeysEnabled = false
+
+  completion.add((new ScrollPane(list_view)).peer.asInstanceOf[JComponent])
+
+
+  /* popup */
+
+  private val popup =
+  {
+    val screen_bounds = JEdit_Lib.screen_bounds(screen_point)
+
+    val geometry = JEdit_Lib.window_geometry(completion, completion)
+
+    val w = geometry.width min (screen_bounds.width / 2)
+    val h = geometry.height min (screen_bounds.height / 2)
+
+    completion.setSize(new Dimension(w, h))
+    completion.setPreferredSize(new Dimension(w, h))
+
+    val x = screen_point.x min (screen_bounds.x + screen_bounds.width - w)
+    val y = screen_point.y min (screen_bounds.y + screen_bounds.height - h)
+    PopupFactory.getSharedInstance.getPopup(parent, completion, x, y)
+  }
+
+  def show_popup()
+  {
+    opt_view.foreach(view => view.setKeyEventInterceptor(key_handler))
+    popup.show
+    list_view.requestFocus
+  }
+
+  def hide_popup()
+  {
+    opt_view match {
+      case Some(view) if view.getKeyEventInterceptor == key_handler =>
+        view.setKeyEventInterceptor(null)
+      case _ =>
+    }
+    popup.hide
+    parent.requestFocus
+  }
+}
+