/*******************************************************************************
* Copyright (c) 2008, 2011 Thomas Holland (thomas@innot.de) 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:
* Thomas Holland - initial API and implementation
*******************************************************************************/
package de.innot.avreclipse.ui.preferences;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
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.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.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Widget;
import org.osgi.service.prefs.BackingStoreException;
import de.innot.avreclipse.AVRPlugin;
import de.innot.avreclipse.core.avrdude.ProgrammerConfig;
import de.innot.avreclipse.core.avrdude.ProgrammerConfigManager;
/**
* A special Field Editor to edit the list of AVRDude programmer configurations.
* <p>
* This editor has a Table of all Programmer Configurations, which can be edited, removed and added.
* </p>
* <p>
* It does not work on a PreferenceStore, because the list of all configurations can only be
* gathered directly from the Preferences. It does however support the Apply, Cancel and partially
* the Defaults actions of a FieldEditorPreferencePage.
* </p>
* <p>
* All modifications of programmer configurations are only persisted when the OK or Apply actions
* occur.
* </p>
*
* @author Thomas Holland
* @since 2.2
*
*/
public class ProgConfigListFieldEditor extends FieldEditor {
/** The Table Control */
private Table fTableControl;
/** The button box Composite containing the Add, Remove and Edit buttons */
private Composite fButtonComposite;
// The GUI Widgets
private Button fAddButton;
private Button fRemoveButton;
private Button fEditButton;
/**
* The list of removed configurations. They will be removed in the {@link #doStore()} method
*/
private List<ProgrammerConfig> fRemovedConfigs;
private final ProgrammerConfigManager fCfgManager = ProgrammerConfigManager.getDefault();
/**
* Creates a AVRDude Programmers Configuration List field editor.
* <p>
* Because this field editor does not work on a PreferenceStore, it does not need to be passed
* to the constructor.
* </p>
*
* @param labelText
* the label text of the field editor
* @param parent
* the parent of the field editor's control
*/
public ProgConfigListFieldEditor(String label, Composite parent) {
super();
super.setLabelText(label);
createControl(parent);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.preference.FieldEditor#adjustForNumColumns(int)
*/
@Override
protected void adjustForNumColumns(int numColumns) {
// The Table gets all but one column, the ButtonBox one column.
Control control = getLabelControl();
((GridData) control.getLayoutData()).horizontalSpan = numColumns;
((GridData) fTableControl.getLayoutData()).horizontalSpan = numColumns - 1;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.preference.FieldEditor#doFillIntoGrid(org.eclipse.swt.widgets.Composite,
* int)
*/
@Override
protected void doFillIntoGrid(Composite parent, int numColumns) {
// Add the three controls:
// Label, Table and Buttonbox
Control control = getLabelControl(parent);
GridData gd = new GridData();
gd.horizontalSpan = numColumns;
control.setLayoutData(gd);
fTableControl = getTableControl(parent);
gd = new GridData(SWT.FILL, SWT.FILL, true, true, numColumns - 1, 1);
fTableControl.setLayoutData(gd);
fButtonComposite = getButtonBoxComposite(parent);
gd = new GridData();
gd.verticalAlignment = GridData.BEGINNING;
fButtonComposite.setLayoutData(gd);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.preference.FieldEditor#doLoad()
*/
@Override
protected void doLoad() {
if (fTableControl != null) {
Set<String> allconfigids = fCfgManager.getAllConfigIDs();
for (String configid : allconfigids) {
if (configid.length() > 0) {
ProgrammerConfig config = fCfgManager.getConfig(configid);
TableItem item = new TableItem(fTableControl, SWT.NONE);
item.setText(new String[] { config.getName(), config.getDescription() });
item.setData(config);
}
}
}
// init the list of removed configurations
fRemovedConfigs = new ArrayList<ProgrammerConfig>();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.preference.FieldEditor#doLoadDefault()
*/
@Override
protected void doLoadDefault() {
// No defaults supported for the List of Programmer Configurations
// however we just reload the existing configurations. (loosing all
// modifications)
fTableControl.removeAll();
doLoad();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.preference.FieldEditor#doStore()
*/
@Override
protected void doStore() {
// Save all configs still in the table, then remove all configs marked
// for removal
TableItem[] allitems = fTableControl.getItems();
// save all current configs to the persistent store
for (TableItem item : allitems) {
ProgrammerConfig config = (ProgrammerConfig) item.getData();
try {
fCfgManager.saveConfig(config);
} catch (BackingStoreException e) {
IStatus status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID,
"Can't save Programmer Configuration [" + config.getName()
+ "] to the preference storage area", e);
AVRPlugin.getDefault().log(status);
ErrorDialog.openError(fTableControl.getShell(), "Programmer Configuration Error",
null, status);
}
}
// now delete the Configs marked as removed
for (ProgrammerConfig config : fRemovedConfigs) {
try {
fCfgManager.deleteConfig(config);
} catch (BackingStoreException e) {
IStatus status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID,
"Can't delete Programmer Configuration [" + config.getName()
+ "] from the preference storage area", e);
AVRPlugin.getDefault().log(status);
ErrorDialog.openError(fTableControl.getShell(), "Programmer Configuration Error",
null, status);
}
}
fRemovedConfigs.clear();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.preference.FieldEditor#getNumberOfControls()
*/
@Override
public int getNumberOfControls() {
// Two: List and Buttons Composite
return 2;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.preference.FieldEditor#setFocus()
*/
@Override
public void setFocus() {
if (fTableControl != null) {
fTableControl.setFocus();
}
}
/**
* Returns this field editors Table control.
*
* @param parent
* the parent control
* @return the list control
*/
public Table getTableControl(Composite parent) {
if (fTableControl == null) {
// Create the Table control, add two columns (name and description)
// and set up the required listeners
fTableControl = new Table(parent, SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION
| SWT.V_SCROLL | SWT.H_SCROLL);
fTableControl.setFont(parent.getFont());
fTableControl.setLinesVisible(true);
fTableControl.setHeaderVisible(true);
fTableControl.addSelectionListener(new SelectionAdapter() {
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
*/
@Override
public void widgetSelected(SelectionEvent e) {
Widget widget = e.widget;
if (widget == fTableControl) {
selectionChanged();
}
}
});
fTableControl.addDisposeListener(new DisposeListener() {
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
*/
public void widgetDisposed(DisposeEvent event) {
fTableControl = null;
}
});
TableColumn column = new TableColumn(fTableControl, SWT.NONE);
column.setText("Configuration");
column.setWidth(100);
column = new TableColumn(fTableControl, SWT.NONE);
column.setText("Description");
column.setWidth(200);
} else { // fTableControl != null
checkParent(fTableControl, parent);
}
return fTableControl;
}
/**
* Returns this field editor's button box containing the Add, Remove and Edit buttons.
*
* @param parent
* the parent control
* @return the button box
*/
public Composite getButtonBoxComposite(Composite parent) {
if (fButtonComposite == null) {
fButtonComposite = new Composite(parent, SWT.NULL);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = 0;
fButtonComposite.setLayout(layout);
createButtons(fButtonComposite);
fButtonComposite.addDisposeListener(new DisposeListener() {
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
*/
public void widgetDisposed(DisposeEvent event) {
fAddButton = null;
fRemoveButton = null;
fEditButton = null;
}
});
} else {
checkParent(fButtonComposite, parent);
}
selectionChanged();
return fButtonComposite;
}
/**
* Creates the Add, Remove and Edit buttons in the given button box.
*
* @param box
* the box for the buttons
*/
private void createButtons(Composite box) {
fAddButton = createPushButton(box, "Add...");
fEditButton = createPushButton(box, "Edit...");
fRemoveButton = createPushButton(box, "Remove");
}
/**
* Helper method to create a push button.
*
* @param parent
* the parent control
* @param label
* the button's label text
* @return Button
*/
private Button createPushButton(Composite parent, String label) {
Button button = new Button(parent, SWT.PUSH);
button.setText(label);
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(new SelectionAdapter() {
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
*/
@Override
public void widgetSelected(SelectionEvent e) {
Widget widget = e.widget;
if (widget == fAddButton) {
editButtonAction(true);
} else if (widget == fRemoveButton) {
removeButtonAction();
} else if (widget == fEditButton) {
editButtonAction(false);
}
}
});
return button;
}
/**
* Enable / Disable Buttons as required.
* <p>
* The Remove and Edit Buttons are only enabled, when an item is selected in the Table.
* </p>
* <p>
* Called after each change of the Table.
* </p>
*/
private void selectionChanged() {
int index = fTableControl.getSelectionIndex();
fRemoveButton.setEnabled(index >= 0);
fEditButton.setEnabled(index >= 0);
// TODO Is this necessary?
fTableControl.redraw();
}
/**
* Remove the selected configuration.
* <p>
* The config is stored in the <code>fRemovedConfigs</code> list. All removed are only
* physically removed in the {@link #doStore()} method.
* </p>
* <p>
* Called when the remove button has been clicked.
* </p>
*/
private void removeButtonAction() {
setPresentsDefaultValue(false);
int index = fTableControl.getSelectionIndex();
if (index >= 0) {
TableItem ti = fTableControl.getItem(index);
fRemovedConfigs.add((ProgrammerConfig) ti.getData());
fTableControl.remove(index);
selectionChanged();
}
}
/**
* Adds a new configuration or edit the currently selected config.
* <p>
* Called when either the add or the edit button has been clicked.
* </p>
*/
private void editButtonAction(boolean createnew) {
setPresentsDefaultValue(false);
ProgrammerConfig config = null;
TableItem ti = null;
// Create a list of all currently available configurations
// This is used by the editor to avoid name clashes
// (a configuration name needs to be unique)
Set<String> allconfigs = new HashSet<String>();
TableItem[] allitems = fTableControl.getItems();
for (TableItem item : allitems) {
allconfigs.add(item.getText(0));
}
if (createnew) { // new config
// Create a new configuration with a default name
// (with a trailing running number if required),
String basename = "New Configuration";
String defaultname = basename;
int i = 1;
while (allconfigs.contains(defaultname)) {
defaultname = basename + " (" + i++ + ")";
}
config = fCfgManager.createNewConfig();
config.setName(defaultname);
} else { // edit existing config
// Get the ProgrammerConfig from the selected TableItem
ti = fTableControl.getItem(fTableControl.getSelectionIndex());
config = (ProgrammerConfig) ti.getData();
}
// Open the Config Editor.
// If the OK Button was selected, the modified Config is fetched from
// the Dialog and the relevant TableItem is updated.
AVRDudeConfigEditor dialog = new AVRDudeConfigEditor(fTableControl.getShell(), config,
allconfigs);
if (dialog.open() == Window.OK) {
// OK Button selected:
ProgrammerConfig newconfig = dialog.getResult();
if (createnew) {
ti = new TableItem(fTableControl, SWT.NONE);
}
// Change the TableItem
if (ti != null) {
ti.setText(new String[] { newconfig.getName(), newconfig.getDescription() });
ti.setData(newconfig);
}
selectionChanged();
}
}
}