Package entagged.tageditor.util.swing

Source Code of entagged.tageditor.util.swing.QuickHelpFieldExtender$CompletionModel

/*
*  ********************************************************************   **
*  Copyright notice                                                       **
*  **                                     **
*  (c) 2003 Entagged Developpement Team                           **
*  http://www.sourceforge.net/projects/entagged                           **
*  **                                     **
*  All rights reserved                                                    **
*  **                                     **
*  This script is part of the Entagged project. The Entagged          **
*  project is free software; you can redistribute it and/or modify        **
*  it under the terms of the GNU General Public License as published by   **
*  the Free Software Foundation; either version 2 of the License, or      **
*  (at your option) any later version.                                    **
*  **                                     **
*  The GNU General Public License can be found at                         **
*  http://www.gnu.org/copyleft/gpl.html.                                  **
*  **                                     **
*  This copyright notice MUST APPEAR in all copies of the file!           **
*  ********************************************************************
*/
package entagged.tageditor.util.swing;

import java.awt.Component;
import java.awt.Point;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import javax.swing.ComboBoxEditor;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.JTextComponent;

/**
* This class is intended to act as a normal JTextField with one enhancement.
* The field recieves a list of strings for completition. If the beginning of
* such strings is written, a window will pop up and shows all matching in a
* selectable list. If an item is selected with the mouse or by pressing enter,
* the string will be written. <br>
* If you know eclipse, similiar to its auto completement functionality.
*
* @author Christian Laireiter
*/
public class QuickHelpFieldExtender extends KeyAdapter implements
    DocumentListener {

  /**
   * This model is used to display the completion values.
   */
  private final class CompletionModel implements ListModel {
    private Vector listeners = new Vector();

    /**
     * (overridden)
     *
     * @see javax.swing.ListModel#addListDataListener(javax.swing.event.ListDataListener)
     */
    public void addListDataListener(ListDataListener l) {
      listeners.add(l);
    }

    /**
     * Notifies all registered listeners, that the contents have been
     * changed.
     *
     */
    public void fireChange() {
      Iterator it = listeners.iterator();
      while (it.hasNext()) {
        ListDataListener ldl = (ListDataListener) it.next();
        ldl.contentsChanged(new ListDataEvent(window.getValueList(),
            ListDataEvent.CONTENTS_CHANGED, 0, getSize()));
      }
    }

    /**
     * (overridden)
     *
     * @see javax.swing.ListModel#getElementAt(int)
     */
    public Object getElementAt(int index) {
      List matches = getMatches();
      index = ((Integer) matches.get(index)).intValue();
      return values.get(index)
          + " - "
          + (descriptions.get(index) != null ? descriptions
              .get(index) : "");
    }

    /**
     * (overridden)
     *
     * @see javax.swing.ListModel#getSize()
     */
    public int getSize() {
      return getMatches().size();
    }

    /**
     * (overridden)
     *
     * @see javax.swing.ListModel#removeListDataListener(javax.swing.event.ListDataListener)
     */
    public void removeListDataListener(ListDataListener l) {
      listeners.remove(l);
    }

  }

  /**
   * This field stores the descriptions for the correspoinding values in
   * {@link #values}.
   */
  protected Vector descriptions = new Vector();

  /**
   * When test is inserted, the system time in milliseconds is stored here.
   */
  protected long lastInsert;

  protected CompletionModel model;

  /**
   * Here the {@link JTextComponent}is stored on which current instance is
   * working.
   */
  protected JTextComponent textField;

  /**
   * This field stores the values for completement.
   */
  protected Vector values = new Vector();

  /**
   * This field stores the window which will allow the user to select a
   * completition.
   */
  protected QuickHelpSelectionWindow window = null;

  /**
   * Creates an instance with its on text field.
   */
  public QuickHelpFieldExtender() {
    this.textField = new JTextField();
    this.textField.setBorder(null);
    initialize();
  }

  /**
   * This instance creates an instance, which will listen to the given field
   * in order to use completion functionality.
   *
   * @param listenTo
   *            text field where the input is done.
   */
  public QuickHelpFieldExtender(JTextField listenTo) {
    assert listenTo != null : "Argument must not be null";
    this.textField = listenTo;
    initialize();
  }

  /**
   * This method adds a value for completement.
   *
   * @param value
   *            Value, which will be typed.
   * @param description
   *            Description for the help window. Can be <code>null</code>.
   */
  public void addCompletionValue(String value, String description) {
    if (value == null || value.length() == 0) {
      throw new IllegalArgumentException(
          "Value must not be null nor empty.");
    }
    if (!values.contains(value)) {
      this.values.add(value);
      this.descriptions.add(description);
    } else
      throw new IllegalStateException("The value: \"" + value
          + "\" is already inserted.");
  }

  /**
   * This method will look where the test cursor is displayed and set the
   * completement windows location next to it.
   */
  protected void adjustPosition() {
    Point caretPosition = this.getTextField().getCaret()
        .getMagicCaretPosition();
    if (caretPosition != null) {
      caretPosition.y = this.getTextField().getLocationOnScreen().y - 10
          - getSelectionWindow().getSize().height;
      caretPosition.x += this.getTextField().getLocationOnScreen().x
          - (getSelectionWindow().getWidth() / 2);
      /*
       * Now make some adjustments, if the window would be partly outside
       * the screen.
       */
      if (caretPosition.x < 0) {
        caretPosition.x = 0;
      }
      if (caretPosition.y < 0) {
        caretPosition.y = 0;
      }
      /*
       * The other possibility will be ignored, because I can't imagine
       * this would happen on normal use. Same with overlaying the textfield.
       */
      getSelectionWindow().setLocation(caretPosition);
    }
  }

  /**
   * (overridden)
   *
   * @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent)
   */
  public void changedUpdate(DocumentEvent e) {
    this.lastInsert = System.currentTimeMillis();
    triggerCompletement();
  }

  /**
   * Creates a editor for use with comboboxes.
   *
   * @return a combobox editor.
   */
  public ComboBoxEditor createComboBoxEditor() {
    return new ComboBoxEditor() {

      public void addActionListener(ActionListener l) {
        // Nope
      }

      public Component getEditorComponent() {
        return textField;
      }

      public Object getItem() {
        return textField.getText();
      }

      public void removeActionListener(ActionListener l) {
        // nope
      }

      public void selectAll() {
        textField.selectAll();
      }

      public void setItem(Object anObject) {
        textField.setText(anObject.toString());
      }
    };
  }

  /**
   * This method will read the string form the current textcursor back to a
   * space character.
   *
   * @return word between space character till text cursor.
   */
  protected String getLastWord() {
    StringBuffer result = new StringBuffer();
    String text = textField.getText();
    if (text.length() == 0) {
      return "";
    }
    int caretPos = this.textField.getCaretPosition();
    int pointer = caretPos - 1;

    while (pointer > 0) {
      if (!isDelimiter(text.charAt(pointer))) {
        pointer--;
      } else {
        pointer++;
        break;
      }
    }
    if (pointer >= 0 && pointer < caretPos)
      result.append(text.substring(pointer, caretPos));

    return result.toString();
  }

  protected List getMatches() {
    ArrayList result = new ArrayList();
    String lastWort = getLastWord();
    for (int i = 0; lastWort.length() > 0 && i < values.size(); i++) {
      if (values.get(i).toString().startsWith(lastWort)) {
        result.add(new Integer(i));
      }
    }
    return result;
  }

  /**
   * This method returns the window for displaying completions. <br>
   *
   * @return Window for selection completions.
   */
  public QuickHelpSelectionWindow getSelectionWindow() {
    if (this.window == null) {
      this.window = new QuickHelpSelectionWindow(this,
          this.model = new CompletionModel());
    }
    return this.window;
  }

  /**
   * This method returns the text field this object is working with.
   *
   * @return Text field.
   */
  public JTextComponent getTextField() {
    return this.textField;
  }

  /**
   * Initializes this Field
   */
  private void initialize() {
    registerTo(textField);
  }

  /**
   * (overridden)
   *
   * @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent)
   */
  public void insertUpdate(DocumentEvent e) {
    this.lastInsert = System.currentTimeMillis();
    triggerCompletement();
  }

  /**
   * Searches in the registered values for one which starts with the given
   * string. If so <code>true</code> is returned.
   *
   * @param start
   *            The string which is searched.
   * @return <code>true</code> if one value starts with given string.
   */
  protected boolean isAnyValueStartingWith(String start) {
    Iterator iterator = values.iterator();
    while (iterator.hasNext() && start.length() > 0) {
      String current = (String) iterator.next();
      if (current.startsWith(start) && !current.equals(start))
        return true;
    }
    return false;
  }

  /**
   * @param c
   * @return
   */
  private boolean isDelimiter(char c) {
    return !Character.isLetterOrDigit(c) && c != '<';
  }

  /**
   * (overridden)
   *
   * @see java.awt.event.KeyAdapter#keyTyped(java.awt.event.KeyEvent)
   */
  public void keyPressed(KeyEvent e) {
    if (e.getSource() == this.textField
        || e.getSource() == this.window.getValueList()
        && !e.isConsumed()) {
      if (getSelectionWindow().isVisible()) {
        if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
          getSelectionWindow().setVisible(false);
          e.consume();
        }
        if (e.getKeyCode() == KeyEvent.VK_LEFT
            || e.getKeyCode() == KeyEvent.VK_RIGHT) {
          this.model.fireChange();
        }
        final JList list = getSelectionWindow().getValueList();
        if (e.getKeyCode() == KeyEvent.VK_UP) {
          if (list.getSelectedIndex() > 0) {
            list.setSelectedIndex(list.getSelectedIndex() - 1);
          }
          e.consume();
        }
        if (e.getKeyCode() == KeyEvent.VK_DOWN) {
          if (list.getSelectedIndex() < list.getModel().getSize() - 1) {
            list.setSelectedIndex(list.getSelectedIndex() + 1);
          }
          e.consume();
        }
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
          SwingUtilities.invokeLater(new Runnable() {
            public void run() {
              select(list.getSelectedIndex());
            }
          });
          e.consume();
        }
      }
    }
  }

  /**
   * This method will change the object to now work with the given textfield.
   *
   * @param toRegister
   *            The textfield
   */
  public void registerTo(JTextComponent toRegister) {
    if (this.textField != null) {
      this.textField.getDocument().removeDocumentListener(this);
      this.textField.removeKeyListener(this);
    }
    toRegister.getDocument().addDocumentListener(this);
    toRegister.addKeyListener(this);
    this.textField = toRegister;
  }

  /**
   * This removes the given completion value.
   *
   * @param value
   *            The value which shouldn't be provided any more.
   */
  public void removeCompletionValue(String value) {
    assert value != null;
    int index = -1;
    while ((index = values.indexOf(value)) != -1) {
      values.remove(index);
      descriptions.remove(index);
    }
  }

  /**
   * (overridden)
   *
   * @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent)
   */
  public void removeUpdate(DocumentEvent e) {
    this.lastInsert = System.currentTimeMillis();
    triggerCompletement();
  }

  /**
   * @param index
   */
  public void select(int index) {
    List matches = getMatches();
    index = ((Integer) matches.get(index)).intValue();
    String word = getLastWord();
    String value = (String) values.get(index);
    value = value.substring(word.length(), value.length());
    try {
      AttributeSet set = getTextField().getDocument().getRootElements()[0]
          .getAttributes();
      getTextField().getDocument().insertString(
          getTextField().getCaretPosition(), value, set);
    } catch (Exception e) {
      e.printStackTrace();
    }
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        getSelectionWindow().setVisible(false);
        textField.requestFocus();
      }
    });
  }

  /**
   * This method will adjust the position of the completion window and set it
   * visible. <br>
   * This method has been introduced for subclasses, in order to interrupt the
   * displaying of the window. <br>
   */
  protected void showWindow() {
    getSelectionWindow().setVisible(true);
    adjustPosition();
  }

  /**
   * This method will start a Thread which will display the completion window.
   */
  private void triggerCompletement() {
    if (SwingUtilities.getRoot(textField) == null) {
      return;
    }
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        final long started = lastInsert = System.currentTimeMillis();
        if (!getSelectionWindow().isVisible()) {
          if (isAnyValueStartingWith(getLastWord())) {
            new Timer(true).schedule(new TimerTask() {
              public void run() {
                if (lastInsert == started
                    && !getSelectionWindow().isVisible()) {
                  model.fireChange();
                  showWindow();
                }
              }
            }, 500);
          }
        } else {
          if (!isAnyValueStartingWith(getLastWord())) {
            window.setVisible(false);
          } else {
            // Position can be updated if all events have been handled
            // (repainting after typing and so on)
            SwingUtilities.invokeLater(new Runnable() {
              public void run() {
                adjustPosition();
                model.fireChange();
              }
            });
          }
        }
      }
    });
  }
}
TOP

Related Classes of entagged.tageditor.util.swing.QuickHelpFieldExtender$CompletionModel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.