/******************************************************************************* * Copyright (c) 2015 GoPivotal, Inc. * 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: * GoPivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.launch.properties; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.fieldassist.ContentProposalAdapter; import org.eclipse.jface.fieldassist.IContentProposalProvider; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.CellLabelProvider; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.ICheckStateProvider; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import org.springframework.ide.eclipse.boot.core.BootActivator; import org.springframework.ide.eclipse.boot.launch.AbstractBootLaunchConfigurationDelegate.PropVal; import org.springframework.ide.eclipse.boot.launch.BootLaunchConfigurationDelegate; import org.springframework.ide.eclipse.boot.launch.util.ILaunchConfigurationTabSection; import org.springframework.ide.eclipse.boot.launch.util.TextCellEditorWithContentProposal; import org.springframework.ide.eclipse.editor.support.completions.ProposalProcessor; import org.springframework.ide.eclipse.editor.support.util.StringUtil; import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression; import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable; import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections; import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection; /** * An IPageSection that contains a table-based properties editor. * * @author Kris De Volder */ public class PropertiesTableSection extends WizardPageSection implements ILaunchConfigurationTabSection { private static final String[] COLUMN_NAMES = {"property", "value"}; private static final int PROPERTY_NAME_COLUMN = 0; private static final KeyStroke CTRL_SPACE = KeyStroke.getInstance(SWT.CTRL, SWT.SPACE); //private static final int PROPERTY_VALUE_COLUMN = 1; public class CellEditorSupport extends EditingSupport { private CellEditor editor; private int col; public CellEditorSupport(int col) { super(tableViewer); this.col = col; if (col==PROPERTY_NAME_COLUMN) { IContentProposalProvider proposalProvider = ProposalExtensionPoint.create(project); this.editor = new TextCellEditorWithContentProposal(tableViewer.getTable(), proposalProvider, CTRL_SPACE, ProposalProcessor.AUTO_ACTIVATION_CHARS ).setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); } else { this.editor = new TextCellEditor(tableViewer.getTable()); } } @Override protected CellEditor getCellEditor(Object element) { return editor; } @Override protected boolean canEdit(Object element) { String val = getColumnValue(element, col); return val!=null; } @Override protected Object getValue(Object element) { return getColumnValue(element, col); } @Override protected void setValue(Object element, Object value) { setColumnValue(element, col, value); tableViewer.refresh(element); } } private class CheckStateSynchronizer implements ICheckStateProvider, ICheckStateListener { @Override public boolean isGrayed(Object element) { return false; } @Override public boolean isChecked(Object element) { if (element instanceof PropVal) { PropVal prop = (PropVal) element; return prop.isChecked; } return false; } @Override public void checkStateChanged(CheckStateChangedEvent event) { PropVal element = (PropVal) event.getElement(); element.isChecked = event.getChecked(); dirtyState.setValue(true); } } @Override public LiveVariable<Boolean> getDirtyState() { return dirtyState; } public PropertiesTableSection(IPageWithSections owner, LiveExpression<IProject> project) { super(owner); this.project = project; } private final LiveExpression<IProject> project; private LiveVariable<Boolean> dirtyState = new LiveVariable<>(false); private CheckboxTableViewer tableViewer; private List<PropVal> props = new ArrayList<>(); private CheckStateSynchronizer checkStateProvider = new CheckStateSynchronizer(); /** * Remembers element that was last clicked by mouse, so that we * can use it in executing context menu actions on it. */ private PropVal lastMouseDownTarget; private MouseListener clickListener = new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { PropVal clickedElement = getElementUnder(e); lastMouseDownTarget = clickedElement; if (e.button==1) { if (clickedElement==null) { lastMouseDownTarget = null; // System.out.println("NO cell"); addNewRow(true); } } } private PropVal getElementUnder(MouseEvent e) { TableItem item = tableViewer.getTable().getItem(new Point(e.x, e.y)); return item == null ? null : (PropVal) item.getData(); } }; @Override public void createContents(Composite parent) { Label label = new Label(parent, SWT.NONE); label.setText("Override properties:"); tableViewer = CheckboxTableViewer.newCheckList(parent, SWT.FULL_SELECTION); tableViewer.setColumnProperties(COLUMN_NAMES); tableViewer.getTable().setHeaderVisible(true); tableViewer.getTable().setLinesVisible(true); tableViewer.setContentProvider(ArrayContentProvider.getInstance()); tableViewer.setCheckStateProvider(checkStateProvider); tableViewer.addCheckStateListener(checkStateProvider); tableViewer.getTable().addMouseListener(clickListener); createColumns(); // make sure to call before 'setInput' or labels won't be updated. createContextMenu(); //TODO: Add TableResizeHelper?? But it doesn't play nice on GTK and doesn't like // Checkbox tables. tableViewer.setInput(props); GridDataFactory.fillDefaults().grab(true, true).applyTo(tableViewer.getTable()); } private void createContextMenu() { Table table = tableViewer.getTable(); Menu contextMenu = new Menu(table); addMenu(contextMenu, "Delete Row", new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { PropVal toRemove = lastMouseDownTarget; if (toRemove!=null) { props.remove(toRemove); tableViewer.remove(toRemove); dirtyState.setValue(true); } } }); addMenu(contextMenu, "Add Row", new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { addNewRow(false); } }); table.setMenu(contextMenu); } /** * Add a new row and start editing it. If reuseExistingEmptyRow * is true then will check if the last row in table is a 'empty' * row and reuse that rather than create another empty row. */ private void addNewRow(boolean reuseExistingEmptyRow) { PropVal newProp = null; if (reuseExistingEmptyRow) { newProp = findEmptyProp(); } if (newProp==null) { newProp = new PropVal("", "", true); props.add(newProp); tableViewer.add(newProp); } tableViewer.editElement(newProp, 0); } private PropVal findEmptyProp() { if (!props.isEmpty()) { PropVal last = props.get(props.size()-1); if (!StringUtil.hasText(last.name)) { return last; } } return null; } private void addMenu(Menu contextMenu, String label, SelectionAdapter handler) { MenuItem mnu = new MenuItem(contextMenu, SWT.PUSH); mnu.setText(label); mnu.addSelectionListener(handler); } private String getColumnValue(Object element, final int col) { if (element instanceof PropVal) { if (col==PROPERTY_NAME_COLUMN) { return ((PropVal) element).name; } return ((PropVal) element).value; } return null; } private void setColumnValue(Object element, int col, Object value) { if (element instanceof PropVal) { if (col==PROPERTY_NAME_COLUMN) { ((PropVal) element).name = (String)value; } else { ((PropVal) element).value = (String)value; } dirtyState.setValue(true); } } private void createColumns() { for (int i = 0; i < COLUMN_NAMES.length; i++) { final int _i = i; String colname = COLUMN_NAMES[i]; TableViewerColumn col = new TableViewerColumn(tableViewer, SWT.NONE); col.getColumn().setWidth(200); col.getColumn().setText(colname); col.setLabelProvider(new CellLabelProvider() { @Override public void update(ViewerCell cell) { Object element = cell.getElement(); cell.setText(getColumnValue(element, _i)); } }); col.setEditingSupport(new CellEditorSupport(i)); } } @Override public void initializeFrom(ILaunchConfiguration conf) { try { props = BootLaunchConfigurationDelegate.getProperties(conf); dirtyState.setValue(false); if (tableViewer!=null) { tableViewer.setInput(props); } } catch (Exception e) { BootActivator.log(e); } } @Override public void performApply(ILaunchConfigurationWorkingCopy conf) { BootLaunchConfigurationDelegate.setProperties(conf, props); dirtyState.setValue(false); } @Override public void setDefaults(ILaunchConfigurationWorkingCopy conf) { BootLaunchConfigurationDelegate.clearProperties(conf); } }