Skip to main content

Multiple layers on one component

10 replies [Last post]
lebesnec
Offline
Joined: 2007-09-13

hello,

I want to write two LayerUIs, each one with a specific behavior, and then applied them on the same component. This solution : http://www.pbjar.org/blogs/jxlayer/JXLayer_one.html does not work in my case because it remove the generic and the second layer is not applied on the component but on the first layer.

I have wrote this class which seem to work, but you have to extend CompoundLayerUI.AbstractLayerUI instead of AbstractLayerUI for the "sub-layer". Also super.paintLayer(...) should not be called on the "sub-layer" (since it's already done in CompoundLayerUI).

The question is : is there a better way of doing this ?

<br />
package ilist.ui.generic;</p>
<p>import java.awt.Graphics2D;<br />
import java.awt.event.FocusEvent;<br />
import java.awt.event.KeyEvent;<br />
import java.awt.event.MouseEvent;<br />
import java.awt.event.MouseWheelEvent;</p>
<p>import javax.swing.JComponent;</p>
<p>import org.jdesktop.jxlayer.JXLayer;<br />
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;</p>
<p>public class CompoundLayerUI extends AbstractLayerUI {</p>
<p>	private AbstractLayerUI layer1;<br />
	private AbstractLayerUI layer2;</p>
<p>	public CompoundLayerUI(AbstractLayerUI layer1, AbstractLayerUI layer2) {<br />
		super();<br />
		this.layer1 = layer1;<br />
		this.layer2 = layer2;<br />
	}</p>
<p>	@Override<br />
	protected void paintLayer(Graphics2D g, JXLayer l) {<br />
		super.paintLayer(g, l);<br />
		this.layer1.paintLayer(g, l);<br />
		this.layer2.paintLayer(g, l);<br />
	}</p>
<p>	@Override<br />
	protected void processFocusEvent(FocusEvent e, JXLayer l) {<br />
		super.processFocusEvent(e, l);<br />
		this.layer1.eventDispatched(e, l);<br />
		this.layer2.eventDispatched(e, l);<br />
	}</p>
<p>	@Override<br />
	protected void processKeyEvent(KeyEvent e, JXLayer l) {<br />
		super.processKeyEvent(e, l);<br />
		this.layer1.eventDispatched(e, l);<br />
		this.layer2.eventDispatched(e, l);<br />
	}</p>
<p>	@Override<br />
	protected void processMouseEvent(MouseEvent e, JXLayer l) {<br />
		super.processMouseEvent(e, l);<br />
		this.layer1.eventDispatched(e, l);<br />
		this.layer2.eventDispatched(e, l);<br />
	}</p>
<p>	@Override<br />
	protected void processMouseMotionEvent(MouseEvent e, JXLayer l) {<br />
		super.processMouseMotionEvent(e, l);<br />
		this.layer1.eventDispatched(e, l);<br />
		this.layer2.eventDispatched(e, l);<br />
	}</p>
<p>	@Override<br />
	protected void processMouseWheelEvent(MouseWheelEvent e, JXLayer l) {<br />
		super.processMouseWheelEvent(e, l);<br />
		this.layer1.eventDispatched(e, l);<br />
		this.layer2.eventDispatched(e, l);<br />
	}</p>
<p>	public static class AbstractLayerUI extends org.jdesktop.jxlayer.plaf.AbstractLayerUI {</p>
<p>		// paintLayer is now public :<br />
		@Override<br />
		public void paintLayer(Graphics2D g, JXLayer l) {<br />
			super.paintLayer(g, l);<br />
		}</p>
<p>	}</p>
<p>}<br />

How to use it :

<br />
JXLayer myLayer = new JXLayer(myList);<br />
CompoundLayerUI compoundLayerUI = new CompoundLayerUI(layerUI1, layerUI2);<br />
myLayer.setUI(compoundLayerUI);<br />
JScrollPane scrollPane = new JScrollPane(myLayer);<br />

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
lebesnec
Offline
Joined: 2007-09-13

yes, both methods seem correct !

pietblok
Offline
Joined: 2003-07-17

I see.

In that case, define the outer JXLayer as follows:

[code]JXLayer> outerLayer;[/code]

then you get at the index as follows:

[code]
public void paintLayer(Graphics2D g, JXLayer> layer) {
layer.getView().getView().getSelectedIndex(); // getSelectedIndex is a method of JList
....
}
[/code]

Piet

pietblok
Offline
Joined: 2003-07-17

Or something like this (to keep it more transparent)

[code]import java.awt.Graphics2D;

import javax.swing.JComponent;
import javax.swing.JList;

import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;

public class JListLayerUI extends AbstractLayerUI {

@Override
protected void paintLayer(Graphics2D g2, JXLayer layer) {
super.paintLayer(g2, layer);
JComponent view = layer.getView();
while (!(view instanceof JList)) {
if (view instanceof JXLayer) {
view = ((JXLayer) view).getView();
} else if (view == null) {
break;
} else {
throw new RuntimeException("Unexpected view encountered");
}
}
if (view != null) {
JList list = (JList) view;
int selectedIndex = list.getSelectedIndex();
// etc.
}
}

}
[/code]

Not tested

Piet

psychostud
Offline
Joined: 2005-11-02

I suppose the api should be extended to add multiple layers , maybe having a LayeredUIContainer whose layout supports adding multiple layers

pietblok
Offline
Joined: 2003-07-17

Hi,

That's what I initially thought also. For a second time I tried to wite a MultiLayerUI (probably it will have many flaws, but it serves as a demo).

I found the results very disapponting, but now I think I understand why this never will work.

Imagine two or three LayerUI's that all do some customized painting.

1) Paints curves following the mouse
2) Paints dots where the mouse is clicked
3) Some magnifying glass

When applying those LayerUI's upon wrapped JXLayer's, they work so to speak recursive:

A) LayerUI 3 will, by invoking super.paintLayer(..) have LayerUI 2 do its work
B) Then LayerUI 2 will, by invoking super.paintLayer(..) have LayerUI 1 do its work
C) Then LayerUI1 will, by invoking super.paintLayer(..) have the view component be painted
D) LayerUI 1 paints its curves
E) LayerUI 2 paints its dots
F) LayerUI 3 paints it magnifying glass

Now we see on the screen the views painting and the paintings of the three LayerUI's.

Now try this with a MultiLayerUI (use the code below for the implementation)

You will see that all LayerUI's will erase the painting of their peers, leaving the painting of thelast one only.

You may vary this experiment by commenting out the invocation of super.paintLayer(..) in the MultiLayerUI and/or in LayerUI 1, 2 and 3.

You will see that acting of those LayerUI's as peers, instead of a wrapped hierarchy, is the problematic part.

Well, this was just my experiment. I am very curious if anyone can setup some implementation of some multi LayerUI where the LayerUI's work in harmony together.

[code]
import java.awt.AWTEvent;
import java.awt.Graphics;
import java.util.LinkedHashSet;

import javax.swing.JComponent;

import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;
import org.jdesktop.jxlayer.plaf.LayerUI;
import org.jdesktop.jxlayer.plaf.item.LayerItemChangeEvent;
import org.jdesktop.jxlayer.plaf.item.LayerItemListener;

public class MultiLayerUI extends AbstractLayerUI {

private LayerItemListener layerItemListener = new LayerItemListener() {

@SuppressWarnings("unchecked")
@Override
public void layerItemChanged(LayerItemChangeEvent event) {
/*
* Getting the originating LayerUI. But what to do with it?
*/
@SuppressWarnings("unused")
LayerUI layerUI = (LayerUI) event.getSource();
/*
* When a child LayerUI is changed, then this LayerUI is changed as
* well.
*/
MultiLayerUI.this.fireLayerItemChanged();
}
};

private LinkedHashSet> uis = new LinkedHashSet>();

public void addLayerUI(LayerUI layerUI) {
layerUI.addLayerItemListener(layerItemListener);
uis.add(layerUI);
}

@Override
public void eventDispatched(AWTEvent e, JXLayer layer) {
for (LayerUI layerUI : this.getLayerUIs()) {
if (layerUI.isEnabled()) {
layerUI.eventDispatched(e, layer);
}
}
}

@SuppressWarnings("unchecked")
public LayerUI[] getLayerUIs() {
return (LayerUI[]) uis.toArray(new LayerUI[uis.size()]);
}

@Override
public void installUI(JComponent c) {
super.installUI(c);
for (LayerUI layerUI : this.getLayerUIs()) {
layerUI.installUI(c);
}
}

@Override
public void paint(Graphics g, JComponent component) {
super.paint(g, component);
for (LayerUI layerUI : this.getLayerUIs()) {
if (layerUI.isEnabled()) {
layerUI.paint(g.create(), component);
}
}
}

public void removeAllLayerUIs() {
for (LayerUI layerUI : getLayerUIs()) {
removeLayerUI(layerUI);
}
uis.clear();
}

public void removeLayerUI(LayerUI layerUI) {
layerUI.removeLayerItemListener(layerItemListener);
uis.remove(layerUI);
}

@Override
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
for (LayerUI layerUI : this.getLayerUIs()) {
layerUI.uninstallUI(c);
}
}

}
[/code]

Piet

alexfromsun
Offline
Joined: 2005-09-05

(Writing from Bangkok, at the end of vacation)

Hello Piet

I like your evaluation about MultiLayerUI
I also believe that it can never be implemented correctly

the ui ordering is another problem,
(adding one more ui on top of the others or in between of them)
it can also be solved by prioritizing the ui's
but it makes the result API not easy to use and maintain

I remember the experiment with MultiplePainter implementation from SwingX team
it made me decide to not follow this way
(don't tell Richard about that :-))

Thanks
alexp

lebesnec
Offline
Joined: 2007-09-13

hello Piet,

in fact the problem is not that the solution at http://www.pbjar.org/blogs/jxlayer/JXLayer_one.html does not work, it is that I can't use it ...

For example let say I want to use 2 layers on a JList. If I used this solution the second layers will be on a JXlayer instead of a JList, and I won't be able to do :

[code]
public void paintLayer(Graphics2D g, JXLayer layer) {
layer.getView().getSelectedIndex(); // getSelectedIndex is a method of JList
....
}
[/code]

[i]Just as a hint. It seems that you implement your own AbstractLayerUI, extending from the original AbstractLayerUI. Your naming is very confusing. Shouldn't you name your class differently? Something like PublicPaintAbstractLayerUI?
[/i]
I keep the same name because I wanted it to be as invisible as possible for the users of CompoundLayerUI, but may be this too confusing :)

lebesnec
Offline
Joined: 2007-09-13

another small change :

I have changed :
[code]
// paintLayer is now public :
@Override
public void paintLayer(Graphics2D g, JXLayer l) {
super.paintLayer(g, l);
}
[/code]

into :
[code]
// paintLayer is now public :
@Override
public void paintLayer(Graphics2D g, JXLayer l) {}
[/code]

So it will work even if in the sub-layers you forget to remove the call to super.paintLayer(...)

pietblok
Offline
Joined: 2003-07-17

Hi Lebesnec,

I don't understand exactly what you mean by:

[i]This solution : http://www.pbjar.org/blogs/jxlayer/JXLayer_one.html does not work in my case because it remove the generic and the second layer is not applied on the component but on the first layer.[/i]

Could you provide some code example that shows what goes wrong?

Your code looks somewhat familiar to me, because I also made some attempt to write a generic MultiLayerUI. I failed because for virtually all methods I had to decide to either do the processing in the MultiLayerUI or delegate to the child LayerUIs. For me, that was very problematic.

Just as a hint. It seems that you implement your own AbstractLayerUI, extending from the original AbstractLayerUI. Your naming is very confusing. Shouldn't you name your class differently? Something like PublicPaintAbstractLayerUI?

Piet

lebesnec
Offline
Joined: 2007-09-13

[b]Update[/b] : this work even better :

[code]
package ilist.ui.generic;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;

import javax.swing.JComponent;

import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;

public class CompoundLayerUI extends AbstractLayerUI {

private AbstractLayerUI layer1;
private AbstractLayerUI layer2;

public CompoundLayerUI(AbstractLayerUI layer1, AbstractLayerUI layer2) {
super();
this.layer1 = layer1;
this.layer1.setCompoundLayer(this);
this.layer2 = layer2;
this.layer2.setCompoundLayer(this);
}

@Override
protected void paintLayer(Graphics2D g, JXLayer l) {
super.paintLayer(g, l);
this.layer1.paintLayer(g, l);
this.layer2.paintLayer(g, l);
}

@Override
protected void processFocusEvent(FocusEvent e, JXLayer l) {
super.processFocusEvent(e, l);
this.layer1.eventDispatched(e, l);
this.layer2.eventDispatched(e, l);
}

@Override
protected void processKeyEvent(KeyEvent e, JXLayer l) {
super.processKeyEvent(e, l);
this.layer1.eventDispatched(e, l);
this.layer2.eventDispatched(e, l);
}

@Override
protected void processMouseEvent(MouseEvent e, JXLayer l) {
super.processMouseEvent(e, l);
this.layer1.eventDispatched(e, l);
this.layer2.eventDispatched(e, l);
}

@Override
protected void processMouseMotionEvent(MouseEvent e, JXLayer l) {
super.processMouseMotionEvent(e, l);
this.layer1.eventDispatched(e, l);
this.layer2.eventDispatched(e, l);
}

@Override
protected void processMouseWheelEvent(MouseWheelEvent e, JXLayer l) {
super.processMouseWheelEvent(e, l);
this.layer1.eventDispatched(e, l);
this.layer2.eventDispatched(e, l);
}

@Override
public Dimension getPreferredScrollableViewportSize(JXLayer l) {
return layer1.getPreferredScrollableViewportSize(l);
}

@Override
public int getScrollableBlockIncrement(JXLayer l, Rectangle r, int orientation, int direction) {
return layer1.getScrollableBlockIncrement(l, r, orientation, direction);
}

@Override
public boolean getScrollableTracksViewportHeight(JXLayer l) {
return layer1.getScrollableTracksViewportHeight(l);
}

@Override
public boolean getScrollableTracksViewportWidth(JXLayer l) {
return layer1.getScrollableTracksViewportWidth(l);
}

@Override
public int getScrollableUnitIncrement(JXLayer l, Rectangle r,int orientation, int direction) {
return layer1.getScrollableUnitIncrement(l, r, orientation, direction);
}

public static class AbstractLayerUI extends org.jdesktop.jxlayer.plaf.AbstractLayerUI {

private CompoundLayerUI compoundLayerUI;

// paintLayer is now public :
@Override
public void paintLayer(Graphics2D g, JXLayer l) {
super.paintLayer(g, l);
}

protected void setCompoundLayer(CompoundLayerUI compoundLayerUI) {
this.compoundLayerUI = compoundLayerUI;
}

@Override
protected void setDirty(boolean dirty) {
super.setDirty(dirty);
this.compoundLayerUI.setDirty(dirty);
}

}

}
[/code]