<Swing Dev> Public API for internal Swing classes.
Alexander Scherbatiy
alexandr.scherbatiy at oracle.com
Tue Aug 4 10:29:36 UTC 2015
On 8/4/2015 10:50 AM, Walter Laan wrote:
> Below a use case where I had to cast to sun.swing.UIAction to correctly check if it is enabled or not.
> Let me know if I need to make the bug report or if you can add it as a comment to any existing issue.
Thank you for the report. Yes, please, file an enhancement on it.
Thanks,
Alexandr.
>
> The issue with sun.swing.UIAction is as follows:
>
> It does not return the correct value from #isEnabled(), so you need to cast and call #isEnabled(Object) instead (check argument). This is because a single instance of UIAction is shared between all components with the same UI class, I assume originally for performance reasons.
> If you just want to perform the action when really enabled, you can use SwingUtilities#notifyAction(Action, KeyStroke, KeyEvent, Object, int) where the Object is the 'sender' (the action event source component) which is passed to UIAction#isEnabled(Object).
> It is technically possible to work around it with current public API, but that means not using any of the Swing UI classes at all and rewrite them without using UIAction.
>
> Solutions:
> 1) Fix UIAction by removing #isEnabled(Object) and have an instance per component
> Lots of work and large impact.
> Swing API users can remove references to UIAction and just call Action#isEnabled()
>
> 2) Make UIAction public API (move to javax.swing.plaf?)
> Simple refactor but lots of files changed
> Swing API users only need to update their imports
>
> 3) Do nothing and force Swing API to do point 1 themselves by re-implementing the components and UI classes using only public API
> Even more work than first solution but only for Swing API users
> Or they can access through reflection if that is not blocked by the module system? It will probably depend on the SecurityManager I guess.
>
> 4) Provide boolean SwingUtilities.canNotifyAction(Action, Object) which returns true if #notifyAction(Action, KeyStroke, KeyEvent, Object, int) would call action performed
> Minimal work and minimal impact
> Swing API users need to change code from ((sun.swing.UIAction) action).isEnabled(component) to SwingUtilities.canNotifyAction(component, action)
>
> 5) Something else?
>
> My preference would be solution 4 due to minimal impact - solution 2 also has not much impact but then you have public API which does not implement the Action interface correctly. Solution 1 would be the correct one but a lot of work.
>
> A simple test case:
>
> import java.awt.EventQueue;
>
> import javax.swing.Action;
> import javax.swing.JTable;
> import javax.swing.table.DefaultTableModel;
>
> public class TestUIAction {
>
> public static void main(String[] args) {
> EventQueue.invokeLater(new Runnable() {
>
> @Override
> public void run() {
> JTable table = new JTable(new DefaultTableModel(1, 1));
>
> Action action = table.getActionMap().get("cancel");
> System.out.println("JTable#isEditing() = " + table.isEditing());
> System.out.println("Action#isEnabled() = " + action.isEnabled() + " but should be false!"); System.out.println("UIAction#isEnabled(Object) = " + ((sun.swing.UIAction) action).isEnabled(table));
>
> table.editCellAt(0, 0);
>
> System.out.println("JTable#isEditing() = " + table.isEditing());
> System.out.println("Action#isEnabled() = " + action.isEnabled()); System.out.println("UIAction#isEnabled(Object) = " + ((sun.swing.UIAction) action).isEnabled(table));
> }
> });
> }
> }
>
> Output:
> JTable#isEditing() = false
> Action#isEnabled() = true but should be false!
> UIAction#isEnabled(Object) = false
>
> JTable#isEditing() = true
> Action#isEnabled() = true
> UIAction#isEnabled(Object) = true
>
>
> Below a (quite long) scenario is which I had to use it:
>
> Using Jidesoft HierarchicalTable (see http://www.jidesoft.com/images/hierarchicaltable.png) which has (multiple) JTables as child components of a JTable. Their implementation has a work around to avoid keystrokes from being processed by the parent table if the focus is in a child component table. They do this by placing a JPanel between the parent and child table which registers a no-op action for each action in the JTable Input/ActionMap.
>
> For example:
> -top row is selected in the focused child table
> -users presses up arrow
> -child table up action is not enabled (since top row selection cannot move up)
> -key press goes up to the inserted panel and is consumed (action performed that does nothing)
> -parent table up arrow action does not get executed so its row selection is not changed
>
> As mentioned this is a workaround to avoid the parent component handling the key input, but a 'correct' solution would require re-writing BasicTableUI (and all the sub-classes for all the look and feels) for their HierarchicalTable instead of re-using the JTable UI.
> To improve the hack in the case of the escape key, which I want to use to close a JDialog the HierarchicalTable (skip the parent table actions instead of consuming it before gets there), I changed the no-op action to be only enabled if the original action is enabled in the parent table.
>
> For example:
> -child table is focused and not cell editing
> -user presses escape
> -child table 'cancel cell edit action' does not get notified because is not enabled
> -key press goes up the inserted panel but does not consume the action because the parent table 'cancel cell edit' action is also not enabled
> -parent table 'cancel cell edit action' does not get notified because is not enabled
> -eventually key press comes to the JDialog root pane and executes the close dialog action
>
> But since the 'cancel cell edit action' is an sun.swing.UIAction, to check if it is really enabled, I need to cast to the interal API and call #isEnabled(parentTable):
>
> /**
> * Mute an action by doing nothing if the original action is enabled
> */
> private static class MutedAction extends AbstractAction {
> private final Object sender;
> private final Action action;
>
> public MutedAction(Object sender, Action action) {
> this.sender = sender;
> this.action = action;
> }
>
> @Override
> public void actionPerformed(ActionEvent e) {
> // muted
> }
>
> @Override
> public boolean isEnabled() {
> if(action instanceof sun.swing.UIAction) {
> return ((sun.swing.UIAction) action).isEnabled(sender);
> }
> else {
> return action.isEnabled();
> }
> }
> }
>
>
> Kind regards,
> Walter Laan
> Cost Engineering Consultancy
>
> -----Original Message-----
> From: swing-dev [mailto:swing-dev-bounces at openjdk.java.net] On Behalf Of Alexander Scherbatiy
> Sent: maandag 3 augustus 2015 13:22
> To: Van Den Borre, Koen <Koen.VanDenBorre at esko.com>
> Cc: swing-dev at openjdk.java.net; macosx-port-dev at openjdk.java.net
> Subject: Re: <Swing Dev> Public API for internal Swing classes.
>
>
>
> Hello Koen,
>
> Are you using the isEnabled(Object sender) method just to separate a logic that checks that an action needs to be executed from the action execution in the same way as it it done in the UIAction?
>
> Could you file an enhancement on it and provide a simple use case:
> http://bugreport.java.com/bugreport
>
> Thanks,
> Alexandr.
>
>
> On 7/27/2015 4:13 PM, Van Den Borre, Koen wrote:
>> Hey,
>>
>> We are using sun.swing.UIAction in a custom ListUI where we override the following method and use the sender object:
>>
>> @Override
>> public boolean isEnabled(Object sender)
>>
>> Regards,
>>
>> Koen
>>
>>
>> On 27 Jul 2015, at 14:30, Alexander Scherbatiy <alexandr.scherbatiy at oracle.com> wrote:
>>
>>> According to the JEP 200: The Modular JDK (see
>>> http://openjdk.java.net/jeps/200) we expect that the standard Java SE modules will not export any internal packages.
>>>
>>> It means that classes from internal packages (like sun.swing) will not be accessible.
>>>
>>> For example:
>>> sun.swing.FilePane
>>> sun.swing.SwingUtilities2
>>> sun.swing.sun.swing.plaf.synth.SynthIcon
>>> and others.
>>>
>>>
>>> Please, let us known if you are using the internal Swing API and it is not possible to replace it by public API.
>>>
>>> There are some known requests:
>>>
>>> JDK-8132119 Provide public API for text related methods in SwingUtilities2
>>> https://bugs.openjdk.java.net/browse/JDK-8132119
>>>
>>> JDK-8132120 Provide public API for screen menu bar support on MacOS
>>> https://bugs.openjdk.java.net/browse/JDK-8132120
>>>
>>> JDK-6274842 RFE: Provide a means for a custom look and feel to use desktop font antialiasing settings.
>>> https://bugs.openjdk.java.net/browse/JDK-6274842
>>>
>>>
>>> If you don't know if you use these types (because you use 3rd party
>>> jars) you can use the JDK 8 "jdeps" tool to find such dependencies :-
>>>
>>> ~/jdk1.8/bin/jdeps
>>> Usage: jdeps <options> <classes...>
>>> where <classes> can be a pathname to a .class file, a directory, a
>>> JAR file, or a fully-qualified class name
>>>
>>> Thanks,
>>> Alexandr.
>>>
More information about the swing-dev
mailing list