# HG changeset patch # User wenzelm # Date 1376425524 -7200 # Node ID f127e949389f173abc8699af55bccaf4c5a7a5e2 # Parent 5f4703de414066e330ad5f41ae76e5f9213771ea Completion popup based on javax.swing.PopupFactory, which has better cross-platform chances than JWindow (cf. org/gjt/jedit/gui/CompletionPopup.java); diff -r 5f4703de4140 -r f127e949389f src/Tools/jEdit/lib/Tools/jedit --- 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" diff -r 5f4703de4140 -r f127e949389f src/Tools/jEdit/src/completion_popup.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 + } +} +