001: package tide.outputtabs.search;
002:
003: import snow.utils.gui.ProgressModalDialog;
004: import snow.utils.StringUtils;
005: import tide.sources.SourceFile;
006: import snow.utils.gui.Icons;
007: import snow.utils.gui.GUIUtils;
008: import snow.datatransfer.ClipboardUtils;
009: import snow.sortabletable.*;
010: import javax.swing.table.*;
011: import java.util.*;
012: import javax.swing.*;
013: import javax.swing.event.*;
014: import java.awt.BorderLayout;
015: import java.awt.EventQueue;
016: import java.awt.event.*;
017: import tide.sources.FileItem;
018: import tide.editor.MainEditorFrame;
019:
020: /** Browse search results with replace features. todo:test
021: */
022: public final class SearchTab extends JPanel {
023: final private Vector<SearchHit> searchHits = new Vector<SearchHit>();
024: final private static String[] COLUMN_NAMES = { "file:line",
025: "capture" }; // and then group1, group2, ...
026: final private static int[] COLUMN_PREFERED_SIZES = { 20, 40 }; // 10 for the rest
027: final private HitsTableModel hitsTableModel;
028: final private SortableTableModel sortableTableModel;
029: JTable table;
030: final private MultiSearchPanel asp;
031:
032: private static SearchTab instance;
033: JTextField searchLabel = new JTextField("");
034: private int numberOfSuplementaryRegexToDisplay = 0;
035:
036: public static synchronized SearchTab getInstance() {
037: if (instance == null) {
038: instance = new SearchTab();
039: }
040: return instance;
041: }
042:
043: void setNumberOfSuplementaryRegexGroupsToDisplay(int n) {
044: boolean change = (this .numberOfSuplementaryRegexToDisplay != n);
045: this .numberOfSuplementaryRegexToDisplay = n;
046: if (change) {
047: EventQueue.invokeLater(new Runnable() {
048: public void run() {
049: sortableTableModel.setAllColumnsVisible();
050: }
051: });
052: }
053: }
054:
055: private SearchTab() {
056: super (new BorderLayout(0, 0));
057: hitsTableModel = new HitsTableModel();
058: sortableTableModel = new SortableTableModel(hitsTableModel);
059: table = new JTable(sortableTableModel);
060: sortableTableModel.installGUI(table);
061: add(new JScrollPane(table), BorderLayout.CENTER);
062:
063: asp = new MultiSearchPanel("Filter: ", null, sortableTableModel);
064: JPanel pt = new JPanel();
065: pt.setLayout(new BoxLayout(pt, BoxLayout.X_AXIS));
066: pt.add(GUIUtils.wrapLeft(asp, 0));
067: add(pt, BorderLayout.NORTH);
068: pt.add(searchLabel);
069: searchLabel.setBorder(null);
070: searchLabel.setEditable(false);
071:
072: table.addMouseListener(new MouseAdapter() {
073: @Override
074: public void mousePressed(MouseEvent me) {
075: /* if(me.getClickCount()==2)
076: {
077: //select the line on double click
078: int pos = sortableTableModel.getIndexInUnsortedFromTablePos( table.getSelectedRow() );
079: if(pos<0) return;
080: final LineMessage lm = messagesTableModel.getMessageAt(pos);
081: jumpToSourceForMessage(lm);
082: return;
083: }*/
084:
085: if (me.isPopupTrigger()) {
086: showTablePopup(me);
087: return;
088: }
089:
090: if (table.getSelectedRowCount() == 1) {
091: int pos = sortableTableModel
092: .getIndexInUnsortedFromTablePos(table
093: .getSelectedRow());
094: if (pos < 0)
095: return;
096: final SearchHit lm = hitsTableModel.getHitAt(pos);
097: jumpToSourceForHit(lm);
098: return;
099: }
100: }
101:
102: @Override
103: public void mouseReleased(MouseEvent me) {
104: if (me.isPopupTrigger()) {
105: showTablePopup(me);
106: return;
107: }
108: }
109: });
110: }
111:
112: private void showTablePopup(MouseEvent me) {
113: JPopupMenu popup = new JPopupMenu("Search table context menu");
114:
115: /* if(table.getSelectedRowCount()==1)
116: {
117: int pos = sortableTableModel.getIndexInUnsortedFromTablePos( table.getSelectedRow() );
118: if(pos<0) return;
119: //final SearchHit lm = hitsTableModel.getHitAt(pos);
120: }*/
121:
122: if (table.getSelectedRowCount() > 0) {
123: final List<SearchHit> selected = new ArrayList<SearchHit>();
124: for (int r : table.getSelectedRows()) {
125: int pos = sortableTableModel
126: .getIndexInUnsortedFromTablePos(r);
127: selected.add(hitsTableModel.getHitAt(pos));
128: }
129:
130: /*if(MainEditorFrame.instance.enableExperimental)
131: {*/
132: JMenuItem repl = new JMenuItem("Replace capture with...",
133: Icons.sharedWiz);
134: popup.add(repl);
135: popup.addSeparator();
136: repl.addActionListener(new ActionListener() {
137: public void actionPerformed(ActionEvent ae) {
138:
139: replace(selected);
140: }
141: });
142: //}
143:
144: JMenuItem remove = new JMenuItem("Remove "
145: + selected.size() + " item"
146: + (selected.size() == 1 ? "" : "s") + " from list",
147: Icons.sharedCross);
148:
149: popup.add(remove);
150: remove.addActionListener(new ActionListener() {
151: public void actionPerformed(ActionEvent ae) {
152: hitsTableModel.remove(selected);
153: if (selected.size() > 0) {
154: refresh();
155: }
156: }
157: });
158:
159: JMenuItem copyToClipboard = new JMenuItem(
160: "Copy locations to clipboard");
161: popup.addSeparator();
162: popup.add(copyToClipboard);
163: copyToClipboard.addActionListener(new ActionListener() {
164: public void actionPerformed(ActionEvent ae) {
165: StringBuilder sb = new StringBuilder(searchLabel
166: .getText()
167: + "\r\n");
168: for (SearchHit sh : selected) {
169: sb.append("\r\n" + sh);
170: }
171: ClipboardUtils.copyToClipboard(sb.toString());
172: }
173: });
174: }
175:
176: if (table.getRowCount() > 0) {
177: popup.addSeparator();
178: JMenuItem selectAll = new JMenuItem("Select all");
179: popup.add(selectAll);
180: selectAll.addActionListener(new ActionListener() {
181: public void actionPerformed(ActionEvent ae) {
182: table.selectAll();
183: }
184: });
185: }
186:
187: popup.show(table, me.getX(), me.getY());
188: }
189:
190: private void replace(final List<SearchHit> hits) {
191: MainEditorFrame.instance.editorPanel
192: .storeTemporary_ActualEditedSource();
193:
194: final String rep = JOptionPane
195: .showInputDialog(
196: this ,
197: "Please enter the string to replace with.\n"
198: + "\nCAUTION1: ensure you've made a recent backup (use file menu feature),"
199: + "\n the replace is a recent feature, not well tested. The replace has no undo!"
200: + "\nCAUTION2: verify carefully the captured strings, browse them, ..."
201: + "\nCAUTION3: ensure your search is \"recent\". In doubt, look at the dates.");
202:
203: if (rep == null)
204: return;
205:
206: final ProgressModalDialog pmd = new ProgressModalDialog(
207: MainEditorFrame.instance, "Replacing", true);
208: pmd.start();
209: Thread t = new Thread() {
210: public void run() {
211: try {
212: replaceT(hits, pmd, rep);
213: } finally {
214: pmd.closeDialog();
215: }
216: }
217: };
218: t.start();
219:
220: }
221:
222: private void replaceT(final List<SearchHit> hits,
223: ProgressModalDialog pmd, String rep) {
224: Collections.sort(hits);
225:
226: boolean hasErrors = false;
227: boolean editedDocHasChanged = false;
228: pmd.setProgressBounds(hits.size());
229:
230: for (int i = hits.size() - 1; i >= 0; i--) // REVERSE ! to avoid ugly side effects. so we can also
231: {
232: SearchHit shit = hits.get(i);
233: pmd.incrementProgress(1);
234: pmd.setProgressComment(shit.fileItem.getJavaName() + ":"
235: + shit.line);
236: if (pmd.getWasCancelled()) {
237: pmd.closeDialog();
238: JOptionPane.showMessageDialog(null,
239: "The replace operation has been cancelled.",
240: "Replace cancelled",
241: JOptionPane.INFORMATION_MESSAGE);
242: return;
243: }
244:
245: System.out.println("" + shit + " => replace with " + rep);
246:
247: FileItem fi = shit.fileItem;
248: if (!(fi instanceof SourceFile)) {
249: hasErrors = true;
250: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
251: .appendErrorLine(" " + fi.getJavaName() + ":"
252: + shit.line
253: + ": can't replace, not a source file");
254: continue;
255: }
256:
257: SourceFile sf = (SourceFile) fi;
258: if (!sf.isEditable()) {
259: hasErrors = true;
260: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
261: .appendErrorLine(" " + fi.getJavaName() + ":"
262: + shit.line
263: + ": can't replace, is not editable");
264: continue;
265: }
266:
267: try {
268: if (!editedDocHasChanged
269: && MainEditorFrame.instance.editorPanel
270: .getActualDisplayedFile() == sf) {
271: // special case
272: sf.saveContentToFile();
273: editedDocHasChanged = true;
274: }
275:
276: String cont = sf.getContent();
277:
278: cont = StringUtils.replace(cont, shit.line - 1,
279: shit.column - 1, shit.lineEnd - 1,
280: shit.colEnd - 1, rep);
281:
282: sf.setContentFromEditor(cont, true); //, sf.getCaretLinePosition(), sf.getCaretColumnPosition());
283: // let the user save itself. (?)
284: sf.saveContentToFile(); // no, do it.
285: } catch (Exception e) {
286: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
287: .appendErrorLine(" " + fi.getJavaName() + ":"
288: + shit.line + ": can't replace: "
289: + e.getMessage());
290:
291: e.printStackTrace();
292: }
293: }
294:
295: if (hasErrors) {
296: MainEditorFrame.instance.outputPanels.selectToolsTab(true);
297: }
298:
299: if (editedDocHasChanged) {
300: // force rewrite.
301: try {
302: ((SourceFile) MainEditorFrame.instance.editorPanel
303: .getActualDisplayedFile()).saveContentToFile();
304: } catch (Exception ex) {
305: ex.printStackTrace();
306: }
307: // and reload
308: MainEditorFrame.instance.editorPanel
309: .replaceContentWithContentOnDisk();
310: }
311: }
312:
313: /** Must be called in the EDT !
314: * 1) reread all messages from manager
315: * 2) update the view
316: */
317: public void refresh() {
318: EventQueue.invokeLater(new Runnable() {
319: public void run() {
320: hitsTableModel.refresh();
321: }
322: });
323: }
324:
325: public void setFilter(String filter) {
326: asp.setQueries(Query.simpleSearchQuery(filter));
327: }
328:
329: public void setDescription(String description) {
330: searchLabel.setText(description);
331: }
332:
333: /** 0: no occurences
334: * -1: no search (reset)
335: */
336: public void setHits(int n, int maxNrOfGroups,
337: boolean jumpIfSingleHit) {
338: if (n == 0) {
339: if (!searchLabel.getText().endsWith(": no hits")) {
340: searchLabel
341: .setText(searchLabel.getText() + ": no hits");
342: }
343: }
344: if (n == -1)
345: searchLabel
346: .setText(" press CTRL+G to search or use the tree popup");
347:
348: this .setNumberOfSuplementaryRegexGroupsToDisplay(maxNrOfGroups);
349:
350: //TODO: set the tab name?
351: //searchLabel.setText(searchLabel.getText()+": "+n+" hit"+(n==1?"":"s"));
352: MainEditorFrame.instance.outputPanels.setSearchCount(n);
353:
354: if (jumpIfSingleHit) {
355: EventQueue.invokeLater(new Runnable() {
356: public void run() {
357: if (hitsTableModel.getRowCount() == 1) {
358: jumpToSourceForHit(hitsTableModel.getHitAt(0));
359: }
360: }
361: });
362: }
363:
364: }
365:
366: public void jumpToSourceForHit(SearchHit lm) {
367: FileItem fi = MainEditorFrame.instance.getFileItem(lm.fileItem
368: .getJavaName(), null);
369: MainEditorFrame.instance.setSourceOrItemToEditOrView(fi, true);
370:
371: int line = lm.line;
372: if (line < 1)
373: line = 1; // enforce showing
374:
375: //System.out.println("jsfh: "+lm.line+", "+lm.column+", "+lm.lineEnd+", "+lm.colEnd);
376:
377: MainEditorFrame.instance.editorPanel.selectDocumentPart(
378: line - 1, lm.column - 1, lm.lineEnd - 1, lm.colEnd - 1);
379: }
380:
381: class HitsTableModel extends FineGrainTableModel {
382: public HitsTableModel() {
383: //no: refresh();
384: }
385:
386: public void refresh() {
387: fireTableModelWillChange();
388: synchronized (SearchResultsManager.getInstance()) {
389: searchHits.clear();
390: for (String jn : SearchResultsManager.getInstance().hitsForFile
391: .keySet()) {
392: searchHits.addAll(SearchResultsManager
393: .getInstance().hitsForFile.get(jn));
394: }
395: }
396: fireTableDataChanged();
397: fireTableModelHasChanged();
398: }
399:
400: public int getRowCount() {
401: return searchHits.size();
402: }
403:
404: public int getColumnCount() {
405: return 2 + numberOfSuplementaryRegexToDisplay;
406: }
407:
408: public SearchHit getHitAt(int row) {
409: return searchHits.get(row);
410: }
411:
412: public void remove(List<SearchHit> mess) {
413: fireTableModelWillChange();
414:
415: for (SearchHit lm : mess) {
416: SearchResultsManager.getInstance().remove(lm);
417: }
418: searchHits.removeAll(mess);
419:
420: fireTableDataChanged();
421: fireTableModelHasChanged();
422: }
423:
424: public Object getValueAt(int row, int col) {
425: if (row < 0)
426: return "bad row:" + row;
427: if (row >= searchHits.size())
428: return "bad row:" + row;
429:
430: SearchHit pi = searchHits.get(row);
431:
432: if (col == 0)
433: return pi.fileItem.getJavaName()
434: + (pi.line >= 0 ? ":" + pi.line : "");
435: if (col == 1)
436: return pi.capture.length() > 0 ? pi.capture : "";
437: // supplementary regex groups
438: if (col - 2 < pi.supplementaryRegexCaptureGroups.size()) {
439: return pi.supplementaryRegexCaptureGroups.get(col - 2);
440: } else {
441: return "";
442: }
443: }
444:
445: // should be called from EDT !
446: public void update() {
447: fireTableModelWillChange();
448:
449: fireTableDataChanged();
450: fireTableModelHasChanged();
451: }
452:
453: // should be called from EDT !
454: public void updateStructure() {
455: fireTableModelWillChange();
456:
457: super .fireTableStructureChanged();
458: fireTableModelHasChanged();
459: }
460:
461: @Override
462: public String getColumnName(int column) {
463: if (column >= 0 && column < COLUMN_NAMES.length)
464: return COLUMN_NAMES[column];
465: return "group " + (column - 1);
466: }
467:
468: @Override
469: public int getPreferredColumnWidth(int column) {
470: if (column >= 0 && column < COLUMN_PREFERED_SIZES.length)
471: return COLUMN_PREFERED_SIZES[column];
472: return 10;
473: }
474:
475: @Override
476: public int compareForColumnSort(int pos1, int pos2, int col) {
477: if (col == 0) {
478: SearchHit m1 = searchHits.get(pos1);
479: SearchHit m2 = searchHits.get(pos2);
480: int comp = m1.fileItem.getJavaName().compareTo(
481: m2.fileItem.getJavaName());
482: if (comp != 0)
483: return comp;
484: return super .compareDoubles(m2.line, m1.line);
485: } else if (col == 1) {
486: SearchHit m1 = searchHits.get(pos1);
487: SearchHit m2 = searchHits.get(pos2);
488: return m1.capture.compareTo(m2.capture);
489: } else {
490: return super.compareForColumnSort(pos1, pos2, col);
491: }
492: }
493: }
494:
495: }
|