4

I am trying to use the UIManager to get and clear some default key bindings so that the spacebar doesn't activate my JButtons, as explained here. Problem is, likely due to my synth look and feel, (InputMap)UIManager.get("Button.focusInputMap"); returns a null. Does anyone know of a way to easily clear components input maps some other way, or why the UIManager returns a null in this case? Any tips are appreciated, thanks beforehand.

Community
  • 1
  • 1
SuperTron
  • 4,203
  • 6
  • 35
  • 62
  • workforme (nimbus, jdk6/7) - so what _is_ your LAF? Looks like something is wrong with it .. – kleopatra Sep 21 '12 at 07:10
  • paddle back: doesn't work ;-) The inputMap is not null, but replacing the pressed/released bindings in it _does not_ prevent the space from triggering the button in Nimbus (as it does in both win and metal) – kleopatra Sep 21 '12 at 07:38

2 Answers2

4

First off: I like the idea of a decorated StyleFactory as suggested in the other answer, by @David :-) - so if that works out would suggest to use go that direction.

Anyway, couldn't resist experimenting a bit: looks like Nimbus (and possibly other Synth-based LAFs) need those defaults overrides extremely early in the life-cycle: they accept them only if done before any component is actually created

// setting LAF
InteractiveTestCase.setLAF("Nimbus");
// tweak inputMap, immediately after setting the ui is fine
// uncomment the following line and it doesn't work
// new JPanel();
InputMap inputMap = (InputMap) UIManager.get("Button.focusInputMap");
inputMap.put(KeyStroke.getKeyStroke("SPACE"), "do-nothing");

If the UIManager doesn't return an inputMap at that point, I would regard that as a misbehaviour of your custom LAF and would try to dig further as to why that happens. Another thingy you could try is to set an entirely new inputMap (which would have the advantage of surviving a LAF toggle, as it isn't a UIResource, like:

// setting LAF
InteractiveTestCase.setLAF("Nimbus");
// tweak inputMap, immediately after setting the ui is fine
InputMap inputMap = (InputMap) UIManager.get("Button.focusInputMap");
InputMap custom = new InputMap();
if (inputMap != null) {
    // copy all bindings to custom
    ...
} else {
    // add the binding we know of (as implementation detail)
    custom.put(KeyStroke.getKeyStroke("released SPACE"), "released");
}
// overwrite the binding you want to change
custom.put(KeyStroke.getKeyStroke("SPACE"), "do-nothing");
// set the custom map
UIManager.put("Button.focusInputMap", custom);
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • 2
    +1 Oddly, my [previous look at this](http://stackoverflow.com/a/12136312/230513) works on Mac OS X with `com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel`. – trashgod Sep 21 '12 at 10:37
  • +1, 1. `UIManager.getLookAndFeelDefaults().put("Xxx", "Xxx")`, 2. not sure for `JButton` because I tried with `JMenu(Item)` and works only in case if `Nimbus L&F` was `uinstalled` ..., 3. in this case I used `putClientProperty("Xxx", "Xxx")` with `ButtonModel`, – mKorbel Sep 21 '12 at 10:53
  • @mKorbel 1) didn't make no difference 2) well, I tried many permutations - this was the only one that worked :-) 3) don't understand, ButtonModel has nothing to do with it – kleopatra Sep 21 '12 at 11:18
  • @trashgod oioioioi ... yet another case of write-once-debug-everywhere ;-) Actually, I consider the Nimbus behaviour (on win as seen here) a bug: looks like it doesn't listen - or not listen enough - to changes in manager properties. – kleopatra Sep 21 '12 at 11:22
3

I'm not near a computer to try this, but looking at the openjdk 7 source here, the mapping looks fixed by default.

A possible, but slightly hacky, solution could be to create and install a decorator SynthStyleFactory that modifies the style prior to returning it.

EDIT: I've updated the below code sample as I've had a chance to test this. It didn't work in it's original form, but the updated code worked for me.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.synth.Region;
import javax.swing.plaf.synth.SynthLookAndFeel;
import javax.swing.plaf.synth.SynthStyle;
import javax.swing.plaf.synth.SynthStyleFactory;

import sun.swing.plaf.synth.DefaultSynthStyle;

public class LnFTest {

    public static void main(String[] args) throws UnsupportedLookAndFeelException{

        SynthLookAndFeel laf = new SynthLookAndFeel();
        laf.load(LnFTest.class.getResourceAsStream("laf.xml"), LnFTest.class);
        UIManager.setLookAndFeel(laf);
        SynthLookAndFeel.setStyleFactory(new MyStyleFactory(SynthLookAndFeel.getStyleFactory()));


        JButton button = new JButton("Test");
        button.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
                System.out.println("Action Performed");
            }
        });

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.add(button, BorderLayout.CENTER);
        frame.pack();

        frame.setVisible(true);

    }



}

class MyStyleFactory extends SynthStyleFactory {

    private SynthStyleFactory delegate;
    private Map overrides;

     public MyStyleFactory(SynthStyleFactory delegate){
         this.delegate = delegate;

         overrides = new HashMap();
         overrides.put("Button.focusInputMap", new UIDefaults.LazyInputMap(new Object[0]));
     }

     public SynthStyle getStyle(JComponent c, Region id) {
         SynthStyle style = delegate.getStyle(c, id);

         System.out.println("Style is a: " + style);

         if(style instanceof DefaultSynthStyle){
             ((DefaultSynthStyle)style).setData(overrides);
         }

         return style;

     }
}

EDIT: I don't seem able to add a comment to the original post, so just to clarify, I had confirmed that UIManager.get("Button.focusInputMap") returned null with just plain Synth, prior to creating any components. Possibly Nimbus is overriding this behaviour.

 SynthLookAndFeel laf = new SynthLookAndFeel();
 UIManager.setLookAndFeel(laf);
 System.out.println(UIManager.get("Button.focusInputMap") == null); 
David Hutchison
  • 2,343
  • 2
  • 19
  • 24
  • getDefaultValue() is static and private in SynthStyle, any other ideas as to how to get the input map? – SuperTron Sep 21 '12 at 14:54
  • Oops my bad. Going by the openJDK 7 link I'd posted, SynthStyle has a method `public Object get(SynthContext context, Object key)` which just calls that private static method. It only uses the key. – David Hutchison Sep 21 '12 at 21:48
  • hmm, well, I have been trying this, but I can't figure out what to use as the context :S calling `(InputMap)style.get(null, "Button.focusInputMap")` gives me a null pointer exception, even though the context is not used... – SuperTron Sep 22 '12 at 01:48
  • @SuperTron, I've updated the code sample after testing and found a way that made it work for me at least. I don't know if you get notifications of edits to answers. – David Hutchison Sep 23 '12 at 21:09
  • the one drawback (a showstopper in many contexts) I see, is the need to access sun.xx package. – kleopatra Sep 24 '12 at 08:27
  • 1
    I know. Looking at the API it seems an odd class. In the original post, since its a parsed LNF, it may not use that class and use a subclass of it (ParsedSynthStyle) which is not in the sun package. – David Hutchison Sep 24 '12 at 13:26
  • Any idea how to get this decorator to work in JDK7? Seems they are removing the DefaultSynthStyle class... – SuperTron Jan 04 '14 at 01:55