org.nuiton.jaxx.widgets.jformattedtextfield.JFormattedTextFieldNavigationManager Maven / Gradle / Ivy
package org.nuiton.jaxx.widgets.jformattedtextfield;
/*
* #%L
* JAXX :: Widgets
* %%
* Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import com.google.common.base.Preconditions;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.swing.JFormattedTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.MaskFormatter;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
/**
* Manager to navigate inside a JFormattedTextField using a MaskFormatter.
*
* Created on 3/21/15.
*
* @author Tony Chemit - [email protected]
* @since 2.23
*/
public class JFormattedTextFieldNavigationManager {
/**
* Logger.
*/
private static final Logger log = LogManager.getLogger(JFormattedTextFieldNavigationManager.class);
private static final String CLIENT_PROPERTY_NAVIGATION_MANAGER = "JFormattedTextFieldNavigationHandler";
private final JFormatterTextFieldInternalGroups groups;
private final KeyAdapter keyAdapter;
private final MouseAdapter mouseAdapter;
private final FocusAdapter focusAdapter;
private final Map actions;
private JFormatterTextFieldInternalGroup lastGroup = null;
private static class SelectComponentAction implements Runnable {
private final JFormatterTextFieldInternalGroup group;
private final JFormattedTextField source;
private SelectComponentAction(JFormattedTextField source, JFormatterTextFieldInternalGroup group) {
this.group = group;
this.source = source;
}
@Override
public void run() {
int startIndex = group.getStartIndex();
int endIndex = group.getEndIndex() + 1;
if (log.isDebugEnabled()) {
log.debug(String.format("Select group [%s]", group));
}
source.select(startIndex, endIndex);
}
}
public static void install(JFormattedTextField component) {
JFormattedTextField.AbstractFormatter formatter = component.getFormatter();
Preconditions.checkState(formatter != null, "No formatter found on " + component);
Preconditions.checkState(formatter instanceof MaskFormatter, "Formatter " + formatter + " is not a MaskFormatter");
String mask = ((MaskFormatter) formatter).getMask();
JFormatterTextFieldInternalGroups componentPositions = JFormatterTextFieldInternalGroups.create(mask);
JFormattedTextFieldNavigationManager handler = new JFormattedTextFieldNavigationManager(componentPositions, component);
component.putClientProperty(CLIENT_PROPERTY_NAVIGATION_MANAGER, handler);
handler.install0(component);
}
public static void uninstall(JFormattedTextField component) {
JFormattedTextFieldNavigationManager handler = (JFormattedTextFieldNavigationManager) component.getClientProperty(CLIENT_PROPERTY_NAVIGATION_MANAGER);
try {
handler.uninstall0(component);
} finally {
component.putClientProperty(CLIENT_PROPERTY_NAVIGATION_MANAGER, null);
}
}
protected JFormattedTextFieldNavigationManager(JFormatterTextFieldInternalGroups groups, JFormattedTextField component) {
this.groups = groups;
this.actions = new HashMap<>();
for (JFormatterTextFieldInternalGroup componentPosition : groups) {
actions.put(componentPosition, new SelectComponentAction(component, componentPosition));
}
this.keyAdapter = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
JFormattedTextField source = (JFormattedTextField) e.getSource();
int caretPosition = source.getCaretPosition();
if (canGoRight(e)) {
goRight(source, caretPosition);
e.consume();
} else if (canTransferFocus(e)) {
source.transferFocus();
e.consume();
} else if (canGoLeft(e)) {
goLeft(source, caretPosition);
e.consume();
} else if (canTransferFocusBackward(e)) {
source.transferFocusBackward();
e.consume();
}
}
@Override
public void keyReleased(KeyEvent e) {
JFormattedTextField source = (JFormattedTextField) e.getSource();
JFormatterTextFieldInternalGroup currentGroup = getCurrentGroup(source);
selectComponent(currentGroup);
}
};
this.mouseAdapter = new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
JFormattedTextField source = (JFormattedTextField) e.getSource();
JFormatterTextFieldInternalGroup currentGroup = getCurrentGroup(source);
selectComponent(currentGroup);
}
};
this.focusAdapter = new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
lastGroup = null;
JFormattedTextField source = (JFormattedTextField) e.getSource();
JFormatterTextFieldInternalGroup currentGroup = getCurrentGroup(source);
selectComponent(currentGroup);
}
};
}
protected void gotoComponent(JFormattedTextField source, JFormatterTextFieldInternalGroup group) {
if (group != null) {
int startIndex = group.getStartIndex();
int endIndex = group.getEndIndex();
if (log.isDebugEnabled()) {
log.debug(String.format("Goto component [%s - %s]", startIndex, endIndex));
}
source.setCaretPosition(startIndex);
}
}
protected void selectComponent(JFormatterTextFieldInternalGroup currentGroup) {
if (lastGroup == null || !lastGroup.equals(currentGroup)) {
if (log.isDebugEnabled()) {
log.debug("New select group: " + currentGroup);
}
lastGroup = currentGroup;
SelectComponentAction action = actions.get(currentGroup);
if (action != null) {
SwingUtilities.invokeLater(action);
}
}
}
protected JFormatterTextFieldInternalGroup getCurrentGroup(JFormattedTextField source) {
return getGroup(source, source.getCaretPosition());
}
protected JFormatterTextFieldInternalGroup getGroup(JFormattedTextField source, int caretPosition) {
JFormatterTextFieldInternalGroup result = groups.getGroupAtPosition(caretPosition);
if (result == null && caretPosition >= source.getText().length()) {
result = groups.getGroupAtPosition(source.getText().length() - 1);
}
if (result == null) {
result = groups.iterator().next();
}
return result;
}
protected void goRight(JFormattedTextField source, int caretPosition) {
if (lastGroup == null || !lastGroup.isLastGroup()) {
if (log.isDebugEnabled()) {
log.debug("Go right from position " + caretPosition);
}
JFormatterTextFieldInternalGroup currentGroup = getGroup(source, caretPosition);
JFormatterTextFieldInternalGroup nextGroup = currentGroup.getNextGroup();
gotoComponent(source, nextGroup);
}
}
protected void goLeft(JFormattedTextField source, int caretPosition) {
if (lastGroup == null || !lastGroup.isFirstGroup()) {
if (log.isDebugEnabled()) {
log.debug("Go left from position " + caretPosition);
}
JFormatterTextFieldInternalGroup currentGroup = getGroup(source, caretPosition);
JFormatterTextFieldInternalGroup nextGroup = currentGroup.getPreviousGroup();
gotoComponent(source, nextGroup);
}
}
protected boolean canGoRight(KeyEvent e) {
boolean result = false;
boolean noModifiers = e.getModifiersEx() == 0;
if (KeyEvent.VK_RIGHT == e.getKeyCode() && noModifiers) {
// right key go to next component
result = true;
} else if (KeyEvent.VK_TAB == e.getKeyCode() && noModifiers) {
// Tab go to next component if not on last one
result = lastGroup == null || !lastGroup.isLastGroup();
}
return result;
}
protected boolean canGoLeft(KeyEvent e) {
boolean result = false;
if (KeyEvent.VK_LEFT == e.getKeyCode() && e.getModifiersEx() == 0) {
// Left key go to previous component
result = true;
} else if (KeyEvent.VK_TAB == e.getKeyCode() && e.isShiftDown() && !e.isControlDown()) {
// Shift Tab go to previous component if not on first one
result = lastGroup != null && !lastGroup.isFirstGroup();
}
return result;
}
protected boolean canTransferFocus(KeyEvent e) {
boolean result = false;
boolean onTabKey = KeyEvent.VK_TAB == e.getKeyCode();
if (onTabKey && e.isControlDown() && !e.isShiftDown()) {
// Ctrl Tab go direct to focus
result = true;
} else if (onTabKey && e.getModifiersEx() == 0) {
// Tab on last component go to focus
result = lastGroup != null && lastGroup.isLastGroup();
}
return result;
}
protected boolean canTransferFocusBackward(KeyEvent e) {
boolean result = false;
boolean onShiftTabKey = KeyEvent.VK_TAB == e.getKeyCode() && e.isShiftDown();
if (onShiftTabKey && e.isControlDown()) {
// Ctrl Shift Tab go direct to focus backward
result = true;
} else if (onShiftTabKey) {
// Shit Tab go to focus backward only if no component selected or on the first one
result = lastGroup == null || lastGroup.isFirstGroup();
}
return result;
}
private void install0(JFormattedTextField component) {
component.setFocusTraversalKeysEnabled(false);
component.addKeyListener(keyAdapter);
component.addMouseListener(mouseAdapter);
component.addFocusListener(focusAdapter);
}
private void uninstall0(JFormattedTextField component) {
component.setFocusTraversalKeysEnabled(true);
component.removeKeyListener(keyAdapter);
component.removeMouseListener(mouseAdapter);
component.removeFocusListener(focusAdapter);
}
}