| // Copyright (C) 2013 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.change; |
| |
| import static com.google.gerrit.common.PageLinks.op; |
| |
| import com.google.gerrit.client.changes.ChangeApi; |
| import com.google.gerrit.client.changes.ChangeInfo; |
| import com.google.gerrit.client.changes.ChangeInfo.CommitInfo; |
| import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo; |
| import com.google.gerrit.client.changes.ChangeList; |
| import com.google.gerrit.client.rpc.Natives; |
| import com.google.gerrit.extensions.common.ListChangesOption; |
| import com.google.gerrit.reviewdb.client.Change; |
| import com.google.gerrit.reviewdb.client.PatchSet; |
| import com.google.gwt.core.client.GWT; |
| import com.google.gwt.core.client.JavaScriptObject; |
| import com.google.gwt.core.client.JsArray; |
| import com.google.gwt.event.logical.shared.SelectionEvent; |
| import com.google.gwt.event.logical.shared.SelectionHandler; |
| import com.google.gwt.resources.client.ClientBundle; |
| import com.google.gwt.resources.client.CssResource; |
| import com.google.gwt.user.client.rpc.AsyncCallback; |
| import com.google.gwt.user.client.ui.Composite; |
| import com.google.gwt.user.client.ui.TabBar; |
| import com.google.gwt.user.client.ui.TabPanel; |
| |
| import java.util.ArrayList; |
| import java.util.EnumSet; |
| import java.util.List; |
| |
| public class RelatedChanges extends TabPanel { |
| static final RelatedChangesResources R = GWT |
| .create(RelatedChangesResources.class); |
| |
| interface RelatedChangesResources extends ClientBundle { |
| @Source("related_changes.css") |
| RelatedChangesCss css(); |
| } |
| |
| interface RelatedChangesCss extends CssResource { |
| String activeRow(); |
| String current(); |
| String gitweb(); |
| String indirect(); |
| String notCurrent(); |
| String pointer(); |
| String row(); |
| String subject(); |
| String tabPanel(); |
| } |
| |
| private enum Tab { |
| RELATED_CHANGES(Resources.C.relatedChanges(), |
| Resources.C.relatedChangesTooltip()) { |
| @Override |
| String getTitle(int count) { |
| return Resources.M.relatedChanges(count); |
| } |
| |
| @Override |
| String getTitle(String count) { |
| return Resources.M.relatedChanges(count); |
| } |
| }, |
| |
| SAME_TOPIC(Resources.C.sameTopic(), |
| Resources.C.sameTopicTooltip()) { |
| @Override |
| String getTitle(int count) { |
| return Resources.M.sameTopic(count); |
| } |
| |
| @Override |
| String getTitle(String count) { |
| return Resources.M.sameTopic(count); |
| } |
| }, |
| |
| CONFLICTING_CHANGES(Resources.C.conflictingChanges(), |
| Resources.C.conflictingChangesTooltip()) { |
| @Override |
| String getTitle(int count) { |
| return Resources.M.conflictingChanges(count); |
| } |
| |
| @Override |
| String getTitle(String count) { |
| return Resources.M.conflictingChanges(count); |
| } |
| }, |
| |
| CHERRY_PICKS(Resources.C.cherryPicks(), |
| Resources.C.cherryPicksTooltip()) { |
| @Override |
| String getTitle(int count) { |
| return Resources.M.cherryPicks(count); |
| } |
| |
| @Override |
| String getTitle(String count) { |
| return Resources.M.cherryPicks(count); |
| } |
| }; |
| |
| final String defaultTitle; |
| final String tooltip; |
| |
| abstract String getTitle(int count); |
| abstract String getTitle(String count); |
| |
| private Tab(String defaultTitle, String tooltip) { |
| this.defaultTitle = defaultTitle; |
| this.tooltip = tooltip; |
| } |
| } |
| |
| private final List<RelatedChangesTab> tabs; |
| private int maxHeightWithHeader; |
| private int selectedTab; |
| private int outstandingCallbacks; |
| |
| RelatedChanges() { |
| tabs = new ArrayList<>(Tab.values().length); |
| selectedTab = -1; |
| |
| setVisible(false); |
| addStyleName(R.css().tabPanel()); |
| initTabBar(); |
| } |
| |
| private void initTabBar() { |
| TabBar tabBar = getTabBar(); |
| tabBar.addSelectionHandler(new SelectionHandler<Integer>() { |
| @Override |
| public void onSelection(SelectionEvent<Integer> event) { |
| if (selectedTab >= 0) { |
| tabs.get(selectedTab).registerKeys(false); |
| } |
| selectedTab = event.getSelectedItem(); |
| tabs.get(selectedTab).registerKeys(true); |
| } |
| }); |
| |
| for (Tab tabInfo : Tab.values()) { |
| RelatedChangesTab panel = new RelatedChangesTab(); |
| add(panel, tabInfo.defaultTitle); |
| tabs.add(panel); |
| |
| TabBar.Tab tab = tabBar.getTab(tabInfo.ordinal()); |
| tab.setWordWrap(false); |
| ((Composite) tab).setTitle(tabInfo.tooltip); |
| |
| setTabEnabled(tabInfo, false); |
| } |
| getTab(Tab.RELATED_CHANGES).setShowIndirectAncestors(true); |
| getTab(Tab.CHERRY_PICKS).setShowBranches(true); |
| } |
| |
| void set(final ChangeInfo info, final String revision) { |
| if (info.status().isOpen()) { |
| setForOpenChange(info, revision); |
| } |
| |
| ChangeApi.revision(info.legacy_id().get(), revision).view("related") |
| .get(new TabCallback<RelatedInfo>(Tab.RELATED_CHANGES, info.project(), revision) { |
| @Override |
| public JsArray<ChangeAndCommit> convert(RelatedInfo result) { |
| return result.changes(); |
| } |
| }); |
| |
| StringBuilder cherryPicksQuery = new StringBuilder(); |
| cherryPicksQuery.append(op("project", info.project())); |
| cherryPicksQuery.append(" ").append(op("change", info.change_id())); |
| cherryPicksQuery.append(" ").append(op("-change", info.legacy_id().get())); |
| cherryPicksQuery.append(" -is:abandoned"); |
| ChangeList.query(cherryPicksQuery.toString(), |
| EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT), |
| new TabChangeListCallback(Tab.CHERRY_PICKS, info.project(), revision)); |
| |
| if (info.topic() != null && !"".equals(info.topic())) { |
| StringBuilder topicQuery = new StringBuilder(); |
| topicQuery.append("status:open"); |
| topicQuery.append(" ").append(op("project", info.project())); |
| topicQuery.append(" ").append(op("branch", info.branch())); |
| topicQuery.append(" ").append(op("topic", info.topic())); |
| topicQuery.append(" ").append(op("-change", info.legacy_id().get())); |
| ChangeList.query(topicQuery.toString(), |
| EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT), |
| new TabChangeListCallback(Tab.SAME_TOPIC, info.project(), revision)); |
| } |
| } |
| |
| private void setForOpenChange(final ChangeInfo info, final String revision) { |
| if (info.mergeable()) { |
| StringBuilder conflictsQuery = new StringBuilder(); |
| conflictsQuery.append("status:open"); |
| conflictsQuery.append(" is:mergeable"); |
| conflictsQuery.append(" ").append(op("conflicts", info.legacy_id().get())); |
| ChangeList.query(conflictsQuery.toString(), |
| EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT), |
| new TabChangeListCallback(Tab.CONFLICTING_CHANGES, info.project(), revision)); |
| } |
| } |
| |
| @Override |
| protected void onLoad() { |
| super.onLoad(); |
| R.css().ensureInjected(); |
| } |
| |
| private RelatedChangesTab getTab(Tab tabInfo) { |
| return tabs.get(tabInfo.ordinal()); |
| } |
| |
| private void setTabTitle(Tab tabInfo, String title) { |
| getTabBar().setTabText(tabInfo.ordinal(), title); |
| } |
| |
| private void setTabEnabled(Tab tabInfo, boolean enabled) { |
| getTabBar().setTabEnabled(tabInfo.ordinal(), enabled); |
| } |
| |
| void setMaxHeight(int height) { |
| maxHeightWithHeader = height; |
| if (isVisible()) { |
| applyMaxHeight(); |
| } |
| } |
| |
| private void applyMaxHeight() { |
| int header = getTabBar().getOffsetHeight() + 2 /* padding */; |
| for (int i = 0; i < getTabBar().getTabCount(); i++) { |
| tabs.get(i).setMaxHeight(maxHeightWithHeader - header); |
| } |
| } |
| |
| private abstract class TabCallback<T> implements AsyncCallback<T> { |
| private final Tab tabInfo; |
| private final String project; |
| private final String revision; |
| |
| TabCallback(Tab tabInfo, String project, String revision) { |
| this.tabInfo = tabInfo; |
| this.project = project; |
| this.revision = revision; |
| outstandingCallbacks++; |
| } |
| |
| protected abstract JsArray<ChangeAndCommit> convert(T result); |
| |
| @Override |
| public void onSuccess(T result) { |
| JsArray<ChangeAndCommit> changes = convert(result); |
| if (changes.length() > 0) { |
| setTabTitle(tabInfo, tabInfo.getTitle(changes.length())); |
| getTab(tabInfo).setChanges(project, revision, changes); |
| } |
| onDone(changes.length() > 0); |
| } |
| |
| @Override |
| public void onFailure(Throwable err) { |
| setTabTitle(tabInfo, tabInfo.getTitle(Resources.C.notAvailable())); |
| getTab(tabInfo).setError(err.getMessage()); |
| onDone(true); |
| } |
| |
| private void onDone(boolean enabled) { |
| setTabEnabled(tabInfo, enabled); |
| outstandingCallbacks--; |
| if (outstandingCallbacks == 0 || (enabled && tabInfo == Tab.RELATED_CHANGES)) { |
| outstandingCallbacks = 0; // Only execute this block once |
| for (int i = 0; i < getTabBar().getTabCount(); i++) { |
| if (getTabBar().isTabEnabled(i)) { |
| selectTab(i); |
| setVisible(true); |
| applyMaxHeight(); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| private class TabChangeListCallback extends TabCallback<ChangeList> { |
| TabChangeListCallback(Tab tabInfo, String project, String revision) { |
| super(tabInfo, project, revision); |
| } |
| |
| @Override |
| protected JsArray<ChangeAndCommit> convert(ChangeList l) { |
| JsArray<ChangeAndCommit> arr = JavaScriptObject.createArray().cast(); |
| for (ChangeInfo i : Natives.asList(l)) { |
| if (i.current_revision() != null && i.revisions().containsKey(i.current_revision())) { |
| RevisionInfo currentRevision = i.revision(i.current_revision()); |
| ChangeAndCommit c = ChangeAndCommit.create(); |
| c.set_id(i.id()); |
| c.set_commit(currentRevision.commit()); |
| c.set_change_number(i.legacy_id().get()); |
| c.set_revision_number(currentRevision._number()); |
| c.set_branch(i.branch()); |
| arr.push(c); |
| } |
| } |
| return arr; |
| } |
| } |
| |
| public static class RelatedInfo extends JavaScriptObject { |
| public final native JsArray<ChangeAndCommit> changes() /*-{ return this.changes }-*/; |
| protected RelatedInfo() { |
| } |
| } |
| |
| public static class ChangeAndCommit extends JavaScriptObject { |
| static ChangeAndCommit create() { |
| return (ChangeAndCommit) createObject(); |
| } |
| |
| public final native String id() /*-{ return this.change_id }-*/; |
| public final native CommitInfo commit() /*-{ return this.commit }-*/; |
| final native String branch() /*-{ return this.branch }-*/; |
| |
| final native void set_id(String i) |
| /*-{ if(i)this.change_id=i; }-*/; |
| |
| final native void set_commit(CommitInfo c) |
| /*-{ if(c)this.commit=c; }-*/; |
| |
| final native void set_branch(String b) |
| /*-{ if(b)this.branch=b; }-*/; |
| |
| public final Change.Id legacy_id() { |
| return has_change_number() ? new Change.Id(_change_number()) : null; |
| } |
| |
| public final PatchSet.Id patch_set_id() { |
| return has_change_number() && has_revision_number() |
| ? new PatchSet.Id(legacy_id(), _revision_number()) |
| : null; |
| } |
| |
| public final native boolean has_change_number() |
| /*-{ return this.hasOwnProperty('_change_number') }-*/; |
| |
| final native boolean has_revision_number() |
| /*-{ return this.hasOwnProperty('_revision_number') }-*/; |
| |
| final native boolean has_current_revision_number() |
| /*-{ return this.hasOwnProperty('_current_revision_number') }-*/; |
| |
| final native int _change_number() |
| /*-{ return this._change_number }-*/; |
| |
| final native int _revision_number() |
| /*-{ return this._revision_number }-*/; |
| |
| final native int _current_revision_number() |
| /*-{ return this._current_revision_number }-*/; |
| |
| final native void set_change_number(int n) |
| /*-{ this._change_number=n; }-*/; |
| |
| final native void set_revision_number(int n) |
| /*-{ this._revision_number=n; }-*/; |
| |
| final native void set_current_revision_number(int n) |
| /*-{ this._current_revision_number=n; }-*/; |
| |
| protected ChangeAndCommit() { |
| } |
| } |
| } |