/******************************************************************************* * Copyright (c) 2010, 2012 SAP AG 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: * Tomasz Zarna <Tomasz.Zarna@pl.ibm.com> - Allow to save patches in Workspace * Daniel Megert <daniel_megert@ch.ibm.com> - Create Patch... should remember previously chosen location * Daniel Megert <daniel_megert@ch.ibm.com> - Create Patch wizard must validate initial input *******************************************************************************/ package org.eclipse.egit.ui.internal.history; import java.io.File; import java.util.ArrayList; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.egit.core.op.CreatePatchOperation; import org.eclipse.egit.ui.UIUtils; import org.eclipse.egit.ui.internal.UIIcons; import org.eclipse.egit.ui.internal.UIText; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.TitleAreaDialog; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.graphics.Image; 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.Event; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.model.BaseWorkbenchContentProvider; import org.eclipse.ui.model.WorkbenchLabelProvider; import org.eclipse.ui.views.navigator.ResourceComparator; /** * A wizard page to choose the target location. * <p> * Extracted from {@link GitCreatePatchWizard} */ public class LocationPage extends WizardPage { private static final String PATH_KEY = "GitCreatePatchWizard.LocationPage.path"; //$NON-NLS-1$ private static final String LOCATION_KEY = "GitCreatePatchWizard.LocationPage.location"; //$NON-NLS-1$ private static final String LOCATION_VALUE_CLIPBOARD = "clipboard"; //$NON-NLS-1$ private static final String LOCATION_VALUE_FILE_SYSTEM = "filesystem"; //$NON-NLS-1$ private static final String LOCATION_VALUE_WORKSPACE = "workspace"; //$NON-NLS-1$ private Button cpRadio; Button fsRadio; private Text fsPathText; private Button fsBrowseButton; private Button wsRadio; private Text wsPathText; private Button wsBrowseButton; private boolean wsBrowsed = false; private boolean pageValid; private IContainer wsSelectedContainer; private static class LocationPageContentProvider extends BaseWorkbenchContentProvider { //Never show closed projects boolean showClosedProjects = false; @Override public Object[] getChildren(Object element) { if (element instanceof IWorkspace) { // check if closed projects should be shown IProject[] allProjects = ((IWorkspace) element).getRoot().getProjects(); if (showClosedProjects) return allProjects; ArrayList<IProject> accessibleProjects = new ArrayList<>(); for (IProject project : allProjects) if (project.isOpen()) accessibleProjects.add(project); return accessibleProjects.toArray(); } return super.getChildren(element); } } private class WorkspaceDialog extends TitleAreaDialog { protected TreeViewer wsTreeViewer; protected Text wsFilenameText; protected Image dlgTitleImage; private boolean modified = false; public WorkspaceDialog(Shell shell) { super(shell); } @Override protected Control createContents(Composite parent) { Control control = super.createContents(parent); setTitle(UIText.GitCreatePatchWizard_WorkspacePatchDialogTitle); setMessage(UIText.GitCreatePatchWizard_WorkspacePatchDialogDescription); //create title image dlgTitleImage = UIIcons.WIZBAN_CREATE_PATCH.createImage(); setTitleImage(dlgTitleImage); return control; } @Override protected Control createDialogArea(Composite parent){ Composite parentComposite = (Composite) super.createDialogArea(parent); // create a composite with standard margins and spacing Composite composite = new Composite(parentComposite, SWT.NONE); GridLayout layout = new GridLayout(); layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN); layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING); layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL_BOTH)); composite.setFont(parentComposite.getFont()); getShell().setText(UIText.GitCreatePatchWizard_WorkspacePatchDialogSavePatch); wsTreeViewer = new TreeViewer(composite, SWT.BORDER); final GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true); gd.widthHint= 550; gd.heightHint= 250; wsTreeViewer.getTree().setLayoutData(gd); wsTreeViewer.setContentProvider(new LocationPageContentProvider()); wsTreeViewer.setComparator(new ResourceComparator(ResourceComparator.NAME)); wsTreeViewer.setLabelProvider(new WorkbenchLabelProvider()); wsTreeViewer.setInput(ResourcesPlugin.getWorkspace()); //Open to whatever is selected in the workspace field IPath existingWorkspacePath = new Path(wsPathText.getText()); //Ensure that this workspace path is valid IResource selectedResource = ResourcesPlugin.getWorkspace().getRoot().findMember(existingWorkspacePath); if (selectedResource != null) { wsTreeViewer.expandToLevel(selectedResource, 0); wsTreeViewer.setSelection(new StructuredSelection(selectedResource)); } final Composite group = new Composite(composite, SWT.NONE); layout = new GridLayout(2, false); layout.marginWidth = 0; group.setLayout(layout); group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); final Label label = new Label(group, SWT.NONE); label.setLayoutData(new GridData()); label.setText(UIText.GitCreatePatchWizard_WorkspacePatchDialogFileName); wsFilenameText = new Text(group,SWT.BORDER); wsFilenameText.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); setupListeners(); return parent; } @Override protected Button createButton(Composite parent, int id, String label, boolean defaultButton) { Button button = super.createButton(parent, id, label, defaultButton); if (id == IDialogConstants.OK_ID) button.setEnabled(false); return button; } private void validateDialog() { String fileName = wsFilenameText.getText(); if (fileName.equals("")) //$NON-NLS-1$ if (modified) { setErrorMessage(UIText.GitCreatePatchWizard_WorkspacePatchDialogEnterFileName); getButton(IDialogConstants.OK_ID).setEnabled(false); return; } else { setErrorMessage(null); getButton(IDialogConstants.OK_ID).setEnabled(false); return; } // make sure that the filename is valid if (!(ResourcesPlugin.getWorkspace().validateName(fileName, IResource.FILE)).isOK() && modified) { setErrorMessage(UIText.GitCreatePatchWizard_WorkspacePatchEnterValidFileName); getButton(IDialogConstants.OK_ID).setEnabled(false); return; } // Make sure that a container has been selected if (getSelectedContainer() == null) { setErrorMessage(UIText.GitCreatePatchWizard_WorkspacePatchDialogEnterValidLocation); getButton(IDialogConstants.OK_ID).setEnabled(false); return; } else { IWorkspace workspace = ResourcesPlugin.getWorkspace(); IPath fullPath = wsSelectedContainer.getFullPath().append( fileName); if (workspace.getRoot().getFolder(fullPath).exists()) { setErrorMessage(UIText.GitCreatePatchWizard_WorkspacePatchFolderExists); getButton(IDialogConstants.OK_ID).setEnabled(false); return; } } setErrorMessage(null); getButton(IDialogConstants.OK_ID).setEnabled(true); } @Override protected void okPressed() { IFile file = wsSelectedContainer.getFile(new Path( wsFilenameText.getText())); if (file != null) wsPathText.setText(file.getFullPath().toString()); validatePage(); super.okPressed(); } private IContainer getSelectedContainer() { Object obj = ((IStructuredSelection)wsTreeViewer.getSelection()).getFirstElement(); if (obj instanceof IContainer) wsSelectedContainer = (IContainer) obj; else if (obj instanceof IFile) wsSelectedContainer = ((IFile) obj).getParent(); return wsSelectedContainer; } @Override protected void cancelPressed() { validatePage(); super.cancelPressed(); } @Override public boolean close() { if (dlgTitleImage != null) dlgTitleImage.dispose(); return super.close(); } void setupListeners(){ wsTreeViewer.addSelectionChangedListener( new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection s = (IStructuredSelection)event.getSelection(); Object obj=s.getFirstElement(); if (obj instanceof IContainer) wsSelectedContainer = (IContainer) obj; else if (obj instanceof IFile){ IFile tempFile = (IFile) obj; wsSelectedContainer = tempFile.getParent(); wsFilenameText.setText(tempFile.getName()); } validateDialog(); } }); wsTreeViewer.addDoubleClickListener( new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent event) { ISelection s= event.getSelection(); if (s instanceof IStructuredSelection) { Object item = ((IStructuredSelection)s).getFirstElement(); if (wsTreeViewer.getExpandedState(item)) wsTreeViewer.collapseToLevel(item, 1); else wsTreeViewer.expandToLevel(item, 1); } validateDialog(); } }); wsFilenameText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { modified = true; validateDialog(); } }); } } /** * @param pageName * @param title * @param titleImage */ protected LocationPage(String pageName, String title, ImageDescriptor titleImage) { super(pageName, title, titleImage); } @Override public void createControl(Composite parent) { final Composite composite = new Composite(parent, SWT.NULL); GridLayout gridLayout = new GridLayout(); gridLayout.numColumns = 3; composite.setLayout(gridLayout); composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); initializeDialogUnits(composite); String selectedOption= getDialogSettings().get(LOCATION_KEY); if (selectedOption == null) selectedOption = LOCATION_VALUE_CLIPBOARD; // clipboard GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); gd.horizontalSpan = 3; cpRadio = new Button(composite, SWT.RADIO); cpRadio.setText(UIText.GitCreatePatchWizard_Clipboard); cpRadio.setLayoutData(gd); cpRadio.setSelection(LOCATION_VALUE_CLIPBOARD.equals(selectedOption)); // filesystem boolean isFileSystemSelected = LOCATION_VALUE_FILE_SYSTEM.equals(selectedOption); fsRadio = new Button(composite, SWT.RADIO); fsRadio.setText(UIText.GitCreatePatchWizard_File); fsRadio.setSelection(isFileSystemSelected); fsPathText = new Text(composite, SWT.BORDER); gd = new GridData(GridData.FILL_HORIZONTAL); fsPathText.setLayoutData(gd); fsPathText.setText(createFileName()); fsPathText.setEnabled(isFileSystemSelected); fsBrowseButton = new Button(composite, SWT.PUSH); fsBrowseButton.setText(UIText.GitCreatePatchWizard_Browse); UIUtils.setButtonLayoutData(fsBrowseButton); fsBrowseButton.setEnabled(isFileSystemSelected); // workspace boolean isWorkspaceSelected = LOCATION_VALUE_WORKSPACE.equals(selectedOption); wsRadio = new Button(composite, SWT.RADIO); wsRadio.setText(UIText.GitCreatePatchWizard_Workspace); wsRadio.setSelection(isWorkspaceSelected); wsPathText = new Text(composite, SWT.BORDER); wsPathText.setLayoutData(gd); wsPathText.setEnabled(isWorkspaceSelected); wsBrowseButton = new Button(composite, SWT.PUSH); wsBrowseButton.setText(UIText.GitCreatePatchWizard_Browse); UIUtils.setButtonLayoutData(wsBrowseButton); wsBrowseButton.setEnabled(isWorkspaceSelected); cpRadio.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { // disable other input controls if (((Button) event.widget).getSelection()) { fsPathText.setEnabled(false); fsBrowseButton.setEnabled(false); wsPathText.setEnabled(false); wsBrowseButton.setEnabled(false); getDialogSettings().put(LOCATION_KEY, LOCATION_VALUE_CLIPBOARD); validatePage(); } } }); fsRadio.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { if (((Button) event.widget).getSelection()) { // enable filesystem input controls fsPathText.setEnabled(true); fsBrowseButton.setEnabled(true); wsPathText.setEnabled(false); wsBrowseButton.setEnabled(false); // set focus to filesystem input text control fsPathText.setFocus(); getDialogSettings().put(LOCATION_KEY, LOCATION_VALUE_FILE_SYSTEM); validatePage(); } } }); fsPathText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { if (validatePage()) { IPath filePath= Path.fromOSString(fsPathText.getText()).removeLastSegments(1); getDialogSettings().put(PATH_KEY, filePath.toPortableString()); } } }); fsBrowseButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { final FileDialog dialog = new FileDialog(getShell(), SWT.PRIMARY_MODAL | SWT.SAVE); if (pageValid) { final File file = new File(fsPathText.getText()); dialog.setFilterPath(file.getParent()); dialog.setFileName(file.getName()); } else dialog.setFileName(""); //$NON-NLS-1$ dialog.setText(""); //$NON-NLS-1$ final String path = dialog.open(); if (path != null) fsPathText.setText(new Path(path).toOSString()); validatePage(); } }); wsRadio.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { if (((Button) event.widget).getSelection()) { fsPathText.setEnabled(false); fsBrowseButton.setEnabled(false); // enable workspace input controls wsPathText.setEnabled(true); wsBrowseButton.setEnabled(true); // set focus to workspace input text control wsPathText.setFocus(); getDialogSettings().put(LOCATION_KEY, LOCATION_VALUE_WORKSPACE); validatePage(); } } }); wsPathText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { if (validatePage()) { IPath filePath= Path.fromOSString(wsPathText.getText()).removeLastSegments(1); getDialogSettings().put(PATH_KEY, filePath.toPortableString()); } } }); wsBrowseButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { final WorkspaceDialog dialog = new WorkspaceDialog(getShell()); wsBrowsed = true; dialog.open(); validatePage(); } }); Dialog.applyDialogFont(composite); setControl(composite); validatePage(); } private String createFileName() { String suggestedFileName = getCommit() != null ? CreatePatchOperation .suggestFileName(getCommit()) : getRepository().getWorkTree() .getName().concat(".patch"); //$NON-NLS-1$ String path = getDialogSettings().get(PATH_KEY); if (path != null) return Path.fromPortableString(path).append(suggestedFileName).toOSString(); return (new File(System.getProperty("user.dir", ""), suggestedFileName)).getPath(); //$NON-NLS-1$ //$NON-NLS-2$ } private RevCommit getCommit() { return ((GitCreatePatchWizard)getWizard()).getCommit(); } private Repository getRepository() { return ((GitCreatePatchWizard)getWizard()).getRepository(); } /** * Allow the user to finish if a valid file has been entered. * * @return page is valid */ protected boolean validatePage() { if (wsRadio.getSelection()) pageValid = validateWorkspaceLocation(); else if (fsRadio.getSelection()) pageValid = validateFilesystemLocation(); else if (cpRadio.getSelection()) pageValid = true; /** * Avoid draw flicker by clearing error message if all is valid. */ if (pageValid) { setMessage(null); setErrorMessage(null); } setPageComplete(pageValid); return pageValid; } /** * The following conditions must hold for the file system location to be * valid: * - the path must be valid and non-empty * - the path must be absolute * - the specified file must be of type file * - the parent must exist (new folders can be created via the browse button) * * @return if the location is valid */ private boolean validateFilesystemLocation() { final String pathString = fsPathText.getText().trim(); if (pathString.length() == 0 || !new Path("").isValidPath(pathString)) { //$NON-NLS-1$ setErrorMessage(UIText.GitCreatePatchWizard_FilesystemError); return false; } final File file = new File(pathString); if (!file.isAbsolute()) { setErrorMessage(UIText.GitCreatePatchWizard_FilesystemInvalidError); return false; } if (file.isDirectory()) { setErrorMessage(UIText.GitCreatePatchWizard_FilesystemDirectoryError); return false; } if (pathString.endsWith("/") || pathString.endsWith("\\")) { //$NON-NLS-1$//$NON-NLS-2$ setErrorMessage(UIText.GitCreatePatchWizard_FilesystemDirectoryNotExistsError); return false; } final File parent = file.getParentFile(); if (!(parent.exists() && parent.isDirectory())) { setErrorMessage(UIText.GitCreatePatchWizard_FilesystemDirectoryNotExistsError); return false; } return true; } /** * The following conditions must hold for the workspace location to be valid: * - a parent must be selected in the workspace tree view * - the resource name must be valid * * @return if the location is valid */ private boolean validateWorkspaceLocation() { //make sure that the field actually has a filename in it - making //sure that the user has had a chance to browse the workspace first if (wsPathText.getText().equals("")){ //$NON-NLS-1$ if (wsRadio.getSelection() && wsBrowsed) setErrorMessage(UIText.GitCreatePatchWizard_WorkspacePatchEnterValidFileName); else setErrorMessage(UIText.GitCreatePatchWizard_WorkspacePatchSelectByBrowsing); return false; } //Make sure that all the segments but the last one (i.e. project + all //folders) exist - file doesn't have to exist. It may have happened that //some folder refactoring has been done since this path was last saved. // // The path will always be in format project/{folders}*/file - this // is controlled by the workspace location dialog and by // validatePath method when path has been entered manually. IPath pathToWorkspaceFile = new Path(wsPathText.getText()); IStatus status = ResourcesPlugin.getWorkspace().validatePath(wsPathText.getText(), IResource.FILE); if (status.isOK()) { //Trim file name from path IPath containerPath = pathToWorkspaceFile.removeLastSegments(1); IResource container = ResourcesPlugin.getWorkspace().getRoot().findMember(containerPath); if (container == null) { if (wsRadio.getSelection()) setErrorMessage(UIText.GitCreatePatchWizard_WorkspacePatchSelectByBrowsing); return false; } else if (!container.isAccessible()) { if (wsRadio.getSelection()) setErrorMessage(UIText.GitCreatePatchWizard_WorkspacePatchProjectClosed); return false; } else if (ResourcesPlugin.getWorkspace().getRoot().getFolder( pathToWorkspaceFile).exists()) { setErrorMessage(UIText.GitCreatePatchWizard_WorkspacePatchFolderExists); return false; } } else { setErrorMessage(status.getMessage()); return false; } return true; } /** * @return answers a full path to a file system file or * <code>null</code> if the user selected to save the patch in * the clipboard. */ public File getFile() { if (pageValid && fsRadio.getSelection()) return new File(fsPathText.getText().trim()); if (pageValid && wsRadio.getSelection()) { final String filename= wsPathText.getText().trim(); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); final IFile file = root.getFile(new Path(filename)); return file.getLocation().toFile(); } return null; } }