diff --git a/build/org.eclipse.cdt.managedbuilder.ui/src/org/eclipse/cdt/managedbuilder/ui/properties/FileListControlFieldEditor.java b/build/org.eclipse.cdt.managedbuilder.ui/src/org/eclipse/cdt/managedbuilder/ui/properties/FileListControlFieldEditor.java index d395801e86a..97a0de8c514 100644 --- a/build/org.eclipse.cdt.managedbuilder.ui/src/org/eclipse/cdt/managedbuilder/ui/properties/FileListControlFieldEditor.java +++ b/build/org.eclipse.cdt.managedbuilder.ui/src/org/eclipse/cdt/managedbuilder/ui/properties/FileListControlFieldEditor.java @@ -72,6 +72,7 @@ public class FileListControlFieldEditor extends FieldEditor { * @param name the name of the preference this field editor works on * @param labelText the label text of the field editor * @param tooltip the tooltip text of the field editor + * @param contextId * @param parent the parent of the field editor's control * @param type the browseType of the file list control */ @@ -154,11 +155,7 @@ public class FileListControlFieldEditor extends FieldEditor { gddata.horizontalSpan = 2; topLayout.setLayoutData(gddata); // file list control - list = - new FileListControl( - topLayout, - getLabelText(), - getType()); + list = new FileListControl(topLayout, getLabelText(), getType(), false); list.addChangeListener(new IFileListChangeListener(){ public void fileListChanged(FileListControl fileList, String oldValue[], String newValue[]) { diff --git a/core/org.eclipse.cdt.ui/plugin.xml b/core/org.eclipse.cdt.ui/plugin.xml index 435b18f121f..e88671921aa 100644 --- a/core/org.eclipse.cdt.ui/plugin.xml +++ b/core/org.eclipse.cdt.ui/plugin.xml @@ -3277,4 +3277,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/PluginResources.properties b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/PluginResources.properties index 2f330529b4c..84bb7d49976 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/PluginResources.properties +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/PluginResources.properties @@ -242,13 +242,15 @@ FileListControl.movedown=Move Down FileListControl.filedialog.title=Select File FileListControl.dirdialog.title=Select Include Directory FileListControl.dirdialog.desc=Select Include Paths -FileListControl.deletedialog.message=Are you sure you want to delete the selected files or directories? -FileListControl.deletedialog.title=Confirm Delete +FileListControl.deletedialog.message=Are you sure you want to remove the selected entries? +FileListControl.deletedialog.title=Confirm Remove FileListControl.editdialog.title=Edit Dialog FileListControl.newdialog.title=New Dialog FileListControl.button.workspace=Workspace... FileListControl.button.fs=File system... - +FileListControl.BrowseEntryDialog.wsp.dir.dlg.msg=Select one or more Workspace Folders +FileListControl.BrowseEntryDialog.wsp.file.dlg.msg=Select one or more Workspace Files +FileListControl.BrowseEntryDialog.wsp.file.dlg.err=One of the elements selected isn't a file # ----------- Property Page ---------- MngMakeProjectPropertyPage.closedproject=Project Closed MngMakeProjectPropertyPage.internalError=An Internal error has occurred. Please check error log. diff --git a/core/org.eclipse.cdt.ui/utils.ui/org/eclipse/cdt/utils/ui/controls/FileListControl.java b/core/org.eclipse.cdt.ui/utils.ui/org/eclipse/cdt/utils/ui/controls/FileListControl.java index afdcbba3e85..f3fce68c825 100644 --- a/core/org.eclipse.cdt.ui/utils.ui/org/eclipse/cdt/utils/ui/controls/FileListControl.java +++ b/core/org.eclipse.cdt.ui/utils.ui/org/eclipse/cdt/utils/ui/controls/FileListControl.java @@ -8,20 +8,32 @@ * Contributors: * BitMethods Inc - initial API and implementation * Sascha Radike - Support for workspace browsing and small improvements + * James Blackburn (Broadcom Corp.) *******************************************************************************/ package org.eclipse.cdt.utils.ui.controls; - import java.util.ArrayList; +import java.util.Arrays; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.operations.AbstractOperation; +import org.eclipse.core.commands.operations.IOperationHistory; +import org.eclipse.core.commands.operations.IUndoContext; +import org.eclipse.core.commands.operations.IUndoableOperation; +import org.eclipse.core.commands.operations.ObjectUndoContext; +import org.eclipse.core.commands.operations.OperationHistoryFactory; +import org.eclipse.core.filesystem.URIUtil; 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.ResourcesPlugin; +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.core.runtime.Status; import org.eclipse.core.variables.IStringVariableManager; import org.eclipse.core.variables.VariablesPlugin; import org.eclipse.jface.dialogs.IDialogConstants; @@ -30,6 +42,9 @@ import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MouseAdapter; @@ -44,6 +59,7 @@ import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.DirectoryDialog; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.List; @@ -51,45 +67,75 @@ import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Widget; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.ElementTreeSelectionDialog; import org.eclipse.ui.dialogs.ISelectionStatusValidator; import org.eclipse.ui.model.WorkbenchContentProvider; import org.eclipse.ui.model.WorkbenchLabelProvider; +import org.eclipse.ui.swt.IFocusService; import org.eclipse.ui.views.navigator.ResourceComparator; +import org.eclipse.cdt.core.cdtvariables.CdtVariableException; +import org.eclipse.cdt.core.cdtvariables.ICdtVariable; import org.eclipse.cdt.ui.CDTUIImages; +import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.newui.CDTStatusInfo; import org.eclipse.cdt.ui.newui.TypedCDTViewerFilter; import org.eclipse.cdt.ui.newui.UIMessages; +import org.eclipse.cdt.utils.cdtvariables.CdtVariableResolver; import org.eclipse.cdt.utils.cdtvariables.IVariableContextInfo; +import org.eclipse.cdt.utils.cdtvariables.IVariableSubstitutor; +import org.eclipse.cdt.utils.cdtvariables.SupplierBasedCdtVariableManager; +import org.eclipse.cdt.utils.cdtvariables.SupplierBasedCdtVariableSubstitutor; import org.eclipse.cdt.internal.core.resources.ResourceLookup; /** - * Instances of this class allow the user to add,remove, delete, moveup and movedown + * Instances of this class allow the user to add, remove, delete, moveup and movedown * the items in the list control. * * @noextend This class is not intended to be subclassed by clients. */ - public class FileListControl { - // Browse type values - // (copied from IOption to fix the dependency on MBS ui plugins issue) - private static final int BROWSE_NONE = 0; - private static final int BROWSE_FILE = 1; - private static final int BROWSE_DIR = 2; + /** + * Constant copied from ManagedBuild IOption indicating that the entries in this + * FileListControl are neither files nor directories -- they're treated as a plain + * String list. + * + * #see org.eclipse.cdt.managedbuilder.core.IOption#BROWSE_NONE + * @since 5.2 + */ + public static final int BROWSE_NONE = 0; + /** + * Constant copied from ManagedBuild IOption indicating that the entries in this + * FileListControl are Files. + * + * #see org.eclipse.cdt.managedbuilder.core.IOption#BROWSE_FILE + * @since 5.2 + */ + public static final int BROWSE_FILE = 1; + /** + * Constant copied from ManagedBuild IOption indicating that the entries in this + * FileListControl are Directories. + * + * #see org.eclipse.cdt.managedbuilder.core.IOption#BROWSE_DIR + * @since 5.2 + */ + public static final int BROWSE_DIR = 2; /** * Multi-purpose dialog to prompt the user for a value, path, or file. - * + * * @since 2.0 */ class SelectPathInputDialog extends InputDialog { + + private String[] values = new String[0]; + private int type; /* True if user successfully set the text value by a browse dialog */ private boolean fSetByBrowseDialog = false; - /** * @param parentShell @@ -103,22 +149,32 @@ public class FileListControl { super(parentShell, dialogTitle, dialogMessage, initialValue, validator); this.type = browseType; } - + /** - * Returns true if the value has been set by a browse dialog. + * Returns true if the value has been set by a browse dialog. */ public boolean isValueSetByBrowse() { return fSetByBrowseDialog; } - - + + /** + * Allow this dialog to return multiple entires + * @return String[] represeting the collected values + */ + public String[] getValues() { + // If values only has one entry or fewer, then return getValue() to catch more recent changes to edit field + if (values.length <= 1) + return new String[] { getValue() }; + return values; + } + /* (non-Javadoc) * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite) */ @Override protected void createButtonsForButtonBar(Composite parent) { super.createButtonsForButtonBar(parent); - + if (((type == BROWSE_DIR) || (type == BROWSE_FILE) ) && (fWorkspaceSupport)) { @@ -130,107 +186,110 @@ public class FileListControl { /* Before opening the browse dialog we try to convert the current * path text to a valid workspace resource, so we can set it * as initial selection in the dialog. - * + * * First we remove all double-quotes. Then the build macro provider * will resolve all macros/variables (like workspace_loc, ...). - * + * * If the workspace location path is a prefix of our resolved path, * we will remove that part and finally get a full path relative to the * workspace. We can use that path to set the initially selected resource. */ - - String currentPathText; - IPath path; - - currentPathText = getText().getText(); - if(contextInfo != null){ - /* - try { - currentPathText = - MacroResolver.resolveToString(currentPathText, - new DefaultMacroSubstitutor(contextInfo , - "", //$NON-NLS-1$ - " ")); //$NON-NLS-1$ - } catch (BuildMacroException e) { - } - */ - } + + String currentPathText = getText().getText(); /* Remove double quotes */ currentPathText = currentPathText.replaceAll("\"", ""); //$NON-NLS-1$ //$NON-NLS-2$ /* Resolve variables */ - IStringVariableManager variableManager = - VariablesPlugin.getDefault().getStringVariableManager(); - - /* Remove workspace location prefix (if any) */ - path = new Path(currentPathText); - + IStringVariableManager variableManager = VariablesPlugin.getDefault().getStringVariableManager(); + + /* See if we can discover the project from the context * + * and check whether the path must be resolved... */ + IProject project = null; + IResource resource = null; + if(contextInfo != null) { + try { + // Try to find the project + ICdtVariable var = SupplierBasedCdtVariableManager.getVariable(PROJECTNAME_VAR, contextInfo, true); + if (var != null && var.getValueType() == ICdtVariable.VALUE_TEXT) + project = ResourcesPlugin.getWorkspace().getRoot().getProject(var.getStringValue()); + + // Try to resolve the currentPathText + IVariableSubstitutor varSubs = new SupplierBasedCdtVariableSubstitutor(contextInfo, "", ""); //$NON-NLS-1$//$NON-NLS-2$ + String value = CdtVariableResolver.resolveToString(currentPathText, varSubs); + if (!"".equals(value)) { //$NON-NLS-1$ + IResource rs[] = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocationURI(URIUtil.toURI(value)); + if (rs == null || rs.length == 0) + resource = ResourceLookup.selectFileForLocation(path, null); + if (rs != null && rs.length > 0) + resource = rs[0]; + } + } catch (CdtVariableException e) { + // It's OK not to find the project... carry on as before + } + } + /* Create workspace folder/file selection dialog and * set initial selection */ ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), new WorkbenchLabelProvider(), new WorkbenchContentProvider()); - dialog.setInput(ResourcesPlugin.getWorkspace().getRoot()); + dialog.setInput(ResourcesPlugin.getWorkspace().getRoot()); dialog.setComparator(new ResourceComparator(ResourceComparator.NAME)); - + if (type == BROWSE_DIR) { - IResource container = null; - if(path.isAbsolute()){ - IContainer cs[] = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocation(path); - if(cs != null && cs.length > 0) - container = cs[0]; - } - if(container == null && rc instanceof IContainer) - container = rc; - - - dialog.setInitialSelection(container); - + dialog.setInitialSelection(resource); Class[] filteredResources = {IContainer.class, IProject.class}; dialog.addFilter(new TypedCDTViewerFilter(filteredResources)); - dialog.setTitle(WORKSPACE_DIR_DIALOG_TITLE); - dialog.setMessage(WORKSPACE_DIR_DIALOG_MSG); + dialog.setTitle(WORKSPACE_DIR_DIALOG_TITLE); + dialog.setMessage(WORKSPACE_DIR_DIALOG_MSG); } else { - IResource resource = null; - if(path.isAbsolute()){ - resource= ResourceLookup.selectFileForLocation(path, null); - } - if(resource == null) resource = rc; - dialog.setInitialSelection(resource); dialog.setValidator(new ISelectionStatusValidator() { public IStatus validate(Object[] selection) { if (selection != null) - if (selection.length > 0) - if (!(selection[0] instanceof IFile)) + for (Object sel : selection) + if (!(sel instanceof IFile)) return new CDTStatusInfo(IStatus.ERROR, WORKSPACE_FILE_DIALOG_ERR); return new CDTStatusInfo(); } }); - dialog.setTitle(WORKSPACE_FILE_DIALOG_TITLE); - dialog.setMessage(WORKSPACE_FILE_DIALOG_MSG); + dialog.setTitle(WORKSPACE_FILE_DIALOG_TITLE); + dialog.setMessage(WORKSPACE_FILE_DIALOG_MSG); } - - /* Open dialog and process result. If a resource has - * been selected we create an absolute file system - * path for it based on the workspace_loc variable */ + + /* Open dialog and process result. + * If a resource has been selected we create a workspace relative path for it. + * Use ${ProjName} if the full path is relative to the context's location */ if (dialog.open() == Window.OK) { fSetByBrowseDialog = true; - - IResource resource = (IResource) dialog.getFirstResult(); - - if (resource != null) { - getText().setText(variableManager.generateVariableExpression(WORKSPACELOC_VAR, - resource.getFullPath().toString())); + + Object[] rs = dialog.getResult(); + + if (rs != null) { + int i = 0; + values = new String[rs.length]; + for (Object o : rs) { + resource = (IResource) o; + if (resource.getProject().equals(project)) + values[i++] = variableManager.generateVariableExpression(WORKSPACELOC_VAR, + PROJECTNAME_PATH.append(resource.getProjectRelativePath()).toString()); + else + values[i++] = variableManager.generateVariableExpression(WORKSPACELOC_VAR, + resource.getFullPath().makeRelative().toString()); + } + // If only one entry, update the text field + if (values.length == 1) + getText().setText(values[0]); + else + // More then one item selected and OK pressed. Exit this edit dialog + buttonPressed(IDialogConstants.OK_ID); } } - - } }); } - + if (type != BROWSE_NONE) { /* Browse button for external directories/files */ final Button externalButton = createButton(parent, 4, FILESYSTEMBUTTON_NAME, false); @@ -241,7 +300,7 @@ public class FileListControl { String result; switch (type) { case BROWSE_DIR : - DirectoryDialog dialog = new DirectoryDialog(getParentShell(), + DirectoryDialog dialog = new DirectoryDialog(getParentShell(), SWT.OPEN|SWT.APPLICATION_MODAL); currentName = getText().getText(); if(currentName != null && currentName.trim().length() != 0) { @@ -274,9 +333,97 @@ public class FileListControl { } + /** + * An extended List control with support for cut / copy / paste & undo + * Needs to be public for the copy method to be called by the platform via reflection + * @since 5.2 + * @noextend + * @noinstantiate This class is not intended to be instantiated by clients. + */ + public final class ClipboardList extends List { + private Clipboard clipboard; + + public ClipboardList(Composite parent, int style) { + super (parent, style); + } + private String[] getClipboardContents() { + Clipboard cp = getClipboard(); + String contents = (String)cp.getContents(TextTransfer.getInstance()); + if (contents != null) { + String[] arr = contents.split("\n"); //$NON-NLS-1$ + return arr; + } + return new String[0]; + } + public void copy() { + String[] toCopy = getSelection(); + if (toCopy != null && toCopy.length > 0) { + StringBuilder sb = new StringBuilder(); + for (String item : toCopy) + sb.append(item.trim()).append("\n"); //$NON-NLS-1$ + Clipboard cp = getClipboard(); + cp.setContents(new Object[]{sb.toString().trim()}, new Transfer[] {TextTransfer.getInstance()}); + } + } + public void cut() { + copy(); + // Only remove from the list box if the cut was successful + if (Arrays.equals(getClipboardContents(), getSelection())) + removePressed(); + } + public void paste() { + String[] pasteBuffer = getClipboardContents(); + int i = getSelectionIndex(); + // insert items at the correct location + for (String item : pasteBuffer) + if (!item.trim().equals("")) //$NON-NLS-1$ + add(item.trim(), ++i); + checkNotificationNeeded(); + } + public void undo() { + try { + operationHistory.undo(undoContext, null, null); + } catch (ExecutionException e) { + CUIPlugin.log(e); + } + } + public void redo() { + try { + operationHistory.redo(undoContext, null, null); + } catch (ExecutionException e) { + CUIPlugin.log(e); + } + } + private Clipboard getClipboard() { + if (clipboard == null) + clipboard = new Clipboard(Display.getDefault()); + return clipboard; + } + @Override + public void dispose() { + super.dispose(); + if (clipboard != null) + clipboard.dispose(); + } + /** + * Handle backspace / delete key + */ + public void delete() { + removePressed(); + } + @Override + protected void checkSubclass() { + // We're adding action handlers, override... + } + } + + /* Variable names */ + /* See CdtMacroSupplier: used for making absolute paths relative if desired */ private static final String WORKSPACELOC_VAR = "workspace_loc"; //$NON-NLS-1$ - + private static final String PROJECTNAME_VAR = "ProjName"; //$NON-NLS-1$ + private static final IPath PROJECTNAME_PATH = new Path(VariablesPlugin.getDefault().getStringVariableManager().generateVariableExpression(PROJECTNAME_VAR, null)); + /* Names, messages and titles */ private static final String WORKSPACEBUTTON_NAME = UIMessages.getString("FileListControl.button.workspace"); //$NON-NLS-1$ private static final String FILESYSTEMBUTTON_NAME = UIMessages.getString("FileListControl.button.fs"); //$NON-NLS-1$ @@ -292,42 +439,46 @@ public class FileListControl { private static final String DIR_TITLE_EDIT = UIMessages.getString("BrowseEntryDialog.dir.title.edit"); //$NON-NLS-1$ private static final String WORKSPACE_DIR_DIALOG_TITLE = UIMessages.getString("BrowseEntryDialog.wsp.dir.dlg.title"); //$NON-NLS-1$ private static final String WORKSPACE_FILE_DIALOG_TITLE = UIMessages.getString("BrowseEntryDialog.wsp.file.dlg.title"); //$NON-NLS-1$ - private static final String WORKSPACE_DIR_DIALOG_MSG = UIMessages.getString("BrowseEntryDialog.wsp.dir.dlg.msg"); //$NON-NLS-1$ - private static final String WORKSPACE_FILE_DIALOG_MSG = UIMessages.getString("BrowseEntryDialog.wsp.file.dlg.msg"); //$NON-NLS-1$ - private static final String WORKSPACE_FILE_DIALOG_ERR = UIMessages.getString("BrowseEntryDialog.wsp.file.dlg.err"); //$NON-NLS-1$ + private static final String WORKSPACE_DIR_DIALOG_MSG = UIMessages.getString("FileListControl.BrowseEntryDialog.wsp.dir.dlg.msg"); //$NON-NLS-1$ + private static final String WORKSPACE_FILE_DIALOG_MSG = UIMessages.getString("FileListControl.BrowseEntryDialog.wsp.file.dlg.msg"); //$NON-NLS-1$ + private static final String WORKSPACE_FILE_DIALOG_ERR = UIMessages.getString("FileListControl.BrowseEntryDialog.wsp.file.dlg.err"); //$NON-NLS-1$ private static final String FILESYSTEM_DIR_DIALOG_MSG = UIMessages.getString("BrowseEntryDialog.fs.dir.dlg.msg"); //$NON-NLS-1$ private static final String FILE_MSG = UIMessages.getString("BrowseEntryDialog.message.file"); //$NON-NLS-1$ private static final String DIR_MSG = UIMessages.getString("BrowseEntryDialog.message.directory"); //$NON-NLS-1$ private static final String TITLE = UIMessages.getString("BuildPropertyCommon.label.title"); //$NON-NLS-1$ - + + /** flag which prevents us from resetting the prompt for delete flag */ + private boolean neverPromptForDelete; + /** Flag indicating whether the user should be prompted for delete */ + private boolean promptForDelete; + //toolbar private ToolBar toolBar; // toolbar items - private ToolItem addItem, deleteItem, editItem, moveUpItem, - moveDownItem; + private ToolItem addItem, deleteItem, editItem, moveUpItem, moveDownItem; // title label private Label title; - // images -// private Image addImage, deleteImage, editImage, moveUpImage, moveDownImage; -// private Composite composite; // list control - private List list; + private ClipboardList list; private String compTitle; private SelectionListener selectionListener; private GridData tgdata, grid3, grid4, grid2; - + // The type of browse support that is required private int browseType; + /** The base path that should be used when adding new resources */ private IPath path; - + /* Workspace support */ private boolean fWorkspaceSupport = false; private IVariableContextInfo contextInfo; - private IResource rc; - - private java.util.List listeners = new ArrayList(); - private String oldValue[]; + /** Undo support */ + IUndoContext undoContext; + IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory(); + private java.util.List listeners = new ArrayList(); + private String[] oldValue; + //images private final Image IMG_ADD = CDTUIImages .get(CDTUIImages.IMG_FILELIST_ADD); @@ -339,15 +490,37 @@ public class FileListControl { .get(CDTUIImages.IMG_FILELIST_MOVEUP); private final Image IMG_MOVEDOWN = CDTUIImages .get(CDTUIImages.IMG_FILELIST_MOVEDOWN); - + /** * Constructor - * + * * @param parent * @param compTitle * @param type + * @param promptForDelete indicates whether the user should be prompted on delete + * @see #FileListControl(Composite, String, int) + * @since 5.2 + */ + public FileListControl(Composite parent, String compTitle, int type, boolean promptForDelete) { + this(parent, compTitle, type); + this.promptForDelete = promptForDelete; + this.neverPromptForDelete = !promptForDelete; + } + + /** + * Constructor + * + * This FileListControl only prompts the user on Delete for BROWSE_FILE and BROWSE_DIR + * @param parent + * @param compTitle + * @param type one of the IOption BROWSE types + * @see #BROWSE_NONE + * @see #BROWSE_FILE + * @see #BROWSE_DIR */ public FileListControl(Composite parent, String compTitle, int type) { + promptForDelete = type == BROWSE_FILE || type == BROWSE_DIR; + // Default to no browsing browseType = type; @@ -419,10 +592,10 @@ public class FileListControl { | GridData.HORIZONTAL_ALIGN_END); buttonPanel.setLayoutData(grid3); // list control - list = new List(filePanel, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER); + list = new ClipboardList(filePanel, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.MULTI); grid4 = new GridData(GridData.FILL_BOTH); // force the list to be no wider than the title bar - Point preferredSize = titlePanel.computeSize(SWT.DEFAULT, SWT.DEFAULT); + Point preferredSize = titlePanel.computeSize(SWT.DEFAULT, SWT.DEFAULT); grid4.widthHint = preferredSize.x; grid4.heightHint = preferredSize.y * 3; grid4.horizontalSpan = 2; @@ -436,27 +609,44 @@ public class FileListControl { editSelection(); } }); - // Add a delete event handler + // Add a delete key listener list.addKeyListener(new KeyAdapter() { /* (non-Javadoc) * @see org.eclipse.swt.events.KeyAdapter#keyPressed(org.eclipse.swt.events.KeyEvent) */ @Override public void keyPressed(KeyEvent e) { - // Is this the delete key - if (e.keyCode == SWT.DEL) { - removePressed(); - } else { - super.keyPressed(e); + switch (e.keyCode) { + case SWT.BS: + case SWT.DEL: + if (e.stateMask == SWT.NONE) + removePressed(); + break; } } }); + // Set-up Undo history + undoContext = new ObjectUndoContext(this); + operationHistory.setLimit(undoContext, 50); + + // Add command handlers for undo to the control + try { + IFocusService fs = (IFocusService)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() + .getActivePart().getSite().getService(IFocusService.class); + fs.addFocusTracker(list, "org.eclipse.cdt.ui.FileListControl"); //$NON-NLS-1$ + } catch (Exception e) { + // Any of the get* methods may return null. As this is in the UI constructor for this control + // it shouldn't happen. Log and carry on. + CUIPlugin.log(e); + } + selectionChanged(); } + /** * Set list values - * + * * @param listVal */ public void setList(String[] listVal) { @@ -468,44 +658,71 @@ public class FileListControl { } checkNotificationNeeded(); } - + public void addChangeListener(IFileListChangeListener listener){ listeners.add(listener); } - + public void removeChangeListener(IFileListChangeListener listener){ listeners.remove(listener); } + /** + * Checks whether a notification is needed, and notifies listeners + * + * Persist any changes in the undo history. + * + * This method must be called after every change to the contents of the list box + * + * At end of method oldValue.equals(list.getItems()) + */ public void checkNotificationNeeded(){ - String items[] = getItems(); - if(oldValue != null){ - if(oldValue.length == items.length){ - int i; - for(i = 0; i < oldValue.length; i++){ - if(!oldValue[i].equals(items[i])) - break; - } - if(i == oldValue.length) - return; - } - String old[] = oldValue; - System.arraycopy(items,0,oldValue = new String[items.length],0,items.length); - notifyListeners(old,oldValue); - } else{ - System.arraycopy(items,0,oldValue = new String[items.length],0,items.length); - } - } + final String items[] = getItems(); + if(oldValue != null) { + if (Arrays.equals(oldValue, items)) + return; + // Add some context to the undo history + IUndoableOperation op = new AbstractOperation("") { //$NON-NLS-1$ + final String[] previousValue = oldValue; + final String[] newValue = items; + @Override + public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { + list.setItems(previousValue); + notifyListeners(newValue, previousValue); + oldValue = previousValue; + return Status.OK_STATUS; + } + @Override + public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { + list.setItems(newValue); + notifyListeners(previousValue, newValue); + oldValue = newValue; + return Status.OK_STATUS; + } + @Override + public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { + return Status.CANCEL_STATUS; + } + }; + op.addContext(undoContext); + operationHistory.add(op); + System.arraycopy(items, 0, oldValue = new String[items.length], 0, items.length); + notifyListeners(oldValue, items); + list.setFocus(); // Ensure this control retains focus + } else + System.arraycopy(items, 0, oldValue = new String[items.length], 0, items.length); + } + public void notifyListeners(String oldVal[], String newVal[]){ for (IFileListChangeListener listener: listeners) { listener.fileListChanged(this,oldVal,newVal); } } - + /** * Set selection - * + * * @param sel */ public void setSelection(int sel) { @@ -561,7 +778,7 @@ public class FileListControl { } /** * Returns selection listener - * + * * @return */ private SelectionListener getSelectionListener() { @@ -569,45 +786,43 @@ public class FileListControl { createSelectionListener(); return selectionListener; } + /** * This method will be called when the add button is pressed */ private void addPressed() { // Prompt user for a new item - String input = getNewInputObject(); - + String[] input = getNewInputObject(); + // Add it to the list - if (input != null && input.length() > 0) { + if (input.length > 0) { int index = list.getSelectionIndex(); - if (index >= 0) { - list.add(input, index + 1); - list.setSelection(index + 1); - } - else { - list.add(input, 0); - list.setSelection(0); - } + int i = 0; + for (String s : input) + list.add(s, index + ++i); + list.setSelection(index + 1); checkNotificationNeeded(); } selectionChanged(); } + /** * This method will be called when the remove button is pressed */ private void removePressed() { - int index = list.getSelectionIndex(); - if (browseType == BROWSE_DIR || browseType == BROWSE_FILE) { + if (list.getSelectionCount() == 0 || list.getSelectionIndex() == -1) + return; + boolean delDir = true; + if (promptForDelete) { String quest = UIMessages.getString("FileListControl.deletedialog.message"); //$NON-NLS-1$ String title = UIMessages.getString("FileListControl.deletedialog.title"); //$NON-NLS-1$ - boolean delDir = MessageDialog.openQuestion(list.getShell(), title, - quest); - if (delDir && index != -1){ - list.remove(index); - checkNotificationNeeded(); - } - } else if (index != -1){ - list.remove(index); + delDir = MessageDialog.openQuestion(list.getShell(), title, quest); + } + if (delDir){ + int i; + while ((i = list.getSelectionIndex()) != -1) + list.remove(i); checkNotificationNeeded(); } selectionChanged(); @@ -642,7 +857,7 @@ public class FileListControl { * This method will be called when the edit button is pressed */ private void editSelection() { - int index = list.getSelectionIndex(); + final int index = list.getSelectionIndex(); if (index != -1) { String selItem = list.getItem(index); if (selItem != null) { @@ -662,20 +877,15 @@ public class FileListControl { title = FILE_TITLE_EDIT; message = FILE_MSG; } - dialog = new SelectPathInputDialog(getListControl().getShell(), title, - message, selItem, null, browseType); - + dialog = new SelectPathInputDialog(getListControl().getShell(), title, message, selItem, null, browseType); } else { String title = UIMessages.getString("FileListControl.editdialog.title"); //$NON-NLS-1$ - dialog = new InputDialog(null, title, compTitle, - selItem, null); + dialog = new InputDialog(null, title, compTitle, selItem, null); } - - - String newItem = null; + if (dialog.open() == Window.OK) { - newItem = dialog.getValue(); - + String[] newItems; + /* If newItem is a directory or file path we need to * double-quote it if required. We only do this if the user * selected a new path using a browse button. If he/she simply @@ -683,29 +893,40 @@ public class FileListControl { * wants to. */ if (dialog instanceof SelectPathInputDialog) { - if (((SelectPathInputDialog) dialog).isValueSetByBrowse()) - newItem = doubleQuotePath(newItem); - } - - if (newItem != null && !newItem.equals(selItem)) { - list.setItem(index, newItem); - checkNotificationNeeded(); - selectionChanged(); - } + SelectPathInputDialog selDialog = (SelectPathInputDialog)dialog; + newItems = selDialog.getValues(); + if (selDialog.isValueSetByBrowse()) + for (int i = 0 ; i < newItems.length ; i++) + newItems[i] = doubleQuotePath(newItems[i]); + } else + newItems = new String[] { dialog.getValue() }; + + // If no change, return + if (newItems.length == 1 && newItems[0].equals(selItem)) + return; + + // Replace the changed item & insert new items + list.setItem(index, newItems[0]); + for (int i = 1 ; i < newItems.length ; i++) + list.add(newItems[i], index + i); + checkNotificationNeeded(); + selectionChanged(); } } } } + /** * This method will be called when the list selection changed */ public void selectionChanged() { int index = list.getSelectionIndex(); int size = list.getItemCount(); + int selectionCount = list.getSelectionCount(); deleteItem.setEnabled(size > 0); - moveUpItem.setEnabled(size > 1 && index > 0); - moveDownItem.setEnabled(size > 1 && index >= 0 && index < size - 1); - editItem.setEnabled(size > 0); + moveUpItem.setEnabled(size > 1 && index > 0 && selectionCount == 1); + moveDownItem.setEnabled(size > 1 && index >= 0 && index < size - 1 && selectionCount == 1); + editItem.setEnabled(selectionCount == 1); } /** * Returns List control @@ -717,18 +938,22 @@ public class FileListControl { /** * Sets the IPath of the project the field editor was * created for. - * - * @param path The path to the + * + * @param path The path to the */ public void setPath(IPath path) { this.path = path; } - + /** * Set browseType + * @deprecated This class should be constructed with the correct type */ + @Deprecated public void setType(int type) { browseType = type; + if (!neverPromptForDelete) + promptForDelete = type == BROWSE_FILE || type == BROWSE_DIR; } /** @@ -759,13 +984,13 @@ public class FileListControl { /** * Returns the input dialog string */ - private String getNewInputObject() { + private String[] getNewInputObject() { // Create a dialog to prompt for a new list item - String input = null; + String[] input = new String[0]; String title = new String(); String message = new String(); String initVal = new String(); - + if (browseType == BROWSE_DIR) { title = DIR_TITLE_ADD; message = DIR_MSG; @@ -778,34 +1003,33 @@ public class FileListControl { title = TITLE; message = compTitle; } - + // Prompt for value SelectPathInputDialog dialog = new SelectPathInputDialog(getListControl().getShell(), title, message, initVal, null, browseType); if (dialog.open() == Window.OK) { - input = dialog.getValue(); - } - - /* Double-quote (if required) the text if it is a directory or file */ - if (input != null && input.length() > 0) { - if (browseType == BROWSE_DIR || - browseType == BROWSE_FILE) { - input = doubleQuotePath(input); + input = dialog.getValues(); + + /* Double-quote (if required) the text if it is a directory or file */ + if (input.length > 0) { + if (browseType == BROWSE_DIR || browseType == BROWSE_FILE) + for (int i = 0 ; i < input.length ; i++) + input[i] = doubleQuotePath(input[i]); } } return input; } - + public Label getLabelControl(){ return title; } - + public void setEnabled(boolean enabled){ title.setEnabled(enabled); toolBar.setEnabled(enabled); list.setEnabled(enabled); } - + /** * Double-quotes a path name if it contains white spaces, backslahes * or a macro/variable (We don't know if a macro will contain spaces, so we @@ -816,15 +1040,15 @@ public class FileListControl { private String doubleQuotePath(String pathName) { /* Trim */ pathName = pathName.trim(); - + /* Check if path is already double-quoted */ boolean bStartsWithQuote = pathName.startsWith("\""); //$NON-NLS-1$ boolean bEndsWithQuote = pathName.endsWith("\""); //$NON-NLS-1$ - - /* Check for spaces, backslashes or macros */ + + /* Check for spaces, backslashes or macros */ int i = pathName.indexOf(" ") + pathName.indexOf("\\") //$NON-NLS-1$ //$NON-NLS-2$ + pathName.indexOf("${"); //$NON-NLS-1$ - + /* If indexof didn't fail all three times, double-quote path */ if (i != -3) { if (!bStartsWithQuote) @@ -832,7 +1056,7 @@ public class FileListControl { if (!bEndsWithQuote) pathName = pathName + "\""; //$NON-NLS-1$ } - + return pathName; } }