/*******************************************************************************
* Copyright (c) 2006-2011 Gluster, Inc. <http://www.gluster.com>
* This file is part of Gluster Management Console.
*
* Gluster Management Console is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Gluster Management Console is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*******************************************************************************/
package org.gluster.storage.management.console.views.pages;
import java.util.List;
import org.apache.commons.lang.WordUtils;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnLayoutData;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FillLayout;
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.Display;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.gluster.storage.management.console.GlusterDataModelManager;
import org.gluster.storage.management.console.VolumeOptionsContentProvider;
import org.gluster.storage.management.console.VolumeOptionsTableLabelProvider;
import org.gluster.storage.management.console.utils.GUIHelper;
import org.gluster.storage.management.core.constants.CoreConstants;
import org.gluster.storage.management.core.model.DefaultClusterListener;
import org.gluster.storage.management.core.model.Event;
import org.gluster.storage.management.core.model.Volume;
import org.gluster.storage.management.core.model.VolumeOption;
import org.gluster.storage.management.core.model.VolumeOptionInfo;
public class VolumeOptionsPage extends Composite {
private final FormToolkit toolkit = new FormToolkit(Display.getCurrent());
private TableViewer tableViewer;
private GUIHelper guiHelper = GUIHelper.getInstance();
private Volume volume;
private DefaultClusterListener clusterListener;
private Text filterText;
private List<VolumeOptionInfo> defaultVolumeOptions = GlusterDataModelManager.getInstance()
.getVolumeOptionsInfo();
public enum OPTIONS_TABLE_COLUMN_INDICES {
OPTION_KEY, OPTION_VALUE
};
private static final String[] OPTIONS_TABLE_COLUMN_NAMES = new String[] { "Option Key", "Option Value" };
private Button addTopButton;
private Button addBottomButton;
private TableViewerColumn keyColumn;
private OptionKeyEditingSupport keyEditingSupport;
public VolumeOptionsPage(final Composite parent, int style, Volume volume) {
super(parent, style);
this.volume = volume;
toolkit.adapt(this);
toolkit.paintBordersFor(this);
setupPageLayout();
addTopButton = createAddButton();
filterText = guiHelper.createFilterText(toolkit, this);
setupOptionsTableViewer(filterText);
addBottomButton = createAddButton();
if (defaultVolumeOptions.size() == volume.getOptions().size()) {
setAddButtonsEnabled(false);
}
tableViewer.setInput(volume.getOptions());
parent.layout(); // Important - this actually paints the table
registerListeners(parent);
}
private void setAddButtonsEnabled(boolean enable) {
addTopButton.setEnabled(enable);
addBottomButton.setEnabled(enable);
}
private Button createAddButton() {
return toolkit.createButton(this, "&Add", SWT.FLAT);
}
private void registerListeners(final Composite parent) {
/**
* Ideally not required. However the table viewer is not getting laid out properly on performing
* "maximize + restore" So this is a hack to make sure that the table is laid out again on re-size of the window
*/
addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
parent.layout();
}
});
clusterListener = new DefaultClusterListener() {
@Override
public void volumeChanged(Volume volume, Event event) {
super.volumeChanged(volume, event);
switch (event.getEventType()) {
case VOLUME_OPTIONS_RESET:
if (!tableViewer.getControl().isDisposed()) {
//While reseting the options, clear the filter text before refreshing the tree
filterText.setText("");
tableViewer.refresh();
setAddButtonsEnabled(true);
}
break;
case VOLUME_OPTION_SET:
String key = (String)event.getEventData();
if (isNewOption(volume, key)) {
// option has been set successfully by the user. re-enable the add button and search filter
// textbox
setAddButtonsEnabled(true);
filterText.setEnabled(true);
}
if (defaultVolumeOptions.size() == volume.getOptions().size()) {
setAddButtonsEnabled(false);
}
tableViewer.refresh();
break;
case VOLUME_CHANGED:
tableViewer.refresh();
if(volume.getOptions().size() == defaultVolumeOptions.size()) {
setAddButtonsEnabled(false);
} else {
setAddButtonsEnabled(true);
}
default:
break;
}
}
private boolean isNewOption(Volume volume, String optionKey) {
if (filterText.getText().length() > 0) {
// user has been filtering the contents. adding new option is allowed only when contents are NOT
// filtered. Thus it's impossible that this is a newly added option
return false;
}
// if this is the last option in the volume options, it must be the new option
return optionKey.equals(volume.getOptions().getOptions().get(volume.getOptions().size() - 1).getKey());
}
};
SelectionListener addButtonSelectionListener = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// add an empty option to be filled up by user
volume.setOption("", "");
tableViewer.refresh();
tableViewer.setSelection(new StructuredSelection(getEntry("")));
keyColumn.getViewer().editElement(getEntry(""), 0); // edit newly created entry
// disable the add button AND search filter textbox till user fills up the new option
setAddButtonsEnabled(false);
filterText.setEnabled(false);
}
private VolumeOption getEntry(String key) {
for (VolumeOption entry : volume.getOptions().getOptions()) {
if (entry.getKey().equals(key)) {
return entry;
}
}
return null;
}
};
addTopButton.addSelectionListener(addButtonSelectionListener);
addBottomButton.addSelectionListener(addButtonSelectionListener);
// Make sure that add button is enabled only when search filter textbox is empty
filterText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
if (filterText.getText().length() > 0) {
setAddButtonsEnabled(false);
} else {
if (defaultVolumeOptions.size() == volume.getOptions().size()) {
setAddButtonsEnabled(false);
} else {
setAddButtonsEnabled(true);
}
}
}
});
GlusterDataModelManager.getInstance().addClusterListener(clusterListener);
addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
toolkit.dispose();
if (!(addTopButton.isEnabled() || addBottomButton.isEnabled())) {
// user has selected key, but not added value. Since this is not a valid entry,
// remove the last option (without value) from the volume
volume.getOptions().remove(keyEditingSupport.getEntryBeingAdded().getKey());
}
GlusterDataModelManager.getInstance().removeClusterListener(clusterListener);
}
});
}
private void setupPageLayout() {
final GridLayout layout = new GridLayout(2, false);
layout.verticalSpacing = 10;
layout.marginTop = 10;
setLayout(layout);
}
private void setupOptionsTable(Composite parent) {
Table table = tableViewer.getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);
TableColumnLayout tableColumnLayout = createTableColumnLayout();
parent.setLayout(tableColumnLayout);
setColumnProperties(table, OPTIONS_TABLE_COLUMN_INDICES.OPTION_KEY, SWT.CENTER, 100);
setColumnProperties(table, OPTIONS_TABLE_COLUMN_INDICES.OPTION_VALUE, SWT.CENTER, 100);
}
private TableColumnLayout createTableColumnLayout() {
TableColumnLayout tableColumnLayout = new TableColumnLayout();
ColumnLayoutData defaultColumnLayoutData = new ColumnWeightData(100);
tableColumnLayout.setColumnData(createKeyColumn(), defaultColumnLayoutData);
tableColumnLayout.setColumnData(createValueColumn(), defaultColumnLayoutData);
return tableColumnLayout;
}
private TableColumn createValueColumn() {
TableViewerColumn valueColumn = new TableViewerColumn(tableViewer, SWT.NONE);
valueColumn.getColumn()
.setText(OPTIONS_TABLE_COLUMN_NAMES[OPTIONS_TABLE_COLUMN_INDICES.OPTION_VALUE.ordinal()]);
valueColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element) {
return ((VolumeOption) element).getValue();
}
});
// User can edit value of a volume option
valueColumn.setEditingSupport(new OptionValueEditingSupport(valueColumn.getViewer(), volume));
return valueColumn.getColumn();
}
private TableColumn createKeyColumn() {
keyColumn = new TableViewerColumn(tableViewer, SWT.NONE);
keyColumn.getColumn().setText(OPTIONS_TABLE_COLUMN_NAMES[OPTIONS_TABLE_COLUMN_INDICES.OPTION_KEY.ordinal()]);
keyColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element) {
return ((VolumeOption) element).getKey();
}
@Override
public String getToolTipText(Object element) {
String key = ((VolumeOption) element).getKey();
if (key.isEmpty()) {
return "Click to select a volume option key";
}
VolumeOptionInfo optionInfo = GlusterDataModelManager.getInstance().getVolumeOptionInfo(key);
// Wrap the description before adding to tooltip so that long descriptions are displayed properly
return WordUtils.wrap(optionInfo.getDescription(), 60) + CoreConstants.NEWLINE + "Default value: "
+ optionInfo.getDefaultValue();
}
});
// Editing support required when adding new key
keyEditingSupport = new OptionKeyEditingSupport(keyColumn.getViewer(), volume);
keyColumn.setEditingSupport(keyEditingSupport);
return keyColumn.getColumn();
}
private void createOptionsTableViewer(Composite parent) {
tableViewer = new TableViewer(parent, SWT.FLAT | SWT.FULL_SELECTION | SWT.SINGLE);
tableViewer.setLabelProvider(new VolumeOptionsTableLabelProvider());
tableViewer.setContentProvider(new VolumeOptionsContentProvider());
tableViewer.getTable().setLinesVisible(true);
setupOptionsTable(parent);
}
private Composite createTableViewerComposite() {
Composite tableViewerComposite = new Composite(this, SWT.NO);
tableViewerComposite.setLayout(new FillLayout(SWT.HORIZONTAL));
GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
layoutData.horizontalSpan = 2;
tableViewerComposite.setLayoutData(layoutData);
return tableViewerComposite;
}
private void setupOptionsTableViewer(final Text filterText) {
Composite tableViewerComposite = createTableViewerComposite();
createOptionsTableViewer(tableViewerComposite);
ColumnViewerToolTipSupport.enableFor(tableViewer);
// Create a case insensitive filter for the table viewer using the filter text field
guiHelper.createFilter(tableViewer, filterText, false);
}
private void setColumnProperties(Table table, OPTIONS_TABLE_COLUMN_INDICES columnIndex, int alignment, int weight) {
TableColumn column = table.getColumn(columnIndex.ordinal());
column.setAlignment(alignment);
TableColumnLayout tableColumnLayout = (TableColumnLayout) table.getParent().getLayout();
tableColumnLayout.setColumnData(column, new ColumnWeightData(weight));
}
}