Add admin screen to edit the plugin configuration

Change-Id: I6830a863a42d6630d63b6e14db901f698f41bb0c
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/ImagareMenu.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/ImagareMenu.java
index 63f4689..b968b77 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/ImagareMenu.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/ImagareMenu.java
@@ -30,6 +30,8 @@
     menuEntries = new ArrayList<TopMenu.MenuEntry>();
     menuEntries.add(new MenuEntry("Tools", Collections
         .singletonList(new MenuItem("Image Upload", "#/x/" + pluginName + "/upload", ""))));
+    menuEntries.add(new MenuEntry("Plugins", Collections
+        .singletonList(new MenuItem("Imagare Admin", "#/x/" + pluginName + "/admin", ""))));
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java
index 332ef3d..dcea642 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java
@@ -18,6 +18,7 @@
 
 public class ConfigInfo extends JavaScriptObject {
   final native String getDefaultProject() /*-{ return this.default_project }-*/;
+  final native void setDefaultProject(String p) /*-{ this.default_project = p; }-*/;
 
   static ConfigInfo create() {
     ConfigInfo g = (ConfigInfo) createObject();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareAdminScreen.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareAdminScreen.java
new file mode 100644
index 0000000..cab716d
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareAdminScreen.java
@@ -0,0 +1,104 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.imagare.client;
+
+import com.google.gerrit.plugin.client.Plugin;
+import com.google.gerrit.plugin.client.rpc.RestApi;
+import com.google.gerrit.plugin.client.screen.Screen;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+public class ImagareAdminScreen extends VerticalPanel {
+
+  static class Factory implements Screen.EntryPoint {
+    @Override
+    public void onLoad(Screen screen) {
+      screen.setPageTitle("Imagare Admin");
+      screen.show(new ImagareAdminScreen());
+    }
+  }
+
+  private TextBox projectBox;
+  private Button saveButton;
+
+  ImagareAdminScreen() {
+    setStyleName("imagare-admin-screen");
+
+    new RestApi("config").id("server").view(Plugin.get().getPluginName(), "config")
+        .get(new AsyncCallback<ConfigInfo>() {
+          @Override
+          public void onSuccess(ConfigInfo info) {
+            display(info);
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            // never invoked
+          }
+        });
+  }
+
+  private void display(ConfigInfo info) {
+    HorizontalPanel p = new HorizontalPanel();
+    p.setStyleName("imagare-project-panel");
+    p.add(new Label("Project:"));
+    projectBox = new TextBox();
+    projectBox.setValue(info.getDefaultProject());
+    p.add(projectBox);
+    add(p);
+
+    HorizontalPanel buttons = new HorizontalPanel();
+    add(buttons);
+
+    saveButton = new Button("Save");
+    saveButton.addClickHandler(new ClickHandler() {
+      @Override
+      public void onClick(final ClickEvent event) {
+        doSave();
+      }
+    });
+    buttons.add(saveButton);
+    saveButton.setEnabled(false);
+    new OnEditEnabler(saveButton, projectBox);
+
+    projectBox.setFocus(true);
+    saveButton.setEnabled(false);
+  }
+
+  private void doSave() {
+    ConfigInfo in = ConfigInfo.create();
+    in.setDefaultProject(projectBox.getValue());
+    new RestApi("config").id("server").view(Plugin.get().getPluginName(), "config")
+        .put(in, new AsyncCallback<JavaScriptObject>() {
+
+          @Override
+          public void onSuccess(JavaScriptObject result) {
+            saveButton.setEnabled(false);
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            // never invoked
+          }
+        });
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java
index 221238a..68fca51 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java
@@ -24,5 +24,6 @@
   @Override
   public void onPluginLoad() {
     Plugin.get().screen("upload", new ImageUploadScreen.Factory());
+    Plugin.get().screen("admin", new ImagareAdminScreen.Factory());
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/OnEditEnabler.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/OnEditEnabler.java
new file mode 100644
index 0000000..c0de2ee
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/OnEditEnabler.java
@@ -0,0 +1,184 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.imagare.client;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.TextBoxBase;
+import com.google.gwt.user.client.ui.ValueBoxBase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/** Enables a FocusWidget (e.g. a Button) if an edit is detected from any
+ *  registered input widget.
+ */
+public class OnEditEnabler implements KeyPressHandler, KeyDownHandler,
+   MouseUpHandler, ChangeHandler, ValueChangeHandler<Object> {
+
+  private final FocusWidget widget;
+  private Map<TextBoxBase, String> strings = new HashMap<TextBoxBase, String>();
+  private String originalValue;
+
+
+  // The first parameter to the contructors must be the FocusWidget to enable,
+  // subsequent parameters are widgets to listenTo.
+
+  public OnEditEnabler(final FocusWidget w, final TextBoxBase tb) {
+    this(w);
+    originalValue = tb.getValue().trim();
+    listenTo(tb);
+  }
+
+  public OnEditEnabler(final FocusWidget w, final ListBox lb) {
+    this(w);
+    listenTo(lb);
+  }
+
+  public OnEditEnabler(final FocusWidget w, final CheckBox cb) {
+    this(w);
+    listenTo(cb);
+  }
+
+  public OnEditEnabler(final FocusWidget w) {
+    widget = w;
+  }
+
+
+  // Register input widgets to be listened to
+
+  public void listenTo(final TextBoxBase tb) {
+    strings.put(tb, tb.getText().trim());
+    tb.addKeyPressHandler(this);
+
+    // Is there another way to capture middle button X11 pastes in browsers
+    // which do not yet support ONPASTE events (Firefox)?
+    tb.addMouseUpHandler(this);
+
+    // Resetting the "original text" on focus ensures that we are
+    // up to date with non-user updates of the text (calls to
+    // setText()...) and also up to date with user changes which
+    // occured after enabling "widget".
+    tb.addFocusHandler(new FocusHandler() {
+        @Override
+        public void onFocus(FocusEvent event) {
+          strings.put(tb, tb.getText().trim());
+        }
+      });
+
+    // CTRL-V Pastes in Chrome seem only detectable via BrowserEvents or
+    // KeyDownEvents, the latter is better.
+    tb.addKeyDownHandler(this);
+  }
+
+  public void listenTo(final ListBox lb) {
+    lb.addChangeHandler(this);
+  }
+
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  public void listenTo(final CheckBox cb) {
+    cb.addValueChangeHandler((ValueChangeHandler) this);
+  }
+
+
+  // Handlers
+
+  @Override
+  public void onKeyPress(final KeyPressEvent e) {
+    on(e);
+  }
+
+  @Override
+  public void onKeyDown(final KeyDownEvent e) {
+    on(e);
+  }
+
+  @Override
+  public void onMouseUp(final MouseUpEvent e) {
+    on(e);
+  }
+
+  @Override
+  public void onChange(final ChangeEvent e) {
+    on(e);
+  }
+
+  @SuppressWarnings("rawtypes")
+  @Override
+  public void onValueChange(final ValueChangeEvent e) {
+    on(e);
+  }
+
+  private void on(final GwtEvent<?> e) {
+    if (widget.isEnabled() ||
+        ! (e.getSource() instanceof FocusWidget) ||
+        ! ((FocusWidget) e.getSource()).isEnabled() ) {
+      if (e.getSource() instanceof ValueBoxBase) {
+        final TextBoxBase box = ((TextBoxBase) e.getSource());
+        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+          @Override
+          public void execute() {
+            if (box.getValue().trim().equals(originalValue)) {
+              widget.setEnabled(false);
+            }
+          }
+        });
+      }
+      return;
+    }
+
+    if (e.getSource() instanceof TextBoxBase) {
+      onTextBoxBase((TextBoxBase) e.getSource());
+    } else {
+      // For many widgets, we can assume that a change is an edit. If
+      // a widget does not work that way, it should be special cased
+      // above.
+      widget.setEnabled(true);
+    }
+  }
+
+  private void onTextBoxBase(final TextBoxBase tb) {
+    // The text appears to not get updated until the handlers complete.
+    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+      @Override
+      public void execute() {
+        String orig = strings.get(tb);
+        if (orig == null) {
+          orig = "";
+        }
+        if (! orig.equals(tb.getText().trim())) {
+          widget.setEnabled(true);
+        }
+      }
+    });
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/public/imagare.css b/src/main/java/com/googlesource/gerrit/plugins/imagare/public/imagare.css
index b227fc2..b30fdff 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/public/imagare.css
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/public/imagare.css
@@ -1,4 +1,5 @@
-.imagare-image-upload-screen, .imagare-uploaded-images-panel {
+.imagare-image-upload-screen, .imagare-uploaded-images-panel,
+.imagare-admin-screen {
   width: 100%;
   border-spacing: 0px 5px;
 }