/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.bookmark.ui.editor; import java.util.ArrayList; import java.util.List; import org.eclipse.e4.ui.workbench.swt.internal.copy.FilteredTree; import org.eclipse.e4.ui.workbench.swt.internal.copy.PatternFilter; import org.eclipse.jface.dialogs.DialogMessageArea; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.dialogs.TrayDialog; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.viewers.CellLabelProvider; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; 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.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import au.gov.ga.earthsci.application.ImageRegistry; import au.gov.ga.earthsci.bookmark.model.IBookmark; import au.gov.ga.earthsci.bookmark.model.IBookmarkProperty; import au.gov.ga.earthsci.bookmark.ui.Messages; import au.gov.ga.earthsci.bookmark.ui.editor.IBookmarkEditorMessage.Level; import au.gov.ga.earthsci.common.ui.util.SWTUtil; import au.gov.ga.earthsci.worldwind.common.util.Util; /** * A dialog that allows the editing of an {@link IBookmark} and it's properties. * <p/> * The dialog presents a list view which gives access to available bookmark * property editors (as available via the {@link BookmarkPropertyEditorFactory} * ), and a main editor view which is populated with the fields of the selected * editor. * <p/> * A special "General" editor is provided for editing the top-level bookmark * fields (name etc.). * <p/> * Edits made to fields are only applied when the user presses "OK". If the * dialog is dismissed in any other way no edits are applied to the bookmark. * * @author James Navin (james.navin@ga.gov.au) */ public class BookmarkEditorDialog extends TrayDialog implements IBookmarkEditorListener { private static final int[] SASH_WEIGHTS = new int[] { 30, 70 }; private static final Point DEFAULT_MIN_PAGE_SIZE = new Point(400, 400); /** The current bookmark being edited */ private final IBookmark bookmark; /** The tree menu containing available editors */ private FilteredTree editorFilteredTree; /** The list of available editors */ private List<IBookmarkEditor> editors = new ArrayList<IBookmarkEditor>(); /** The current editor */ private IBookmarkEditor currentEditor; /** The primary scroller that holds the editor and message area etc. */ private ScrolledComposite editorScroller; /** The editor message area, for display of title and errors etc. */ private DialogMessageArea messageArea; /** The 'include in bookmark' question area */ private Composite editorInclusionArea; /** The 'include in bookmark' checkbox */ private Button editorInclusionCheck; /** The container that holds the controls of the editors */ private Composite editorContainer; /** The secondary button bar */ private Composite editorButtonBar; /** The 'fill from current' button */ private Button fillFromCurrentButton; /** The 'restore to originals' button */ private Button resetButton; /** * Create a new dialog for the editing of the given {@link IBookmark} */ public BookmarkEditorDialog(IBookmark bookmark, Shell shell) { super(shell); setShellStyle(SWT.RESIZE | SWT.SHELL_TRIM | SWT.APPLICATION_MODAL); this.bookmark = bookmark; GeneralBookmarkEditor generalBookmarkEditor = new GeneralBookmarkEditor(); generalBookmarkEditor.addListener(this); editors.add(generalBookmarkEditor); for (String type : BookmarkPropertyEditorFactory.getSupportedTypes()) { IBookmarkPropertyEditor editor = BookmarkPropertyEditorFactory.createEditor(type); editor.addListener(this); if (bookmark.hasProperty(type)) { editor.setProperty(bookmark.getProperty(type)); editor.setIncludedInBookmark(true); } editors.add(editor); } } @Override protected Control createDialogArea(Composite parent) { getShell().setText(Messages.BookmarkEditorDialog_DialogTitle); getShell().setImage(ImageRegistry.getInstance().get(ImageRegistry.ICON_EDIT)); // Remove margins etc. from the parent composite final Composite composite = (Composite) super.createDialogArea(parent); GridLayout parentLayout = ((GridLayout) composite.getLayout()); parentLayout.numColumns = 1; parentLayout.marginHeight = 0; parentLayout.marginWidth = 0; parentLayout.verticalSpacing = 0; // Use a sash form to hold the left (property tree) and right (property editor) areas SashForm form = new SashForm(composite, SWT.HORIZONTAL); GridData gd = new GridData(GridData.FILL_BOTH); form.setLayoutData(gd); GridLayout formLayout = new GridLayout(); formLayout.horizontalSpacing = 0; formLayout.marginHeight = 0; formLayout.marginWidth = 0; form.setLayout(formLayout); createPropertyTree(form); createPropertyEditArea(form); form.setWeights(SASH_WEIGHTS); // Separate the button bar from the dialog area Label bottomSeparator = new Label(composite, SWT.HORIZONTAL | SWT.SEPARATOR); bottomSeparator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL)); showEditor(editors.get(0)); return composite; } /** * Create the property tree control (left side) used to display available * {@link IBookmarkProperty}s * <p/> * Expects the {@code parent} to have a {@link GridLayout}. */ private Control createPropertyTree(Composite parent) { PatternFilter filter = new BookmarkPropertyTreeFilter(); filter.setIncludeLeadingWildcard(true); editorFilteredTree = new FilteredTree(parent, SWT.FULL_SELECTION, filter, true); GridData gd = new GridData(GridData.FILL_VERTICAL); editorFilteredTree.setLayoutData(gd); if (editorFilteredTree.getFilterControl() != null) { Composite filterComposite = editorFilteredTree.getFilterControl().getParent(); gd = (GridData) filterComposite.getLayoutData(); gd.verticalIndent = 2; gd.horizontalIndent = 1; } ColumnViewerToolTipSupport.enableFor(editorFilteredTree.getViewer()); editorFilteredTree.getViewer().setContentProvider(new BookmarkPropertyTreeContentProvider()); editorFilteredTree.getViewer().setLabelProvider(new BookmarkPropertyTreeLabelProvider()); editorFilteredTree.getViewer().setComparator(new BookmarkPropertyTreeViewerComparator()); editorFilteredTree.getViewer().setInput(editors); editorFilteredTree.getViewer().setAutoExpandLevel(2); editorFilteredTree.getViewer().setSelection(new StructuredSelection(editors.get(0))); editorFilteredTree.getViewer().addPostSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { if (event.getSelection().isEmpty()) { return; } if (!showEditor((IBookmarkEditor) ((IStructuredSelection) event.getSelection()).getFirstElement())) { handleEditorChangeError(); } } private void handleEditorChangeError() { try { editorFilteredTree.getViewer().removePostSelectionChangedListener(this); editorFilteredTree.getViewer().setSelection(new StructuredSelection(currentEditor)); } finally { editorFilteredTree.getViewer().addPostSelectionChangedListener(this); } } }); return editorFilteredTree; } /** * Create the property edit area (right side) used to contain the * {@link IBookmarkProperty} editors. * <p/> * Expects the {@code parent} to have a {@link GridLayout}. */ protected Control createPropertyEditArea(Composite parent) { final Composite container = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(); layout.numColumns = 2; layout.horizontalSpacing = 0; layout.marginHeight = 0; layout.marginWidth = 0; container.setLayout(layout); final Label leftSeparator = new Label(container, SWT.VERTICAL | SWT.SEPARATOR); leftSeparator.setLayoutData(new GridData(GridData.FILL_VERTICAL | GridData.GRAB_VERTICAL)); editorScroller = new ScrolledComposite(container, SWT.V_SCROLL | SWT.H_SCROLL); editorScroller.setShowFocusedControl(true); editorScroller.setExpandHorizontal(true); editorScroller.setExpandVertical(true); editorScroller.setLayoutData(new GridData(GridData.FILL_BOTH | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL)); editorScroller.setMinSize(DEFAULT_MIN_PAGE_SIZE); final Composite inner = new Composite(editorScroller, SWT.NONE); inner.setLayoutData(new GridData(GridData.FILL_BOTH)); inner.setLayout(new GridLayout()); editorScroller.setContent(inner); editorScroller.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { updateScrollerMinSize(); } }); messageArea = new DialogMessageArea(); messageArea.createContents(inner); GridData gd = new GridData(GridData.FILL_HORIZONTAL); messageArea.setTitleLayoutData(gd); messageArea.setMessageLayoutData(gd); Label separator = new Label(inner, SWT.HORIZONTAL | SWT.SEPARATOR); separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL)); editorInclusionArea = new Composite(inner, SWT.NONE); editorInclusionArea.setLayout(new GridLayout()); editorInclusionArea.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); editorInclusionCheck = new Button(editorInclusionArea, SWT.CHECK); editorInclusionCheck.setText(Messages.BookmarkEditorDialog_IncludeInBookmarkLabel); editorInclusionCheck.setSelection(true); editorInclusionCheck.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateEditorIncluded(editorInclusionCheck.getSelection()); } }); editorContainer = new Composite(inner, SWT.NONE); editorContainer.setLayoutData(new GridData(GridData.FILL_BOTH)); editorContainer.setLayout(new GridLayout()); editorButtonBar = new Composite(inner, SWT.RIGHT_TO_LEFT); editorButtonBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); editorButtonBar.setLayout(new RowLayout()); resetButton = new Button(editorButtonBar, SWT.NONE); resetButton.setText(Messages.BookmarkEditorDialog_ResetValuesLabel); resetButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (currentEditor == null) { return; } currentEditor.restoreOriginalValues(); } }); fillFromCurrentButton = new Button(editorButtonBar, SWT.NONE); fillFromCurrentButton.setText(Messages.BookmarkEditorDialog_FillFromCurrentLabel); fillFromCurrentButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (currentEditor == null || !(currentEditor instanceof IBookmarkPropertyEditor)) { return; } ((IBookmarkPropertyEditor) currentEditor).fillFromCurrent(); } }); return container; } /** * Set the current editor in the editor area, if there are no validation * errors present in the currently visible editor. * * @param selectedEditor * The editor to show * * @return <code>true</code> if the editor was successfully changed; * <code>false</code> if the editor was unable to be changed (e.g. * there is a validation error present on the current page) */ private boolean showEditor(IBookmarkEditor selectedEditor) { if (selectedEditor == null) { return false; } if (selectedEditor == currentEditor) { return true; } // Check validity and hide current editor as appropriate if (currentEditor != null) { updateValidityIndicatorsForCurrent(); if (!currentEditor.isValid() && currentEditorIncludedInBookmark()) { return false; } if (currentEditor.getControl() != null) { currentEditor.getControl().setVisible(false); ((GridData) currentEditor.getControl().getLayoutData()).exclude = true; } } // Change editors currentEditor = selectedEditor; // Update the common components based on the current editor messageArea.showTitle(currentEditor.getName(), null); fillFromCurrentButton.setVisible(currentEditor instanceof IBookmarkPropertyEditor); // Instantiate / show editor control as appropriate if (currentEditor.getControl() == null) { Control control = currentEditor.createControl(editorContainer); control.setLayoutData(new GridData(GridData.FILL_BOTH)); } else { currentEditor.getControl().setVisible(true); ((GridData) currentEditor.getControl().getLayoutData()).exclude = false; } // Update the "include in bookmark" indicator updateEditorIncluded(currentEditorIncludedInBookmark()); // Refresh the layout and scrollbar activation based on new content editorScroller.layout(true, true); updateScrollerMinSize(); return true; } private void updateScrollerMinSize() { Rectangle r = editorScroller.getClientArea(); Point computeSize = editorScroller.getContent().computeSize(r.width, SWT.DEFAULT); editorScroller.setMinSize(Math.max(DEFAULT_MIN_PAGE_SIZE.x, computeSize.x), Math.max(DEFAULT_MIN_PAGE_SIZE.y, computeSize.y)); } private boolean currentEditorIncludedInBookmark() { return editorIncludedInBookmark(currentEditor); } private boolean editorIncludedInBookmark(IBookmarkEditor editor) { if (editor == null) { return false; } if (!(editor instanceof IBookmarkPropertyEditor)) { return true; } return ((IBookmarkPropertyEditor) editor).isIncludedInBookmark(); } private void updateEditorIncluded(boolean included) { SWTUtil.setEnabled(editorContainer, included); SWTUtil.setEnabled(editorButtonBar, included); if (currentEditor instanceof IBookmarkPropertyEditor) { editorInclusionArea.setVisible(true); ((GridData) editorInclusionArea.getLayoutData()).exclude = false; editorInclusionCheck.setSelection(included); ((IBookmarkPropertyEditor) currentEditor).setIncludedInBookmark(included); updateValidityIndicatorsForCurrent(); editorFilteredTree.getViewer().refresh(currentEditor, true); } else { editorInclusionArea.setVisible(false); ((GridData) editorInclusionArea.getLayoutData()).exclude = true; } } @Override protected void okPressed() { if (!currentEditor.isValid()) { updateValidityIndicatorsForCurrent(); return; } for (IBookmarkEditor e : editors) { if (editorIncludedInBookmark(e)) { e.okPressed(); if (e instanceof IBookmarkPropertyEditor) { if (!bookmark.hasProperty(((IBookmarkPropertyEditor) e).getProperty().getType())) { bookmark.addProperty(((IBookmarkPropertyEditor) e).getProperty()); } } } else { bookmark.removeProperty(((IBookmarkPropertyEditor) e).getProperty()); } } setReturnCode(OK); close(); } @Override protected void cancelPressed() { for (IBookmarkEditor e : editors) { e.cancelPressed(); } setReturnCode(CANCEL); close(); } @Override public void editorInvalid(IBookmarkEditor editor, IBookmarkEditorMessage[] messages) { updateValidityIndicators(editor, false, messages); } @Override public void editorValid(IBookmarkEditor editor) { updateValidityIndicators(editor, true, null); } private void updateValidityIndicatorsForCurrent() { if (currentEditor == null) { return; } updateValidityIndicators(currentEditor, currentEditor.isValid(), currentEditor.getMessages()); } private void updateValidityIndicators(IBookmarkEditor editor, boolean valid, IBookmarkEditorMessage[] messages) { if (editor == null) { return; } if (valid || !currentEditorIncludedInBookmark()) { messageArea.restoreTitle(); if (getButton(IDialogConstants.OK_ID) != null) { getButton(IDialogConstants.OK_ID).setEnabled(true); } } else if (!valid) { if (messages != null && messages.length > 0) { messageArea.updateText(messages[0].getMessage(), messages[0].getLevel() == Level.ERROR ? IMessageProvider.ERROR : messages[0].getLevel() == Level.WARNING ? IMessageProvider.WARNING : IMessageProvider.INFORMATION); } if (getButton(IDialogConstants.OK_ID) != null) { getButton(IDialogConstants.OK_ID).setEnabled(false); } } } /** * A class used to provide the editor fields for top-level bookmark members * (name etc.) */ private class GeneralBookmarkEditor extends AbstractBookmarkEditor { private final String BOOKMARK_NAME_ID = "general.bookmark.name"; //$NON-NLS-1$ private final IBookmarkEditorMessage BOOKMARK_NAME_EMPTY_MESSAGE = new BookmarkEditorMessage(Level.ERROR, "general.bookmark.name.empty", //$NON-NLS-1$ Messages.BookmarkEditorDialog_EmptyBookmarkNameMessage); private Composite container; private Text bookmarkNameField; @Override public void okPressed() { bookmark.setName(bookmarkNameField.getText()); } @Override public void cancelPressed() { bookmarkNameField.setText(""); //$NON-NLS-1$ } @Override public void restoreOriginalValues() { bookmarkNameField.setText(bookmark.getName()); } @Override public Control createControl(Composite parent) { container = new Composite(parent, SWT.NONE); container.setLayoutData(new GridData(GridData.FILL_BOTH)); container.setLayout(new GridLayout(2, false)); Label nameFieldLabel = new Label(container, SWT.NONE); nameFieldLabel.setText(Messages.BookmarkEditorDialog_BookmarkNameFieldLabel); nameFieldLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, true)); bookmarkNameField = new Text(container, SWT.SINGLE | SWT.LEFT | SWT.BORDER); bookmarkNameField.setText(bookmark.getName()); GridData gd = new GridData(GridData.FILL_HORIZONTAL); gd.verticalAlignment = SWT.TOP; bookmarkNameField.setLayoutData(gd); bookmarkNameField.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { validate(BOOKMARK_NAME_ID, !Util.isBlank(bookmarkNameField.getText()), BOOKMARK_NAME_EMPTY_MESSAGE); } }); return container; } @Override public Control getControl() { return container; } @Override public String getName() { return Messages.BookmarkEditorDialog_GeneralEditorTitle; } @Override public String getDescription() { return Messages.BookmarkEditorDialog_GeneralEditorDescription; } } private final class BookmarkPropertyTreeLabelProvider extends CellLabelProvider { @Override public String getToolTipText(Object element) { return ((IBookmarkEditor) element).getDescription(); } @Override public int getToolTipDisplayDelayTime(Object object) { return 1000; } @Override public void update(ViewerCell cell) { IBookmarkEditor editor = (IBookmarkEditor) cell.getElement(); cell.setText(editor.getName()); if (editorIncludedInBookmark(editor)) { cell.setFont(JFaceResources.getFontRegistry().getBold("default")); //$NON-NLS-1$ } else { cell.setFont(JFaceResources.getDefaultFont()); } } } private final class BookmarkPropertyTreeFilter extends PatternFilter { @Override protected boolean isLeafMatch(Viewer viewer, Object element) { if (element instanceof IBookmarkEditor) { return wordMatches(((IBookmarkEditor) element).getName()); } return false; } } private final class BookmarkPropertyTreeContentProvider implements ITreeContentProvider { @Override public void dispose() { // DO nothing } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { // Do nothing } @Override public Object[] getElements(Object inputElement) { return editors.toArray(); } @Override public Object[] getChildren(Object parentElement) { return null; } @Override public Object getParent(Object element) { return null; } @Override public boolean hasChildren(Object element) { return false; } } private final class BookmarkPropertyTreeViewerComparator extends ViewerComparator { @Override public int compare(Viewer viewer, Object e1, Object e2) { if (e1 instanceof IBookmarkPropertyEditor && e2 instanceof IBookmarkPropertyEditor) { return String.CASE_INSENSITIVE_ORDER.compare(((IBookmarkPropertyEditor) e1).getName(), ((IBookmarkPropertyEditor) e2).getName()); } return (e1 instanceof GeneralBookmarkEditor) ? -1 : 1; } } }