/** 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.utils; import ij.gui.GenericDialog; import ini.trakem2.ControlWindow; import ini.trakem2.Project; import ini.trakem2.display.AreaList; import ini.trakem2.display.AreaTree; import ini.trakem2.display.Ball; import ini.trakem2.display.Connector; import ini.trakem2.display.Coordinate; import ini.trakem2.display.DLabel; import ini.trakem2.display.Display; import ini.trakem2.display.Display3D; import ini.trakem2.display.Displayable; import ini.trakem2.display.Dissector; import ini.trakem2.display.Layer; import ini.trakem2.display.LayerSet; import ini.trakem2.display.Node; import ini.trakem2.display.Patch; import ini.trakem2.display.Pipe; import ini.trakem2.display.Polyline; import ini.trakem2.display.Profile; import ini.trakem2.display.Tag; import ini.trakem2.display.Tree; import ini.trakem2.display.Treeline; import ini.trakem2.display.ZDisplayable; import ini.trakem2.persistence.DBObject; import ini.trakem2.persistence.FSLoader; import ini.trakem2.tree.ProjectThing; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.regex.Pattern; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableModel; public class Search { private JFrame search_frame = null; private JTabbedPane search_tabs = null; private JTextField search_field = null; private JComboBox pulldown = null; private KeyListener kl = null; private JComboBox projects = null; private Map<Project, List<JPanel>> tabMap = Collections .synchronizedMap(new HashMap<Project, List<JPanel>>()); static private Search instance = null; private Class<?>[] types = null; static public final void showWindow() { if (null != instance) { instance.makeGUI(); } else { instance = new Search(); } } /** Creates the GUI for searching text in any TrakEM2 element. */ private Search() { types = new Class[] { DBObject.class, Displayable.class, DLabel.class, Patch.class, AreaList.class, Profile.class, Pipe.class, Ball.class, Layer.class, Dissector.class, Polyline.class, Treeline.class, AreaTree.class, Connector.class }; makeGUI(); } private void tryCloseTab(KeyEvent ke) { switch (ke.getKeyCode()) { case KeyEvent.VK_W: if (!ke.isControlDown()) return; int ntabs = search_tabs.getTabCount(); if (0 == ntabs) { instance.destroy(); return; } search_tabs.remove(search_tabs.getSelectedIndex()); return; default: return; } } private void makeGUI() { // create GUI if not there if (null == search_frame) { search_frame = ControlWindow .createJFrame("Search Regular Expressions"); search_frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { instance.destroy(); } }); search_tabs = new JTabbedPane(JTabbedPane.TOP); kl = new KeyAdapter() { public void keyPressed(KeyEvent ke) { tryCloseTab(ke); } }; search_tabs.addKeyListener(kl); search_field = new JTextField(14); search_field.addKeyListener(new VKEnterListener()); GridBagLayout gb = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); JPanel all = new JPanel(); all.setLayout(gb); all.setPreferredSize(new Dimension(600, 400)); JButton b = new JButton("Search"); b.addActionListener(new ButtonListener()); pulldown = new JComboBox(new String[] { "All", "All displayables", "Labels", "Images", "Area Lists", "Profiles", "Pipes", "Balls", "Layers", "Dissectors", "Polylines", "Treelines", "AreaTrees", "Connectors" }); List<Project> ps = Project.getProjects(); String[] sps = new String[ps.size()]; int k = 0; for (final Project p : ps) sps[k++] = p.getTitle(); this.projects = new JComboBox(sps); Display front = Display.getFront(); if (null != front) this.projects.setSelectedIndex(ps.indexOf(front.getProject())); c.gridx = 0; c.gridy = 0; c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1; c.insets = new Insets(4, 10, 5, 2); gb.setConstraints(search_field, c); all.add(search_field); c.gridx = 1; c.weightx = 0; c.insets = new Insets(4, 2, 5, 10); gb.setConstraints(b, c); all.add(b); c.gridx = 2; gb.setConstraints(pulldown, c); all.add(pulldown); c.gridx = 3; gb.setConstraints(projects, c); all.add(projects); c.gridx = 0; c.gridy = 1; c.gridwidth = 4; c.weightx = 1; c.weighty = 1; c.fill = GridBagConstraints.BOTH; c.insets = new Insets(0, 0, 0, 0); gb.setConstraints(search_tabs, c); all.add(search_tabs); search_frame.getContentPane().add(all); search_frame.pack(); javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { ij.gui.GUI.center(search_frame); search_frame.setVisible(true); } }); } else { search_frame.toFront(); } } synchronized private void destroy() { if (null != instance) { if (null != search_frame) search_frame.dispose(); search_frame = null; search_tabs = null; search_field = null; pulldown = null; types = null; kl = null; // deregister instance = null; tabMap.clear(); } } private class DisplayableTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; private Vector<DBObject> v_obs; private Vector<String> v_txt; private Vector<Coordinate<?>> v_co; DisplayableTableModel(Vector<DBObject> v_obs, Vector<String> v_txt, Vector<Coordinate<?>> v_co) { super(); this.v_obs = v_obs; this.v_txt = v_txt; this.v_co = v_co; } public String getColumnName(int col) { if (0 == col) return "Type"; else if (1 == col) return "Info"; else if (2 == col) return "Matched"; else return ""; } public int getRowCount() { return v_obs.size(); } public int getColumnCount() { return 3; } public Object getValueAt(int row, int col) { if (0 == col) return Project.getName(v_obs.get(row).getClass()); else if (1 == col) return v_obs.get(row).getShortTitle(); else if (2 == col) return v_txt.get(row); else return ""; } public DBObject getDBObjectAt(int row) { return (DBObject) v_obs.get(row); } /* * public Displayable getDisplayableAt(int row) { return * (Displayable)v_obs.get(row); } */ public Coordinate<?> getCoordinateAt(int row) { return v_co.get(row); } public boolean isCellEditable(int row, int col) { return false; } public void setValueAt(Object value, int row, int col) { // nothing // fireTableCellUpdated(row, col); } public boolean remove(Displayable displ) { int i = v_obs.indexOf(displ); if (-1 != i) { v_obs.remove(i); v_txt.remove(i); v_co.remove(i); return true; } return false; } public boolean contains(Object ob) { return v_obs.contains(ob); } } private void executeSearch() { final Project project = Project.getProjects().get( projects.getSelectedIndex()); if (null == project) { // Should not happen return; } Bureaucrat.createAndStart(new Worker.Task("Searching") { public void exec() { String pattern = search_field.getText(); if (null == pattern || 0 == pattern.length()) { return; } // fix pattern final String typed_pattern = pattern; final StringBuilder sb = new StringBuilder(); // I hate java if (!pattern.startsWith("^")) sb.append("^.*"); for (int i = 0; i < pattern.length(); i++) { sb.append(pattern.charAt(i)); } if (!pattern.endsWith("$")) sb.append(".*$"); pattern = sb.toString(); final Pattern pat = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); // Utils.log2("pattern after: " + pattern); final ArrayList<DBObject> al = new ArrayList<DBObject>(); // Utils.log("types[pulldown] = " + // types[pulldown.getSelectedIndex()]); find(project.getRootLayerSet(), al, types[pulldown.getSelectedIndex()]); // Utils.log2("found labels: " + al.size()); if (0 == al.size()) return; final Vector<DBObject> v_obs = new Vector<DBObject>(); final Vector<String> v_txt = new Vector<String>(); final Vector<Coordinate<?>> v_co = new Vector<Coordinate<?>>(); Coordinate<?> co = null; for (final DBObject dbo : al) { if (Thread.currentThread().isInterrupted()) { return; } boolean matched = false; // Search in its title Displayable d = null; if (dbo instanceof Displayable) { d = (Displayable) dbo; } String txt; String meaningful_title = null; if (null == d || Patch.class == d.getClass()) txt = dbo.getTitle(); else { txt = meaningful_title = dbo.getProject() .getMeaningfulTitle(d); } if (null == txt || 0 == txt.trim().length()) continue; matched = pat.matcher(txt).matches(); if (!matched && null != d) { // Search also in its annotation txt = d.getAnnotation(); if (null != txt) matched = pat.matcher(txt).matches(); } if (!matched) { // Search also in its toString() txt = dbo.toString(); matched = pat.matcher(txt).matches(); } if (!matched) { // Search also in its id txt = Long.toString(dbo.getId()); matched = pat.matcher(txt).matches(); if (matched) txt = "id: #" + txt; } if (!matched && null != d) { // Search also in its properties Map<String, String> props = d.getProperties(); if (null != props) { for (final Map.Entry<String, String> e : props .entrySet()) { if (pat.matcher(e.getKey()).matches() || pat.matcher(e.getValue()).matches()) { matched = true; txt = e.getKey() + " => " + e.getValue() + " [property]"; break; } } } if (!matched) { Map<Displayable, Map<String, String>> linked_props = ((Displayable) dbo) .getLinkedProperties(); if (null != linked_props) { for (final Map.Entry<Displayable, Map<String, String>> e : linked_props .entrySet()) { for (final Map.Entry<String, String> ee : e .getValue().entrySet()) { if (pat.matcher(ee.getKey()).matches() || pat.matcher(ee.getValue()) .matches()) { matched = true; txt = ee.getKey() + " => " + e.getValue() + " [linked property]"; break; } } } } } } if (!matched && dbo instanceof Tree<?>) { // search Node tags Node<?> root = ((Tree<?>) dbo).getRoot(); if (null == root) continue; for (final Node<?> nd : root.getSubtreeNodes()) { Set<Tag> tags = nd.getTags(); if (null == tags) continue; for (final Tag tag : tags) { if (pat.matcher(tag.toString()).matches()) { v_obs.add(dbo); v_txt.add(new StringBuilder(tag.toString()) .append(" (") .append(null == meaningful_title ? dbo .toString() : meaningful_title) .append(')').toString()); v_co.add(createCoordinate((Tree<?>) dbo, nd)); } } } continue; // all added if any } if (!matched) continue; // txt = txt.length() > 30 ? txt.substring(0, 27) + "..." : // txt; v_obs.add(dbo); v_txt.add(txt); v_co.add(co); } if (0 == v_obs.size()) { Utils.showMessage("Nothing found."); return; } final JPanel result = new JPanel(); GridBagLayout gb = new GridBagLayout(); result.setLayout(gb); GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.NORTHWEST; c.fill = GridBagConstraints.HORIZONTAL; c.insets = new Insets(5, 10, 5, 10); String xml = ""; if (project.getLoader() instanceof FSLoader) { String path = ((FSLoader) project.getLoader()) .getProjectXMLPath(); if (null != path) { xml = " [" + new File(path).getName() + "]"; } } JLabel projectTitle = new JLabel(project.getTitle() + xml); gb.setConstraints(projectTitle, c); result.add(projectTitle); c.insets = new Insets(0, 0, 0, 0); JPanel padding = new JPanel(); c.weightx = 1; gb.setConstraints(padding, c); result.add(padding); c.gridy = 1; c.gridwidth = 2; c.fill = GridBagConstraints.BOTH; c.weighty = 1; JScrollPane jsp = makeTable(new DisplayableTableModel(v_obs, v_txt, v_co), project); gb.setConstraints(jsp, c); result.add(jsp); search_tabs.addTab(typed_pattern, result); search_tabs.setSelectedComponent(result); synchronized (tabMap) { List<JPanel> cs = tabMap.get(project); if (null == cs) { cs = new ArrayList<JPanel>(); tabMap.put(project, cs); } cs.add(result); } } }, project); } private Coordinate<Node<?>> createCoordinate(Tree<?> tree, Node<?> nd) { double x = nd.getX(), y = nd.getY(); if (!tree.getAffineTransform().isIdentity()) { double[] dp = new double[] { x, y }; tree.getAffineTransform().transform(dp, 0, dp, 0, 1); x = dp[0]; y = dp[1]; } return new Coordinate<Node<?>>(x, y, nd.getLayer(), nd); } private Coordinate<Displayable> createCoordinate(final Displayable d) { Rectangle r = d.getBoundingBox(); Layer la = d instanceof ZDisplayable ? ((ZDisplayable) d) .getFirstLayer() : d.getLayer(); if (null == la) { Display display = Display.getFront(d.getProject()); if (null == display) la = d.getProject().getRootLayerSet().getLayer(0); else la = display.getLayer(); } return new Coordinate<Displayable>(r.x + r.width / 2, r.y + r.height / 2, la, d); } private class Results extends JTable { private final Project project; private Results(TableModel model, Project project) { super(model); this.project = project; } } private JScrollPane makeTable(TableModel model, Project project) { JTable table = new Results(model, project); // java 1.6.0 only!! //table.setAutoCreateRowSorter(true); table.addMouseListener(new DisplayableListListener()); table.addKeyListener(kl); JScrollPane jsp = new JScrollPane(table); jsp.setPreferredSize(new Dimension(500, 500)); return jsp; } /** Listen to the search button. */ private class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent ae) { executeSearch(); } } /** Listen to the search field. */ private class VKEnterListener extends KeyAdapter { public void keyPressed(KeyEvent ke) { if (ke.getKeyCode() == KeyEvent.VK_ENTER) { executeSearch(); return; } tryCloseTab(ke); } } /** Listen to double clicks in the table rows. */ private class DisplayableListListener extends MouseAdapter { public void mousePressed(MouseEvent me) { final Results table = (Results) me.getSource(); final int row = table.rowAtPoint(me.getPoint()); final DBObject ob = ((DisplayableTableModel) table.getModel()) .getDBObjectAt(row); final Coordinate<?> co = ((DisplayableTableModel) table.getModel()) .getCoordinateAt(row); if (2 == me.getClickCount()) { if (null != co) { Display.centerAt(co); } else if (ob instanceof Displayable) { // no zoom Display.centerAt(createCoordinate((Displayable) ob), true, me.isShiftDown()); } else if (ob instanceof Layer) { Display.showFront((Layer) ob); } else { Utils.log2("Search: Unhandable table selection: " + ob); } } else if (Utils.isPopupTrigger(me)) { final int numRowsSelected = table.getSelectedRowCount(); if (0 == numRowsSelected) return; JPopupMenu popup = new JPopupMenu(); final String show2D = "Show"; final String select = "Select in display"; final String show3D = "Show in 3D"; final String openNodeTable = "Show tabular view"; ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent ae) { final String command = ae.getActionCommand(); if (command.equals(show2D)) { if (ob instanceof Displayable) { Display.centerAt( createCoordinate((Displayable) ob), true, 0 != (ae.getModifiers() & ActionEvent.SHIFT_MASK)); } else if (ob instanceof Layer) { Display.showFront((Layer) ob); } } else if (command.equals(select)) { if (ob instanceof Layer) { Display.showFront((Layer) ob); } else if (ob instanceof Displayable) { // How many rows are selected? if (0 == numRowsSelected) { return; } else if (1 == numRowsSelected) { Displayable displ = (Displayable) ob; if (!displ.isVisible()) displ.setVisible(true); Display display = Display.getFront(displ .getProject()); if (null == display) return; boolean shift_down = 0 != (ae .getModifiers() & ActionEvent.SHIFT_MASK); display.select(displ, shift_down); } else { Collection<Displayable> ds = new ArrayList<Displayable>(); Display display = null; HashSet<Layer> layers = new HashSet<Layer>(); for (int row : table.getSelectedRows()) { final DBObject dob = ((DisplayableTableModel) table .getModel()).getDBObjectAt(row); if (null == dob || !(dob instanceof Displayable)) { Utils.log("Not selecting row " + row); } else { Displayable d = (Displayable) dob; ds.add(d); if (!(d instanceof ZDisplayable)) { layers.add(d.getLayer()); } if (null == display) display = Display.getFront(dob .getProject()); } } // Filter out Displayable not in the front // layer if (layers.size() > 0) { GenericDialog gd = new GenericDialog( "All layers?"); String[] s = new String[] { "Only from current layer", "From " + layers.size() + " layers" }; gd.addChoice("Select objects from:", s, s[0]); gd.showDialog(); if (gd.wasCanceled()) return; if (0 == gd.getNextChoiceIndex()) { Layer la = display.getLayer(); for (final Iterator<Displayable> it = ds .iterator(); it.hasNext();) { if (it.next().getLayer() != la) it.remove(); } } } display.getSelection().selectAll(ds); } } } else if (command.equals(show3D)) { if (ob instanceof Displayable) { ProjectThing pt = ob.getProject().findProjectThing(ob); if (null != pt) { Display3D.show(pt); } } } else if (command.equals(openNodeTable)) { if (ob instanceof Tree<?>) { ((Tree<?>)ob).createMultiTableView(); } } } }; JMenuItem item = new JMenuItem(show2D); popup.add(item); item.addActionListener(listener); item = new JMenuItem(select); popup.add(item); item.addActionListener(listener); popup.addSeparator(); item = new JMenuItem(show3D); popup.add(item); item.addActionListener(listener); if (ob instanceof Tree<?>) { item = new JMenuItem(openNodeTable); item.addActionListener(listener); popup.add(item); } popup.show(table, me.getX(), me.getY()); } } } /** * Recursive search into nested LayerSet instances, accumulating instances * of type into the list al. */ private void find(final LayerSet set, final ArrayList<DBObject> al, final Class<?> type) { if (type == DBObject.class) { al.add(set); } for (final ZDisplayable zd : set.getZDisplayables()) { if (DBObject.class == type || Displayable.class == type) { al.add(zd); } else if (zd.getClass() == type) { al.add(zd); } } for (final Layer layer : set.getLayers()) { if (DBObject.class == type || Layer.class == type) { al.add(layer); } for (final Displayable ob : layer.getDisplayables()) { if (DBObject.class == type || Displayable.class == type) { if (ob instanceof LayerSet) find((LayerSet) ob, al, type); else al.add(ob); } else if (ob.getClass() == type) { al.add(ob); } } } } static public void removeTabs(final Project p) { final Search search = instance; if (null == search) return; synchronized (search.tabMap) { List<JPanel> cs = search.tabMap.get(p); if (null == cs) return; for (final JPanel c : cs) { Utils.invokeLater(new Runnable() { public void run() { search.search_tabs.remove(c); } }); } search.tabMap.remove(p); } if (0 == search.search_tabs.getTabCount()) { search.destroy(); } } /** Remove from the tables if there. */ static public void remove(final Displayable displ) { final Search se = instance; try { if (null == se || null == displ) return; final List<JPanel> panels = se.tabMap.get(displ.getProject()); if (null == panels) return; for (final JPanel p : panels) { Results table = (Results) ((JScrollPane)p.getComponent(2)).getViewport().getComponent(0); DisplayableTableModel data = (DisplayableTableModel) table.getModel(); if (data.remove(displ)) { Utils.updateComponent(p); // in the event dispatch thread. } } } catch (Exception e) { IJError.print(e); se.destroy(); } } /** * Repaint (refresh the text in the cells) only if any of the selected tabs * in any of the search frames contains the given object in its rows. */ static public void repaint(final Object ob) { if (null == instance) return; SwingUtilities.invokeLater(new Runnable() { public void run() { final int selected = instance.search_tabs.getSelectedIndex(); if (-1 == selected) return; java.awt.Component c = instance.search_tabs .getComponentAt(selected); JTable table = (JTable) ((JScrollPane) c).getViewport() .getComponent(0); DisplayableTableModel data = (DisplayableTableModel) table .getModel(); if (data.contains(ob)) { Utils.updateComponent(instance.search_frame); } } }); } }