Browse Source

Fixed toggle buttons not being traversable in a button group.

pull/127/head
weisj 5 years ago
parent
commit
231f23151d
  1. 5
      core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonUI.java
  2. 188
      core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/ButtonGroupInfo.java
  3. 64
      core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/DarkToggleButtonKeyHandler.java
  4. 22
      core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/DarkToggleButtonUI.java
  5. 92
      core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/ToggleButtonFocusNavigationActions.java
  6. 32
      core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/radiobutton/DarkRadioButtonUI.java

5
core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonUI.java

@ -75,6 +75,11 @@ public class DarkButtonUI extends BasicButtonUI implements ButtonConstants {
return new DarkButtonUI();
}
@Override
public String getPropertyPrefix() {
return super.getPropertyPrefix();
}
@Override
public void installUI(final JComponent c) {
button = (AbstractButton) c;

188
core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/ButtonGroupInfo.java

@ -0,0 +1,188 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.weisj.darklaf.ui.togglebutton;
import javax.swing.*;
import java.awt.*;
import java.util.Enumeration;
import java.util.HashSet;
public class ButtonGroupInfo {
protected JToggleButton activeButton;
protected JToggleButton firstButton = null;
protected JToggleButton lastButton = null;
protected JToggleButton previousButton = null;
protected JToggleButton nextButton = null;
protected HashSet<JToggleButton> buttonsInGroup;
protected boolean sourceFound = false;
public ButtonGroupInfo(final JToggleButton btn) {
activeButton = btn;
buttonsInGroup = new HashSet<>();
}
public static boolean isValidToggleButtonObject(final Object obj) {
return obj instanceof JToggleButton
&& ((JToggleButton) obj).isEnabled()
&& ((JToggleButton) obj).isVisible();
}
protected boolean containsInGroup(final JToggleButton button) {
return buttonsInGroup.contains(button);
}
protected Component getFocusTransferBaseComponent(final boolean next) {
return firstButton;
}
protected boolean getButtonGroupInfo() {
if (activeButton == null) {
return false;
}
buttonsInGroup.clear();
// Get the button model from the source.
ButtonModel model = activeButton.getModel();
if (!(model instanceof DefaultButtonModel)) {
return false;
}
// If the button model is DefaultButtonModel, and use it, otherwise return.
DefaultButtonModel bm = (DefaultButtonModel) model;
// get the ButtonGroup of the button from the button model
ButtonGroup group = bm.getGroup();
if (group == null) {
return false;
}
// Get all the buttons in the group
Enumeration<AbstractButton> e = group.getElements();
if (e == null) {
return false;
}
while (e.hasMoreElements()) {
AbstractButton curElement = e.nextElement();
if (!isValidToggleButtonObject(curElement)) {
continue;
}
buttonsInGroup.add((JToggleButton) curElement);
// If firstBtn is not set yet, curElement is that first button
if (firstButton == null) {
firstButton = (JToggleButton) curElement;
}
if (activeButton == curElement) {
sourceFound = true;
} else if (!sourceFound) {
// The source has not been yet found and the current element
// is the last previousBtn
previousButton = (JToggleButton) curElement;
} else if (nextButton == null) {
// The source has been found and the current element
// is the next valid button of the list
nextButton = (JToggleButton) curElement;
}
// Set new last "valid" JToggleButton of the list
lastButton = (JToggleButton) curElement;
}
return true;
}
/**
* Find the new radio button that focus needs to be
* moved to in the group, select the button
*
* @param next, indicate if it's arrow up/left or down/right
*/
protected void selectNewButton(final boolean next) {
if (!getButtonGroupInfo()) {
return;
}
if (sourceFound) {
JToggleButton newSelectedButton;
if (next) {
// Select Next button. Cycle to the first button if the source.
// button is the last of the group.
newSelectedButton = (null == nextButton) ? firstButton : nextButton;
} else {
// Select previous button. Cycle to the last button if the source.
// button is the first button of the group.
newSelectedButton = (null == previousButton) ? lastButton : previousButton;
}
if (newSelectedButton != null &&
(newSelectedButton != activeButton)) {
newSelectedButton.requestFocusInWindow();
// newSelectedButton.setSelected(true);
}
}
}
/**
* Find the button group the passed in JToggleButton belongs to, and
* move focus to next component of the last button in the group
* or previous component of first button
*
* @param next, indicate if jump to next component or previous
*/
protected void jumpToNextComponent(final boolean next) {
if (!getButtonGroupInfo()) {
// In case the button does not belong to any group, it needs
// to be treated as a component
if (activeButton != null) {
lastButton = activeButton;
firstButton = activeButton;
} else {
return;
}
}
// Update the component we will use as base to transfer
// focus from
JComponent compTransferFocusFrom = activeButton;
// If next component in the parent window is not in
// the button group, current active button will be
// base, otherwise, the base will be first or last
// button in the button group
Component focusBase = getFocusTransferBaseComponent(next);
if (focusBase != null) {
if (next) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(focusBase);
} else {
KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent(focusBase);
}
}
}
}

64
core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/DarkToggleButtonKeyHandler.java

@ -0,0 +1,64 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.weisj.darklaf.ui.togglebutton;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Set;
public class DarkToggleButtonKeyHandler implements KeyListener {
// This listener checks if the key event is a focus traversal key event
// on a radio button, consume the event if so and move the focus
// to next/previous component
public void keyPressed(final KeyEvent e) {
AWTKeyStroke stroke = AWTKeyStroke.getAWTKeyStrokeForEvent(e);
if (stroke != null && e.getSource() instanceof JRadioButton) {
JRadioButton source = (JRadioButton) e.getSource();
boolean next = isFocusTraversalKey(source,
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
stroke);
if (next || isFocusTraversalKey(source,
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
stroke)) {
e.consume();
ButtonGroupInfo btnGroupInfo = new ButtonGroupInfo(source);
btnGroupInfo.jumpToNextComponent(next);
}
}
}
private boolean isFocusTraversalKey(final JComponent c, final int id, final AWTKeyStroke stroke) {
Set<AWTKeyStroke> keys = c.getFocusTraversalKeys(id);
return keys != null && keys.contains(stroke);
}
public void keyReleased(final KeyEvent e) {
}
public void keyTyped(final KeyEvent e) {
}
}

22
core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/DarkToggleButtonUI.java

@ -35,6 +35,7 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import java.awt.*;
import java.awt.event.KeyListener;
import java.awt.geom.RoundRectangle2D;
/**
@ -54,6 +55,7 @@ public class DarkToggleButtonUI extends DarkButtonUI implements ToggleButtonCons
protected Color sliderBorderColor;
protected Color inactiveSliderBorderColor;
protected Color selectedForeground;
protected KeyListener keyListener;
public static ComponentUI createUI(final JComponent c) {
return new DarkToggleButtonUI();
@ -75,6 +77,26 @@ public class DarkToggleButtonUI extends DarkButtonUI implements ToggleButtonCons
selectedForeground = UIManager.getColor("ToggleButton.selectedForeground");
}
@Override
protected void installListeners(final AbstractButton b) {
super.installListeners(b);
keyListener = createKeyListener(b);
b.addKeyListener(keyListener);
ToggleButtonFocusNavigationActions.installActions(b);
}
@Override
protected void uninstallListeners(final AbstractButton b) {
super.uninstallListeners(b);
b.removeKeyListener(keyListener);
keyListener = null;
ToggleButtonFocusNavigationActions.uninstallActions(b);
}
protected KeyListener createKeyListener(final AbstractButton button) {
return new DarkToggleButtonKeyHandler();
}
@Override
protected BasicButtonListener createButtonListener(final AbstractButton b) {
return new DarkToggleButtonListener(b, this);

92
core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/ToggleButtonFocusNavigationActions.java

@ -0,0 +1,92 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.weisj.darklaf.ui.togglebutton;
import javax.swing.*;
import java.awt.event.ActionEvent;
public class ToggleButtonFocusNavigationActions {
public static void installActions(final AbstractButton button) {
if (button == null) return;
button.getActionMap().put("Previous", new SelectPreviousBtn());
button.getActionMap().put("Next", new SelectNextBtn());
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("UP"), "Previous");
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("DOWN"), "Next");
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("LEFT"), "Previous");
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("RIGHT"), "Next");
}
public static void uninstallActions(final AbstractButton button) {
if (button == null) return;
button.getActionMap().remove("Previous");
button.getActionMap().remove("Next");
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("UP"));
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("DOWN"));
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("LEFT"));
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("RIGHT"));
}
protected static void selectToggleButton(final ActionEvent event, final boolean next) {
// Get the source of the event.
Object eventSrc = event.getSource();
// Check whether the source is JRadioButton, it so, whether it is visible
if (!ButtonGroupInfo.isValidToggleButtonObject(eventSrc)) {
return;
}
ButtonGroupInfo btnGroupInfo = new ButtonGroupInfo((JToggleButton) eventSrc);
btnGroupInfo.selectNewButton(next);
}
public static class SelectPreviousBtn extends AbstractAction {
public SelectPreviousBtn() {
super("Previous");
}
public void actionPerformed(final ActionEvent e) {
selectToggleButton(e, false);
}
}
public static class SelectNextBtn extends AbstractAction {
public SelectNextBtn() {
super("Next");
}
public void actionPerformed(final ActionEvent e) {
selectToggleButton(e, true);
}
}
}

32
core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/radiobutton/DarkRadioButtonUI.java

@ -24,9 +24,7 @@
package com.github.weisj.darklaf.ui.togglebutton.radiobutton;
import com.github.weisj.darklaf.icons.EmptyIcon;
import com.github.weisj.darklaf.ui.togglebutton.DarkToggleButtonUI;
import com.github.weisj.darklaf.ui.togglebutton.ToggleButtonConstants;
import com.github.weisj.darklaf.ui.togglebutton.StateIconUI;
import com.github.weisj.darklaf.ui.togglebutton.*;
import com.github.weisj.darklaf.util.GraphicsContext;
import com.github.weisj.darklaf.util.GraphicsUtil;
import com.github.weisj.darklaf.util.PropertyKey;
@ -35,10 +33,12 @@ import sun.swing.SwingUtilities2;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.IconUIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.metal.MetalRadioButtonUI;
import javax.swing.text.View;
import java.awt.*;
import java.awt.event.KeyListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RectangularShape;
import java.beans.PropertyChangeEvent;
@ -65,6 +65,8 @@ public class DarkRadioButtonUI extends MetalRadioButtonUI implements PropertyCha
private Icon radioSelectedIcon;
private Icon radioSelectedDisabledIcon;
private Icon radioSelectedFocusedIcon;
protected BasicButtonListener buttonListener;
protected KeyListener keyListener;
public static ComponentUI createUI(final JComponent c) {
@ -98,10 +100,22 @@ public class DarkRadioButtonUI extends MetalRadioButtonUI implements PropertyCha
@Override
protected void installListeners(final AbstractButton button) {
super.installListeners(button);
buttonListener = createButtonListener(button);
button.addMouseListener(buttonListener);
button.addMouseMotionListener(buttonListener);
button.addFocusListener(buttonListener);
button.addPropertyChangeListener(buttonListener);
button.addChangeListener(buttonListener);
keyListener = createKeyListener(button);
button.addKeyListener(keyListener);
ToggleButtonFocusNavigationActions.installActions(radioButton);
button.addPropertyChangeListener(this);
}
protected KeyListener createKeyListener(final AbstractButton button) {
return new DarkToggleButtonKeyHandler();
}
@Override
public void uninstallUI(final JComponent c) {
super.uninstallUI(c);
@ -110,7 +124,15 @@ public class DarkRadioButtonUI extends MetalRadioButtonUI implements PropertyCha
@Override
protected void uninstallListeners(final AbstractButton button) {
super.uninstallListeners(button);
button.removeMouseListener(buttonListener);
button.removeMouseMotionListener(buttonListener);
button.removeFocusListener(buttonListener);
button.removeChangeListener(buttonListener);
button.removePropertyChangeListener(buttonListener);
buttonListener = null;
button.removeKeyListener(keyListener);
keyListener = null;
ToggleButtonFocusNavigationActions.uninstallActions(radioButton);
button.removePropertyChangeListener(this);
}

Loading…
Cancel
Save