Default button input map in a synth look and feel?
Asked Answered
H

2

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.

Hawking answered 17/9, 2012 at 17:19 Comment(2)
workforme (nimbus, jdk6/7) - so what is your LAF? Looks like something is wrong with it ..Aranyaka
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)Aranyaka
F
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); 
Farquhar answered 20/9, 2012 at 22:52 Comment(7)
getDefaultValue() is static and private in SynthStyle, any other ideas as to how to get the input map?Hawking
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.Farquhar
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...Hawking
@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.Farquhar
the one drawback (a showstopper in many contexts) I see, is the need to access sun.xx package.Aranyaka
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.Farquhar
Any idea how to get this decorator to work in JDK7? Seems they are removing the DefaultSynthStyle class...Hawking
A
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);
Aranyaka answered 21/9, 2012 at 9:12 Comment(4)
+1 Oddly, my previous look at this works on Mac OS X with com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel.Heisenberg
+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,Bingo
@Bingo 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 itAranyaka
@Heisenberg 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.Aranyaka
F
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); 
Farquhar answered 20/9, 2012 at 22:52 Comment(7)
getDefaultValue() is static and private in SynthStyle, any other ideas as to how to get the input map?Hawking
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.Farquhar
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...Hawking
@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.Farquhar
the one drawback (a showstopper in many contexts) I see, is the need to access sun.xx package.Aranyaka
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.Farquhar
Any idea how to get this decorator to work in JDK7? Seems they are removing the DefaultSynthStyle class...Hawking

© 2022 - 2024 — McMap. All rights reserved.