blob: ed4ac25f2c24abf1a3201af73ace11273ae1b593 [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.diff;
import static com.google.gerrit.extensions.client.DiffPreferencesInfo.DEFAULT_CONTEXT;
import static com.google.gerrit.extensions.client.DiffPreferencesInfo.WHOLE_FILE_CONTEXT;
import static com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace.IGNORE_ALL;
import static com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace.IGNORE_LEADING_AND_TRAILING;
import static com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace.IGNORE_NONE;
import static com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace.IGNORE_TRAILING;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_ESCAPE;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.AccountApi;
import com.google.gerrit.client.account.DiffPreferences;
import com.google.gerrit.client.patches.PatchUtil;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.NpIntTextBox;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.client.Theme;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
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.uibinder.client.UiHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.UIObject;
import java.util.Objects;
import net.codemirror.lib.CodeMirror;
import net.codemirror.mode.ModeInfo;
import net.codemirror.mode.ModeInjector;
import net.codemirror.theme.ThemeLoader;
/** Displays current diff preferences. */
public class PreferencesBox extends Composite {
interface Binder extends UiBinder<HTMLPanel, PreferencesBox> {}
private static final Binder uiBinder = GWT.create(Binder.class);
public interface Style extends CssResource {
String dialog();
}
private final DiffScreen view;
private DiffPreferences prefs;
private int contextLastValue;
private Timer updateContextTimer;
@UiField Style style;
@UiField Element header;
@UiField Anchor close;
@UiField ListBox ignoreWhitespace;
@UiField NpIntTextBox tabWidth;
@UiField NpIntTextBox lineLength;
@UiField NpIntTextBox context;
@UiField NpIntTextBox cursorBlinkRate;
@UiField CheckBox contextEntireFile;
@UiField ToggleButton intralineDifference;
@UiField ToggleButton syntaxHighlighting;
@UiField ToggleButton whitespaceErrors;
@UiField ToggleButton showTabs;
@UiField ToggleButton lineNumbers;
@UiField Element leftSideLabel;
@UiField ToggleButton leftSide;
@UiField ToggleButton emptyPane;
@UiField ToggleButton topMenu;
@UiField ToggleButton autoHideDiffTableHeader;
@UiField ToggleButton manualReview;
@UiField ToggleButton expandAllComments;
@UiField ToggleButton renderEntireFile;
@UiField ToggleButton matchBrackets;
@UiField ToggleButton lineWrapping;
@UiField ToggleButton skipDeleted;
@UiField ToggleButton skipUnchanged;
@UiField ToggleButton skipUncommented;
@UiField ListBox theme;
@UiField Element modeLabel;
@UiField ListBox mode;
@UiField Button apply;
@UiField Button save;
public PreferencesBox(DiffScreen view) {
this.view = view;
initWidget(uiBinder.createAndBindUi(this));
initIgnoreWhitespace();
initTheme();
if (view != null) {
initMode();
} else {
UIObject.setVisible(header, false);
apply.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
}
@Override
public void onLoad() {
super.onLoad();
save.setVisible(Gerrit.isSignedIn());
if (view != null) {
addDomHandler(
new KeyDownHandler() {
@Override
public void onKeyDown(KeyDownEvent event) {
if (event.getNativeKeyCode() == KEY_ESCAPE || event.getNativeKeyCode() == ',') {
close();
}
}
},
KeyDownEvent.getType());
updateContextTimer =
new Timer() {
@Override
public void run() {
if (prefs.context() == WHOLE_FILE_CONTEXT) {
contextEntireFile.setValue(true);
}
if (view.canRenderEntireFile(prefs)) {
renderEntireFile.setEnabled(true);
renderEntireFile.setValue(prefs.renderEntireFile());
} else {
renderEntireFile.setValue(false);
renderEntireFile.setEnabled(false);
}
view.setContext(prefs.context());
}
};
}
}
public Style getStyle() {
return style;
}
public void set(DiffPreferences prefs) {
this.prefs = prefs;
setIgnoreWhitespace(prefs.ignoreWhitespace());
tabWidth.setIntValue(prefs.tabSize());
if (view != null && Patch.COMMIT_MSG.equals(view.path)) {
lineLength.setEnabled(false);
lineLength.setIntValue(72);
} else {
lineLength.setEnabled(true);
lineLength.setIntValue(prefs.lineLength());
}
cursorBlinkRate.setIntValue(prefs.cursorBlinkRate());
syntaxHighlighting.setValue(prefs.syntaxHighlighting());
whitespaceErrors.setValue(prefs.showWhitespaceErrors());
showTabs.setValue(prefs.showTabs());
lineNumbers.setValue(prefs.showLineNumbers());
emptyPane.setValue(!prefs.hideEmptyPane());
if (view != null) {
leftSide.setValue(view.getDiffTable().isVisibleA());
leftSide.setEnabled(
!(prefs.hideEmptyPane() && view.getDiffTable().getChangeType() == ChangeType.ADDED));
} else {
UIObject.setVisible(leftSideLabel, false);
leftSide.setVisible(false);
}
topMenu.setValue(!prefs.hideTopMenu());
autoHideDiffTableHeader.setValue(!prefs.autoHideDiffTableHeader());
manualReview.setValue(prefs.manualReview());
expandAllComments.setValue(prefs.expandAllComments());
matchBrackets.setValue(prefs.matchBrackets());
lineWrapping.setValue(prefs.lineWrapping());
skipDeleted.setValue(!prefs.skipDeleted());
skipUnchanged.setValue(!prefs.skipUnchanged());
skipUncommented.setValue(!prefs.skipUncommented());
setTheme(prefs.theme());
if (view == null || view.canRenderEntireFile(prefs)) {
renderEntireFile.setValue(prefs.renderEntireFile());
renderEntireFile.setEnabled(true);
} else {
renderEntireFile.setValue(false);
renderEntireFile.setEnabled(false);
}
if (view != null) {
mode.setEnabled(prefs.syntaxHighlighting());
if (prefs.syntaxHighlighting()) {
setMode(view.getCmFromSide(DisplaySide.B).getStringOption("mode"));
}
} else {
UIObject.setVisible(modeLabel, false);
mode.setVisible(false);
}
if (view != null) {
switch (view.getIntraLineStatus()) {
case OFF:
case OK:
intralineDifference.setValue(prefs.intralineDifference());
break;
case TIMEOUT:
case FAILURE:
intralineDifference.setValue(false);
intralineDifference.setEnabled(false);
break;
}
} else {
intralineDifference.setValue(prefs.intralineDifference());
}
if (prefs.context() == WHOLE_FILE_CONTEXT) {
contextLastValue = DEFAULT_CONTEXT;
context.setText("");
contextEntireFile.setValue(true);
} else {
context.setIntValue(prefs.context());
contextEntireFile.setValue(false);
}
}
@UiHandler("ignoreWhitespace")
void onIgnoreWhitespace(@SuppressWarnings("unused") ChangeEvent e) {
prefs.ignoreWhitespace(
Whitespace.valueOf(ignoreWhitespace.getValue(ignoreWhitespace.getSelectedIndex())));
if (view != null) {
view.reloadDiffInfo();
}
}
@UiHandler("intralineDifference")
void onIntralineDifference(ValueChangeEvent<Boolean> e) {
prefs.intralineDifference(Boolean.valueOf(e.getValue()));
if (view != null) {
view.setShowIntraline(prefs.intralineDifference());
}
}
@UiHandler("context")
void onContextKey(KeyPressEvent e) {
if (contextEntireFile.getValue()) {
char c = e.getCharCode();
if ('0' <= c && c <= '9') {
contextEntireFile.setValue(false);
}
}
}
@UiHandler("context")
void onContext(ValueChangeEvent<String> e) {
String v = e.getValue();
int c;
if (v != null && v.length() > 0) {
c = Math.min(Math.max(0, Integer.parseInt(v)), 32767);
contextEntireFile.setValue(false);
} else if (v == null || v.isEmpty()) {
c = WHOLE_FILE_CONTEXT;
} else {
return;
}
prefs.context(c);
if (view != null) {
updateContextTimer.schedule(200);
}
}
@UiHandler("contextEntireFile")
void onContextEntireFile(ValueChangeEvent<Boolean> e) {
// If a click arrives too fast after onContext applied an update
// the user committed the context line update by clicking on the
// whole file checkmark. Drop this event, but transfer focus.
if (e.getValue()) {
contextLastValue = context.getIntValue();
context.setText("");
prefs.context(WHOLE_FILE_CONTEXT);
} else {
prefs.context(contextLastValue > 0 ? contextLastValue : DEFAULT_CONTEXT);
context.setIntValue(prefs.context());
context.setFocus(true);
context.setSelectionRange(0, context.getText().length());
}
if (view != null) {
updateContextTimer.schedule(200);
}
}
@UiHandler("tabWidth")
void onTabWidth(ValueChangeEvent<String> e) {
String v = e.getValue();
if (v != null && v.length() > 0) {
prefs.tabSize(Math.max(1, Integer.parseInt(v)));
if (view != null) {
view.operation(
() -> {
int size = prefs.tabSize();
for (CodeMirror cm : view.getCms()) {
cm.setOption("tabSize", size);
}
});
}
}
}
@UiHandler("lineLength")
void onLineLength(ValueChangeEvent<String> e) {
String v = e.getValue();
if (v != null && v.length() > 0) {
prefs.lineLength(Math.max(1, Integer.parseInt(v)));
if (view != null) {
view.operation(() -> view.setLineLength(prefs.lineLength()));
}
}
}
@UiHandler("expandAllComments")
void onExpandAllComments(ValueChangeEvent<Boolean> e) {
prefs.expandAllComments(e.getValue());
if (view != null) {
view.getCommentManager().setExpandAllComments(prefs.expandAllComments());
}
}
@UiHandler("cursorBlinkRate")
void onCursoBlinkRate(ValueChangeEvent<String> e) {
String v = e.getValue();
if (v != null && v.length() > 0) {
// A negative value hides the cursor entirely:
// don't let user shoot himself in the foot.
prefs.cursorBlinkRate(Math.max(0, Integer.parseInt(v)));
view.getCmFromSide(DisplaySide.A).setOption("cursorBlinkRate", prefs.cursorBlinkRate());
view.getCmFromSide(DisplaySide.B).setOption("cursorBlinkRate", prefs.cursorBlinkRate());
}
}
@UiHandler("showTabs")
void onShowTabs(ValueChangeEvent<Boolean> e) {
prefs.showTabs(e.getValue());
if (view != null) {
view.setShowTabs(prefs.showTabs());
}
}
@UiHandler("lineNumbers")
void onLineNumbers(ValueChangeEvent<Boolean> e) {
prefs.showLineNumbers(e.getValue());
if (view != null) {
view.setShowLineNumbers(prefs.showLineNumbers());
}
}
@UiHandler("leftSide")
void onLeftSide(ValueChangeEvent<Boolean> e) {
if (view.getDiffTable() instanceof SideBySideTable) {
((SideBySideTable) view.getDiffTable()).setVisibleA(e.getValue());
}
}
@UiHandler("emptyPane")
void onHideEmptyPane(ValueChangeEvent<Boolean> e) {
prefs.hideEmptyPane(!e.getValue());
if (view != null) {
view.getDiffTable().setHideEmptyPane(prefs.hideEmptyPane());
if (prefs.hideEmptyPane()) {
if (view.getDiffTable().getChangeType() == ChangeType.ADDED) {
leftSide.setValue(false);
leftSide.setEnabled(false);
}
} else {
leftSide.setValue(view.getDiffTable().isVisibleA());
leftSide.setEnabled(true);
}
}
}
@UiHandler("topMenu")
void onTopMenu(ValueChangeEvent<Boolean> e) {
prefs.hideTopMenu(!e.getValue());
if (view != null) {
Gerrit.setHeaderVisible(!prefs.hideTopMenu());
view.resizeCodeMirror();
}
}
@UiHandler("autoHideDiffTableHeader")
void onAutoHideDiffTableHeader(ValueChangeEvent<Boolean> e) {
prefs.autoHideDiffTableHeader(!e.getValue());
if (view != null) {
view.setAutoHideDiffHeader(!e.getValue());
}
}
@UiHandler("manualReview")
void onManualReview(ValueChangeEvent<Boolean> e) {
prefs.manualReview(e.getValue());
}
@UiHandler("syntaxHighlighting")
void onSyntaxHighlighting(ValueChangeEvent<Boolean> e) {
prefs.syntaxHighlighting(e.getValue());
if (view != null) {
mode.setEnabled(prefs.syntaxHighlighting());
if (prefs.syntaxHighlighting()) {
setMode(view.getContentType());
}
view.setSyntaxHighlighting(prefs.syntaxHighlighting());
}
}
@UiHandler("mode")
void onMode(@SuppressWarnings("unused") ChangeEvent e) {
String mode = getSelectedMode();
prefs.syntaxHighlighting(true);
syntaxHighlighting.setValue(true, false);
new ModeInjector()
.add(mode)
.inject(
new GerritCallback<Void>() {
@Override
public void onSuccess(Void result) {
if (prefs.syntaxHighlighting()
&& Objects.equals(mode, getSelectedMode())
&& view.isAttached()) {
view.operation(
() -> {
view.getCmFromSide(DisplaySide.A).setOption("mode", mode);
view.getCmFromSide(DisplaySide.B).setOption("mode", mode);
});
}
}
});
}
private String getSelectedMode() {
String m = mode.getValue(mode.getSelectedIndex());
return m != null && !m.isEmpty() ? m : null;
}
@UiHandler("whitespaceErrors")
void onWhitespaceErrors(ValueChangeEvent<Boolean> e) {
prefs.showWhitespaceErrors(e.getValue());
if (view != null) {
view.operation(
() -> {
boolean s = prefs.showWhitespaceErrors();
for (CodeMirror cm : view.getCms()) {
cm.setOption("showTrailingSpace", s);
}
});
}
}
@UiHandler("renderEntireFile")
void onRenderEntireFile(ValueChangeEvent<Boolean> e) {
prefs.renderEntireFile(e.getValue());
if (view != null) {
view.updateRenderEntireFile();
}
}
@UiHandler("matchBrackets")
void onMatchBrackets(ValueChangeEvent<Boolean> e) {
prefs.matchBrackets(e.getValue());
view.getCmFromSide(DisplaySide.A).setOption("matchBrackets", prefs.matchBrackets());
view.getCmFromSide(DisplaySide.B).setOption("matchBrackets", prefs.matchBrackets());
}
@UiHandler("lineWrapping")
void onLineWrapping(ValueChangeEvent<Boolean> e) {
prefs.lineWrapping(e.getValue());
view.getCmFromSide(DisplaySide.A).setOption("lineWrapping", prefs.lineWrapping());
view.getCmFromSide(DisplaySide.B).setOption("lineWrapping", prefs.lineWrapping());
}
@UiHandler("skipDeleted")
void onSkipDeleted(ValueChangeEvent<Boolean> e) {
prefs.skipDeleted(!e.getValue());
// TODO: Update the navigation links on the current DiffScreen
}
@UiHandler("skipUnchanged")
void onSkipUnchanged(ValueChangeEvent<Boolean> e) {
prefs.skipUnchanged(!e.getValue());
// TODO: Update the navigation links on the current DiffScreen
}
@UiHandler("skipUncommented")
void onSkipUncommented(ValueChangeEvent<Boolean> e) {
prefs.skipUncommented(!e.getValue());
// TODO: Update the navigation links on the current DiffScreen
}
@UiHandler("theme")
void onTheme(@SuppressWarnings("unused") ChangeEvent e) {
Theme newTheme = getSelectedTheme();
prefs.theme(newTheme);
if (view != null) {
ThemeLoader.loadTheme(
newTheme,
new GerritCallback<Void>() {
@Override
public void onSuccess(Void result) {
view.operation(
() -> {
if (getSelectedTheme() == newTheme && isAttached()) {
String t = newTheme.name().toLowerCase();
view.getCmFromSide(DisplaySide.A).setOption("theme", t);
view.getCmFromSide(DisplaySide.B).setOption("theme", t);
view.setThemeStyles(newTheme.isDark());
}
});
}
});
}
}
private Theme getSelectedTheme() {
return Theme.valueOf(theme.getValue(theme.getSelectedIndex()));
}
@UiHandler("apply")
void onApply(@SuppressWarnings("unused") ClickEvent e) {
close();
}
@UiHandler("save")
void onSave(@SuppressWarnings("unused") ClickEvent e) {
AccountApi.putDiffPreferences(
prefs,
new GerritCallback<DiffPreferences>() {
@Override
public void onSuccess(DiffPreferences result) {
DiffPreferencesInfo p = new DiffPreferencesInfo();
result.copyTo(p);
Gerrit.setDiffPreferences(p);
}
});
if (view != null) {
close();
}
}
@UiHandler("close")
void onClose(ClickEvent e) {
e.preventDefault();
close();
}
void setFocus(boolean focus) {
ignoreWhitespace.setFocus(focus);
}
private void close() {
((PopupPanel) getParent()).hide();
}
private void setIgnoreWhitespace(Whitespace v) {
String name = v != null ? v.name() : IGNORE_NONE.name();
for (int i = 0; i < ignoreWhitespace.getItemCount(); i++) {
if (ignoreWhitespace.getValue(i).equals(name)) {
ignoreWhitespace.setSelectedIndex(i);
return;
}
}
ignoreWhitespace.setSelectedIndex(0);
}
private void initIgnoreWhitespace() {
ignoreWhitespace.addItem(PatchUtil.C.whitespaceIGNORE_NONE(), IGNORE_NONE.name());
ignoreWhitespace.addItem(PatchUtil.C.whitespaceIGNORE_TRAILING(), IGNORE_TRAILING.name());
ignoreWhitespace.addItem(
PatchUtil.C.whitespaceIGNORE_LEADING_AND_TRAILING(), IGNORE_LEADING_AND_TRAILING.name());
ignoreWhitespace.addItem(PatchUtil.C.whitespaceIGNORE_ALL(), IGNORE_ALL.name());
}
private void initMode() {
mode.addItem("", "");
for (ModeInfo m : Natives.asList(ModeInfo.all())) {
mode.addItem(m.name(), m.mime());
}
}
private void setMode(String modeType) {
if (modeType != null && !modeType.isEmpty()) {
ModeInfo m = ModeInfo.findModeByMIME(modeType);
if (m != null) {
for (int i = 0; i < mode.getItemCount(); i++) {
if (mode.getValue(i).equals(m.mime())) {
mode.setSelectedIndex(i);
return;
}
}
}
}
mode.setSelectedIndex(0);
}
private void setTheme(Theme v) {
String name = v != null ? v.name() : Theme.DEFAULT.name();
for (int i = 0; i < theme.getItemCount(); i++) {
if (theme.getValue(i).equals(name)) {
theme.setSelectedIndex(i);
return;
}
}
theme.setSelectedIndex(0);
}
private void initTheme() {
for (Theme t : Theme.values()) {
theme.addItem(t.name().toLowerCase(), t.name());
}
}
}