--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/DialogUtils.java Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,404 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * A small set of utility classes that help programming dialog window logic.
+ */
+public class DialogUtils {
+ /*
+ * Part 1. Update graph.
+ *
+ * All logical elements of dialog and dependencies between them are modeled as DAG.
+ * The data flows from vertices that corresponds to various input fields through
+ * some transformations to the terminal vertices that are in-dialog helper messages and OK button.
+ *
+ * A linear data flow (no forks, no merges) is suitable for data transformation and
+ * is programmed manually. Forks and merges are hard to dispatch manually and are managed by
+ * class Updater.
+ *
+ * Updater knows about "source" vertices that may have several outgoing edges and about
+ * "consumer" vertices that may have several incoming edges. Based on source vertex changes
+ * updater updates consumer vertices in topological order.
+ */
+
+ /**
+ * Represents source vertex for Updater. Technically updater uses this interface only as a flag
+ * interface, because the only methods it uses are {@link Object#equals}/Object{@link #hashCode}.
+ */
+ public interface ValueSource<T> {
+ /**
+ * Method is not used by updater, for convenience only.
+ * @return current value of the vertex
+ */
+ T getValue();
+ }
+
+ /**
+ * Represents consumer vertex for Updater. Each instance should be explicitly registered in
+ * Updater.
+ */
+ public interface ValueConsumer {
+ /**
+ * Called by updater when some linked sources have changed and it's time this vertex
+ * got updated. {@link Updater#reportChanged} may be called if some {@line ValueSource}s have
+ * changed during this update (but this should not break topological order of the graph).
+ */
+ void update(Updater updater);
+ }
+
+ /**
+ * Helps to conduct update for vertices in a value graph.
+ * Technically Updater does not see a real graph, because it doesn't support vertices
+ * that are simultaneously source and consumer. Programmer helps manage other edges manually by
+ * calling {@link #reportChanged} method.
+ */
+ public static class Updater {
+ private final LinkedHashMap<ValueConsumer, Boolean> needsUpdateMap =
+ new LinkedHashMap<ValueConsumer, Boolean>();
+ private final Map<ValueSource<?>, List<ValueConsumer>> reversedDependenciesMap =
+ new HashMap<ValueSource<?>, List<ValueConsumer>>();
+ private boolean alreadyUpdating = false;
+
+ public void addConsumer(ValueConsumer value, ValueSource<?> ... dependencies) {
+ addConsumer(value, Arrays.asList(dependencies));
+ }
+
+ /**
+ * Registers a consumer vertex with all its dependencies.
+ */
+ public void addConsumer(ValueConsumer value, List<? extends ValueSource<?>> dependencies) {
+ Boolean res = needsUpdateMap.put(value, Boolean.FALSE);
+ if (res != null) {
+ throw new IllegalArgumentException("Already added"); //$NON-NLS-1$
+ }
+ for (ValueSource<?> dep : dependencies) {
+ List<ValueConsumer> reversedDeps = reversedDependenciesMap.get(dep);
+ if (reversedDeps == null) {
+ reversedDeps = new ArrayList<ValueConsumer>(2);
+ reversedDependenciesMap.put(dep, reversedDeps);
+ }
+ reversedDeps.add(value);
+ }
+ }
+
+ /**
+ * Reports about sources that have been changed and plans future update of consumers. This
+ * method may be called at any time (it is not thread-safe though).
+ */
+ public void reportChanged(ValueSource<?> source) {
+ List<ValueConsumer> reversedDeps = reversedDependenciesMap.get(source);
+ if (reversedDeps != null) {
+ for (ValueConsumer consumer : reversedDeps) {
+ needsUpdateMap.put(consumer, Boolean.TRUE);
+ }
+ }
+ }
+
+ /**
+ * Performs update of all vertices that need it. If some sources are reported changed
+ * during the run of this method, their consumers are also updated.
+ */
+ public void update() {
+ if (alreadyUpdating) {
+ return;
+ }
+ alreadyUpdating = true;
+ try {
+ updateImpl();
+ } finally {
+ alreadyUpdating = false;
+ }
+ }
+
+ private void updateImpl() {
+ boolean hasChanges = true;
+ while (hasChanges) {
+ hasChanges = false;
+ for (Map.Entry<ValueConsumer, Boolean> en : needsUpdateMap.entrySet()) {
+ if (en.getValue() == Boolean.TRUE) {
+ en.setValue(Boolean.FALSE);
+ ValueConsumer currentValue = en.getKey();
+ currentValue.update(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates all consumer vertices in graph.
+ */
+ public void updateAll() {
+ for (Map.Entry<?, Boolean> en : needsUpdateMap.entrySet()) {
+ en.setValue(Boolean.TRUE);
+ }
+ update();
+ }
+ }
+
+ /**
+ * A basic implementation of object that is both consumer and source. Updater will treat
+ * as 2 separate objects.
+ */
+ public static abstract class ValueProcessor<T> implements ValueConsumer, ValueSource<T> {
+ private T currentValue = null;
+ public T getValue() {
+ return currentValue;
+ }
+ protected void setCurrentValue(T currentValue) {
+ this.currentValue = currentValue;
+ }
+ }
+
+ /*
+ * Part 2. Optional data type etc
+ *
+ * Since dialog should deal with error user entry, the typical data type is either value or error.
+ * This is implemented as Optional interface. Most of data transformations should work only
+ * when all inputs are non-error and generate error in return otherwise. This is implemented in
+ * ExpressionProcessor.
+ */
+
+
+ /**
+ * A primitive approach to "optional" algebraic type. This type is T + Set<Message>.
+ */
+ public interface Optional<V> {
+ V getNormal();
+ boolean isNormal();
+ Set<? extends Message> errorMessages();
+ }
+
+ public static <V> Optional<V> createOptional(final V value) {
+ return new Optional<V>() {
+ public Set<Message> errorMessages() {
+ return Collections.emptySet();
+ }
+ public V getNormal() {
+ return value;
+ }
+ public boolean isNormal() {
+ return true;
+ }
+ };
+ }
+
+ public static <V> Optional<V> createErrorOptional(Message message) {
+ return createErrorOptional(Collections.singleton(message));
+ }
+
+ public static <V> Optional<V> createErrorOptional(final Set<? extends Message> messages) {
+ return new Optional<V>() {
+ public Set<? extends Message> errorMessages() {
+ return messages;
+ }
+ public V getNormal() {
+ throw new UnsupportedOperationException();
+ }
+ public boolean isNormal() {
+ return false;
+ }
+ };
+ }
+
+ /**
+ * A user interface message for dialog window. It has text and priority that helps choosing
+ * the most important message it there are many of them.
+ */
+ public static class Message {
+ private final String text;
+ private final MessagePriority priority;
+ public Message(String text, MessagePriority priority) {
+ this.text = text;
+ this.priority = priority;
+ }
+ public String getText() {
+ return text;
+ }
+ public MessagePriority getPriority() {
+ return priority;
+ }
+ }
+
+ /**
+ * Priority of a user interface message.
+ * Constants are listed from most important to least important.
+ */
+ public enum MessagePriority {
+ BLOCKING_PROBLEM(IMessageProvider.ERROR),
+ BLOCKING_INFO(IMessageProvider.NONE),
+ WARNING(IMessageProvider.WARNING);
+
+ private final int messageProviderType;
+ private MessagePriority(int messageProviderType) {
+ this.messageProviderType = messageProviderType;
+ }
+ public int getMessageProviderType() {
+ return messageProviderType;
+ }
+ }
+
+ /**
+ * A base class for the source-consumer pair that accepts several values as a consumer,
+ * performs a calculation over them and gives it away the result via source interface.
+ * Some sources may be of Optional type. If some of sources have error value the corresponding
+ * error value is returned automatically.
+ * <p>
+ * The implementation should override a single method {@link #calculateNormal}.
+ */
+ public static abstract class ExpressionProcessor<T> extends ValueProcessor<Optional<T>> {
+ private final List<ValueSource<? extends Optional<?>>> optionalSources;
+ public ExpressionProcessor(List<ValueSource<? extends Optional<?>>> optionalSources) {
+ this.optionalSources = optionalSources;
+ }
+
+ protected abstract Optional<T> calculateNormal();
+
+ private Optional<T> calculateNewValue() {
+ Set<Message> errors = new LinkedHashSet<Message>(0);
+ for (ValueSource<? extends Optional<?>> source : optionalSources) {
+ if (!source.getValue().isNormal()) {
+ errors.addAll(source.getValue().errorMessages());
+ }
+ }
+ if (errors.isEmpty()) {
+ return calculateNormal();
+ } else {
+ return createErrorOptional(errors);
+ }
+ }
+ public void update(Updater updater) {
+ Optional<T> result = calculateNewValue();
+ Optional<T> oldValue = getValue();
+ setCurrentValue(result);
+ if (!result.equals(oldValue)) {
+ updater.reportChanged(this);
+ }
+ }
+ }
+
+ /*
+ * Part 3. Various utils.
+ */
+
+ /**
+ * A general-purpose implementation of OK button vertex. It works as a consumer of
+ * 1 result value and several warning sources. From its sources it decides whether
+ * OK button should be enabled and also provides dialog messages (errors, warnings, infos).
+ */
+ public static class OkButtonControl implements ValueConsumer {
+ private final ValueSource<? extends Optional<?>> resultSource;
+ private final List<? extends ValueSource<String>> warningSources;
+ private final DialogElements dialogElements;
+
+ public OkButtonControl(ValueSource<? extends Optional<?>> resultSource,
+ List<? extends ValueSource<String>> warningSources, DialogElements dialogElements) {
+ this.resultSource = resultSource;
+ this.warningSources = warningSources;
+ this.dialogElements = dialogElements;
+ }
+
+ /**
+ * Returns a list of dependencies for updater -- a convenience method.
+ */
+ public List<? extends ValueSource<?>> getDependencies() {
+ ArrayList<ValueSource<?>> result = new ArrayList<ValueSource<?>>();
+ result.add(resultSource);
+ result.addAll(warningSources);
+ return result;
+ }
+
+ public void update(Updater updater) {
+ Optional<?> result = resultSource.getValue();
+ List<Message> messages = new ArrayList<Message>();
+ for (ValueSource<String> warningSource : warningSources) {
+ if (warningSource.getValue() != null) {
+ messages.add(new Message(warningSource.getValue(), MessagePriority.WARNING));
+ }
+ }
+ boolean enabled;
+ if (result.isNormal()) {
+ enabled = true;
+ } else {
+ enabled = false;
+ messages.addAll(result.errorMessages());
+ }
+ dialogElements.getOkButton().setEnabled(enabled);
+ String errorMessage;
+ int type;
+ if (messages.isEmpty()) {
+ errorMessage = null;
+ type = IMessageProvider.NONE;
+ } else {
+ Message visibleMessage = Collections.max(messages, messageComparatorBySeverity);
+ errorMessage = visibleMessage.getText();
+ type = visibleMessage.getPriority().getMessageProviderType();
+ }
+ dialogElements.setMessage(errorMessage, type);
+ }
+
+ private static final Comparator<Message> messageComparatorBySeverity =
+ new Comparator<Message>() {
+ public int compare(Message o1, Message o2) {
+ int ordinal1 = o1.getPriority().ordinal();
+ int ordinal2 = o2.getPriority().ordinal();
+ if (ordinal1 < ordinal2) {
+ return +1;
+ } else if (ordinal1 == ordinal2) {
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+ };
+ }
+
+ /**
+ * A basic interface to elements of the dialog window from dialog logic part. The user may extend
+ * this interface with more elements.
+ */
+ public interface DialogElements {
+ Shell getShell();
+ Button getOkButton();
+ void setMessage(String message, int type);
+ }
+
+ /**
+ * A wrapper around Combo that provides logic-level data-oriented access to the control.
+ * This is not a simply convenience wrapper, because Combo itself does not keep a real data,
+ * but only its string representation.
+ */
+ public static abstract class ComboWrapper<E> {
+ private final Combo combo;
+ public ComboWrapper(Combo combo) {
+ this.combo = combo;
+ }
+ public Combo getCombo() {
+ return combo;
+ }
+ public void addSelectionListener(SelectionListener listener) {
+ combo.addSelectionListener(listener);
+ }
+ public abstract E getSelected();
+ public abstract void setSelected(E element);
+ }
+}