// Copyright (C) 2008 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.patches; import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.changes.CommentApi; import com.google.gerrit.client.changes.CommentInfo; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.ui.CommentLinkProcessor; import com.google.gerrit.client.ui.CommentPanel; import com.google.gerrit.common.changes.Side; import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.reviewdb.client.PatchLineComment; import com.google.gerrit.reviewdb.client.PatchSet; 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.DoubleClickEvent; import com.google.gwt.event.dom.client.DoubleClickHandler; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Widget; import com.google.gwtexpui.globalkey.client.NpTextArea; import com.google.gwtjsonrpc.common.AsyncCallback; import com.google.gwtjsonrpc.common.VoidResult; import java.sql.Timestamp; public class CommentEditorPanel extends CommentPanel implements ClickHandler, DoubleClickHandler { private static final int INITIAL_COLS = 60; private static final int INITIAL_LINES = 5; private static final int MAX_LINES = 30; private static final AsyncCallback<VoidResult> NULL_CALLBACK = new AsyncCallback<VoidResult>() { @Override public void onFailure(Throwable caught) { } @Override public void onSuccess(VoidResult result) { } }; private PatchLineComment comment; private final NpTextArea text; private final Button edit; private final Button save; private final Button cancel; private final Button discard; private final Timer expandTimer; public CommentEditorPanel(final PatchLineComment plc, final CommentLinkProcessor commentLinkProcessor) { super(commentLinkProcessor); comment = plc; addStyleName(Gerrit.RESOURCES.css().commentEditorPanel()); setAuthorNameText(Gerrit.getUserAccountInfo(), PatchUtil.C.draft()); setMessageText(plc.getMessage()); addDoubleClickHandler(this); expandTimer = new Timer() { @Override public void run() { expandText(); } }; text = new NpTextArea(); text.setText(comment.getMessage()); text.setCharacterWidth(INITIAL_COLS); text.setVisibleLines(INITIAL_LINES); text.setSpellCheck(true); text.addKeyDownHandler(new KeyDownHandler() { @Override public void onKeyDown(final KeyDownEvent event) { if ((event.isControlKeyDown() || event.isMetaKeyDown()) && !event.isAltKeyDown() && !event.isShiftKeyDown()) { switch (event.getNativeKeyCode()) { case 's': case 'S': event.preventDefault(); onSave(NULL_CALLBACK); return; } } expandTimer.schedule(250); } }); addContent(text); edit = new Button(); edit.setText(PatchUtil.C.buttonEdit()); edit.addClickHandler(this); addButton(edit); save = new Button(); save.setText(PatchUtil.C.buttonSave()); save.addClickHandler(this); addButton(save); cancel = new Button(); cancel.setText(PatchUtil.C.buttonCancel()); cancel.addClickHandler(this); addButton(cancel); discard = new Button(); discard.setText(PatchUtil.C.buttonDiscard()); discard.addClickHandler(this); addButton(discard); setOpen(true); if (isNew()) { edit(); } else { render(); } } private void expandText() { final double cols = text.getCharacterWidth(); int rows = 2; for (final String line : text.getText().split("\n")) { rows += Math.ceil((1.0 + line.length()) / cols); } rows = Math.max(INITIAL_LINES, Math.min(rows, MAX_LINES)); if (text.getVisibleLines() != rows) { text.setVisibleLines(rows); } } private void edit() { if (!isOpen()) { setOpen(true); } text.setText(comment.getMessage()); expandText(); stateEdit(true); text.setFocus(true); } private void render() { final Timestamp on = comment.getWrittenOn(); setDateText(PatchUtil.M.draftSaved(new java.util.Date(on.getTime()))); setMessageText(comment.getMessage()); stateEdit(false); } private void stateEdit(final boolean inEdit) { expandTimer.cancel(); setMessageTextVisible(!inEdit); edit.setVisible(!inEdit); if (inEdit) { text.setVisible(true); } else { text.setFocus(false); text.setVisible(false); } save.setVisible(inEdit); cancel.setVisible(inEdit && !isNew()); discard.setVisible(inEdit); } void setFocus(final boolean take) { if (take && !isOpen()) { setOpen(true); } if (text.isVisible()) { text.setFocus(take); } else if (take) { edit(); } } boolean isNew() { return comment.getKey().get() == null; } public PatchLineComment getComment() { return comment; } @Override public void onDoubleClick(final DoubleClickEvent event) { edit(); } @Override public void onClick(final ClickEvent event) { final Widget sender = (Widget) event.getSource(); if (sender == edit) { edit(); } else if (sender == save) { onSave(NULL_CALLBACK); } else if (sender == cancel) { render(); } else if (sender == discard) { onDiscard(); } } public void saveDraft(AsyncCallback<VoidResult> onSave) { if (isOpen() && text.isVisible()) { onSave(onSave); } else { onSave.onSuccess(VoidResult.INSTANCE); } } private void onSave(final AsyncCallback<VoidResult> onSave) { expandTimer.cancel(); final String txt = text.getText().trim(); if ("".equals(txt)) { return; } comment.setMessage(txt); text.setFocus(false); text.setReadOnly(true); save.setEnabled(false); cancel.setEnabled(false); discard.setEnabled(false); final PatchSet.Id psId = comment.getKey().getParentKey().getParentKey(); final boolean wasNew = isNew(); GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() { public void onSuccess(CommentInfo result) { notifyDraftDelta(wasNew ? 1 : 0); comment = toComment(psId, comment.getKey().getParentKey().get(), result); text.setReadOnly(false); save.setEnabled(true); cancel.setEnabled(true); discard.setEnabled(true); render(); onSave.onSuccess(VoidResult.INSTANCE); } @Override public void onFailure(final Throwable caught) { text.setReadOnly(false); text.setFocus(true); save.setEnabled(true); cancel.setEnabled(true); discard.setEnabled(true); super.onFailure(caught); onSave.onFailure(caught); } }; CommentInfo input = toInput(comment); if (wasNew) { CommentApi.createDraft(psId, input, cb); } else { CommentApi.updateDraft(psId, input.id(), input, cb); } } private void notifyDraftDelta(final int delta) { CommentEditorContainer c = getContainer(); if (c != null) { c.notifyDraftDelta(delta); } } private void onDiscard() { expandTimer.cancel(); if (isNew()) { text.setFocus(false); removeUI(); return; } text.setFocus(false); text.setReadOnly(true); save.setEnabled(false); cancel.setEnabled(false); discard.setEnabled(false); CommentApi.deleteDraft( comment.getKey().getParentKey().getParentKey(), comment.getKey().get(), new GerritCallback<JavaScriptObject>() { public void onSuccess(JavaScriptObject result) { notifyDraftDelta(-1); removeUI(); } @Override public void onFailure(final Throwable caught) { text.setReadOnly(false); text.setFocus(true); save.setEnabled(true); cancel.setEnabled(true); discard.setEnabled(true); super.onFailure(caught); } }); } private void removeUI() { CommentEditorContainer c = getContainer(); if (c != null) { c.remove(this); } } private CommentEditorContainer getContainer() { Widget p = getParent(); while (p != null) { if (p instanceof CommentEditorContainer) { return (CommentEditorContainer) p; } p = p.getParent(); } return null; } public static CommentInfo toInput(PatchLineComment c) { CommentInfo i = CommentInfo.createObject().cast(); i.id(c.getKey().get()); i.path(c.getKey().getParentKey().get()); i.side(c.getSide() == 0 ? Side.PARENT : Side.REVISION); if (c.getLine() > 0) { i.line(c.getLine()); } i.in_reply_to(c.getParentUuid()); i.message(c.getMessage()); return i; } public static PatchLineComment toComment(PatchSet.Id ps, String path, CommentInfo i) { PatchLineComment p = new PatchLineComment( new PatchLineComment.Key( new Patch.Key(ps, path), i.id()), i.line(), Gerrit.getUserAccount().getId(), i.in_reply_to(), i.updated()); p.setMessage(i.message()); p.setSide((short) (i.side() == Side.PARENT ? 0 : 1)); return p; } }