// Copyright (C) 2012 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.google.gerrit.client.changes; import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.account.AccountApi; import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.reviewdb.client.Change; 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.event.dom.client.KeyPressEvent; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Image; import com.google.gwtexpui.globalkey.client.KeyCommand; import com.google.web.bindery.event.shared.Event; import com.google.web.bindery.event.shared.HandlerRegistration; import java.util.LinkedHashMap; import java.util.Map; /** Supports the star icon displayed on changes and tracking the status. */ public class StarredChanges { private static final Event.Type<ChangeStarHandler> TYPE = new Event.Type<>(); /** Handler that can receive notifications of a change's starred status. */ public static interface ChangeStarHandler { public void onChangeStar(ChangeStarEvent event); } /** Event fired when a star changes status. The new status is reported. */ public static class ChangeStarEvent extends Event<ChangeStarHandler> { private boolean starred; public ChangeStarEvent(Change.Id source, boolean starred) { setSource(source); this.starred = starred; } public boolean isStarred() { return starred; } @Override public Type<ChangeStarHandler> getAssociatedType() { return TYPE; } @Override protected void dispatch(ChangeStarHandler handler) { handler.onChangeStar(this); } } /** * Create a star icon for the given change, and current status. Returns null * if the user is not signed in and cannot support starred changes. */ public static Icon createIcon(Change.Id source, boolean starred) { return Gerrit.isSignedIn() ? new Icon(source, starred) : null; } /** Make a key command that toggles the star for a change. */ public static KeyCommand newKeyCommand(final Icon icon) { return new KeyCommand(0, 's', Util.C.changeTableStar()) { @Override public void onKeyPress(KeyPressEvent event) { icon.toggleStar(); } }; } /** Add a handler to listen for starred status to change. */ public static HandlerRegistration addHandler( Change.Id source, ChangeStarHandler handler) { return Gerrit.EVENT_BUS.addHandlerToSource(TYPE, source, handler); } /** * Broadcast the current starred value of a change to UI widgets. This does * not RPC to the server and does not alter the starred status of a change. */ public static void fireChangeStarEvent(Change.Id id, boolean starred) { Gerrit.EVENT_BUS.fireEventFromSource( new ChangeStarEvent(id, starred), id); } /** * Set the starred status of a change. This method broadcasts to all * interested UI widgets and sends an RPC to the server to record the * updated status. */ public static void toggleStar( final Change.Id changeId, final boolean newValue) { pending.put(changeId, newValue); fireChangeStarEvent(changeId, newValue); if (!busy) { startRequest(); } } private static boolean busy; private static final Map<Change.Id, Boolean> pending = new LinkedHashMap<>(4); private static void startRequest() { busy = true; final Change.Id id = pending.keySet().iterator().next(); final boolean starred = pending.remove(id); RestApi call = AccountApi.self().view("starred.changes").id(id.get()); AsyncCallback<JavaScriptObject> cb = new AsyncCallback<JavaScriptObject>() { @Override public void onSuccess(JavaScriptObject none) { if (pending.isEmpty()) { busy = false; } else { startRequest(); } } @Override public void onFailure(Throwable caught) { if (!starred && RestApi.isStatus(caught, 404)) { onSuccess(null); return; } fireChangeStarEvent(id, !starred); for (Map.Entry<Change.Id, Boolean> e : pending.entrySet()) { fireChangeStarEvent(e.getKey(), !e.getValue()); } pending.clear(); busy = false; } }; if (starred) { call.put(cb); } else { call.delete(cb); } } public static class Icon extends Image implements ChangeStarHandler, ClickHandler { private final Change.Id changeId; private boolean starred; private HandlerRegistration handler; Icon(Change.Id changeId, boolean starred) { super(resource(starred)); this.changeId = changeId; this.starred = starred; addClickHandler(this); } /** * Toggles the state of the star, as if the user clicked on the image. This * will broadcast the new star status to all interested UI widgets, and RPC * to the server to store the changed value. */ public void toggleStar() { StarredChanges.toggleStar(changeId, !starred); } @Override protected void onLoad() { handler = StarredChanges.addHandler(changeId, this); } @Override protected void onUnload() { handler.removeHandler(); handler = null; } @Override public void onChangeStar(ChangeStarEvent event) { setResource(resource(event.isStarred())); starred = event.isStarred(); } @Override public void onClick(ClickEvent event) { toggleStar(); } private static ImageResource resource(boolean starred) { return starred ? Gerrit.RESOURCES.starFilled() : Gerrit.RESOURCES.starOpen(); } } private StarredChanges() { } }