/******************************************************************************* * Copyright (c) 2000, 2016 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 API and implementation *******************************************************************************/ package org.eclipse.help.ui.internal.views; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Observable; import java.util.Observer; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.IJobChangeListener; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.help.HelpSystem; import org.eclipse.help.internal.base.BaseHelpSystem; import org.eclipse.help.internal.search.federated.FederatedSearchEntry; import org.eclipse.help.internal.search.federated.FederatedSearchJob; import org.eclipse.help.internal.search.federated.LocalHelp; import org.eclipse.help.search.ISearchEngineResult; import org.eclipse.help.search.ISearchEngineResultCollector; import org.eclipse.help.search.ISearchScope; import org.eclipse.help.ui.internal.HelpUIResources; import org.eclipse.help.ui.internal.IHelpUIConstants; import org.eclipse.help.ui.internal.Messages; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.preference.PreferenceDialog; import org.eclipse.jface.preference.PreferenceManager; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IMemento; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.forms.AbstractFormPart; import org.eclipse.ui.forms.IFormColors; import org.eclipse.ui.forms.events.HyperlinkAdapter; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.widgets.FormText; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.Hyperlink; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.forms.widgets.TableWrapData; import org.eclipse.ui.forms.widgets.TableWrapLayout; public class SearchPart extends AbstractFormPart implements IHelpPart, IHelpUIConstants { public class SearchScopeObserver implements Observer { @Override public void update(Observable arg0, Object arg1) { ScopeSet set = scopeSetManager.getActiveSet(); scopeSetLink.setText(set.getName()); scopeSetManager.setActiveSet(set); scopeSection.layout(); if (parent != null) parent.reflow(); } } private ReusableHelpPart parent; @SuppressWarnings("rawtypes") protected static java.util.List previousSearchQueryData = new java.util.ArrayList(20); private static final String HREF_SEARCH_HELP = "/org.eclipse.platform.doc.user/tasks/help_search.htm"; //$NON-NLS-1$ private static boolean SEARCH_HELP_AVAILABLE = false; static { InputStream is = HelpSystem.getHelpContent(HREF_SEARCH_HELP); if (is != null) { // don't leak the input stream try { is.close(); SEARCH_HELP_AVAILABLE = true; } catch (IOException e) { // ignore } } } private String id; private Composite container; private Composite filteringGroup; private FormText searchWordText; private ComboPart searchWordCombo; private Section scopeSection; private Button goButton; private Button shellDefaultButton; private Hyperlink scopeSetLink; private Hyperlink advancedLink; private Observer engineObserver; private ScopeSetManager scopeSetManager; private static final int COMBO_HISTORY_SIZE = 10; private JobListener jobListener; private boolean searchPending; private SearchScopeObserver scopeObserver; private Section alternateQuerySection; private FormToolkit toolkit; private Composite alternateQueryComposite; private class JobListener implements IJobChangeListener, Runnable { private boolean searchInProgress = false; @Override public void aboutToRun(IJobChangeEvent event) { } @Override public void awake(IJobChangeEvent event) { } @Override public void done(IJobChangeEvent event) { if (event.getJob().belongsTo(FederatedSearchJob.FAMILY)) { Job[] searchJobs = Job.getJobManager().find(FederatedSearchJob.FAMILY); if (searchJobs.length == 0) { // search finished searchInProgress = false; if (container.isDisposed()) return; container.getDisplay().asyncExec(this); SearchResultsPart results = (SearchResultsPart) parent .findPart(IHelpUIConstants.HV_FSEARCH_RESULT); results.completed(); } } } @Override public void running(IJobChangeEvent event) { } @Override public void scheduled(IJobChangeEvent event) { if (!searchInProgress && event.getJob().belongsTo(FederatedSearchJob.FAMILY)) { searchInProgress = true; container.getDisplay().asyncExec(this); } } @Override public void sleeping(IJobChangeEvent event) { } @Override public void run() { searchWordCombo.getControl().setEnabled(!searchInProgress); if (!searchInProgress) goButton.setEnabled(true); if (searchInProgress) goButton.setText(Messages.SearchPart_stop); else goButton.setText(Messages.SearchPart_go); parent.getForm().getForm().setBusy(searchInProgress); goButton.getParent().layout(); } } /** * @param parent * @param toolkit * @param style */ public SearchPart(final Composite parent, FormToolkit toolkit) { this.toolkit = toolkit; container = toolkit.createComposite(parent); scopeSetManager = ScopeState.getInstance().getScopeSetManager(); TableWrapLayout layout = new TableWrapLayout(); layout.numColumns = 2; container.setLayout(layout); TableWrapData td; //createSearchExpressionDescription(parent, toolkit); createSearchExpressionSection(toolkit); // Pattern combo searchWordCombo = new ComboPart(container, toolkit, toolkit.getBorderStyle()); updateSearchCombo(null); td = new TableWrapData(TableWrapData.FILL_GRAB); td.maxWidth = 100; td.valign = TableWrapData.MIDDLE; searchWordCombo.getControl().setLayoutData(td); goButton = toolkit.createButton(container, Messages.SearchPart_go, SWT.PUSH); goButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { handleButtonPressed(); } }); goButton.setEnabled(false); searchWordCombo.addModifyListener(e -> goButton.setEnabled(searchWordCombo.getText().length() > 0)); searchWordCombo.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.character == '\r') { if (goButton.isEnabled()) { doSearch(searchWordCombo.getText()); } } } }); searchWordCombo.getControl().addListener(SWT.FocusIn, event -> { shellDefaultButton = null; Shell shell = searchWordCombo.getControl().getShell(); Button button = shell.getDefaultButton(); if (button != null) { shellDefaultButton = button; shell.setDefaultButton(goButton); } }); searchWordCombo.getControl().addListener(SWT.FocusOut, event -> { if (shellDefaultButton != null) { Shell shell = searchWordCombo.getControl().getShell(); shell.setDefaultButton(shellDefaultButton); shellDefaultButton = null; } }); createScopeSection(toolkit); // createAlternateQueriesSection(toolkit); toolkit.paintBordersFor(container); jobListener = new JobListener(); Job.getJobManager().addJobChangeListener(jobListener); } private void createScopeSection(FormToolkit toolkit) { TableWrapData td; scopeSection = toolkit.createSection(container, Section.TWISTIE | Section.COMPACT | Section.LEFT_TEXT_CLIENT_ALIGNMENT); scopeSection.setText(Messages.limit_to); td = new TableWrapData(); td.colspan = 2; td.align = TableWrapData.FILL; scopeSection.setLayoutData(td); filteringGroup = toolkit.createComposite(scopeSection); scopeSection.setClient(filteringGroup); TableWrapLayout flayout = new TableWrapLayout(); flayout.numColumns = 2; filteringGroup.setLayout(flayout); createScopeSet(scopeSection, toolkit); toolkit.paintBordersFor(filteringGroup); scopeObserver = new SearchScopeObserver(); scopeSetManager.addObserver(scopeObserver); } private void createSearchExpressionSection(FormToolkit toolkit) { TableWrapData td; Section searchExpressionSection = toolkit.createSection(container, Section.TWISTIE | Section.COMPACT | Section.LEFT_TEXT_CLIENT_ALIGNMENT); searchExpressionSection.setText(Messages.expression); td = new TableWrapData(); td.colspan = 2; td.align = TableWrapData.FILL; searchExpressionSection.setLayoutData(td); Composite detailGroup = toolkit.createComposite(searchExpressionSection); searchExpressionSection.setClient(detailGroup); TableWrapLayout dgLayout = new TableWrapLayout(); detailGroup.setLayout(dgLayout); //Label syntaxLabel = toolkit.createLabel(detailGroup, Messages.expression_label, SWT.WRAP); searchWordText = toolkit.createFormText(detailGroup, false); searchWordText.setImage(IHelpUIConstants.IMAGE_HELP, HelpUIResources .getImage(IHelpUIConstants.IMAGE_HELP)); searchWordText.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { SearchPart.this.parent.showURL(HREF_SEARCH_HELP, true); } }); updateSearchWordText(); toolkit.paintBordersFor(detailGroup); } private void createAlternateQueriesSection(FormToolkit toolkit){ TableWrapData td = new TableWrapData(); td.colspan = 2; td.align = TableWrapData.FILL; container.setMenu(new Menu(container)); alternateQuerySection = toolkit.createSection(container, Section.TWISTIE | Section.COMPACT | Section.LEFT_TEXT_CLIENT_ALIGNMENT); alternateQuerySection.setLayoutData(td); alternateQuerySection.setText(Messages.AlternateQueries); alternateQueryComposite = toolkit.createComposite(alternateQuerySection); alternateQuerySection.setClient(alternateQueryComposite); TableWrapLayout flayout = new TableWrapLayout(); flayout.numColumns = 1; alternateQueryComposite.setLayout(flayout); alternateQuerySection.setExpanded(true); // alternateQuerySection.setVisible(false); } private void createAdvancedLink(Composite parent, FormToolkit toolkit) { advancedLink = toolkit.createHyperlink(parent, Messages.FederatedSearchPart_advanced, SWT.NULL); advancedLink.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { doAdvanced(); } }); TableWrapData td = new TableWrapData(); td.colspan = 2; advancedLink.setLayoutData(td); } private void createScopeSet(Section section, FormToolkit toolkit) { scopeSetLink = toolkit.createHyperlink(section, null, SWT.WRAP); scopeSetLink.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { doChangeScopeSet(); } }); scopeSetLink.setToolTipText(Messages.FederatedSearchPart_changeScopeSet); section.setTextClient(scopeSetLink); ScopeSet active = scopeSetManager.getActiveSet(); setActiveScopeSet(active); } private void updateSearchWordText() { StringBuffer buff = new StringBuffer(); buff.append("<form>"); //$NON-NLS-1$ buff.append("<p>"); //$NON-NLS-1$ buff.append(Messages.expression_label); // Only add the link if available if (SEARCH_HELP_AVAILABLE) { buff.append("</p><p>"); //$NON-NLS-1$ buff.append("<img href=\""); //$NON-NLS-1$ buff.append(IHelpUIConstants.IMAGE_HELP); buff.append("\"/> "); //$NON-NLS-1$ buff.append("<a href=\""); //$NON-NLS-1$ buff.append(HREF_SEARCH_HELP); buff.append("\">"); //$NON-NLS-1$ buff.append(Messages.SearchPart_learnMore); buff.append("</a>"); //$NON-NLS-1$ } buff.append("</p>"); //$NON-NLS-1$ buff.append("</form>"); //$NON-NLS-1$ searchWordText.setText(buff.toString(), true, false); } private void setActiveScopeSet(ScopeSet set) { scopeSetLink.setText(set.getName()); scopeSetManager.setActiveSet(set); updateMasters(set); scopeSection.layout(); if (parent != null) parent.reflow(); } private void updateMasters(ScopeSet set) { Control[] children = ((Composite) scopeSection.getClient()).getChildren(); for (int i = 0; i < children.length; i++) { Control child = children[i]; if (child instanceof Button) { Button master = (Button) child; Object data = master.getData(); if (data != null && data instanceof EngineDescriptor) { EngineDescriptor ed = (EngineDescriptor) data; master.setSelection(set.getEngineEnabled(ed)); } } } } private void loadEngines(final Composite container, final FormToolkit toolkit) { EngineDescriptorManager descManager = parent.getEngineManager(); EngineDescriptor[] descriptors = descManager.getDescriptors(); for (int i = 0; i < descriptors.length; i++) { EngineDescriptor desc = descriptors[i]; loadEngine(desc, container, toolkit); } engineObserver = new Observer() { @Override public void update(Observable o, Object arg) { EngineDescriptorManager.DescriptorEvent event = (EngineDescriptorManager.DescriptorEvent) arg; int kind = event.getKind(); EngineDescriptor desc = event.getDescriptor(); if (kind == IHelpUIConstants.ADD) { advancedLink.dispose(); loadEngine(desc, container, toolkit); createAdvancedLink(container, toolkit); parent.reflow(); } else if (kind == IHelpUIConstants.REMOVE) { removeEngine(desc); } else { updateEngine(desc); } } }; descManager.addObserver(engineObserver); updateMasters(scopeSetManager.getActiveSet()); } private EngineDescriptor loadEngine(final EngineDescriptor edesc, Composite container, FormToolkit toolkit) { Label ilabel = toolkit.createLabel(container, null); ilabel.setImage(edesc.getIconImage()); ilabel.setData(edesc); final Button master = toolkit.createButton(container, edesc.getLabel(), SWT.CHECK); master.setData(edesc); master.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { scopeSetManager.getActiveSet().setEngineEnabled(edesc, master.getSelection()); } }); String desc = edesc.getDescription(); if (desc != null) { Label spacer = toolkit.createLabel(container, null); spacer.setData(edesc); Label dlabel = toolkit.createLabel(container, desc, SWT.WRAP); dlabel.setForeground(toolkit.getColors().getColor(IFormColors.TITLE)); dlabel.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); dlabel.setMenu(container.getMenu()); dlabel.setData(edesc); } return edesc; } private void removeEngine(EngineDescriptor desc) { boolean reflowNeeded = false; Control[] children = ((Composite) scopeSection.getClient()).getChildren(); for (int i = 0; i < children.length; i++) { Control child = children[i]; EngineDescriptor ed = (EngineDescriptor) child.getData(); if (ed == desc) { child.setMenu(null); child.dispose(); reflowNeeded = true; } } if (reflowNeeded) parent.reflow(); } private void updateEngine(EngineDescriptor desc) { Control[] children = ((Composite) scopeSection.getClient()).getChildren(); boolean reflowNeeded = false; for (int i = 0; i < children.length; i++) { Control child = children[i]; EngineDescriptor ed = (EngineDescriptor) child.getData(); if (ed == desc) { Button b = (Button) children[i + 1]; b.setText(desc.getLabel()); Label d = (Label) children[i + 3]; d.setText(desc.getDescription()); d.getParent().layout(); reflowNeeded = true; break; } } if (reflowNeeded) parent.reflow(); } public void startSearch(String text) { searchWordCombo.setText(text); doSearch(text); } private void storeSearchHistory(String expression) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=95479 HistoryScopeSet sset = scopeSetManager.findSearchSet(expression); if (sset == null) { sset = new HistoryScopeSet(expression); scopeSetManager.add(sset); } ScopeSet activeSet = scopeSetManager.getActiveSet(); sset.copyFrom(activeSet); sset.save(); updateSearchCombo(sset); searchWordCombo.setText(expression); } private void updateSearchCombo(HistoryScopeSet current) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=95479 ScopeSet[] sets = scopeSetManager.getScopeSets(true); ArrayList<String> items = new ArrayList<>(); ArrayList<HistoryScopeSet> toDelete = new ArrayList<>(); // if (current!=null) // items.add(current.getExpression()); for (int i = sets.length - 1; i >= 0; i--) { HistoryScopeSet sset = (HistoryScopeSet) sets[i]; if (current != null && sset == current) continue; if (sets.length - i > COMBO_HISTORY_SIZE) toDelete.add(sset); items.add(sset.getExpression()); } for (int i = 0; i < toDelete.size(); i++) { HistoryScopeSet sset = toDelete.get(i); scopeSetManager.remove(sset); } if (items.size() > 0) searchWordCombo.setItems(items.toArray(new String[items.size()])); } private void handleButtonPressed() { if (searchWordCombo.getControl().isEnabled()) doSearch(searchWordCombo.getText()); else { goButton.setEnabled(false); stop(); } } private void doSearch(String text) { doSearch(text, false); } private void doSearch(String text, boolean fromHistory) { ScopeSet set = scopeSetManager.getActiveSet(); if (!fromHistory && set instanceof HistoryScopeSet) { String setExpression = ((HistoryScopeSet) set).getExpression(); if (setExpression.equals(text)) fromHistory = true; } if (!fromHistory) { storeSearchHistory(text); boolean switchedSet = scopeSetManager.restoreLastExplicitSet(); set = scopeSetManager.getActiveSet(); if (switchedSet) setActiveScopeSet(set); } ArrayList<FederatedSearchEntry> entries = new ArrayList<>(); final SearchResultsPart results = (SearchResultsPart) parent .findPart(IHelpUIConstants.HV_FSEARCH_RESULT); ArrayList<EngineDescriptor> eds = new ArrayList<>(); EngineDescriptor[] engineDescriptors = parent.getEngineManager().getDescriptors(); for (int i = 0; i < engineDescriptors.length; i++) { final EngineDescriptor ed = engineDescriptors[i]; if (set.getEngineEnabled(ed) && ed.getEngine() != null) { ISearchScope scope = ed.createSearchScope(set.getPreferenceStore()); FederatedSearchEntry entry = new FederatedSearchEntry(ed.getId(), ed.getLabel(), scope, ed .getEngine(), new ISearchEngineResultCollector() { @Override public void accept(ISearchEngineResult searchResult) { results.add(ed, searchResult); } @Override public void accept(ISearchEngineResult[] searchResults) { results.add(ed, searchResults); if (ed.getEngine() instanceof LocalHelp) { container.getDisplay().asyncExec(new Thread(){ @Override public void run(){ if (alternateQuerySection!=null) { alternateQuerySection.dispose(); alternateQuerySection = null; } List<String> alts = ((LocalHelp) ed.getEngine()).getAlternates(); if (!alts.isEmpty()) { createAlternateQueriesSection(toolkit); for (int b=0;b<alts.size();b++) { Hyperlink link = toolkit.createHyperlink( alternateQueryComposite, alts.get(b), SWT.NONE); link.addHyperlinkListener(new HyperlinkAdapter(){ @Override public void linkActivated(HyperlinkEvent e) { searchWordCombo.setText(((Hyperlink)e.getSource()).getText()); doSearch(((Hyperlink)e.getSource()).getText()); } }); } } } }); } } @Override public void error(IStatus status) { results.error(ed, status); } }); entries.add(entry); eds.add(ed); } } if (entries.size() == 0) return; FederatedSearchEntry[] array = entries.toArray(new FederatedSearchEntry[entries.size()]); if (scopeSection.isExpanded()) { scopeSection.setExpanded(false); parent.reflow(); } results.clearResults(); results.startNewSearch(text, eds); BaseHelpSystem.getSearchManager().search(text, array); } private void doAdvanced() { ScopeSet set = scopeSetManager.getActiveSet(); PreferenceManager manager = new ScopePreferenceManager(parent.getEngineManager(), set); PreferenceDialog dialog = new ScopePreferenceDialog(container.getShell(), manager, parent .getEngineManager(), set.isEditable()); dialog.setPreferenceStore(set.getPreferenceStore()); dialog.create(); dialog.getShell().setText(NLS.bind(Messages.ScopePreferenceDialog_wtitle, set.getName())); dialog.open(); updateMasters(set); } private void doChangeScopeSet() { ScopeSetDialog dialog = new ScopeSetDialog(container.getShell(), scopeSetManager, parent .getEngineManager(), false); dialog.setInput(scopeSetManager); dialog.create(); dialog.getShell().setText(Messages.ScopeSetDialog_wtitle); if (dialog.open() == ScopeSetDialog.OK) { ScopeSet set = dialog.getActiveSet(); if (set != null) { setActiveScopeSet(set); } scopeSetManager.save(); scopeSetManager.notifyObservers(); } } @Override public void dispose() { ScopeSet activeSet = scopeSetManager.getActiveSet(); if (activeSet != null) activeSet.save(); if (engineObserver != null) { parent.getEngineManager().deleteObserver(engineObserver); engineObserver = null; } if (scopeObserver != null) { ScopeState.getInstance().getScopeSetManager().deleteObserver(scopeObserver); } Job.getJobManager().removeJobChangeListener(jobListener); stop(); super.dispose(); } @Override public Control getControl() { return container; } @Override public void init(ReusableHelpPart parent, String id, IMemento memento) { this.parent = parent; this.id = id; loadEngines(filteringGroup, parent.getForm().getToolkit()); createAdvancedLink(filteringGroup, parent.getForm().getToolkit()); parent.hookFormText(searchWordText); if (memento != null) restorePart(memento); } private void restorePart(IMemento memento) { String setName = memento.getString("activeSet"); //$NON-NLS-1$ if (setName != null) { ScopeSet sset = scopeSetManager.findSet(setName); if (sset != null) scopeSetManager.setActiveSet(sset); } String expression = memento.getString("expression"); //$NON-NLS-1$ if (expression != null && expression.length() > 0) { searchWordCombo.setText(expression); searchPending = true; markStale(); } } @Override public void refresh() { super.refresh(); if (searchPending) { searchPending = false; doSearch(searchWordCombo.getText()); } } @Override public String getId() { return id; } @Override public void setVisible(boolean visible) { getControl().setVisible(visible); } @Override public boolean fillContextMenu(IMenuManager manager) { return parent.fillFormContextMenu(searchWordText, manager); } @Override public boolean hasFocusControl(Control control) { return control == searchWordText || control == searchWordCombo.getControl() || scopeSection.getClient() == control; } @Override public void setFocus() { searchWordCombo.getControl().setFocus(); } @Override public IAction getGlobalAction(String id) { if (id.equals(ActionFactory.COPY.getId())) return parent.getCopyAction(); return null; } @Override public void stop() { SearchResultsPart results = (SearchResultsPart) parent.findPart(IHelpUIConstants.HV_FSEARCH_RESULT); if (results != null) { results.canceling(); } Job.getJobManager().cancel(FederatedSearchJob.FAMILY); } @Override public void toggleRoleFilter() { } @Override public void refilter() { } @Override public void saveState(IMemento memento) { ScopeSet sset = scopeSetManager.getActiveSet(); if (sset != null) memento.putString("activeSet", sset.getName()); //$NON-NLS-1$ memento.putString("expression", searchWordCombo.getText()); //$NON-NLS-1$ } }