Fix dialog layouts, login widget order, agreement persistence
diff --git a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/AbstractDialog.java b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/AbstractDialog.java
index 8e8c18e..6c71027 100644
--- a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/AbstractDialog.java
+++ b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/AbstractDialog.java
@@ -10,24 +10,67 @@
  */
 package org.eclipse.userstorage.ui;
 
-import org.eclipse.userstorage.IStorageService;
+import org.eclipse.userstorage.internal.util.StringUtil;
 import org.eclipse.userstorage.ui.internal.Activator;
 
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.jface.dialogs.TitleAreaDialog;
 import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.graphics.FontData;
 import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+import java.lang.reflect.Field;
 
 /**
  * @author Eike Stepper
  */
 public abstract class AbstractDialog extends TitleAreaDialog
 {
+  private static final String DIALOG_WIDTH = getDialogConstant("DIALOG_WIDTH", "DIALOG_WIDTH");
+
+  private static final String DIALOG_HEIGHT = getDialogConstant("DIALOG_HEIGHT", "DIALOG_HEIGHT");
+
+  private static final String DIALOG_FONT_DATA = getDialogConstant("DIALOG_FONT_DATA", "DIALOG_FONT_NAME");
+
+  private static final String DIALOG_MESSAGE = "DIALOG_MESSAGE";
+
+  private static final int WIDTH_MIN = 350;
+
+  private static final int WIDTH_MAX = 5000;
+
+  private static final int WIDTH_INC1 = 250;
+
+  private static final int WIDTH_INC2 = WIDTH_INC1 / 10;
+
+  private static final Field messageLabelField;
+
+  static
+  {
+    Field field = null;
+
+    try
+    {
+      field = TitleAreaDialog.class.getDeclaredField("messageLabel");
+      field.setAccessible(true);
+    }
+    catch (Throwable ex)
+    {
+      field = null;
+    }
+
+    messageLabelField = field;
+  }
+
   public AbstractDialog(Shell parentShell)
   {
     super(parentShell);
@@ -35,6 +78,168 @@
   }
 
   @Override
+  public boolean close()
+  {
+    Shell shell = getShell();
+    if (shell != null && !shell.isDisposed())
+    {
+      IDialogSettings settings = getDialogBoundsSettings();
+      if (settings != null)
+      {
+        String message = getMessage();
+
+        int strategy = getDialogBoundsStrategy();
+        if ((strategy & DIALOG_PERSISTSIZE) != 0)
+        {
+          settings.put(DIALOG_MESSAGE, message);
+        }
+      }
+    }
+
+    return super.close();
+  }
+
+  protected IDialogSettings getPluginSettings()
+  {
+    return null;
+  }
+
+  protected String getDialogSettingsName()
+  {
+    return getClass().getSimpleName();
+  }
+
+  @Override
+  protected IDialogSettings getDialogBoundsSettings()
+  {
+    IDialogSettings settings = getPluginSettings();
+    if (settings == null)
+    {
+      return null;
+    }
+
+    String sectionName = getDialogSettingsName();
+    if (sectionName == null)
+    {
+      return null;
+    }
+
+    IDialogSettings section = settings.getSection(sectionName);
+    if (section == null)
+    {
+      section = settings.addNewSection(sectionName);
+    }
+
+    return section;
+  }
+
+  @Override
+  protected int getDialogBoundsStrategy()
+  {
+    return DIALOG_PERSISTSIZE;
+  }
+
+  @Override
+  protected Point getInitialSize()
+  {
+    Shell shell = getShell();
+    Point result = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
+
+    Point minimum = getMinimumSize();
+    result.x = Math.max(result.x, minimum.x);
+    result.y = Math.max(result.y, minimum.y);
+
+    if ((getDialogBoundsStrategy() & DIALOG_PERSISTSIZE) != 0)
+    {
+      IDialogSettings settings = getDialogBoundsSettings();
+      if (settings != null)
+      {
+        if (hasSameFont(settings) && hasSameMessage(settings))
+        {
+          try
+          {
+            int width = settings.getInt(DIALOG_WIDTH);
+            if (width != DIALOG_DEFAULT_BOUNDS)
+            {
+              result.x = width;
+            }
+          }
+          catch (NumberFormatException ex)
+          {
+            //$FALL-THROUGH$
+          }
+
+          try
+          {
+            int height = settings.getInt(DIALOG_HEIGHT);
+            if (height != DIALOG_DEFAULT_BOUNDS)
+            {
+              result.y = height;
+            }
+          }
+          catch (NumberFormatException ex)
+          {
+            //$FALL-THROUGH$
+          }
+
+          return result;
+        }
+      }
+    }
+
+    Text messageLabel;
+
+    try
+    {
+      messageLabel = (Text)messageLabelField.get(this);
+    }
+    catch (Throwable ex)
+    {
+      return result;
+    }
+
+    String message = messageLabel.getText();
+    messageLabel.setText("\n\n");
+
+    int messageHeight = messageLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
+    messageLabel.setText(message);
+
+    result.x = WIDTH_MIN;
+    while (result.x < WIDTH_MAX)
+    {
+      result.x += WIDTH_INC1;
+      shell.setSize(result);
+
+      Point messageSize = messageLabel.computeSize(messageLabel.getSize().x, SWT.DEFAULT);
+      if (messageSize.y <= messageHeight)
+      {
+        break;
+      }
+    }
+
+    result.x -= WIDTH_INC1;
+    while (result.x < WIDTH_MAX)
+    {
+      result.x += WIDTH_INC2;
+      shell.setSize(result);
+
+      Point messageSize = messageLabel.computeSize(messageLabel.getSize().x, SWT.DEFAULT);
+      if (messageSize.y <= messageHeight)
+      {
+        break;
+      }
+    }
+
+    result = shell.computeSize(result.x + WIDTH_INC2, SWT.DEFAULT, true);
+    return result;
+  }
+
+  protected Point getMinimumSize()
+  {
+    return new Point(WIDTH_MIN, 0);
+  }
+
+  @Override
   protected Control createDialogArea(Composite parent)
   {
     Shell shell = getShell();
@@ -55,16 +260,50 @@
     return super.createDialogArea(parent);
   }
 
-  public static String createShellText(IStorageService service)
+  private boolean hasSameFont(IDialogSettings settings)
   {
-    String shellText = "User Storage Service";
-
-    String authority = service.getServiceURI().getAuthority();
-    if (authority != null && authority.endsWith(".eclipse.org"))
+    String previousFontData = settings.get(DIALOG_FONT_DATA);
+    if (StringUtil.isEmpty(previousFontData))
     {
-      shellText = "Eclipse " + shellText;
+      return false;
     }
 
-    return shellText;
+    FontData[] fontDatas = JFaceResources.getDialogFont().getFontData();
+    if (fontDatas.length == 0)
+    {
+      return false;
+    }
+
+    String currentFontData = fontDatas[0].toString();
+    return previousFontData.equalsIgnoreCase(currentFontData);
+  }
+
+  private boolean hasSameMessage(IDialogSettings settings)
+  {
+    String previousMessage = settings.get(DIALOG_MESSAGE);
+    if (StringUtil.isEmpty(previousMessage))
+    {
+      return false;
+    }
+
+    String currentMessage = getMessage();
+    return previousMessage.equals(currentMessage);
+  }
+
+  private static String getDialogConstant(String name, String defaultValue)
+  {
+    try
+    {
+      Field field = Dialog.class.getDeclaredField(name);
+      field.setAccessible(true);
+
+      return (String)field.get(null);
+    }
+    catch (Throwable ex)
+    {
+      //$FALL-THROUGH$
+    }
+
+    return defaultValue;
   }
 }
diff --git a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/AddServiceDialog.java b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/AddServiceDialog.java
index 6118c21..acaf415 100644
--- a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/AddServiceDialog.java
+++ b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/AddServiceDialog.java
@@ -15,6 +15,7 @@
 import org.eclipse.userstorage.ui.AbstractDialog;
 
 import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ModifyEvent;
 import org.eclipse.swt.events.ModifyListener;
@@ -98,7 +99,13 @@
   }
 
   @Override
-  protected Point getInitialSize()
+  protected IDialogSettings getPluginSettings()
+  {
+    return Activator.getDefault().getDialogSettings();
+  }
+
+  @Override
+  protected Point getMinimumSize()
   {
     return new Point(600, 350);
   }
diff --git a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/CredentialsComposite.java b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/CredentialsComposite.java
index 5d9faa4..db0fa0f 100644
--- a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/CredentialsComposite.java
+++ b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/CredentialsComposite.java
@@ -37,7 +37,7 @@
  */
 public class CredentialsComposite extends Composite
 {
-  public static final Point INITIAL_SIZE = new Point(500, 350);
+  public static final Point INITIAL_SIZE = new Point(600, 500);
 
   private final Callable<URI> createAccountURIProvider = new Callable<URI>()
   {
@@ -72,7 +72,7 @@
     public void modifyText(ModifyEvent e)
     {
       credentials = new Credentials(usernameText.getText(), passwordText.getText());
-      validate();
+      updateEnablement();
     }
   };
 
@@ -82,14 +82,6 @@
 
   private Credentials credentials;
 
-  private boolean termsOfUseAgreed;
-
-  private Button termsOfUseButton;
-
-  private MultiLink termsOfUseMultiLink;
-
-  private Label spacer;
-
   private Label usernameLabel;
 
   private Text usernameText;
@@ -98,12 +90,24 @@
 
   private Text passwordText;
 
+  private Label horizontalSpacer;
+
+  private boolean termsOfUseAgreed;
+
+  private Button termsOfUseButton;
+
+  private MultiLink termsOfUseMultiLink;
+
+  private Label verticalSpacer;
+
   private Link createAccountLink;
 
   private Link editAccountLink;
 
   private Link recoverPasswordLink;
 
+  private boolean valid;
+
   public CredentialsComposite(Composite parent, int style, int marginWidth, int marginHeight, boolean showServiceCredentials)
   {
     super(parent, style);
@@ -137,15 +141,18 @@
       {
         int columns = getGridColumns();
 
+        horizontalSpacer.setVisible(true);
+        horizontalSpacer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1));
+
         termsOfUseButton.setVisible(true);
         termsOfUseButton.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false));
 
-        termsOfUseMultiLink.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false, columns - 1, 1));
+        termsOfUseMultiLink.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false, columns - 2, 1));
         termsOfUseMultiLink.setVisible(true);
         termsOfUseMultiLink.setText(termsOfUse);
 
-        spacer.setVisible(true);
-        spacer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, columns, 1));
+        verticalSpacer.setVisible(true);
+        verticalSpacer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, columns, 1));
       }
 
       if (showServiceCredentials)
@@ -203,12 +210,18 @@
 
   public int getGridColumns()
   {
-    return 2;
+    return 3;
+  }
+
+  public boolean isValid()
+  {
+    return valid;
   }
 
   @Override
   public void setEnabled(boolean enabled)
   {
+    super.setEnabled(true);
     usernameLabel.setEnabled(enabled);
     usernameText.setEnabled(enabled);
     passwordLabel.setEnabled(enabled);
@@ -220,6 +233,19 @@
 
   protected void createUI(Composite parent, int columns)
   {
+    usernameLabel = new Label(parent, SWT.NONE);
+    usernameLabel.setText("User name:");
+    usernameText = new Text(parent, SWT.BORDER);
+    usernameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, columns - 1, 1));
+    usernameText.addModifyListener(modifyListener);
+
+    passwordLabel = new Label(parent, SWT.NONE);
+    passwordLabel.setText("Password:");
+    passwordText = new Text(parent, SWT.BORDER | SWT.PASSWORD);
+    passwordText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, columns - 1, 1));
+    passwordText.addModifyListener(modifyListener);
+
+    horizontalSpacer = new Label(parent, SWT.NONE);
     termsOfUseButton = new Button(parent, SWT.CHECK);
     termsOfUseButton.addSelectionListener(new SelectionAdapter()
     {
@@ -228,37 +254,49 @@
       {
         termsOfUseAgreed = termsOfUseButton.getSelection();
         updateEnablement();
-        validate();
       }
     });
 
     termsOfUseMultiLink = new MultiLink.ForSystemBrowser(parent, SWT.WRAP);
-    spacer = new Label(parent, SWT.NONE);
-    hideTermsOfUse();
-
-    usernameLabel = new Label(parent, SWT.NONE);
-    usernameLabel.setText("User name:");
-
-    usernameText = new Text(parent, SWT.BORDER);
-    usernameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, columns - 1, 1));
-    usernameText.addModifyListener(modifyListener);
-
-    passwordLabel = new Label(parent, SWT.NONE);
-    passwordLabel.setText("Password:");
-
-    passwordText = new Text(parent, SWT.BORDER | SWT.PASSWORD);
-    passwordText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, columns - 1, 1));
-    passwordText.addModifyListener(modifyListener);
+    verticalSpacer = new Label(parent, SWT.NONE);
 
     createAccountLink = createLink(parent, columns, "Create an account", createAccountURIProvider);
     editAccountLink = createLink(parent, columns, "Edit your account", editAccountURIProvider);
     recoverPasswordLink = createLink(parent, columns, "Recover your password", recoverPasswordURIProvider);
+
+    hideTermsOfUse();
   }
 
   protected void validate()
   {
   }
 
+  private void updateEnablement()
+  {
+    boolean enabled = isEnabled() && service != null;
+
+    enableLink(createAccountLink, createAccountURIProvider, enabled);
+    enableLink(editAccountLink, editAccountURIProvider, enabled);
+    enableLink(recoverPasswordLink, recoverPasswordURIProvider, enabled);
+
+    if (enabled)
+    {
+      String termsOfUseLink = service.getTermsOfUseLink();
+      if (!StringUtil.isEmpty(termsOfUseLink))
+      {
+        if (!termsOfUseAgreed)
+        {
+          valid = false;
+          validate();
+          return;
+        }
+      }
+    }
+
+    valid = enabled;
+    validate();
+  }
+
   private Link createLink(Composite parent, int columns, final String label, final Callable<URI> uriProvider)
   {
     new Label(parent, SWT.NONE); // Skip first column.
@@ -301,47 +339,20 @@
     }
   }
 
-  private void updateEnablement()
-  {
-    boolean enabled = isValid();
-
-    usernameLabel.setEnabled(enabled);
-    usernameText.setEnabled(enabled);
-    passwordLabel.setEnabled(enabled);
-    passwordText.setEnabled(enabled);
-
-    enableLink(createAccountLink, createAccountURIProvider, enabled);
-    enableLink(editAccountLink, editAccountURIProvider, enabled);
-    enableLink(recoverPasswordLink, recoverPasswordURIProvider, enabled);
-  }
-
-  private boolean isValid()
-  {
-    if (service == null)
-    {
-      return false;
-    }
-
-    String termsOfUseLink = service.getTermsOfUseLink();
-    if (StringUtil.isEmpty(termsOfUseLink))
-    {
-      return true;
-    }
-
-    return termsOfUseAgreed;
-  }
-
   private void hideTermsOfUse()
   {
+    horizontalSpacer.setVisible(false);
+    horizontalSpacer.setLayoutData(emptyGridData(1, 1));
+
     termsOfUseButton.setVisible(false);
     termsOfUseButton.setLayoutData(emptyGridData(1, 1));
 
     termsOfUseMultiLink.setVisible(false);
-    termsOfUseMultiLink.setLayoutData(emptyGridData(getGridColumns() - 1, 1));
+    termsOfUseMultiLink.setLayoutData(emptyGridData(getGridColumns() - 2, 1));
     termsOfUseMultiLink.setText(StringUtil.EMPTY);
 
-    spacer.setVisible(false);
-    spacer.setLayoutData(emptyGridData(getGridColumns(), 1));
+    verticalSpacer.setVisible(false);
+    verticalSpacer.setLayoutData(emptyGridData(getGridColumns(), 1));
   }
 
   private static GridData emptyGridData(int horizontalSpan, int verticalSpan)
diff --git a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/CredentialsDialog.java b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/CredentialsDialog.java
similarity index 83%
rename from org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/CredentialsDialog.java
rename to org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/CredentialsDialog.java
index 72cf073..33f11e7 100644
--- a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/CredentialsDialog.java
+++ b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/CredentialsDialog.java
@@ -8,17 +8,18 @@
  * Contributors:
  *    Eike Stepper - initial API and implementation
  */
-package org.eclipse.userstorage.ui;
+package org.eclipse.userstorage.ui.internal;
 
 import org.eclipse.userstorage.IStorageService;
+import org.eclipse.userstorage.internal.StorageService;
 import org.eclipse.userstorage.internal.util.StringUtil;
 import org.eclipse.userstorage.spi.Credentials;
-import org.eclipse.userstorage.ui.internal.CredentialsComposite;
+import org.eclipse.userstorage.ui.AbstractDialog;
 
 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
@@ -58,9 +59,9 @@
   }
 
   @Override
-  protected Point getInitialSize()
+  protected IDialogSettings getPluginSettings()
   {
-    return CredentialsComposite.INITIAL_SIZE;
+    return Activator.getDefault().getDialogSettings();
   }
 
   @Override
@@ -68,14 +69,21 @@
   {
     super.configureShell(newShell);
 
-    String shellText = AbstractDialog.createShellText(service);
+    String shellText = "User Storage Service";
+
+    String authority = service.getServiceURI().getAuthority();
+    if (authority != null && authority.endsWith(".eclipse.org"))
+    {
+      shellText = "Eclipse " + shellText;
+    }
+
     newShell.setText(shellText);
   }
 
   @Override
   protected Control createDialogArea(Composite parent)
   {
-    setTitle("LogIn");
+    setTitle("Login");
     if (reauthentication)
     {
       setErrorMessage("You could not be logged in to your " + service.getServiceLabel() + " account. Please try again.");
@@ -115,6 +123,7 @@
   @Override
   protected void okPressed()
   {
+    ((StorageService)service).setTermsOfUseAgreed(credentialsComposite.isTermsOfUseAgreed());
     credentials = credentialsComposite.getCredentials();
     super.okPressed();
   }
@@ -154,7 +163,7 @@
   {
     if (okButton != null)
     {
-      boolean valid = isPageValid();
+      boolean valid = credentialsComposite.isValid();
       okButton.setEnabled(valid);
     }
   }
diff --git a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/DialogCredentialsProvider.java b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/DialogCredentialsProvider.java
index a8b6740..0594865 100644
--- a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/DialogCredentialsProvider.java
+++ b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/DialogCredentialsProvider.java
@@ -13,7 +13,6 @@
 import org.eclipse.userstorage.IStorageService;
 import org.eclipse.userstorage.spi.Credentials;
 import org.eclipse.userstorage.spi.ICredentialsProvider;
-import org.eclipse.userstorage.ui.CredentialsDialog;
 
 import org.eclipse.swt.widgets.Shell;