Updated copyright dates.
[trakem2.git] / ini / trakem2 / utils / Search.java
blobf2d74070ad717a1a3e537b97ae590dbdb76093fe
1 /**
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.
21 **/
23 package ini.trakem2.utils;
25 import ini.trakem2.Project;
26 import ini.trakem2.persistence.DBObject;
27 import ini.trakem2.display.*;
28 import ini.trakem2.ControlWindow;
29 import javax.swing.*;
30 import javax.swing.table.*;
31 import java.awt.event.*;
32 import java.awt.Dimension;
33 import java.util.ArrayList;
34 import java.util.Hashtable;
35 import java.util.Iterator;
36 import java.util.Vector;
37 import java.util.regex.*;
39 public class Search {
40 private JFrame search_frame = null;
41 private JTabbedPane search_tabs = null;
42 private JTextField search_field = null;
43 private JComboBox pulldown = null;
44 private KeyListener kl = null;
46 static private Search instance = null;
48 private Class[] types = null;
51 /** Creates the GUI for searching text in any TrakEM2 element. */
52 public Search() {
53 if (null != instance) {
54 instance.makeGUI();
55 } else {
56 instance = this;
57 types = new Class[]{DBObject.class, Displayable.class, DLabel.class, Patch.class, AreaList.class, Profile.class, Pipe.class, Ball.class, Layer.class, Dissector.class};
58 makeGUI();
62 private void tryCloseTab(KeyEvent ke) {
63 switch (ke.getKeyCode()) {
64 case KeyEvent.VK_W:
65 if (!ke.isControlDown()) return;
66 int ntabs = search_tabs.getTabCount();
67 if (0 == ntabs) {
68 instance.destroy();
69 return;
71 search_tabs.remove(search_tabs.getSelectedIndex());
72 return;
73 default:
74 return;
78 private void makeGUI() {
79 // create GUI if not there
80 if (null == search_frame) {
81 search_frame = ControlWindow.createJFrame("Search");
82 search_frame.addWindowListener(new WindowAdapter() {
83 public void windowClosing(WindowEvent we) {
84 instance.destroy();
86 });
87 search_tabs = new JTabbedPane(JTabbedPane.TOP);
88 kl = new KeyAdapter() {
89 public void keyPressed(KeyEvent ke) {
90 tryCloseTab(ke);
93 search_tabs.addKeyListener(kl);
94 search_field = new JTextField(14);
95 search_field.addKeyListener(new VKEnterListener());
96 JButton b = new JButton("Search");
97 b.addActionListener(new ButtonListener());
98 pulldown = new JComboBox(new String[]{"All", "All displayables", "Labels", "Images", "Area Lists", "Profiles", "Pipes", "Balls", "Layers", "Dissectors"});
99 JPanel top = new JPanel();
100 top.add(search_field);
101 top.add(b);
102 top.add(pulldown);
103 top.setMinimumSize(new Dimension(400, 30));
104 top.setMaximumSize(new Dimension(10000, 30));
105 top.setPreferredSize(new Dimension(400, 30));
106 JPanel all = new JPanel();
107 all.setPreferredSize(new Dimension(400, 400));
108 BoxLayout bl = new BoxLayout(all, BoxLayout.Y_AXIS);
109 all.setLayout(bl);
110 all.add(top);
111 all.add(search_tabs);
112 search_frame.getContentPane().add(all);
113 search_frame.pack();
114 search_frame.setVisible(true);
115 } else {
116 search_frame.toFront();
120 synchronized private void destroy() {
121 if (null != instance) {
122 if (null != search_frame) search_frame.dispose();
123 search_frame = null;
124 search_tabs = null;
125 search_field = null;
126 pulldown = null;
127 types = null;
128 kl = null;
129 // deregister
130 instance = null;
134 private class DisplayableTableModel extends AbstractTableModel {
135 private Vector v_obs;
136 private Vector v_txt;
137 DisplayableTableModel(Vector v_obs, Vector v_txt) {
138 super();
139 this.v_obs = v_obs;
140 this.v_txt = v_txt;
142 public String getColumnName(int col) {
143 if (0 == col) return "Type";
144 else if (1 == col) return "Info";
145 else if (2 == col) return "Matched";
146 else return "";
148 public int getRowCount() { return v_obs.size(); }
149 public int getColumnCount() { return 3; }
150 public Object getValueAt(int row, int col) {
151 if (0 == col) return Project.getName(v_obs.get(row).getClass());
152 else if (1 == col) return ((DBObject)v_obs.get(row)).getShortTitle();
153 else if (2 == col) return v_txt.get(row).toString();
154 else return "";
156 public DBObject getDBObjectAt(int row) {
157 return (DBObject)v_obs.get(row);
159 public Displayable getDisplayableAt(int row) {
160 return (Displayable)v_obs.get(row);
162 public boolean isCellEditable(int row, int col) {
163 return false;
165 public void setValueAt(Object value, int row, int col) {
166 // nothing
167 //fireTableCellUpdated(row, col);
169 public boolean remove(Displayable displ) {
170 int i = v_obs.indexOf(displ);
171 if (-1 != i) {
172 v_obs.remove(i);
173 v_txt.remove(i);
174 return true;
176 return false;
178 public boolean contains(Object ob) {
179 return v_obs.contains(ob);
183 private void executeSearch() {
184 String pattern = search_field.getText();
185 if (null == pattern || 0 == pattern.length()) {
186 return;
188 // fix pattern
189 final String typed_pattern = pattern;
190 final StringBuffer sb = new StringBuffer(); // I hate java
191 if (!pattern.startsWith("^")) sb.append("^.*");
192 for (int i=0; i<pattern.length(); i++) {
193 sb.append(pattern.charAt(i));
195 if (!pattern.endsWith("$")) sb.append(".*$");
196 pattern = sb.toString();
197 final Pattern pat = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
198 //Utils.log2("pattern after: " + pattern);
199 final ArrayList al = new ArrayList();
200 //Utils.log("types[pulldown] = " + types[pulldown.getSelectedIndex()]);
201 find(ControlWindow.getActive().getRootLayerSet(), al, types[pulldown.getSelectedIndex()]);
202 //Utils.log2("found labels: " + al.size());
203 if (0 == al.size()) return;
204 final Vector v_obs = new Vector();
205 final Vector v_txt = new Vector();
206 for (Iterator it = al.iterator(); it.hasNext(); ) {
207 final DBObject dbo = (DBObject)it.next();
208 boolean matched = false;
209 // Search in its title
210 String txt = dbo instanceof Displayable ?
211 dbo.getProject().getMeaningfulTitle((Displayable)dbo)
212 : dbo.getTitle();
213 matched = pat.matcher(txt).matches();
214 long id = dbo.getId();
215 if (87 == id) Utils.log2("Searching title: " + txt + (matched ? "********** TRUE" : ""));
216 if (!matched) {
217 // Search also in its toString()
218 txt = dbo.toString();
219 matched = pat.matcher(txt).matches();
220 if (87 == id) Utils.log2("Searching toString: " + txt + (matched ? "********** TRUE" : ""));
222 if (!matched) {
223 // Search also in its id
224 txt = Long.toString(dbo.getId());
225 matched = pat.matcher(txt).matches();
226 if (matched) txt = "id: #" + txt;
227 if (87 == id) Utils.log2("Searching ID: " + txt + (matched ? "********** TRUE" : ""));
229 if (!matched) continue;
231 //txt = txt.length() > 30 ? txt.substring(0, 27) + "..." : txt;
232 v_obs.add(dbo);
233 v_txt.add(txt);
236 if (0 == v_obs.size()) {
237 Utils.showMessage("Nothing found.");
238 return;
240 final JScrollPane jsp = makeTable(new DisplayableTableModel(v_obs, v_txt));
241 search_tabs.addTab(typed_pattern, jsp);
242 search_tabs.setSelectedComponent(jsp);
243 search_frame.pack();
246 private JScrollPane makeTable(TableModel model) {
247 JTable table = new JTable(model);
248 //java 1.6.0 only!! //table.setAutoCreateRowSorter(true);
249 table.addMouseListener(new DisplayableListListener());
250 table.addKeyListener(kl);
251 JScrollPane jsp = new JScrollPane(table);
252 jsp.setPreferredSize(new Dimension(500, 500));
253 return jsp;
256 /** Listen to the search button. */
257 private class ButtonListener implements ActionListener {
258 public void actionPerformed(ActionEvent ae) {
259 executeSearch();
262 /** Listen to the search field. */
263 private class VKEnterListener extends KeyAdapter {
264 public void keyPressed(KeyEvent ke) {
265 if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
266 executeSearch();
267 return;
269 tryCloseTab(ke);
273 /** Listen to double clicks in the table rows. */
274 private class DisplayableListListener extends MouseAdapter {
275 public void mousePressed(MouseEvent me) {
276 final JTable table = (JTable)me.getSource();
277 final DBObject ob = ((DisplayableTableModel)table.getModel()).getDBObjectAt(table.rowAtPoint(me.getPoint()));
278 if (2 == me.getClickCount()) {
279 // is a table//Utils.log2("LLL source is " + me.getSource());
280 // JTable is AN ABSOLUTE PAIN to work with
281 // ???? THERE IS NO OBVIOUS WAY to retrieve the data. How lame.
282 // Ah, a "model" ... since when a model holds the DATA (!!), same with JTree, how lame.
283 if (ob instanceof Displayable) {
284 Displayable displ = (Displayable)ob;
285 Display.showCentered(displ.getLayer(), displ, true, me.isShiftDown());
286 } else if (ob instanceof Layer) {
287 Display.showFront((Layer)ob);
288 } else {
289 Utils.log2("Search: Unhandable table selection: " + ob);
291 } else if (Utils.isPopupTrigger(me)) {
292 JPopupMenu popup = new JPopupMenu();
293 final String show2D = "Show";
294 final String select = "Select in display";
295 ActionListener listener = new ActionListener() {
296 public void actionPerformed(ActionEvent ae) {
297 final String command = ae.getActionCommand();
298 if (command.equals(show2D)) {
299 if (ob instanceof Displayable) {
300 Displayable displ = (Displayable)ob;
301 Display.showCentered(displ.getLayer(), displ, true, 0 != (ae.getModifiers() & ActionEvent.SHIFT_MASK));
302 } else if (ob instanceof Layer) {
303 Display.showFront((Layer)ob);
305 } else if (command.equals(select)) {
306 if (ob instanceof Layer) {
307 Display.showFront((Layer)ob);
308 } else if (ob instanceof Displayable) {
309 Displayable displ = (Displayable)ob;
310 if (!displ.isVisible()) displ.setVisible(true);
311 Display display = Display.getFront(displ.getProject());
312 if (null == display) return;
313 boolean shift_down = 0 != (ae.getModifiers() & ActionEvent.SHIFT_MASK);
314 display.select(displ, shift_down);
319 JMenuItem item = new JMenuItem(show2D); popup.add(item); item.addActionListener(listener);
320 item = new JMenuItem(select); popup.add(item); item.addActionListener(listener);
321 popup.show(table, me.getX(), me.getY());
326 /** Recursive search into nested LayerSet instances, accumulating instances of type into the list al. */
327 private void find(final LayerSet set, final ArrayList al, final Class type) {
328 if (type == DBObject.class) {
329 al.add(set);
331 for (ZDisplayable zd : set.getZDisplayables()) {
332 if (DBObject.class == type || Displayable.class == type) {
333 al.add(zd);
334 } else if (zd.getClass() == type) {
335 al.add(zd);
338 for (Layer layer : set.getLayers()) {
339 if (DBObject.class == type || Layer.class == type) {
340 al.add(layer);
342 for (Displayable ob : layer.getDisplayables()) {
343 if (DBObject.class == type || Displayable.class == type) {
344 if (ob instanceof LayerSet) find((LayerSet)ob, al, type);
345 else al.add(ob);
346 } else if (ob.getClass() == type) {
347 al.add(ob);
353 /** Remove from the tables if there. */
354 static public void remove(final Displayable displ) {
355 if (null == displ || null == instance) return;
356 final int n_tabs = instance.search_tabs.getTabCount();
357 boolean repaint = false;
358 final int selected = instance.search_tabs.getSelectedIndex();
359 for (int t=0; t<n_tabs; t++) {
360 java.awt.Component c = instance.search_tabs.getComponentAt(t);
361 JTable table = (JTable)((JScrollPane)c).getViewport().getComponent(0);
362 DisplayableTableModel data = (DisplayableTableModel)table.getModel();
363 if (data.remove(displ)) {
364 // remake table (can't delete just a row, only columns??)
365 String name = instance.search_tabs.getTitleAt(t);
366 instance.search_tabs.removeTabAt(t);
367 // need to think about it TODO // if (0 == data.getRowCount()) continue;
368 instance.search_tabs.insertTab(name, null, instance.makeTable(data), "", t);
369 if (t == selected) repaint = true;
370 try { Thread.sleep(100); } catch (Exception e) {} // I love swing
373 if (repaint) {
374 Utils.updateComponent(instance.search_frame);
375 instance.search_tabs.setSelectedIndex(selected);
379 /** 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. */
380 static public void repaint(final Object ob) {
381 if (null == instance) return;
382 final int selected = instance.search_tabs.getSelectedIndex();
383 if (-1 == selected) return;
384 java.awt.Component c = instance.search_tabs.getComponentAt(selected);
385 JTable table = (JTable)((JScrollPane)c).getViewport().getComponent(0);
386 DisplayableTableModel data = (DisplayableTableModel)table.getModel();
387 if (data.contains(ob)) {
388 Utils.updateComponent(instance.search_frame);