preparing release pom-trakem2-2.0.0, VectorString-2.0.0, TrakEM2_-1.0h
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / d3d / Display3DGUI.java
blob26630cbfcdb4e9b8dc45baa9e1596aa384dad9c4
1 package ini.trakem2.display.d3d;
3 import java.awt.Color;
4 import java.awt.Component;
5 import java.awt.Dimension;
6 import java.awt.GridBagConstraints;
7 import java.awt.GridBagLayout;
8 import java.awt.Insets;
9 import java.awt.event.ActionEvent;
10 import java.awt.event.ActionListener;
11 import java.awt.event.AdjustmentEvent;
12 import java.awt.event.AdjustmentListener;
13 import java.awt.event.KeyAdapter;
14 import java.awt.event.KeyEvent;
15 import java.awt.event.MouseAdapter;
16 import java.awt.event.MouseEvent;
17 import java.util.ArrayList;
18 import java.util.Enumeration;
19 import java.util.Hashtable;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.TreeMap;
23 import java.util.regex.Pattern;
24 import java.util.regex.PatternSyntaxException;
26 import javax.swing.BorderFactory;
27 import javax.swing.JButton;
28 import javax.swing.JLabel;
29 import javax.swing.JMenuItem;
30 import javax.swing.JOptionPane;
31 import javax.swing.JPanel;
32 import javax.swing.JPopupMenu;
33 import javax.swing.JScrollBar;
34 import javax.swing.JScrollPane;
35 import javax.swing.JTable;
36 import javax.swing.JTextField;
37 import javax.swing.table.AbstractTableModel;
38 import javax.swing.table.TableColumn;
40 import org.scijava.java3d.BranchGroup;
41 import org.scijava.java3d.Canvas3D;
42 import org.scijava.java3d.View;
43 import org.scijava.vecmath.Color3f;
45 import ij3d.Content;
46 import ij3d.Image3DUniverse;
47 import ij3d.ImageWindow3D;
48 import ij3d.UniverseListener;
49 import ini.trakem2.display.Display;
50 import ini.trakem2.display.Display3D;
51 import ini.trakem2.display.Displayable;
52 import ini.trakem2.display.LayerSet;
53 import ini.trakem2.persistence.DBObject;
54 import ini.trakem2.utils.IntegerField;
55 import ini.trakem2.utils.Utils;
57 public class Display3DGUI {
59 private final Image3DUniverse univ;
61 public Display3DGUI(final Image3DUniverse univ) {
62 this.univ = univ;
65 public Image3DUniverse getUniverse() {
66 return univ;
69 public ImageWindow3D init() {
70 // Extract the Canvas3D from the ImageWindow3D
71 final ImageWindow3D frame = new ImageWindow3D("TrakEM2 3D Display", this.univ);
72 frame.getContentPane().removeAll();
74 // New layout
75 final JPanel all = new JPanel();
76 all.setBackground(Color.white);
77 all.setPreferredSize(new Dimension(768, 512));
78 final GridBagConstraints c = new GridBagConstraints();
79 final GridBagLayout gb = new GridBagLayout();
80 all.setLayout(gb);
82 // Add Canvas3D
83 final Canvas3D canvas = this.univ.getCanvas();
84 c.anchor = GridBagConstraints.NORTHWEST;
85 c.fill = GridBagConstraints.BOTH;
86 c.weightx = 1;
87 c.weighty = 1;
88 c.gridheight = 4;
89 gb.setConstraints(canvas, c);
90 all.add(canvas);
92 // 1. Panel to edit color and assign random colors
93 c.fill = GridBagConstraints.HORIZONTAL;
94 c.weightx = 0;
95 c.weighty = 0;
96 c.gridheight = 1;
97 c.gridx = 1;
98 final JPanel p1 = newPanelColors(this.univ);
99 gb.setConstraints(p1, c);
100 all.add(p1);
102 // 2. Panel to delete all whose name matches a regex
103 c.gridy = 1;
104 c.insets = new Insets(10, 0, 0, 0);
105 final JPanel p2 = newPanelRemoveContents(this.univ);
106 gb.setConstraints(p2, c);
107 all.add(p2);
109 // 3. Filterable selection list
110 c.gridy = 2;
111 c.weighty = 1;
112 c.fill = GridBagConstraints.BOTH;
113 final JPanel p3 = newPanelFilterableTable(this.univ);
114 gb.setConstraints(p3, c);
115 all.add(p3);
117 frame.getContentPane().add(all);
118 return frame;
121 /** The {@link Image3DUniverse#getContents()} returns an unordered list, because the
122 * list are the values of a {@link Map}; this method circumvents that by getting
123 * the list from the enumeration of children elements in the scene of the {@param univ},
124 * which is a {@link BranchGroup}.
126 * @param univ
127 * @return
129 static private final List<Content> getOrderedContents(final Image3DUniverse univ) {
130 final ArrayList<Content> cs = new ArrayList<Content>();
131 final Enumeration<?> seq = univ.getScene().getAllChildren();
132 while (seq.hasMoreElements()) {
133 final Object o = seq.nextElement();
134 if (o instanceof Content) cs.add((Content)o);
136 return cs;
139 static private final void addTitledLineBorder(final JPanel p, final String title) {
140 p.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black, 1), title));
143 static private final class SliderTyperLink extends KeyAdapter {
144 private final Image3DUniverse univ;
145 private final JScrollBar slider;
146 private final JTextField typer;
147 SliderTyperLink(final Image3DUniverse univ, final JScrollBar slider, final JTextField typer) {
148 this.slider = slider;
149 this.typer = typer;
150 this.univ = univ;
152 @Override
153 public void keyPressed(final KeyEvent ke) {
154 final String txt = typer.getText();
155 if (txt.length() > 0) {
156 final int val = Integer.parseInt(txt);
157 slider.setValue(val);
158 final Content content = univ.getSelected();
159 if (null != content) {
160 slider.setValue(val); // will also set the color
166 static private final JPanel newPanelColors(final Image3DUniverse univ) {
167 final JPanel p = new JPanel();
168 p.setBackground(Color.white);
169 final GridBagLayout gb = new GridBagLayout();
170 final GridBagConstraints c = new GridBagConstraints();
171 c.anchor = GridBagConstraints.NORTHWEST;
172 c.fill = GridBagConstraints.HORIZONTAL;
173 p.setLayout(gb);
174 final String[] labels = new String[]{"Red", "Green", "Blue"};
175 final JScrollBar[] sliders = new JScrollBar[3];
176 final JTextField[] typers = new JTextField[3];
177 for (int i=0; i<3; ++i) {
178 final JScrollBar slider = new JScrollBar(JScrollBar.HORIZONTAL, 255, 1, 0, 256);
179 sliders[i] = slider;
180 final JTextField typer = new IntegerField(255, 3);
181 typers[i] = typer;
182 final int k = i;
183 slider.addAdjustmentListener(new AdjustmentListener() {
184 @Override
185 public void adjustmentValueChanged(final AdjustmentEvent e) {
186 final Content content = univ.getSelected();
187 if (null == content) {
188 Utils.log("Nothing selected!");
189 return;
191 Color3f color = content.getColor();
192 if (null == color) color = new Color3f(1, 1, 0); // default to yellow
193 final float[] co = new float[3];
194 color.get(co);
195 co[k] = e.getValue() / 255.0f;
196 content.setColor(new Color3f(co));
197 typer.setText(Integer.toString(e.getValue()));
200 typer.addKeyListener(new SliderTyperLink(univ, slider, typer));
202 final JLabel l = new JLabel(labels[i]);
204 // Layout
205 c.gridx = 0;
206 c.gridy = i;
207 gb.setConstraints(l, c);
208 p.add(l);
210 c.gridx = 1;
211 c.weightx = 1;
212 c.fill = GridBagConstraints.HORIZONTAL;
213 gb.setConstraints(slider, c);
214 p.add(slider);
216 c.gridx = 2;
217 c.weightx = 0;
218 c.fill = GridBagConstraints.NONE;
219 gb.setConstraints(typer, c);
220 p.add(typer);
223 // Alpha slider
224 c.gridx = 0;
225 c.gridy += 1;
226 final JLabel aL = new JLabel("Alpha:");
227 gb.setConstraints(aL, c);
228 p.add(aL);
230 c.gridx = 1;
231 c.fill = GridBagConstraints.HORIZONTAL;
232 c.weightx = 1;
233 final JScrollBar alphaSlider = new JScrollBar(JScrollBar.HORIZONTAL, 255, 1, 0, 256);
234 gb.setConstraints(alphaSlider, c);
235 p.add(alphaSlider);
237 c.gridx = 2;
238 c.fill = GridBagConstraints.NONE;
239 c.weightx = 0;
240 final JTextField alphaTyper = new IntegerField(255, 3);
241 gb.setConstraints(alphaTyper, c);
242 p.add(alphaTyper);
244 alphaSlider.addAdjustmentListener(new AdjustmentListener() {
245 @Override
246 public void adjustmentValueChanged(final AdjustmentEvent e) {
247 final Content content = univ.getSelected();
248 if (null == content) {
249 Utils.log("Nothing selected!");
250 return;
252 final float alpha = e.getValue() / 255.0f;
253 content.setTransparency(1 - alpha);
254 alphaTyper.setText(Integer.toString(e.getValue()));
257 alphaTyper.addKeyListener(new SliderTyperLink(univ, alphaSlider, alphaTyper));
259 // Button to colorize randomly
260 c.gridx = 0;
261 c.gridy += 1;
262 c.gridwidth = 3;
263 c.weightx = 1;
264 c.insets = new Insets(15, 4, 4, 4);
265 final JButton r = new JButton("Assign random colors to all");
266 r.addActionListener(new ActionListener() {
267 @Override
268 public void actionPerformed(final ActionEvent e) {
269 randomizeColors(univ);
272 gb.setConstraints(r, c);
273 p.add(r);
275 addTitledLineBorder(p, "Colors");
277 univ.addUniverseListener(new UniverseListener() {
279 @Override
280 public void universeClosed() {}
282 @Override
283 public void transformationUpdated(final View arg0) {}
285 @Override
286 public void transformationStarted(final View arg0) {}
288 @Override
289 public void transformationFinished(final View arg0) {}
291 @Override
292 public void contentSelected(final Content arg0) {
293 if (null == arg0) {
294 return;
296 Color3f color = arg0.getColor();
297 if (null == color) color = new Color3f(1, 1, 0); // default to yellow
298 final float[] co = new float[3];
299 color.get(co);
300 for (int i=0; i<3; ++i) {
301 // Disallow the slider from firing an event when its value is adjusted
302 sliders[i].setValueIsAdjusting(true);
303 final int val = (int)(co[i] * 255);
304 typers[i].setText(Integer.toString(val));
305 sliders[i].setValue(val);
307 // After all are set, re-enable, which triggers events (the color will be set three times...)
308 for (int i=0; i<3; ++i) {
309 sliders[i].setValueIsAdjusting(false);
312 // Alpha slider:
313 alphaSlider.setValueIsAdjusting(true);
314 final int alpha = (int)((1 - arg0.getTransparency()) * 255);
315 alphaTyper.setText(Integer.toString(alpha));
316 alphaSlider.setValue(alpha);
317 alphaSlider.setValueIsAdjusting(false);
320 @Override
321 public void contentRemoved(final Content arg0) {}
323 @Override
324 public void contentChanged(final Content arg0) {
325 if (arg0 == univ.getSelected()) {
326 contentSelected(arg0);
330 @Override
331 public void contentAdded(final Content arg0) {}
333 @Override
334 public void canvasResized() {}
337 return p;
340 static private final float[][] colors = new float[][]{
341 new float[]{255, 255, 0}, // yellow
342 new float[]{255, 0, 0}, // red
343 new float[]{255, 0, 255}, // magenta
344 new float[]{0, 255, 0}, // blue
345 new float[]{0, 255, 255}, // cyan
346 new float[]{0, 255, 0}, // green
347 new float[]{255, 255, 255},// white
348 new float[]{255, 128, 0}, // orange
349 new float[]{255, 0, 128},
350 new float[]{128, 255, 0},
351 new float[]{128, 0, 255},
352 new float[]{0, 255, 128},
353 new float[]{0, 128, 255},
354 new float[]{128, 128, 128},// grey
357 static public final void randomizeColors(final Image3DUniverse univ) {
358 final ArrayList<Content> cs = new ArrayList<Content>(getOrderedContents(univ));
359 for (int i=0; i<cs.size(); ++i) {
360 if (i < colors.length) {
361 cs.get(i).setColor(new Color3f(colors[i]));
362 } else {
363 cs.get(i).setColor(new Color3f((float)Math.random(), (float)Math.random(), (float)Math.random()));
366 // Update the color bars if something is selected:
367 final Content content = univ.getSelected();
368 if (null != content) univ.fireContentChanged(content);
371 static private final JPanel newPanelRemoveContents(final Image3DUniverse univ) {
372 final JPanel p = new JPanel();
373 p.setBackground(Color.white);
374 final GridBagLayout gb = new GridBagLayout();
375 final GridBagConstraints c = new GridBagConstraints();
376 c.anchor = GridBagConstraints.SOUTHWEST;
377 c.fill = GridBagConstraints.HORIZONTAL;
378 p.setLayout(gb);
380 final JLabel label = new JLabel("RegEx:");
381 final JTextField regex = new JTextField();
382 final JButton remove = new JButton("X");
383 final ActionListener a = new ActionListener() {
384 @Override
385 public void actionPerformed(final ActionEvent e) {
386 String s = regex.getText();
387 if (0 == s.length()) return;
388 if (!s.startsWith("^")) s = "^.*" + s;
389 if (!s.endsWith("$")) s = s + ".*$";
390 Pattern pattern = null;
391 try {
392 pattern = Pattern.compile(s);
393 } catch (final PatternSyntaxException pse) {
394 JOptionPane.showMessageDialog(univ.getWindow(), "Error parsing the regular expression:\n" + pse.getMessage());
395 return;
397 for (final Content c : new ArrayList<Content>(getOrderedContents(univ))) {
398 if (pattern.matcher(c.getName()).matches()) {
399 univ.removeContent(c.getName());
400 Utils.log("Removed " + c.getName());
405 remove.addActionListener(a);
406 regex.addActionListener(a);
408 gb.setConstraints(label, c);
409 p.add(label);
411 c.gridx = 1;
412 c.weightx = 1;
413 c.fill = GridBagConstraints.BOTH;
414 gb.setConstraints(regex, c);
415 p.add(regex);
417 c.gridx = 2;
418 c.weightx = 0;
419 c.fill = GridBagConstraints.NONE;
420 gb.setConstraints(remove, c);
421 p.add(remove);
423 addTitledLineBorder(p, "Remove content");
425 return p;
428 static private final class ContentTableModel extends AbstractTableModel
430 private List<Content> contents;
431 private final Image3DUniverse univ;
432 private final JTextField regexField;
434 public ContentTableModel(final Image3DUniverse univ, final JTextField regexField) {
435 this.univ = univ;
436 this.regexField = regexField;
437 this.contents = new ArrayList<Content>(getOrderedContents(univ));
440 @Override
441 public int getRowCount() {
442 return contents.size();
445 @Override
446 public int getColumnCount() {
447 return 2;
450 @Override
451 public String getColumnName(final int columnIndex) {
452 switch(columnIndex) {
453 case 0: return "nth";
454 case 1: return "Name";
456 return null;
459 @Override
460 public boolean isCellEditable(final int rowIndex, final int columnIndex) {
461 return false;
464 @Override
465 public Object getValueAt(final int rowIndex, final int columnIndex) {
466 switch (columnIndex) {
467 case 0: return rowIndex + 1;
468 case 1: return contents.get(rowIndex).getName();
470 return null;
473 @Override
474 public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) {}
476 private void sortByName() {
477 final TreeMap<String, Content> m = new TreeMap<String, Content>();
478 for (final Content c : contents) {
479 m.put(c.getName(), c);
481 // Swap
482 this.contents = new ArrayList<Content>(m.values());
483 fireTableDataChanged();
486 private void update(final ContentTable table) {
487 Utils.invokeLater(new Runnable() { @Override
488 public void run() {
489 final ArrayList<Content> cs = new ArrayList<Content>();
490 try {
491 final RegExFilter f = new RegExFilter(regexField.getText());
492 for (final Object ob : getOrderedContents(univ)) {
493 final Content c = (Content)ob;
494 if (f.accept(c.getName())) {
495 cs.add(c);
498 } catch (final PatternSyntaxException pse) {
499 JOptionPane.showMessageDialog(univ.getWindow(), "Error parsing the regular expression:\n" + pse.getMessage());
500 cs.addAll(getOrderedContents(univ));
502 ContentTableModel.this.contents = cs;
503 fireTableDataChanged();
504 // Adjust cell width:
505 final TableColumn tc = table.getColumnModel().getColumn(0);
506 final Component label = table.getDefaultRenderer(getColumnClass(0))
507 .getTableCellRendererComponent(table, cs.size()-1, false, false, cs.size()-1, 0);
508 tc.setMaxWidth(label.getBounds().width + 10); // 10 pixels of padding
509 }});
513 static private class ContentTable extends JTable {
514 private static final long serialVersionUID = 1L;
515 ContentTable(final Image3DUniverse univ) {
516 super();
517 getTableHeader().addMouseListener(new MouseAdapter() {
518 @Override
519 public void mouseClicked(final MouseEvent me) {
520 if (2 != me.getClickCount()) return;
521 final int viewColumn = getColumnModel().getColumnIndexAtX(me.getX());
522 final int column = convertColumnIndexToModel(viewColumn);
523 if (-1 == column) return;
524 if (1 == column) {
525 ((ContentTableModel)getModel()).sortByName();
529 addMouseListener(new MouseAdapter() {
530 @Override
531 public void mousePressed(final MouseEvent me) {
532 final int row = ContentTable.this.rowAtPoint(me.getPoint());
533 if (2 == me.getClickCount()) {
534 univ.select(((ContentTableModel)getModel()).contents.get(row));
535 } else if (Utils.isPopupTrigger(me)) {
536 final List<Content> data = ((ContentTableModel)getModel()).contents;
537 final ArrayList<Content> cs = new ArrayList<Content>();
538 for (final int i : getSelectedRows()) {
539 cs.add(data.get(i));
541 final JPopupMenu jp = new JPopupMenu();
542 JMenuItem item = new JMenuItem("Select in 3D view");
543 jp.add(item);
544 item.addActionListener(new ActionListener() {
545 @Override
546 public void actionPerformed(final ActionEvent e) {
547 univ.select(((ContentTableModel)getModel()).contents.get(row));
550 item = new JMenuItem("Select in TrakEM2");
551 jp.add(item);
552 item.addActionListener(new ActionListener() {
553 @Override
554 public void actionPerformed(final ActionEvent e) {
555 Utils.log("Selecting in TrakEM2:");
556 final Hashtable<LayerSet,Display3D> ht = Display3D.getMasterTable();
557 LayerSet ls = null;
558 for (final Map.Entry<LayerSet,Display3D> entry : ht.entrySet()) {
559 if (entry.getValue().getUniverse() == univ) {
560 ls = entry.getKey();
561 break;
564 if (null == ls) {
565 Utils.log("Could not find an appropriate TrakEM2 project!");
566 return;
568 Display front = Display.getFront();
569 if (front.getLayerSet() != ls) {
570 for (final Display display : Display.getDisplays()) {
571 if (display.getLayerSet() == ls) {
572 front = display;
573 break;
576 if (front.getLayerSet() != ls) {
577 Utils.log("Could not find an open display for the appropriate TrakEM2 project!");
578 return;
581 for (final Content c : cs) {
582 final String name = c.getName();
583 int start = name.lastIndexOf('#');
584 if (-1 == start) {
585 Utils.log("..skipped " + name);
586 continue;
588 final StringBuilder sb = new StringBuilder(10);
589 start += 1;
590 char ch;
591 while (start < name.length() && Character.isDigit(ch = name.charAt(start))) {
592 sb.append(ch);
593 start += 1;
595 if (sb.length() > 0) {
596 final long id = Long.parseLong(sb.toString());
597 final DBObject dbo = ls.findById(id);
598 if (null == dbo || !(dbo instanceof Displayable)) {
599 Utils.log("Could not find an object with id #" + id);
600 continue;
602 front.getSelection().add((Displayable)dbo);
603 Utils.log("Selected: #" + id);
604 } else {
605 Utils.log("..skipped " + name);
606 continue;
611 item = new JMenuItem("Remove from 3D view");
612 jp.add(item);
613 item.addActionListener(new ActionListener() {
614 @Override
615 public void actionPerformed(final ActionEvent e) {
616 for (final Content c : cs) {
617 univ.removeContent(c.getName());
622 jp.show(ContentTable.this, me.getX(), me.getY());
627 @Override
628 public String getToolTipText(final MouseEvent me) {
629 return ((ContentTableModel)getModel()).contents
630 .get(ContentTable.this.rowAtPoint(me.getPoint())).getName();
634 static private final class TableUniverseListener implements UniverseListener {
635 private final ContentTable table;
636 TableUniverseListener(final ContentTable table) {
637 this.table = table;
639 @Override
640 public void universeClosed() {}
642 @Override
643 public void transformationUpdated(final View arg0) {}
645 @Override
646 public void transformationStarted(final View arg0) {}
648 @Override
649 public void transformationFinished(final View arg0) {}
651 @Override
652 public void contentSelected(final Content c) {
653 Utils.invokeLater(new Runnable() { @Override
654 public void run() {
655 final int i = ((ContentTableModel)table.getModel()).contents.indexOf(c);
656 table.getSelectionModel().setSelectionInterval(i, i);
657 }});
660 @Override
661 public void contentRemoved(final Content arg0) {
662 ((ContentTableModel)table.getModel()).update(table);
665 @Override
666 public void contentChanged(final Content arg0) {
667 ((ContentTableModel)table.getModel()).update(table);
670 @Override
671 public void contentAdded(final Content arg0) {
672 ((ContentTableModel)table.getModel()).update(table);
674 @Override
675 public void canvasResized() {}
678 static private final class RegExFilter {
679 final Pattern pattern;
680 RegExFilter(String regex) {
681 if (0 == regex.length()) {
682 this.pattern = null;
683 return;
685 if (!regex.startsWith("^")) regex = "^.*" + regex;
686 if (!regex.endsWith("$")) regex = regex + ".*$";
687 this.pattern = Pattern.compile(regex);
689 final boolean accept(final String s) {
690 if (null == pattern) return true;
691 return pattern.matcher(s).matches();
695 static private final JPanel newPanelFilterableTable(final Image3DUniverse univ) {
696 final JPanel p = new JPanel();
697 p.setBackground(Color.white);
698 final GridBagConstraints c = new GridBagConstraints();
699 final GridBagLayout gb = new GridBagLayout();
700 p.setLayout(gb);
702 c.anchor = GridBagConstraints.NORTHWEST;
703 c.fill = GridBagConstraints.HORIZONTAL;
705 final JLabel label = new JLabel("RegEx: ");
706 final JTextField regexField = new JTextField();
707 final ContentTable table = new ContentTable(univ);
708 final ContentTableModel ctm = new ContentTableModel(univ, regexField);
709 table.setModel(ctm);
710 univ.addUniverseListener(new TableUniverseListener(table));
712 regexField.addActionListener(new ActionListener() {
713 @Override
714 public void actionPerformed(final ActionEvent e) {
715 ctm.update(table);
719 gb.setConstraints(label, c);
720 p.add(label);
722 c.gridx = 1;
723 c.weightx = 1;
724 gb.setConstraints(regexField, c);
725 p.add(regexField);
727 c.gridx = 0;
728 c.gridy = 1;
729 c.gridwidth = 2;
730 c.weighty = 1;
731 c.fill = GridBagConstraints.BOTH;
732 final JScrollPane jsp = new JScrollPane(table);
733 gb.setConstraints(jsp, c);
734 p.add(jsp);
736 addTitledLineBorder(p, "Contents");
737 return p;