blob: f8a7cc7f26d40c3e438960b825cb7f7aad1b664a [file] [log] [blame]
// 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 com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GitwebLink;
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.ui.NavigationTable;
import com.google.gerrit.common.PageLinks;
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.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.impl.HyperlinkImpl;
import com.google.gwtexpui.progress.client.ProgressBar;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
class RelatedChanges extends Composite {
interface Binder extends UiBinder<HTMLPanel, RelatedChanges> {}
private static final Binder uiBinder = GWT.create(Binder.class);
private static final String OPEN;
private static final HyperlinkImpl link = GWT.create(HyperlinkImpl.class);
static {
OPEN = DOM.createUniqueId().replace('-', '_');
init(OPEN);
}
private static final native void init(String o) /*-{
$wnd[o] = $entry(function(e,i) {
return @com.google.gerrit.client.change.RelatedChanges::onOpen(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i);
});
}-*/;
private static boolean onOpen(NativeEvent e, int idx) {
if (link.handleAsClick(e.<Event> cast())) {
MyTable t = getMyTable(e);
if (t != null) {
t.onOpenRow(idx);
e.preventDefault();
return false;
}
}
return true;
}
private static MyTable getMyTable(NativeEvent event) {
com.google.gwt.user.client.Element e = event.getEventTarget().cast();
for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
EventListener l = DOM.getEventListener(e);
if (l instanceof MyTable) {
return (MyTable) l;
}
}
return null;
}
interface Style extends CssResource {
String subject();
}
private String project;
private MyTable table;
private boolean register;
@UiField Style style;
@UiField Element header;
@UiField Element none;
@UiField ScrollPanel scroll;
@UiField ProgressBar progress;
@UiField Element error;
RelatedChanges() {
initWidget(uiBinder.createAndBindUi(this));
}
void set(ChangeInfo info, final String revision) {
if (info.status().isClosed()) {
setVisible(false);
return;
}
project = info.project();
ChangeApi.revision(info.legacy_id().get(), revision)
.view("related")
.get(new AsyncCallback<RelatedInfo>() {
@Override
public void onSuccess(RelatedInfo result) {
render(revision, result.changes());
}
@Override
public void onFailure(Throwable err) {
progress.setVisible(false);
scroll.setVisible(false);
UIObject.setVisible(error, true);
error.setInnerText(err.getMessage());
}
});
}
void setMaxHeight(int height) {
int h = height - header.getOffsetHeight();
scroll.setHeight(h + "px");
}
void registerKeys() {
register = true;
if (table != null) {
table.setRegisterKeys(true);
}
}
private void render(String revision, JsArray<ChangeAndCommit> list) {
if (0 < list.length()) {
DisplayCommand cmd = new DisplayCommand(revision, list);
if (cmd.execute()) {
Scheduler.get().scheduleIncremental(cmd);
}
} else {
progress.setVisible(false);
UIObject.setVisible(header, false);
UIObject.setVisible(none, true);
}
}
private void setTable(MyTable t) {
progress.setVisible(false);
scroll.clear();
scroll.add(t);
scroll.setVisible(true);
table = t;
if (register) {
table.setRegisterKeys(true);
}
}
private String url(ChangeAndCommit c) {
if (c.has_change_number() && c.has_revision_number()) {
PatchSet.Id id = c.patch_set_id();
return "#" + PageLinks.toChange(
id.getParentKey(),
String.valueOf(id.get()));
}
GitwebLink gw = Gerrit.getGitwebLink();
if (gw != null) {
return gw.toRevision(project, c.commit().commit());
}
return null;
}
private class MyTable extends NavigationTable<ChangeAndCommit> {
private final JsArray<ChangeAndCommit> list;
MyTable(JsArray<ChangeAndCommit> list) {
this.list = list;
table.setWidth("");
keysNavigation.setName(Gerrit.C.sectionNavigation());
keysNavigation.add(new PrevKeyCommand(0, 'K',
Resources.C.previousChange()));
keysNavigation.add(new NextKeyCommand(0, 'J', Resources.C.nextChange()));
keysNavigation.add(new OpenKeyCommand(0, 'O', Resources.C.openChange()));
}
@Override
protected Object getRowItemKey(ChangeAndCommit item) {
return item.id();
}
@Override
protected ChangeAndCommit getRowItem(int row) {
if (0 <= row && row <= list.length()) {
return list.get(row);
}
return null;
}
@Override
protected void onOpenRow(int row) {
if (0 <= row && row <= list.length()) {
ChangeAndCommit c = list.get(row);
String url = url(c);
if (url != null && url.startsWith("#")) {
Gerrit.display(url.substring(1));
} else if (url != null) {
Window.Location.assign(url);
}
}
}
void selectRow(int select) {
movePointerTo(select, true);
}
}
private final class DisplayCommand implements RepeatingCommand {
private final SafeHtmlBuilder sb = new SafeHtmlBuilder();
private final MyTable table;
private final String revision;
private final JsArray<ChangeAndCommit> list;
private boolean attached;
private int row;
private int select;
private double start;
private DisplayCommand(String revision, JsArray<ChangeAndCommit> list) {
this.table = new MyTable(list);
this.revision = revision;
this.list = list;
}
public boolean execute() {
boolean attachedNow = isAttached();
if (!attached && attachedNow) {
// Remember that we have been attached at least once. If
// later we find we aren't attached we should stop running.
attached = true;
} else if (attached && !attachedNow) {
// If the user navigated away, we aren't in the DOM anymore.
// Don't continue to render.
return false;
}
start = System.currentTimeMillis();
while (row < list.length()) {
ChangeAndCommit info = list.get(row);
if (revision.equals(info.commit().commit())) {
select = row;
}
render(sb, row, info);
if ((++row % 10) == 0 && longRunning()) {
updateMeter();
return true;
}
}
table.resetHtml(sb);
setTable(table);
table.selectRow(select);
return false;
}
private void render(SafeHtmlBuilder sb, int row, ChangeAndCommit info) {
sb.openTr();
sb.openTd().setStyleName(FileTable.R.css().pointer()).closeTd();
sb.openTd().addStyleName(style.subject());
String url = url(info);
if (url != null) {
sb.openAnchor().setAttribute("href", url);
if (url.startsWith("#")) {
sb.setAttribute("onclick", OPEN + "(event," + row + ")");
}
sb.append(info.commit().subject());
sb.closeAnchor();
} else {
sb.append(info.commit().subject());
}
sb.closeTd();
sb.closeTr();
}
private void updateMeter() {
progress.setValue((100 * row) / list.length());
}
private boolean longRunning() {
return System.currentTimeMillis() - start > 200;
}
}
private static class RelatedInfo extends JavaScriptObject {
final native JsArray<ChangeAndCommit> changes() /*-{ return this.changes }-*/;
protected RelatedInfo() {
}
}
private static class ChangeAndCommit extends JavaScriptObject {
final native String id() /*-{ return this.change_id }-*/;
final native CommitInfo commit() /*-{ return this.commit }-*/;
final Change.Id legacy_id() {
return has_change_number() ? new Change.Id(_change_number()) : null;
}
final PatchSet.Id patch_set_id() {
return has_change_number() && has_revision_number()
? new PatchSet.Id(legacy_id(), _revision_number())
: null;
}
private final native boolean has_change_number()
/*-{ return this.hasOwnProperty('_change_number') }-*/;
private final native boolean has_revision_number()
/*-{ return this.hasOwnProperty('_revision_number') }-*/;
private final native int _change_number()
/*-{ return this._change_number }-*/;
private final native int _revision_number()
/*-{ return this._revision_number }-*/;
protected ChangeAndCommit() {
}
}
}