/*FreeMind - A Program for creating and viewing Mindmaps *Copyright (C) 2000-2006 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitri Polivaev and others. * *See COPYING for Details * *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; either version 2 *of the License, or (at your option) any later version. * *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. */ package freemind.modes.common.actions; import java.awt.Container; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.ListIterator; import javax.swing.AbstractAction; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JTextField; import freemind.main.FreeMind; import freemind.main.HtmlTools; import freemind.main.Resources; import freemind.main.Tools; import freemind.modes.ControllerAdapter; import freemind.modes.MindMapNode; public class FindAction extends AbstractAction { private final ControllerAdapter controller; private ArrayList findNodesUnfoldedByLastFind; private MindMapNode findFromNode; private String searchTerm; private Collection subterms; /** * @return Returns the subterms. */ public Collection getSubterms() { return subterms; } public String getSearchTerm() { return searchTerm; } public String getFindFromText() { String plainNodeText = HtmlTools.htmlToPlain(findFromNode.toString()) .replaceAll("\n", " "); return plainNodeText.length() <= 30 ? plainNodeText : plainNodeText .substring(0, 30) + "..."; } private boolean findCaseSensitive; private LinkedList findNodeQueue; private JDialog mDialog; private int mResult; private JCheckBox mFindInNotesTooBox; private JTextField mSearchField; /** This isn't stored and is per map. */ private String mLastSearchString; public FindAction(ControllerAdapter controller) { super(controller.getText("find"), new ImageIcon( controller.getResource("images/filefind.png"))); this.controller = controller; } public void actionPerformed(ActionEvent e) { displayDialog(); if (mResult != JOptionPane.OK_OPTION) { return; } String what = mSearchField.getText(); if (what == null || what.equals("")) { return; } Collection subterms = breakSearchTermIntoSubterms(what); this.searchTerm = what; // System.err.println(subterms); /* caseSensitive=false */ boolean found = find(controller.getSelected(), subterms, false); controller.getView().repaint(); if (!found) { String messageText = controller.getText("no_found_from"); String searchTerm = getSearchTermAsEscapedString(messageText); controller.getController().informationMessage( messageText.replaceAll("\\$1", searchTerm).replaceAll( "\\$2", getFindFromText()), controller.getView().getSelected()); } } private void close(int pResult) { mResult = pResult; mDialog.setVisible(false); mDialog.dispose(); // Store "find in notes too" value to prop. if (pResult == JOptionPane.OK_OPTION) { Resources .getInstance() .getProperties() .setProperty(FreeMind.RESOURCES_SEARCH_IN_NOTES_TOO, mFindInNotesTooBox.isSelected() ? "true" : "false"); mLastSearchString = mSearchField.getText(); } } void displayDialog() { mDialog = null; mDialog = new JDialog(controller.getFrame().getJFrame(), controller.getText("find")); mDialog.setModal(true); mDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); AbstractAction cancelAction = new AbstractAction() { public void actionPerformed(ActionEvent pE) { close(JOptionPane.CANCEL_OPTION); } }; AbstractAction okAction = new AbstractAction() { public void actionPerformed(ActionEvent pE) { close(JOptionPane.OK_OPTION); } }; Tools.addEscapeActionToDialog(mDialog, cancelAction); mDialog.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent pE) { close(JOptionPane.CANCEL_OPTION); } }); Container contentPane = mDialog.getContentPane(); contentPane.setLayout(new GridBagLayout()); contentPane.add(new JLabel(controller.getText("find_what")), new GridBagConstraints(1, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 0, 0), 0, 0)); mSearchField = new JTextField(mLastSearchString); mSearchField.selectAll(); mSearchField.setMinimumSize(new Dimension(500, 14)); contentPane.add(mSearchField, new GridBagConstraints(2, 0, 10, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 0, 0), 0, 0)); ImageIcon findImage = new ImageIcon(Resources.getInstance() .getResource("images/filefind_big.png")); contentPane.add(new JLabel(findImage), new GridBagConstraints(0, 0, 1, 2, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 0, 0), 0, 0)); mFindInNotesTooBox = new JCheckBox( controller .getText("ExtendedFindDialog.find_search_in_notes_too")); mFindInNotesTooBox.setSelected(Resources.getInstance().getBoolProperty( FreeMind.RESOURCES_SEARCH_IN_NOTES_TOO)); Tools.setLabelAndMnemonic(mFindInNotesTooBox, null); contentPane.add(mFindInNotesTooBox, new GridBagConstraints(0, 2, 3, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 0, 0), 0, 0)); JButton okButton = new JButton( controller.getText("ExtendedFindDialog.ok")); Tools.setLabelAndMnemonic(okButton, null); okButton.addActionListener(okAction); contentPane.add(okButton, new GridBagConstraints(2, 3, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 0, 0), 0, 0)); JButton cancelButton = new JButton( controller.getText("ExtendedFindDialog.cancel")); Tools.setLabelAndMnemonic(cancelButton, null); cancelButton.addActionListener(cancelAction); contentPane.add(cancelButton, new GridBagConstraints(3, 3, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 0, 0), 0, 0)); mDialog.getRootPane().setDefaultButton(okButton); mDialog.pack(); Tools.setDialogLocationRelativeTo(mDialog, controller.getSelectedView()); mDialog.setVisible(true); } private String getSearchTermAsEscapedString(String messageText) { String searchTerm = messageText.startsWith("<html>") ? HtmlTools .toXMLEscapedText(getSearchTerm()) : getSearchTerm(); // Fix for // https://sourceforge.net/tracker/?func=detail&aid=3200783&group_id=7118&atid=107118 // Patch // https://sourceforge.net/tracker/?func=detail&aid=3276562&group_id=7118&atid=307118, // thanks to the author searchTerm = searchTerm.replace("$", "\\$"); return searchTerm; } public static class FindNextAction extends AbstractAction { private final ControllerAdapter controller; private final FindAction find; public FindNextAction(ControllerAdapter controller, FindAction find) { super(controller.getText("find_next")); this.controller = controller; this.find = find; } public void actionPerformed(ActionEvent e) { Collection subterms = find.getSubterms(); if (subterms == null) { controller.getController().informationMessage( controller.getText("no_previous_find"), controller.getView().getSelected()); return; } boolean found = find.findNext(); controller.getView().repaint(); if (!found) { String messageText = controller.getText("no_more_found_from"); String searchTerm = find .getSearchTermAsEscapedString(messageText); controller.getController().informationMessage( messageText.replaceAll("\\$1", searchTerm).replaceAll( "\\$2", find.getFindFromText()), controller.getView().getSelected()); } } } public boolean find(MindMapNode node, Collection subterms, boolean caseSensitive) { findNodesUnfoldedByLastFind = new ArrayList(); LinkedList nodes = new LinkedList(); nodes.addFirst(node); findFromNode = node; Collection finalizedSubterms; if (!caseSensitive) { finalizedSubterms = new ArrayList(); for (Iterator i = subterms.iterator(); i.hasNext();) { finalizedSubterms.add(((String) i.next()).toLowerCase()); } } else { finalizedSubterms = subterms; } return find(nodes, finalizedSubterms, caseSensitive); } private boolean find(LinkedList /* queue of MindMapNode */nodes, Collection subterms, boolean caseSensitive) { // Precondition: if !caseSensitive then >>what<< is in lowercase. boolean searchInNotesToo = Resources.getInstance().getBoolProperty( FreeMind.RESOURCES_SEARCH_IN_NOTES_TOO); // Fold the path of previously found node boolean thereWereNodesToBeFolded = !findNodesUnfoldedByLastFind .isEmpty(); if (!findNodesUnfoldedByLastFind.isEmpty()) { // if (false) { ListIterator i = findNodesUnfoldedByLastFind .listIterator(findNodesUnfoldedByLastFind.size()); while (i.hasPrevious()) { MindMapNode node = (MindMapNode) i.previous(); try { controller.setFolded(node, true); } catch (Exception e) { } } findNodesUnfoldedByLastFind = new ArrayList(); } // We implement width-first search. while (!nodes.isEmpty()) { MindMapNode node = (MindMapNode) nodes.removeFirst(); // Add children to the queue for (ListIterator i = node.childrenUnfolded(); i.hasNext();) { nodes.addLast(i.next()); } if (!node.isVisible()) continue; // Bug fix for // http://sourceforge.net/tracker/?func=detail&aid=3035387&group_id=7118&atid=107118 String nodeText = node.toString(); nodeText = prepareTextContent(caseSensitive, nodeText); // End bug fix. String noteText = node.getNoteText(); noteText = prepareTextContent(caseSensitive, noteText); boolean found = true; boolean foundInNotes = false; for (Iterator i = subterms.iterator(); i.hasNext();) { if (nodeText.indexOf((String) i.next()) < 0) { // Subterm not found found = false; break; } } if ((!found) && searchInNotesToo) { /* now, search the notes. */ found = true; for (Iterator i = subterms.iterator(); i.hasNext();) { if (noteText.indexOf((String) i.next()) < 0) { // Subterm not found found = false; break; } } foundInNotes = true; } if (found) { // Found displayNode(node, findNodesUnfoldedByLastFind); centerNode(node); if (foundInNotes) { // TODO: Select text in notes window. } // Save the state for find next this.subterms = subterms; findCaseSensitive = caseSensitive; findNodeQueue = nodes; return true; } } centerNode(findFromNode); return false; } public String prepareTextContent(boolean caseSensitive, String nodeText) { if (nodeText == null) { nodeText = ""; } if (HtmlTools.isHtmlNode(nodeText)) { nodeText = HtmlTools.removeHtmlTagsFromString(nodeText); nodeText = HtmlTools.unescapeHTMLUnicodeEntity(nodeText); } if (!caseSensitive) { nodeText = nodeText.toLowerCase(); } return nodeText; } private Collection breakSearchTermIntoSubterms(String searchTerm) { ArrayList subterms = new ArrayList(); StringBuffer subterm = new StringBuffer(); int len = searchTerm.length(); char myChar; char previousChar = 'a'; boolean withinQuotes = false; for (int i = 0; i < len; ++i) { myChar = searchTerm.charAt(i); if (myChar == ' ' && withinQuotes) { subterm.append(myChar); } else if ((myChar == ' ' && !withinQuotes)) { subterms.add(subterm.toString()); subterm.setLength(0); } else if (myChar == '"' && i > 0 && i < len - 1 && searchTerm.charAt(i - 1) != ' ' && searchTerm.charAt(i + 1) != ' ') { // Character " surrounded by non-spaces subterm.append(myChar); } else if (myChar == '"' && withinQuotes) { withinQuotes = false; } else if (myChar == '"' && !withinQuotes) { withinQuotes = true; } else { subterm.append(myChar); } previousChar = myChar; } subterms.add(subterm.toString()); return subterms; } /** * Display a node in the display (used by find and the goto action by arrow * link actions). */ public void displayNode(MindMapNode node, ArrayList nodesUnfoldedByDisplay) { // Unfold the path to the node Object[] path = controller.getMap().getPathToRoot(node); // Iterate the path with the exception of the last node for (int i = 0; i < path.length - 1; i++) { MindMapNode nodeOnPath = (MindMapNode) path[i]; // System.out.println(nodeOnPath); if (nodeOnPath.isFolded()) { if (nodesUnfoldedByDisplay != null) nodesUnfoldedByDisplay.add(nodeOnPath); controller.setFolded(nodeOnPath, false); } } } public boolean findNext() { // Precodition: subterms != null. We check the precodition but give no // message. // The logic of find next is vulnerable. find next relies on the queue // of nodes from previous find / find next. However, between previous // find / find next and this find next, nodes could have been deleted // or moved. The logic expects that no changes happened, even that no // node has been folded / unfolded. // You may want to come with more correct solution, but this one // works for most uses, and does not cause any big trouble except // perhaps for some uncaught exceptions. As a result, it is not very // nice, but far from critical and working quite fine. if (subterms != null) { return find(findNodeQueue, subterms, findCaseSensitive); } return false; } /** */ private void centerNode(MindMapNode node) { // Select the node and scroll to it. controller.centerNode(node); } }