blob: 7e3e8970407d698bc689cc524daa9720d67be0e2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pivot.wtk.skin.terra;
import java.awt.Color;
import java.awt.Font;
import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.collections.List;
import org.apache.pivot.util.Vote;
import org.apache.pivot.wtk.Border;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.ComponentKeyListener;
import org.apache.pivot.wtk.ComponentStateListener;
import org.apache.pivot.wtk.Container;
import org.apache.pivot.wtk.ContainerMouseListener;
import org.apache.pivot.wtk.Display;
import org.apache.pivot.wtk.FocusTraversalDirection;
import org.apache.pivot.wtk.GraphicsUtilities;
import org.apache.pivot.wtk.Keyboard;
import org.apache.pivot.wtk.Keyboard.KeyCode;
import org.apache.pivot.wtk.Keyboard.KeyLocation;
import org.apache.pivot.wtk.Keyboard.Modifier;
import org.apache.pivot.wtk.ListView;
import org.apache.pivot.wtk.ListViewSelectionListener;
import org.apache.pivot.wtk.Mouse;
import org.apache.pivot.wtk.Panorama;
import org.apache.pivot.wtk.Point;
import org.apache.pivot.wtk.Style;
import org.apache.pivot.wtk.SuggestionPopup;
import org.apache.pivot.wtk.SuggestionPopupListener;
import org.apache.pivot.wtk.SuggestionPopupSelectionListener;
import org.apache.pivot.wtk.SuggestionPopupStateListener;
import org.apache.pivot.wtk.TextInput;
import org.apache.pivot.wtk.Theme;
import org.apache.pivot.wtk.Window;
import org.apache.pivot.wtk.effects.DropShadowDecorator;
import org.apache.pivot.wtk.effects.Transition;
import org.apache.pivot.wtk.effects.TransitionListener;
import org.apache.pivot.wtk.skin.WindowSkin;
/**
* Terra suggestion popup skin.
*/
public class TerraSuggestionPopupSkin extends WindowSkin implements SuggestionPopupListener,
SuggestionPopupSelectionListener, SuggestionPopupStateListener {
private Panorama listViewPanorama;
private Border listViewBorder;
private ListView listView = new ListView();
private DropShadowDecorator dropShadowDecorator = null;
private Transition closeTransition = null;
private boolean returnFocusToTextInput = true;
private int closeTransitionDuration = DEFAULT_CLOSE_TRANSITION_DURATION;
private int closeTransitionRate = DEFAULT_CLOSE_TRANSITION_RATE;
private boolean variableItemHeight = true; // This is the historical default
private ContainerMouseListener displayMouseListener = new ContainerMouseListener() {
@Override
public boolean mouseDown(Container container, Mouse.Button button, int x, int y) {
SuggestionPopup suggestionPopup = (SuggestionPopup) getComponent();
TextInput textInput = suggestionPopup.getTextInput();
Display display = (Display) container;
Component descendant = display.getDescendantAt(x, y);
if (!suggestionPopup.isAncestor(descendant) && descendant != textInput) {
returnFocusToTextInput = false;
suggestionPopup.close(false);
}
return false;
}
@Override
public boolean mouseWheel(Container container, Mouse.ScrollType scrollType,
int scrollAmount, int wheelRotation, int x, int y) {
return true;
}
};
private ComponentStateListener textInputStateListener = new ComponentStateListener() {
@Override
public void focusedChanged(Component component, Component obverseComponent) {
SuggestionPopup suggestionPopup = (SuggestionPopup) getComponent();
if (!component.isFocused() && !suggestionPopup.containsFocus()) {
returnFocusToTextInput = false;
suggestionPopup.close();
}
}
};
private ComponentKeyListener textInputKeyListener = new ComponentKeyListener() {
/**
* {@link KeyCode#DOWN DOWN} Transfer focus to the suggestion list and
* select the first suggestion if no others are selected.<br>
* {@link KeyCode#ESCAPE ESCAPE} Close the popup with a 'result' of
* false.
*/
@Override
public boolean keyPressed(Component component, int keyCode, KeyLocation keyLocation) {
boolean consumed = false;
SuggestionPopup suggestionPopup = (SuggestionPopup) getComponent();
if (keyCode == KeyCode.DOWN) {
if (listView.getSelectedIndex() == -1 && listView.getListData().getLength() > 0) {
listView.setSelectedIndex(0);
}
suggestionPopup.requestFocus();
consumed = true;
} else if (keyCode == KeyCode.ESCAPE) {
suggestionPopup.close(false);
consumed = true;
}
return consumed;
}
};
private ListViewSelectionListener listViewSelectionListener = new ListViewSelectionListener() {
@Override
public void selectedItemChanged(ListView listViewArgument, Object previousSelectedItem) {
int index = listViewArgument.getSelectedIndex();
SuggestionPopup suggestionPopup = (SuggestionPopup) getComponent();
suggestionPopup.setSelectedIndex(index);
}
};
private ComponentKeyListener listViewKeyListener = new ComponentKeyListener() {
/**
* {@link KeyCode#TAB TAB} Close the suggestion popup with a 'result' of
* true, and transfer focus forwards from the TextInput.<br>
* {@link KeyCode#TAB TAB} + {@link Modifier#SHIFT SHIFT} Close the
* suggestion popup with a 'result' of true, and transfer focus
* backwards from the TextInput.<br>
*/
@Override
public boolean keyPressed(Component component, int keyCode, KeyLocation keyLocation) {
SuggestionPopup suggestionPopup = (SuggestionPopup) getComponent();
TextInput textInput = suggestionPopup.getTextInput();
switch (keyCode) {
case KeyCode.TAB:
returnFocusToTextInput = false;
suggestionPopup.close(true);
FocusTraversalDirection direction = (Keyboard.isPressed(Modifier.SHIFT))
? FocusTraversalDirection.BACKWARD : FocusTraversalDirection.FORWARD;
textInput.transferFocus(direction);
break;
default:
break;
}
return false;
}
};
private static final int DEFAULT_CLOSE_TRANSITION_DURATION = 150;
private static final int DEFAULT_CLOSE_TRANSITION_RATE = 30;
public TerraSuggestionPopupSkin() {
listView.getStyles().put(Style.variableItemHeight, variableItemHeight);
listView.getListViewSelectionListeners().add(listViewSelectionListener);
listView.getComponentKeyListeners().add(listViewKeyListener);
listViewPanorama = new Panorama(listView);
listViewPanorama.getStyles().put(Style.buttonBackgroundColor,
listView.getStyles().getColor(Style.backgroundColor));
listViewPanorama.getStyles().put(Style.alwaysShowScrollButtons, true);
listViewBorder = new Border(listViewPanorama);
}
@Override
public void install(Component component) {
super.install(component);
SuggestionPopup suggestionPopup = (SuggestionPopup) component;
suggestionPopup.getSuggestionPopupListeners().add(this);
suggestionPopup.getSuggestionPopupSelectionListeners().add(this);
suggestionPopup.getSuggestionPopupStateListeners().add(this);
suggestionPopup.setContent(listViewBorder);
listView.setListData(suggestionPopup.getSuggestionData());
listView.setItemRenderer(suggestionPopup.getSuggestionRenderer());
if (!themeIsFlat()) {
// Attach the drop-shadow decorator
dropShadowDecorator = new DropShadowDecorator();
suggestionPopup.getDecorators().add(dropShadowDecorator);
}
}
public Font getFont() {
return listView.getStyles().getFont(Style.font);
}
public void setFont(Font font) {
listView.getStyles().put(Style.font, font);
}
public final void setFont(String font) {
setFont(decodeFont(font));
}
public final void setFont(Dictionary<String, ?> font) {
setFont(Theme.deriveFont(font));
}
public Color getColor() {
return listView.getStyles().getColor(Style.color);
}
public void setColor(Color color) {
listView.getStyles().put(Style.color, color);
}
public final void setColor(String color) {
setColor(GraphicsUtilities.decodeColor(color, "color"));
}
public Color getBorderColor() {
return listViewBorder.getStyles().getColor(Style.color);
}
public void setBorderColor(Color borderColor) {
listViewBorder.getStyles().put(Style.color, borderColor);
}
public final void setBorderColor(String borderColor) {
setBorderColor(GraphicsUtilities.decodeColor(borderColor, "borderColor"));
}
public int getCloseTransitionDuration() {
return closeTransitionDuration;
}
public void setCloseTransitionDuration(int closeTransitionDuration) {
this.closeTransitionDuration = closeTransitionDuration;
}
public int getCloseTransitionRate() {
return closeTransitionRate;
}
public void setCloseTransitionRate(int closeTransitionRate) {
this.closeTransitionRate = closeTransitionRate;
}
public boolean isVariableItemHeight() {
return variableItemHeight;
}
public void setVariableItemHeight(boolean variableItemHeight) {
this.variableItemHeight = variableItemHeight;
listView.getStyles().put(Style.variableItemHeight, variableItemHeight);
}
@Override
public boolean mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
SuggestionPopup suggestionPopup = (SuggestionPopup) getComponent();
suggestionPopup.close(true);
return true;
}
/**
* {@link KeyCode#ENTER ENTER} Close the suggestion popup with a 'result' of
* true.<br> {@link KeyCode#ESCAPE ESCAPE} Close the suggestion popup with a
* 'result' of false.
*/
@Override
public boolean keyPressed(Component component, int keyCode, KeyLocation keyLocation) {
SuggestionPopup suggestionPopup = (SuggestionPopup) getComponent();
switch (keyCode) {
case KeyCode.ENTER:
suggestionPopup.close(true);
break;
case KeyCode.ESCAPE:
suggestionPopup.close(false);
break;
default:
break;
}
return false;
}
@Override
public void windowOpened(Window window) {
super.windowOpened(window);
// Adjust for list size
SuggestionPopup suggestionPopup = (SuggestionPopup) window;
int listSize = suggestionPopup.getListSize();
if (listSize == -1) {
listViewBorder.setPreferredHeight(-1);
} else {
if (!listViewBorder.isPreferredHeightSet()) {
ListView.ItemRenderer itemRenderer = listView.getItemRenderer();
int borderHeight = itemRenderer.getPreferredHeight(-1) * listSize + 2;
if (listViewBorder.getPreferredHeight() > borderHeight) {
listViewBorder.setPreferredHeight(borderHeight);
} else {
listViewBorder.setPreferredHeight(-1);
}
}
}
Display display = window.getDisplay();
display.getContainerMouseListeners().add(displayMouseListener);
if (dropShadowDecorator != null) {
dropShadowDecorator.setShadowOpacity(DropShadowDecorator.DEFAULT_SHADOW_OPACITY);
}
returnFocusToTextInput = true;
TextInput textInput = suggestionPopup.getTextInput();
textInput.getComponentStateListeners().add(textInputStateListener);
textInput.getComponentKeyListeners().add(textInputKeyListener);
// Size and position the popup
Point location = textInput.mapPointToAncestor(textInput.getDisplay(), 0, 0);
window.setLocation(location.x, location.y + textInput.getHeight() - 1);
window.setMinimumWidth(textInput.getWidth());
window.setMaximumHeight(display.getHeight() - window.getY());
}
@Override
public void windowCloseVetoed(Window window, Vote reason) {
if (reason == Vote.DENY && closeTransition != null) {
closeTransition.stop();
listViewBorder.setEnabled(true);
closeTransition = null;
}
}
@Override
public void windowClosed(Window window, Display display, Window owner) {
display.getContainerMouseListeners().remove(displayMouseListener);
super.windowClosed(window, display, owner);
}
@Override
public void suggestionDataChanged(SuggestionPopup suggestionPopup,
List<?> previousSuggestionData) {
listView.setListData(suggestionPopup.getSuggestionData());
}
@Override
public void suggestionRendererChanged(SuggestionPopup suggestionPopup,
ListView.ItemRenderer previousSuggestionRenderer) {
listView.setItemRenderer(suggestionPopup.getSuggestionRenderer());
}
@Override
public void listSizeChanged(SuggestionPopup suggestionPopup, int previousListSize) {
// No-op
}
@Override
public void selectedIndexChanged(SuggestionPopup suggestionPopup, int previousSelectedIndex) {
// No-op
}
@Override
public void selectedSuggestionChanged(SuggestionPopup suggestionPopup,
Object previousSelectedSuggestion) {
TextInput textInput = suggestionPopup.getTextInput();
Object suggestion = suggestionPopup.getSelectedSuggestion();
if (suggestion != null) {
ListView.ItemRenderer suggestionRenderer = suggestionPopup.getSuggestionRenderer();
textInput.setText(suggestionRenderer.toString(suggestion));
}
}
@Override
public Vote previewSuggestionPopupClose(final SuggestionPopup suggestionPopup,
final boolean result) {
if (closeTransition == null) {
listViewBorder.setEnabled(false);
closeTransition = new FadeWindowTransition(suggestionPopup, closeTransitionDuration,
closeTransitionRate, dropShadowDecorator);
closeTransition.start(new TransitionListener() {
@Override
public void transitionCompleted(Transition transition) {
suggestionPopup.close(result);
}
});
}
return (closeTransition != null && closeTransition.isRunning()) ? Vote.DEFER : Vote.APPROVE;
}
@Override
public void suggestionPopupCloseVetoed(SuggestionPopup suggestionPopup, Vote reason) {
if (reason == Vote.DENY && closeTransition != null) {
closeTransition.stop();
listViewBorder.setEnabled(true);
closeTransition = null;
}
}
@Override
public void suggestionPopupClosed(SuggestionPopup suggestionPopup) {
suggestionPopup.clearFocusDescendant();
TextInput textInput = suggestionPopup.getTextInput();
textInput.getComponentStateListeners().remove(textInputStateListener);
textInput.getComponentKeyListeners().remove(textInputKeyListener);
if (returnFocusToTextInput) {
textInput.requestFocus();
}
listViewBorder.setEnabled(true);
closeTransition = null;
}
}