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
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
;
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
;
58 * A User interface that holds a viewer and satellites elements.
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.
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.
68 * This class allows a user to select and check elements.
71 * It can also synchronize the state of checked element into a {@link DataHolder}
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
>();
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
) {
114 Composite contentComposite
= new Composite(parent
, SWT
.NONE
);
115 final int numberOfColumns
;
116 if (hasConfiguration
) {
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
) {
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$
137 this.configurationComposite
= null;
138 this.defaultComposite
= null;
140 if (hasDescription
) {
141 // Descriptor engine Text
142 this.descriptionText
= createDescriptionComposite(parent
);
144 this.descriptionText
= null;
149 * Adds a configuration to this Interactive content.
152 * Id of the item to configure
153 * @param configuratorfactory
154 * Factory for the configuration
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
,
161 configurators
.put(id
, configurator
);
165 * Checks one element in the viewer.
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)
180 public void checkElements(IItemDescriptor
<?
>[] descriptors
) {
181 viewer
.setCheckedElements(descriptors
);
185 * Creates the composite that will hold all configurations for a tab.
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
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
);
238 * @return The viewer.
240 private CheckboxTableViewer
getViewer() {
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.
259 public void select(IItemDescriptor
<?
> descriptor
) {
261 viewer
.setSelection(new StructuredSelection(descriptor
), true);
262 updateLinkedElements(descriptor
);
266 * Sets the viewer in the interactive content.
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.
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.
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());
304 stackLayout
.topControl
= defaultComposite
;
306 configurationComposite
.layout();
310 * This listener updates the Data Holder.
312 * With this listener, only one element can be checked at a time
315 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
319 private static final class SingleCheckListener
<T
> implements ICheckStateListener
{
321 private final DataHolder
<T
> dataObject
;
324 private final CheckboxTableViewer descriptorViewer
;
327 private final Shell shell
;
334 * @param descriptorViewer
339 private SingleCheckListener(DataHolder
<T
> dataObject
, CheckboxTableViewer descriptorViewer
,
341 this.dataObject
= dataObject
;
342 this.descriptorViewer
= descriptorViewer
;
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
});
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
{
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
{
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>
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;
458 * Holding composite of all the structure.
460 * Item registry holding input of the viewer.
462 public InteractiveUIBuilder(Composite parent
, IItemRegistry
<T
> registry
) {
464 this.parent
= parent
;
465 this.registry
= registry
;
469 * Sets a dataHolder that will be synchronized with the checked element.
471 * @param newDataHolder
473 * @return {@link InteractiveUIBuilder}
475 public InteractiveUIBuilder
<T
> setHoldingData(DataHolder
<T
> newDataHolder
) {
476 this.dataHolder
= newDataHolder
;
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)
487 * @return {@link InteractiveUIBuilder}
489 public InteractiveUIBuilder
<T
> setConfigurationNodeKey(String key
) {
490 this.configurationNodeKey
= key
;
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
;
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
;
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
;
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
;
545 * Set to true if the viewer can only have only one element checked at a time.
549 * @return {@link InteractiveUIBuilder}
551 public InteractiveUIBuilder
<T
> setSimple(boolean newIsSimple
) {
552 this.isSimple
= newIsSimple
;
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
,
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
;
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
);
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()));
647 if (dataHolder
!= null) {
648 if (defaultCheck
!= null) {
649 dataHolder
.setData(defaultCheck
);
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
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(
685 ctx
.bindSet(target
, model
);