Add functionalities to CommentBox and fix various styles

- Added expandText() to DraftBox.
- Fixed CommentBox styles to make it look better. Added box-shadow.
- Adjusted CodeMirror's line height to avoid cutting off the bottom
  of text.
- Added refocusing on CM after clicking on CommentBoxes and SkipBars.
- Prevented feedback loop in synced scrolling.
- Fixed a bug in skip calculation.

Change-Id: I7cbe644f3a890cb6e62a5d83942a93411ea5550f
This commit is contained in:
Michael Zhou
2013-07-13 01:21:55 -07:00
parent 95b233df2d
commit 0485172aaa
15 changed files with 221 additions and 94 deletions

View File

@@ -226,10 +226,12 @@ public class CodeMirrorDemo extends Screen {
private void registerCmEvents(CodeMirror cm) { private void registerCmEvents(CodeMirror cm) {
cm.on("cursorActivity", updateActiveLine(cm)); cm.on("cursorActivity", updateActiveLine(cm));
cm.on("scroll", doScroll(otherCM(cm))); cm.on("scroll", doScroll(cm));
/** /**
* TODO: Trying to prevent right click from updating the cursor. * Trying to prevent right click from updating the cursor.
* Doesn't seem to work for now. *
* TODO: Change to listen on "contextmenu" instead. Latest CM has
* provided a patch that will hopefully make this work.
*/ */
cm.on("mousedown", ignoreRightClick()); cm.on("mousedown", ignoreRightClick());
cm.addKeyMap(KeyMap.create().on("'u'", upToChange())); cm.addKeyMap(KeyMap.create().on("'u'", upToChange()));
@@ -238,7 +240,10 @@ public class CodeMirrorDemo extends Screen {
if (Gerrit.isSignedIn()) { if (Gerrit.isSignedIn()) {
cm.addKeyMap(KeyMap.create().on("'c'", insertNewDraft(cm))); cm.addKeyMap(KeyMap.create().on("'c'", insertNewDraft(cm)));
} }
// TODO: Examine if a better way exists. /**
* TODO: Maybe remove this after updating CM to HEAD. The latest VIM mode
* doesn't enter INSERT mode when document is read only.
*/
for (String c : new String[]{"A", "C", "D", "I", "O", "P", "R", "S", "U", for (String c : new String[]{"A", "C", "D", "I", "O", "P", "R", "S", "U",
"X", "Y", "~"}) { "X", "Y", "~"}) {
CodeMirror.disableUnwantedKey("vim", c); CodeMirror.disableUnwantedKey("vim", c);
@@ -328,6 +333,7 @@ public class CodeMirrorDemo extends Screen {
.set("lineNumbers", true) .set("lineNumbers", true)
.set("tabSize", 2) .set("tabSize", 2)
.set("mode", getContentType(meta)) .set("mode", getContentType(meta))
.set("lineWrapping", true)
.set("styleSelectedText", true) .set("styleSelectedText", true)
.set("showTrailingSpace", true) .set("showTrailingSpace", true)
.set("keyMap", "vim") .set("keyMap", "vim")
@@ -422,20 +428,20 @@ public class CodeMirrorDemo extends Screen {
return box; return box;
} }
CommentBox addCommentBox(CommentInfo info, final CommentBox box) { CommentBox addCommentBox(CommentInfo info, CommentBox box) {
diffTable.add(box); diffTable.add(box);
Side mySide = info.side(); Side mySide = info.side();
CodeMirror cm = mySide == Side.PARENT ? cmA : cmB; CodeMirror cm = mySide == Side.PARENT ? cmA : cmB;
CodeMirror other = otherCM(cm); CodeMirror other = otherCm(cm);
int line = info.line() - 1; // CommentInfo is 1-based, but CM is 0-based int line = info.line() - 1; // CommentInfo is 1-based, but CM is 0-based
LineHandle handle = cm.getLineHandle(line); LineHandle handle = cm.getLineHandle(line);
PaddingManager manager; PaddingManager manager;
if (linePaddingManagerMap.containsKey(handle)) { if (linePaddingManagerMap.containsKey(handle)) {
manager = linePaddingManagerMap.get(handle); manager = linePaddingManagerMap.get(handle);
} else { } else {
// Estimated height at 21px, fixed by deferring after display // Estimated height at 28px, fixed by deferring after display
manager = new PaddingManager( manager = new PaddingManager(
addPaddingWidget(cm, DiffTable.style.padding(), line, 21, Unit.PX, 0)); addPaddingWidget(cm, DiffTable.style.padding(), line, 28, Unit.PX, 0));
linePaddingManagerMap.put(handle, manager); linePaddingManagerMap.put(handle, manager);
} }
int lineToPad = mapper.lineOnOther(mySide, line).getLine(); int lineToPad = mapper.lineOnOther(mySide, line).getLine();
@@ -444,7 +450,7 @@ public class CodeMirrorDemo extends Screen {
PaddingManager.link(manager, linePaddingManagerMap.get(otherHandle)); PaddingManager.link(manager, linePaddingManagerMap.get(otherHandle));
} else { } else {
PaddingManager otherManager = new PaddingManager( PaddingManager otherManager = new PaddingManager(
addPaddingWidget(other, DiffTable.style.padding(), lineToPad, 21, Unit.PX, 0)); addPaddingWidget(other, DiffTable.style.padding(), lineToPad, 28, Unit.PX, 0));
linePaddingManagerMap.put(otherHandle, otherManager); linePaddingManagerMap.put(otherHandle, otherManager);
PaddingManager.link(manager, otherManager); PaddingManager.link(manager, otherManager);
} }
@@ -484,13 +490,14 @@ public class CodeMirrorDemo extends Screen {
private void renderPublished() { private void renderPublished() {
List<CommentInfo> sorted = sortComment(published); List<CommentInfo> sorted = sortComment(published);
for (CommentInfo info : sorted) { for (CommentInfo info : sorted) {
final PublishedBox box = CodeMirror cm = getCmFromSide(info.side());
new PublishedBox(this, revision, info, commentLinkProcessor); PublishedBox box =
new PublishedBox(this, cm, revision, info, commentLinkProcessor);
box.setOpen(false); box.setOpen(false);
initialBoxes.add(box); initialBoxes.add(box);
publishedMap.put(info.id(), box); publishedMap.put(info.id(), box);
int line = info.line() - 1; int line = info.line() - 1;
LineHandle handle = getCmFromSide(info.side()).getLineHandle(line); LineHandle handle = cm.getLineHandle(line);
lineLastPublishedBoxMap.put(handle, box); lineLastPublishedBoxMap.put(handle, box);
lineActiveBoxMap.put(handle, box); lineActiveBoxMap.put(handle, box);
addCommentBox(info, box); addCommentBox(info, box);
@@ -500,7 +507,7 @@ public class CodeMirrorDemo extends Screen {
private void renderDrafts() { private void renderDrafts() {
List<CommentInfo> sorted = sortComment(drafts); List<CommentInfo> sorted = sortComment(drafts);
for (CommentInfo info : sorted) { for (CommentInfo info : sorted) {
final DraftBox box = DraftBox box =
new DraftBox(this, getCmFromSide(info.side()), revision, info, new DraftBox(this, getCmFromSide(info.side()), revision, info,
commentLinkProcessor, false, false); commentLinkProcessor, false, false);
box.setOpen(false); box.setOpen(false);
@@ -529,7 +536,7 @@ public class CodeMirrorDemo extends Screen {
int boxLine = info.line(); int boxLine = info.line();
int deltaBefore = boxLine - startLine; int deltaBefore = boxLine - startLine;
int deltaAfter = startLine + skip.getSize() - boxLine; int deltaAfter = startLine + skip.getSize() - boxLine;
if (deltaBefore < 0 || deltaAfter < 0) { if (deltaBefore < -context || deltaAfter < -context) {
temp.add(skip); temp.add(skip);
} else if (deltaBefore > context && deltaAfter > context) { } else if (deltaBefore > context && deltaAfter > context) {
SkippedLine before = new SkippedLine( SkippedLine before = new SkippedLine(
@@ -584,7 +591,7 @@ public class CodeMirrorDemo extends Screen {
return bar; return bar;
} }
private CodeMirror otherCM(CodeMirror me) { private CodeMirror otherCm(CodeMirror me) {
return me == cmA ? cmB : cmA; return me == cmA ? cmB : cmA;
} }
@@ -633,11 +640,11 @@ public class CodeMirrorDemo extends Screen {
private void insertEmptyLines(CodeMirror cm, int nextLine, int cnt) { private void insertEmptyLines(CodeMirror cm, int nextLine, int cnt) {
// -1 to compensate for the line we went past when this method is called. // -1 to compensate for the line we went past when this method is called.
addPaddingWidget(cm, DiffTable.style.padding(), nextLine - 1, addPaddingWidget(cm, DiffTable.style.padding(), nextLine - 1,
cnt, Unit.EM, null); 1.1 * cnt, Unit.EM, null);
} }
private LineWidgetElementPair addPaddingWidget(CodeMirror cm, String style, private LineWidgetElementPair addPaddingWidget(CodeMirror cm, String style,
int line, int height, Unit unit, Integer index) { int line, double height, Unit unit, Integer index) {
Element div = DOM.createDiv(); Element div = DOM.createDiv();
div.setClassName(style); div.setClassName(style);
div.getStyle().setHeight(height, unit); div.getStyle().setHeight(height, unit);
@@ -652,16 +659,23 @@ public class CodeMirrorDemo extends Screen {
} }
private Runnable doScroll(final CodeMirror cm) { private Runnable doScroll(final CodeMirror cm) {
final CodeMirror other = otherCM(cm); final CodeMirror other = otherCm(cm);
return new Runnable() { return new Runnable() {
public void run() { public void run() {
cm.scrollToY(other.getScrollInfo().getTop()); // Prevent feedback loop, Chrome seems fine but Firefox chokes.
double now = (double) System.currentTimeMillis();
if (cm.getScrollSetBy() == other && cm.getScrollSetAt() + 50 > now) {
return;
}
other.scrollToY(cm.getScrollInfo().getTop());
other.setScrollSetBy(cm);
other.setScrollSetAt(now);
} }
}; };
} }
private Runnable updateActiveLine(final CodeMirror cm) { private Runnable updateActiveLine(final CodeMirror cm) {
final CodeMirror other = otherCM(cm); final CodeMirror other = otherCm(cm);
return new Runnable() { return new Runnable() {
public void run() { public void run() {
if (cm.hasActiveLine()) { if (cm.hasActiveLine()) {
@@ -793,7 +807,7 @@ public class CodeMirrorDemo extends Screen {
} }
@Override @Override
public void onKeyPress(final KeyPressEvent event) { public void onKeyPress(KeyPressEvent event) {
} }
} }
} }

View File

@@ -20,6 +20,8 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiField;
@@ -30,6 +32,7 @@ import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.safehtml.client.SafeHtml; import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder; import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.LineWidget; import net.codemirror.lib.LineWidget;
import java.sql.Timestamp; import java.sql.Timestamp;
@@ -48,6 +51,9 @@ abstract class CommentBox extends Composite {
private CodeMirrorDemo diffView; private CodeMirrorDemo diffView;
private boolean draft; private boolean draft;
private LineWidget selfWidget; private LineWidget selfWidget;
private CodeMirror cm;
private HandlerRegistration regClick;
private ClickHandler clickFocusHandler;
@UiField(provided=true) @UiField(provided=true)
CommentBoxHeader header; CommentBoxHeader header;
@@ -60,24 +66,27 @@ abstract class CommentBox extends Composite {
CommentBox( CommentBox(
CodeMirrorDemo host, CodeMirrorDemo host,
CodeMirror cmInstance,
UiBinder<? extends Widget, CommentBox> binder, UiBinder<? extends Widget, CommentBox> binder,
PatchSet.Id id, CommentInfo info, CommentLinkProcessor linkProcessor, PatchSet.Id id, CommentInfo info, CommentLinkProcessor linkProcessor,
boolean isDraft) { boolean isDraft) {
diffView = host; diffView = host;
cm = cmInstance;
commentLinkProcessor = linkProcessor; commentLinkProcessor = linkProcessor;
original = info; original = info;
patchSetId = id; patchSetId = id;
draft = isDraft; draft = isDraft;
header = new CommentBoxHeader(info.author(), info.updated(), isDraft); header = new CommentBoxHeader(info.author(), info.updated(), isDraft);
initWidget(binder.createAndBindUi(this)); initWidget(binder.createAndBindUi(this));
setMessageText(info.message()); clickFocusHandler = new ClickHandler() {
}
@Override @Override
protected void onLoad() { public void onClick(ClickEvent event) {
super.onLoad(); cm.focus();
}
};
enableClickFocusHandler();
res.style().ensureInjected(); res.style().ensureInjected();
setMessageText(info.message());
} }
void resizePaddingWidget() { void resizePaddingWidget() {
@@ -160,8 +169,26 @@ abstract class CommentBox extends Composite {
return selfWidget; return selfWidget;
} }
CodeMirror getCm() {
return cm;
}
@UiHandler("header") @UiHandler("header")
void onHeaderClick(ClickEvent e) { void onHeaderClick(ClickEvent e) {
setOpen(!isOpen()); setOpen(!isOpen());
cm.focus();
}
void enableClickFocusHandler() {
if (regClick == null) {
regClick = addDomHandler(clickFocusHandler, ClickEvent.getType());
}
}
void disableClickFocusHandler() {
if (regClick != null) {
regClick.removeHandler();
regClick = null;
}
} }
} }

View File

@@ -55,17 +55,23 @@ class CommentBoxHeader extends Composite implements HasClickHandlers {
Element date; Element date;
CommentBoxHeader(AccountInfo author, Timestamp when, boolean isDraft) { CommentBoxHeader(AccountInfo author, Timestamp when, boolean isDraft) {
// TODO: Set avatar's display to none if we get a 404. if (author != null) {
avatar = author == null ? new AvatarImage() : new AvatarImage(author, 26); avatar = new AvatarImage(author, 26);
avatar.setSize("", "");
} else {
avatar = new AvatarImage();
}
initWidget(uiBinder.createAndBindUi(this)); initWidget(uiBinder.createAndBindUi(this));
this.draft = isDraft; draft = isDraft;
if (when != null) { if (when != null) {
setDate(when); setDate(when);
} }
if (isDraft) { if (isDraft) {
name.setInnerText("(Draft)"); name.setInnerText(PatchUtil.C.draft());
} else { } else {
name.setInnerText(FormatUtil.name(author)); name.setInnerText(FormatUtil.name(author));
name.setTitle(FormatUtil.nameEmail(author));
date.setTitle(FormatUtil.mediumFormat(when));
} }
} }

View File

@@ -22,7 +22,7 @@ limitations under the License.
<table class='{res.style.table}'> <table class='{res.style.table}'>
<tr> <tr>
<td><c:AvatarImage ui:field='avatar' /></td> <td><c:AvatarImage ui:field='avatar' /></td>
<td ui:field='name'></td> <td ui:field='name' class='{res.style.name}'></td>
<td class='{res.style.summary}'> <td class='{res.style.summary}'>
<div ui:field='summary' class='{res.style.summaryText}'></div> <div ui:field='summary' class='{res.style.summaryText}'></div>
</td> </td>

View File

@@ -29,10 +29,12 @@ interface CommentBoxResources extends ClientBundle {
String close(); String close();
String commentBox(); String commentBox();
String table(); String table();
String name();
String summary(); String summary();
String summaryText(); String summaryText();
String date(); String date();
String contentPanel(); String contentPanel();
String message(); String message();
String button();
} }
} }

View File

@@ -1,6 +1,14 @@
.commentBox { .commentBox {
background-color: #e5ecf9; background-color: #e5ecf9;
border: 1px solid black; border: 1px solid black;
-webkit-box-shadow: 3px 3px 3px #888888;
-moz-box-shadow: 3px 3px 3px #888888;
box-shadow: 3px 3px 3px #888888;
margin-bottom: 5px;
margin-right: 5px;
overflow: visible;
word-wrap: break-word;
overflow-wrap: break-word;
} }
.table { .table {
@@ -9,6 +17,11 @@
table-layout: fixed; table-layout: fixed;
} }
.name {
width: 20%;
font-weight: bold;
}
.summary { .summary {
width: 60%; width: 60%;
} }
@@ -18,7 +31,7 @@
height: 1em; height: 1em;
max-width: 80%; max-width: 80%;
overflow: hidden; overflow: hidden;
padding-bottom: 1px; padding-bottom: 2px;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
@@ -37,5 +50,11 @@
} }
.message { .message {
margin: 5px; margin-left: 5px;
margin-right: 5px;
}
.button {
margin-left: 5px;
margin-bottom: 5px;
} }

View File

@@ -24,6 +24,7 @@ limitations under the License.
@external .cm-searching, .cm-trailingspace; @external .cm-searching, .cm-trailingspace;
.difftable .CodeMirror pre { .difftable .CodeMirror pre {
padding: 0; padding: 0;
padding-bottom: 0.1em;
overflow: hidden; overflow: hidden;
} }
.table { .table {
@@ -60,7 +61,7 @@ limitations under the License.
} }
.activeLine .CodeMirror-linenumber, .activeLine .CodeMirror-linenumber,
.activeLine .diff, .activeLine .intralineBg { .activeLine .diff, .activeLine .intralineBg {
background-color: #E0FFFF; background-color: #E0FFFF !important;
} }
.activeLineBg { .activeLineBg {
background-color: #E0FFFF !important; background-color: #E0FFFF !important;
@@ -74,11 +75,14 @@ limitations under the License.
.CodeMirror-selectedtext { .CodeMirror-selectedtext {
background-color: inherit !important; background-color: inherit !important;
} }
.difftable .CodeMirror-linenumber {
height: 1.1em;
}
.hideNumber .CodeMirror-linenumber { .hideNumber .CodeMirror-linenumber {
color: transparent; color: transparent;
background-color: #def; background-color: #def;
width: 23px !important; width: 23px !important;
height: 16px; height: 1.3em;
} }
.difftable .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { .difftable .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
opacity: 0.8; opacity: 0.8;

View File

@@ -24,16 +24,15 @@ import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.DoubleClickEvent; import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler; import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwtexpui.globalkey.client.NpTextArea; import com.google.gwtexpui.globalkey.client.NpTextArea;
@@ -70,10 +69,13 @@ class DraftBox extends CommentBox {
@UiField @UiField
Button discard; Button discard;
private HandlerRegistration messageClick; private static final int INITIAL_COLS = 60;
private static final int INITIAL_LINES = 5;
private static final int MAX_LINES = 30;
private boolean isNew; private boolean isNew;
private PublishedBox replyToBox; private PublishedBox replyToBox;
private CodeMirror cm; private Timer expandTimer;
DraftBox( DraftBox(
CodeMirrorDemo host, CodeMirrorDemo host,
@@ -81,46 +83,46 @@ class DraftBox extends CommentBox {
PatchSet.Id id, PatchSet.Id id,
CommentInfo info, CommentInfo info,
CommentLinkProcessor linkProcessor, CommentLinkProcessor linkProcessor,
boolean isNew, boolean isNewDraft,
boolean saveOnInit) { boolean saveOnInit) {
super(host, uiBinder, id, info, linkProcessor, true); super(host, cm, uiBinder, id, info, linkProcessor, true);
this.cm = cm; isNew = isNewDraft;
this.isNew = isNew; editArea.setText(info.message());
editArea.setText(contentPanelMessage.getText()); editArea.setCharacterWidth(INITIAL_COLS);
editArea.setVisibleLines(INITIAL_LINES);
editArea.setSpellCheck(true);
if (saveOnInit) { if (saveOnInit) {
onSave(null); onSave(null);
} }
if (isNew) { if (isNew) {
addStyleName(draftStyle.newDraft()); addStyleName(draftStyle.newDraft());
} }
} expandTimer = new Timer() {
@Override @Override
protected void onLoad() { public void run() {
super.onLoad(); expandText();
messageClick = contentPanelMessage.addDoubleClickHandler(
new DoubleClickHandler() {
@Override
public void onDoubleClick(DoubleClickEvent arg0) {
setEdit(true);
} }
}); };
addDomHandler(new MouseMoveHandler() { addDomHandler(new MouseMoveHandler() {
@Override @Override
public void onMouseMove(MouseMoveEvent arg0) { public void onMouseMove(MouseMoveEvent event) {
resizePaddingWidget(); resizePaddingWidget();
} }
}, MouseMoveEvent.getType()); }, MouseMoveEvent.getType());
} }
@Override private void expandText() {
protected void onUnload() { double cols = editArea.getCharacterWidth();
super.onUnload(); int rows = 2;
for (String line : editArea.getText().split("\n")) {
messageClick.removeHandler(); rows += Math.ceil((1.0 + line.length()) / cols);
messageClick = null; }
rows = Math.max(INITIAL_LINES, Math.min(rows, MAX_LINES));
if (editArea.getVisibleLines() != rows) {
editArea.setVisibleLines(rows);
}
resizePaddingWidget();
} }
void setEdit(boolean edit) { void setEdit(boolean edit) {
@@ -128,11 +130,15 @@ class DraftBox extends CommentBox {
setOpen(true); setOpen(true);
removeStyleName(draftStyle.view()); removeStyleName(draftStyle.view());
addStyleName(draftStyle.edit()); addStyleName(draftStyle.edit());
editArea.setText(contentPanelMessage.getText()); editArea.setText(getOriginal().message());
expandText();
editArea.setFocus(true); editArea.setFocus(true);
disableClickFocusHandler();
} else { } else {
expandTimer.cancel();
removeStyleName(draftStyle.edit()); removeStyleName(draftStyle.edit());
addStyleName(draftStyle.view()); addStyleName(draftStyle.view());
enableClickFocusHandler();
} }
resizePaddingWidget(); resizePaddingWidget();
} }
@@ -142,6 +148,7 @@ class DraftBox extends CommentBox {
} }
private void removeUI() { private void removeUI() {
expandTimer.cancel();
if (replyToBox != null) { if (replyToBox != null) {
replyToBox.unregisterReplyBox(); replyToBox.unregisterReplyBox();
} }
@@ -152,7 +159,12 @@ class DraftBox extends CommentBox {
PaddingManager manager = getPaddingManager(); PaddingManager manager = getPaddingManager();
manager.remove(this); manager.remove(this);
manager.resizePaddingWidget(); manager.resizePaddingWidget();
cm.focus(); getCm().focus();
}
@UiHandler("contentPanelMessage")
void onDoubleClick(DoubleClickEvent e) {
setEdit(true);
} }
@UiHandler("edit") @UiHandler("edit")
@@ -187,13 +199,13 @@ class DraftBox extends CommentBox {
} else { } else {
CommentApi.updateDraft(getPatchSetId(), original.id(), input, cb); CommentApi.updateDraft(getPatchSetId(), original.id(), input, cb);
} }
cm.focus(); getCm().focus();
} }
@UiHandler("cancel") @UiHandler("cancel")
void onCancel(ClickEvent e) { void onCancel(ClickEvent e) {
setEdit(false); setEdit(false);
cm.focus(); getCm().focus();
} }
@UiHandler("discard") @UiHandler("discard")
@@ -213,11 +225,18 @@ class DraftBox extends CommentBox {
@UiHandler("editArea") @UiHandler("editArea")
void onCtrlS(KeyDownEvent e) { void onCtrlS(KeyDownEvent e) {
if (e.isControlKeyDown() && e.getNativeKeyCode() == 83) { if ((e.isControlKeyDown() || e.isMetaKeyDown())
onSave(null); && !e.isAltKeyDown() && !e.isShiftKeyDown()) {
switch (e.getNativeKeyCode()) {
case 's':
case 'S':
e.preventDefault(); e.preventDefault();
onSave(null);
return;
} }
} }
expandTimer.schedule(250);
}
/** TODO: Unused now. Re-enable this after implementing auto-save */ /** TODO: Unused now. Re-enable this after implementing auto-save */
void onEsc(KeyDownEvent e) { void onEsc(KeyDownEvent e) {

View File

@@ -23,6 +23,10 @@ limitations under the License.
.edit .messagePanel { .edit .messagePanel {
display: none; display: none;
} }
textarea.editArea {
margin-left: 5px;
margin-bottom: 2px;
}
.view .editArea { .view .editArea {
display: none; display: none;
} }
@@ -30,27 +34,27 @@ limitations under the License.
display: none; display: none;
} }
</ui:style> </ui:style>
<g:HTMLPanel styleName='{res.style.commentBox}'> <g:HTMLPanel addStyleNames='{res.style.commentBox}'>
<d:CommentBoxHeader ui:field='header' /> <d:CommentBoxHeader ui:field='header' />
<div class='{res.style.contentPanel}'> <div class='{res.style.contentPanel}'>
<c:NpTextArea ui:field='editArea' styleName='{draftStyle.editArea}'/> <c:NpTextArea ui:field='editArea' addStyleNames='{draftStyle.editArea}'/>
<div> <div class='{res.style.button}'>
<g:Button ui:field='save' styleName='{draftStyle.editArea}'> <g:Button ui:field='save' addStyleNames='{draftStyle.editArea}'>
<ui:msg>Save</ui:msg> <ui:msg>Save</ui:msg>
</g:Button> </g:Button>
<g:Button ui:field='cancel' <g:Button ui:field='cancel'
styleName='{draftStyle.editArea} {draftStyle.cancel}'> addStyleNames='{draftStyle.editArea} {draftStyle.cancel}'>
<ui:msg>Cancel</ui:msg> <ui:msg>Cancel</ui:msg>
</g:Button> </g:Button>
<g:Button ui:field='discard' styleName='{draftStyle.editArea}'> <g:Button ui:field='discard' addStyleNames='{draftStyle.editArea}'>
<ui:msg>Discard</ui:msg> <ui:msg>Discard</ui:msg>
</g:Button> </g:Button>
</div> </div>
</div> </div>
<div class='{res.style.contentPanel}'> <div class='{res.style.contentPanel}'>
<g:HTML ui:field='contentPanelMessage' <g:HTML ui:field='contentPanelMessage'
styleName='{res.style.message} {draftStyle.messagePanel}'></g:HTML> addStyleNames='{res.style.message} {draftStyle.messagePanel}'></g:HTML>
<g:Button ui:field='edit' styleName='{draftStyle.messagePanel}'> <g:Button ui:field='edit' addStyleNames='{draftStyle.messagePanel} {res.style.button}'>
<ui:msg>Edit</ui:msg> <ui:msg>Edit</ui:msg>
</g:Button> </g:Button>
</div> </div>

View File

@@ -58,7 +58,7 @@ class PaddingManager {
private int getMyTotalHeight() { private int getMyTotalHeight() {
int total = 0; int total = 0;
for (CommentBox box : comments) { for (CommentBox box : comments) {
total += box.getOffsetHeight(); total += box.getOffsetHeight() + 5; // 5px for shadow margin
} }
return total; return total;
} }

View File

@@ -23,6 +23,8 @@ import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HTMLPanel;
import net.codemirror.lib.CodeMirror;
/** An HtmlPanel for displaying a published comment */ /** An HtmlPanel for displaying a published comment */
class PublishedBox extends CommentBox { class PublishedBox extends CommentBox {
interface Binder extends UiBinder<HTMLPanel, PublishedBox> {} interface Binder extends UiBinder<HTMLPanel, PublishedBox> {}
@@ -31,9 +33,13 @@ class PublishedBox extends CommentBox {
private DraftBox replyBox; private DraftBox replyBox;
PublishedBox(CodeMirrorDemo host, PatchSet.Id id, CommentInfo info, PublishedBox(
CodeMirrorDemo host,
CodeMirror cm,
PatchSet.Id id,
CommentInfo info,
CommentLinkProcessor linkProcessor) { CommentLinkProcessor linkProcessor) {
super(host, uiBinder, id, info, linkProcessor, false); super(host, cm, uiBinder, id, info, linkProcessor, false);
} }
void registerReplyBox(DraftBox box) { void registerReplyBox(DraftBox box) {

View File

@@ -18,11 +18,11 @@ limitations under the License.
xmlns:g='urn:import:com.google.gwt.user.client.ui' xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:d='urn:import:com.google.gerrit.client.diff'> xmlns:d='urn:import:com.google.gerrit.client.diff'>
<ui:with field='res' type='com.google.gerrit.client.diff.CommentBoxResources' /> <ui:with field='res' type='com.google.gerrit.client.diff.CommentBoxResources' />
<g:HTMLPanel styleName='{res.style.commentBox}'> <g:HTMLPanel addStyleNames='{res.style.commentBox}'>
<d:CommentBoxHeader ui:field='header' /> <d:CommentBoxHeader ui:field='header' />
<g:HTMLPanel styleName='{res.style.contentPanel}'> <g:HTMLPanel addStyleNames='{res.style.contentPanel}'>
<g:HTML ui:field='contentPanelMessage' styleName='{res.style.message}'></g:HTML> <g:HTML ui:field='contentPanelMessage' addStyleNames='{res.style.message}'></g:HTML>
<div> <div class='{res.style.button}'>
<g:Button ui:field='reply'><ui:msg>Reply ...</ui:msg></g:Button> <g:Button ui:field='reply'><ui:msg>Reply ...</ui:msg></g:Button>
<g:Button ui:field='replyDone'><ui:msg>Reply 'Done'</ui:msg></g:Button> <g:Button ui:field='replyDone'><ui:msg>Reply 'Done'</ui:msg></g:Button>
</div> </div>

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.client.diff;
import com.google.gerrit.client.patches.PatchUtil; import com.google.gerrit.client.patches.PatchUtil;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiField;
@@ -65,12 +66,18 @@ class SkipBar extends Composite {
private CodeMirror cm; private CodeMirror cm;
private int numSkipLines; private int numSkipLines;
SkipBar(CodeMirror cm) { SkipBar(CodeMirror cmInstance) {
this.cm = cm; cm = cmInstance;
skipNum = new Anchor(true); skipNum = new Anchor(true);
upArrow = new Anchor(true); upArrow = new Anchor(true);
downArrow = new Anchor(true); downArrow = new Anchor(true);
initWidget(uiBinder.createAndBindUi(this)); initWidget(uiBinder.createAndBindUi(this));
addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
cm.focus();
}
}, ClickEvent.getType());
} }
void setWidget(LineWidget widget) { void setWidget(LineWidget widget) {

View File

@@ -19,6 +19,8 @@ limitations under the License.
<ui:style type='com.google.gerrit.client.diff.SkipBar.SkipBarStyle'> <ui:style type='com.google.gerrit.client.diff.SkipBar.SkipBarStyle'>
.skipBar { .skipBar {
background-color: #def; background-color: #def;
height: 1.3em;
overflow: hidden;
} }
.text { .text {
display: table; display: table;
@@ -38,16 +40,16 @@ limitations under the License.
display: none; display: none;
} }
</ui:style> </ui:style>
<g:HTMLPanel styleName='{style.skipBar}'> <g:HTMLPanel addStyleNames='{style.skipBar}'>
<div class='{style.text}'> <div class='{style.text}'>
<ui:msg> <ui:msg>
<g:Anchor ui:field='upArrow' styleName='{style.anchor}'> <g:Anchor ui:field='upArrow' addStyleNames='{style.anchor}'>
</g:Anchor> </g:Anchor>
<span>... skipped </span> <span><ui:msg>... skipped </ui:msg></span>
<g:Anchor ui:field='skipNum' styleName='{style.anchor}'> <g:Anchor ui:field='skipNum' addStyleNames='{style.anchor}'>
</g:Anchor> </g:Anchor>
<span> common lines ...</span> <span><ui:msg> common lines ...</ui:msg></span>
<g:Anchor ui:field='downArrow' styleName='{style.anchor}'> <g:Anchor ui:field='downArrow' addStyleNames='{style.anchor}'>
</g:Anchor> </g:Anchor>
</ui:msg> </ui:msg>
</div> </div>

View File

@@ -128,12 +128,29 @@ public class CodeMirror extends JavaScriptObject {
return this.getScrollInfo(); return this.getScrollInfo();
}-*/; }-*/;
public final native CodeMirror getScrollSetBy() /*-{
return this.state.scrollSetBy;
}-*/;
public final native void setScrollSetBy(CodeMirror cm) /*-{
this.state.scrollSetBy = cm;
}-*/;
public final native double getScrollSetAt() /*-{
return this.state.scrollSetAt;
}-*/;
public final native void setScrollSetAt(double when) /*-{
this.state.scrollSetAt = when;
}-*/;
public final native void on(String event, Runnable thunk) /*-{ public final native void on(String event, Runnable thunk) /*-{
this.on(event, $entry(function() { this.on(event, $entry(function() {
thunk.@java.lang.Runnable::run()(); thunk.@java.lang.Runnable::run()();
})); }));
}-*/; }-*/;
/** TODO: Break this line after updating GWT */
public final native void on(String event, EventHandler handler) /*-{ public final native void on(String event, EventHandler handler) /*-{
this.on(event, $entry(function(cm, e) { this.on(event, $entry(function(cm, e) {
handler.@net.codemirror.lib.CodeMirror.EventHandler::handle(Lnet/codemirror/lib/CodeMirror;Lcom/google/gwt/dom/client/NativeEvent;)(cm, e); handler.@net.codemirror.lib.CodeMirror.EventHandler::handle(Lnet/codemirror/lib/CodeMirror;Lcom/google/gwt/dom/client/NativeEvent;)(cm, e);