/*******************************************************************************
* Copyright (c) 2004, 2009 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - initial API and implementation
* IBM Corporation
* Anton Leherbauer (Wind River Systems)
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.wizards.filewizard;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.views.contentoutline.ContentOutline;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICContainer;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ISourceRoot;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.utils.PathUtil;
import org.eclipse.cdt.internal.corext.util.CModelUtil;
import org.eclipse.cdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.cdt.internal.ui.editor.CEditor;
import org.eclipse.cdt.internal.ui.preferences.CodeTemplatePreferencePage;
import org.eclipse.cdt.internal.ui.viewsupport.IViewPartInputProvider;
import org.eclipse.cdt.internal.ui.wizards.NewElementWizardPage;
import org.eclipse.cdt.internal.ui.wizards.SourceFolderSelectionDialog;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.ComboDialogField;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.DialogField;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.IStringButtonAdapter;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.LayoutUtil;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.Separator;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.StringButtonDialogField;
public abstract class AbstractFileCreationWizardPage extends NewElementWizardPage {
private static final int MAX_FIELD_CHARS = 50;
private static final String NO_TEMPLATE = ""; //$NON-NLS-1$
private IWorkspaceRoot fWorkspaceRoot;
// field IDs
private static final int SOURCE_FOLDER_ID = 1;
protected static final int NEW_FILE_ID = 2;
private static final int ALL_FIELDS = SOURCE_FOLDER_ID | NEW_FILE_ID;
int fLastFocusedField = 0;
private StringButtonDialogField fSourceFolderDialogField;
private IStatus fSourceFolderStatus;
private IStatus fNewFileStatus;
private final IStatus STATUS_OK = new StatusInfo();
/**
* This flag isFirstTime is used to keep a note
* that the file creation wizard has just been
* created.
*/
private boolean isFirstTime = true;
private Template[] fTemplates;
private ComboDialogField fTemplateDialogField;
public AbstractFileCreationWizardPage(String name) {
super(name);
setDescription(NewFileWizardMessages.AbstractFileCreationWizardPage_description);
fWorkspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
SourceFolderFieldAdapter sourceFolderAdapter = new SourceFolderFieldAdapter();
fSourceFolderDialogField = new StringButtonDialogField(sourceFolderAdapter);
fSourceFolderDialogField.setDialogFieldListener(sourceFolderAdapter);
fSourceFolderDialogField.setLabelText(NewFileWizardMessages.AbstractFileCreationWizardPage_sourceFolder_label);
fSourceFolderDialogField.setButtonLabel(NewFileWizardMessages.AbstractFileCreationWizardPage_sourceFolder_button);
fTemplates= getApplicableTemplates();
if (fTemplates != null && fTemplates.length > 0) {
fTemplateDialogField= new ComboDialogField(SWT.READ_ONLY);
fTemplateDialogField.setLabelText(NewFileWizardMessages.AbstractFileCreationWizardPage_template_label);
}
fSourceFolderStatus = STATUS_OK;
fNewFileStatus = STATUS_OK;
fLastFocusedField = 0;
}
// -------- UI Creation ---------
public void createControl(Composite parent) {
initializeDialogUnits(parent);
Composite composite = new Composite(parent, SWT.NONE);
int nColumns = 3;
GridLayout layout = new GridLayout();
layout.numColumns = nColumns;
composite.setLayout(layout);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
composite.setFont(parent.getFont());
createSourceFolderControls(composite, nColumns);
createFileControls(composite, nColumns - 1);
// Placeholder for the right column.
(new Composite(composite, SWT.NO_FOCUS)).setLayoutData(new GridData(1, 1));
createTemplateControls(composite, nColumns);
composite.layout();
setErrorMessage(null);
setMessage(null);
setControl(composite);
}
/**
* Creates a separator line. Expects a <code>GridLayout</code> with at least 1 column.
*
* @param composite the parent composite
* @param nColumns number of columns to span
*/
protected void createSeparator(Composite composite, int nColumns) {
(new Separator(SWT.SEPARATOR | SWT.HORIZONTAL)).doFillIntoGrid(composite, nColumns, convertHeightInCharsToPixels(1));
}
/**
* Creates the necessary controls (label, text field and browse button) to edit
* the source folder location. The method expects that the parent composite
* uses a <code>GridLayout</code> as its layout manager and that the
* grid layout has at least 3 columns.
*
* @param parent the parent composite
* @param nColumns the number of columns to span. This number must be
* greater or equal three
*/
protected void createSourceFolderControls(Composite parent, int nColumns) {
fSourceFolderDialogField.doFillIntoGrid(parent, nColumns);
Text textControl = fSourceFolderDialogField.getTextControl(null);
LayoutUtil.setWidthHint(textControl, getMaxFieldWidth());
textControl.addFocusListener(new StatusFocusListener(SOURCE_FOLDER_ID));
}
/**
* Creates the controls for the file name field. Expects a <code>GridLayout</code> with at
* least 2 columns.
*
* @param parent the parent composite
* @param nColumns number of columns to span
*/
protected abstract void createFileControls(Composite parent, int nColumns);
/**
* Creates the controls for the file template field. Expects a <code>GridLayout</code> with at
* least 3 columns.
*
* @param parent the parent composite
* @param columns number of columns to span
*/
protected void createTemplateControls(Composite parent, int columns) {
if (fTemplateDialogField != null) {
fTemplateDialogField.doFillIntoGrid(parent, columns - 1);
Button configureButton= new Button(parent, SWT.PUSH);
configureButton.setText(NewFileWizardMessages.AbstractFileCreationWizardPage_configure_label);
configureButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
configureButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
editTemplates();
}
});
Combo comboControl= fTemplateDialogField.getComboControl(null);
LayoutUtil.setWidthHint(comboControl, getMaxFieldWidth());
}
}
protected void editTemplates() {
String prefPageId= CodeTemplatePreferencePage.PREF_ID;
Map<String, String> data= null;
String templateName= null;
Template template= getSelectedTemplate();
if (template != null) {
templateName= template.getName();
}
if (templateName != null) {
data= new HashMap<String, String>();
data.put(CodeTemplatePreferencePage.DATA_SELECT_TEMPLATE, templateName);
}
PreferenceDialog dialog= PreferencesUtil.createPreferenceDialogOn(getShell(), prefPageId, new String[] { prefPageId }, data);
if (dialog.open() == Window.OK) {
updateTemplates();
}
}
protected void updateTemplates() {
if (fTemplateDialogField != null) {
Template selected= getSelectedTemplate();
String name = selected != null ?
selected.getName() :
getDefaultTemplateName();
fTemplates= getApplicableTemplates();
int idx= NO_TEMPLATE.equals(name) ? 0 : 1;
String[] names= new String[fTemplates.length + 1];
for (int i = 0; i < fTemplates.length; i++) {
names[i + 1]= fTemplates[i].getName();
if (name != null && name.equals(names[i + 1])) {
idx= i + 1;
}
}
names[0]= NewFileWizardMessages.AbstractFileCreationWizardPage_noTemplate;
fTemplateDialogField.setItems(names);
fTemplateDialogField.selectItem(idx);
}
}
/**
* Configure the set of templates to select from.
* @return the set of templates
*/
protected abstract Template[] getApplicableTemplates();
/**
* Returns the selected template and saves its name for future use.
*
* @return the selected template or <code>null</code> if none.
*/
protected Template getTemplate() {
Template template = getSelectedTemplate();
saveLastUsedTemplateName(template != null ? template.getName() : NO_TEMPLATE);
return template;
}
private Template getSelectedTemplate() {
if (fTemplateDialogField != null) {
int index= fTemplateDialogField.getSelectionIndex() - 1;
if (index >= 0 && index < fTemplates.length) {
return fTemplates[index];
}
}
return null;
}
/**
* The wizard owning this page is responsible for calling this method with the
* current selection. The selection is used to initialize the fields of the wizard
* page.
*
* @param selection used to initialize the fields
*/
public void init(IStructuredSelection selection) {
ICElement celem = getInitialCElement(selection);
initFields(celem);
doStatusUpdate();
}
/**
* Utility method to inspect a selection to find a C element.
*
* @param selection the selection to be inspected
* @return a C element to be used as the initial selection, or <code>null</code>,
* if no C element exists in the given selection
*/
protected ICElement getInitialCElement(IStructuredSelection selection) {
ICElement celem = null;
if (selection != null && !selection.isEmpty()) {
Object selectedElement = selection.getFirstElement();
if (selectedElement instanceof IAdaptable) {
IAdaptable adaptable = (IAdaptable) selectedElement;
celem = (ICElement) adaptable.getAdapter(ICElement.class);
if (celem == null) {
IResource resource = (IResource) adaptable.getAdapter(IResource.class);
if (resource != null && resource.getType() != IResource.ROOT) {
while (celem == null && resource.getType() != IResource.PROJECT) {
celem = (ICElement) resource.getAdapter(ICElement.class);
resource = resource.getParent();
}
if (celem == null) {
celem = CoreModel.getDefault().create(resource); // c project
}
}
}
}
}
if (celem == null) {
IWorkbenchPart part = CUIPlugin.getActivePage().getActivePart();
if (part instanceof ContentOutline) {
part = CUIPlugin.getActivePage().getActiveEditor();
}
if (part instanceof IViewPartInputProvider) {
Object elem = ((IViewPartInputProvider)part).getViewPartInput();
if (elem instanceof ICElement) {
celem = (ICElement) elem;
}
}
if (celem == null && part instanceof CEditor) {
IEditorInput input = ((IEditorPart)part).getEditorInput();
if (input != null) {
final IResource res = (IResource) input.getAdapter(IResource.class);
if (res != null && res instanceof IFile) {
celem = CoreModel.getDefault().create((IFile)res);
}
}
}
}
if (celem == null || celem.getElementType() == ICElement.C_MODEL) {
try {
ICProject[] projects = CoreModel.create(getWorkspaceRoot()).getCProjects();
if (projects.length == 1) {
celem = projects[0];
}
} catch (CModelException e) {
CUIPlugin.log(e);
}
}
return celem;
}
/**
* Initializes all fields provided by the page with a given selection.
*
* @param elem the selection used to initialize this page or <code>
* null</code> if no selection was available
*/
protected void initFields(ICElement elem) {
initSourceFolder(elem);
updateTemplates();
handleFieldChanged(ALL_FIELDS);
}
/**
* Initializes the source folder field.
*
* @param elem the C element used to compute the initial folder
*/
protected void initSourceFolder(ICElement elem) {
ICContainer folder = null;
if (elem != null) {
folder = CModelUtil.getSourceFolder(elem);
if (folder == null) {
ICProject cproject = elem.getCProject();
if (cproject != null) {
try {
if (cproject.exists()) {
ISourceRoot[] roots = cproject.getSourceRoots();
if (roots != null && roots.length > 0)
folder = roots[0];
}
} catch (CModelException e) {
CUIPlugin.log(e);
}
if (folder == null) {
folder = cproject.findSourceRoot(cproject.getResource());
}
}
}
}
setSourceFolderFullPath(folder != null ? folder.getResource().getFullPath() : null, false);
}
/**
* Returns the recommended maximum width for text fields (in pixels). This
* method requires that createContent has been called before this method is
* call. Subclasses may override to change the maximum width for text
* fields.
*
* @return the recommended maximum width for text fields.
*/
protected int getMaxFieldWidth() {
return convertWidthInCharsToPixels(MAX_FIELD_CHARS);
}
/**
* Returns the test selection of the current editor. <code>null</code> is returned
* when the current editor does not have focus or does not return a text selection.
* @return Returns the test selection of the current editor or <code>null</code>.
*
* @since 3.0
*/
protected ITextSelection getCurrentTextSelection() {
IWorkbenchPart part = CUIPlugin.getActivePage().getActivePart();
if (part instanceof IEditorPart) {
ISelectionProvider selectionProvider = part.getSite().getSelectionProvider();
if (selectionProvider != null) {
ISelection selection = selectionProvider.getSelection();
if (selection instanceof ITextSelection) {
return (ITextSelection) selection;
}
}
}
return null;
}
/**
* Sets the focus to the source folder's text field.
*/
protected void setFocusOnSourceFolder() {
fSourceFolderDialogField.setFocus();
}
protected final class StatusFocusListener implements FocusListener {
private int fieldID;
public StatusFocusListener(int fieldID) {
this.fieldID = fieldID;
}
public void focusGained(FocusEvent e) {
fLastFocusedField = this.fieldID;
if (isFirstTime) {
isFirstTime = false;
return;
}
doStatusUpdate();
}
public void focusLost(FocusEvent e) {
fLastFocusedField = 0;
doStatusUpdate();
}
}
private class SourceFolderFieldAdapter implements IStringButtonAdapter, IDialogFieldListener {
public void changeControlPressed(DialogField field) {
IPath oldFolderPath = getSourceFolderFullPath();
IPath newFolderPath = chooseSourceFolder(oldFolderPath);
if (newFolderPath != null) {
setSourceFolderFullPath(newFolderPath, false);
handleFieldChanged(ALL_FIELDS);
}
}
public void dialogFieldChanged(DialogField field) {
handleFieldChanged(ALL_FIELDS);
}
}
// ----------- validation ----------
/**
* This method is a hook which gets called after the source folder's
* text input field has changed. This default implementation updates
* the model and returns an error status. The underlying model
* is only valid if the returned status is OK.
*
* @return the model's error status
*/
protected IStatus sourceFolderChanged() {
StatusInfo status = new StatusInfo();
IPath folderPath = getSourceFolderFullPath();
if (folderPath == null) {
status.setError(NewFileWizardMessages.AbstractFileCreationWizardPage_error_EnterSourceFolderName);
return status;
}
IResource res = fWorkspaceRoot.findMember(folderPath);
if (res != null && res.exists()) {
int resType = res.getType();
if (resType == IResource.PROJECT || resType == IResource.FOLDER) {
IProject proj = res.getProject();
if (!proj.isOpen()) {
status.setError(NLS.bind(NewFileWizardMessages.AbstractFileCreationWizardPage_error_NotAFolder, folderPath));
return status;
}
if (!CoreModel.hasCCNature(proj) && !CoreModel.hasCNature(proj)) {
if (resType == IResource.PROJECT) {
status.setError(NewFileWizardMessages.AbstractFileCreationWizardPage_warning_NotACProject);
return status;
}
status.setWarning(NewFileWizardMessages.AbstractFileCreationWizardPage_warning_NotInACProject);
}
ICElement e = CoreModel.getDefault().create(res.getFullPath());
if (CModelUtil.getSourceFolder(e) == null) {
status.setError(NLS.bind(NewFileWizardMessages.AbstractFileCreationWizardPage_error_NotASourceFolder, folderPath));
return status;
}
} else {
status.setError(NLS.bind(NewFileWizardMessages.AbstractFileCreationWizardPage_error_NotAFolder, folderPath));
return status;
}
} else {
status.setError(NLS.bind(NewFileWizardMessages.AbstractFileCreationWizardPage_error_FolderDoesNotExist, folderPath));
return status;
}
return status;
}
/**
* Hook method that gets called when a field on this page has changed.
*
* @param fields Bitwise-OR'd ids of the fields that changed.
*/
protected void handleFieldChanged(int fields) {
if (fields == 0)
return; // no change
if (fieldChanged(fields, SOURCE_FOLDER_ID)) {
fSourceFolderStatus = sourceFolderChanged();
}
if (fieldChanged(fields, NEW_FILE_ID)) {
fNewFileStatus = fileNameChanged();
}
doStatusUpdate();
}
private boolean fieldChanged(int fields, int fieldID) {
return ((fields & fieldID) != 0);
}
protected void doStatusUpdate() {
// do the last focused field first
IStatus lastStatus = getLastFocusedStatus();
// status of all used components
IStatus[] status = new IStatus[] {
lastStatus,
(fSourceFolderStatus != lastStatus) ? fSourceFolderStatus : STATUS_OK,
(fNewFileStatus != lastStatus) ? fNewFileStatus : STATUS_OK,
};
// the mode severe status will be displayed and the ok button enabled/disabled.
updateStatus(status);
}
private IStatus getLastFocusedStatus() {
switch (fLastFocusedField) {
case SOURCE_FOLDER_ID:
return fSourceFolderStatus;
case NEW_FILE_ID:
return fNewFileStatus;
default:
return STATUS_OK;
}
}
public IPath getSourceFolderFullPath() {
String text = fSourceFolderDialogField.getText();
if (text.length() > 0)
return new Path(text).makeAbsolute();
return null;
}
public void setSourceFolderFullPath(IPath folderPath, boolean update) {
String str = (folderPath != null) ? folderPath.makeRelative().toString() : ""; //.makeRelative().toString(); //$NON-NLS-1$
fSourceFolderDialogField.setTextWithoutUpdate(str);
if (update) {
fSourceFolderDialogField.dialogFieldChanged();
}
}
protected IProject getCurrentProject() {
IPath folderPath = getSourceFolderFullPath();
if (folderPath != null) {
return PathUtil.getEnclosingProject(folderPath);
}
return null;
}
/**
* Returns the workspace root.
*
* @return the workspace root
*/
protected IWorkspaceRoot getWorkspaceRoot() {
return fWorkspaceRoot;
}
/*
* @see WizardPage#becomesVisible
*/
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (visible) {
setFocus();
}
}
/**
* Sets the focus on the starting input field.
*/
protected abstract void setFocus();
IPath chooseSourceFolder(IPath initialPath) {
ICElement initElement = getSourceFolderFromPath(initialPath);
if (initElement instanceof ISourceRoot) {
ICProject cProject = initElement.getCProject();
ISourceRoot projRoot = cProject.findSourceRoot(cProject.getProject());
if (projRoot != null && projRoot.equals(initElement))
initElement = cProject;
}
SourceFolderSelectionDialog dialog = new SourceFolderSelectionDialog(getShell());
dialog.setInput(CoreModel.create(fWorkspaceRoot));
dialog.setInitialSelection(initElement);
if (dialog.open() == Window.OK) {
Object result = dialog.getFirstResult();
if (result instanceof ICElement) {
ICElement element = (ICElement)result;
if (element instanceof ICProject) {
ICProject cproject = (ICProject)element;
ISourceRoot folder = cproject.findSourceRoot(cproject.getProject());
if (folder != null)
return folder.getResource().getFullPath();
}
return element.getResource().getFullPath();
}
}
return null;
}
private ICElement getSourceFolderFromPath(IPath path) {
if (path == null)
return null;
while (path.segmentCount() > 0) {
IResource res = fWorkspaceRoot.findMember(path);
if (res != null && res.exists()) {
int resType = res.getType();
if (resType == IResource.PROJECT || resType == IResource.FOLDER) {
ICElement elem = CoreModel.getDefault().create(res.getFullPath());
ICContainer sourceFolder = CModelUtil.getSourceFolder(elem);
if (sourceFolder != null)
return sourceFolder;
if (resType == IResource.PROJECT) {
return elem;
}
}
}
path = path.removeLastSegments(1);
}
return null;
}
/**
* Returns the full path computed from the file name field
* and the source folder.
*
* @return the file path
*/
public abstract IPath getFileFullPath();
/**
* Hook method that gets called when the file name has changed. The method validates the
* file name and returns the status of the validation.
*
* @return the status of the validation
*/
protected abstract IStatus fileNameChanged();
/**
* Creates the new file using the entered field values.
*
* @param monitor a progress monitor to report progress.
* @throws CoreException Thrown when the creation failed.
*/
public abstract void createFile(IProgressMonitor monitor) throws CoreException;
/**
* Returns the created file. The method only returns a valid translation unit
* after <code>createFile</code> has been called.
*
* @return the created translation unit
* @see #createFile(IProgressMonitor)
*/
public abstract ITranslationUnit getCreatedFileTU();
/**
* @return the name of the template used in the previous dialog invocation, or
* the name of the default template.
*/
public abstract String getDefaultTemplateName();
/**
* Saves the name of the last used template.
*
* @param name the name of a template, or an empty string for no template.
*/
public abstract void saveLastUsedTemplateName(String name);
}