Improves preference page UI.
[EMFCompare2.git] / plugins / org.eclipse.emf.compare.rcp.ui / src / org / eclipse / emf / compare / rcp / ui / internal / preferences / impl / InteractiveUIContent.java
blob2db4dbea70dc92fb55130187725562c7cf02e014
1 /*******************************************************************************
2 * Copyright (c) 2014 Obeo.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * Obeo - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.emf.compare.rcp.ui.internal.preferences.impl;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.ImmutableMap;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
22 import org.eclipse.core.databinding.DataBindingContext;
23 import org.eclipse.core.databinding.beans.PojoProperties;
24 import org.eclipse.core.databinding.observable.set.IObservableSet;
25 import org.eclipse.emf.compare.rcp.internal.extension.IItemDescriptor;
26 import org.eclipse.emf.compare.rcp.internal.extension.IItemRegistry;
27 import org.eclipse.emf.compare.rcp.internal.extension.impl.ItemUtil;
28 import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareRCPUIMessages;
29 import org.eclipse.emf.compare.rcp.ui.internal.configuration.ui.AbstractConfigurationUI;
30 import org.eclipse.emf.compare.rcp.ui.internal.configuration.ui.IConfigurationUIFactory;
31 import org.eclipse.emf.compare.rcp.ui.internal.preferences.DataHolder;
32 import org.eclipse.jface.databinding.viewers.IViewerObservableSet;
33 import org.eclipse.jface.databinding.viewers.ViewersObservables;
34 import org.eclipse.jface.dialogs.MessageDialog;
35 import org.eclipse.jface.viewers.ArrayContentProvider;
36 import org.eclipse.jface.viewers.CheckStateChangedEvent;
37 import org.eclipse.jface.viewers.CheckboxTableViewer;
38 import org.eclipse.jface.viewers.ICheckStateListener;
39 import org.eclipse.jface.viewers.ISelection;
40 import org.eclipse.jface.viewers.ISelectionChangedListener;
41 import org.eclipse.jface.viewers.IStructuredSelection;
42 import org.eclipse.jface.viewers.SelectionChangedEvent;
43 import org.eclipse.jface.viewers.StructuredSelection;
44 import org.eclipse.jface.viewers.StructuredViewer;
45 import org.eclipse.swt.SWT;
46 import org.eclipse.swt.custom.StackLayout;
47 import org.eclipse.swt.layout.GridData;
48 import org.eclipse.swt.layout.GridLayout;
49 import org.eclipse.swt.widgets.Composite;
50 import org.eclipse.swt.widgets.Display;
51 import org.eclipse.swt.widgets.Group;
52 import org.eclipse.swt.widgets.Label;
53 import org.eclipse.swt.widgets.Shell;
54 import org.eclipse.swt.widgets.Text;
55 import org.osgi.service.prefs.Preferences;
57 /**
58 * A User interface that holds a viewer and satellites elements.
59 * <p>
60 * This viewer can have a satellite configuration composite reacting on selection. It displays a configuration
61 * UI for the current selection. It's requires a configuration UI registry.
62 * </p>
63 * <p>
64 * This viewer can have a satellite text field holding the description of the current selection. This field
65 * display the description for the current element.
66 * </p>
67 * <p>
68 * This class allows a user to select and check elements.
69 * </p>
70 * <p>
71 * It can also synchronize the state of checked element into a {@link DataHolder}
72 * </p>
74 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
76 public final class InteractiveUIContent {
78 /** Width hint for configuration composite. */
79 private static final int WIDTH_HINT_CONFIG_COMPOSITE = 400;
81 /** Height hint for description composite. */
82 private static final int DESCRIPTION_COMPOSITE_HEIGHT_HINT = 50;
84 /** Text that will be updated with the description of the viewer. */
85 private final Text descriptionText;
87 /** Composite holding the viewer. */
88 private final Composite viewerCompsite;
90 /** Composite holding the configuration. This will react to the selection in the viewer. */
91 private final Composite configurationComposite;
93 /** Composite that is used when the selection has no registered configuration. */
94 private final Composite defaultComposite;
96 /** Viewer of {@link IItemDescriptor}. */
97 private CheckboxTableViewer viewer;
99 /** List of all {@link AbstractConfigurationUI} that are linked to this viewer. */
100 private final Map<String, AbstractConfigurationUI> configurators = new HashMap<String, AbstractConfigurationUI>();
103 * Constructor.
105 * @param parent
106 * Composite parent holding this interactive content.
107 * @param hasDescription
108 * Set to true if this has a description label that react to viewer selection.
109 * @param hasConfiguration
110 * set to true if this has a configuration composite that react to viewer selection.
112 private InteractiveUIContent(Composite parent, boolean hasDescription, boolean hasConfiguration) {
113 super();
114 Composite contentComposite = new Composite(parent, SWT.NONE);
115 final int numberOfColumns;
116 if (hasConfiguration) {
117 numberOfColumns = 2;
118 } else {
119 numberOfColumns = 1;
121 contentComposite.setLayout(new GridLayout(numberOfColumns, false));
122 contentComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
123 // Engine chooser composite
124 this.viewerCompsite = new Composite(contentComposite, SWT.NONE);
125 viewerCompsite.setLayout(new GridLayout(1, true));
126 viewerCompsite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
127 if (hasConfiguration) {
128 // Config composite
129 this.configurationComposite = createConfigComposite(contentComposite);
130 // Init default composite.
131 defaultComposite = new Composite(configurationComposite, SWT.NONE);
132 defaultComposite.setLayout(new GridLayout(1, true));
133 Label text = new Label(defaultComposite, SWT.WRAP);
134 text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true, 1, 1));
135 text.setText(EMFCompareRCPUIMessages.getString("InteractiveUIContent.defaultConfiguration.label")); //$NON-NLS-1$
136 } else {
137 this.configurationComposite = null;
138 this.defaultComposite = null;
140 if (hasDescription) {
141 // Descriptor engine Text
142 this.descriptionText = createDescriptionComposite(parent);
143 } else {
144 this.descriptionText = null;
149 * Adds a configuration to this Interactive content.
151 * @param id
152 * Id of the item to configure
153 * @param configuratorfactory
154 * Factory for the configuration
155 * @param pref
156 * Preference store that will hold this {@link IConfigurationUIFactory} value.
158 public void addConfigurator(String id, IConfigurationUIFactory configuratorfactory, Preferences pref) {
159 AbstractConfigurationUI configurator = configuratorfactory.createUI(configurationComposite, SWT.NONE,
160 pref);
161 configurators.put(id, configurator);
165 * Checks one element in the viewer.
167 * @param descriptor
168 * element to check.
170 public void checkElement(IItemDescriptor<?> descriptor) {
171 viewer.setCheckedElements(new Object[] {descriptor });
175 * Checks multiple element in the viewer. (Only use if multiple selection is allowed)
177 * @param descriptors
178 * elements to check.
180 public void checkElements(IItemDescriptor<?>[] descriptors) {
181 viewer.setCheckedElements(descriptors);
185 * Creates the composite that will hold all configurations for a tab.
187 * @param composite
188 * Main composite
189 * @return Group that will hold configurations in a stack layout.
191 private Group createConfigComposite(Composite composite) {
192 Group confComposite = new Group(composite, SWT.NONE);
193 confComposite.setText(EMFCompareRCPUIMessages
194 .getString("InteractiveUIContent.configurationComposite.label")); //$NON-NLS-1$
195 StackLayout layout = new StackLayout();
196 layout.marginHeight = 10;
197 layout.marginWidth = 10;
198 confComposite.setLayout(layout);
199 GridData layoutData = new GridData(SWT.BEGINNING, SWT.FILL, false, true);
200 layoutData.widthHint = WIDTH_HINT_CONFIG_COMPOSITE;
201 confComposite.setLayoutData(layoutData);
202 return confComposite;
206 * Composite for description. This composite holds the text widget that will be updated with the current
207 * selection.
209 * @param composite
210 * Main composite.
211 * @return Text that will hold viewer selection description.
213 private Text createDescriptionComposite(Composite composite) {
214 Group descriptionComposite = new Group(composite, SWT.NONE);
215 descriptionComposite.setText(EMFCompareRCPUIMessages
216 .getString("InteractiveUIContent.descriptionComposite.label")); //$NON-NLS-1$
217 descriptionComposite.setLayout(new GridLayout(1, false));
218 descriptionComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
219 Text engineDescriptionText = new Text(descriptionComposite, SWT.WRAP | SWT.MULTI);
220 engineDescriptionText.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
221 GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1);
222 layoutData.heightHint = DESCRIPTION_COMPOSITE_HEIGHT_HINT;
223 engineDescriptionText.setLayoutData(layoutData);
224 engineDescriptionText.setEditable(false);
225 return engineDescriptionText;
229 * @return A map of all configuration.
231 public Map<String, AbstractConfigurationUI> getConfigurators() {
232 return ImmutableMap.copyOf(configurators);
236 * Gets the viewer.
238 * @return The viewer.
240 private CheckboxTableViewer getViewer() {
241 return viewer;
245 * Returns the composite that will hold the viewer.
247 * @return The composite holding the viewer.
249 private Composite getViewerComposite() {
250 return viewerCompsite;
254 * Handles a selection in the viewer. Update related components.
256 * @param descriptor
257 * Item to select.
259 public void select(IItemDescriptor<?> descriptor) {
260 // Update viewer
261 viewer.setSelection(new StructuredSelection(descriptor), true);
262 updateLinkedElements(descriptor);
266 * Sets the viewer in the interactive content.
268 * @param inputViewer
269 * A {@link StructuredViewer} of {@link IItemDescriptor}
271 public void setViewer(CheckboxTableViewer inputViewer) {
272 this.viewer = inputViewer;
273 if (configurationComposite != null) {
274 viewer.addSelectionChangedListener(new ConfigurationListener());
276 viewer.addSelectionChangedListener(new DescriptionListener());
280 * Updates the linked element in this interactive content.
282 * @param descriptor
283 * Item used as input to get information for satellite elements.
285 private void updateLinkedElements(IItemDescriptor<?> descriptor) {
286 // Update description
287 descriptionText.setText(descriptor.getDescription());
288 if (configurationComposite != null) {
289 updateConfigurationComposite(descriptor);
294 * Updates the configuration composite.
296 * @param descriptor
297 * New descriptor.
299 private void updateConfigurationComposite(IItemDescriptor<?> descriptor) {
300 StackLayout stackLayout = (StackLayout)configurationComposite.getLayout();
301 if (configurators.containsKey(descriptor.getID())) {
302 stackLayout.topControl = configurators.get(descriptor.getID());
303 } else {
304 stackLayout.topControl = defaultComposite;
306 configurationComposite.layout();
310 * This listener updates the Data Holder.
311 * <p>
312 * With this listener, only one element can be checked at a time
313 * </p>
315 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
316 * @param <T>
317 * Type of item.
319 private static final class SingleCheckListener<T> implements ICheckStateListener {
320 /** Data holder. */
321 private final DataHolder<T> dataObject;
323 /** Viewer. */
324 private final CheckboxTableViewer descriptorViewer;
326 /** Shell. */
327 private final Shell shell;
330 * Constructor.
332 * @param dataObject
333 * Data holder.
334 * @param descriptorViewer
335 * Viewer
336 * @param shell
337 * Shell.
339 private SingleCheckListener(DataHolder<T> dataObject, CheckboxTableViewer descriptorViewer,
340 Shell shell) {
341 this.dataObject = dataObject;
342 this.descriptorViewer = descriptorViewer;
343 this.shell = shell;
347 * {@inheritDoc}
349 public void checkStateChanged(CheckStateChangedEvent event) {
350 Object element = event.getElement();
351 if (event.getChecked()) {
352 if (element instanceof IItemDescriptor<?>) {
353 @SuppressWarnings("unchecked")
354 IItemDescriptor<T> descriptor = (IItemDescriptor<T>)element;
355 dataObject.setData(Collections.singleton(descriptor));
357 descriptorViewer.setCheckedElements(new Object[] {element });
358 } else {
359 // Prevent from nothing checked
360 if (descriptorViewer.getCheckedElements().length == 0) {
361 descriptorViewer.setCheckedElements(new Object[] {element });
362 MessageDialog.openWarning(shell, EMFCompareRCPUIMessages
363 .getString("InteractiveUIContent.INCORRECT_SELECTION_TITLE"), //$NON-NLS-1$
364 EMFCompareRCPUIMessages
365 .getString("InteractiveUIContent.INCORRECT_SELECTION_MESSAGE")); //$NON-NLS-1$
373 * Listener in charge of updating the configuration composite.
375 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
377 private final class ConfigurationListener implements ISelectionChangedListener {
379 * {@inheritDoc}
381 public void selectionChanged(SelectionChangedEvent event) {
382 ISelection selection = event.getSelection();
383 if (selection instanceof IStructuredSelection) {
384 IStructuredSelection structSelection = (IStructuredSelection)selection;
385 Object selected = structSelection.getFirstElement();
386 if (selected instanceof IItemDescriptor<?>) {
387 updateLinkedElements((IItemDescriptor<?>)selected);
394 * Listener used to update description text.
396 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
398 private final class DescriptionListener implements ISelectionChangedListener {
401 * {@inheritDoc}
403 public void selectionChanged(SelectionChangedEvent event) {
404 ISelection selection = event.getSelection();
405 if (selection instanceof IStructuredSelection) {
406 IStructuredSelection structSelection = (IStructuredSelection)selection;
407 Object selected = structSelection.getFirstElement();
408 if (selected instanceof IItemDescriptor<?>) {
409 IItemDescriptor<?> desc = (IItemDescriptor<?>)selected;
410 String description = desc.getDescription();
411 descriptionText.setText(description);
419 * Builder for an Interactive UI.
421 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
422 * @param <T>
423 * type of item in the viewer.
425 public static class InteractiveUIBuilder<T> {
427 /** Holding composite of all the structure. */
428 private Composite parent;
430 /** Item registry holding input of the viewer. */
431 private IItemRegistry<T> registry;
433 /** Key of the preference node where the configuration for an item is stored. */
434 private String configurationNodeKey;
436 /** Configuration UI registry. */
437 private Map<String, IConfigurationUIFactory> configurationUIRegistry;
439 /** Set of elements to check by default. */
440 private Set<IItemDescriptor<T>> defaultCheck;
442 /** Element to select by default. */
443 private IItemDescriptor<T> defaultSelection;
445 /** Object holding data representing the current status of the interactive content. */
446 private DataHolder<T> dataHolder;
448 /** Set to true if the interactive content has a description field. */
449 private boolean hasDescription = true;
451 /** Set to true if this interactive content is synchronized with only one element at a time. */
452 private boolean isSimple = false;
455 * Constructor.
457 * @param parent
458 * Holding composite of all the structure.
459 * @param registry
460 * Item registry holding input of the viewer.
462 public InteractiveUIBuilder(Composite parent, IItemRegistry<T> registry) {
463 super();
464 this.parent = parent;
465 this.registry = registry;
469 * Sets a dataHolder that will be synchronized with the checked element.
471 * @param newDataHolder
472 * DataHolder.
473 * @return {@link InteractiveUIBuilder}
475 public InteractiveUIBuilder<T> setHoldingData(DataHolder<T> newDataHolder) {
476 this.dataHolder = newDataHolder;
477 return this;
481 * Node key used to get the {@link Preferences} to retrieve {@link IConfigurationUIFactory}. See
482 * {@link ItemUtil#getConfigurationPreferenceNode(String, String)} (needed if a
483 * ConfigurationUIRegistry has been provided)
485 * @param key
487 * @return {@link InteractiveUIBuilder}
489 public InteractiveUIBuilder<T> setConfigurationNodeKey(String key) {
490 this.configurationNodeKey = key;
491 return this;
495 * Registry of {@link IConfigurationUIFactory} used to fill the configuration composite.
497 * @param configuratorUIRegistry
499 * @return {@link InteractiveUIBuilder}
501 public InteractiveUIBuilder<T> setConfiguratorUIRegistry(
502 Map<String, IConfigurationUIFactory> configuratorUIRegistry) {
503 this.configurationUIRegistry = configuratorUIRegistry;
504 return this;
508 * Sets the default element to check. (A singleton if "this" is set to simple
509 * {@link InteractiveUIBuilder#setSimple(boolean)}
511 * @param newDefaultCheck
513 * @return InteractiveUIBuilder
515 public InteractiveUIBuilder<T> setDefaultCheck(Set<IItemDescriptor<T>> newDefaultCheck) {
516 this.defaultCheck = newDefaultCheck;
517 return this;
521 * Set the default element to select.
523 * @param newDefaultSelection
525 * @return InteractiveUIBuilder
527 public InteractiveUIBuilder<T> setDefaultSelection(IItemDescriptor<T> newDefaultSelection) {
528 this.defaultSelection = newDefaultSelection;
529 return this;
533 * Set to true if "this" needs to create a description field.
535 * @param newHasDescription
537 * @return {@link InteractiveUIBuilder}
539 public InteractiveUIBuilder<T> setHasDescription(boolean newHasDescription) {
540 this.hasDescription = newHasDescription;
541 return this;
545 * Set to true if the viewer can only have only one element checked at a time.
547 * @param newIsSimple
549 * @return {@link InteractiveUIBuilder}
551 public InteractiveUIBuilder<T> setSimple(boolean newIsSimple) {
552 this.isSimple = newIsSimple;
553 return this;
557 * Build a new {@link InteractiveUI}.
559 * @return InteractiveUIContent
561 public InteractiveUIContent build() {
562 // If simple only one element check at a time
563 Preconditions.checkArgument(!isSimple || defaultCheck == null || defaultCheck.size() == 1);
565 boolean hasConfiguration = configurationUIRegistry != null;
566 // If has a configuration composite then the key to retrieve the preference must be set up.
567 Preconditions.checkArgument(!hasConfiguration || configurationNodeKey != null);
569 final InteractiveUIContent interactiveUI = new InteractiveUIContent(parent, hasDescription,
570 hasConfiguration);
572 CheckboxTableViewer descriptorViewer = createViewer(interactiveUI);
574 if (hasConfiguration) {
575 createConfigurationComposite(interactiveUI);
577 setViewerInput(descriptorViewer);
578 // Init and bind data
579 bindAndInit(interactiveUI, descriptorViewer);
580 return interactiveUI;
584 * Initializes the viewer.
586 * @param interactiveUI
588 * @return CheckboxTableViewer
590 private CheckboxTableViewer createViewer(final InteractiveUIContent interactiveUI) {
591 int style = SWT.BORDER | SWT.V_SCROLL | SWT.FULL_SELECTION;
592 if (isSimple) {
593 style = style | SWT.SINGLE;
595 CheckboxTableViewer descriptorViewer = CheckboxTableViewer.newCheckList(interactiveUI
596 .getViewerComposite(), style);
597 interactiveUI.setViewer(descriptorViewer);
598 descriptorViewer.setContentProvider(ArrayContentProvider.getInstance());
599 descriptorViewer.setLabelProvider(new ItemDescriptorLabelProvider());
600 GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
601 descriptorViewer.getControl().setLayoutData(gd);
602 return descriptorViewer;
606 * Creates the configuration composite.
608 * @param interactiveUI
611 private void createConfigurationComposite(final InteractiveUIContent interactiveUI) {
612 // Init configuration elements
613 for (IItemDescriptor<T> item : registry.getItemDescriptors()) {
614 String itemId = item.getID();
615 IConfigurationUIFactory configuratorFactory = configurationUIRegistry.get(itemId);
616 if (configuratorFactory != null) {
617 Preferences pref = ItemUtil.getConfigurationPreferenceNode(configurationNodeKey, itemId);
618 interactiveUI.addConfigurator(itemId, configuratorFactory, pref);
624 * Initializes and binds interactive content with the data holder value.
626 * @param interactiveUI
628 * @param descriptorViewer
631 private void bindAndInit(final InteractiveUIContent interactiveUI,
632 CheckboxTableViewer descriptorViewer) {
633 if (defaultSelection != null) {
634 interactiveUI.select(defaultSelection);
636 if (isSimple) {
637 if (defaultCheck != null) {
638 IItemDescriptor<T> defaultCheckedElement = defaultCheck.iterator().next();
639 interactiveUI.checkElement(defaultCheckedElement);
640 if (dataHolder != null) {
641 dataHolder.setData(Collections.singleton(defaultCheckedElement));
644 descriptorViewer.addCheckStateListener(new SingleCheckListener<T>(dataHolder,
645 descriptorViewer, Display.getDefault().getActiveShell()));
646 } else {
647 if (dataHolder != null) {
648 if (defaultCheck != null) {
649 dataHolder.setData(defaultCheck);
651 // Bind data
652 bindMultipleData(interactiveUI.getViewer(), dataHolder);
658 * Sets the viewer input.
660 * @param descriptorViewer
663 private void setViewerInput(CheckboxTableViewer descriptorViewer) {
664 List<IItemDescriptor<T>> itemDescriptors = registry.getItemDescriptors();
665 Collections.sort(itemDescriptors);
666 descriptorViewer.setInput(itemDescriptors);
670 * Binds UI to data object.
672 * @param descriptorViewer
674 * @param dataObject
675 * The data holder.
677 private void bindMultipleData(CheckboxTableViewer descriptorViewer, final DataHolder<T> dataObject) {
678 DataBindingContext ctx = new DataBindingContext();
679 // Bind the button with the corresponding field in data
680 IViewerObservableSet target = ViewersObservables.observeCheckedElements(descriptorViewer,
681 IItemDescriptor.class);
682 IObservableSet model = PojoProperties.set(DataHolder.class, DataHolder.DATA_FIELD_NAME).observe(
683 dataObject);
685 ctx.bindSet(target, model);