/* * Bibliothek - DockingFrames * Library built on Java/Swing, allows the user to "drag and drop" * panels containing any Swing-Component the developer likes to add. * * Copyright (C) 2007 Benjamin Sigg * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Benjamin Sigg * benjamin_sigg@gmx.ch * CH - Switzerland */ package bibliothek.gui.dock.security; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Point; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.util.EventListener; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JToolTip; import javax.swing.SwingUtilities; import bibliothek.gui.DockController; import bibliothek.gui.dock.control.focus.FocusController; import bibliothek.gui.dock.control.focus.MouseFocusObserver; import bibliothek.gui.dock.util.PropertyKey; import bibliothek.gui.dock.util.PropertyValue; import bibliothek.gui.dock.util.property.ConstantPropertyFactory; import bibliothek.util.Todo; import bibliothek.util.Todo.Compatibility; import bibliothek.util.Todo.Priority; import bibliothek.util.Todo.Version; import bibliothek.util.Workarounds; /** * A panel containing two children: a "content pane" and a "glass pane". The * "content pane" can be replaced by the client and can be any {@link JComponent}. * The "glassed pane" is an invisible panel above the "content pane". It will * catch all {@link MouseEvent}s, inform the {@link FocusController} about * them, and then forward the events to the "content pane". * @author Benjamin Sigg */ @Todo( compatibility=Compatibility.COMPATIBLE, priority=Priority.MAJOR, target=Version.VERSION_1_1_2, description="In Java 1.7 if a mouse-dragged is followed by a mouse-exit, and the mouse is over another GlassedPane, then this GlassedPane no longer receives events that it received in Java 1.6") public class GlassedPane extends JPanel{ /** the strategy used by a {@link GlassedPane} to manage its tooltips */ public static final PropertyKey<TooltipStrategy> TOOLTIP_STRATEGY = new PropertyKey<TooltipStrategy>( "tooltip strategy", new ConstantPropertyFactory<TooltipStrategy>( new DefaultTooltipStrategy() ), true ); /** An arbitrary component */ private JComponent contentPane = new JPanel(); /** A component lying over all other components. Catches every MouseEvent */ private JComponent glassPane = new GlassPane(); /** A controller which will be informed about every click of the mouse */ private DockController controller; /** whether a {@link MouseEvent} is forwarded right now */ private boolean onSending = false; /** the strategy used to manage tooltips */ private PropertyValue<TooltipStrategy> tooltips = new PropertyValue<TooltipStrategy>( TOOLTIP_STRATEGY ){ @Override protected void valueChanged( TooltipStrategy oldValue, TooltipStrategy newValue ){ if( oldValue != null ){ oldValue.uninstall( GlassedPane.this ); } if( newValue != null ){ newValue.install( GlassedPane.this ); } } }; /** * Creates a new pane */ public GlassedPane(){ setLayout( null ); contentPane.setOpaque( false ); setOpaque( false ); add( glassPane ); add( contentPane ); setFocusCycleRoot( true ); } /** * Sets the controller to inform about {@link KeyEvent}s. * @param controller the controller to inform */ public void setController( DockController controller ){ this.controller = controller; tooltips.setProperties( controller ); } @Override public void doLayout() { int width = getWidth(); int height = getHeight(); if( contentPane != null ){ contentPane.setBounds( 0, 0, width, height ); } glassPane.setBounds( 0, 0, width, height ); } @Override public Dimension getPreferredSize() { if( contentPane == null ){ return super.getPreferredSize(); } return contentPane.getPreferredSize(); } @Override public Dimension getMaximumSize() { if( contentPane == null ){ return super.getMaximumSize(); } return contentPane.getMaximumSize(); } @Override public Dimension getMinimumSize() { if( contentPane == null ){ return super.getMinimumSize(); } return contentPane.getMinimumSize(); } /** * Sets the center panel of this GlassedPane. * @param contentPane the content of this pane, a value of <code>null</code> will * just remove the current content pane */ public void setContentPane( JComponent contentPane ) { this.contentPane = contentPane; removeAll(); add( glassPane ); if( contentPane != null ){ add( contentPane ); } } /** * Gets the content of this pane. * @return the content, may be <code>null</code> */ public JComponent getContentPane(){ return contentPane; } /** * Gets the transparent panel that is lying over the ContentPane. * @return the GlassPane */ public JComponent getGlassPane(){ return glassPane; } /** * A panel that lies over all other components of the enclosing GlassedPane. * This panel catches all MouseEvent, and informs the {@link MouseFocusObserver}. * @author Benjamin Sigg */ public class GlassPane extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener{ /** the component where a drag-event started */ private Component dragged; /** the component currently under the mouse */ private Component over; /** the number of pressed buttons */ private int downCount = 0; /** callback forwarded to the current {@link TooltipStrategy} of {@link GlassedPane#tooltips} */ private TooltipStrategyCallback callback = new TooltipStrategyCallback(){ public void setToolTipText( String text ){ GlassPane.this.setToolTipText( text ); } public String getToolTipText(){ return GlassPane.this.getToolTipText(); } public GlassedPane getGlassedPane(){ return GlassedPane.this; } public JToolTip createToolTip(){ return superCreateToolTip(); } }; /** * Creates a new GlassPane. */ public GlassPane(){ addMouseListener( this ); addMouseMotionListener( this ); addMouseWheelListener( this ); setOpaque( false ); setFocusable( false ); Workarounds.getDefault().markAsGlassPane( this ); } public void mouseClicked( MouseEvent e ) { if( !e.isConsumed() ) send( e ); } public void mousePressed( MouseEvent e ) { if( !e.isConsumed() ) send( e ); } public void mouseReleased( MouseEvent e ) { if( !e.isConsumed() ) send( e ); } public void mouseEntered( MouseEvent e ) { if( !e.isConsumed() ) send( e ); } public void mouseExited( MouseEvent e ) { if( !e.isConsumed() && isVisible() ) send( e ); if( !isVisible() ){ downCount = 0; } } public void mouseDragged( MouseEvent e ) { if( !e.isConsumed() ) send( e ); } public void mouseMoved( MouseEvent e ) { if( !e.isConsumed() ) send( e ); } public void mouseWheelMoved( MouseWheelEvent e ) { if( !e.isConsumed() ) send( e ); } /** * Shorthand for <code>send( e, e.getID() );</code>. * @param e the event to send */ private void send( MouseEvent e ){ send( e, e.getID() ); } /** * Dispatches the event <code>e</code> to the ContentPane or a child * of the ContentPane. Also informs the {@link MouseFocusObserver} about the event. * @param e the event to handle * @param id the type of the event */ private void send( MouseEvent e, int id ){ if( !onSending ){ try{ onSending = true; sendNow( e, id ); } finally{ onSending = false; } } } private void sendNow( MouseEvent e, int id ){ if( contentPane == null ){ return; } Point mouse = e.getPoint(); Component component = SwingUtilities.getDeepestComponentAt( contentPane, mouse.x, mouse.y ); if( component != null && !component.isEnabled() ){ component = null; } else{ component = fallThrough( component, e ); } boolean drag = id == MouseEvent.MOUSE_DRAGGED; boolean press = id == MouseEvent.MOUSE_PRESSED; boolean release = id == MouseEvent.MOUSE_RELEASED; boolean moved = id == MouseEvent.MOUSE_MOVED; boolean entered = id == MouseEvent.MOUSE_ENTERED; boolean exited = id == MouseEvent.MOUSE_EXITED; if( drag && dragged == null ) dragged = component; else if( drag ) component = dragged; if( press ){ downCount |= 1 << e.getButton(); } if( downCount > 0 && dragged != null ) component = dragged; else if( downCount > 0 && dragged == null ) dragged = component; else if( downCount == 0 ) dragged = null; if( release ){ downCount &= ~(1 << e.getButton()); } if( (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) == 0 ){ // no button is pressed currently, reset dragging downCount = 0; dragged = null; } boolean overNewComponent = false; if( moved || entered || exited ){ if( over != component ){ overNewComponent = true; if( over != null ){ Point overMouse = SwingUtilities.convertPoint( this, mouse, over ); over.dispatchEvent( new MouseEvent( over, MouseEvent.MOUSE_EXITED, e.getWhen(), e.getModifiers(), overMouse.x, overMouse.y, e.getClickCount(), e.isPopupTrigger(), e.getButton() )); } over = component; if( over != null ){ Point overMouse = SwingUtilities.convertPoint( this, mouse, over ); over.dispatchEvent( new MouseEvent( over, MouseEvent.MOUSE_ENTERED, e.getWhen(), e.getModifiers(), overMouse.x, overMouse.y, e.getClickCount(), e.isPopupTrigger(), e.getButton() )); } } } if( component == null ){ setCursor( null ); setToolTipText( null ); } else{ mouse = SwingUtilities.convertPoint( this, mouse, component ); MouseEvent forward = new MouseEvent( component, id, e.getWhen(), e.getModifiers(), mouse.x, mouse.y, e.getClickCount(), e.isPopupTrigger(), e.getButton() ); if( controller != null ){ controller.getGlobalMouseDispatcher().dispatch( forward ); } component.dispatchEvent( forward ); Cursor cursor = component.getCursor(); if( getCursor() != cursor ) setCursor( cursor ); tooltips.getValue().setTooltipText( over, forward, overNewComponent, callback ); } } /** * Assuming this {@link GlassedPane} wants to forward <code>event</code> to <code>component</code>, * this method can decide that <code>component</code> should not receive the event. Instead some * other {@link Component} should. * @param component the component which in theory should get the event * @param event the event to be forwarded * @return the component which really gets the event, can also be <code>null</code> or <code>component</code> */ private Component fallThrough( Component component, MouseEvent event ){ Class<? extends EventListener> type = null; if( event.getID() == MouseEvent.MOUSE_DRAGGED || event.getID() == MouseEvent.MOUSE_MOVED ){ type = MouseMotionListener.class; } else if( event.getID() == MouseEvent.MOUSE_WHEEL ){ type = MouseWheelListener.class; } else{ type = MouseListener.class; } while( component != null && component.getListeners( type ).length == 0 ){ component = component.getParent(); } return component; } @Override public JToolTip createToolTip(){ return tooltips.getValue().createTooltip( over, callback ); } private JToolTip superCreateToolTip(){ return super.createToolTip(); } /** * Dispatches the event <code>e</code> to the ContentPane or one * of the children of ContentPane. Also informs the focusController about * the event. * @param e the event to dispatch */ private void send( MouseWheelEvent e ){ if( contentPane == null ){ return; } Point mouse = e.getPoint(); Component component = SwingUtilities.getDeepestComponentAt( contentPane, mouse.x, mouse.y ); if( component != null ){ mouse = SwingUtilities.convertPoint( this, mouse, component ); MouseWheelEvent forward = new MouseWheelEvent( component, e.getID(), e.getWhen(), e.getModifiers(), mouse.x, mouse.y, e.getClickCount(), e.isPopupTrigger(), e.getScrollType(), e.getScrollAmount(), e.getWheelRotation() ); if( controller != null ){ controller.getGlobalMouseDispatcher().dispatch( forward ); } component.dispatchEvent( forward ); } } } }