package info.u250.c2d.box2deditor.ui.util;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
/*
* The DisablePanel will simulate the usage of a "Glass Pane" except that the
* glass pane effect is only for the container and not the entire frame.
* By default the glass pane is invisible. When the DisablePanel is disabled
* then the glass pane is made visible. In addition:
*
* a) All MouseEvents will now be intercepted by the glass pane.
* b) The panel is removed from the normal focus traveral policy to prevent
* any component on the panel from receiving focus.
* c) Key Bindings for any component on the panel will be intercepted.
*
* Therefore, the panel and components will behave as though they are disabled
* even though the state of each component has not been changed.
*
* The background color of the glass pane should use a color with an
* alpha value to create the disabled look.
*
* The other approach to disabling components on a Container is to recurse
* through all the components and set each individual component disabled.
* This class supports two static methods to support this functionality:
*
* a) disable( Container ) - to disable all Components on the Container
* b) enable ( Container ) - to enable all Components disabled by the
* disable() method. That is any component that was disabled prior to using
* the disable() method method will remain disabled.
*/
public class DisabledPanel extends JPanel
{
private static final long serialVersionUID = 1L;
private static DisabledEventQueue queue = new DisabledEventQueue();
private static Map<Container, List<JComponent>> containers = new HashMap<Container, List<JComponent>>();
private JComponent glassPane;
private Container contentContainer;
public Container getContentContainer(){
return this.contentContainer;
}
/**
* Create a DisablePanel for the specified Container. The disabled color
* will be the following color from the UIManager with an alpha value:
* UIManager.getColor("inactiveCaptionBorder");
*
* @param container a Container to be added to this DisabledPanel
*/
public DisabledPanel(Container container)
{
this(container, null);
}
/**
* Create a DisablePanel for the specified Container using the specified
* disabled color.
*
* @param disabledColor the background color of the GlassPane
* @param container a Container to be added to this DisabledPanel
*/
public DisabledPanel(Container container, Color disabledColor)
{
contentContainer = container;
setLayout( new OverlapLayout() );
add( container );
glassPane = new GlassPane();
add( glassPane );
if (disabledColor != null)
glassPane.setBackground( disabledColor );
}
/**
* The background color of the glass pane.
*
* @return the background color of the glass pane
*/
public Color getDisabledColor()
{
return glassPane.getBackground();
}
/**
* Set the background color of the glass pane. This color should
* contain an alpha value to give the glass pane a transparent effect.
*
* @param disabledColor the background color of the glass pane
*/
public void setDisabledColor(Color disabledColor)
{
glassPane.setBackground( disabledColor );
}
/**
* The glass pane of this DisablePanel. It can be customized by adding
* components to it.
*
* @return the glass pane
*/
public JComponent getGlassPane()
{
return glassPane;
}
/**
* Use a custom glass pane. You are responsible for adding the
* appropriate mouse listeners to intercept mouse events.
*
* @param glassPane a JComponent to be used as a glass pane
*/
public void setGlassPane(JComponent glassPane)
{
this.glassPane = glassPane;
}
/**
* Change the enabled state of the panel.
*/
@Override
public void setEnabled(boolean enabled)
{
super.setEnabled(enabled);
if (enabled)
{
glassPane.setVisible(false);
setFocusCycleRoot(false);
queue.removePanel(this);
}
else
{
glassPane.setVisible(true);
setFocusCycleRoot(true); // remove from focus cycle
queue.addPanel(this);
}
}
/**
* Because we use layered panels this should be disabled.
*/
@Override
public boolean isOptimizedDrawingEnabled()
{
return false;
}
/**
* Convenience static method to disable all components of a given
* Container, including nested Containers.
*
* @param container the Container containing Components to be disabled
*/
public static void disable(Container container)
{
List<JComponent> components =
SwingUtils.getDescendantsOfType(JComponent.class, container, true);
List<JComponent> enabledComponents = new ArrayList<JComponent>();
containers.put(container, enabledComponents);
for (JComponent component: components)
{
if (component.isEnabled())
{
enabledComponents.add( component );
component.setEnabled(false);
}
}
}
/**
* Convenience static method to enable Components disabled by using
* the disable() method. Only Components disable by the disable()
* method will be enabled.
*
* @param container a Container that has been previously disabled.
*/
public static void enable(Container container)
{
List<JComponent> enabledComponents = containers.get( container );
if (enabledComponents != null)
{
for (JComponent component: enabledComponents)
{
component.setEnabled(true);
}
containers.remove( container );
}
}
/**
* A simple "glass pane" that has two functions:
*
* a) to paint over top of the Container added to the DisablePanel
* b) to intercept mouse events when visible
*/
class GlassPane extends JComponent
{
private static final long serialVersionUID = 1L;
public GlassPane()
{
setOpaque( false );
setVisible( false );
Color base = UIManager.getColor("inactiveCaptionBorder");
base = (base == null) ? Color.LIGHT_GRAY : base;
Color background = new Color(base.getRed(), base.getGreen(), base.getBlue(), 128);
setBackground( background );
// Disable Mouse events for the panel
addMouseListener( new MouseAdapter() {} );
addMouseMotionListener( new MouseMotionAdapter() {} );
}
/*
* The component is transparent but we want to paint the background
* to give it the disabled look.
*/
@Override
protected void paintComponent(Graphics g)
{
g.setColor( getBackground() );
g.fillRect(0, 0, getSize().width, getSize().height);
}
}
/**
* A custom EventQueue to intercept Key Bindings that are used by any
* component on a DisabledPanel. When a DisabledPanel is disabled it is
* registered with the DisabledEventQueue. This class will check if any
* components on the DisablePanel use KeyBindings. If not then nothing
* changes. However, if some do, then the DisableEventQueue installs
* itself as the current EquentQueue. The dispatchEvent() method is
* overriden to check each KeyEvent. If the KeyEvent is for a Component
* on a DisablePanel then the event is ignored, otherwise it is
* dispatched for normal processing.
*/
static class DisabledEventQueue extends EventQueue
implements WindowListener
{
private Map<DisabledPanel, Set<KeyStroke>> panels =
new HashMap<DisabledPanel, Set<KeyStroke>>();
/**
* Check if any component on the DisabledPanel is using Key Bindings.
* If so, then track the bindings and use a custom EventQueue to
* intercept the KeyStroke before it is passed to the component.
*/
public void addPanel(DisabledPanel panel)
{
// Get all the KeyStrokes used by all the components on the panel
Set<KeyStroke> keyStrokes = getKeyStrokes( panel );
if (keyStrokes.size() == 0) return;
panels.put(panel, keyStrokes);
// More than one panel can be disabled but we only need to install
// the custom EventQueue when the first panel is disabled.
EventQueue current = Toolkit.getDefaultToolkit().getSystemEventQueue();
if (current != this)
current.push(queue);
// We need to track when a Window is closed so we can remove
// the references for all the DisabledPanels on that window.
Window window = SwingUtilities.windowForComponent( panel );
window.removeWindowListener( this );
window.addWindowListener( this );
}
/**
* Check each component to see if its using Key Bindings
*/
private Set<KeyStroke> getKeyStrokes(DisabledPanel panel)
{
Set<KeyStroke> keyStrokes = new HashSet<KeyStroke>();
// Only JComponents use Key Bindings
Container container = ((Container)panel.getComponent(1));
List<JComponent> components =
SwingUtils.getDescendantsOfType(JComponent.class, container);
// We only care about the WHEN_IN_FOCUSED_WINDOW bindings
for (JComponent component: components)
{
InputMap im = component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
if (im != null && im.allKeys() != null)
{
for (KeyStroke keyStroke: im.allKeys())
keyStrokes.add( keyStroke );
}
}
return keyStrokes;
}
/**
* The panel is no longer disabled so we no longer need to intercept
* its KeyStrokes. Restore the default EventQueue when all panels
* using Key Bindings have been enabled.
*/
public void removePanel(DisabledPanel panel)
{
if (panels.remove(panel) != null
&& panels.size() == 0)
pop();
}
/**
* Intercept KeyEvents bound to a component on a DisabledPanel.
*/
@Override
protected void dispatchEvent(AWTEvent event)
{
// Potentially intercept KeyEvents
if (event.getID() == KeyEvent.KEY_TYPED
|| event.getID() == KeyEvent.KEY_PRESSED
|| event.getID() == KeyEvent.KEY_RELEASED)
{
KeyEvent keyEvent = (KeyEvent)event;
KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(keyEvent);
// When using Key Bindings, the source of the KeyEvent will be
// the Window. Check each panel belonging to the source Window.
for (DisabledPanel panel: panels.keySet())
{
Window panelWindow = SwingUtilities.windowForComponent(panel);
// A binding was found so just return without dispatching it.
if (panelWindow == keyEvent.getComponent()
&& searchForKeyBinding(panel, keyStroke))
return;
}
}
// Dispatch normally
super.dispatchEvent(event);
}
/**
* Check if the KeyStroke is for a Component on the DisablePanel
*/
private boolean searchForKeyBinding(DisabledPanel panel, KeyStroke keyStroke)
{
Set<KeyStroke> keyStrokes = panels.get(panel);
return keyStrokes.contains( keyStroke );
}
// Implement WindowListener interface
/**
* When a Window containing a DisablePanel that has been disabled is
* closed, remove the DisablePanel from the DisabledEventQueue. This
* may result in the DisabledEventQueue deregistering itself as the
* current EventQueue.
*/
public void windowClosed(WindowEvent e)
{
List<DisabledPanel> panelsToRemove = new ArrayList<DisabledPanel>();
Window window = e.getWindow();
// Create a List of DisabledPanels to remove
for (DisabledPanel panel: panels.keySet())
{
Window panelWindow = SwingUtilities.windowForComponent(panel);
if (panelWindow == window)
{
panelsToRemove.add( panel );
}
}
// Remove panels here to prevent ConcurrentModificationException
for (DisabledPanel panel: panelsToRemove)
{
removePanel( panel );
}
window.removeWindowListener( this );
}
public void windowActivated(WindowEvent e) {}
public void windowClosing(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
}
}