All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.pivot.wtk.RadioButtonGroup Maven / Gradle / Ivy

The newest version!
/*
 * 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;

import java.util.Iterator;

import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Group;
import org.apache.pivot.collections.List;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.util.Filter;
import org.apache.pivot.util.ImmutableIterator;
import org.apache.pivot.wtk.Keyboard.KeyCode;
import org.apache.pivot.wtk.Keyboard.KeyLocation;
import org.apache.pivot.wtk.Keyboard.Modifier;

/**
 * Extension of {@link ButtonGroup} providing keyboard navigation within the
 * group and modified focus navigation that treats the group as a single
 * focusable entity.

* * {@link KeyCode#UP UP} & {@link KeyCode#LEFT LEFT} Select the previous * button
* {@link KeyCode#DOWN DOWN} & {@link KeyCode#RIGHT RIGHT} Select the next * button
* {@link KeyCode#HOME HOME} Select the first button
* {@link KeyCode#END END} Select the last button

* * (Note that only {@link Component#isFocusable() focusable} buttons are * considered when searching for a Button to select)

* * When a button within the group is focused and key is typed, an attempt is * made to find the next button (default) or previous button (when the SHIFT * modifier is pressed) whose renderer text starts with the typed character. * This search will always behave as if the circular property were * set.

* * By default, {@link KeyCode#TAB TAB} and {@link KeyCode#TAB SHIFT+TAB} * key presses will transfer focus out of the group (forwards or backwards * respectively). * This is managed by the {@link #setIntraGroupFocusTransferEnabled(boolean) * intraGroupFocusTransferEnabled} property.

* * The {@link #setCircular(boolean) circular} property can be enabled to allow * the selection to transfer seamlessly from one end of the group to the other. * (i.e. holding down an arrow key will cycle through all focusable buttons) *

* * Note that due to the conflicting return types of the add(T) and * remove(T) methods in the {@link Group#add(Object) Group} and * {@link Sequence#add(Object) Sequence} interfaces, this class cannot actually * implement Sequence<Button>, although most of the same * methods are implemented.
*/ public class RadioButtonGroup extends ButtonGroup { /** * Filter used to determine selectable buttons whose rendered data starts * with the target character. */ private class FirstCharacterFilter implements Filter { private char target = '\0'; public void setTarget(char target) { this.target = Character.toUpperCase(target); } @Override public boolean include(Integer index) { boolean include = defaultFilter.include(index); if (include) { Button button = buttonOrder.get(index); String rendered = button.getDataRenderer().toString(button.getButtonData()); if (rendered != null && rendered.length() > 0) { char first = Character.toUpperCase(rendered.charAt(0)); if (first != target) { include = false; } } } return include; } } /** * ComponentKeyListener to be applied to all buttons as they are added to * the group.

* * At least one button in the group must be focused for this listener to be * executed, but that won't necessarily be a selected button.
* This also means that the group will not be empty, although some of the * buttons contained within may not be focusable, or even visible. */ private final ComponentKeyListener componentKeyListener = new ComponentKeyListener.Adapter() { /** * Handle TAB & SHIFT+TAB focus traversal, HOME, END & arrow keys */ @Override public boolean keyPressed(Component component, int keyCode, KeyLocation keyLocation) { int modifiers = Keyboard.getModifiers(); boolean handled = false; /* * Potentially transfer focus away from the buttons in this group. * * At this point we know that at least one button is focused, so we * just need to find the first or last (and possibly only) focusable * button depending on the focus transfer direction and then * transfer away from it. */ if (!intraGroupFocusTransferEnabled) { if (keyCode == KeyCode.TAB) { if (modifiers == 0) { Button lastFocusableButton = get(findPrevious(buttonOrder.getLength())); lastFocusableButton.transferFocus(FocusTraversalDirection.FORWARD); handled = true; } else if (modifiers == Modifier.SHIFT.getMask()) { Button firstFocusableButton = get(findNext(NO_SELECTION_INDEX)); firstFocusableButton.transferFocus(FocusTraversalDirection.BACKWARD); handled = true; } } } // Navigation/selection within the group if (!handled && modifiers == 0) { RadioButtonGroup radioButtonGroup = RadioButtonGroup.this; Button selectedButton = radioButtonGroup.getSelection(); handled = true; if (keyCode == Keyboard.KeyCode.HOME) { radioButtonGroup.selectFirstButton(); } else if (keyCode == Keyboard.KeyCode.END) { radioButtonGroup.selectLastButton(); } else if (keyCode == Keyboard.KeyCode.LEFT || keyCode == Keyboard.KeyCode.UP) { radioButtonGroup.selectPreviousButton(selectedButton); } else if (keyCode == Keyboard.KeyCode.RIGHT || keyCode == Keyboard.KeyCode.DOWN) { radioButtonGroup.selectNextButton(selectedButton); } else { handled = false; } } return handled; } /** * Attempt to jump to the button whose rendered text begins with the * typed character.
*/ @Override public boolean keyTyped(Component component, char character) { int modifiers = Keyboard.getModifiers(); boolean handled = false; // We are only interested when a key is typed with no modifier, or // just SHIFT (which is used to reverse the search direction) boolean noModifiersPressed = (modifiers == 0); boolean shiftPressed = (modifiers == Modifier.SHIFT.getMask()); if (noModifiersPressed || shiftPressed) { RadioButtonGroup radioButtonGroup = RadioButtonGroup.this; Button selectedButton = radioButtonGroup.getSelection(); firstCharacterFilter.setTarget(character); // Determine the starting point for the search int searchStartIndex; if (selectedButton != null) { searchStartIndex = radioButtonGroup.indexOf(selectedButton); } else { if (noModifiersPressed) { searchStartIndex = NO_SELECTION_INDEX; } else { searchStartIndex = buttonOrder.getLength(); } } int result = NOT_FOUND_INDEX; if (noModifiersPressed) { result = radioButtonGroup.findNext(searchStartIndex, firstCharacterFilter, true); } else if (shiftPressed) { result = radioButtonGroup.findPrevious(searchStartIndex, firstCharacterFilter, true); } // Consider the event to have been handled if a different // button end up being selected if (result != NOT_FOUND_INDEX && result != searchStartIndex) { radioButtonGroup.setSelection(result); handled = true; } } return handled; } }; /** * Ensure that all buttons in this group have the custom * ComponentKeyListener.
* This relies on the logic within ButtonGroup to prevent duplicates. */ private final ButtonGroupListener buttonGroupListener = new ButtonGroupListener.Adapter() { @Override public void buttonAdded(ButtonGroup buttonGroup, Button button) { button.getComponentKeyListeners().add(componentKeyListener); } @Override public void buttonRemoved(ButtonGroup buttonGroup, Button button) { button.getComponentKeyListeners().remove(componentKeyListener); } }; /** * Filter used to determine selectable buttons within the group */ private final Filter defaultFilter = new Filter() { @Override public boolean include(Integer index) { Button button = buttonOrder.get(index); boolean focusable = button.isFocusable(); return focusable; } }; private final FirstCharacterFilter firstCharacterFilter = new FirstCharacterFilter(); private final List




© 2015 - 2024 Weber Informatics LLC | Privacy Policy