3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
23 package ini
.trakem2
.utils
;
25 import ij
.gui
.GenericDialog
;
26 import ini
.trakem2
.ControlWindow
;
27 import ini
.trakem2
.Project
;
28 import ini
.trakem2
.display
.AreaList
;
29 import ini
.trakem2
.display
.AreaTree
;
30 import ini
.trakem2
.display
.Ball
;
31 import ini
.trakem2
.display
.Connector
;
32 import ini
.trakem2
.display
.Coordinate
;
33 import ini
.trakem2
.display
.DLabel
;
34 import ini
.trakem2
.display
.Display
;
35 import ini
.trakem2
.display
.Display3D
;
36 import ini
.trakem2
.display
.Displayable
;
37 import ini
.trakem2
.display
.Dissector
;
38 import ini
.trakem2
.display
.Layer
;
39 import ini
.trakem2
.display
.LayerSet
;
40 import ini
.trakem2
.display
.Node
;
41 import ini
.trakem2
.display
.Patch
;
42 import ini
.trakem2
.display
.Pipe
;
43 import ini
.trakem2
.display
.Polyline
;
44 import ini
.trakem2
.display
.Profile
;
45 import ini
.trakem2
.display
.Tag
;
46 import ini
.trakem2
.display
.Tree
;
47 import ini
.trakem2
.display
.Treeline
;
48 import ini
.trakem2
.display
.ZDisplayable
;
49 import ini
.trakem2
.persistence
.DBObject
;
50 import ini
.trakem2
.persistence
.FSLoader
;
51 import ini
.trakem2
.tree
.ProjectThing
;
53 import java
.awt
.Dimension
;
54 import java
.awt
.GridBagConstraints
;
55 import java
.awt
.GridBagLayout
;
56 import java
.awt
.Insets
;
57 import java
.awt
.Rectangle
;
58 import java
.awt
.event
.ActionEvent
;
59 import java
.awt
.event
.ActionListener
;
60 import java
.awt
.event
.KeyAdapter
;
61 import java
.awt
.event
.KeyEvent
;
62 import java
.awt
.event
.KeyListener
;
63 import java
.awt
.event
.MouseAdapter
;
64 import java
.awt
.event
.MouseEvent
;
65 import java
.awt
.event
.WindowAdapter
;
66 import java
.awt
.event
.WindowEvent
;
68 import java
.util
.ArrayList
;
69 import java
.util
.Collection
;
70 import java
.util
.Collections
;
71 import java
.util
.HashMap
;
72 import java
.util
.HashSet
;
73 import java
.util
.Iterator
;
74 import java
.util
.List
;
77 import java
.util
.Vector
;
78 import java
.util
.regex
.Pattern
;
80 import javax
.swing
.JButton
;
81 import javax
.swing
.JComboBox
;
82 import javax
.swing
.JFrame
;
83 import javax
.swing
.JLabel
;
84 import javax
.swing
.JMenuItem
;
85 import javax
.swing
.JPanel
;
86 import javax
.swing
.JPopupMenu
;
87 import javax
.swing
.JScrollPane
;
88 import javax
.swing
.JTabbedPane
;
89 import javax
.swing
.JTable
;
90 import javax
.swing
.JTextField
;
91 import javax
.swing
.SwingUtilities
;
92 import javax
.swing
.table
.AbstractTableModel
;
93 import javax
.swing
.table
.TableModel
;
96 private JFrame search_frame
= null;
97 private JTabbedPane search_tabs
= null;
98 private JTextField search_field
= null;
99 private JComboBox pulldown
= null;
100 private KeyListener kl
= null;
101 private JComboBox projects
= null;
102 private Map
<Project
, List
<JPanel
>> tabMap
= Collections
103 .synchronizedMap(new HashMap
<Project
, List
<JPanel
>>());
105 static private Search instance
= null;
107 private Class
<?
>[] types
= null;
109 static public final void showWindow() {
110 if (null != instance
) {
113 instance
= new Search();
117 /** Creates the GUI for searching text in any TrakEM2 element. */
119 types
= new Class
[] { DBObject
.class, Displayable
.class, DLabel
.class,
120 Patch
.class, AreaList
.class, Profile
.class, Pipe
.class,
121 Ball
.class, Layer
.class, Dissector
.class, Polyline
.class,
122 Treeline
.class, AreaTree
.class, Connector
.class };
126 private void tryCloseTab(KeyEvent ke
) {
127 switch (ke
.getKeyCode()) {
129 if (!ke
.isControlDown())
131 int ntabs
= search_tabs
.getTabCount();
136 search_tabs
.remove(search_tabs
.getSelectedIndex());
143 private void makeGUI() {
144 // create GUI if not there
145 if (null == search_frame
) {
146 search_frame
= ControlWindow
147 .createJFrame("Search Regular Expressions");
148 search_frame
.addWindowListener(new WindowAdapter() {
149 public void windowClosing(WindowEvent we
) {
153 search_tabs
= new JTabbedPane(JTabbedPane
.TOP
);
154 kl
= new KeyAdapter() {
155 public void keyPressed(KeyEvent ke
) {
159 search_tabs
.addKeyListener(kl
);
160 search_field
= new JTextField(14);
161 search_field
.addKeyListener(new VKEnterListener());
163 GridBagLayout gb
= new GridBagLayout();
164 GridBagConstraints c
= new GridBagConstraints();
165 JPanel all
= new JPanel();
167 all
.setPreferredSize(new Dimension(600, 400));
169 JButton b
= new JButton("Search");
170 b
.addActionListener(new ButtonListener());
171 pulldown
= new JComboBox(new String
[] { "All", "All displayables",
172 "Labels", "Images", "Area Lists", "Profiles", "Pipes",
173 "Balls", "Layers", "Dissectors", "Polylines", "Treelines",
174 "AreaTrees", "Connectors" });
176 List
<Project
> ps
= Project
.getProjects();
177 String
[] sps
= new String
[ps
.size()];
179 for (final Project p
: ps
)
180 sps
[k
++] = p
.getTitle();
181 this.projects
= new JComboBox(sps
);
182 Display front
= Display
.getFront();
184 this.projects
.setSelectedIndex(ps
.indexOf(front
.getProject()));
188 c
.fill
= GridBagConstraints
.HORIZONTAL
;
190 c
.insets
= new Insets(4, 10, 5, 2);
191 gb
.setConstraints(search_field
, c
);
192 all
.add(search_field
);
196 c
.insets
= new Insets(4, 2, 5, 10);
197 gb
.setConstraints(b
, c
);
201 gb
.setConstraints(pulldown
, c
);
205 gb
.setConstraints(projects
, c
);
213 c
.fill
= GridBagConstraints
.BOTH
;
214 c
.insets
= new Insets(0, 0, 0, 0);
215 gb
.setConstraints(search_tabs
, c
);
216 all
.add(search_tabs
);
218 search_frame
.getContentPane().add(all
);
220 javax
.swing
.SwingUtilities
.invokeLater(new Runnable() {
222 ij
.gui
.GUI
.center(search_frame
);
223 search_frame
.setVisible(true);
227 search_frame
.toFront();
231 synchronized private void destroy() {
232 if (null != instance
) {
233 if (null != search_frame
)
234 search_frame
.dispose();
247 private class DisplayableTableModel
extends AbstractTableModel
{
248 private static final long serialVersionUID
= 1L;
249 private Vector
<DBObject
> v_obs
;
250 private Vector
<String
> v_txt
;
251 private Vector
<Coordinate
<?
>> v_co
;
253 DisplayableTableModel(Vector
<DBObject
> v_obs
, Vector
<String
> v_txt
,
254 Vector
<Coordinate
<?
>> v_co
) {
261 public String
getColumnName(int col
) {
272 public int getRowCount() {
276 public int getColumnCount() {
280 public Object
getValueAt(int row
, int col
) {
282 return Project
.getName(v_obs
.get(row
).getClass());
284 return v_obs
.get(row
).getShortTitle();
286 return v_txt
.get(row
);
291 public DBObject
getDBObjectAt(int row
) {
292 return (DBObject
) v_obs
.get(row
);
296 * public Displayable getDisplayableAt(int row) { return
297 * (Displayable)v_obs.get(row); }
299 public Coordinate
<?
> getCoordinateAt(int row
) {
300 return v_co
.get(row
);
303 public boolean isCellEditable(int row
, int col
) {
307 public void setValueAt(Object value
, int row
, int col
) {
309 // fireTableCellUpdated(row, col);
312 public boolean remove(Displayable displ
) {
313 int i
= v_obs
.indexOf(displ
);
323 public boolean contains(Object ob
) {
324 return v_obs
.contains(ob
);
328 private void executeSearch() {
329 final Project project
= Project
.getProjects().get(
330 projects
.getSelectedIndex());
331 if (null == project
) {
335 Bureaucrat
.createAndStart(new Worker
.Task("Searching") {
338 String pattern
= search_field
.getText();
339 if (null == pattern
|| 0 == pattern
.length()) {
343 final String typed_pattern
= pattern
;
344 final StringBuilder sb
= new StringBuilder(); // I hate java
345 if (!pattern
.startsWith("^"))
347 for (int i
= 0; i
< pattern
.length(); i
++) {
348 sb
.append(pattern
.charAt(i
));
350 if (!pattern
.endsWith("$"))
352 pattern
= sb
.toString();
353 final Pattern pat
= Pattern
.compile(pattern
,
354 Pattern
.CASE_INSENSITIVE
| Pattern
.MULTILINE
356 // Utils.log2("pattern after: " + pattern);
357 final ArrayList
<DBObject
> al
= new ArrayList
<DBObject
>();
358 // Utils.log("types[pulldown] = " +
359 // types[pulldown.getSelectedIndex()]);
360 find(project
.getRootLayerSet(), al
,
361 types
[pulldown
.getSelectedIndex()]);
362 // Utils.log2("found labels: " + al.size());
365 final Vector
<DBObject
> v_obs
= new Vector
<DBObject
>();
366 final Vector
<String
> v_txt
= new Vector
<String
>();
367 final Vector
<Coordinate
<?
>> v_co
= new Vector
<Coordinate
<?
>>();
368 Coordinate
<?
> co
= null;
369 for (final DBObject dbo
: al
) {
370 if (Thread
.currentThread().isInterrupted()) {
373 boolean matched
= false;
374 // Search in its title
375 Displayable d
= null;
376 if (dbo
instanceof Displayable
) {
377 d
= (Displayable
) dbo
;
380 String meaningful_title
= null;
381 if (null == d
|| Patch
.class == d
.getClass())
382 txt
= dbo
.getTitle();
384 txt
= meaningful_title
= dbo
.getProject()
385 .getMeaningfulTitle(d
);
388 if (null == txt
|| 0 == txt
.trim().length())
390 matched
= pat
.matcher(txt
).matches();
391 if (!matched
&& null != d
) {
392 // Search also in its annotation
393 txt
= d
.getAnnotation();
395 matched
= pat
.matcher(txt
).matches();
398 // Search also in its toString()
399 txt
= dbo
.toString();
400 matched
= pat
.matcher(txt
).matches();
403 // Search also in its id
404 txt
= Long
.toString(dbo
.getId());
405 matched
= pat
.matcher(txt
).matches();
409 if (!matched
&& null != d
) {
410 // Search also in its properties
411 Map
<String
, String
> props
= d
.getProperties();
413 for (final Map
.Entry
<String
, String
> e
: props
415 if (pat
.matcher(e
.getKey()).matches()
416 || pat
.matcher(e
.getValue()).matches()) {
418 txt
= e
.getKey() + " => " + e
.getValue()
425 Map
<Displayable
, Map
<String
, String
>> linked_props
= ((Displayable
) dbo
)
426 .getLinkedProperties();
427 if (null != linked_props
) {
428 for (final Map
.Entry
<Displayable
, Map
<String
, String
>> e
: linked_props
430 for (final Map
.Entry
<String
, String
> ee
: e
431 .getValue().entrySet()) {
432 if (pat
.matcher(ee
.getKey()).matches()
433 || pat
.matcher(ee
.getValue())
436 txt
= ee
.getKey() + " => "
438 + " [linked property]";
446 if (!matched
&& dbo
instanceof Tree
<?
>) {
448 Node
<?
> root
= ((Tree
<?
>) dbo
).getRoot();
451 for (final Node
<?
> nd
: root
.getSubtreeNodes()) {
452 Set
<Tag
> tags
= nd
.getTags();
455 for (final Tag tag
: tags
) {
456 if (pat
.matcher(tag
.toString()).matches()) {
458 v_txt
.add(new StringBuilder(tag
.toString())
460 .append(null == meaningful_title ? dbo
463 .append(')').toString());
464 v_co
.add(createCoordinate((Tree
<?
>) dbo
, nd
));
468 continue; // all added if any
474 // txt = txt.length() > 30 ? txt.substring(0, 27) + "..." :
481 if (0 == v_obs
.size()) {
482 Utils
.showMessage("Nothing found.");
485 final JPanel result
= new JPanel();
486 GridBagLayout gb
= new GridBagLayout();
487 result
.setLayout(gb
);
488 GridBagConstraints c
= new GridBagConstraints();
489 c
.anchor
= GridBagConstraints
.NORTHWEST
;
490 c
.fill
= GridBagConstraints
.HORIZONTAL
;
491 c
.insets
= new Insets(5, 10, 5, 10);
493 if (project
.getLoader() instanceof FSLoader
) {
494 String path
= ((FSLoader
) project
.getLoader())
495 .getProjectXMLPath();
497 xml
= " [" + new File(path
).getName() + "]";
500 JLabel projectTitle
= new JLabel(project
.getTitle() + xml
);
501 gb
.setConstraints(projectTitle
, c
);
502 result
.add(projectTitle
);
503 c
.insets
= new Insets(0, 0, 0, 0);
504 JPanel padding
= new JPanel();
506 gb
.setConstraints(padding
, c
);
510 c
.fill
= GridBagConstraints
.BOTH
;
512 JScrollPane jsp
= makeTable(new DisplayableTableModel(v_obs
,
513 v_txt
, v_co
), project
);
514 gb
.setConstraints(jsp
, c
);
516 search_tabs
.addTab(typed_pattern
, result
);
517 search_tabs
.setSelectedComponent(result
);
519 synchronized (tabMap
) {
520 List
<JPanel
> cs
= tabMap
.get(project
);
522 cs
= new ArrayList
<JPanel
>();
523 tabMap
.put(project
, cs
);
532 private Coordinate
<Node
<?
>> createCoordinate(Tree
<?
> tree
, Node
<?
> nd
) {
533 double x
= nd
.getX(), y
= nd
.getY();
534 if (!tree
.getAffineTransform().isIdentity()) {
535 double[] dp
= new double[] { x
, y
};
536 tree
.getAffineTransform().transform(dp
, 0, dp
, 0, 1);
540 return new Coordinate
<Node
<?
>>(x
, y
, nd
.getLayer(), nd
);
543 private Coordinate
<Displayable
> createCoordinate(final Displayable d
) {
544 Rectangle r
= d
.getBoundingBox();
545 Layer la
= d
instanceof ZDisplayable ?
((ZDisplayable
) d
)
546 .getFirstLayer() : d
.getLayer();
548 Display display
= Display
.getFront(d
.getProject());
550 la
= d
.getProject().getRootLayerSet().getLayer(0);
552 la
= display
.getLayer();
554 return new Coordinate
<Displayable
>(r
.x
+ r
.width
/ 2, r
.y
+ r
.height
558 private class Results
extends JTable
{
559 private final Project project
;
560 private Results(TableModel model
, Project project
) {
562 this.project
= project
;
566 private JScrollPane
makeTable(TableModel model
, Project project
) {
567 JTable table
= new Results(model
, project
);
568 // java 1.6.0 only!! //table.setAutoCreateRowSorter(true);
569 table
.addMouseListener(new DisplayableListListener());
570 table
.addKeyListener(kl
);
571 JScrollPane jsp
= new JScrollPane(table
);
572 jsp
.setPreferredSize(new Dimension(500, 500));
576 /** Listen to the search button. */
577 private class ButtonListener
implements ActionListener
{
578 public void actionPerformed(ActionEvent ae
) {
583 /** Listen to the search field. */
584 private class VKEnterListener
extends KeyAdapter
{
585 public void keyPressed(KeyEvent ke
) {
586 if (ke
.getKeyCode() == KeyEvent
.VK_ENTER
) {
594 /** Listen to double clicks in the table rows. */
595 private class DisplayableListListener
extends MouseAdapter
{
596 public void mousePressed(MouseEvent me
) {
597 final Results table
= (Results
) me
.getSource();
598 final int row
= table
.rowAtPoint(me
.getPoint());
599 final DBObject ob
= ((DisplayableTableModel
) table
.getModel())
601 final Coordinate
<?
> co
= ((DisplayableTableModel
) table
.getModel())
602 .getCoordinateAt(row
);
604 if (2 == me
.getClickCount()) {
606 Display
.centerAt(co
);
607 } else if (ob
instanceof Displayable
) {
609 Display
.centerAt(createCoordinate((Displayable
) ob
), true,
611 } else if (ob
instanceof Layer
) {
612 Display
.showFront((Layer
) ob
);
614 Utils
.log2("Search: Unhandable table selection: " + ob
);
616 } else if (Utils
.isPopupTrigger(me
)) {
617 final int numRowsSelected
= table
.getSelectedRowCount();
618 if (0 == numRowsSelected
)
620 JPopupMenu popup
= new JPopupMenu();
621 final String show2D
= "Show";
622 final String select
= "Select in display";
623 final String show3D
= "Show in 3D";
624 final String openNodeTable
= "Show tabular view";
625 ActionListener listener
= new ActionListener() {
626 public void actionPerformed(ActionEvent ae
) {
627 final String command
= ae
.getActionCommand();
628 if (command
.equals(show2D
)) {
629 if (ob
instanceof Displayable
) {
631 createCoordinate((Displayable
) ob
),
633 0 != (ae
.getModifiers() & ActionEvent
.SHIFT_MASK
));
634 } else if (ob
instanceof Layer
) {
635 Display
.showFront((Layer
) ob
);
637 } else if (command
.equals(select
)) {
638 if (ob
instanceof Layer
) {
639 Display
.showFront((Layer
) ob
);
640 } else if (ob
instanceof Displayable
) {
641 // How many rows are selected?
642 if (0 == numRowsSelected
) {
644 } else if (1 == numRowsSelected
) {
645 Displayable displ
= (Displayable
) ob
;
646 if (!displ
.isVisible())
647 displ
.setVisible(true);
648 Display display
= Display
.getFront(displ
652 boolean shift_down
= 0 != (ae
653 .getModifiers() & ActionEvent
.SHIFT_MASK
);
654 display
.select(displ
, shift_down
);
656 Collection
<Displayable
> ds
= new ArrayList
<Displayable
>();
657 Display display
= null;
658 HashSet
<Layer
> layers
= new HashSet
<Layer
>();
659 for (int row
: table
.getSelectedRows()) {
660 final DBObject dob
= ((DisplayableTableModel
) table
661 .getModel()).getDBObjectAt(row
);
663 || !(dob
instanceof Displayable
)) {
664 Utils
.log("Not selecting row "
667 Displayable d
= (Displayable
) dob
;
669 if (!(d
instanceof ZDisplayable
)) {
670 layers
.add(d
.getLayer());
673 display
= Display
.getFront(dob
677 // Filter out Displayable not in the front
679 if (layers
.size() > 0) {
680 GenericDialog gd
= new GenericDialog(
682 String
[] s
= new String
[] {
683 "Only from current layer",
684 "From " + layers
.size()
686 gd
.addChoice("Select objects from:", s
,
689 if (gd
.wasCanceled())
691 if (0 == gd
.getNextChoiceIndex()) {
692 Layer la
= display
.getLayer();
693 for (final Iterator
<Displayable
> it
= ds
694 .iterator(); it
.hasNext();) {
695 if (it
.next().getLayer() != la
)
700 display
.getSelection().selectAll(ds
);
703 } else if (command
.equals(show3D
)) {
704 if (ob
instanceof Displayable
) {
705 ProjectThing pt
= ob
.getProject().findProjectThing(ob
);
710 } else if (command
.equals(openNodeTable
)) {
711 if (ob
instanceof Tree
<?
>) {
712 ((Tree
<?
>)ob
).createMultiTableView();
717 JMenuItem item
= new JMenuItem(show2D
);
719 item
.addActionListener(listener
);
720 item
= new JMenuItem(select
);
722 item
.addActionListener(listener
);
723 popup
.addSeparator();
724 item
= new JMenuItem(show3D
);
726 item
.addActionListener(listener
);
727 if (ob
instanceof Tree
<?
>) {
728 item
= new JMenuItem(openNodeTable
);
729 item
.addActionListener(listener
);
732 popup
.show(table
, me
.getX(), me
.getY());
738 * Recursive search into nested LayerSet instances, accumulating instances
739 * of type into the list al.
741 private void find(final LayerSet set
, final ArrayList
<DBObject
> al
,
742 final Class
<?
> type
) {
743 if (type
== DBObject
.class) {
746 for (final ZDisplayable zd
: set
.getZDisplayables()) {
747 if (DBObject
.class == type
|| Displayable
.class == type
) {
749 } else if (zd
.getClass() == type
) {
753 for (final Layer layer
: set
.getLayers()) {
754 if (DBObject
.class == type
|| Layer
.class == type
) {
757 for (final Displayable ob
: layer
.getDisplayables()) {
758 if (DBObject
.class == type
|| Displayable
.class == type
) {
759 if (ob
instanceof LayerSet
)
760 find((LayerSet
) ob
, al
, type
);
763 } else if (ob
.getClass() == type
) {
770 static public void removeTabs(final Project p
) {
771 final Search search
= instance
;
774 synchronized (search
.tabMap
) {
775 List
<JPanel
> cs
= search
.tabMap
.get(p
);
778 for (final JPanel c
: cs
) {
779 Utils
.invokeLater(new Runnable() {
781 search
.search_tabs
.remove(c
);
785 search
.tabMap
.remove(p
);
787 if (0 == search
.search_tabs
.getTabCount()) {
792 /** Remove from the tables if there. */
793 static public void remove(final Displayable displ
) {
794 final Search se
= instance
;
796 if (null == se
|| null == displ
) return;
797 final List
<JPanel
> panels
= se
.tabMap
.get(displ
.getProject());
798 if (null == panels
) return;
799 for (final JPanel p
: panels
) {
800 Results table
= (Results
) ((JScrollPane
)p
.getComponent(2)).getViewport().getComponent(0);
801 DisplayableTableModel data
= (DisplayableTableModel
) table
.getModel();
802 if (data
.remove(displ
)) {
803 Utils
.updateComponent(p
); // in the event dispatch thread.
806 } catch (Exception e
) {
813 * Repaint (refresh the text in the cells) only if any of the selected tabs
814 * in any of the search frames contains the given object in its rows.
816 static public void repaint(final Object ob
) {
817 if (null == instance
)
819 SwingUtilities
.invokeLater(new Runnable() {
821 final int selected
= instance
.search_tabs
.getSelectedIndex();
824 java
.awt
.Component c
= instance
.search_tabs
825 .getComponentAt(selected
);
826 JTable table
= (JTable
) ((JScrollPane
) c
).getViewport()
828 DisplayableTableModel data
= (DisplayableTableModel
) table
830 if (data
.contains(ob
)) {
831 Utils
.updateComponent(instance
.search_frame
);