blob: 9464eb7d0c881a96ea2b1253708fb2a149926b46 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2010, 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.viewers.breadcrumb;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
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.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.internal.services.IServiceLocatorCreator;
import org.eclipse.ui.internal.services.ServiceLocator;
import org.eclipse.ui.services.IDisposable;
import org.eclipse.ui.services.IServiceLocator;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
public abstract class AbstractBreadcrumb implements IBreadcrumb {
private static final String ACTIVE_TAB_BG_END= "org.eclipse.ui.workbench.ACTIVE_TAB_BG_END"; //$NON-NLS-1$
private BreadcrumbViewer breadcrumbViewer;
private boolean hasFocus;
private boolean isActive;
private Composite composite;
private Listener displayFocusListener;
private Listener displayKeyListener;
private IPropertyChangeListener propertyChangeListener;
private int breadcrumbServiceState;
private ServiceLocator breadcrumbServices;
public AbstractBreadcrumb() {
}
@Override
public ISelectionProvider getSelectionProvider() {
return this.breadcrumbViewer;
}
@Override
public void setInput(final Object element) {
if (element == null) {
return;
}
final Object input= this.breadcrumbViewer.getInput();
if (element.equals(input)) {
return;
}
if (this.breadcrumbViewer.isDropDownOpen()) {
return;
}
this.breadcrumbViewer.setInput(element);
}
@Override
public void activate() {
this.breadcrumbViewer.setSelection(new StructuredSelection(this.breadcrumbViewer.getInput()));
this.breadcrumbViewer.setFocus();
}
@Override
public boolean isActive() {
return this.isActive;
}
@Override
public Control createContent(final Composite parent) {
assert (this.composite == null);
this.composite= new Composite(parent, SWT.NONE);
final GridLayout gridLayout= LayoutUtils.newSashGrid();
this.composite.setLayout(gridLayout);
this.displayFocusListener= new Listener() {
@Override
public void handleEvent(final Event event) {
if (isBreadcrumbEvent(event)) {
if (AbstractBreadcrumb.this.hasFocus) {
return;
}
AbstractBreadcrumb.this.isActive= true;
focusGained();
}
else {
if (!AbstractBreadcrumb.this.isActive) {
return;
}
if (hasInputFocus()) {
AbstractBreadcrumb.this.isActive= false;
}
if (!AbstractBreadcrumb.this.hasFocus) {
return;
}
focusLost();
}
}
};
Display.getCurrent().addFilter(SWT.FocusIn, this.displayFocusListener);
this.breadcrumbViewer= createViewer(this.composite);
this.breadcrumbViewer.getControl().setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
this.breadcrumbViewer.addOpenListener(new IOpenListener() {
@Override
public void open(final OpenEvent event) {
doRevealOrOpen(event.getSelection());
}
});
this.breadcrumbViewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(final DoubleClickEvent event) {
final Object element= ((IStructuredSelection) event.getSelection()).getFirstElement();
if (element == null) {
return;
}
if (doRevealOrOpen(event.getSelection())) {
return;
}
// final BreadcrumbItem item= (BreadcrumbItem) fBreadcrumbViewer.doFindItem(element);
// if (item == null) {
// return;
// }
// final int index= fBreadcrumbViewer.getIndexOfItem(item);
// final BreadcrumbItem parentItem= fBreadcrumbViewer.getItem(index - 1);
// parentItem.openDropDownMenu();
}
});
// fBreadcrumbViewer.addMenuDetectListener(new MenuDetectListener() {
// public void menuDetected(final MenuDetectEvent event) {
// final ISelectionProvider selectionProvider= (fBreadcrumbViewer.isDropDownOpen()) ?
// fBreadcrumbViewer.getDropDownSelectionProvider() : fBreadcrumbViewer;
//
// System.out.println(fBreadcrumbViewer.isDropDownOpen());
// System.out.println(event);
//
// final MenuManager manager= new MenuManager();
// try {
// fillContextMenu(manager, (IStructuredSelection) selectionProvider.getSelection());
// if (manager.isEmpty()) {
// return;
// }
// final Menu menu= manager.createContextMenu(fBreadcrumbViewer.getControl());
// menu.setLocation(event.x + 10, event.y + 10);
// menu.setVisible(true);
// while (!menu.isDisposed() && menu.isVisible()) {
// if (!menu.getDisplay().readAndDispatch()) {
// menu.getDisplay().sleep();
// }
// }
// }
// finally {
// manager.dispose();
// }
// }
// });
this.propertyChangeListener= new IPropertyChangeListener() {
@Override
public void propertyChange(final PropertyChangeEvent event) {
if (ACTIVE_TAB_BG_END.equals(event.getProperty())) {
if (AbstractBreadcrumb.this.composite.isFocusControl()) {
AbstractBreadcrumb.this.composite.setBackground(JFaceResources.getColorRegistry().get(ACTIVE_TAB_BG_END));
}
}
}
};
JFaceResources.getColorRegistry().addListener(this.propertyChangeListener);
return this.composite;
}
// protected void fillContextMenu(final MenuManager manager, final IStructuredSelection selection) {
// }
@Override
public void dispose() {
if (isServiceLocatorReady(false)) {
this.breadcrumbServiceState= -1;
this.breadcrumbServices.dispose();
this.breadcrumbServices= null;
}
if (this.propertyChangeListener != null) {
JFaceResources.getColorRegistry().removeListener(this.propertyChangeListener);
}
if (this.displayFocusListener != null) {
Display.getDefault().removeFilter(SWT.FocusIn, this.displayFocusListener);
}
deinstallDisplayListeners();
}
/**
* Either reveal the selection in the editor or open the selection in a new editor. If both fail
* open the child pop up of the selected element.
*
* @param selection the selection to open
*/
private boolean doRevealOrOpen(final ISelection selection) {
if (doReveal(selection)) {
setFocusToInput();
return true;
}
else if (doOpen(selection)) {
this.isActive= false;
focusLost();
updateInput();
return true;
}
return false;
}
private boolean doOpen(final ISelection selection) {
if (!(selection instanceof StructuredSelection)) {
return false;
}
final StructuredSelection structuredSelection= (StructuredSelection) selection;
if (structuredSelection.isEmpty()) {
return false;
}
return open(structuredSelection.getFirstElement());
}
private boolean doReveal(final ISelection selection) {
if (!(selection instanceof StructuredSelection)) {
return false;
}
final StructuredSelection structuredSelection= (StructuredSelection) selection;
if (structuredSelection.isEmpty()) {
return false;
}
return reveal(structuredSelection.getFirstElement());
}
/**
* Focus has been transfered into the breadcrumb.
*/
private void focusGained() {
if (this.hasFocus) {
focusLost();
}
this.composite.setBackground(JFaceResources.getColorRegistry().get(ACTIVE_TAB_BG_END));
this.hasFocus= true;
installDisplayListeners();
activateBreadcrumb();
updateActions();
}
/**
* Focus has been revoked from the breadcrumb.
*/
private void focusLost() {
this.composite.setBackground(null);
this.hasFocus= false;
deinstallDisplayListeners();
deactivateBreadcrumb();
updateActions();
}
/**
* Installs all display listeners.
*/
private void installDisplayListeners() {
//Sanity check
deinstallDisplayListeners();
this.displayKeyListener= new Listener() {
@Override
public void handleEvent(final Event event) {
if (!event.doit) {
return;
}
if (event.keyCode == SWT.ESC) {
if (isBreadcrumbEvent(event)) {
setFocusToInput();
}
}
}
};
Display.getDefault().addFilter(SWT.KeyDown, this.displayKeyListener);
}
/**
* Removes all previously installed display listeners.
*/
private void deinstallDisplayListeners() {
if (this.displayKeyListener != null) {
Display.getDefault().removeFilter(SWT.KeyDown, this.displayKeyListener);
this.displayKeyListener= null;
}
}
/**
* Tells whether the given event was issued inside the breadcrumb viewer's control.
*
* @param event the event to inspect
* @return <code>true</code> if event was generated by a breadcrumb child
*/
private boolean isBreadcrumbEvent(final Event event) {
if (this.breadcrumbViewer == null) {
return false;
}
final Widget item= event.widget;
if (!(item instanceof Control)) {
return false;
}
final Shell dropDownShell= this.breadcrumbViewer.getDropDownShell();
if (dropDownShell != null && isChild((Control) item, dropDownShell)) {
return true;
}
return isChild((Control) item, this.breadcrumbViewer.getControl());
}
private boolean isChild(final Control child, final Control parent) {
if (child == null) {
return false;
}
if (child == parent) {
return true;
}
return isChild(child.getParent(), parent);
}
private boolean isServiceLocatorReady(final boolean init) {
if (this.breadcrumbServiceState == 0) {
this.breadcrumbServiceState= -1;
final IServiceLocator pageServices= getParentServiceLocator();
final IServiceLocatorCreator serviceCreator= pageServices.getService(IServiceLocatorCreator.class);
this.breadcrumbServices= (ServiceLocator) serviceCreator.createServiceLocator(pageServices, null, new IDisposable() {
@Override
public void dispose() {
AbstractBreadcrumb.this.breadcrumbServiceState= -1;
AbstractBreadcrumb.this.breadcrumbServices= null;
}
});
this.breadcrumbServiceState= 1;
initActions(this.breadcrumbServices);
}
return (this.breadcrumbServiceState > 0);
}
protected void initActions(final IServiceLocator services) {
final IContextService contextService= services.getService(IContextService.class);
contextService.activateContext("org.eclipse.jdt.ui.breadcrumbEditorScope"); //$NON-NLS-1$
}
protected abstract BreadcrumbViewer createViewer(final Composite parent);
protected abstract IServiceLocator getParentServiceLocator();
protected abstract boolean hasInputFocus();
protected abstract void setFocusToInput();
/**
* Intend to implement.
*/
protected void updateActions() {
}
/**
* The breadcrumb has been activated. Implementors must retarget the editor actions to the
* breadcrumb aware actions.
*/
protected void activateBreadcrumb() {
if (isServiceLocatorReady(true)) {
this.breadcrumbServices.activate();
}
}
/**
* The breadcrumb has been deactivated. Implementors must retarget the breadcrumb actions to the
* editor actions.
*/
protected void deactivateBreadcrumb() {
if (isServiceLocatorReady(true)) {
this.breadcrumbServices.deactivate();
}
}
/**
* Intend to implement.
*/
protected void updateInput() {
}
/**
* Reveal the given element in the editor if possible.
*
* @param element the element to reveal
* @return true if the element could be revealed
*/
protected abstract boolean reveal(Object element);
/**
* Open the element in a new editor if possible.
*
* @param element the element to open
* @return true if the element could be opened
*/
protected abstract boolean open(Object element);
}