/* * Copyright (C) 2010 IsmAvatar <IsmAvatar@gmail.com> * Copyright (C) 2008 Quadduc <quadduc@gmail.com> * * This file is part of LateralGM. * LateralGM is free software and comes with ABSOLUTELY NO WARRANTY. * See LICENSE for details. */ package org.lateralgm.jedit; import java.awt.Color; import java.awt.Frame; import java.awt.Point; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowFocusListener; import java.util.ArrayList; import javax.swing.BorderFactory; import javax.swing.JList; import javax.swing.JScrollPane; import javax.swing.JWindow; import javax.swing.ListSelectionModel; import javax.swing.text.BadLocationException; public class CompletionMenu extends JWindow { private static final long serialVersionUID = 1L; protected JEditTextArea area; private JScrollPane scroll; private final Completion[] completions; private Completion[] options; private String word; private JList completionList; private KeyHandler keyHandler; protected int wordOffset; protected int wordPos; protected int wordLength; public CompletionMenu(Frame f, JEditTextArea a, int offset, int pos, int length, Completion[] c) { super(f); area = a; wordOffset = offset; wordPos = pos; wordLength = length; completions = c; keyHandler = new KeyHandler(); completionList = new JList(); completionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); completionList.addKeyListener(keyHandler); completionList.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (apply()) e.consume(); else dispose(); } }); scroll = new JScrollPane(completionList); scroll.setBorder(BorderFactory.createLineBorder(Color.BLACK)); add(scroll); getContentPane().setFocusTraversalKeysEnabled(false); addWindowFocusListener(new WindowFocusListener() { public void windowGainedFocus(WindowEvent e) { area.setCaretVisible(true); } public void windowLostFocus(WindowEvent e) { dispose(); } }); reset(); } public void dispose() { super.dispose(); area.requestFocus(); } public void setLocation() { int wl = area.getLineOfOffset(wordOffset); int lwo = wordOffset - area.getLineStartOffset(wl); Point p = area.getLocationOnScreen(); p.x += Math.min(area.getWidth(),Math.max(0,area.offsetToX(wl,lwo))); p.y += Math.min(area.getHeight(),Math.max(0,area.lineToY(wl + 1))) + 3; setLocation(p); } public void reset() { if (area.getSelectionStart() != wordOffset + wordPos) area.setCaretPosition(wordOffset + wordPos); String w = area.getText(wordOffset,wordPos); if (w == "") { options = completions; } else if ((options != null) && (word != null) && (w.startsWith(word))) { ArrayList<Completion> l = new ArrayList<Completion>(); for (Completion c : options) { if (c.match(w)) l.add(c); } options = l.toArray(new Completion[l.size()]); } else { ArrayList<Completion> l = new ArrayList<Completion>(); for (Completion c : completions) { if (c.match(w)) l.add(c); } options = l.toArray(new Completion[l.size()]); } if (options.length <= 0) { dispose(); return; } word = w; completionList.setListData(options); completionList.setVisibleRowCount(Math.min(options.length,8)); pack(); setLocation(); select(0); setVisible(true); requestFocus(); completionList.requestFocusInWindow(); } public void select(int n) { completionList.setSelectedIndex(n); completionList.ensureIndexIsVisible(n); } public void selectRelative(int n) { int s = completionList.getModel().getSize(); if (s <= 1) return; int i = completionList.getSelectedIndex(); select((s + ((i + n) % s)) % s); } public boolean apply() { return apply('\0'); } public boolean apply(char input) { Object o = completionList.getSelectedValue(); if (o instanceof Completion) { Completion c = (Completion) o; dispose(); return c.apply(area,input,wordOffset,wordPos,wordLength); } return false; } public void setSelectedText(String s) { int ss = area.getSelectionStart(); int se = area.getSelectionEnd(); int sl = se - ss; int nl = s.length(); int ne = ss + nl; int[] w = { wordOffset,wordOffset + wordPos,wordOffset + wordLength }; for (int i = 1; i < 3; i++) { if (w[i] >= (i == 0 ? se + 1 : se)) w[i] += nl - sl; else if (w[i] >= ss) w[i] = i == 0 ? ss : ne; } area.setSelectedText(s); wordOffset = w[0]; wordPos = w[1] - w[0]; wordLength = w[2] - w[0]; } public abstract static class Completion { protected String name; public boolean match(String start) { return match(start,name) >= 0; } public abstract boolean apply(JEditTextArea a, char input, int offset, int pos, int length); public static boolean replace(SyntaxDocument d, int offset, int length, String text) { try { d.replace(offset,length,text,null); } catch (BadLocationException ble) { return false; } return true; } public static int match(String input, String name) { if (input.equals(name)) return 0; if (name.startsWith(input)) return 1; String il = input.toLowerCase(); String nl = name.toLowerCase(); if (il.equals(nl)) return 2; if (nl.startsWith(il)) return 3; String re = "(?<!(^|_))" + (name.matches("[A-Z_]+") ? "." : "[a-z_]"); String ns = name.replaceAll(re,"").toLowerCase(); if (il.equals(ns)) return 4; if (ns.startsWith(il)) return 5; return -1; } public String toString() { return name; } } public static class WordCompletion extends Completion { public WordCompletion(String w) { name = w; } public boolean apply(JEditTextArea a, char input, int offset, int pos, int length) { String s = name + (input != '\0' ? String.valueOf(input) : ""); int l = input != '\0' ? pos : length; SyntaxDocument d = a.getDocument(); if (!replace(d,offset,l,s)) return false; a.setCaretPosition(offset + s.length()); return true; } } private class KeyHandler extends KeyAdapter { public KeyHandler() { super(); } public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_BACK_SPACE: if (area.getSelectionStart() == area.getSelectionEnd()) { if (wordPos <= 0) dispose(); else try { area.getDocument().remove(wordOffset + wordPos - 1,1); wordPos -= 1; wordLength -= 1; } catch (BadLocationException ble) { dispose(); } } else setSelectedText(""); e.consume(); reset(); break; case KeyEvent.VK_LEFT: if (wordPos <= 0) dispose(); else wordPos -= 1; e.consume(); reset(); break; case KeyEvent.VK_RIGHT: if (wordPos >= wordLength) dispose(); else wordPos += 1; e.consume(); reset(); break; case KeyEvent.VK_ENTER: if (apply()) e.consume(); else dispose(); break; case KeyEvent.VK_ESCAPE: dispose(); e.consume(); break; case KeyEvent.VK_UP: selectRelative(-1); e.consume(); break; case KeyEvent.VK_DOWN: selectRelative(1); e.consume(); break; } } public void keyTyped(KeyEvent e) { if ((e.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0) return; char c = e.getKeyChar(); if (c == KeyEvent.VK_BACK_SPACE) return; String s = String.valueOf(c); if (s.matches("[^\\v\\t\\w]")) { apply(c); e.consume(); dispose(); return; } setSelectedText(s); e.consume(); reset(); } } }