View Javadoc

1   package com.eviware.soapui.support.components;
2   
3   import java.awt.AlphaComposite;
4   import java.awt.Color;
5   import java.awt.Component;
6   import java.awt.Composite;
7   import java.awt.Dimension;
8   import java.awt.Graphics;
9   import java.awt.Graphics2D;
10  import java.awt.Image;
11  import java.awt.Point;
12  import java.awt.Rectangle;
13  import java.awt.RenderingHints;
14  import java.awt.event.ActionEvent;
15  import java.awt.event.ActionListener;
16  import java.awt.event.MouseEvent;
17  import java.awt.event.MouseListener;
18  import java.awt.event.MouseMotionListener;
19  import java.awt.image.BufferedImage;
20  
21  import javax.swing.BorderFactory;
22  import javax.swing.ImageIcon;
23  import javax.swing.JButton;
24  import javax.swing.JComponent;
25  import javax.swing.JLabel;
26  import javax.swing.JLayeredPane;
27  import javax.swing.JPanel;
28  import javax.swing.JPopupMenu;
29  import javax.swing.JScrollPane;
30  import javax.swing.JViewport;
31  import javax.swing.MenuSelectionManager;
32  
33  import com.eviware.soapui.support.UISupport;
34  
35  /***
36   * This is a button which is designed to be the corner component of a
37   * <code>JScrollPane</code>. It triggers a popup menu which holds a scaled image
38   * of the component contained inside the <code>JScrollPane</code>.
39   */
40  
41  public class PreviewCorner extends JButton implements ActionListener
42  {
43  	private String _corner;
44  	private PreviewPopup _previewPopup;
45  
46  	/***
47  	 * @param scrollPane
48  	 *           the <code>JScrollPane</code> to preview
49  	 * @param zoomIcon
50  	 *           the icon to use for the button
51  	 * @param doCloseAfterClick
52  	 *           When <code>true</code> the preview popup menu is closed on mouse
53  	 *           click.
54  	 * @param corner
55  	 *           Supposed to be one of the four corners of a
56  	 *           <code>JScrollPane</code>, like
57  	 *           <code>JScrollPane.LOWER_RIGHT_CORNER</code> for example, which
58  	 *           should match the position of the corner component of the scroll
59  	 *           pane. Note: If this parameter is not set correctly,
60  	 *           <code>JScrollPane.UPPER_LEFT_CORNER</code> will be used instead.
61  	 */
62  
63  	public PreviewCorner( JScrollPane scrollPane, ImageIcon zoomIcon, boolean doCloseAfterClick, String corner )
64  	{
65  
66  		super( zoomIcon );
67  		this._corner = corner;
68  
69  		// Creates the popup menu, containing the scaled image of the component.
70  		_previewPopup = new PreviewPopup( scrollPane, doCloseAfterClick );
71  
72  		setToolTipText( "Show a Panorama View of the contained editor" );
73  
74  		// The action listener is used to trigger the popup menu.
75  		addActionListener( this );
76  	}
77  
78  	public PreviewCorner( JScrollPane scrollPane, ImageIcon zoomIcon, String corner )
79  	{
80  		this( scrollPane, zoomIcon, false, corner );
81  	}
82  
83  	public void actionPerformed( ActionEvent e )
84  	{
85  		_previewPopup.showUpInCorner( this, _corner );
86  	}
87  
88  	public void release()
89  	{
90  		_previewPopup.release();
91  		removeActionListener( this );
92  	}
93  }
94  
95  class PreviewPopup extends JPopupMenu implements MouseListener, MouseMotionListener
96  {
97  
98  	private JScrollPane _scrollPane;
99  	private JViewport _viewPort;
100 
101 	private JLabel _zoomWindow; // the JLabel containing the scaled image
102 
103 	private JPanel _cursorLabel; // the JLabel mimicking the fake rectangle
104 	// cursor
105 
106 	// This component will hold both JLabels _zoomWindow and _cursorLabel,
107 	// the latter on top of the other.
108 	private JLayeredPane _layeredPane;
109 
110 	private int _iconWidth;
111 	private int _iconHeight;
112 
113 	private boolean _doCloseAfterClick;
114 
115 	float _ratio;
116 
117 	// DELTA is the space between the scroll pane and the preview popup menu.
118 	private static int DELTA = 5;
119 
120 	public PreviewPopup( JScrollPane scrollPane, boolean doCloseAfterClick )
121 	{
122 		this.setBorder( BorderFactory.createEtchedBorder() );
123 
124 		_doCloseAfterClick = doCloseAfterClick;
125 
126 		_scrollPane = scrollPane;
127 		_viewPort = _scrollPane.getViewport();
128 
129 		_zoomWindow = new JLabel();
130 		_cursorLabel = createCursor();
131 
132 		_layeredPane = new JLayeredPane();
133 
134 		_layeredPane.add( _zoomWindow, new Integer( 0 ) );
135 		_layeredPane.add( _cursorLabel, new Integer( 1 ) );
136 
137 		// Creates a blank transparent cursor to be used as the cursor of
138 		// the popup menu.
139 		BufferedImage bim = new BufferedImage( 1, 1, BufferedImage.TYPE_4BYTE_ABGR );
140 		setCursor( getToolkit().createCustomCursor( bim, ( new Point( 0, 0 ) ), "PreviewCursor" ) );
141 
142 		this.add( _layeredPane );
143 
144 		// Adds the mouse input listeners to the _layeredPane to scroll the
145 		// viewport and to move the fake cursor (_cursorLabel).
146 		_layeredPane.addMouseListener( this );
147 		_layeredPane.addMouseMotionListener( this );
148 	}
149 
150 	public void release()
151 	{
152 		if( getParent() != null )
153 			getParent().remove( this );
154 
155 		_layeredPane.removeMouseListener( this );
156 		_layeredPane.removeMouseMotionListener( this );
157 
158 		_scrollPane = null;
159 		_layeredPane = null;
160 		_viewPort = null;
161 	}
162 
163 	/***
164 	 * By default, the right corner of a popup menu is positionned at the right
165 	 * of a mouse click. What we want is to have the preview popup menu
166 	 * positionned <i>inside</i> the scroll pane, near the corner component. The
167 	 * purpose of this method is to display the scaled image of the component of
168 	 * the scroll pane, and to calculate the correct position of the preview
169 	 * popup menu.
170 	 */
171 
172 	public void showUpInCorner( Component c, String corner )
173 	{
174 
175 		if( _viewPort.getComponentCount() == 0 )
176 			return;
177 
178 		float scaleFactor = 1.1f; // _viewPort.getComponent( 0 ).getHeight() /
179 		// _scrollPane.getHeight() * 2;
180 
181 		// if (_viewPort.getWidth() < (_viewPort.getHeight() * scaleFactor))
182 		// _ratio =( int ) ( _viewPort.getComponent(0).getWidth() /
183 		// (_viewPort.getWidth() / scaleFactor) );
184 		//
185 		// else
186 		// _ratio = ( int ) (( _viewPort.getComponent(0).getHeight() /
187 		// (_viewPort.getHeight()) / scaleFactor ));
188 		_ratio = ( ( ( float )_viewPort.getComponent( 0 ).getHeight() / ( ( float )_viewPort.getHeight() ) / scaleFactor ) );
189 
190 		if( _ratio < 2 )
191 			_ratio = 2;
192 
193 		// System.out.println( "ratio = " + _ratio );
194 
195 		int zoomWindowImageWidth = ( int )( _viewPort.getComponent( 0 ).getWidth() / _ratio );
196 		if( zoomWindowImageWidth < 10 )
197 		{
198 			UISupport.showInfoMessage( "Viewport too large for readable image, use scrollbar instead" );
199 			return;
200 		}
201 
202 		int zoomWindowImageHeight = ( int )( _viewPort.getComponent( 0 ).getHeight() / _ratio );
203 
204 		// System.out.println( "ratio = " + _ratio + ", zoomWindowImageWidth = " +
205 		// zoomWindowImageWidth +
206 		// ", zoomWindowImageHeight = " + zoomWindowImageHeight);
207 
208 		/*
209 		 * Image componentImage =
210 		 * captureComponentViewAsBufferedImage(_viewPort.getComponent(0))
211 		 * .getScaledInstance( zoomWindowImageWidth, zoomWindowImageHeight,
212 		 * Image.SCALE_SMOOTH);
213 		 */
214 		/*
215 		 * Based on Shannon Hickey's comments. This is much faster way to scale
216 		 * instance Thanks! Shannon
217 		 */
218 		Image capture = captureComponentViewAsBufferedImage( _viewPort.getComponent( 0 ) );
219 		Image componentImage = new BufferedImage( zoomWindowImageWidth, zoomWindowImageHeight, BufferedImage.TYPE_INT_RGB );
220 		Graphics2D g2d = ( Graphics2D )componentImage.getGraphics();
221 		/* if you want smoother scaling */
222 		if( zoomWindowImageWidth > 15 )
223 			g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR );
224 		g2d.drawImage( capture, 0, 0, zoomWindowImageWidth, zoomWindowImageHeight, null );
225 		g2d.dispose();
226 
227 		// Converts the Image to an ImageIcon to be used with a JLabel.
228 		ImageIcon componentIcon = new ImageIcon( componentImage );
229 
230 		_iconWidth = componentIcon.getIconWidth();
231 		_iconHeight = componentIcon.getIconHeight();
232 
233 		_zoomWindow.setIcon( componentIcon );
234 
235 		_zoomWindow.setBounds( 0, 0, _iconWidth, _iconHeight );
236 
237 		int cursorWidth = ( int )( _viewPort.getWidth() / _ratio );
238 
239 		int cursorHeight = ( int )( _viewPort.getHeight() / _ratio );
240 
241 		_cursorLabel.setBounds( 0, 0, cursorWidth, cursorHeight );
242 
243 		_layeredPane.setPreferredSize( new Dimension( _iconWidth, _iconHeight ) );
244 
245 		int dx = componentIcon.getIconWidth() + DELTA;
246 		int dy = componentIcon.getIconHeight() + DELTA;
247 
248 		if( corner == JScrollPane.UPPER_LEFT_CORNER )
249 			;
250 		else if( corner == JScrollPane.UPPER_RIGHT_CORNER )
251 			dx = -dx;
252 		else if( corner == JScrollPane.LOWER_RIGHT_CORNER )
253 		{
254 			dx = -dx;
255 			dy = -dy;
256 		}
257 		else if( corner == JScrollPane.LOWER_LEFT_CORNER )
258 			dy = -dy;
259 
260 		if( dy < 0 && Math.abs( dy ) > _viewPort.getHeight() )
261 		{
262 			dy = -_viewPort.getHeight() - 10;
263 		}
264 
265 		// System.out.println( "Showing at " + dx + ", " + dy );
266 
267 		// Shows the popup menu at the right place.
268 		this.show( c, dx, dy );
269 
270 	}
271 
272 	public JPanel createCursor()
273 	{
274 		JPanel label = new JPanel()
275 		{
276 
277 			@Override
278 			protected void paintComponent( Graphics g )
279 			{
280 				Composite composite = ( ( Graphics2D )g ).getComposite();
281 				( ( Graphics2D )g ).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.2f ) );
282 				super.paintComponent( g );
283 				( ( Graphics2D )g ).setComposite( composite );
284 			}
285 
286 		};
287 		label.setBorder( BorderFactory.createLineBorder( Color.gray ) );
288 		label.setVisible( false );
289 		label.setOpaque( true );
290 		label.setBackground( Color.orange.darker() );
291 		return label;
292 	}
293 
294 	public void mouseClicked( MouseEvent e )
295 	{
296 	}
297 
298 	public void mouseEntered( MouseEvent e )
299 	{
300 		// When the mouse enters the preview popup menu, set the visibility
301 		// of the fake cursor to true.
302 		_cursorLabel.setVisible( true );
303 	}
304 
305 	public void mouseExited( MouseEvent e )
306 	{
307 		// When the mouse exits the preview popup menu, set the visibility
308 		// of the fake cursor to false.
309 		_cursorLabel.setVisible( false );
310 	}
311 
312 	public void mousePressed( MouseEvent e )
313 	{
314 	}
315 
316 	public void mouseReleased( MouseEvent e )
317 	{
318 		// When the mouse is released, set the visibility of the preview
319 		// popup menu to false only if doCloseAfterClick is set to true.
320 		if( _doCloseAfterClick )
321 		{
322 			this.setVisible( false );
323 			_cursorLabel.setVisible( false );
324 			MenuSelectionManager.defaultManager().clearSelectedPath();
325 			setInvoker( null );
326 		}
327 	}
328 
329 	public void mouseDragged( MouseEvent e )
330 	{
331 		moveCursor( e.getX(), e.getY() );
332 		scrollViewPort();
333 	}
334 
335 	public void mouseMoved( MouseEvent e )
336 	{
337 		moveCursor( e.getX(), e.getY() );
338 		scrollViewPort();
339 	}
340 
341 	/***
342 	 * Centers the fake cursor (_cursorLabel) position on the coordinates
343 	 * specified in the parameters.
344 	 */
345 	private void moveCursor( int x, int y )
346 	{
347 		int dx = x - _cursorLabel.getWidth() / 2;
348 		int dy = y - _cursorLabel.getHeight() / 2;
349 		_cursorLabel.setLocation( dx, dy );
350 	}
351 
352 	/***
353 	 * Scrolls the viewport according to the fake cursor position in the preview
354 	 * popup menu.
355 	 */
356 	private void scrollViewPort()
357 	{
358 		Point cursorLocation = _cursorLabel.getLocation();
359 		int dx = ( int )Math.max( cursorLocation.getX(), 0 );
360 		int dy = ( int )Math.max( cursorLocation.getY(), 0 );
361 
362 		dx = ( int )( dx * _ratio );
363 		dy = ( int )( dy * _ratio );
364 
365 		( ( JComponent )_viewPort.getComponent( 0 ) ).scrollRectToVisible( new Rectangle( dx, dy, _viewPort.getWidth(),
366 				_viewPort.getHeight() ) );
367 	}
368 
369 	/***
370 	 * takes a java component and generates an image out of it.
371 	 * 
372 	 * @param c
373 	 *           the component for which image needs to be generated
374 	 * @return the generated image
375 	 */
376 	public static BufferedImage captureComponentViewAsBufferedImage( Component c )
377 	{
378 		Dimension size = c.getSize();
379 		BufferedImage bufferedImage = new BufferedImage( size.width, size.height, BufferedImage.TYPE_INT_RGB );
380 		Graphics bufferedGraphics = bufferedImage.createGraphics();
381 		c.paint( bufferedGraphics );
382 		return bufferedImage;
383 	}
384 
385 }