| //------------------------------------------------------------------------------ |
| // Copyright (c) 2005, 2006 IBM Corporation and others. |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // which accompanies this distribution, and is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // Contributors: |
| // IBM Corporation - initial implementation |
| //------------------------------------------------------------------------------ |
| package org.eclipse.epf.richtext.actions; |
| |
| import org.eclipse.epf.common.utils.XMLUtil; |
| import org.eclipse.epf.richtext.IRichText; |
| import org.eclipse.epf.richtext.RichTextCommand; |
| import org.eclipse.epf.richtext.RichTextEditor; |
| import org.eclipse.epf.richtext.RichTextImages; |
| import org.eclipse.epf.richtext.RichTextPlugin; |
| import org.eclipse.epf.richtext.RichTextResources; |
| import org.eclipse.epf.richtext.dialogs.FindReplaceDialog; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Display; |
| |
| /** |
| * Finds and replaces text in a rich text control. |
| * |
| * @author Kelvin Low |
| * @since 1.0 |
| */ |
| public class FindReplaceAction extends RichTextAction { |
| |
| /** |
| * Finds text sub action. |
| */ |
| public static final int FIND_TEXT = 1; |
| |
| /** |
| * Replaces text sub action. |
| */ |
| public static final int REPLACE_TEXT = 2; |
| |
| /** |
| * Replaces and finds text sub action. |
| */ |
| public static final int REPLACE_FIND_TEXT = 3; |
| |
| /** |
| * Replaces all sub action. |
| */ |
| public static final int REPLACE_ALL_TEXT = 4; |
| |
| /** |
| * Find match in a forward direction. |
| */ |
| public static final int FORWARD_MATCH = 1; |
| |
| /** |
| * Find match in a backward direction. |
| */ |
| public static final int BACKWARD_MATCH = -1; |
| |
| /** |
| * Whole word match. |
| */ |
| public static final int WHOLE_WORD_MATCH = 2; |
| |
| /** |
| * Case sensitive match. |
| */ |
| public static final int CASE_SENSITIVE_MATCH = 4; |
| |
| // Encoded single quote. |
| private static final String ENCODED_SINGLE_QUOTE = "%sq%"; //$NON-NLS-1$ |
| |
| private IRichText richText; |
| |
| private StyledText styledText; |
| |
| protected boolean foundMatch = false; |
| |
| // the dialog instance |
| protected FindReplaceDialog dialog; |
| |
| /** |
| * Creates a new instance. |
| */ |
| public FindReplaceAction(IRichText richText) { |
| super(richText, IAction.AS_PUSH_BUTTON); |
| setImageDescriptor(RichTextImages.IMG_DESC_FIND_REPLACE); |
| setDisabledImageDescriptor(RichTextImages.DISABLED_IMG_DESC_FIND_REPLACE); |
| setToolTipText(RichTextResources.findReplaceAction_toolTipText); |
| } |
| |
| /** |
| * Returns <code>true</code> if this action should be disabled when the |
| * rich text editor is in readonly mode. |
| */ |
| public boolean disableInReadOnlyMode() { |
| return false; |
| } |
| |
| /** |
| * Returns <code>true</code> if this action should be disabled when the |
| * rich text editor is in source edit mode. |
| */ |
| public boolean disableInSourceMode() { |
| return false; |
| } |
| |
| /** |
| * Executes the action. |
| * |
| * @param richText |
| * a rich text control |
| */ |
| public void execute(IRichText richText) { |
| if (this.richText == null) |
| this.richText = richText; |
| if (this.richText != null) { |
| try { |
| if (dialog != null) { |
| dialog.setFindOnly(!this.richText.getEditable()); |
| dialog.open(); |
| } else { |
| dialog = new FindReplaceDialog(Display |
| .getCurrent().getActiveShell(), this, !this.richText |
| .getEditable()); |
| dialog.open(); |
| } |
| } catch (Exception e) { |
| RichTextPlugin.getDefault().getLogger().logError(e); |
| } |
| } |
| } |
| |
| /** |
| * Returns <code>true</code> if a match is found. |
| * |
| * @return <code>true</code> if a match is found. |
| */ |
| public boolean getFoundMatch() { |
| return foundMatch; |
| } |
| |
| /** |
| * Executes the action. |
| * |
| * @param subAction |
| * the sub action to execute |
| * @param findText |
| * the find text |
| * @param replaceText |
| * the replace text |
| * @param matchDir |
| * the match direction; the value can either be |
| * <code>FIND_FORWARD</code> or <code>FIND_BACKWARD</code>. |
| * @param matchOptions |
| * the match options |
| */ |
| public void run(int subAction, String findText, String replaceText, |
| int matchDir, int matchOptions) { |
| styledText = null; |
| if (richText instanceof RichTextEditor |
| && ((RichTextEditor) richText).isHTMLTabSelected()) { |
| styledText = ((RichTextEditor) richText).getSourceEdit(); |
| } |
| if (styledText == null) { |
| if (findText.indexOf("'") != -1) { //$NON-NLS-1$ |
| findText = findText.replaceAll("'", ENCODED_SINGLE_QUOTE); //$NON-NLS-1$ |
| } |
| if (replaceText.indexOf("'") != -1) { //$NON-NLS-1$ |
| replaceText = replaceText.replaceAll("'", ENCODED_SINGLE_QUOTE); //$NON-NLS-1$ |
| } |
| } |
| try { |
| foundMatch = false; |
| int status = 0; |
| switch (subAction) { |
| case FIND_TEXT: |
| status = findText(findText, matchDir, matchOptions); |
| break; |
| case REPLACE_TEXT: |
| status = replaceText(replaceText, matchDir, matchOptions); |
| break; |
| case REPLACE_FIND_TEXT: |
| status = replaceFindText(findText, replaceText, matchDir, |
| matchOptions); |
| break; |
| case REPLACE_ALL_TEXT: |
| replaceAll(findText, replaceText, matchOptions); |
| break; |
| } |
| if (status > 0) |
| foundMatch = true; |
| } catch (Exception e) { |
| RichTextPlugin.getDefault().getLogger().logError(e); |
| } |
| } |
| |
| /** |
| * Escapes the given text. |
| * |
| * @param text |
| * text to be escaped |
| */ |
| private static String escape(String text) { |
| if (text == null || text.length() == 0) |
| return ""; //$NON-NLS-1$ |
| StringBuffer sb = new StringBuffer(); |
| int textSize = text.length(); |
| for (int i = 0; i < textSize; i++) { |
| char ch = text.charAt(i); |
| switch (ch) { |
| case '<': |
| sb.append(XMLUtil.XML_LT); |
| break; |
| case '>': |
| sb.append(XMLUtil.XML_GT); |
| break; |
| case '&': |
| sb.append(XMLUtil.XML_AMP); |
| break; |
| default: |
| sb.append(ch); |
| break; |
| } |
| } |
| return sb.toString(); |
| } |
| |
| private int findText(String findText, int matchDir, int matchOptions) { |
| int status = 0; |
| if (styledText != null) { |
| status = styledTextFindTextAndSelect(findText, matchDir, |
| matchOptions); |
| } else { |
| status = richText |
| .executeCommand(RichTextCommand.FIND_TEXT, new String[] { |
| findText, "" + matchDir, "" + matchOptions }); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| return status; |
| } |
| |
| private int replaceText(String replaceText, int matchDir, int matchOptions) { |
| int status = 0; |
| if (styledText != null) { |
| status = styledTextReplaceTextAndSelect(replaceText); |
| } else { |
| status = richText.executeCommand(RichTextCommand.REPLACE_TEXT, |
| new String[] { replaceText, |
| "" + matchDir, "" + matchOptions }); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| return status; |
| } |
| |
| private int replaceFindText(String findText, String replaceText, |
| int matchDir, int matchOptions) { |
| int status = 0; |
| if (styledText != null) { |
| styledTextReplaceTextAndSelect(replaceText); |
| status = styledTextFindTextAndSelect(findText, matchDir, |
| matchOptions); |
| } else { |
| richText.executeCommand(RichTextCommand.REPLACE_TEXT, new String[] { |
| replaceText, "" + matchDir, "" + matchOptions }); //$NON-NLS-1$ //$NON-NLS-2$ |
| status = richText |
| .executeCommand(RichTextCommand.FIND_TEXT, new String[] { |
| findText, "" + matchDir, "" + matchOptions }); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| return status; |
| } |
| |
| private void replaceAll(String findText, String replaceText, |
| int matchOptions) { |
| if (styledText != null) { |
| styledTextReplaceAll(findText, replaceText, matchOptions); |
| } else { |
| richText.executeCommand(RichTextCommand.REPLACE_ALL_TEXT, |
| new String[] { escape(findText), escape(replaceText), |
| "" + matchOptions }); //$NON-NLS-1$ |
| } |
| } |
| |
| private int styledTextFindTextAndSelect(String findText, int matchDir, |
| int matchOptions) { |
| Point selectionOffset = styledText.getSelectionRange(); |
| int firstSelectedOffset = selectionOffset.x; |
| int lastSelectedOffset = selectionOffset.x + selectionOffset.y - 1; |
| String htmlText = styledText.getText(); |
| int indexOfMatch = -1; |
| if ((matchOptions & CASE_SENSITIVE_MATCH) == 0) { |
| // TODO: use toUpperCase(Locale) once library has locale attribute |
| htmlText = htmlText.toUpperCase(); |
| findText = findText.toUpperCase(); |
| } |
| do { |
| if (indexOfMatch != -1) { |
| lastSelectedOffset = indexOfMatch + 1; |
| firstSelectedOffset = indexOfMatch - 1; |
| } |
| if (matchDir == FORWARD_MATCH) { |
| indexOfMatch = htmlText.indexOf(findText, |
| lastSelectedOffset + 1); |
| } else { |
| indexOfMatch = htmlText.lastIndexOf(findText, |
| firstSelectedOffset - 1); |
| } |
| } while (indexOfMatch != -1 |
| && ((matchOptions & WHOLE_WORD_MATCH) == WHOLE_WORD_MATCH) |
| && isPartOfWord(htmlText, indexOfMatch, findText.length())); |
| if (indexOfMatch != -1) { |
| styledText.setSelectionRange(indexOfMatch, findText.length()); |
| styledText.showSelection(); |
| } else { |
| String selectedText = styledText.getSelectionText(); |
| if ((matchOptions & CASE_SENSITIVE_MATCH) == 0) { |
| selectedText = selectedText.toUpperCase(); |
| } |
| if (selectedText.equals(findText)) { |
| indexOfMatch = styledText.getSelectionRange().x; |
| } |
| } |
| return indexOfMatch; |
| |
| } |
| |
| private int styledTextReplaceTextAndSelect(String replaceText) { |
| Point selectionOffset = styledText.getSelectionRange(); |
| styledText.replaceTextRange(selectionOffset.x, selectionOffset.y, |
| replaceText); |
| styledText.setSelectionRange(selectionOffset.x, replaceText.length()); |
| |
| return 1; |
| } |
| |
| private void styledTextReplaceAll(String findText, String replaceText, |
| int matchOptions) { |
| styledText.setSelectionRange(0, 0); |
| while (styledTextFindTextAndSelect(findText, FORWARD_MATCH, |
| matchOptions) != -1) { |
| styledTextReplaceTextAndSelect(replaceText); |
| } |
| } |
| |
| private boolean isWordChar(char c) { |
| if (Character.isLetterOrDigit(c)) |
| return true; |
| return false; |
| } |
| |
| private boolean isPartOfWord(String text, int index, int length) { |
| if (index > 0) |
| if (isWordChar(text.charAt(index - 1))) |
| return true; |
| if (text.length() >= index + length) |
| if (isWordChar(text.charAt(index + length))) |
| return true; |
| return false; |
| } |
| |
| public IRichText getRichText() { |
| return richText; |
| } |
| |
| public void setRichText(IRichText richText) { |
| this.richText = richText; |
| } |
| |
| public void dispose() { |
| if (dialog != null) { |
| dialog.close(); |
| dialog = null; |
| } |
| } |
| |
| } |