/*******************************************************************************
* Copyright (c) 2006 IBM Corporation.
* 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:
* IBM Corporation - Jeff Briggs, Henry Hughes, Ryan Morse
*******************************************************************************/
package org.eclipse.linuxtools.systemtap.graphing.ui.wizards.graph;
import java.text.MessageFormat;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.linuxtools.internal.systemtap.graphing.ui.Localization;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
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.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
/**
* @since 2.1
*/
public class SelectGraphAndSeriesWizardPage extends WizardPage implements
Listener {
private Button[] btnGraphs;
private SelectGraphAndSeriesWizard wizard;
private Text txtTitle;
private Combo cboXItem;
private Combo[] cboYItems;
private Label[] lblYItems;
private GraphModel model;
private boolean[] deleted;
public SelectGraphAndSeriesWizardPage() {
super("selectGraphAndSeries"); //$NON-NLS-1$
setTitle(Localization.getString("SelectGraphAndSeriesWizardPage.SelectGraphAndSeries")); //$NON-NLS-1$
}
@Override
public void createControl(Composite parent) {
wizard = (SelectGraphAndSeriesWizard) getWizard();
model = wizard.model;
boolean edit = wizard.isEditing();
// Set the layout data
Composite comp = new Composite(parent, SWT.NONE);
comp.setLayout(new GridLayout());
Group cmpGraphOptsGraph = new Group(comp, SWT.SHADOW_ETCHED_IN);
cmpGraphOptsGraph.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
RowLayout rowLayout = new RowLayout();
rowLayout.type = SWT.HORIZONTAL;
rowLayout.spacing = 10;
cmpGraphOptsGraph.setLayout(rowLayout);
cmpGraphOptsGraph.setText(Localization.getString("SelectGraphAndSeriesWizardPage.Graph")); //$NON-NLS-1$
String[] graphIDs = GraphFactory.getAvailableGraphs(wizard.model.getDataSet());
int btnGraphSelected = -1;
btnGraphs = new Button[graphIDs.length];
for (int i = 0; i < btnGraphs.length; i++) {
btnGraphs[i] = new Button(cmpGraphOptsGraph, SWT.RADIO);
btnGraphs[i].setImage(GraphFactory.getGraphImage(graphIDs[i]));
btnGraphs[i].addListener(SWT.Selection, this);
btnGraphs[i].setData(graphIDs[i]);
btnGraphs[i].setToolTipText(GraphFactory.getGraphName(btnGraphs[i].getData().toString()) + "\n\n" + //$NON-NLS-1$
GraphFactory.getGraphDescription(btnGraphs[i].getData().toString()));
if (btnGraphSelected == -1 && wizard.isEditing() && graphIDs[i].equals(wizard.model.getGraphID())) {
btnGraphs[i].setSelection(true);
btnGraphSelected = i;
}
}
ScrolledComposite scrolledComposite = new ScrolledComposite(comp, SWT.V_SCROLL | SWT.BORDER);
scrolledComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
scrolledComposite.setExpandHorizontal(true);
Composite cmpGraphOptsSeries = new Composite(scrolledComposite, SWT.NONE);
cmpGraphOptsSeries.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
cmpGraphOptsSeries.setLayout(new GridLayout(2, false));
scrolledComposite.setContent(cmpGraphOptsSeries);
Label lblTitle = new Label(cmpGraphOptsSeries, SWT.NONE);
lblTitle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
lblTitle.setText(Localization.getString("SelectGraphAndSeriesWizardPage.Title")); //$NON-NLS-1$
txtTitle = new Text(cmpGraphOptsSeries, SWT.BORDER);
txtTitle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
if (edit) {
txtTitle.setText(model.getGraphData().title);
}
txtTitle.addModifyListener(e -> checkErrors(false));
// Add the data series widgets
String[] labels = model.getSeries();
cboYItems = new Combo[!edit ? labels.length : Math.max(labels.length, model.getYSeries().length)];
lblYItems = new Label[cboYItems.length];
deleted = new boolean[cboYItems.length + 1];
Label lblXItem = new Label(cmpGraphOptsSeries, SWT.NONE);
lblXItem.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
lblXItem.setText(Localization.getString("SelectGraphAndSeriesWizardPage.XSeries")); //$NON-NLS-1$
cboXItem = new Combo(cmpGraphOptsSeries, SWT.DROP_DOWN | SWT.READ_ONLY);
cboXItem.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
cboXItem.addSelectionListener(new ComboSelectionListener());
cboXItem.add(Localization.getString("SelectGraphAndSeriesWizardPage.RowID")); //$NON-NLS-1$
for (int i = 0; i < cboYItems.length; i++) {
lblYItems[i] = new Label(cmpGraphOptsSeries, SWT.NONE);
lblYItems[i].setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
lblYItems[i].setText(MessageFormat.format(Localization.getString("SelectGraphAndSeriesWizardPage.YSeries"), i)); //$NON-NLS-1$
cboYItems[i] = new Combo(cmpGraphOptsSeries, SWT.DROP_DOWN | SWT.READ_ONLY);
cboYItems[i].setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
cboYItems[i].addSelectionListener(new ComboSelectionListener());
if (i > 0) {
cboYItems[i].add(Localization.getString("SelectGraphAndSeriesWizardPage.NA")); //$NON-NLS-1$
cboYItems[i].setVisible(false);
lblYItems[i].setVisible(false);
}
}
for (int j, i = 0; i < labels.length; i++) {
cboXItem.add(labels[i]);
for (j = 0; j < lblYItems.length; j++) {
cboYItems[j].add(labels[i]);
}
}
int selected;
if (!edit) {
cboXItem.select(0);
cboYItems[0].select(0);
} else {
selected = model.getXSeries();
if (selected < labels.length) {
cboXItem.select(selected + 1);
} else {
cboXItem.add(Localization.getString("SelectGraphAndSeriesWizardPage.Deleted"), 0); //$NON-NLS-1$
cboXItem.select(0);
deleted[0] = true;
}
selected = model.getYSeries()[0];
if (selected < labels.length) {
cboYItems[0].select(selected);
} else {
cboYItems[0].add(Localization.getString("SelectGraphAndSeriesWizardPage.Deleted"), 0); //$NON-NLS-1$
cboYItems[0].select(0);
deleted[1] = true;
}
}
boolean cvisible = true;
for (int i = 1; i < cboYItems.length; i++) {
if (!edit || model.getYSeries().length <= i) {
cboYItems[i].select(selected = 0);
} else {
selected = model.getYSeries()[i];
if (selected < labels.length) {
cboYItems[i].select(selected + 1);
} else {
cboYItems[i].add(Localization
.getString("SelectGraphAndSeriesWizardPage.Deleted"), 0); //$NON-NLS-1$
cboYItems[i].select(0);
deleted[i + 1] = true;
}
}
cboYItems[i].setVisible(cvisible);
lblYItems[i].setVisible(cvisible);
cvisible = (selected > 0);
}
// Select one of the graph types by default, rather than blank choice
if (!edit) {
btnGraphs[0].setSelection(true);
saveDataToModelGraph(graphIDs[0]);
} else if (btnGraphSelected == -1) {
saveDataToModelGraph(null);
}
cmpGraphOptsSeries.pack();
setControl(comp);
checkErrors(true);
}
@Override
public void handleEvent(Event event) {
if (event.widget instanceof Button) {
saveDataToModelGraph(((Button) event.widget).getData().toString());
checkErrors(false);
wizard.getContainer().updateButtons();
}
}
@Override
public boolean canFlipToNextPage() {
return false;
}
@Override
public boolean isPageComplete() {
return saveDataToModelSeries();
}
/**
* Saves the choice of graph type to the model.
* @param selected The ID of the selected graph.
*/
private void saveDataToModelGraph(String selected) {
model.setGraph(selected);
}
/**
* Saves all information pertaining to series data & naming to the model.
* @return <code>true</code> if there are no conflicts in series data selection,
* <code>false</code> otherwise. In the case of the latter, no data is saved.
*/
private boolean saveDataToModelSeries() {
if (getErrorMessage() == null) {
model.setTitle(txtTitle.getText());
model.setXSeries(cboXItem.getSelectionIndex() - 1);
int i, count;
for (i = 1, count = 1; i < cboYItems.length
&& 0 != cboYItems[i].getSelectionIndex(); i++) {
count++;
}
int[] ySeries = new int[count];
ySeries[0] = cboYItems[0].getSelectionIndex();
for (i = 1; i < count; i++) {
ySeries[i] = cboYItems[i].getSelectionIndex() - 1;
}
model.setYSeries(ySeries);
return true;
}
return false;
}
private void markAsDuplicate(Combo item, Boolean bad) {
item.setForeground(item.getDisplay().getSystemColor(
bad ? SWT.COLOR_RED : SWT.COLOR_BLACK));
}
private boolean isSeriesUnique() {
Combo item = cboXItem;
int i = 0;
do {
if (item.isVisible() && item.getForeground().equals(
item.getDisplay().getSystemColor(SWT.COLOR_RED))) {
return false;
}
if (i == cboYItems.length) {
return true;
}
item = cboYItems[i++];
} while (true);
}
/**
* Checks for conflicts in data selection, and marks them. (An example of a conflict
* is two Y-series fields set to the same output value.)
* @return <code>true</code> if there is no conflict, <code>false</code> otherwise.
*/
private boolean findAndMarkDuplicates() {
boolean foundDuplicate = false;
// Undo duplicate marking, as it is to be updated.
markAsDuplicate(cboXItem, false);
for (int i = 0; i < cboYItems.length; i++) {
markAsDuplicate(cboYItems[i], false);
}
for (int j, i = 0; i < cboYItems.length; i++) {
if (cboYItems[i].isVisible() && !deleted[i + 1]) {
// Find duplicates by comparing selection indices. Every combo has an
// extra selection before column names (Row Num or NA), except Y-series 0.
int offset = (i == 0 ? 1 : 0);
for (j = i + 1; j < cboYItems.length; j++) {
try {
if (!deleted[j + 1]
&& cboYItems[j].isVisible()
&& cboYItems[i].getSelectionIndex() + offset == cboYItems[j]
.getSelectionIndex()) {
markAsDuplicate(cboYItems[i], true);
markAsDuplicate(cboYItems[j], true);
foundDuplicate = true;
}
} catch (Exception e) {
// If a cboYItem has no item selected, don't mark any duplicates. Ignore.
}
}
if (deleted[0]) {
continue;
}
try {
int selection = cboYItems[i].getSelectionIndex() + offset;
if (selection != 0 && selection == cboXItem.getSelectionIndex()) {
markAsDuplicate(cboYItems[i], true);
markAsDuplicate(cboXItem, true);
foundDuplicate = true;
}
} catch (Exception e) {
// Ignore for same reason as above.
}
}
}
return !foundDuplicate;
}
/**
* Checks for deleted/unselected series entries.
* @return <code>true if some value is not selected, <code>false</code> otherwise.
*/
private boolean isSeriesDeleted() {
for (int i = 0; i < deleted.length; i++) {
if (deleted[i]) {
return true;
}
}
return false;
}
@Override
public void dispose() {
super.dispose();
if (null != btnGraphs) {
for (int i = 0; i < btnGraphs.length; i++) {
btnGraphs[i].dispose();
}
}
btnGraphs = null;
if (null != txtTitle) {
txtTitle.dispose();
}
txtTitle = null;
if (null != cboXItem) {
cboXItem.dispose();
}
cboXItem = null;
if (null != cboYItems) {
for (int i = 0; i < cboYItems.length; i++) {
if (null != cboYItems[i]) {
cboYItems[i].dispose();
}
cboYItems[i] = null;
if (null != lblYItems[i]) {
lblYItems[i].dispose();
}
lblYItems[i] = null;
}
}
cboYItems = null;
lblYItems = null;
model = null;
}
/**
* This class is responsible for updating the menu elements whenever
* the user interacts with them. Namely, it checks for naming errors
* and invalid series selections, and handles display of Y-series combo boxes.
*/
private class ComboSelectionListener implements SelectionListener {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
@Override
public void widgetSelected(SelectionEvent e) {
Combo source = (Combo) e.getSource();
if (cboXItem.equals(source)) {
if (deleted[0] && cboXItem.getSelectionIndex() != 0) {
cboXItem.remove(0);
deleted[0] = false;
}
} else {
for (int i = 0; i < cboYItems.length; i++) {
if (deleted[i + 1] && cboYItems[i].equals(source)
&& cboYItems[i].getSelectionIndex() != 0) {
cboYItems[i].remove(0);
deleted[i + 1] = false;
break;
}
}
boolean setVisible = true;
if (GraphFactory.isMultiGraph(model.getGraphID())) {
for (int i = 1; i < cboYItems.length; i++) {
cboYItems[i].setVisible(setVisible);
lblYItems[i].setVisible(setVisible);
if (!setVisible && deleted[i + 1]) {
cboYItems[i].remove(0);
deleted[i + 1] = false;
cboYItems[i].select(0);
}
if (deleted[i + 1]
|| (cboYItems[i].getSelectionIndex() > 0
&& cboYItems[i].isVisible())) {
setVisible = true;
} else {
setVisible = false;
}
}
}
}
checkErrors(true);
}
}
private void checkErrors(boolean markDuplicates) {
boolean isUnique = markDuplicates ? findAndMarkDuplicates() : isSeriesUnique();
if (model.getGraphID() == null) {
setErrorMessage(Localization
.getString("SelectGraphAndSeriesWizardPage.NoGraphType")); //$NON-NLS-1$
} else if (!isUnique) {
setErrorMessage(Localization
.getString("SelectGraphAndSeriesWizardPage.SeriesNotUnique")); //$NON-NLS-1$
} else if (isSeriesDeleted()) {
setErrorMessage(Localization
.getString("SelectGraphAndSeriesWizardPage.SeriesDeleted")); //$NON-NLS-1$
} else if (txtTitle.getText().length() == 0) {
setErrorMessage(Localization
.getString("SelectGraphAndSeriesWizardPage.TitleNotSet")); //$NON-NLS-1$
} else {
setErrorMessage(null);
}
getWizard().getContainer().updateButtons();
}
}