/**
TrakEM2 plugin for ImageJ(C).
Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
You may contact Albert Cardona at acardona at ini.phys.ethz.ch
Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
**/
package ini.trakem2.display;
import ini.trakem2.utils.Utils;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public final class DisplayablePanel extends JPanel implements MouseListener {
private static final long serialVersionUID = 1L;
static public final int HEIGHT = 52;
static private ImageIcon LOCKED = new ImageIcon(DisplayablePanel.class.getResource("/img/locked.png"));
static private ImageIcon UNLOCKED = new ImageIcon(DisplayablePanel.class.getResource("/img/unlocked.png"));
static private ImageIcon VISIBLE = new ImageIcon(DisplayablePanel.class.getResource("/img/visible.png"));
static private ImageIcon INVISIBLE = new ImageIcon(DisplayablePanel.class.getResource("/img/invisible.png"));
static private ImageIcon LINKED = new ImageIcon(DisplayablePanel.class.getResource("/img/linked.png"));
static private ImageIcon UNLINKED = new ImageIcon(DisplayablePanel.class.getResource("/img/unlinked.png"));
static private final Font SMALL = new Font("Courier", Font.ITALIC, 11);
static private final Color GRAYISH = new Color(50, 50, 50);
private JCheckBox c, c_locked, c_linked;
private JLabel title, title2, idlabel;
private JPanel titles;
private SnapshotPanel sp;
private Display display;
private Displayable d;
final private ML listener = new ML();
public DisplayablePanel(Display display, Displayable d) {
this.display = display;
this.d = d;
this.c = new JCheckBox();
this.c.setSelected(d.isVisible());
this.c.addMouseListener(listener);
this.c.setIcon(INVISIBLE);
this.c.setSelectedIcon(VISIBLE);
this.c.setBackground(Color.white);
Dimension maxdim = new Dimension(26, 14);
this.c.setPreferredSize(maxdim);
this.c.setMaximumSize(maxdim);
this.c_locked = new JCheckBox();
this.c_locked.setIcon(UNLOCKED);
this.c_locked.setSelectedIcon(LOCKED);
this.c_locked.setSelected(d.isLocked2());
this.c_locked.addMouseListener(listener);
this.c_locked.setBackground(Color.white);
Dimension maxdim10 = new Dimension(26, 10);
this.c_locked.setPreferredSize(maxdim10);
this.c_locked.setMaximumSize(maxdim10);
this.c_linked = new JCheckBox();
this.c_linked.setIcon(UNLINKED);
this.c_linked.setSelectedIcon(LINKED);
this.c_linked.setSelected(d.isLinked());
this.c_linked.addMouseListener(listener);
this.c_linked.setBackground(Color.white);
this.c_linked.setPreferredSize(maxdim10);
this.c_linked.setMaximumSize(maxdim10);
this.sp = new SnapshotPanel(display, d);
title = new JLabel();
title.addMouseListener(this);
title2 = new JLabel();
title2.addMouseListener(this);
idlabel = new JLabel("#" + d.getId());
idlabel.setFont(SMALL);
idlabel.setForeground(GRAYISH);
titles = new JPanel();
updateTitle();
GridBagLayout gb = new GridBagLayout();
GridBagConstraints co = new GridBagConstraints();
this.setLayout(gb);
// Column of checkboxes
co.anchor = GridBagConstraints.NORTHWEST;
co.fill = GridBagConstraints.NONE;
co.gridx = 0;
co.gridy = 0;
gb.setConstraints(c, co);
add(c);
//
co.gridy = 1;
co.anchor = GridBagConstraints.WEST;
co.fill = GridBagConstraints.VERTICAL;
co.weighty = 1;
gb.setConstraints(c_locked, co);
add(c_locked);
//
co.gridy = 2;
co.anchor = GridBagConstraints.SOUTHWEST;
co.weighty = 0;
gb.setConstraints(c_linked, co);
add(c_linked);
// Snapshot panel
co.anchor = GridBagConstraints.NORTHWEST;
co.fill = GridBagConstraints.NONE;
co.gridx = 1;
co.gridy = 0;
co.gridheight = 3;
gb.setConstraints(sp, co);
add(sp);
// Column of strings
co.gridheight = 1;
co.weightx = 1;
co.fill = GridBagConstraints.HORIZONTAL;
co.gridx = 2;
co.gridy = 0;
gb.setConstraints(title, co);
add(title);
//
co.gridy = 1;
co.anchor = GridBagConstraints.WEST;
co.fill = GridBagConstraints.BOTH;
co.weighty = 1;
gb.setConstraints(title2, co);
add(title2);
//
co.gridy = 2;
co.anchor = GridBagConstraints.SOUTHWEST;
co.fill = GridBagConstraints.HORIZONTAL;
co.weighty = 0;
gb.setConstraints(idlabel, co);
add(idlabel);
setMinimumSize(new Dimension(230, DisplayablePanel.HEIGHT));
setPreferredSize(new Dimension(248, DisplayablePanel.HEIGHT));
addMouseListener(this);
setBackground(Color.white);
setBorder(BorderFactory.createLineBorder(Color.black));
}
/** For instance-recycling purposes. */
public void set(final Displayable d) {
this.d = d;
c.setSelected(d.isVisible());
c_locked.setSelected(d.isLocked2());
c_linked.setSelected(d.isLinked());
updateTitle();
sp.set(d);
}
public void paint(final Graphics g) {
if (null == g) return;
if (display.getSelection().contains(d)) {
if (null != display.getActive() && display.getActive() == d) { // can be null when initializing ... because swing is designed with built-in async
setBackground(Color.cyan);
} else {
setBackground(Color.pink);
}
} else {
setBackground(Color.white);
}
super.paint(g);
}
@Override
public void setBackground(Color c) {
super.setBackground(c);
if (null != titles) {
titles.setBackground(c);
title.setBackground(c);
title2.setBackground(c);
}
}
private String makeUpdatedTitle() {
if (null == d) { Utils.log2("null d "); return ""; }
else if (null == d.getTitle()) { Utils.log2("null title for " + d); return ""; }
final Class<?> c = d.getClass();
if (c == Patch.class) {
return d.getTitle();
} else if (c == DLabel.class) {
return d.getTitle().replace('\n', ' ');
} else {
// gather name of the enclosing object in the project tree
return d.getProject().getMeaningfulTitle2(d);
}
}
static private int MAX_CHARS = 20;
public void updateTitle() {
idlabel.setText("#" + d.getId());
String t = makeUpdatedTitle();
if (t.length() <= MAX_CHARS) {
title.setText(t);
title2.setText("");
return;
}
// else split at MAX_CHARS
// First try to see if it can be cut nicely
int lastbracket = t.lastIndexOf('[');
int end = -1,
start = -1;
if (lastbracket -1 <= MAX_CHARS && -1 != lastbracket) { // there's a space in front of the [
end = lastbracket -1;
start = lastbracket;
} else {
end = start = MAX_CHARS;
}
title.setText(t.substring(0, end));
if (t.length() - start -1 > MAX_CHARS) {
title2.setText(t.substring(start, start + 7) + "..." + t.substring(t.length() -10));
} else {
title2.setText(t.substring(start));
}
title.setToolTipText(t);
title2.setToolTipText(t);
}
private class ML extends MouseAdapter {
public void mousePressed(final MouseEvent me) {
display.dispatcher.exec(new Runnable() {
public void run() {
JCheckBox source = (JCheckBox) me.getSource();
if (source == c) {
if (!source.isSelected()) {
d.setVisible(true);
} else {
// Prevent hiding when transforming
if (Display.isTransforming(d)) {
Utils.showStatus("Transforming! Can't change visibility.", false);
SwingUtilities.invokeLater(new Runnable() { public void run() {
c.setSelected(true);
}});
return;
}
d.setVisible(false);
}
} else if (source == c_locked) {
final String[] members = new String[]{"locked"};
if (!source.isSelected()) {
// Prevent locking while transforming
if (Display.isTransforming(d)) {
Utils.logAll("Transforming! Can't lock.");
SwingUtilities.invokeLater(new Runnable() { public void run() {
c_locked.setSelected(false);
}});
return;
}
d.getLayerSet().addDataEditStep(d, members);
d.setLocked(true);
d.getLayerSet().addDataEditStep(d, members);
} else {
d.getLayerSet().addDataEditStep(d, members);
d.setLocked(false);
d.getLayerSet().addDataEditStep(d, members);
}
// Update lock checkboxes of linked Displayables, except of this one
Collection<Displayable> lg = d.getLinkedGroup(null);
if (null != lg) {
lg.remove(d); // not this one!
Display.updateCheckboxes(lg, LOCK_STATE, d.isLocked2());
}
} else if (source == c_linked) {
// Prevent linking/unlinking while transforming
if (Display.isTransforming(d)) {
Utils.logAll("Transforming! Can't modify linking state.");
SwingUtilities.invokeLater(new Runnable() { public void run() {
c_linked.setSelected(d.isLinked());
}});
return;
}
final Set<Displayable> hs;
if (!source.isSelected()) {
final Rectangle box = d.getBoundingBox();
hs = new HashSet<Displayable>(d.getLayer().find(box, true)); // only those visible and overlapping
hs.addAll(d.getLayerSet().findZDisplayables(d.getLayer(), box, true));
if (hs.size() > 1) {
d.getLayerSet().addDataEditStep(hs, new String[]{"data"}); // "data" contains links, because links are dependent on bounding box of data
for (final Displayable other : hs) {
if (other == d) continue;
d.link(other);
}
d.getLayerSet().addDataEditStep(hs, new String[]{"data"}); // "data" contains links, because links are dependent on bounding box of data
} else {
// Nothing to link, restore icon
SwingUtilities.invokeLater(new Runnable() { public void run() {
c_linked.setSelected(false);
}});
}
} else {
hs = d.getLinkedGroup(null);
d.getLayerSet().addDataEditStep(hs, new String[]{"data"});
d.unlink();
d.getLayerSet().addDataEditStep(hs, new String[]{"data"});
}
// Update link checkboxes of linked Displayables, except of this one
if (null != hs) {
hs.remove(d); // not this one!
if (hs.size() > 0) {
Display.updateCheckboxes(hs, LINK_STATE);
Display.updateCheckboxes(hs, LOCK_STATE);
}
}
// Recompute list of links in Selection
Display.updateSelection(Display.getFront());
}
}
});
}
}
public void mousePressed(final MouseEvent me) {
if (display.isTransforming()) return;
display.select(d, me.isShiftDown());
if (me.isPopupTrigger() || (ij.IJ.isMacOSX() && me.isControlDown()) || MouseEvent.BUTTON2 == me.getButton() || 0 != (me.getModifiers() & Event.META_MASK)) {
display.getPopupMenu().show(DisplayablePanel.this, me.getX(), me.getY());
}
}
public void mouseReleased(MouseEvent me) {}
public void mouseEntered(MouseEvent me) {}
public void mouseExited (MouseEvent me) {}
public void mouseClicked(MouseEvent me) {}
public String toString() {
return "Displayable panel for " + d.toString();
}
static public final int LOCK_STATE = 1;
static public final int VISIBILITY_STATE = 2;
static public final int LINK_STATE = 4;
protected void updateCheckbox(final int cb, final boolean state) {
switch(cb) {
case LOCK_STATE:
c_locked.setSelected(state);
break;
case VISIBILITY_STATE:
c.setSelected(state);
d.getLayerSet().clearScreenshots();
break;
case LINK_STATE:
c_linked.setSelected(state);
break;
default:
Utils.log2("Ooops: don't know what to do with checkbox code " + cb);
break;
}
}
protected void updateCheckbox(final int cb) {
switch(cb) {
case LOCK_STATE:
c_locked.setSelected(d.isLocked2());
break;
case VISIBILITY_STATE:
c.setSelected(d.isVisible());
d.getLayerSet().clearScreenshots();
break;
case LINK_STATE:
c_linked.setSelected(d.isLinked());
break;
default:
Utils.log2("Ooops: don't know what to do with checkbox code " + cb);
break;
}
}
}