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-boun...@openjdk.java.net] On Behalf Of 
Alexander Scherbatiy
Sent: maandag 3 augustus 2015 13:22
To: Van Den Borre, Koen <koen.vandenbo...@esko.com>
Cc: swing-dev@openjdk.java.net; macosx-port-...@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.scherba...@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.


Reply via email to