/*
* DrakkarKeel - An Enterprise Collaborative Search Platform
*
* The contents of this file are subject under the terms described in the
* DRAKKARKEEL_LICENSE file included in this distribution; you may not use this
* file except in compliance with the License.
*
* 2013-2014 DrakkarKeel Platform.
*/
package drakkar.cover.swing.plaf.basic;
import drakkar.cover.swing.JQueryField;
import drakkar.cover.swing.QueryFieldListModel;
import drakkar.cover.swing.TermListItem;
import drakkar.cover.swing.facade.SearchFacade;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import javax.accessibility.AccessibleContext;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.event.*;
/**
* This is a basic implementation of the <code>QueryFieldPopup</code> interface.
*
* This class represents the ui for the popup portion of the query field.
*/
public class BasicQueryFieldPopup extends JPopupMenu implements QueryFieldPopup {
static final ListModel EmptyListModel = new EmptyListModelClass();
private static Border LIST_BORDER = new LineBorder(Color.BLACK, 1);
protected JQueryField queryField;
protected SearchFacade searchFacade;
protected QueryFieldListModel model;
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the accessor methods instead.
*
* @see #getList
* @see #createList
*/
protected JList list;
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the create method instead
*
* @see #createScroller
*/
protected JScrollPane scroller;
/**
* As of Java 2 platform v1.4 this previously undocumented field is no
* longer used.
*/
protected boolean valueIsAdjusting = false;
// Listeners that are required by the QueryFieldPopup interface
/**
* Implementation of all the listener classes.
*/
private Handler handler;
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the accessor or create methods instead.
*
* @see #getMouseMotionListener
* @see #createMouseMotionListener
*/
protected MouseMotionListener mouseMotionListener;
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the accessor or create methods instead.
*
* @see #getMouseListener
* @see #createMouseListener
*/
protected MouseListener mouseListener;
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the accessor or create methods instead.
*
* @see #getKeyListener
* @see #createKeyListener
*/
protected KeyListener keyListener;
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the create method instead.
*
* @see #createListSelectionListener
*/
protected ListSelectionListener listSelectionListener;
// Listeners that are attached to the list
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the create method instead.
*
* @see #createListMouseListener
*/
protected MouseListener listMouseListener;
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the create method instead
*
* @see #createListMouseMotionListener
*/
protected MouseMotionListener listMouseMotionListener;
// Added to the query field for bound properties
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the create method instead
*
* @see #createPropertyChangeListener
*/
protected PropertyChangeListener propertyChangeListener;
// Added to the query field model
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the create method instead
*
* @see #createListDataListener
*/
protected ListDataListener listDataListener;
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the create method instead
*
* @see #createQueryFieldFocusListener
*/
protected FocusListener focusListener;
/**
* This protected field is implementation specific. Do not access directly
* or override. Use the create method instead
*
* @see #createQueryFieldHierarchyBoundsListener
*/
protected HierarchyBoundsListener hierarchyBoundsListener;
// /**
// * This protected field is implementation specific. Do not access directly
// * or override. Use the create method instead
// *
// * @see #createItemListener
// */
// protected ItemListener itemListener;
/**
* This protected field is implementation specific. Do not access directly
* or override.
*/
protected Timer autoscrollTimer;
protected boolean hasEntered = false;
protected boolean isAutoScrolling = false;
protected int scrollDirection = SCROLL_UP;
protected static final int SCROLL_UP = 0;
protected static final int SCROLL_DOWN = 1;
protected JPopupMenu auxiliarPopup;
private static class EmptyListModelClass implements ListModel,
Serializable {
public int getSize() {
return 0;
}
public Object getElementAt(int index) {
return null;
}
public void addListDataListener(ListDataListener l) {
}
public void removeListDataListener(ListDataListener l) {
}
};
//========================================
// begin QueryFieldPopup method implementations
//
/**
* Implementation of QueryFieldPopup.show().
*/
public void show() {
// setListSelection(queryField.getSelectedIndex()); // no me hace falta, solo es para seleccionar el item de la lista que está en el textfield
Point location = getPopupLocation();
show(queryField, location.x, location.y);
}
/**
* Implementation of QueryFieldPopup.hide().
*/
public void hide() {
MenuSelectionManager manager = MenuSelectionManager.defaultManager();
MenuElement[] selection = manager.getSelectedPath();
for (int i = 0; i < selection.length; i++) {
if (selection[i] == this) {
manager.clearSelectedPath();
break;
}
}
if (selection.length > 0) {
queryField.repaint();
}
}
/**
* Implementation of QueryFieldPopup.getList().
*/
public JList getList() {
return list;
}
/**
* Implementation of QueryFieldPopup.getMouseListener().
*
* @return a <code>MouseListener</code> or null
* @see QueryFieldPopup#getMouseListener
*/
public MouseListener getMouseListener() {
if (mouseListener == null) {
mouseListener = createMouseListener();
}
return mouseListener;
}
/**
* Implementation of QueryFieldPopup.getMouseMotionListener().
*
* @return a <code>MouseMotionListener</code> or null
* @see QueryFieldPopup#getMouseMotionListener
*/
public MouseMotionListener getMouseMotionListener() {
if (mouseMotionListener == null) {
mouseMotionListener = createMouseMotionListener();
}
return mouseMotionListener;
}
/**
* Implementation of QueryFieldPopup.getKeyListener().
*
* @return a <code>KeyListener</code> or null
* @see QueryFieldPopup#getKeyListener
*/
public KeyListener getKeyListener() {
if (keyListener == null) {
keyListener = createKeyListener();
}
return keyListener;
}
/**
* Called when the UI is uninstalling. Since this popup isn't in the component
* tree, it won't get it's uninstallUI() called. It removes the listeners that
* were added in addComboBoxListeners().
*/
public void uninstallingUI() {
if (propertyChangeListener != null) {
queryField.removePropertyChangeListener(propertyChangeListener);
}
uninstallQueryFieldModelListeners(queryField.getModel());
uninstallKeyboardActions();
uninstallListListeners();
uninstallQueryFieldListeners();
// We do this, otherwise the listener the ui installs on
// the model (the combobox model in this case) will keep a
// reference to the list, causing the list (and us) to never get gced.
list.setModel(EmptyListModel);
}
//
// end QueryFieldPopup method implementations
//======================================
/**
* Removes the listeners from the query field model
*
* @param model The query field model to install listeners
* @see #installComboBoxModelListeners
*/
protected void uninstallQueryFieldModelListeners(QueryFieldListModel model) {
if (model != null && listDataListener != null) {
model.removeListDataListener(listDataListener);
}
}
protected void uninstallKeyboardActions() {
// XXX - shouldn't call this method
// queryField.unregisterKeyboardAction( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ) );
}
//===================================================================
// begin Initialization routines
//
public BasicQueryFieldPopup(JQueryField queryField) {
super();
setName("QueryFieldPopup.popup");
this.queryField = queryField;
this.model = queryField.getModel();
setLightWeightPopupEnabled(this.queryField.isLightWeightPopupEnabled());
// UI construction of the popup.
this.list = createList();
this.list.setName("QueryField.list");
// this.listTerm = queryField.getModel().getValues();
this.scroller = createScroller();
this.scroller.setName("QueryField.scrollPane");
this.auxiliarPopup = createAuxiliarPopup();
configureList();
configureScroller();
configurePopup();
configureAuxiliarPopup();
installQueryFieldListeners();
installKeyboardActions();
list.setComponentPopupMenu(auxiliarPopup);
}
// Overriden PopupMenuListener notification methods to inform query field
// PopupMenuListeners.
@Override
protected void firePopupMenuWillBecomeVisible() {
super.firePopupMenuWillBecomeVisible();
queryField.firePopupMenuWillBecomeVisible();
}
@Override
protected void firePopupMenuWillBecomeInvisible() {
super.firePopupMenuWillBecomeInvisible();
queryField.firePopupMenuWillBecomeInvisible();
}
@Override
protected void firePopupMenuCanceled() {
super.firePopupMenuCanceled();
queryField.firePopupMenuCanceled();
}
/**
* Creates a listener
* that will watch for mouse-press and release events on the query field.
*
* <strong>Warning:</strong>
* When overriding this method, make sure to maintain the existing
* behavior.
*
* @return a <code>MouseListener</code> which will be added to
* the query field or null
*/
protected MouseListener createMouseListener() {
return getHandler();
}
/**
* Creates the mouse motion listener which will be added to the combo
* box.
*
* <strong>Warning:</strong>
* When overriding this method, make sure to maintain the existing
* behavior.
*
* @return a <code>MouseMotionListener</code> which will be added to
* the query field or null
*/
protected MouseMotionListener createMouseMotionListener() {
return getHandler();
}
/**
* Creates the key listener that will be added to the query field. If
* this method returns null then it will not be added to the query field.
*
* @return a <code>KeyListener</code> or null
*/
protected KeyListener createKeyListener() {
return getHandler();
}
/**
* Creates the focus listener that will be added to the query field. If
* this method returns null then it will not be added to the query field.
*
* @return a <code>KeyListener</code> or null
*/
protected FocusListener createFocusListener() {
return getHandler();
}
/**
* Creates the hierarchy bounds listener that will be added to the query field. If
* this method returns null then it will not be added to the query field.
*
* @return a <code>HierarchyBoundsListener</code> or null
*/
protected HierarchyBoundsListener createHierarchyBoundsListener() {
return getHandler();
}
/**
* Creates a list selection listener that watches for selection changes in
* the popup's list. If this method returns null then it will not
* be added to the popup list.
*
* @return an instance of a <code>ListSelectionListener</code> or null
*/
protected ListSelectionListener createListSelectionListener() {
return null;
// return l;
}
/**
* Creates a list data listener which will be added to the
* <code>ComboBoxModel</code>. If this method returns null then
* it will not be added to the query field model.
*
* @return an instance of a <code>ListDataListener</code> or null
*/
protected ListDataListener createListDataListener() {
return null;
}
/**
* Creates a mouse listener that watches for mouse events in
* the popup's list. If this method returns null then it will
* not be added to the query field.
*
* @return an instance of a <code>MouseListener</code> or null
*/
protected MouseListener createListMouseListener() {
return getHandler();
}
/**
* Creates a mouse motion listener that watches for mouse motion
* events in the popup's list. If this method returns null then it will
* not be added to the query field.
*
* @return an instance of a <code>MouseMotionListener</code> or null
*/
protected MouseMotionListener createListMouseMotionListener() {
return getHandler();
}
/**
* Creates a <code>PropertyChangeListener</code> which will be added to
* the query field. If this method returns null then it will not
* be added to the query field.
*
* @return an instance of a <code>PropertyChangeListener</code> or null
*/
protected PropertyChangeListener createPropertyChangeListener() {
return getHandler();
}
private Handler getHandler() {
if (handler == null) {
handler = new Handler();
}
return handler;
}
/**
* Creates the JList used in the popup to display
* the items in the query field model. This method is called when the UI class
* is created.
*
* @return a <code>JList</code> used to display the query field items
*/
protected JList createList() {
return new JList(queryField.getModel()) {
@Override
public void processMouseEvent(MouseEvent e) {
if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) {
// Fix for 4234053. Filter out the Control Key from the list.
// ie., don't allow CTRL key deselection.
Toolkit toolkit = Toolkit.getDefaultToolkit();
e = new MouseEvent((Component) e.getSource(), e.getID(), e.getWhen(),
e.getModifiers() ^ toolkit.getMenuShortcutKeyMask(),
e.getX(), e.getY(),
e.getXOnScreen(), e.getYOnScreen(),
e.getClickCount(),
e.isPopupTrigger(),
MouseEvent.NOBUTTON);
}
super.processMouseEvent(e);
}
};
}
/**
* Configures the list which is used to hold the query field items in the
* popup. This method is called when the UI class
* is created.
*
* @see #createList
*/
protected void configureList() {
list.setFont(queryField.getFont());
list.setForeground(queryField.getForeground());
list.setBackground(queryField.getBackground());
list.setBorder(null);
list.setCellRenderer(queryField.getRenderer());
list.setFocusable(false);
list.setValueIsAdjusting(true);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
installListListeners();
}
/**
* Adds the listeners to the list control.
*/
protected void installListListeners() {
if ((listMouseListener = createListMouseListener()) != null) {
list.addMouseListener(listMouseListener);
}
if ((listMouseMotionListener = createListMouseMotionListener()) != null) {
list.addMouseMotionListener(listMouseMotionListener);
}
if ((listSelectionListener = createListSelectionListener()) != null) {
list.addListSelectionListener(listSelectionListener);
}
}
protected void uninstallListListeners() {
if (listMouseListener != null) {
list.removeMouseListener(listMouseListener);
listMouseListener = null;
}
if (listMouseMotionListener != null) {
list.removeMouseMotionListener(listMouseMotionListener);
listMouseMotionListener = null;
}
if (listSelectionListener != null) {
list.removeListSelectionListener(listSelectionListener);
listSelectionListener = null;
}
}
protected void uninstallQueryFieldListeners() {
if (keyListener != null) {
queryField.removeKeyListener(keyListener);
keyListener = null;
}
if (focusListener != null) {
queryField.removeFocusListener(focusListener);
focusListener = null;
}
if (hierarchyBoundsListener != null) {
queryField.removeHierarchyBoundsListener(hierarchyBoundsListener);
hierarchyBoundsListener = null;
}
if (listDataListener != null) {
queryField.getModel().removeListDataListener(listDataListener);
listDataListener = null;
}
}
/**
* Creates the scroll pane which houses the scrollable list.
* @return
*/
protected JScrollPane createScroller() {
JScrollPane sp = new JScrollPane(list,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
sp.setHorizontalScrollBar(null);
return sp;
}
/**
* Creates the auxiliar popup menu.
* @return
*/
protected JPopupMenu createAuxiliarPopup() {
JPopupMenu jp = new JPopupMenu();
JMenuItem mItemDelete = new JMenuItem("Delete", new ImageIcon(BasicQueryFieldPopup.class.getResource("/drakkar/cover/resources/termSuggestDelete.png")));
mItemDelete.setActionCommand("delete");
AuxiliarPopupActionHandler actionHandler = new AuxiliarPopupActionHandler();
mItemDelete.addActionListener(actionHandler);
JMenuItem mItemClear = new JMenuItem("Clear", new ImageIcon(BasicQueryFieldPopup.class.getResource("/drakkar/cover/resources/termSuggestClear.png")));
mItemClear.setActionCommand("clear");
mItemClear.addActionListener(actionHandler);
jp.add(mItemDelete);
jp.add(mItemClear);
return jp;
}
/**
* Configures the scrollable portion which holds the list within
* the query field popup. This method is called when the UI class
* is created.
*/
protected void configureScroller() {
scroller.setFocusable(false);
scroller.getVerticalScrollBar().setFocusable(false);
scroller.setBorder(null);
scroller.setSize(200, 200);
}
/**
* Configures the popup portion of the query field. This method is called
* when the UI class is created.
*/
protected void configurePopup() {
// setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setLayout(new BorderLayout());
setBorderPainted(true);
setBorder(LIST_BORDER);
setOpaque(false);
add(scroller);
setDoubleBuffered(true);
setFocusable(false);
setSize(200, 150);
}
/**
* Configures the popup portion of the query field. This method is called
* when the UI class is created.
*/
protected void configureAuxiliarPopup() {
auxiliarPopup.setBorderPainted(true);
auxiliarPopup.setOpaque(false);
auxiliarPopup.setDoubleBuffered(true);
auxiliarPopup.setFocusable(false);
}
/**
* This method adds the necessary listeners to the QueryField.
*/
protected void installQueryFieldListeners() {
if ((keyListener = createKeyListener()) != null) {
queryField.addKeyListener(keyListener);
}
if ((focusListener = createFocusListener()) != null) {
queryField.addFocusListener(focusListener);
}
if ((hierarchyBoundsListener = createHierarchyBoundsListener()) != null) {
queryField.addHierarchyBoundsListener(hierarchyBoundsListener);
}
installListModelListeners(queryField.getModel());
}
/**
* Installs the listeners on the query field model. Any listeners installed
* on the query field model should be removed in
* <code>uninstallComboBoxModelListeners</code>.
*
* @param model The query field model to install listeners
* @see #uninstallComboBoxModelListeners
*/
protected void installListModelListeners(QueryFieldListModel model) {
if (model != null && (listDataListener = createListDataListener()) != null) {
model.addListDataListener(listDataListener);
}
}
protected void installKeyboardActions() {
/* XXX - shouldn't call this method. take it out for testing.
ActionListener action = new ActionListener() {
public void actionPerformed(ActionEvent e){
}
};
queryField.registerKeyboardAction( action,
KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); */
}
//
// end Initialization routines
//=================================================================
//===================================================================
// begin Event Listenters
//
/**
* A listener to be registered upon the query field
* (<em>not</em> its popup menu)
* to handle mouse events
* that affect the state of the popup menu.
* The main purpose of this listener is to make the popup menu
* appear and disappear.
* This listener also helps
* with click-and-drag scenarios by setting the selection if the mouse was
* released over the list during a drag.
*
* <p>
* <strong>Warning:</strong>
* We recommend that you <em>not</em>
* create subclasses of this class.
* If you absolutely must create a subclass,
* be sure to invoke the superclass
* version of each method.
*
* @see BasicQueryFieldPopup#createMouseListener
*/
protected class InvocationMouseHandler extends MouseAdapter {
/**
* Responds to mouse-pressed events on the query field.
*
* @param e the mouse-press event to be handled
*/
@Override
public void mousePressed(MouseEvent e) {
getHandler().mousePressed(e);
}
/**
* Responds to the user terminating
* a click or drag that began on the query field.
*
* @param e the mouse-release event to be handled
*/
@Override
public void mouseReleased(MouseEvent e) {
getHandler().mouseReleased(e);
}
}
/**
* This listener watches for dragging and updates the current selection in the
* list if it is dragging over the list.
*/
protected class InvocationMouseMotionHandler extends MouseMotionAdapter {
public void mouseDragged(MouseEvent e) {
getHandler().mouseDragged(e);
}
}
/**
* As of Java 2 platform v 1.4, this class is now obsolete and is only included for
* backwards API compatibility. Do not instantiate or subclass.
* <p>
* All the functionality of this class has been included in
* BasicComboBoxUI ActionMap/InputMap methods.
*/
public class InvocationKeyHandler extends KeyAdapter {
public void keyReleased(KeyEvent e) {
}
}
/**
* As of Java 2 platform v 1.4, this class is now obsolete, doesn't do anything, and
* is only included for backwards API compatibility. Do not call or
* override.
*/
protected class ListSelectionHandler implements ListSelectionListener {
public void valueChanged(ListSelectionEvent e) {
}
}
/**
* As of 1.4, this class is now obsolete, doesn't do anything, and
* is only included for backwards API compatibility. Do not call or
* override.
* <p>
* The functionality has been migrated into <code>ItemHandler</code>.
*
* @see #createItemListener
*/
public class ListDataHandler implements ListDataListener {
public void contentsChanged(ListDataEvent e) {
}
public void intervalAdded(ListDataEvent e) {
}
public void intervalRemoved(ListDataEvent e) {
}
}
/**
* This listener hides the popup when the mouse is released in the list.
*/
protected class ListMouseHandler extends MouseAdapter {
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent anEvent) {
getHandler().mouseReleased(anEvent);
}
}
/**
* This listener changes the selected item as you move the mouse over the list.
* The selection change is not committed to the model, this is for user feedback only.
*/
protected class ListMouseMotionHandler extends MouseMotionAdapter {
@Override
public void mouseMoved(MouseEvent anEvent) {
getHandler().mouseMoved(anEvent);
}
}
/**
* This listener watches for bound properties that have changed in the
* query field.
* <p>
* Subclasses which wish to listen to query field property changes should
* call the superclass methods to ensure that the combo popup correctly
* handles property changes.
*
* @see #createPropertyChangeListener
*/
protected class PropertyChangeHandler implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
getHandler().propertyChange(e);
}
}
private class AutoScrollActionHandler implements ActionListener {
private int direction;
AutoScrollActionHandler(int direction) {
this.direction = direction;
}
public void actionPerformed(ActionEvent e) {
if (direction == SCROLL_UP) {
autoScrollUp();
} else {
autoScrollDown();
}
}
}
private class AuxiliarPopupActionHandler implements ActionListener {
AuxiliarPopupActionHandler() {
}
public void actionPerformed(ActionEvent e) {
JMenuItem source = (JMenuItem) (e.getSource());
if (source.getActionCommand().equals("delete")) {
model.remove(list.getSelectedIndex());
if (model.getSize() == 0) {
setVisible(false);
}
} else {
model.clear();
setVisible(false);
}
}
}
private class Handler implements KeyListener, MouseListener,
MouseMotionListener, PropertyChangeListener, FocusListener, HierarchyBoundsListener,
Serializable {
private int keyCount = 0;
private int vkType = -1;
//
// MouseListener
// NOTE: this is added to both the JList and JComboBox
//
public void ancestorMoved(HierarchyEvent e) {
if (isVisible()) {
setVisible(false);
}
}
/**
* Called when an ancestor of the source is resized.
*/
public void ancestorResized(HierarchyEvent e) {
if (isVisible()) {
setVisible(false);
}
}
public void focusGained(FocusEvent e) {
}
public void focusLost(FocusEvent e) {
if (e.getSource() == queryField) {
if (isVisible()) {
setVisible(false);
keyCount = 0;
}
}
}
public void keyTyped(KeyEvent e) {
keyCount++;
}
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_ESCAPE) {
if (isVisible()) {
setVisible(false);
keyCount = 0;
}
} else if (keyCode == KeyEvent.VK_ENTER) {
if (isVisible() && !list.isSelectionEmpty()) {
TermListItem item = (TermListItem) list.getSelectedValue();
StringBuilder terms = new StringBuilder(queryField.getText());
terms.append(" ");
terms.append(item.getTermSuggest().getTerm());
queryField.setText(terms.toString());
if (!item.isIsSelected()) {
item.setIsSelected(true);
}
}
}
vkType = keyCode;
}
public void keyReleased(KeyEvent e) {
int keyCode = e.getKeyCode();
if ((keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_UP) && isVisible()) {
int indexSelected = -1;
switch (keyCode) {
case KeyEvent.VK_DOWN:
indexSelected = list.getSelectedIndex();
if (indexSelected < queryField.getItemCount() - 1) {
int newIndex = indexSelected + 1;
list.setSelectedIndex(newIndex);
list.ensureIndexIsVisible(newIndex);
}
break;
default:
indexSelected = list.getSelectedIndex();
int newIndex = indexSelected - 1;
if (indexSelected > 0) {
list.setSelectedIndex(newIndex);
list.ensureIndexIsVisible(newIndex);
}
break;
}
} else if (keyCount > 1 && vkType != KeyEvent.VK_ESCAPE && vkType != KeyEvent.VK_ENTER && vkType != KeyEvent.VK_SPACE && vkType != KeyEvent.VK_DELETE) {
if (isVisible()) {
String query = queryField.getText();
if (!query.equals("")) {
String[] term = query.split(" |,");
findTerm(term[term.length - 1]);
}
} else if (!model.isEmpty()) {
setPreferredSize(new Dimension(queryField.getSize().width, 100));
Point p = queryField.getLocationOnScreen();
setLocation(p.x, p.y + queryField.getSize().height);
setVisible(true);
queryField.requestFocusInWindow();
}
} else if (vkType == KeyEvent.VK_ENTER && searchFacade != null) {
searchFacade.search(queryField.getText());
} else if (vkType == KeyEvent.VK_DELETE && queryField.getSelectedText() == null) {
int index = list.getSelectedIndex();
model.remove(index);
if (model.isEmpty()) {
setVisible(false);
} else if (index == 0) {
list.setSelectedIndex(index);
} else {
list.setSelectedIndex(--index);
list.ensureIndexIsVisible(index);
}
}
}
public void mouseClicked(MouseEvent evt) {
if (evt.getSource() == list) {
boolean isLeftBtn = SwingUtilities.isLeftMouseButton(evt);
if (isLeftBtn & evt.getClickCount() == 2) {
TermListItem item = (TermListItem) list.getSelectedValue();
StringBuilder terms = new StringBuilder(queryField.getText());
terms.append(" ");
terms.append(item.getTermSuggest().getTerm());
queryField.setText(terms.toString());
if (!item.isIsSelected()) {
item.setIsSelected(true);
}
}
}
}
public void mousePressed(MouseEvent e) {
if (e.getSource() == list) {
return;
}
if (!SwingUtilities.isLeftMouseButton(e) || !queryField.isEnabled()) {
return;
}
if (queryField.isEditable()) {
Component comp = queryField;
if ((!(comp instanceof JComponent)) || ((JComponent) comp).isRequestFocusEnabled()) {
comp.requestFocus();
}
} else if (queryField.isRequestFocusEnabled()) {
queryField.requestFocus();
}
togglePopup();
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
//
// MouseMotionListener:
// NOTE: this is added to both the List and QueryField
//
public void mouseMoved(MouseEvent anEvent) {
if (anEvent.getSource() == list) {
Point location = anEvent.getPoint();
Rectangle r = new Rectangle();
list.computeVisibleRect(r);
if (r.contains(location)) {
updateListBoxSelectionForEvent(anEvent, false);
}
}
}
public void mouseDragged(MouseEvent e) {
if (e.getSource() == list) {
return;
}
if (isVisible()) {
MouseEvent newEvent = convertMouseEvent(e);
Rectangle r = new Rectangle();
list.computeVisibleRect(r);
if (newEvent.getPoint().y >= r.y && newEvent.getPoint().y <= r.y + r.height - 1) {
hasEntered = true;
if (isAutoScrolling) {
stopAutoScrolling();
}
Point location = newEvent.getPoint();
if (r.contains(location)) {
updateListBoxSelectionForEvent(newEvent, false);
}
} else {
if (hasEntered) {
int directionToScroll = newEvent.getPoint().y < r.y ? SCROLL_UP : SCROLL_DOWN;
if (isAutoScrolling && scrollDirection != directionToScroll) {
stopAutoScrolling();
startAutoScrolling(directionToScroll);
} else if (!isAutoScrolling) {
startAutoScrolling(directionToScroll);
}
} else {
if (e.getPoint().y < 0) {
hasEntered = true;
startAutoScrolling(SCROLL_UP);
}
}
}
}
}
//
// PropertyChangeListener
//
public void propertyChange(PropertyChangeEvent e) {
JQueryField queryFld = (JQueryField) e.getSource();
String propertyName = e.getPropertyName();
switch (propertyName) {
case "model":
QueryFieldListModel oldModel = (QueryFieldListModel) e.getOldValue();
QueryFieldListModel newModel = (QueryFieldListModel) e.getNewValue();
uninstallQueryFieldModelListeners(oldModel);
installListModelListeners(newModel);
list.setModel(newModel);
if (isVisible()) {
hide();
}
break;
case "renderer":
list.setCellRenderer(queryFld.getRenderer());
if (isVisible()) {
hide();
}
break;
case "componentOrientation":
{
// Pass along the new component orientation
// to the list and the scroller
ComponentOrientation o = (ComponentOrientation) e.getNewValue();
JList list = getList();
if (list != null && list.getComponentOrientation() != o) {
list.setComponentOrientation(o);
}
if (scroller != null && scroller.getComponentOrientation() != o) {
scroller.setComponentOrientation(o);
}
if (o != getComponentOrientation()) {
setComponentOrientation(o);
}
break;
}
case "lightWeightPopupEnabled":
setLightWeightPopupEnabled(queryFld.isLightWeightPopupEnabled());
break;
}
}
}
//
// end Event Listeners
//=================================================================
/**
* Overridden to unconditionally return false.
*/
public boolean isFocusTraversable() {
return false;
}
//===================================================================
// begin Autoscroll methods
//
/**
* This protected method is implementation specific and should be private.
* do not call or override.
*/
protected void startAutoScrolling(int direction) {
// XXX - should be a private method within InvocationMouseMotionHandler
// if possible.
if (isAutoScrolling) {
autoscrollTimer.stop();
}
isAutoScrolling = true;
if (direction == SCROLL_UP) {
scrollDirection = SCROLL_UP;
Point convertedPoint = SwingUtilities.convertPoint(scroller, new Point(1, 1), list);
int top = list.locationToIndex(convertedPoint);
list.setSelectedIndex(top);
autoscrollTimer = new Timer(100, new AutoScrollActionHandler(
SCROLL_UP));
} else if (direction == SCROLL_DOWN) {
scrollDirection = SCROLL_DOWN;
Dimension size = scroller.getSize();
Point convertedPoint = SwingUtilities.convertPoint(scroller,
new Point(1, (size.height - 1) - 2),
list);
int bottom = list.locationToIndex(convertedPoint);
list.setSelectedIndex(bottom);
autoscrollTimer = new Timer(100, new AutoScrollActionHandler(
SCROLL_DOWN));
}
autoscrollTimer.start();
}
/**
* This protected method is implementation specific and should be private.
* do not call or override.
*/
protected void stopAutoScrolling() {
isAutoScrolling = false;
if (autoscrollTimer != null) {
autoscrollTimer.stop();
autoscrollTimer = null;
}
}
/**
* This protected method is implementation specific and should be private.
* do not call or override.
*/
protected void autoScrollUp() {
int index = list.getSelectedIndex();
if (index > 0) {
list.setSelectedIndex(index - 1);
list.ensureIndexIsVisible(index - 1);
}
}
/**
* This protected method is implementation specific and should be private.
* do not call or override.
*/
protected void autoScrollDown() {
int index = list.getSelectedIndex();
int lastItem = list.getModel().getSize() - 1;
if (index < lastItem) {
list.setSelectedIndex(index + 1);
list.ensureIndexIsVisible(index + 1);
}
}
//
// end Autoscroll methods
//=================================================================
//===================================================================
// begin Utility methods
//
/**
* Gets the AccessibleContext associated with this BasicQueryFieldPopup.
* The AccessibleContext will have its parent set to the QueryField.
*
* @return an AccessibleContext for the BasicQueryFieldPopup
* @since 1.5
*/
public AccessibleContext getAccessibleContext() {
AccessibleContext context = super.getAccessibleContext();
context.setAccessibleParent(queryField);
return context;
}
/**
* This is is a utility method that helps event handlers figure out where to
* send the focus when the popup is brought up. The standard implementation
* delegates the focus to the editor (if the query field is editable) or to
* the JComboBox if it is not editable.
*/
protected void delegateFocus(MouseEvent e) {
if (queryField.isEditable()) {
// Component comp = queryField.getEditor().getEditorComponent();
Component comp = queryField;
if ((!(comp instanceof JComponent)) || ((JComponent) comp).isRequestFocusEnabled()) {
comp.requestFocus();
}
} else if (queryField.isRequestFocusEnabled()) {
queryField.requestFocus();
}
}
/**
* Makes the popup visible if it is hidden and makes it hidden if it is
* visible.
*/
protected void togglePopup() {
if (isVisible()) {
hide();
} else {
show();
}
}
/**
* Sets the list selection index to the selectedIndex. This
* method is used to synchronize the list selection with the
* query field selection.
*
* @param selectedIndex the index to set the list
*/
private void setListSelection(int selectedIndex) {
if (selectedIndex == -1) {
list.clearSelection();
} else {
list.setSelectedIndex(selectedIndex);
list.ensureIndexIsVisible(selectedIndex);
}
}
protected MouseEvent convertMouseEvent(MouseEvent e) {
Point convertedPoint = SwingUtilities.convertPoint((Component) e.getSource(),
e.getPoint(), list);
MouseEvent newEvent = new MouseEvent((Component) e.getSource(),
e.getID(),
e.getWhen(),
e.getModifiers(),
convertedPoint.x,
convertedPoint.y,
e.getXOnScreen(),
e.getYOnScreen(),
e.getClickCount(),
e.isPopupTrigger(),
MouseEvent.NOBUTTON);
return newEvent;
}
/**
* Retrieves the height of the popup based on the current
* ListCellRenderer and the maximum row count.
*/
protected int getPopupHeightForRowCount(int maxRowCount) {
// Set the cached value of the minimum row count
int minRowCount = Math.min(maxRowCount, queryField.getItemCount());
int height = 0;
ListCellRenderer renderer = list.getCellRenderer();
Object value = null;
for (int i = 0; i < minRowCount; ++i) {
value = list.getModel().getElementAt(i);
Component c = renderer.getListCellRendererComponent(list, value, i, false, false);
height += c.getPreferredSize().height;
}
if (height == 0) {
height = queryField.getHeight();
}
Border border = scroller.getViewportBorder();
if (border != null) {
Insets insets = border.getBorderInsets(null);
height += insets.top + insets.bottom;
}
border = scroller.getBorder();
if (border != null) {
Insets insets = border.getBorderInsets(null);
height += insets.top + insets.bottom;
}
return height;
}
/**
* Calculate the placement and size of the popup portion of the query field based
* on the query field location and the enclosing screen bounds. If
* no transformations are required, then the returned rectangle will
* have the same values as the parameters.
*
* @param px starting x location
* @param py starting y location
* @param pw starting width
* @param ph starting height
* @return a rectangle which represents the placement and size of the popup
*/
protected Rectangle computePopupBounds(int px, int py, int pw, int ph) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Rectangle screenBounds;
// Calculate the desktop dimensions relative to the query field.
GraphicsConfiguration gc = queryField.getGraphicsConfiguration();
Point p = new Point();
SwingUtilities.convertPointFromScreen(p, queryField);
if (gc != null) {
Insets screenInsets = toolkit.getScreenInsets(gc);
screenBounds = gc.getBounds();
screenBounds.width -= (screenInsets.left + screenInsets.right);
screenBounds.height -= (screenInsets.top + screenInsets.bottom);
screenBounds.x += (p.x + screenInsets.left);
screenBounds.y += (p.y + screenInsets.top);
} else {
screenBounds = new Rectangle(p, toolkit.getScreenSize());
}
Rectangle rect = new Rectangle(px, py, pw, ph);
if (py + ph > screenBounds.y + screenBounds.height
&& ph < screenBounds.height) {
rect.y = -rect.height;
}
return rect;
}
/**
* Calculates the upper left location of the Popup.
*/
private Point getPopupLocation() {
Dimension popupSize = queryField.getSize();
Insets insets = getInsets();
// reduce the width of the scrollpane by the insets so that the popup
// is the same width as the query field.
popupSize.setSize(popupSize.width - (insets.right + insets.left),
getPopupHeightForRowCount(queryField.getMaximumRowCount()));
Rectangle popupBounds = computePopupBounds(0, queryField.getBounds().height,
popupSize.width, popupSize.height);
Dimension scrollSize = popupBounds.getSize();
Point popupLocation = popupBounds.getLocation();
scroller.setMaximumSize(scrollSize);
scroller.setPreferredSize(scrollSize);
scroller.setMinimumSize(scrollSize);
list.revalidate();
return popupLocation;
}
/**
* A utility method used by the event listeners. Given a mouse event, it changes
* the list selection to the list item below the mouse.
*/
protected void updateListBoxSelectionForEvent(MouseEvent anEvent, boolean shouldScroll) {
// XXX - only seems to be called from this class. shouldScroll flag is
// never true
Point location = anEvent.getPoint();
if (list == null) {
return;
}
int index = list.locationToIndex(location);
if (index == -1) {
if (location.y < 0) {
index = 0;
} else {
index = queryField.getModel().getSize() - 1;
}
}
if (list.getSelectedIndex() != index) {
list.setSelectedIndex(index);
if (shouldScroll) {
list.ensureIndexIsVisible(index);
}
}
}
@Override
public void setSize(int width, int height) {
super.setSize(width, height);
}
protected void findTerm(String term) {
int index = model.find(term);
if (index >= 0) {
list.setSelectedIndex(index);
list.ensureIndexIsVisible(index);
}
}
/**
*
* @return
*/
public SearchFacade getComponentFacade() {
return this.searchFacade;
}
/**
*
* @param facade
*/
public void setComponentFacade(SearchFacade facade) {
this.searchFacade = facade;
}
//
// end Utility methods
//=================================================================
}