Skip to main content

strange effects with ZoomUI/MagnifierUI

9 replies [Last post]
s_barlow
Offline
Joined: 2009-02-09
Points: 0

Hi,
I tried to use the ZoomUI, but unfortunately encountered some serious problems concerning the (re)painting. At a first glance, everything seems to work properly and the components are zoomed dynamically according to the ZoomPort's size. But as soon as the component-structure changes (the order of the component can be changed by the user) the GUI gets totally messed up - the wrong regions of the GUI are repainted and the components (or rather parts of them) are painted at the wrong locations (After a repaint of the whole GUI the rearranged components are painted properly again, but... there is also a clock which, surprise, updates regularly - at the wrong location (in fact, the clock itself does not get updated any longer, but you can see the region where the clock tries to paint instead)). So one would think that the affine transform used by the UI got corrupted somehow - but that can't be true as there are some buttons which have a different cursor which appears at the right location. And even if the button is not visible due to the repainting-problems, the cursor still appears and the button can even be clicked - so the (inverse) transform obviously still works...
By the way: the situation even gets worse, if I use an additional glasspane above the JXLayer - that causes the strange effects to appear right from the beginning...
So... what the hell is going wrong there? Any idea?

To come to the MagnifierUI (no strange effects here, only a rather general question): I tried to implement the event-redispatching to redirect the (mouse)events to the components shown in the magnifier (as done in the ZoomUI) - but to my surprise I ended up in infinite loops as - in contradiction to the ZoomUI-example - the redirected events came back to my LayerUI again?! At the moment, I circumvent this problem by checking if the event had already been redispatched - but (as I couldn't find such a trick in the ZoomUI) is there an explanation for that?

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
pietblok
Offline
Joined: 2003-07-17
Points: 0

Hi,

First, you downloaded the code from this site? http://www.pbjar.org/blogs/jxlayer/version_2/

The other blogs are outdated.

For your first question, weird painting behavior after dynamically changing the TransformPort's view, I need to setup some test where one can dynamically make those changes. Could you provide an as simple as possible demonstration program? Meanwhile I'll try if I my self can produce such a test case. What you describe somehow reminds me of what happened in earlier versions (when still trying to find out the best painting strategy). Very weird indeed if you are referring to that sort of behavior.

The second question, redispatching events causing infinite loops (or, rather, infinite recursive calling). Have a look at the AbstractTransparentMouseUI. There you will find that:

a) Incoming original events are consumed (except when directed at the glass pane)

b) A flag (dispatchingMode) is set in order to detect that newly incoming events are in fact dispatched by the re dispatching mechanism and thus should be ignored (not consumed and not re dispatched) so that they can do their work as planned.

Thanks for the feed back.

Piet

s_barlow
Offline
Joined: 2009-02-09
Points: 0

A new version? Uhhh... actually, I still used the outdated versions... I think I will give your new version a chance first - I will let you know if that already solves my problems...
Regarding my second question, I really was too blind to get the trick with the dispatching mode - thank you for your explanation.

In the meantime... As far as I see, also the new version of your MagnifierUI uses neither a custom RepaintManager nor event-redispatching - is there a certain reason for (not) doing so, any nasty hidden obstacles? (Because, what I want to implement is quite a mixture of the MagnifierUI and the ZoomUI/TransformerUI, a kind of MagnifierUI with "full functionality" - so I hope you have not already tried that to no avail... ;-) )

pietblok
Offline
Joined: 2003-07-17
Points: 0

Hi,

No problem. Meanwhile I did some tests with dynamically changing the view of the TransformPort and found some very odd behavior (different from what you found). It seems that revalidation doesn't always work as expected. But that may be caused by me doing something wrong in the test case. So, even if everything works for you as expected in the new version, I'll have to do some more investigation, so thanks for the trigger.

Why didn't I use a custom repaint manager nor event re-dispatching for the MagnifierUI? Well, because there was no need to do so. The MagnifierUI accepts mouse events just as they are, because the event is always triggered in the center of the magnifying glass. At that position, there is no need to recompute the mouse point, or redispatch it to some other component. That fact made the MagnifierUI so straight forward to implement.

A mixture of the maginfier ui and the transform ui, that's a different peace of cake. I contemplated testing wrapping the two, out of curiosity just to see what would happen, but for my current peace of mind I postponed that idea for the time being.

Thanks,

Piet

pietblok
Offline
Joined: 2003-07-17
Points: 0

Hi,

Fixed a bug in the Transformer. It didn't detect changes in the view's size and therefore didn't update the AffineTransform.

Also, every time the transform is needed, a check for update is made.

One can now dynamically add or remove children of the TransformPort's view.

Piet

s_barlow
Offline
Joined: 2009-02-09
Points: 0

Hi,
today I managed to integrate your updated version into my code... and... well, unfortunately, the strange effects persist :-(
So I built a sample application (which needs the SwingX, JXLayer and your Transformer-library on the build-path) to demonstrate my problems...

It has some (collapsible) TaskPanes on the left containing some buttons and two clock-visualisations (i.e. changing every second) - notably, both the button-rollover-effects and the "clocks" perfectly work at any zoom-level as long as nothing changes in the panel on the right. There you find some "notes" whose state can be toggled by clicking or dragging on them. The notes can be either in "activated" or "deactivated"
state. Toggling a note changes its Z-order - activating brings them to the front, deactivating sends them to the back. As the Z-Order determines the order in which the components are painted, this also changes their position in the grid (just to give you an idea what is supposed to happen).
Maybe remarkable as well: Although the notes are registered to Mouse(Motion)Listeners the events only arrive there if you click on their border - which might be due to the fact that the events are redispatched to the deepest component?! so maybe it would be more consistent to redispatch all events to the layer's view (without the JXLayer the events are received on the entire notes)?

[code]
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.RoundRectangle2D;
import java.util.Calendar;
import java.util.HashMap;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.border.*;
import org.jdesktop.swingx.*;
import org.jdesktop.swingx.JXBusyLabel.Direction;
import org.jdesktop.swingx.border.DropShadowBorder;
import org.jdesktop.swingx.painter.BusyPainter;
import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.plaf.transform.TransformPort;
import org.pbjar.jxlayer.plaf.transform.TransformUI;

public class TestTransformerUI extends JXFrame {
private static final long serialVersionUID = 1L;

public TestTransformerUI() {
super("TestFrame", true);

JXPanel contentPane = new JXPanel(new BorderLayout());
contentPane.add(createTaskPanel(), BorderLayout.WEST);
contentPane.add(createContent(), BorderLayout.CENTER);
setContentPane(wrapLayer(contentPane));
pack();
}

private JXLayer wrapLayer(JXPanel content) {
HashMap hints =
new HashMap();
hints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
hints.put(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

TransformPort transformPort = new TransformPort(content);
transformPort.setRenderingHints(hints);
transformPort.setOpaque(true);
transformPort.setBackground(Color.ORANGE);
transformPort.setPreferredScale(2.0);

TransformUI zoomUI = new TransformUI();

return new JXLayer(transformPort, zoomUI);
}

private Component createTaskPanel() {
JXTaskPaneContainer container = new JXTaskPaneContainer();

/*
* some buttons
*/
JXTaskPane task1 = new JXTaskPane();
task1.setTitle("buttons");
task1.setSpecial(true);

JXButton button1 = new JXButton("first button");
button1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("action 1");
}
});
task1.add(button1);

JXButton button2 = new JXButton("2nd button");
button2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("action 2");
}
});
task1.add(button2);
container.add(task1);

/*
* a clock (or two)
*/
JXTaskPane task2 = new JXTaskPane();
task2.setTitle("the time");

final JXLabel timeLabel = new JXLabel();
timeLabel.setBorder(new LineBorder(Color.DARK_GRAY));
timeLabel.setHorizontalAlignment(JXLabel.CENTER);
task2.add(timeLabel);

final JXBusyLabel busyLabel = new JXBusyLabel();
busyLabel.setBorder(new LineBorder(Color.DARK_GRAY));
busyLabel.setHorizontalAlignment(JXLabel.CENTER);
busyLabel.setDelay(1000);

final BusyPainter busyPainter = new BusyPainter();
busyPainter.setDirection(Direction.RIGHT);
busyPainter.setBaseColor(setAlpha(Color.BLACK, 0.15f));
busyPainter.setHighlightColor(Color.BLACK);
busyPainter.setAntialiasing(true);
busyPainter.setPaintCentered(true);
busyPainter.setPoints(60);
busyPainter.setTrailLength(15);
busyPainter.setPointShape(
new RoundRectangle2D.Float(2f, 2f, 10f, 1f, 2f, 2f));

busyLabel.setBusyPainter(busyPainter);

Dimension size = busyPainter.getTrajectory().getBounds().getSize();
Rectangle shapeBounds = busyPainter.getPointShape().getBounds();
size.height += (shapeBounds.width * 2 + shapeBounds.x * 2 + 4);

busyLabel.setPreferredSize(size);
busyLabel.setBusy(false);

task2.add(busyLabel);
container.add(task2);

// update the clocks...
Timer clock = new Timer(1, null);
clock.setRepeats(true);
clock.setDelay(1000);
clock.addActionListener(new ActionListener() {
String timeFormat = "%02d:%02d:%02d";
public void actionPerformed(ActionEvent e) {
Calendar cal = Calendar.getInstance();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int min = cal.get(Calendar.MINUTE);
int sec = cal.get(Calendar.SECOND);
int length = busyPainter.getPoints();

int frame = (sec % length) - (length / 4);
if (frame < 0)
frame += length;

busyPainter.setFrame(frame);
busyLabel.repaint();

timeLabel.setText(String.format(timeFormat, hour, min, sec));
}
});
clock.start();
return container;
}

private Container createContent() {
JXPanel panel = new JXPanel(new GridLayout(3,3));
Component note;
Color[] color = {Color.YELLOW, Color.ORANGE, Color.RED, Color.GREEN,
Color.MAGENTA, Color.GRAY, Color.LIGHT_GRAY, Color.PINK};
for (int i=1; i <= color.length; i++) {
note = createNote("note number " + i, color[i - 1]);
panel.add(note);
}
return panel;
}

private Component createNote(String title, Color color) {
JXPanel note = new JXPanel(new BorderLayout());
note.setName(title);
note.setOpaque(false);
note.setBorder(createBorder(true));

JXLabel header = new JXLabel();
setUpBackground(header, color, false);
header.setOpaque(true);
header.setPreferredSize(new Dimension(0, 24));
note.add(header, BorderLayout.NORTH);

JXLabel label = new JXLabel("" + title + "");
label.setBorder(new EmptyBorder(5, 10, 10, 10));
label.setOpaque(true);
setUpBackground(label, color, true);
note.add(label, BorderLayout.CENTER);

ReLocator listener = new ReLocator(note);
note.addMouseMotionListener(listener);
note.addMouseListener(listener);

return note;
}

private static Border createBorder(boolean active) {
Color baseColor = Color.LIGHT_GRAY;
Color shadowColor = Color.DARK_GRAY;
Border inner = new LineBorder(active? shadowColor : baseColor);
Border outer = new DropShadowBorder(shadowColor, 20, .7f, 15,
false, false, active, active);
return new CompoundBorder(outer, inner);
}

private static void setUpBackground(JXLabel comp, Color bg, boolean translucent) {
if (translucent)
bg = setAlpha(bg, 0.25);
comp.setBackground(bg);
}

private static Color setAlpha(Color color, double alpha) {
int alphaInt = (int) (alpha * 255);
return new Color(color.getRed(), color.getGreen(), color.getBlue(), alphaInt);
}

public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TestTransformerUI().setVisible(true);
}
});
}

class ReLocator extends MouseAdapter {
private Point offset;
private JXPanel myComp;

public ReLocator(JXPanel myComp) {
this.myComp = myComp;
}

public void mouseClicked(MouseEvent e) {
JXPanel panel = (JXPanel) myComp.getParent();

float alpha = myComp.getAlpha();
if (alpha < 1f) {
alpha = 1f;
panel.setComponentZOrder(myComp, 0); //moveToFront
myComp.setBorder(createBorder(true));
} else {
alpha = 0.7f;
panel.setComponentZOrder(myComp, panel.getComponentCount() - 1); //moveToBack
myComp.setBorder(createBorder(false));
}

myComp.setAlpha(alpha);
panel.revalidate();
panel.repaint();
}

public void mouseDragged(MouseEvent e) {
Dimension size = myComp.getSize();
Dimension pSize = myComp.getParent().getSize();

Point location = e.getPoint();
location = SwingUtilities.convertPoint(myComp, location, myComp.getParent());
location.translate(offset.x, offset.y);

if (location.x < 0)
location.x = 0;
else if (location.x + size.width > pSize.width)
location.x = pSize.width - size.width;
if (location.y < 0)
location.y = 0;
else if (location.y + size.height > pSize.height)
location.y = pSize.height - size.height;

myComp.setLocation(location);
myComp.setAlpha(1.0f);
myComp.setBorder(createBorder(true));

JXPanel panel = (JXPanel) myComp.getParent();
panel.setComponentZOrder(myComp, 0); //moveToFront
}

public void mousePressed(MouseEvent e) {
System.out.println("pressed: " + myComp.getName());
offset = e.getPoint();
offset.x = -offset.x;
offset.y = -offset.y;
}

public void mouseReleased(MouseEvent e) {
System.out.println("released: " + myComp.getName());
offset = null;
}
}
}
[/code]

pietblok
Offline
Joined: 2003-07-17
Points: 0

Hi,

I found the probable cause of the failing painting: it is the
[code]org.jdesktop.swingx.RepaintManagerX[/code]
It replaces the
[code]org.pbjar.jxlayer.plaf.transform.TransformRepaintManager[/code]

The TransformRepaintManager wraps the currentRepaintManagager before replacing it, thus providing transparency with other RepaintManagers and only adding some extra functionality (well, that's what I'm attempting to do).

I know nothing about the RepaintManagerX. Does it provide for transparency in any way (preserving functionality of the current RepaintManager)?

I know of no way to refactor out my TransformRepaintManager and still have correct painting for child components.

I never worked with SwingX, so I can't tell what is best to do. Maybe you should ask the SwingX team.

Here the code change in your example that shows the problem. I create a debugPanel that wraps your content. On every paint it reveals the current RepaintManager.

[code]
private JXLayer wrapLayer(JXPanel content) {
HashMap hints = new HashMap();
hints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
hints.put(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

/*
* Wrap the content in a panel that displays the current RepaintManager.
*/
JXPanel debugPanel = new JXPanel(new BorderLayout()) {

private static final long serialVersionUID = 1L;

@Override
public void paint(Graphics g) {
super.paint(g);
System.out.println(RepaintManager.currentManager(this)
.getClass().getName());
}
};
debugPanel.add(content);

TransformPort transformPort = new TransformPort(debugPanel);
transformPort.setRenderingHints(hints);
transformPort.setOpaque(true);
transformPort.setBackground(Color.ORANGE);
transformPort.setPreferredScale(2.0);

TransformUI zoomUI = new TransformUI();

return new JXLayer(transformPort, zoomUI);
}
[/code]

Thanks,

Piet

pietblok
Offline
Joined: 2003-07-17
Points: 0

I posted a thread in the SwingLabs forum:

http://forums.java.net/jive/thread.jspa?threadID=57412

Piet

pietblok
Offline
Joined: 2003-07-17
Points: 0

I created a temporary fix for the SwingX problem. It is a replacement for two SwingX classes: [b]RepaintManagerX[/b] and [b]JXPanel[/b]

The fix can be downloaded from the blog.

Also I found that the mouse event redispatching mechanism needed a small refinement. Events are now dispatched to the target component only if that target component actually has the appropriate listener for that event, If not, the hierarchy is scanned upwards to find such a component.

The code of s_barlow revealed the problem, so thank you!

Added a new demo: a JTable with vertical headers.

Please see the blog here: http://www.pbjar.org/blogs/jxlayer/version_2/

Thanks,

Piet

s_barlow
Offline
Joined: 2009-02-09
Points: 0

Thank you very much for your support!
Now everything seems to be fine for me :)

However, I use a slightly different approach to make sure the right RepaintManager is used - I check the current manager's class within the LayerUI's paintLayer-method, i.e. every time the layer is painted and not only once during the startup. So it's a kind of striking-back-strategy... Works, too -- for the ones who do not want to patch the SwingX-library...