/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* IntellicutComboBox.java
* Creation date: Feb 21-2002
* By: David Mosimann
*/
package org.openquark.gems.client;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JComboBox;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.TypeChecker.TypeCheckInfo;
import org.openquark.cal.services.CALWorkspace;
import org.openquark.gems.client.IntellicutListModelAdapter.InputOutputAdapter;
import org.openquark.gems.client.IntellicutListModelAdapter.IntellicutListEntry;
import org.openquark.gems.client.IntellicutManager.IntellicutMode;
/**
* Encapsulates the intellicut functionality in a combo box. The combo box drop down will show the valid
* gems as selected and rendered by the intellicut functionality.
* @author dmosimann
*/
public class IntellicutComboBox extends JComboBox {
private static final long serialVersionUID = -6807031219239353161L;
/** The timer used to show the combobox popup list. */
private final Timer popupTimer;
/** The delay before the popup timer fires. */
private static final int POPUP_TIMER_DELAY = 500;
/** If true the list should be updated when the popup becomes visible. */
private boolean updateList = true;
private class IntellicutListener extends KeyAdapter implements PopupMenuListener {
@Override
public void keyReleased(KeyEvent e) {
// Ignore action keys that don't affect the text.
if (e.isActionKey() || ignoreKey(e.getKeyCode())) {
return;
}
// Update the list if the popup is actually visible,
// otherwise start a timer to show the popup.
if (isPopupVisible()) {
// Set the text to the text of the selected item.
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
IntellicutListEntry selected = (IntellicutListEntry) getSelectedItem();
if (selected != null) {
JTextField textField = (JTextField) getEditor().getEditorComponent();
textField.setText(selected.getDisplayString());
hidePopup();
}
return;
}
updateList();
} else if (Character.isLetterOrDigit(e.getKeyChar()) || e.getKeyChar() == '_' ||
e.getKeyCode() == KeyEvent.VK_DELETE || e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
popupTimer.restart();
}
}
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
if (updateList) {
updateList();
}
}
public void popupMenuCanceled(PopupMenuEvent e) {
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
}
/**
* @param keyCode the keycode of the key to check
* @return boolean true if the key with the given key code should be ignored.
*/
private boolean ignoreKey(int keyCode) {
switch (keyCode) {
case KeyEvent.VK_SHIFT:
case KeyEvent.VK_CONTROL:
case KeyEvent.VK_ALT:
case KeyEvent.VK_PAUSE:
case KeyEvent.VK_CAPS_LOCK:
case KeyEvent.VK_ESCAPE:
return true;
}
return false;
}
}
/**
* Constructor for IntellicutComboBox.
* @param inputExpr a list of valid input types to the gems
* @param outputExpr a list of valid output types to the gems
* @param workspace the workspace to load gems from
* @param typeCheckInfo the type check info to use
*/
public IntellicutComboBox(TypeExpr[] inputExpr,
TypeExpr[] outputExpr,
CALWorkspace workspace,
TypeCheckInfo typeCheckInfo) {
setName("MatchingGemsList");
setBounds(0, 0, 160, 120);
setEditable(true);
setRenderer(new IntellicutListRenderer(IntellicutMode.NOTHING));
InputOutputAdapter adapter = new InputOutputAdapter(inputExpr, outputExpr, workspace, typeCheckInfo);
setModel(new IntellicutListModel (adapter));
// Set a preferred size so that the list is not its default narrow width.
Dimension prefSize = getPreferredSize();
setPreferredSize(new Dimension(200, prefSize.height));
popupTimer = new Timer(POPUP_TIMER_DELAY, this);
popupTimer.setRepeats(false);
// This listener updates the popup list if the user types text.
IntellicutListener listener = new IntellicutListener();
getEditor().getEditorComponent().addKeyListener(listener);
addPopupMenuListener(listener);
}
/**
* This method is called when the popup timer fires.
*/
@Override
public void actionPerformed(ActionEvent evt) {
if (evt.getSource() == popupTimer) {
if (hasFocus() || getEditor().getEditorComponent().hasFocus()) {
if (!isPopupVisible()) {
updateList();
// Only show the popup if there are any items to show.
if (getModel().getSize() > 0) {
updateList = false;
showPopup();
updateList = true;
}
}
}
}
}
/**
* Forces the list of valid gems to be rebuilt for the new input and output type expressions. This
* will also close the popup window if it is open since its values are no longer valid.
* @param inputExpr a list of valid input types to the gems
* @param outputExpr a list of valid output types to the gems
*/
public void rebuildList(TypeExpr[] inputExpr, TypeExpr[] outputExpr) {
hidePopup();
InputOutputAdapter adapter = (InputOutputAdapter) ((IntellicutListModel) getModel()).getAdapter();
adapter.reset(inputExpr, outputExpr);
}
/**
* Updates the visible list of gems based on what the user has typed so far.
* It will also ensure that the text display in the text entry field is correct.
*/
private void updateList() {
IntellicutListModel listModel = (IntellicutListModel) getModel();
JTextField textField = (JTextField) getEditor().getEditorComponent();
String userText = textField.getText();
int oldNumItems = listModel.getSize();
// Refresh the list.
listModel.refreshList(userText);
// If the number of items in the list changes then we have to hide and reshow the
// list so that it resizes itself correctly. If there are no items to show just
// hide the list.
if (listModel.getSize() == 0) {
// Check if the popup is already visible. If the user clicked the popup button
// to show it then this method gets called from the popupWillBecomeVisible method
// in the listener. If we hide the popup while it is not quite shown yet the list
// gets screwed up and the user wont be able to hide the popup anymore.
if (isPopupVisible()) {
hidePopup();
}
} else if (listModel.getSize() != oldNumItems && isPopupVisible()) {
updateList = false;
hidePopup();
showPopup();
updateList = true;
}
// If the list is visible make sure no item is selected.
if (isPopupVisible()) {
setSelectedIndex(-1);
}
// Put the original text back into the textfield and request focus for it.
textField.setText(userText);
textField.setCaretPosition(userText.length());
textField.requestFocus();
}
}