/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.gui.utils.common.components;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.dialogs.ISelectionStatusValidator;
import org.eclipse.ui.forms.widgets.ColumnLayout;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.model.BaseWorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory;
import de.rcenvironment.core.utils.common.StringUtils;
/**
* Helper class to create unified-looking GUIs for everything connected with loading/saving of user data.
*
* @author Arne Bachmann
* @author Jan Flink
*/
public final class PropertyTabGuiHelper {
/**
* Offset for FormLayout.
*/
public static final int OFFSET = 5;
/**
* Constant for the layout manager.
*/
public static final int PERC_100 = 100;
/**
* The log instance.
*/
private static final Log LOGGER = LogFactory.getLog(PropertyTabGuiHelper.class);
/**
* Validates if current selection is instance of {@link IFile}.
*/
private static ISelectionStatusValidator fileValidator = new ISelectionStatusValidator() {
@Override
public IStatus validate(Object[] selection) {
if (selection.length == 1 && selection[0] instanceof IFile) {
return Status.OK_STATUS;
}
return Status.CANCEL_STATUS;
}
};
/**
* Selection type for files and/or directories.
*/
private enum SelectionType {
FILE,
DIRECTORY,
FILE_AND_DIRECTORY
}
/**
* Hiding constructor. There are only static methods in this class.
*/
private PropertyTabGuiHelper() {}
/**
* Create handsome buttons. Provide {@link LinkedHashMap}s for correct button and action order (left-right and top-down)
*
* @param parent The composite to insert the buttons into. They will be layed out in a sub-container
* @param factory Factory for the buttons
* @param buttonActionMap A map of button name -> map of action name -> action
* @return The created composite (sometimes needed in outer class), using formlayout
*/
public static Composite createActionButtons(final Composite parent, final TabbedPropertySheetWidgetFactory factory,
final Map<String, Map<String, Runnable>> buttonActionMap) {
final Composite composite = factory.createFlatFormComposite(parent);
composite.setLayout(new FormLayout());
// first button
FormData data = new FormData();
data.top = new FormAttachment(0, OFFSET);
data.left = new FormAttachment(0, OFFSET);
Button lastButton = factory.createButton(composite, buttonActionMap.keySet().iterator().next(), SWT.NONE | SWT.FLAT);
lastButton.addMouseListener(createActions(lastButton, buttonActionMap.values().iterator().next()));
lastButton.setLayoutData(data);
buttonActionMap.remove(buttonActionMap.keySet().iterator().next()); // remove first entry
// remaining buttons
for (final Entry<String, Map<String, Runnable>> entry : buttonActionMap.entrySet()) {
data = new FormData();
data.top = new FormAttachment(0, OFFSET);
data.left = new FormAttachment(lastButton, OFFSET);
lastButton = factory.createButton(composite, /* button label */entry.getKey(), SWT.NONE | SWT.FLAT);
lastButton.addMouseListener(createActions(lastButton, entry.getValue()));
lastButton.setLayoutData(data);
}
// data.right = new FormAttachment(PERC_100); // HINT: otherwise stretching the last button
// to the right border, looks wrong
return composite;
}
/**
* Create a popup menu action with the specified actions on it.
*
* @param actions The actions to show in the popup. Actions are called on GUI thread (!)
* @return The selection listener
*/
private static MouseListener createActions(final Button button, final Map<String, Runnable> actions) {
return new MouseListener() {
public void action(final MouseEvent event) {
if (actions.size() > 1) {
final Menu menu = new Menu(button.getShell(), SWT.POP_UP);
menu.setLocation(button.toDisplay(event.x, event.y));
menu.setVisible(true);
for (final Entry<String, Runnable> entry : actions.entrySet()) {
final MenuItem item = new MenuItem(menu, SWT.NONE);
item.setText(entry.getKey());
item.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(final SelectionEvent event) {
menu.setVisible(false);
menu.dispose();
entry.getValue().run();
}
@Override
public void widgetDefaultSelected(final SelectionEvent event) {
widgetSelected(event);
}
});
}
} else { // one or zero actions for button
if (actions.size() == 1) {
actions.values().iterator().next().run();
}
}
}
@Override
public void mouseDown(final MouseEvent event) {
action(event);
}
@Override
public void mouseDoubleClick(final MouseEvent event) {}
@Override
public void mouseUp(final MouseEvent event) {}
};
}
/**
* Create main layout for inner components.
*
* @param parent The tabbedpropertysheets parent
* @param factory The widgetfactory
* @param title The title of the section
* @return The section to put a composite in, don't forget to set a section client = inner composite
*/
public static Section createSingleColumnSectionComposite(final Composite parent,
final TabbedPropertySheetWidgetFactory factory, final String title) {
final Composite composite = factory.createFlatFormComposite(parent);
final ColumnLayout layout = new ColumnLayout();
layout.maxNumColumns = 1;
composite.setLayout(layout);
final Section section = factory.createSection(composite, Section.TITLE_BAR);
section.setText(title);
return section;
}
/**
* Dialog for project files.
*
* @param shell The shell to block modally
* @param title The title to show
* @param message The message to show
* @return The ifile selected or null
*/
public static IFile selectFileFromActiveProject(final Shell shell, final String title, final String message) {
String project = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
.getActiveEditor().getEditorInput().toString().split("/")[1];
Object resource = selectFileOrDirectoryFromWorkspace(shell, title, message,
ResourcesPlugin.getWorkspace().getRoot().getProject(project), SelectionType.FILE);
// this should be replaced by an validator inselectFileOrDirectoryFromWorkspace()
if (resource instanceof IFile) {
return (IFile) resource;
}
return null;
}
/**
* Dialog for project directories.
*
* @param shell The shell to block modally
* @param title The title to show
* @param message The message to show
* @return The ifolder selected or null
*/
public static IFolder selectDirectoryFromActiveProject(final Shell shell, final String title, final String message) {
String project = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
.getActiveEditor().getEditorInput().toString().split("/")[1];
return (IFolder) selectFileOrDirectoryFromWorkspace(shell, title, message,
ResourcesPlugin.getWorkspace().getRoot().getProject(project), SelectionType.DIRECTORY);
}
/**
* Dialog for project files.
*
* @param shell The shell to block modally
* @param title The title to show
* @param message The message to show
* @return The ifile selected or null
*/
public static IFile selectFileFromProjects(final Shell shell, final String title, final String message) {
return (IFile) selectFileOrDirectoryFromWorkspace(shell, title, message, ResourcesPlugin.getWorkspace().getRoot(),
SelectionType.FILE);
}
private static Object selectFileOrDirectoryFromWorkspace(final Shell shell, final String title, final String message,
Object input, final SelectionType filter) {
final IProject project = getProjectOfCurrentlyActiveEditor();
final ElementTreeSelectionDialog selectionDialog = new ElementTreeSelectionDialog(shell,
new WorkbenchLabelProvider(),
new BaseWorkbenchContentProvider()) {
@Override
protected Control createContents(Composite parent) {
final Control result = super.createContents(parent);
if (project != null) {
getTreeViewer().setExpandedElements(new Object[] { project });
}
return result;
}
@Override
protected void updateButtonsEnableState(IStatus status) {
getOkButton().setEnabled(status.isOK());
}
};
selectionDialog.setStatusLineAboveButtons(false);
if (filter == SelectionType.FILE) {
selectionDialog.setValidator(fileValidator);
} else if (filter == SelectionType.DIRECTORY) {
selectionDialog.addFilter(new ViewerFilter() {
@Override
public boolean select(Viewer viewer, Object parent, Object element) {
return element instanceof IFolder;
}
});
}
selectionDialog.setTitle(title);
selectionDialog.setMessage(message);
selectionDialog.setInput(input);
if (selectionDialog.open() == ElementTreeSelectionDialog.OK) {
if (selectionDialog.getResult().length > 0) {
return selectionDialog.getResult()[0];
}
}
return null;
}
/**
* Dialog for file system files.
*
* @param shell The shell to block modally
* @param extensionFilters The filter expression, e.g. *.py
* @param title The dialog title
* @param filterPath The path the dialog should start, null if default
* @return The filename or null
*/
public static String selectFileFromFileSystem(final Shell shell, final String[] extensionFilters,
final String title, final String filterPath) {
final FileDialog fileDialog = new FileDialog(shell);
fileDialog.setFilterExtensions(extensionFilters);
fileDialog.setText(title);
fileDialog.setFilterPath(filterPath);
return fileDialog.open();
}
/**
* Dialog for file system files.
*
* @param shell The shell to block modally
* @param extensionFilters The filter expression, e.g. *.py
* @param title The dialog title
* @return The filename or null
*/
public static String selectFileFromFileSystem(final Shell shell, final String[] extensionFilters,
final String title) {
return selectFileFromFileSystem(shell, extensionFilters, title, null);
}
/**
* Dialog for file system directories.
*
* @param shell The shell to block modally
* @param title The dialog title
* @return The filename or null
*/
public static String selectDirectoryFromFileSystem(final Shell shell, final String title) {
final DirectoryDialog fileDialog = new DirectoryDialog(shell);
fileDialog.setText(title);
return fileDialog.open();
}
/**
* Return the path expression to refer to the active editor's project, relative to the current workbench root.
*
* @return The name of the project or null if not found
*/
public static IProject getProjectOfCurrentlyActiveEditor() {
String source = "Return the project path of the active editor: %s";
final IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
IEditorInput input = null;
if (window != null) {
final IWorkbenchPage page = window.getActivePage();
if (page != null) {
final IEditorPart editor = page.getActiveEditor();
if (editor == null) {
LOGGER.debug(StringUtils.format(source, "No active editor in workbench page"));
return null;
} else {
input = editor.getEditorInput();
}
} else {
LOGGER.debug(StringUtils.format(source, "No active page in workbench"));
return null;
}
} else {
LOGGER.debug(StringUtils.format(source, "No active workbench window"));
return null;
}
// second if-chain to avoid deep nesting
if (input.exists()) {
if (input instanceof FileEditorInput) {
return ((FileEditorInput) input).getFile().getProject();
} else {
LOGGER.debug(StringUtils.format(source, "Wrong type of active editor input " + input.getClass()));
}
} else {
LOGGER.debug(StringUtils.format(source, "Editor input does not exist"));
}
return null;
}
}