org.chromium.debug.ui/src/org/chromium/debug/ui/DialogUtils.java
changeset 355 8726e95bcbba
equal deleted inserted replaced
354:0bceeb415e7f 355:8726e95bcbba
       
     1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
       
     2 // Use of this source code is governed by a BSD-style license that can be
       
     3 // found in the LICENSE file.
       
     4 
       
     5 package org.chromium.debug.ui;
       
     6 
       
     7 import java.util.ArrayList;
       
     8 import java.util.Arrays;
       
     9 import java.util.Collections;
       
    10 import java.util.Comparator;
       
    11 import java.util.HashMap;
       
    12 import java.util.LinkedHashMap;
       
    13 import java.util.LinkedHashSet;
       
    14 import java.util.List;
       
    15 import java.util.Map;
       
    16 import java.util.Set;
       
    17 
       
    18 import org.eclipse.jface.dialogs.IMessageProvider;
       
    19 import org.eclipse.swt.events.SelectionListener;
       
    20 import org.eclipse.swt.widgets.Button;
       
    21 import org.eclipse.swt.widgets.Combo;
       
    22 import org.eclipse.swt.widgets.Shell;
       
    23 
       
    24 /**
       
    25  * A small set of utility classes that help programming dialog window logic.
       
    26  */
       
    27 public class DialogUtils {
       
    28   /*
       
    29    * Part 1. Update graph.
       
    30    *
       
    31    * All logical elements of dialog and dependencies between them are modeled as DAG.
       
    32    * The data flows from vertices that corresponds to various input fields through
       
    33    * some transformations to the terminal vertices that are in-dialog helper messages and OK button.
       
    34    *
       
    35    * A linear data flow (no forks, no merges) is suitable for data transformation and
       
    36    * is programmed manually. Forks and merges are hard to dispatch manually and are managed by
       
    37    * class Updater.
       
    38    *
       
    39    * Updater knows about "source" vertices that may have several outgoing edges and about
       
    40    * "consumer" vertices that may have several incoming edges. Based on source vertex changes
       
    41    * updater updates consumer vertices in topological order.
       
    42    */
       
    43 
       
    44   /**
       
    45    * Represents source vertex for Updater. Technically updater uses this interface only as a flag
       
    46    * interface, because the only methods it uses are {@link Object#equals}/Object{@link #hashCode}.
       
    47    */
       
    48   public interface ValueSource<T> {
       
    49     /**
       
    50      * Method is not used by updater, for convenience only.
       
    51      * @return current value of the vertex
       
    52      */
       
    53     T getValue();
       
    54   }
       
    55 
       
    56   /**
       
    57    * Represents consumer vertex for Updater. Each instance should be explicitly registered in
       
    58    * Updater.
       
    59    */
       
    60   public interface ValueConsumer {
       
    61     /**
       
    62      * Called by updater when some linked sources have changed and it's time this vertex
       
    63      * got updated. {@link Updater#reportChanged} may be called if some {@line ValueSource}s have
       
    64      * changed during this update (but this should not break topological order of the graph).
       
    65      */
       
    66     void update(Updater updater);
       
    67   }
       
    68 
       
    69   /**
       
    70    * Helps to conduct update for vertices in a value graph.
       
    71    * Technically Updater does not see a real graph, because it doesn't support vertices
       
    72    * that are simultaneously source and consumer. Programmer helps manage other edges manually by
       
    73    * calling {@link #reportChanged} method.
       
    74    */
       
    75   public static class Updater {
       
    76     private final LinkedHashMap<ValueConsumer, Boolean> needsUpdateMap =
       
    77         new LinkedHashMap<ValueConsumer, Boolean>();
       
    78     private final Map<ValueSource<?>, List<ValueConsumer>> reversedDependenciesMap =
       
    79         new HashMap<ValueSource<?>, List<ValueConsumer>>();
       
    80     private boolean alreadyUpdating = false;
       
    81 
       
    82     public void addConsumer(ValueConsumer value, ValueSource<?> ... dependencies) {
       
    83       addConsumer(value, Arrays.asList(dependencies));
       
    84     }
       
    85 
       
    86     /**
       
    87      * Registers a consumer vertex with all its dependencies.
       
    88      */
       
    89     public void addConsumer(ValueConsumer value, List<? extends ValueSource<?>> dependencies) {
       
    90       Boolean res = needsUpdateMap.put(value, Boolean.FALSE);
       
    91       if (res != null) {
       
    92         throw new IllegalArgumentException("Already added"); //$NON-NLS-1$
       
    93       }
       
    94       for (ValueSource<?> dep : dependencies) {
       
    95         List<ValueConsumer> reversedDeps = reversedDependenciesMap.get(dep);
       
    96         if (reversedDeps == null) {
       
    97           reversedDeps = new ArrayList<ValueConsumer>(2);
       
    98           reversedDependenciesMap.put(dep, reversedDeps);
       
    99         }
       
   100         reversedDeps.add(value);
       
   101       }
       
   102     }
       
   103 
       
   104     /**
       
   105      * Reports about sources that have been changed and plans future update of consumers. This
       
   106      * method may be called at any time (it is not thread-safe though).
       
   107      */
       
   108     public void reportChanged(ValueSource<?> source) {
       
   109       List<ValueConsumer> reversedDeps = reversedDependenciesMap.get(source);
       
   110       if (reversedDeps != null) {
       
   111         for (ValueConsumer consumer : reversedDeps) {
       
   112           needsUpdateMap.put(consumer, Boolean.TRUE);
       
   113         }
       
   114       }
       
   115     }
       
   116 
       
   117     /**
       
   118      * Performs update of all vertices that need it. If some sources are reported changed
       
   119      * during the run of this method, their consumers are also updated.
       
   120      */
       
   121     public void update() {
       
   122       if (alreadyUpdating) {
       
   123         return;
       
   124       }
       
   125       alreadyUpdating = true;
       
   126       try {
       
   127         updateImpl();
       
   128       } finally {
       
   129         alreadyUpdating = false;
       
   130       }
       
   131     }
       
   132 
       
   133     private void updateImpl() {
       
   134       boolean hasChanges = true;
       
   135       while (hasChanges) {
       
   136         hasChanges = false;
       
   137         for (Map.Entry<ValueConsumer, Boolean> en : needsUpdateMap.entrySet()) {
       
   138           if (en.getValue() == Boolean.TRUE) {
       
   139             en.setValue(Boolean.FALSE);
       
   140             ValueConsumer currentValue = en.getKey();
       
   141             currentValue.update(this);
       
   142           }
       
   143         }
       
   144       }
       
   145     }
       
   146 
       
   147     /**
       
   148      * Updates all consumer vertices in graph.
       
   149      */
       
   150     public void updateAll() {
       
   151       for (Map.Entry<?, Boolean> en : needsUpdateMap.entrySet()) {
       
   152         en.setValue(Boolean.TRUE);
       
   153       }
       
   154       update();
       
   155     }
       
   156   }
       
   157 
       
   158   /**
       
   159    * A basic implementation of object that is both consumer and source. Updater will treat
       
   160    * as 2 separate objects.
       
   161    */
       
   162   public static abstract class ValueProcessor<T> implements ValueConsumer, ValueSource<T> {
       
   163     private T currentValue = null;
       
   164     public T getValue() {
       
   165       return currentValue;
       
   166     }
       
   167     protected void setCurrentValue(T currentValue) {
       
   168       this.currentValue = currentValue;
       
   169     }
       
   170   }
       
   171 
       
   172   /*
       
   173    * Part 2. Optional data type etc
       
   174    *
       
   175    * Since dialog should deal with error user entry, the typical data type is either value or error.
       
   176    * This is implemented as Optional interface. Most of data transformations should work only
       
   177    * when all inputs are non-error and generate error in return otherwise. This is implemented in
       
   178    * ExpressionProcessor.
       
   179    */
       
   180 
       
   181 
       
   182   /**
       
   183    * A primitive approach to "optional" algebraic type. This type is T + Set<Message>.
       
   184    */
       
   185   public interface Optional<V> {
       
   186     V getNormal();
       
   187     boolean isNormal();
       
   188     Set<? extends Message> errorMessages();
       
   189   }
       
   190 
       
   191   public static <V> Optional<V> createOptional(final V value) {
       
   192     return new Optional<V>() {
       
   193       public Set<Message> errorMessages() {
       
   194         return Collections.emptySet();
       
   195       }
       
   196       public V getNormal() {
       
   197         return value;
       
   198       }
       
   199       public boolean isNormal() {
       
   200         return true;
       
   201       }
       
   202     };
       
   203   }
       
   204 
       
   205   public static <V> Optional<V> createErrorOptional(Message message) {
       
   206     return createErrorOptional(Collections.singleton(message));
       
   207   }
       
   208 
       
   209   public static <V> Optional<V> createErrorOptional(final Set<? extends Message> messages) {
       
   210     return new Optional<V>() {
       
   211       public Set<? extends Message> errorMessages() {
       
   212         return messages;
       
   213       }
       
   214       public V getNormal() {
       
   215         throw new UnsupportedOperationException();
       
   216       }
       
   217       public boolean isNormal() {
       
   218         return false;
       
   219       }
       
   220     };
       
   221   }
       
   222 
       
   223   /**
       
   224    * A user interface message for dialog window. It has text and priority that helps choosing
       
   225    * the most important message it there are many of them.
       
   226    */
       
   227   public static class Message {
       
   228     private final String text;
       
   229     private final MessagePriority priority;
       
   230     public Message(String text, MessagePriority priority) {
       
   231       this.text = text;
       
   232       this.priority = priority;
       
   233     }
       
   234     public String getText() {
       
   235       return text;
       
   236     }
       
   237     public MessagePriority getPriority() {
       
   238       return priority;
       
   239     }
       
   240   }
       
   241 
       
   242   /**
       
   243    * Priority of a user interface message.
       
   244    * Constants are listed from most important to least important.
       
   245    */
       
   246   public enum MessagePriority {
       
   247     BLOCKING_PROBLEM(IMessageProvider.ERROR),
       
   248     BLOCKING_INFO(IMessageProvider.NONE),
       
   249     WARNING(IMessageProvider.WARNING);
       
   250 
       
   251     private final int messageProviderType;
       
   252     private MessagePriority(int messageProviderType) {
       
   253       this.messageProviderType = messageProviderType;
       
   254     }
       
   255     public int getMessageProviderType() {
       
   256       return messageProviderType;
       
   257     }
       
   258   }
       
   259 
       
   260   /**
       
   261    * A base class for the source-consumer pair that accepts several values as a consumer,
       
   262    * performs a calculation over them and gives it away the result via source interface.
       
   263    * Some sources may be of Optional type. If some of sources have error value the corresponding
       
   264    * error value is returned automatically.
       
   265    * <p>
       
   266    * The implementation should override a single method {@link #calculateNormal}.
       
   267    */
       
   268   public static abstract class ExpressionProcessor<T> extends ValueProcessor<Optional<T>> {
       
   269     private final List<ValueSource<? extends Optional<?>>> optionalSources;
       
   270     public ExpressionProcessor(List<ValueSource<? extends Optional<?>>> optionalSources) {
       
   271       this.optionalSources = optionalSources;
       
   272     }
       
   273 
       
   274     protected abstract Optional<T> calculateNormal();
       
   275 
       
   276     private Optional<T> calculateNewValue() {
       
   277       Set<Message> errors = new LinkedHashSet<Message>(0);
       
   278       for (ValueSource<? extends Optional<?>> source : optionalSources) {
       
   279         if (!source.getValue().isNormal()) {
       
   280           errors.addAll(source.getValue().errorMessages());
       
   281         }
       
   282       }
       
   283       if (errors.isEmpty()) {
       
   284         return calculateNormal();
       
   285       } else {
       
   286         return createErrorOptional(errors);
       
   287       }
       
   288     }
       
   289     public void update(Updater updater) {
       
   290       Optional<T> result = calculateNewValue();
       
   291       Optional<T> oldValue = getValue();
       
   292       setCurrentValue(result);
       
   293       if (!result.equals(oldValue)) {
       
   294         updater.reportChanged(this);
       
   295       }
       
   296     }
       
   297   }
       
   298 
       
   299   /*
       
   300    * Part 3. Various utils.
       
   301    */
       
   302 
       
   303   /**
       
   304    * A general-purpose implementation of OK button vertex. It works as a consumer of
       
   305    * 1 result value and several warning sources. From its sources it decides whether
       
   306    * OK button should be enabled and also provides dialog messages (errors, warnings, infos).
       
   307    */
       
   308   public static class OkButtonControl implements ValueConsumer {
       
   309     private final ValueSource<? extends Optional<?>> resultSource;
       
   310     private final List<? extends ValueSource<String>> warningSources;
       
   311     private final DialogElements dialogElements;
       
   312 
       
   313     public OkButtonControl(ValueSource<? extends Optional<?>> resultSource,
       
   314         List<? extends ValueSource<String>> warningSources, DialogElements dialogElements) {
       
   315       this.resultSource = resultSource;
       
   316       this.warningSources = warningSources;
       
   317       this.dialogElements = dialogElements;
       
   318     }
       
   319 
       
   320     /**
       
   321      * Returns a list of dependencies for updater -- a convenience method.
       
   322      */
       
   323     public List<? extends ValueSource<?>> getDependencies() {
       
   324       ArrayList<ValueSource<?>> result = new ArrayList<ValueSource<?>>();
       
   325       result.add(resultSource);
       
   326       result.addAll(warningSources);
       
   327       return result;
       
   328     }
       
   329 
       
   330     public void update(Updater updater) {
       
   331       Optional<?> result = resultSource.getValue();
       
   332       List<Message> messages = new ArrayList<Message>();
       
   333       for (ValueSource<String> warningSource : warningSources) {
       
   334         if (warningSource.getValue() != null) {
       
   335           messages.add(new Message(warningSource.getValue(), MessagePriority.WARNING));
       
   336         }
       
   337       }
       
   338       boolean enabled;
       
   339       if (result.isNormal()) {
       
   340         enabled = true;
       
   341       } else {
       
   342         enabled = false;
       
   343         messages.addAll(result.errorMessages());
       
   344       }
       
   345       dialogElements.getOkButton().setEnabled(enabled);
       
   346       String errorMessage;
       
   347       int type;
       
   348       if (messages.isEmpty()) {
       
   349         errorMessage = null;
       
   350         type = IMessageProvider.NONE;
       
   351       } else {
       
   352         Message visibleMessage = Collections.max(messages, messageComparatorBySeverity);
       
   353         errorMessage = visibleMessage.getText();
       
   354         type = visibleMessage.getPriority().getMessageProviderType();
       
   355       }
       
   356       dialogElements.setMessage(errorMessage, type);
       
   357     }
       
   358 
       
   359     private static final Comparator<Message> messageComparatorBySeverity =
       
   360         new Comparator<Message>() {
       
   361       public int compare(Message o1, Message o2) {
       
   362         int ordinal1 = o1.getPriority().ordinal();
       
   363         int ordinal2 = o2.getPriority().ordinal();
       
   364         if (ordinal1 < ordinal2) {
       
   365           return +1;
       
   366         } else if (ordinal1 == ordinal2) {
       
   367           return 0;
       
   368         } else {
       
   369           return -1;
       
   370         }
       
   371       }
       
   372     };
       
   373   }
       
   374 
       
   375   /**
       
   376    * A basic interface to elements of the dialog window from dialog logic part. The user may extend
       
   377    * this interface with more elements.
       
   378    */
       
   379   public interface DialogElements {
       
   380     Shell getShell();
       
   381     Button getOkButton();
       
   382     void setMessage(String message, int type);
       
   383   }
       
   384 
       
   385   /**
       
   386    * A wrapper around Combo that provides logic-level data-oriented access to the control.
       
   387    * This is not a simply convenience wrapper, because Combo itself does not keep a real data,
       
   388    * but only its string representation.
       
   389    */
       
   390   public static abstract class ComboWrapper<E> {
       
   391     private final Combo combo;
       
   392     public ComboWrapper(Combo combo) {
       
   393       this.combo = combo;
       
   394     }
       
   395     public Combo getCombo() {
       
   396       return combo;
       
   397     }
       
   398     public void addSelectionListener(SelectionListener listener) {
       
   399       combo.addSelectionListener(listener);
       
   400     }
       
   401     public abstract E getSelected();
       
   402     public abstract void setSelected(E element);
       
   403   }
       
   404 }