/******************************************************************************* * Copyright (c) 2008 xored software, Inc. 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: * xored software, Inc. - initial API and Implementation (Yuri Strot) *******************************************************************************/ package com.xored.glance.ui.panels; import com.xored.glance.internal.ui.GlanceEventDispatcher; import com.xored.glance.internal.ui.GlancePlugin; import com.xored.glance.internal.ui.panels.CheckAction; import com.xored.glance.internal.ui.panels.ImageAnimation; import com.xored.glance.internal.ui.preferences.IPreferenceConstants; import com.xored.glance.internal.ui.search.SearchManager; import com.xored.glance.internal.ui.search.SearchRule; import com.xored.glance.ui.sources.Match; import com.xored.glance.ui.utils.SelectionAdapter; import com.xored.glance.ui.utils.UIUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Path; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.PopupDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.texteditor.TextEditorPlugin; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; /** * @author Yuri Strot */ @SuppressWarnings("restriction") public abstract class SearchPanel implements ISearchPanel, IPreferenceConstants, IPropertyChangeListener { class SearchSynchronizer { private IDialogSettings dialogSettings; private boolean wrapInit; private boolean caseInit; private boolean wholeWordInit; private boolean regExInit; private List<String> findHistory = new ArrayList<String>(); private boolean incrementalInit; /** * Returns the dialog settings object used to share state between several find/replace dialogs. * * @return the dialog settings to be used */ private IDialogSettings getDialogSettings() { IDialogSettings settings = TextEditorPlugin.getDefault().getDialogSettings(); dialogSettings = settings.getSection("org.eclipse.ui.texteditor.FindReplaceDialog"); if (dialogSettings == null) { dialogSettings = settings.addNewSection("org.eclipse.ui.texteditor.FindReplaceDialog"); } return dialogSettings; } private String getFindString() { if (title == null) { return null; } String findString = title.getText(); findString = fixItem(findString); return findString; } /** * Initializes itself from the dialog settings with the same state as at the previous * invocation. */ private void readConfiguration() { IDialogSettings s = getDialogSettings(); wrapInit = s.get("wrap") == null || s.getBoolean("wrap"); //$NON-NLS-1$ //$NON-NLS-2$ caseInit = s.getBoolean("casesensitive"); //$NON-NLS-1$ wholeWordInit = s.getBoolean("wholeword"); //$NON-NLS-1$ incrementalInit = s.getBoolean("incremental"); //$NON-NLS-1$ regExInit = s.getBoolean("isRegEx"); //$NON-NLS-1$ String[] findHistoryInit = s.getArray("findhistory"); //$NON-NLS-1$ if (findHistoryInit != null) { findHistory.clear(); for (int i = 0; i < findHistoryInit.length; i++) { findHistory.add(findHistoryInit[i]); } } IPreferenceStore preferences = GlancePlugin.getDefault().getPreferenceStore(); preferences.setValue(SEARCH_CASE_SENSITIVE, caseInit); preferences.setValue(SEARCH_REGEXP, regExInit); preferences.setValue(SEARCH_WORD_PREFIX, wholeWordInit); } /** * Stores its current configuration in the dialog store. */ private void writeConfiguration() { String findString = getFindString(); if (findString == null) { return; } IDialogSettings s = getDialogSettings(); IPreferenceStore preferences = GlancePlugin.getDefault().getPreferenceStore(); boolean caseSensitive = preferences.getBoolean(SEARCH_CASE_SENSITIVE); boolean regExp = preferences.getBoolean(SEARCH_REGEXP); boolean wordPrefix = preferences.getBoolean(SEARCH_WORD_PREFIX); s.put("wrap", wrapInit); //$NON-NLS-1$ s.put("casesensitive", caseSensitive); //$NON-NLS-1$ s.put("wholeword", wordPrefix); //$NON-NLS-1$ s.put("incremental", incrementalInit); //$NON-NLS-1$ s.put("isRegEx", regExp); //$NON-NLS-1$ s.put("selection", ""); //$NON-NLS-1$ (original: fTarget.getSelectionText()) if (!findHistory.isEmpty() && findString.equals(findHistory.get(0))) { return; } int index = findHistory.indexOf(findString); if (index != -1) { findHistory.remove(index); } findHistory.add(0, findString); while (findHistory.size() > 8) { findHistory.remove(8); } String[] names = new String[findHistory.size()]; findHistory.toArray(names); s.put("findhistory", names); //$NON-NLS-1$ } } private class UpdateInfoThread extends ImageAnimation { private volatile boolean stop = false; public UpdateInfoThread() throws CoreException { super(getWaitImageStream(), getWaitBGColor()); } public void requestStop() { stop = true; } @Override protected boolean isTerminated() { return stop || indexState != INDEXING_STATE_IN_PROGRESS; } @Override protected void updateImage(final Image image) { updateInfo(image); } } protected static final Color GOOD_COLOR = Display.getDefault().getSystemColor(SWT.COLOR_WHITE); protected static final Color BAD_COLOR = new Color(Display.getDefault(), 255, 102, 102); private static URL getWaitImageStream() throws CoreException { URL url = FileLocator.find(GlancePlugin.getDefault().getBundle(), new Path( GlancePlugin.IMG_WAIT), null); try { url = FileLocator.resolve(url); return url; } catch (final IOException e) { throw new CoreException(GlancePlugin.createStatus("Can't find wait image", e)); } } protected Composite container; protected Combo title; private boolean titleEnabled = true; private final ListenerList listeners = new ListenerList(); private final ModifyListener modifyListener = new ModifyListener() { @Override public void modifyText(final ModifyEvent e) { textChanged(); } }; private List<String> findHistory; private boolean historyDirty = true; private ToolItem bNext; private ToolItem bPrev; private ToolItem bIndexing; private ToolBar toolBar; private SearchRule rule; private Match[] result = Match.EMPTY; private double indexState; private double indexPercent; private String taskName; private int preferredHeight; private int preferredWidth; private UpdateInfoThread updateInfoThread; private SearchSynchronizer searchSynchronizer; /** * @param parent * @param style */ public SearchPanel() { rule = new SearchRule(""); getPreferences().addPropertyChangeListener(this); searchSynchronizer = new SearchSynchronizer(); searchSynchronizer.readConfiguration(); } @Override public void addPanelListener(final ISearchPanelListener listener) { listeners.add(listener); } @Override public void allFound(final Match[] matches) { result = matches; UIUtils.asyncExec(title, new Runnable() { @Override public void run() { setBackground(result.length > 0); } }); } @Override public void clearHistory() { if (title != null && !title.isDisposed()) { title.removeModifyListener(modifyListener); try { title.removeAll(); findHistory.clear(); } finally { title.addModifyListener(modifyListener); historyDirty = false; } } } public void createContent(final Composite parent) { final Composite container = createContainer(parent); final GridLayout layout = new GridLayout(3, false); layout.verticalSpacing = 0; layout.marginHeight = 0; layout.marginWidth = 0; container.setLayout(layout); createIcon(container); createText(container, SWT.BORDER); createToolBar(container); initSize(container); } @Override public void findNext() { updateHistory(); final Object[] objects = listeners.getListeners(); for (final Object object : objects) { final ISearchPanelListener listener = (ISearchPanelListener) object; listener.findNext(); } } @Override public void findPrevious() { updateHistory(); final Object[] objects = listeners.getListeners(); for (final Object object : objects) { final ISearchPanelListener listener = (ISearchPanelListener) object; listener.findPrevious(); } } @Override public void finished() { UIUtils.asyncExec(title, new Runnable() { @Override public void run() { if (title.getText().length() == 0) { final Object[] objects = listeners.getListeners(); for (final Object object : objects) { final ISearchPanelListener listener = (ISearchPanelListener) object; listener.clearStatus(); } } } }); } @Override public void firstFound(final Match match) { UIUtils.asyncExec(title, new Runnable() { @Override public void run() { setBackground(match != null); } }); } @Override public Control getControl() { return container; } /** * @return the rule */ @Override public SearchRule getRule() { return rule; } @Override public void newTask(final String name) { this.taskName = name; indexPercent = 0; } @Override public void propertyChange(final PropertyChangeEvent event) { final String property = event.getProperty(); if (property != null && property.startsWith(SEARCH_PREFIX)) { updateRule(); } } @Override public void removePanelListener(final ISearchPanelListener listener) { listeners.remove(listener); } @Override public void selectAll() { updateHistory(); title.setSelection(new Point(0, title.getText().length())); } @Override public void setEnabled(final boolean enabled) { if (isReady()) { title.setEnabled(enabled); titleEnabled = enabled; } } @Override public void setFocus(String text) { if (isReady()) { if (text == null || text.length() == 0) { text = rule.getText(); } if (text != null && text.length() > 0) { title.setText(text); title.setSelection(new Point(0, text.length())); textChanged(); } title.forceFocus(); } } @Override public void setIndexingState(final int state) { indexState = state; if (updateInfoThread != null) { final UpdateInfoThread thread = updateInfoThread; thread.requestStop(); updateInfoThread = null; } if (state == INDEXING_STATE_IN_PROGRESS) { indexPercent = 0; try { updateInfoThread = new UpdateInfoThread(); updateInfoThread.start(); } catch (final CoreException e) { GlancePlugin.log(e.getStatus()); } } else { UIUtils.asyncExec(toolBar, new Runnable() { @Override public void run() { updateInfo(null); } }); } } public void storeSettings() { searchSynchronizer.writeConfiguration(); } @Override public void updateIndexingPercent(final double percent) { indexPercent = percent; } protected ToolItem createClose(final ToolBar bar) { final ToolItem close = new ToolItem(bar, SWT.PUSH); final ImageDescriptor image = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor( ISharedImages.IMG_TOOL_DELETE); if (image != null) { close.setImage(image.createImage()); } close.setToolTipText("Close"); //$NON-NLS-1$ close.addSelectionListener(new SelectionAdapter() { @Override public void selected(final SelectionEvent e) { closePanel(); } }); return close; } protected Composite createContainer(final Composite parent) { container = new Composite(parent, SWT.NONE); return container; } protected Label createIcon(final Composite parent) { final Label label = new Label(parent, SWT.NONE); label.setImage(GlancePlugin.getImage(GlancePlugin.IMG_SEARCH)); return label; } protected ToolItem createNextItem(final ToolBar bar) { bNext = createTool(bar, "Next", GlancePlugin.IMG_NEXT, new SelectionAdapter() { @Override public void selected(final SelectionEvent e) { findNext(); } }); return bNext; } protected ToolItem createPreviousItem(final ToolBar bar) { bPrev = createTool(bar, "Previous", GlancePlugin.IMG_PREV, new SelectionAdapter() { @Override public void selected(final SelectionEvent e) { findPrevious(); } }); return bPrev; } protected ToolItem createSettingsMenu(final ToolBar bar) { final ToolItem settings = new ToolItem(bar, SWT.PUSH); settings.setImage(JFaceResources.getImage(PopupDialog.POPUP_IMG_MENU)); settings.setDisabledImage(JFaceResources.getImage(PopupDialog.POPUP_IMG_MENU_DISABLED)); settings.setToolTipText("Settings"); //$NON-NLS-1$ settings.addSelectionListener(new SelectionAdapter() { @Override public void selected(final SelectionEvent e) { showSettings(); } }); return settings; } protected Control createText(final Composite parent, final int style) { title = new Combo(parent, style | SWT.DROP_DOWN); final String text = rule.getText(); title.setText(text); loadHistory(); if (text != null && text.length() > 0 && result.length == 0) { setBackground(false); } title.addModifyListener(modifyListener); title.addListener(SWT.KeyDown, new Listener() { @Override public void handleEvent(Event event) { GlanceEventDispatcher.INSTANCE.dispatchKeyPressed(event); } }); title.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); title.setEnabled(titleEnabled); return title; } protected ToolBar createToolBar(final Composite parent) { toolBar = new ToolBar(parent, SWT.FLAT); GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER).applyTo(toolBar); if (getPreferences().getBoolean(PANEL_DIRECTIONS)) { createNextItem(toolBar); createPreviousItem(toolBar); } createIndexing(toolBar); createSettingsMenu(toolBar); if (getPreferences().getBoolean(PANEL_CLOSE)) { createClose(toolBar); } return toolBar; } protected void fillMenu(final IMenuManager menu) { menu.add(new Separator()); newAction(menu, SEARCH_CASE_SENSITIVE, LABEL_CASE_SENSITIVE, true); final boolean regExp = newAction(menu, SEARCH_REGEXP, LABEL_REGEXP, true).isChecked(); newAction(menu, SEARCH_CAMEL_CASE, LABEL_CAMEL_CASE, !regExp); newAction(menu, SEARCH_WORD_PREFIX, LABEL_WORD_PREFIX, !regExp); menu.add(new Separator()); menu.add(new Action(LABEL_CLEAR_HISTORY) { @Override public void run() { clearHistory(); } }); // menu.add(new Separator()); // menu.add(new Action("Preferences...") { // @Override // public void run() { // PreferencesUtil.createPreferenceDialogOn(container.getShell(), // PREFERENCE_PAGE_ID, null, null).open(); // } // }); } protected void fireClose() { updateHistory(); saveHistory(); final Object[] objects = listeners.getListeners(); for (final Object object : objects) { final ISearchPanelListener listener = (ISearchPanelListener) object; listener.close(); } getPreferences().removePropertyChangeListener(this); if (updateInfoThread != null) { updateInfoThread.requestStop(); } } protected void fireIndexCanceled() { final Object[] objects = listeners.getListeners(); for (final Object object : objects) { final ISearchPanelListener listener = (ISearchPanelListener) object; listener.indexCanceled(); } } /** * @return the preferedWidth */ protected int getPreferedWidth() { return preferredWidth; } /** * @return the preferredHeight */ protected int getPreferredHeight() { return preferredHeight; } protected int getTextWidth(final Control control, final int width) { final GC gc = new GC(control); try { gc.setFont(title.getFont()); return gc.getFontMetrics().getAverageCharWidth() * width; } finally { gc.dispose(); } } protected void setBackground(final boolean found) { title.setBackground(found ? GOOD_COLOR : BAD_COLOR); } protected void showSettings() { final MenuManager manager = new MenuManager(); fillMenu(manager); final Menu menu = manager.createContextMenu(getControl()); final Point location = getControl().getDisplay().getCursorLocation(); menu.setLocation(location); menu.setVisible(true); } protected void textChanged() { final String text = title.getText(); final boolean empty = text.length() == 0; if (empty) { textEmpty(); } if (bNext != null && !bNext.isDisposed()) { bNext.setEnabled(!empty); } if (bPrev != null && !bPrev.isDisposed()) { bPrev.setEnabled(!empty); } updateRule(); } protected void textEmpty() { setBackground(true); } protected void updateRule() { if (title != null) { rule = new SearchRule(title.getText()); fireRuleChanged(rule); } } protected void updateSelection() { fireSelectionChanged(); } private void createIndexing(final ToolBar bar) { bIndexing = new ToolItem(bar, SWT.CHECK); bIndexing.setDisabledImage(GlancePlugin.getImage(GlancePlugin.IMG_INDEXING_FINISHED)); bIndexing.addSelectionListener(new SelectionAdapter() { @Override public void selected(final SelectionEvent e) { if (indexState == INDEXING_STATE_INITIAL) { SearchManager.getIntance().index(); } else if (indexState != INDEXING_STATE_FINISHED) { fireIndexCanceled(); } } }); } private ToolItem createTool(final ToolBar bar, final String tip, final String image, final SelectionListener listener) { final ToolItem item = new ToolItem(bar, SWT.PUSH); item.setToolTipText(tip); item.setImage(GlancePlugin.getImage(image)); item.addSelectionListener(listener); return item; } private void fireRuleChanged(final SearchRule rule) { historyDirty = true; final Object[] objects = listeners.getListeners(); for (final Object object : objects) { final ISearchPanelListener listener = (ISearchPanelListener) object; listener.ruleChanged(rule); } } private void fireSelectionChanged() { final Object[] objects = listeners.getListeners(); for (final Object object : objects) { final ISearchPanelListener listener = (ISearchPanelListener) object; listener.sourceSelectionChanged(); } } private String fixItem(final String item) { if (item.length() == 0) { return null; } final int index = item.indexOf("\n"); if (index == 0) { return null; } else if (index > 0) { return item.substring(0, index); } else { return item; } } private IPreferenceStore getPreferences() { return GlancePlugin.getDefault().getPreferenceStore(); } private Color getWaitBGColor() { final Display display = PlatformUI.getWorkbench().getDisplay(); final Color[] color = new Color[1]; display.syncExec(new Runnable() { @Override public void run() { color[0] = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); } }); return color[0]; } private void initSize(final Composite composite) { final Point size = composite.computeSize(SWT.DEFAULT, SWT.DEFAULT); preferredWidth = size.x; preferredHeight = size.y; preferredWidth -= title.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; final int widthInChars = getPreferences().getInt(PANEL_TEXT_SIZE); preferredWidth += getTextWidth(title, widthInChars) + 15; } private boolean isReady() { return title != null && !title.isDisposed(); } private void loadHistory() { findHistory = new ArrayList<String>(); final String content = getPreferences().getString(HISTORY); final String[] items = content.split("\n"); for (final String item : items) { findHistory.add(item); title.add(item); } } private CheckAction newAction(final IMenuManager menu, final String name, final String label, final boolean enable) { return newAction(menu, name, label, enable, null); } private CheckAction newAction(final IMenuManager menu, final String name, final String label, final boolean enable, final String path) { final CheckAction action = new CheckAction(name, label, this); if (path != null) { action.setImageDescriptor(GlancePlugin.getImageDescriptor(path)); } action.setEnabled(enable); menu.add(action); return action; } private void saveHistory() { final StringBuffer buffer = new StringBuffer(); for (int i = 0; i < findHistory.size() && i < 8; i++) { final String item = findHistory.get(i); if (i > 0) { buffer.append("\n"); } buffer.append(item); } getPreferences().putValue(HISTORY, buffer.toString()); } /** * Updates history. */ private void updateHistory() { if (title != null && !title.isDisposed() && historyDirty) { title.removeModifyListener(modifyListener); try { String findString = title.getText(); final Point sel = title.getSelection(); findString = fixItem(findString); if (findString != null) { findHistory.remove(findString); findHistory.add(0, findString); title.removeAll(); for (final String string : findHistory) { title.add(string); } title.setText(findString); title.setSelection(sel); storeSettings(); } } finally { title.addModifyListener(modifyListener); historyDirty = false; } } } private void updateInfo(final Image image) { if (bIndexing == null || bIndexing.isDisposed()) { return; } if (indexState == INDEXING_STATE_DISABLE) { bIndexing.setToolTipText("Index component"); bIndexing.setSelection(false); bIndexing.setEnabled(false); if (bIndexing.getImage() == null) { bIndexing.setImage(GlancePlugin.getImage(GlancePlugin.IMG_START_INDEXING)); } } else if (indexState == INDEXING_STATE_INITIAL) { bIndexing.setToolTipText("Index component"); bIndexing.setSelection(false); bIndexing.setImage(GlancePlugin.getImage(GlancePlugin.IMG_START_INDEXING)); bIndexing.setEnabled(true); } else if (indexState == INDEXING_STATE_FINISHED) { bIndexing.setToolTipText("Index finished"); bIndexing.setSelection(false); bIndexing.setEnabled(false); } else { final StringBuffer buffer = new StringBuffer(); bIndexing.setSelection(true); bIndexing.setImage(image); if (taskName != null && taskName.length() > 0) { buffer.append(taskName); buffer.append(": "); } buffer.append((int) (indexPercent * 100)); buffer.append("%. Stop indexing."); bIndexing.setToolTipText(buffer.toString()); } } }