| /*=============================================================================# |
| # Copyright (c) 2009, 2021 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.ecommons.ui.mpbv; |
| |
| import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert; |
| import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullElse; |
| |
| import java.net.URI; |
| |
| import org.eclipse.core.commands.AbstractHandler; |
| import org.eclipse.core.commands.ExecutionEvent; |
| import org.eclipse.core.commands.ExecutionException; |
| import org.eclipse.core.commands.IHandler2; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.jface.action.ContributionItem; |
| import org.eclipse.jface.action.Separator; |
| import org.eclipse.jface.action.ToolBarManager; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.resource.ImageRegistry; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.browser.Browser; |
| import org.eclipse.swt.browser.CloseWindowListener; |
| import org.eclipse.swt.browser.LocationEvent; |
| import org.eclipse.swt.browser.LocationListener; |
| import org.eclipse.swt.browser.OpenWindowListener; |
| import org.eclipse.swt.browser.ProgressEvent; |
| import org.eclipse.swt.browser.ProgressListener; |
| import org.eclipse.swt.browser.StatusTextEvent; |
| import org.eclipse.swt.browser.StatusTextListener; |
| import org.eclipse.swt.browser.TitleEvent; |
| import org.eclipse.swt.browser.TitleListener; |
| import org.eclipse.swt.browser.WindowEvent; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| 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.layout.GridData; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.ToolBar; |
| import org.eclipse.swt.widgets.ToolItem; |
| import org.eclipse.ui.IWorkbenchCommandConstants; |
| import org.eclipse.ui.handlers.IHandlerService; |
| import org.eclipse.ui.menus.CommandContributionItemParameter; |
| import org.eclipse.ui.services.IServiceLocator; |
| |
| import org.eclipse.statet.jcommons.lang.NonNull; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| |
| import org.eclipse.statet.ecommons.commands.core.HandlerCollection; |
| import org.eclipse.statet.ecommons.ui.SharedMessages; |
| import org.eclipse.statet.ecommons.ui.SharedUIResources; |
| import org.eclipse.statet.ecommons.ui.actions.HandlerContributionItem; |
| import org.eclipse.statet.ecommons.ui.actions.SearchContributionItem; |
| import org.eclipse.statet.ecommons.ui.actions.SimpleContributionItem; |
| import org.eclipse.statet.ecommons.ui.components.StatusInfo; |
| import org.eclipse.statet.ecommons.ui.util.BrowserUtils; |
| import org.eclipse.statet.ecommons.ui.util.LayoutUtils; |
| import org.eclipse.statet.ecommons.ui.util.StatusLineMessageManager; |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| import org.eclipse.statet.ecommons.ui.workbench.ContextHandlers; |
| |
| |
| @NonNullByDefault |
| public class PageBookBrowserPage extends ManagedPage<ManagedPageBookView<BrowserSession>> |
| implements ProgressListener, LocationListener, TitleListener, StatusTextListener, |
| OpenWindowListener, CloseWindowListener { |
| |
| |
| protected static void setIcon(final BrowserSession session, final ImageDescriptor imageDescriptor) { |
| session.setImageDescriptor(imageDescriptor); |
| } |
| |
| |
| private static final String STATIC_CONTENT_SCHEME= "estatic"; //$NON-NLS-1$ |
| private static final String STATIC_CONTENT_URL_PREFIX= STATIC_CONTENT_SCHEME + ":///"; //$NON-NLS-1$ |
| |
| |
| protected static final String ABOUT_BLANK_URI_STRING= "about:blank"; //$NON-NLS-1$ |
| protected static final URI ABOUT_BLANK_URI= URI.create(ABOUT_BLANK_URI_STRING); |
| |
| |
| protected class SearchBar implements DisposeListener { |
| |
| |
| private ToolBarManager toolBarManager; |
| private ToolBar toolBar; |
| private SearchContributionItem searchTextItem; |
| |
| private boolean searchCaseSensitive; |
| |
| |
| public SearchBar(final Composite parent) { |
| this.toolBarManager= new ToolBarManager(SWT.FLAT); |
| this.toolBar= nonNullAssert(this.toolBarManager.createControl(parent)); |
| this.toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); |
| this.toolBar.addDisposeListener(this); |
| |
| this.toolBarManager.add(new ContributionItem() { |
| @Override |
| public void fill(final ToolBar parent, final int index) { |
| final ToolItem item= new ToolItem(parent, SWT.PUSH); |
| item.setImage(SharedUIResources.getImages().get(SharedUIResources.LOCTOOL_CLOSETRAY_IMAGE_ID)); |
| item.setHotImage(SharedUIResources.getImages().get(SharedUIResources.LOCTOOL_CLOSETRAY_H_IMAGE_ID)); |
| item.setToolTipText("Close Search"); |
| item.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(final SelectionEvent e) { |
| hide(); |
| } |
| }); |
| } |
| }); |
| |
| this.toolBarManager.add(new Separator()); |
| |
| this.searchTextItem= new SearchContributionItem("search.text", SWT.NONE) { //$NON-NLS-1$ |
| @Override |
| public void fill(final ToolBar parent, final int index) { |
| super.fill(parent, index); |
| getSearchText().getTextControl().addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyPressed(final KeyEvent e) { |
| if (e.keyCode == SWT.ESC && e.doit) { |
| hide(); |
| e.doit= false; |
| return; |
| } |
| } |
| }); |
| } |
| @Override |
| protected void search() { |
| PageBookBrowserPage.this.search(true); |
| } |
| }; |
| this.searchTextItem.setToolTip("Find Text"); |
| this.searchTextItem.setSizeControl(parent); |
| this.toolBarManager.add(this.searchTextItem); |
| |
| final IServiceLocator serviceLocator= getSite(); |
| final ImageRegistry ecommonsImages= SharedUIResources.getImages(); |
| final HandlerCollection pageHandlers= getHandlers(); |
| this.toolBarManager.add(new HandlerContributionItem( |
| new CommandContributionItemParameter(serviceLocator, |
| "Search.Next", SharedUIResources.FIND_NEXT_COMMAND_ID, null, //$NON-NLS-1$ |
| ecommonsImages.getDescriptor(SharedUIResources.LOCTOOL_DOWN_IMAGE_ID), null, ecommonsImages.getDescriptor(SharedUIResources.LOCTOOL_DOWN_H_IMAGE_ID), |
| null, null, SharedMessages.FindNextMatchAction_tooltip, |
| SWT.PUSH, null, false ), |
| pageHandlers )); |
| this.toolBarManager.add(new HandlerContributionItem( |
| new CommandContributionItemParameter(serviceLocator, |
| "Search.Previous", SharedUIResources.FIND_PREVIOUS_COMMAND_ID, null, //$NON-NLS-1$ |
| ecommonsImages.getDescriptor(SharedUIResources.LOCTOOL_UP_IMAGE_ID), null, ecommonsImages.getDescriptor(SharedUIResources.LOCTOOL_UP_H_IMAGE_ID), |
| null, null, SharedMessages.FindPreviousMatchAction_tooltip, |
| SWT.PUSH, null, false ), |
| pageHandlers )); |
| |
| this.toolBarManager.add(new Separator()); |
| |
| final SimpleContributionItem caseItem= new SimpleContributionItem( |
| new CommandContributionItemParameter(null, |
| null, null, null, |
| ecommonsImages.getDescriptor(SharedUIResources.LOCTOOL_CASESENSITIVE_IMAGE_ID), null, null, |
| null, null, "Case Sensitive", |
| SimpleContributionItem.STYLE_CHECK, null, false )) { |
| @Override |
| protected void execute() throws ExecutionException { |
| SearchBar.this.searchCaseSensitive= !SearchBar.this.searchCaseSensitive; |
| setChecked(SearchBar.this.searchCaseSensitive); |
| } |
| }; |
| caseItem.setChecked(this.searchCaseSensitive); |
| this.toolBarManager.add(caseItem); |
| |
| this.toolBarManager.update(true); |
| } |
| |
| @Override |
| public void widgetDisposed(final DisposeEvent e) { |
| if (this.toolBar != null) { |
| this.toolBarManager.dispose(); |
| this.toolBarManager= null; |
| |
| this.toolBar= null; |
| } |
| } |
| |
| |
| @SuppressWarnings("null") |
| public void show() { |
| final GridData gd= (GridData)this.toolBar.getLayoutData(); |
| gd.exclude= false; |
| this.toolBar.getParent().layout(true, true); |
| this.searchTextItem.getSearchText().setFocus(); |
| } |
| |
| @SuppressWarnings("null") |
| public void hide() { |
| setFocusToBrowser(); |
| final GridData gd= (GridData)this.toolBar.getLayoutData(); |
| gd.exclude= true; |
| this.toolBar.getParent().layout(new @NonNull Control[] { this.toolBar }); |
| } |
| |
| public String getText() { |
| return this.searchTextItem.getText(); |
| } |
| |
| public boolean isCaseSensitiveEnabled() { |
| return this.searchCaseSensitive; |
| } |
| |
| } |
| |
| |
| private final BrowserSession session; |
| |
| private Composite composite; |
| |
| private Browser browser; |
| |
| private @Nullable SearchBar searchBar; |
| |
| private @Nullable IStatus browserStatus; |
| |
| private int progressTotal; |
| private int progressWorked; |
| |
| private @Nullable StatusLineMessageManager statusManager; |
| |
| private @Nullable BrowserAddressBar addressBar; |
| |
| |
| public PageBookBrowserPage(final ManagedPageBookView view, final BrowserSession session) { |
| super(view); |
| this.session= session; |
| } |
| |
| |
| public BrowserSession getSession() { |
| return this.session; |
| } |
| |
| |
| @Override |
| public void createControl(final Composite parent) { |
| this.composite= new Composite(parent, SWT.NONE) { |
| @Override |
| public boolean setFocus() { |
| return setDefaultFocus(); |
| } |
| }; |
| this.composite.setLayout(LayoutUtils.newSashGrid()); |
| |
| { final Control control= createAddressBar(this.composite); |
| if (control != null) { |
| control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); |
| } |
| } |
| final Control browser= createBrowser(this.composite); |
| browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| |
| super.createControl(null); |
| |
| // check required for open in new window/page |
| final String url= this.session.getUrl(); |
| if (!url.isEmpty()) { |
| setUrl(url); |
| } |
| } |
| |
| private Control createBrowser(final Composite parent) { |
| final var browser= new Browser(parent, SWT.NONE); |
| this.browser= browser; |
| |
| browser.addProgressListener(this); |
| browser.addLocationListener(this); |
| browser.addTitleListener(this); |
| browser.addStatusTextListener(this); |
| browser.addOpenWindowListener(this); |
| browser.addCloseWindowListener(this); |
| |
| return browser; |
| } |
| |
| protected @Nullable Control createAddressBar(final Composite parent) { |
| final var addressBar= new BrowserAddressBar(parent, this, |
| BrowserUtils.getDefaultAddressInputHistory() ); |
| this.addressBar= addressBar; |
| |
| return addressBar; |
| } |
| |
| void setStatusManager(final @Nullable StatusLineMessageManager statusManager) { |
| this.statusManager= statusManager; |
| if (statusManager != null) { |
| statusManager.setSelectionMessage(this.browserStatus); |
| } |
| } |
| |
| @Override |
| protected void initActions(final IServiceLocator serviceLocator, final ContextHandlers handlers) { |
| final IHandlerService handlerService= handlers.getHandlerService(); |
| |
| final String browserType= this.browser.getBrowserType(); |
| if (browserType.equals("mozilla") || browserType.equals("webkit")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| { final IHandler2 handler= new AbstractHandler() { |
| @Override |
| public @Nullable Object execute(final ExecutionEvent event) throws ExecutionException { |
| if (!UIAccess.isOkToUse(PageBookBrowserPage.this.browser)) { |
| return null; |
| } |
| var searchBar= PageBookBrowserPage.this.searchBar; |
| if (searchBar == null) { |
| searchBar= new SearchBar(PageBookBrowserPage.this.composite); |
| PageBookBrowserPage.this.searchBar= searchBar; |
| } |
| searchBar.show(); |
| return null; |
| } |
| }; |
| handlers.addActivate(IWorkbenchCommandConstants.EDIT_FIND_AND_REPLACE, handler); |
| } |
| { final IHandler2 handler= new AbstractHandler() { |
| @Override |
| public @Nullable Object execute(final ExecutionEvent arg0) { |
| PageBookBrowserPage.this.search(true); |
| return null; |
| } |
| }; |
| handlers.add(SharedUIResources.FIND_NEXT_COMMAND_ID, handler); |
| handlerService.activateHandler("org.eclipse.ui.navigate.next", handler); //$NON-NLS-1$ |
| } |
| { final IHandler2 handler= new AbstractHandler() { |
| @Override |
| public @Nullable Object execute(final ExecutionEvent arg0) { |
| PageBookBrowserPage.this.search(false); |
| return null; |
| } |
| }; |
| handlers.add(SharedUIResources.FIND_PREVIOUS_COMMAND_ID, handler); |
| handlerService.activateHandler("org.eclipse.ui.navigate.previous", handler); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| private void search(final boolean forward) { |
| final var searchBar= this.searchBar; |
| if (searchBar == null || !UIAccess.isOkToUse(this.browser)) { |
| return; |
| } |
| final String text= searchBar.getText(); |
| if (text == null || text.isEmpty()) { |
| return; |
| } |
| final boolean caseSensitive= searchBar.isCaseSensitiveEnabled(); |
| |
| final String message; |
| if (BrowserUtils.searchText(this.browser, text, forward, caseSensitive, false)) { |
| message= null; |
| } |
| else if (BrowserUtils.searchText(this.browser, text, forward, caseSensitive, true)) { |
| message= forward ? "Search continued from top" : "Search continued from bottom"; |
| } |
| else { |
| Display.getCurrent().beep(); |
| message= "Search text not found"; |
| } |
| |
| final var statusManager= this.statusManager; |
| if (statusManager != null) { |
| statusManager.setMessage(new StatusInfo(IStatus.INFO, message)); |
| } |
| } |
| |
| |
| protected Browser getBrowser() { |
| return this.browser; |
| } |
| |
| @Override |
| public Control getControl() { |
| return this.composite; |
| } |
| |
| @Override |
| public void setFocus() { |
| setDefaultFocus(); |
| } |
| |
| protected boolean setDefaultFocus() { |
| final var addressBar= this.addressBar; |
| if (addressBar != null && addressBar.isVisible() |
| && (this.session.getUrl().equals(ABOUT_BLANK_URI_STRING) || addressBar.isEdited()) ) { |
| return addressBar.setFocus(); |
| } |
| return setFocusToBrowser(); |
| } |
| |
| public boolean isBrowserFocusControl() { |
| return (UIAccess.isOkToUse(this.browser) && this.browser.isFocusControl()); |
| } |
| |
| public boolean setFocusToBrowser() { |
| return this.browser.setFocus(); |
| } |
| |
| |
| public void setUrl(@Nullable String url) { |
| final var browser= this.browser; |
| if (browser == null) { |
| return; |
| } |
| if (url == null || url.isEmpty()) { |
| url= ABOUT_BLANK_URI_STRING; |
| } |
| if (url.startsWith("html:///")) { //$NON-NLS-1$ |
| final int id= this.session.putStatic(url.substring(8)); |
| url= STATIC_CONTENT_URL_PREFIX + id; |
| } |
| browser.setUrl(url, null, getHeaders()); |
| } |
| |
| public void setUrl(@Nullable URI url) { |
| final var browser= this.browser; |
| if (browser == null) { |
| return; |
| } |
| if (url == null) { |
| url= ABOUT_BLANK_URI; |
| } |
| setUrl(url.toString()); // url.toASCIIString() ? |
| } |
| |
| public void refresh() { |
| this.browser.refresh(); |
| } |
| |
| protected String @Nullable [] getHeaders() { |
| return new String[] { |
| "Cache-Control: no-cache" }; //$NON-NLS-1$ |
| } |
| |
| public String getCurrentTitle() { |
| return this.session.getTitle(); |
| } |
| |
| public String getCurrentUrl() { |
| return this.session.getUrl(); |
| } |
| |
| int getCurrentProgressTotal() { |
| return this.progressTotal; |
| } |
| |
| int getCurrentProgressWorked() { |
| return this.progressWorked; |
| } |
| |
| |
| @Override |
| public void changed(final ProgressEvent event) { |
| if (event.total == 0) { |
| this.progressTotal= 0; |
| this.progressWorked= 0; |
| } |
| else { |
| this.progressTotal= event.total; |
| this.progressWorked= event.current; |
| } |
| } |
| |
| @Override |
| public void changing(final LocationEvent event) { |
| if (event.top) { |
| this.session.setImageDescriptor(null); |
| } |
| if (event.location.startsWith(STATIC_CONTENT_URL_PREFIX)) { |
| event.doit= false; |
| try { |
| final String html= this.session.getStatic(Integer.parseInt(event.location.substring(11))); |
| if (html != null) { |
| this.browser.setText(html); |
| } |
| } |
| catch (final Exception e) { |
| } |
| return; |
| } |
| if (event.location.startsWith("about:")) { //$NON-NLS-1$ |
| if (!event.location.equals(ABOUT_BLANK_URI_STRING)) { |
| event.doit= false; |
| } |
| return; |
| } |
| if (event.location.startsWith("res:")) { //$NON-NLS-1$ |
| event.doit= false; |
| return; |
| } |
| } |
| |
| @Override |
| public void changed(final LocationEvent event) { |
| if (!event.top) { |
| return; |
| } |
| final String url= nonNullElse(event.location, ""); //$NON-NLS-1$ |
| this.session.setUrl(url); |
| onPageChanged(); |
| } |
| |
| protected void onPageChanged() { |
| final var addressBar= this.addressBar; |
| if (addressBar != null) { |
| addressBar.onPageChanged(); |
| } |
| } |
| |
| @Override |
| public void completed(final ProgressEvent event) { |
| this.progressTotal= 0; |
| this.progressWorked= 0; |
| } |
| |
| @Override |
| public void changed(final TitleEvent event) { |
| String title= nonNullElse(event.title, ""); //$NON-NLS-1$ |
| if (title.startsWith("http://")) { //$NON-NLS-1$ |
| final int idx= title.lastIndexOf('/'); |
| if (idx >= 0) { |
| title= title.substring(idx + 1); |
| } |
| } |
| this.session.setTitle(title); |
| } |
| |
| @Override |
| public void changed(final StatusTextEvent event) { |
| this.browserStatus= (event.text != null && !event.text.isEmpty()) ? |
| new StatusInfo(IStatus.OK, event.text) : null; |
| |
| final var statusManager= this.statusManager; |
| if (statusManager != null) { |
| statusManager.setSelectionMessage(this.browserStatus); |
| } |
| } |
| |
| |
| @Override |
| public void open(final WindowEvent event) { |
| final var page= (PageBookBrowserPage)getView().newPage(new BrowserSession(), true); |
| if (page != null) { |
| event.browser= page.browser; |
| } |
| } |
| |
| @Override |
| public void close(final WindowEvent event) { |
| getView().closePage(this.session); |
| } |
| |
| public @Nullable String getSelectedText() { |
| return BrowserUtils.getSelectedText(this.browser); |
| } |
| |
| } |