| /* |
| * 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; |
| } |
| } |