blob: 86315eb91634e48447b90712b509816d56a174e2 [file] [log] [blame]
/*=============================================================================#
# 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);
}
}