/*
* This file is part of the OpenJML plugin project.
* Copyright (c) 2012-2013 David R. Cok
* @author David R. Cok
*/
package org.jmlspecs.openjml.eclipse;
import java.util.EnumSet;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
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.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;
import org.jmlspecs.openjml.eclipse.widgets.EnumDialog;
/** Implements a dialog that allows editing of the source and specs path of a Java project */
public class PathsEditor extends Utils.ModelessDialog {
/** Window title */
protected String title;
/** Java project whose paths are being edited */
protected IJavaProject jproject;
/** The current shell */
protected Shell shell;
/** Reference to the internal control for editing the source path */
protected ListEditor sourceListEditor;
/** Reference to the internal control for editing the specs path */
protected ListEditor specsListEditor;
/** Constructor for the dialog
* @param parentShell parent shell for the dialog
* @param title text on the title bar
* @param jproject Java project whose paths are to be edited
*/
public PathsEditor(Shell parentShell, String title, IJavaProject jproject) {
super(parentShell);
this.title = title;
this.jproject = jproject;
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText(title);
setShellStyle(SWT.CLOSE | SWT.MODELESS | SWT.BORDER | SWT.TITLE);
setBlockOnOpen(false);
shell = newShell;
}
@Override
public Control createDialogArea(Composite parent) {
CTabFolder tabs = new CTabFolder(parent,SWT.NONE);
CTabItem classtab = new CTabItem(tabs,SWT.NONE);
CTabItem sourcetab = new CTabItem(tabs,SWT.NONE);
CTabItem specstab = new CTabItem(tabs,SWT.NONE);
Composite classcomp = new Composite(tabs,SWT.NONE);
Composite sourcecomp = new Composite(tabs,SWT.NONE);
Composite specscomp = new Composite(tabs,SWT.NONE);
tabs.setSelection(specstab);
specstab.setText(Messages.OpenJMLUI_Editor_SpecsPath);
sourcetab.setText(Messages.OpenJMLUI_Editor_SourcePath);
classtab.setText(Messages.OpenJMLUI_Editor_ClassPath);
specstab.setControl(specscomp);
sourcetab.setControl(sourcecomp);
classtab.setControl(classcomp);
Utils utils = Activator.utils();
StringBuilder text = new StringBuilder();
text.append(Messages.OpenJMLUI_Editor_ClassPathTitle + Env.eol
+ Messages.OpenJMLUI_Editor_ClassPathTitle2);
text.append(Env.eol);
text.append(Env.eol);
for (String s: utils.getClasspath(jproject)) {
text.append(s);
text.append(Env.eol);
}
Text t = new Text(classcomp,SWT.READ_ONLY|SWT.MULTI);
t.setText(text.toString());
t.setSize(500,200);
String label = Messages.OpenJMLUI_Editor_SourcePathTitle;
sourceListEditor = new ListEditor(shell,sourcecomp,jproject,Env.sourceKey,label);
label = Messages.OpenJMLUI_Editor_SpecsPathTitle;
specsListEditor = new ListEditor(shell,specscomp,jproject,Env.specsKey,label);
return tabs;
}
@Override
public void okPressed() {
try {
jproject.getProject().setPersistentProperty(Env.sourceKey, PathItem.concat(sourceListEditor.pathItems));
jproject.getProject().setPersistentProperty(Env.specsKey, PathItem.concat(specsListEditor.pathItems));
if (Options.uiverboseness) {
Log.log("Saved " + jproject.getProject().getPersistentProperty(Env.sourceKey)); //$NON-NLS-1$
Log.log("Saved " + jproject.getProject().getPersistentProperty(Env.specsKey)); //$NON-NLS-1$
}
} catch (CoreException e) {
Activator.utils().showExceptionInUI(shell,Messages.OpenJMLUI_Editor_PersistentPropertyError,e);
} finally {
super.okPressed();
}
}
}
/** A SWT control for editing a list of PathItems */
class ListEditor {
/**
* The list widget; <code>null</code> if none
* (before creation or after disposal).
*/
protected List list;
/** The list of path items. This must be kept in synch with the Strings in
* 'list'.
*/
protected java.util.List<PathItem> pathItems = new java.util.ArrayList<PathItem>();
/** The project whose path is being edited. */
protected IJavaProject jproject;
/** The key for the persistent copy of the path */
protected QualifiedName key;
/**
* The button box containing the buttons;
* <code>null</code> if none (before creation or after disposal).
*/
protected Composite buttonBox;
protected Button addJarButton;
protected Button addDirButton;
protected Button removeButton;
protected Button upButton;
protected Button downButton;
protected Button defaultButton;
protected Button addSpecialButton;
protected Label label;
protected String labelText;
/**
* The selection listener.
*/
protected SelectionListener selectionListener;
/** The cached dialog for selecting jar files */
protected FileDialog fileDialog;
/** The cached dialog for selecting folders */
protected DirectoryDialog dirDialog;
/** Constructs a widget to edit the path corresponding to the given key and for
* the given project.
*/
// The GridLayout code is copied from FieldEditor
public ListEditor(Shell shell, Composite parent, IJavaProject jproject, QualifiedName key, String labelText) {
this.jproject = jproject;
this.key = key;
this.labelText = labelText;
fileDialog = new FileDialog(shell);
fileDialog.setFilterExtensions(new String[]{"*.jar"}); //$NON-NLS-1$
dirDialog = new DirectoryDialog(shell);
dirDialog.setMessage(Messages.OpenJMLUI_Editor_DirectoryDialogMessage);
String path = jproject.getProject().getLocation().toString();
dirDialog.setFilterPath(path);
GridLayout layout = new GridLayout(2,false);
layout.marginWidth = 0;
layout.marginHeight = 0;
layout.horizontalSpacing = 8; //HORIZONTAL_GAP;
parent.setLayout(layout);
doFillIntoGrid(parent, layout.numColumns); // uses fileDialog
}
/** Initializes the widget from the persistent property. */
protected void init(QualifiedName key) {
pathItems.clear();
String prop = PathItem.getEncodedPath(jproject,key);
if (Options.uiverboseness) {
Log.log("Read path property: " + prop); //$NON-NLS-1$
}
String[] paths = prop.split(PathItem.split);
for (String s : paths) {
s = s.trim();
if (s.isEmpty()) continue;
PathItem p = PathItem.parse(s);
if (p != null) {
pathItems.add(p);
list.add(p.display());
} else {
Activator.utils().showMessageInUI(fileDialog.getParent(),
Messages.OpenJMLUI_Editor_ErrorDialogTitle,
Messages.OpenJMLUI_Editor_UnparsableError + s);
}
}
}
/** Manages the layout of controls */
// The GridLayout code is copied from FieldEditor
protected void doFillIntoGrid(Composite parent, int numColumns) {
Control control = getLabelControl(parent);
GridData gd = new GridData();
gd.horizontalSpan = numColumns;
control.setLayoutData(gd);
list = getListControl(parent);
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.verticalAlignment = GridData.FILL;
gd.horizontalSpan = numColumns - 1;
gd.grabExcessHorizontalSpace = true;
gd.minimumWidth = 300;
list.setLayoutData(gd);
buttonBox = getButtonBoxControl(parent);
gd = new GridData();
gd.verticalAlignment = GridData.BEGINNING;
buttonBox.setLayoutData(gd);
}
protected void add(PathItem item) {
String rep = item.display();
int index = list.getSelectionIndex();
if (index >= 0) {
list.add(rep, index + 1);
pathItems.add(index + 1, item);
} else {
list.add(rep, 0);
pathItems.add(0, item);
}
}
protected void addJarPressed() {
String input = fileDialog.open();
if (input != null) {
PathItem item = PathItem.create(jproject,input);
add(item);
selectionChanged();
}
}
protected void addDirPressed() {
String input = dirDialog.open();
if (input != null) {
PathItem item = PathItem.create(jproject,input);
add(item);
selectionChanged();
}
}
protected void defaultPressed() {
list.removeAll();
pathItems.clear();
java.util.List<PathItem> defaults = key == Env.specsKey ? PathItem.defaultSpecsPath : PathItem.defaultSourcePath;
for (PathItem item : defaults) {
list.add(item.display());
pathItems.add(item);
}
selectionChanged();
}
protected void removePressed() {
int index = list.getSelectionIndex();
if (index >= 0) {
list.remove(index);
pathItems.remove(index);
selectionChanged();
}
}
protected void upPressed() {
swap(true);
}
protected void downPressed() {
swap(false);
}
protected void specialPressed() {
EnumSet<PathItem.SpecialPath.Kind> disable = EnumSet.noneOf(PathItem.SpecialPath.Kind.class);
if (key == Env.sourceKey) disable.add(PathItem.SpecialPath.Kind.SOURCEPATH);
for (PathItem k : pathItems) {
if (k instanceof PathItem.SpecialPath){
disable.add(((PathItem.SpecialPath)k).kind);
}
}
EnumDialog<PathItem.SpecialPath.Kind> d =
new EnumDialog<PathItem.SpecialPath.Kind>(
fileDialog.getParent(),
PathItem.SpecialPath.Kind.values(),
disable);
d.create();
if (d.open() == Dialog.OK) {
PathItem item = new PathItem.SpecialPath(d.selection());
add(item);
selectionChanged();
}
}
/**
* Moves the currently selected item up or down.
*
* @param up <code>true</code> if the item should move up,
* and <code>false</code> if it should move down
*/
protected void swap(boolean up) {
int index = list.getSelectionIndex();
int target = up ? index - 1 : index + 1;
if (index >= 0) {
String[] selection = list.getSelection();
Assert.isTrue(selection.length == 1);
list.remove(index);
list.add(selection[0], target);
list.setSelection(target);
PathItem p = pathItems.remove(index);
pathItems.add(target,p);
}
selectionChanged();
}
/**
* Creates the buttons in the given button box.
*
* @param box the box for the buttons
*/
private void createButtons(Composite box) {
addJarButton = createPushButton(box, Messages.OpenJMLUI_Editor_AddJar);
addDirButton = createPushButton(box, Messages.OpenJMLUI_Editor_AddFolder);
addSpecialButton = createPushButton(box, Messages.OpenJMLUI_Editor_AddSpecial);
removeButton = createPushButton(box, Messages.OpenJMLUI_Editor_Remove);
upButton = createPushButton(box, Messages.OpenJMLUI_Editor_Up);
downButton = createPushButton(box, Messages.OpenJMLUI_Editor_Down);
defaultButton = createPushButton(box, Messages.OpenJMLUI_Editor_Default);
}
/**
* Helper method to create a push button.
*
* @param parent the parent control
* @param key the resource name used to supply the button's label text
* @return Button
*/
private Button createPushButton(Composite parent, String label) {
Button button = new Button(parent, SWT.PUSH);
button.setText(label); // JFaceResources.getString(key));
button.setFont(parent.getFont());
GridData data = new GridData(GridData.FILL_HORIZONTAL);
int widthHint = convertHorizontalDLUsToPixels(button,
IDialogConstants.BUTTON_WIDTH);
data.widthHint = Math.max(widthHint, button.computeSize(SWT.DEFAULT,
SWT.DEFAULT, true).x);
button.setLayoutData(data);
button.addSelectionListener(getSelectionListener());
return button;
}
protected int convertHorizontalDLUsToPixels(Control control, int dlus) {
GC gc = new GC(control);
gc.setFont(control.getFont());
int averageWidth = gc.getFontMetrics().getAverageCharWidth();
gc.dispose();
double horizontalDialogUnitSize = averageWidth * 0.25;
return (int) Math.round(dlus * horizontalDialogUnitSize);
}
private SelectionListener getSelectionListener() {
if (selectionListener == null) {
createSelectionListener();
}
return selectionListener;
}
/**
* Creates a selection listener.
*/
// Note - we could add a specific listener to each button, which would be
// much more object-oriented, but also means many more objects created.
// This code copies how the ListEditor did it.
public void createSelectionListener() {
selectionListener = new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
Widget widget = event.widget;
if (widget == addJarButton) {
addJarPressed();
} else if (widget == addDirButton) {
addDirPressed();
} else if (widget == removeButton) {
removePressed();
} else if (widget == upButton) {
upPressed();
} else if (widget == downButton) {
downPressed();
} else if (widget == defaultButton) {
defaultPressed();
} else if (widget == addSpecialButton) {
specialPressed();
} else if (widget == list) {
selectionChanged();
}
}
};
}
/**
* Returns this field editor's label component.
* <p>
* The label is created if it does not already exist
* </p>
*
* @param parent the parent
* @return the label control
*/
public Label getLabelControl(Composite parent) {
if (label == null) {
label = new Label(parent, SWT.LEFT);
label.setFont(parent.getFont());
String text = getLabelText();
if (text != null) {
label.setText(text);
}
label.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent event) {
label = null;
}
});
} else {
checkParent(label, parent);
}
return label;
}
/**
* Returns this field editor's label text.
*
* @return the label text
*/
public String getLabelText() {
return labelText;
}
protected void selectionChanged() {
int index = list.getSelectionIndex();
int size = list.getItemCount();
removeButton.setEnabled(index >= 0);
upButton.setEnabled(size > 1 && index > 0);
downButton.setEnabled(size > 1 && index >= 0 && index < size - 1);
}
public void setFocus() {
if (list != null) {
list.setFocus();
}
}
public void setEnabled(boolean enabled, Composite parent) {
getListControl(parent).setEnabled(enabled);
addJarButton.setEnabled(enabled);
addDirButton.setEnabled(enabled);
removeButton.setEnabled(enabled);
upButton.setEnabled(enabled);
downButton.setEnabled(enabled);
defaultButton.setEnabled(enabled);
addSpecialButton.setEnabled(enabled);
selectionChanged();
}
public List getListControl(Composite parent) {
if (list == null) {
list = new List(parent, SWT.BORDER | SWT.SINGLE | SWT.V_SCROLL
| SWT.H_SCROLL);
list.setFont(parent.getFont());
list.addSelectionListener(getSelectionListener());
list.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent event) {
list = null;
}
});
} else {
checkParent(list, parent);
}
init(key); // Sets list
return list;
}
public Composite getButtonBoxControl(Composite parent) {
if (buttonBox == null) {
buttonBox = new Composite(parent, SWT.NULL);
GridLayout layout = new GridLayout();
layout.marginWidth = 0;
buttonBox.setLayout(layout);
createButtons(buttonBox);
buttonBox.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent event) {
addJarButton = null;
addDirButton = null;
addSpecialButton = null;
removeButton = null;
upButton = null;
downButton = null;
buttonBox = null;
}
});
} else {
checkParent(buttonBox, parent);
}
selectionChanged();
return buttonBox;
}
protected void checkParent(Control control, Composite parent) {
Assert.isTrue(control.getParent() == parent, "Different parents");//$NON-NLS-1$
}
}