/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on May 30, 2005
*
* @author Fabio Zadrozny
*/
package org.python.pydev.ui.editors;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
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.DirectoryDialog;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.dialogs.SelectionDialog;
import org.python.pydev.plugin.PyStructureConfigHelpers;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.ui.dialogs.MapOfStringsInputDialog;
import org.python.pydev.ui.filetypes.FileTypesPreferencesPage;
/**
* @author Fabio Zadrozny
*/
@SuppressWarnings("unchecked")
public abstract class TreeWithAddRemove extends Composite {
/**
* The actual tree.
*/
protected Tree tree;
/**
* Used if the initial items map to an array of strings.
*/
public final static int EDITING_STYLE_ARRAY_OF_STRINGS = 0;
/**
* Used if the initial items map to a map with strings.
*/
public final static int EDITING_STYLE_MAP_OF_STRINGS = 1;
/**
* Signals what's the editing mode we're using.
*/
private int editingStyle;
private TreeColumn[] columns;
public TreeWithAddRemove(Composite parent, int style, Object initialItems) {
this(parent, style, initialItems, false);
}
/**
* @param initialItems: Can be a String[] or a HashMap<String, String> (if null it's considered String[])
*/
public TreeWithAddRemove(Composite parent, int style, Object initialItems, boolean createEditButton) {
super(parent, style);
if (initialItems == null) {
initialItems = new String[] {};
}
GridLayout layout = new GridLayout();
layout.numColumns = 2;
this.setLayout(layout);
GridData data = new GridData(GridData.FILL_BOTH);
tree = new Tree(this, SWT.BORDER | SWT.MULTI);
data.grabExcessHorizontalSpace = true;
data.grabExcessVerticalSpace = true;
tree.setLayoutData(data);
Composite buttonsSourceFolders = new Composite(this, SWT.NONE);
buttonsSourceFolders.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
buttonsSourceFolders.setLayout(layout);
for (int i = 0; i < getNumberOfAddButtons(); i++) {
createAddButton(buttonsSourceFolders, i);
}
createRemoveButton(buttonsSourceFolders);
if (createEditButton) {
this.createEditButton(buttonsSourceFolders);
}
if (initialItems instanceof String[]) {
editingStyle = EDITING_STYLE_ARRAY_OF_STRINGS;
} else if (initialItems instanceof Map) {
editingStyle = EDITING_STYLE_MAP_OF_STRINGS;
tree.setHeaderVisible(true);
TreeColumn column1 = new TreeColumn(tree, SWT.LEFT);
column1.setText("Key");
column1.setWidth(200);
TreeColumn column2 = new TreeColumn(tree, SWT.LEFT);
column2.setText("Value");
column2.setWidth(200);
columns = new TreeColumn[] { column1, column2 };
} else {
throw new RuntimeException("Unexpected initial items: " + initialItems);
}
setTreeItems(initialItems);
}
public void fitToContents() {
if (columns != null) {
for (TreeColumn c : columns) {
c.pack();
}
}
}
public void setTreeItems(Object items) {
this.tree.removeAll();
switch (editingStyle) {
case EDITING_STYLE_ARRAY_OF_STRINGS:
String[] its = (String[]) items;
for (int i = 0; i < its.length; i++) {
addTreeItem(its[i]);
}
break;
case EDITING_STYLE_MAP_OF_STRINGS:
Map<String, String> map = (Map<String, String>) items;
for (Map.Entry<String, String> entry : map.entrySet()) {
addTreeItem(entry.getKey(), entry.getValue());
}
break;
}
}
private void configButtonLayout(Button button) {
GridData data;
data = new GridData();
data.horizontalAlignment = GridData.FILL;
data.grabExcessHorizontalSpace = true;
button.setLayoutData(data);
}
protected void createAddButton(Composite buttonsSourceFolders, int buttonNumber) {
Button buttonAddSourceFolder = new Button(buttonsSourceFolders, SWT.PUSH);
customizeAddSomethingButton(buttonAddSourceFolder, buttonNumber);
buttonAddSourceFolder.setText(getButtonLabel(buttonNumber));
configButtonLayout(buttonAddSourceFolder);
}
protected void createRemoveButton(Composite buttonsSourceFolders) {
Button buttonRemSourceFolder = new Button(buttonsSourceFolders, SWT.PUSH);
customizeRemSourceFolderButton(buttonRemSourceFolder);
configButtonLayout(buttonRemSourceFolder);
}
protected void createEditButton(Composite buttonsSourceFolders) {
Button buttonEdit = new Button(buttonsSourceFolders, SWT.PUSH);
customizeEditButton(buttonEdit);
configButtonLayout(buttonEdit);
}
/**
* Remove is almost always default
*/
protected void customizeRemSourceFolderButton(Button buttonRem) {
buttonRem.setText(getButtonRemoveText());
buttonRem.setToolTipText("Remove the selected item");
buttonRem.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
handleRemove();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
}
/**
* Edit
*/
protected void customizeEditButton(Button buttonEdit) {
buttonEdit.setText(getButtonEditText());
buttonEdit.setToolTipText("Edit the selected item");
buttonEdit.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
handleEdit();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
}
private static String lastDirectoryDialogPath = null;
private static String lastFileDialogPath = null;
/**
* @param addButton the button to be customized
* @param nButton the number of the add button (1 for first, 2 for second)
*/
protected void customizeAddSomethingButton(Button addButton, final int nButton) {
addButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
handleAddButtonSelected(nButton);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
}
public void addItemWithDialog(MapOfStringsInputDialog dialog) {
if (dialog.open() == Window.OK) {
Tuple<String, String> keyAndValueEntered = dialog.getKeyAndValueEntered();
if (keyAndValueEntered != null) {
addTreeItem(keyAndValueEntered.o1, keyAndValueEntered.o2);
}
}
}
public void addItemWithDialog(InputDialog dialog) {
dialog.open();
String value = dialog.getValue();
addTreeItem(value);
}
public void addItemWithDialog(FileDialog dialog) {
dialog.setFilterPath(lastFileDialogPath);
dialog.open();
String[] fileNames = dialog.getFileNames();
String parent = dialog.getFilterPath();
if (fileNames != null && fileNames.length > 0) {
for (String s : fileNames) {
addTreeItem(FileUtils.getFileAbsolutePath(new File(parent, s)));
}
}
}
public void addItemWithDialog(DirectoryDialog dialog) {
dialog.setFilterPath(lastDirectoryDialogPath);
String filePath = dialog.open();
if (filePath != null) {
lastDirectoryDialogPath = filePath;
}
addTreeItem(filePath);
}
public void addItemWithDialog(SelectionDialog dialog, IProject project) {
dialog.open();
Object[] objects = dialog.getResult();
if (objects != null) {
for (int i = 0; i < objects.length; i++) {
Object object = objects[i];
if (object instanceof IPath) {
IPath p = (IPath) object;
//IMPORTANT: get it relative to the workspace root, and not to the project!!
//(historical reasons)
String pathAsString = getPathAsString(p, project);
addTreeItem(pathAsString);
} else if (object instanceof IFile) {
//IMPORTANT: get it relative to the workspace root, and not to the project!!
//(historical reasons)
IFile p = (IFile) object;
String pathAsString = getPathAsString(p.getProjectRelativePath(), project);
pathAsString = "/" + p.getProject().getName() + pathAsString;
if (FileTypesPreferencesPage.isValidZipFile(pathAsString)) {
addTreeItem(pathAsString);
}
}
}
}
}
/**
* @param project
* @return The passed path as a string (used for the selection dialog, as things come relative to the workspace).
*/
private String getPathAsString(IPath p, IProject project) {
String ret = p.toString();
if (ret.startsWith("/") == false) {
ret = "/" + ret;
}
return PyStructureConfigHelpers.convertToProjectRelativePath(project.getFullPath().toString(), ret);
}
/**
* @param pathAsString
*/
private void addTreeItem(String pathAsString) {
if (pathAsString != null && pathAsString.trim().length() > 0) {
// forbid duplicate selections
TreeItem[] items = tree.getItems();
for (int i = 0; i < items.length; i++) {
if (items[i].getText().equals(pathAsString)) {
return;
}
}
TreeItem item = new TreeItem(tree, 0);
item.setText(pathAsString);
item.setImage(PydevPlugin.getImageCache().get(getImageConstant()));
}
}
protected void addTreeItem(String key, String value) {
if (key != null && key.trim().length() > 0 && value != null && value.trim().length() > 0) {
TreeItem item = new TreeItem(tree, 0);
item.setText(new String[] { key, value });
item.setImage(PydevPlugin.getImageCache().get(getImageConstant()));
}
}
public String getTreeItemsAsStr() {
if (editingStyle != EDITING_STYLE_ARRAY_OF_STRINGS) {
throw new AssertionError("Can only call it if we're dealing with array of strings.");
}
FastStringBuffer ret = new FastStringBuffer();
TreeItem[] items = tree.getItems();
for (int i = 0; i < items.length; i++) {
String text = items[i].getText();
if (text != null && text.trim().length() > 0) {
if (ret.length() > 0) {
ret.append("|");
}
ret.append(text);
}
}
return ret.toString();
}
public Map<String, String> getTreeItemsAsMap() {
if (editingStyle != EDITING_STYLE_MAP_OF_STRINGS) {
throw new AssertionError("Can only call it if we're dealing with map of strings.");
}
HashMap<String, String> ret = new HashMap<String, String>();
TreeItem[] items = tree.getItems();
for (int i = 0; i < items.length; i++) {
String text0 = items[i].getText(0);
String text1 = items[i].getText(1);
if (text0 != null && text0.trim().length() > 0 && text1 != null && text1.trim().length() > 0) {
ret.put(text0.trim(), text1.trim());
}
}
return ret;
}
//Things to customize (remove has a default implementation because it's usually the same)
protected String getButtonRemoveText() {
return "Remove";
}
protected String getButtonEditText() {
return "Edit";
}
protected void handleRemove() {
TreeItem[] selection = tree.getSelection();
for (int i = 0; i < selection.length; i++) {
selection[i].dispose();
}
}
protected abstract String getButtonLabel(int i);
protected abstract String getImageConstant();
protected abstract void handleAddButtonSelected(int nButton);
/**
* If a subclass specified that edit should be treated, it should also implement the handleEdit.
*/
protected void handleEdit() {
throw new RuntimeException("Not implemented");
}
protected int getNumberOfAddButtons() {
return 3;
}
}