I draw a line on the slide and then I use the slider to change the zoom.
The line doesn't move or scale correctly.
In the code, the line is rendered directly by Swing, ie the calls to zoomPaint don't do anything.
Moreover, the code doesn't do a real zoom but emulates it by changing the bounds of the component, which makes it impossible to accurately change the zoom because of the loss of precision when converting doubles to ints.
Eg: zoom = 1 -> width = 11; zoom = 0.5 -> width = 5; zoom = 1 -> width = 10 instead of 11.
I tried saving the original bounds but this information needs to be updated when the user moves or resizes the component, but not when it is moved or resized by the zoom emulation algorithm.
I can see of a way to do that using reflection, but we really shouldn't go that way.
Even if the save succeeds (or if the user doesn't move the component with the mouse), the positioning of the component still seems off for some zoom values; I suspect this is due to the layout manager, but I'm not sure.
Bottom line: the rendering should be done differently, because the way it is now makes it quite difficult to implement the zoom correctly.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
The problem with this fix is that when the zoom is not 1, the mouse-sensitive areas of the components don't match the display.
As a consequence, selecting/modifying/creating an object can result in unpleasant visual artifacts if the zoom is not 1.
Possible solutions:
emulate the mouse event dispatching mechanism in Slide;
use a new "gizmo component", rendered taking the zoom into account; but that wouldn't solve the problem when creating objects.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
The goal is to intercept mouse events before they get to the slide objects, duplicate these events but use a zoom-adjusted position, and dispatch the new events to the appropriate components.
Here is a demo to illustrate (the important parts are the swing timer updating the slide and the MouseEventRedispatcher class):
packagesandbox;importjava.awt.BorderLayout;importjava.awt.Component;importjava.awt.Graphics;importjava.awt.Graphics2D;importjava.awt.Point;importjava.awt.event.ActionEvent;importjava.awt.event.ActionListener;importjava.awt.event.MouseEvent;importjava.awt.event.MouseWheelEvent;importjavax.swing.JComponent;importjavax.swing.JFrame;importjavax.swing.JPanel;importjavax.swing.JScrollPane;importjavax.swing.JSlider;importjavax.swing.JTextArea;importjavax.swing.SwingUtilities;importjavax.swing.WindowConstants;importjavax.swing.event.MouseInputAdapter;/****@authorcodistmonk(creation2010-05-01)*/publicfinalclassDemoZoom{/***Privatedefaultconstructortoensurethattheclassisn't instantiated.*/privateDemoZoom(){//Nothing}/****@paramarguments*<br>Unused*/publicstaticfinalvoidmain(finalString[]arguments){SwingUtilities.invokeLater(newRunnable(){@Overridepublicfinalvoidrun(){newMainFrame().setVisible(true);}});}/****@authorcodistmonk(creation2010-05-01)*/@SuppressWarnings("serial")privatestaticfinalclassMainFrameextendsJFrame{privatefinalSlideslide;privatefinalJSliderzoomSlider;publicMainFrame(){this.slide=newSlide(this);this.zoomSlider=newJSlider(50,200,100);this.setGlassPane(newGlassPane(this));this.add(newJScrollPane(this.getSlide()),BorderLayout.CENTER);this.add(this.getZoomSlider(),BorderLayout.SOUTH);this.getGlassPane().setVisible(true);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);this.pack();this.setLocationRelativeTo(null);}/****@return*<br>Anon-nullvalue*<br>Areference*/publicfinalSlidegetSlide(){returnthis.slide;}/****@return*<br>Anon-nullvalue*<br>Areference*/publicfinalJSlidergetZoomSlider(){returnthis.zoomSlider;}}/****@authorcodistmonk(creation2010-05-01)*/@SuppressWarnings("serial")privatestaticfinalclassSlideextendsJPanel{privatefinalMainFramemainFrame;/****@parammainFrame*<br>Shouldnotbenull*<br>Referenceparameter*/publicSlide(finalMainFramemainFrame){super(newBorderLayout());this.mainFrame=mainFrame;this.add(newJTextArea("Editable text area inside the slide."));newjavax.swing.Timer(REFRESH_DELAY,newActionListener(){@OverridepublicfinalvoidactionPerformed(finalActionEventevent){finalDoublezoom=mainFrame.getZoomSlider().getValue()/100.0;mainFrame.getSlide().setSize((int)(SLIDE_WIDTH*zoom),(int)(SLIDE_HEIGHT*zoom));mainFrame.getSlide().setPreferredSize(mainFrame.getSlide().getSize());mainFrame.getSlide().repaint();}}).start();}@Overridepublicfinalvoidpaint(finalGraphicsg){finalDoublezoom=this.mainFrame.getZoomSlider().getValue()/100.0;((Graphics2D)g).scale(zoom,zoom);super.paint(g);}publicstaticfinalintSLIDE_WIDTH=800;publicstaticfinalintSLIDE_HEIGHT=600;privatestaticfinalintREFRESH_DELAY=100;}/****@authorcodistmonk(creation2010-05-01)*/@SuppressWarnings("serial")privatestaticfinalclassGlassPaneextendsJComponent{/****@parammainFrame*<br>Shouldnotbenull*<br>Referenceparameter*/publicGlassPane(finalMainFramemainFrame){finalMouseEventRedispatchermouseEventRedispatcher=newMouseEventRedispatcher(mainFrame);this.addMouseListener(mouseEventRedispatcher);this.addMouseMotionListener(mouseEventRedispatcher);this.addMouseWheelListener(mouseEventRedispatcher);}}/****@authorcodistmonk(creation2010-05-01)*/privatestaticfinalclassMouseEventRedispatcherextendsMouseInputAdapter{privatefinalMainFramemainFrame;/****@parammainFrame*<br>Shouldnotbenull*<br>Referenceparameter*/publicMouseEventRedispatcher(finalMainFramemainFrame){this.mainFrame=mainFrame;}@OverridepublicfinalvoidmouseClicked(finalMouseEventevent){this.redispatch(event);}@OverridepublicfinalvoidmouseDragged(finalMouseEventevent){this.redispatch(event);}@OverridepublicfinalvoidmouseEntered(finalMouseEventevent){this.redispatch(event);}@OverridepublicfinalvoidmouseExited(finalMouseEventevent){this.redispatch(event);}@OverridepublicfinalvoidmouseMoved(finalMouseEventevent){this.redispatch(event);}@OverridepublicfinalvoidmousePressed(finalMouseEventevent){this.redispatch(event);}@OverridepublicfinalvoidmouseReleased(finalMouseEventevent){this.redispatch(event);}@OverridepublicfinalvoidmouseWheelMoved(finalMouseWheelEventevent){this.redispatch(event);}/***@paramevent*<br>Shouldnotbenull*/privatefinalvoidredispatch(finalMouseEventevent){finalComponentcomponent=this.getDestinationComponent(event);component.dispatchEvent(this.convertMouseEvent(event,component));}/****@paramevent*<br>Shouldnotbenull*@paramdestinationComponent*<br>Shouldnotbenull*<br>Referenceparameter*@return*<br>Anon-nullvalue*<br>Anewvalue*/privatefinalMouseEventconvertMouseEvent(finalMouseEventevent,finalComponentdestinationComponent){if(SwingUtilities.isDescendingFrom(destinationComponent,this.mainFrame.getSlide())){finalPointzoomedPoint=SwingUtilities.convertPoint((Component)event.getSource(),event.getPoint(),this.mainFrame.getSlide());finalDoublezoom=this.mainFrame.getZoomSlider().getValue()/100.0;returnconvertMouseEvent(event,destinationComponent,(int)(zoomedPoint.x/zoom),(int)(zoomedPoint.y/zoom));}returnSwingUtilities.convertMouseEvent((Component)event.getSource(),event,destinationComponent);}/****@paramevent*<br>Shouldnotbenull*@return*<br>Anon-nullvalue*<br>Areference*/privatefinalComponentgetDestinationComponent(finalMouseEventevent){finalComponentcomponent=SwingUtilities.getDeepestComponentAt(this.mainFrame.getContentPane(),event.getX(),event.getY());returncomponent==null?this.mainFrame.getContentPane():component;}/****@paramevent*<br>Shouldnotbenull*@paramdestinationComponent*<br>Shouldnotbenull*<br>Referenceparameter*@paramdestinationX*<br>Range:Anyinteger*@paramdestinationY*<br>Range:Anyinteger*@return*<br>Anon-nullvalue*<br>Anewvalue*/privatestaticfinalMouseEventconvertMouseEvent(finalMouseEventevent,finalComponentdestinationComponent,finalintdestinationX,finalintdestinationY){finalMouseEventresult;if(eventinstanceofMouseWheelEvent){result=newMouseWheelEvent(destinationComponent,event.getID(),event.getWhen(),event.getModifiers(),destinationX,destinationY,event.getClickCount(),event.isPopupTrigger(),((MouseWheelEvent)event).getScrollType(),((MouseWheelEvent)event).getScrollAmount(),((MouseWheelEvent)event).getWheelRotation());}else{result=newMouseEvent(destinationComponent,event.getID(),event.getWhen(),event.getModifiers(),destinationX,destinationY,event.getClickCount(),event.isPopupTrigger(),event.getButton());}returnresult;}}}
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I really am impressed with the turnout of your glass pane idea. Very nice job. I did also notice that it seems to take much more time to repaint. I will see if I can help speed that up.
Kyle
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I forgot to mention that we also need to get the resize of the slide back to how it was. We don't want it getting all out of proportion when the component is resized.
Kyle
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
the menus don't work anymore, dispatching the events on the menu bar doesn't seem to work;
changing the zoom causes brief visual anomalies, possibly because of the scrollpane's viewport's layout manager.
To fix the first one, we could use the glass pane only on the slide instead of the whole frame.
To fix the second one, we could put the slide in a container with a layout manager that doesn't move/resize the child components (layered pane, card layout…).
When resizing the slide, we resize the container at the same time, and let the viewport layout manager do whatever it wants to the container when it is too small.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I'm afraid I don't understand exactly what or why you did what is in commit 62. In commit 61 there were no artifacts and I see no visible difference or advantage of the changes in commit 62. Perhaps you could change my mind?
Kyle
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
In revision 62 I added a listener to the zoom slider to resize the slide according to the zoom.
But the viewport layout manager detects the change and also tries to resize the slide.
As a result, when the zoom is being changed, the slide is drawn twice with different dimensions, hence the visual defect.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I realized that the layered pane in Slide is the actual slide because it contains the objects handled by the user.
I suggest to rename Slide into SlideContainer.
Then, the layered pane should be made into a new class Slide, and the zooming code should be moved there.
Also, the only way I found to reduce the area covered by he glass pane is by using a layered pane (instead of the frame glassPane property) to host the glass pane and the components under it.
Possibilities:
SlideContainer (currently Slide in revision 64) could extend JLayeredPane instead of JPanel; there doesn't seem to be an easy way to operate this change with the NetBeans GUI builder, so SlideContainer would probably have to be created using the current Slide as a guide;
the future Slide (currently a simple layered pane in revision 64) is already a layered pane, so we could add the glass pane to it and change the way z-order is handled for graphic objects.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Here is the problem I'm having.
I draw a line on the slide and then I use the slider to change the zoom.
The line doesn't move or scale correctly.
In the code, the line is rendered directly by Swing, ie the calls to zoomPaint don't do anything.
Moreover, the code doesn't do a real zoom but emulates it by changing the bounds of the component, which makes it impossible to accurately change the zoom because of the loss of precision when converting doubles to ints.
Eg: zoom = 1 -> width = 11; zoom = 0.5 -> width = 5; zoom = 1 -> width = 10 instead of 11.
I tried saving the original bounds but this information needs to be updated when the user moves or resizes the component, but not when it is moved or resized by the zoom emulation algorithm.
I can see of a way to do that using reflection, but we really shouldn't go that way.
Even if the save succeeds (or if the user doesn't move the component with the mouse), the positioning of the component still seems off for some zoom values; I suspect this is due to the layout manager, but I'm not sure.
Bottom line: the rendering should be done differently, because the way it is now makes it quite difficult to implement the zoom correctly.
I fixed the zoom by modifying the Slide.paint() method so that when the components are rendered, they are scaled according to the zoom factor.
The problem with this fix is that when the zoom is not 1, the mouse-sensitive areas of the components don't match the display.
As a consequence, selecting/modifying/creating an object can result in unpleasant visual artifacts if the zoom is not 1.
Possible solutions:
emulate the mouse event dispatching mechanism in Slide;
use a new "gizmo component", rendered taking the zoom into account; but that wouldn't solve the problem when creating objects.
I added repaint() to mouse up and Mose down and that fixed part of the problem. Still issues when adding a new graphicObject and when resizing
I am going to try to use the frame's glass pane as explained here: http://java.sun.com/docs/books/tutorial/uiswing/components/rootpane.html#glasspane
The goal is to intercept mouse events before they get to the slide objects, duplicate these events but use a zoom-adjusted position, and dispatch the new events to the appropriate components.
Here is a demo to illustrate (the important parts are the swing timer updating the slide and the MouseEventRedispatcher class):
I wish I could edit my last post, the BBCode got messed up :-(
I really am impressed with the turnout of your glass pane idea. Very nice job. I did also notice that it seems to take much more time to repaint. I will see if I can help speed that up.
Kyle
I forgot to mention that we also need to get the resize of the slide back to how it was. We don't want it getting all out of proportion when the component is resized.
Kyle
Some problems:
the menus don't work anymore, dispatching the events on the menu bar doesn't seem to work;
changing the zoom causes brief visual anomalies, possibly because of the scrollpane's viewport's layout manager.
To fix the first one, we could use the glass pane only on the slide instead of the whole frame.
To fix the second one, we could put the slide in a container with a layout manager that doesn't move/resize the child components (layered pane, card layout…).
When resizing the slide, we resize the container at the same time, and let the viewport layout manager do whatever it wants to the container when it is too small.
I'm afraid I don't understand exactly what or why you did what is in commit 62. In commit 61 there were no artifacts and I see no visible difference or advantage of the changes in commit 62. Perhaps you could change my mind?
Kyle
In revision 61 the scrollbars don't appear when the zoom makes the slide bigger than the viewport (I have a small screen).
In revision 62 I added a listener to the zoom slider to resize the slide according to the zoom.
But the viewport layout manager detects the change and also tries to resize the slide.
As a result, when the zoom is being changed, the slide is drawn twice with different dimensions, hence the visual defect.
I realized that the layered pane in Slide is the actual slide because it contains the objects handled by the user.
I suggest to rename Slide into SlideContainer.
Then, the layered pane should be made into a new class Slide, and the zooming code should be moved there.
Also, the only way I found to reduce the area covered by he glass pane is by using a layered pane (instead of the frame glassPane property) to host the glass pane and the components under it.
Possibilities:
SlideContainer (currently Slide in revision 64) could extend JLayeredPane instead of JPanel; there doesn't seem to be an easy way to operate this change with the NetBeans GUI builder, so SlideContainer would probably have to be created using the current Slide as a guide;
the future Slide (currently a simple layered pane in revision 64) is already a layered pane, so we could add the glass pane to it and change the way z-order is handled for graphic objects.