1 package ini
.trakem2
.display
.d3d
;
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
;
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
;
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
) {
65 public Image3DUniverse
getUniverse() {
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();
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();
83 final Canvas3D canvas
= this.univ
.getCanvas();
84 c
.anchor
= GridBagConstraints
.NORTHWEST
;
85 c
.fill
= GridBagConstraints
.BOTH
;
89 gb
.setConstraints(canvas
, c
);
92 // 1. Panel to edit color and assign random colors
93 c
.fill
= GridBagConstraints
.HORIZONTAL
;
98 final JPanel p1
= newPanelColors(this.univ
);
99 gb
.setConstraints(p1
, c
);
102 // 2. Panel to delete all whose name matches a regex
104 c
.insets
= new Insets(10, 0, 0, 0);
105 final JPanel p2
= newPanelRemoveContents(this.univ
);
106 gb
.setConstraints(p2
, c
);
109 // 3. Filterable selection list
112 c
.fill
= GridBagConstraints
.BOTH
;
113 final JPanel p3
= newPanelFilterableTable(this.univ
);
114 gb
.setConstraints(p3
, c
);
117 frame
.getContentPane().add(all
);
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}.
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
);
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
;
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
;
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);
180 final JTextField typer
= new IntegerField(255, 3);
183 slider
.addAdjustmentListener(new AdjustmentListener() {
185 public void adjustmentValueChanged(final AdjustmentEvent e
) {
186 final Content content
= univ
.getSelected();
187 if (null == content
) {
188 Utils
.log("Nothing selected!");
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];
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
]);
207 gb
.setConstraints(l
, c
);
212 c
.fill
= GridBagConstraints
.HORIZONTAL
;
213 gb
.setConstraints(slider
, c
);
218 c
.fill
= GridBagConstraints
.NONE
;
219 gb
.setConstraints(typer
, c
);
226 final JLabel aL
= new JLabel("Alpha:");
227 gb
.setConstraints(aL
, c
);
231 c
.fill
= GridBagConstraints
.HORIZONTAL
;
233 final JScrollBar alphaSlider
= new JScrollBar(JScrollBar
.HORIZONTAL
, 255, 1, 0, 256);
234 gb
.setConstraints(alphaSlider
, c
);
238 c
.fill
= GridBagConstraints
.NONE
;
240 final JTextField alphaTyper
= new IntegerField(255, 3);
241 gb
.setConstraints(alphaTyper
, c
);
244 alphaSlider
.addAdjustmentListener(new AdjustmentListener() {
246 public void adjustmentValueChanged(final AdjustmentEvent e
) {
247 final Content content
= univ
.getSelected();
248 if (null == content
) {
249 Utils
.log("Nothing selected!");
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
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() {
268 public void actionPerformed(final ActionEvent e
) {
269 randomizeColors(univ
);
272 gb
.setConstraints(r
, c
);
275 addTitledLineBorder(p
, "Colors");
277 univ
.addUniverseListener(new UniverseListener() {
280 public void universeClosed() {}
283 public void transformationUpdated(final View arg0
) {}
286 public void transformationStarted(final View arg0
) {}
289 public void transformationFinished(final View arg0
) {}
292 public void contentSelected(final Content arg0
) {
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];
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);
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);
321 public void contentRemoved(final Content arg0
) {}
324 public void contentChanged(final Content arg0
) {
325 if (arg0
== univ
.getSelected()) {
326 contentSelected(arg0
);
331 public void contentAdded(final Content arg0
) {}
334 public void canvasResized() {}
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
]));
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
;
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() {
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;
392 pattern
= Pattern
.compile(s
);
393 } catch (final PatternSyntaxException pse
) {
394 JOptionPane
.showMessageDialog(univ
.getWindow(), "Error parsing the regular expression:\n" + pse
.getMessage());
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
);
413 c
.fill
= GridBagConstraints
.BOTH
;
414 gb
.setConstraints(regex
, c
);
419 c
.fill
= GridBagConstraints
.NONE
;
420 gb
.setConstraints(remove
, c
);
423 addTitledLineBorder(p
, "Remove content");
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
) {
436 this.regexField
= regexField
;
437 this.contents
= new ArrayList
<Content
>(getOrderedContents(univ
));
441 public int getRowCount() {
442 return contents
.size();
446 public int getColumnCount() {
451 public String
getColumnName(final int columnIndex
) {
452 switch(columnIndex
) {
453 case 0: return "nth";
454 case 1: return "Name";
460 public boolean isCellEditable(final int rowIndex
, final int columnIndex
) {
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();
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
);
482 this.contents
= new ArrayList
<Content
>(m
.values());
483 fireTableDataChanged();
486 private void update(final ContentTable table
) {
487 Utils
.invokeLater(new Runnable() { @Override
489 final ArrayList
<Content
> cs
= new ArrayList
<Content
>();
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())) {
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
513 static private class ContentTable
extends JTable
{
514 private static final long serialVersionUID
= 1L;
515 ContentTable(final Image3DUniverse univ
) {
517 getTableHeader().addMouseListener(new MouseAdapter() {
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;
525 ((ContentTableModel
)getModel()).sortByName();
529 addMouseListener(new MouseAdapter() {
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()) {
541 final JPopupMenu jp
= new JPopupMenu();
542 JMenuItem item
= new JMenuItem("Select in 3D view");
544 item
.addActionListener(new ActionListener() {
546 public void actionPerformed(final ActionEvent e
) {
547 univ
.select(((ContentTableModel
)getModel()).contents
.get(row
));
550 item
= new JMenuItem("Select in TrakEM2");
552 item
.addActionListener(new ActionListener() {
554 public void actionPerformed(final ActionEvent e
) {
555 Utils
.log("Selecting in TrakEM2:");
556 final Hashtable
<LayerSet
,Display3D
> ht
= Display3D
.getMasterTable();
558 for (final Map
.Entry
<LayerSet
,Display3D
> entry
: ht
.entrySet()) {
559 if (entry
.getValue().getUniverse() == univ
) {
565 Utils
.log("Could not find an appropriate TrakEM2 project!");
568 Display front
= Display
.getFront();
569 if (front
.getLayerSet() != ls
) {
570 for (final Display display
: Display
.getDisplays()) {
571 if (display
.getLayerSet() == ls
) {
576 if (front
.getLayerSet() != ls
) {
577 Utils
.log("Could not find an open display for the appropriate TrakEM2 project!");
581 for (final Content c
: cs
) {
582 final String name
= c
.getName();
583 int start
= name
.lastIndexOf('#');
585 Utils
.log("..skipped " + name
);
588 final StringBuilder sb
= new StringBuilder(10);
591 while (start
< name
.length() && Character
.isDigit(ch
= name
.charAt(start
))) {
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
);
602 front
.getSelection().add((Displayable
)dbo
);
603 Utils
.log("Selected: #" + id
);
605 Utils
.log("..skipped " + name
);
611 item
= new JMenuItem("Remove from 3D view");
613 item
.addActionListener(new ActionListener() {
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());
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
) {
640 public void universeClosed() {}
643 public void transformationUpdated(final View arg0
) {}
646 public void transformationStarted(final View arg0
) {}
649 public void transformationFinished(final View arg0
) {}
652 public void contentSelected(final Content c
) {
653 Utils
.invokeLater(new Runnable() { @Override
655 final int i
= ((ContentTableModel
)table
.getModel()).contents
.indexOf(c
);
656 table
.getSelectionModel().setSelectionInterval(i
, i
);
661 public void contentRemoved(final Content arg0
) {
662 ((ContentTableModel
)table
.getModel()).update(table
);
666 public void contentChanged(final Content arg0
) {
667 ((ContentTableModel
)table
.getModel()).update(table
);
671 public void contentAdded(final Content arg0
) {
672 ((ContentTableModel
)table
.getModel()).update(table
);
675 public void canvasResized() {}
678 static private final class RegExFilter
{
679 final Pattern pattern
;
680 RegExFilter(String regex
) {
681 if (0 == regex
.length()) {
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();
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
);
710 univ
.addUniverseListener(new TableUniverseListener(table
));
712 regexField
.addActionListener(new ActionListener() {
714 public void actionPerformed(final ActionEvent e
) {
719 gb
.setConstraints(label
, c
);
724 gb
.setConstraints(regexField
, c
);
731 c
.fill
= GridBagConstraints
.BOTH
;
732 final JScrollPane jsp
= new JScrollPane(table
);
733 gb
.setConstraints(jsp
, c
);
736 addTitledLineBorder(p
, "Contents");