| // 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.reviewdb.client.AccountDiffPreference.DEFAULT_CONTEXT; |
| import static com.google.gerrit.reviewdb.client.AccountDiffPreference.WHOLE_FILE_CONTEXT; |
| import static com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace.IGNORE_ALL_SPACE; |
| import static com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace.IGNORE_NONE; |
| import static com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace.IGNORE_SPACE_AT_EOL; |
| import static com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace.IGNORE_SPACE_CHANGE; |
| 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.ui.NpIntTextBox; |
| import com.google.gerrit.reviewdb.client.AccountDiffPreference; |
| import com.google.gerrit.reviewdb.client.AccountDiffPreference.Theme; |
| import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace; |
| import com.google.gerrit.reviewdb.client.Patch.ChangeType; |
| import com.google.gwt.core.client.GWT; |
| import com.google.gwt.core.client.Scheduler; |
| import com.google.gwt.core.client.Scheduler.RepeatingCommand; |
| 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 net.codemirror.lib.ModeInjector; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| /** Displays current diff preferences. */ |
| class PreferencesBox extends Composite { |
| interface Binder extends UiBinder<HTMLPanel, PreferencesBox> {} |
| private static final Binder uiBinder = GWT.create(Binder.class); |
| |
| interface Style extends CssResource { |
| String dialog(); |
| } |
| |
| private final SideBySide2 view; |
| private DiffPreferences prefs; |
| private int contextLastValue; |
| private Timer updateContextTimer; |
| |
| @UiField Style style; |
| @UiField Anchor close; |
| @UiField ListBox ignoreWhitespace; |
| @UiField NpIntTextBox tabWidth; |
| @UiField NpIntTextBox lineLength; |
| @UiField NpIntTextBox context; |
| @UiField CheckBox contextEntireFile; |
| @UiField ToggleButton intralineDifference; |
| @UiField ToggleButton syntaxHighlighting; |
| @UiField ToggleButton whitespaceErrors; |
| @UiField ToggleButton showTabs; |
| @UiField ToggleButton lineNumbers; |
| @UiField ToggleButton leftSide; |
| @UiField ToggleButton emptyPane; |
| @UiField ToggleButton topMenu; |
| @UiField ToggleButton manualReview; |
| @UiField ToggleButton expandAllComments; |
| @UiField ToggleButton renderEntireFile; |
| @UiField ListBox theme; |
| @UiField ListBox mode; |
| @UiField Button apply; |
| @UiField Button save; |
| |
| PreferencesBox(SideBySide2 view) { |
| this.view = view; |
| |
| initWidget(uiBinder.createAndBindUi(this)); |
| initIgnoreWhitespace(); |
| initTheme(); |
| initMode(); |
| } |
| |
| @Override |
| public void onLoad() { |
| super.onLoad(); |
| |
| save.setVisible(Gerrit.isSignedIn()); |
| 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.canEnableRenderEntireFile(prefs)) { |
| renderEntireFile.setEnabled(true); |
| } else { |
| if (prefs.renderEntireFile()) { |
| prefs.renderEntireFile(false); |
| renderEntireFile.setValue(false); |
| view.updateRenderEntireFile(); |
| } |
| renderEntireFile.setEnabled(false); |
| } |
| view.setContext(prefs.context()); |
| } |
| }; |
| } |
| |
| void set(DiffPreferences prefs) { |
| this.prefs = prefs; |
| |
| setIgnoreWhitespace(prefs.ignoreWhitespace()); |
| tabWidth.setIntValue(prefs.tabSize()); |
| lineLength.setIntValue(prefs.lineLength()); |
| syntaxHighlighting.setValue(prefs.syntaxHighlighting()); |
| whitespaceErrors.setValue(prefs.showWhitespaceErrors()); |
| showTabs.setValue(prefs.showTabs()); |
| lineNumbers.setValue(prefs.showLineNumbers()); |
| leftSide.setValue(view.diffTable.isVisibleA()); |
| emptyPane.setValue(!prefs.hideEmptyPane()); |
| leftSide.setEnabled(!(prefs.hideEmptyPane() |
| && view.diffTable.getChangeType() == ChangeType.ADDED)); |
| topMenu.setValue(!prefs.hideTopMenu()); |
| manualReview.setValue(prefs.manualReview()); |
| expandAllComments.setValue(prefs.expandAllComments()); |
| renderEntireFile.setValue(prefs.renderEntireFile()); |
| renderEntireFile.setEnabled(view.canEnableRenderEntireFile(prefs)); |
| setTheme(prefs.theme()); |
| |
| mode.setEnabled(prefs.syntaxHighlighting()); |
| if (prefs.syntaxHighlighting()) { |
| setMode(view.getCmFromSide(DisplaySide.B).getStringOption("mode")); |
| } |
| |
| switch (view.getIntraLineStatus()) { |
| case OFF: |
| case OK: |
| intralineDifference.setValue(prefs.intralineDifference()); |
| break; |
| |
| case TIMEOUT: |
| case FAILURE: |
| intralineDifference.setValue(false); |
| intralineDifference.setEnabled(false); |
| break; |
| } |
| |
| 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(ChangeEvent e) { |
| prefs.ignoreWhitespace(Whitespace.valueOf( |
| ignoreWhitespace.getValue(ignoreWhitespace.getSelectedIndex()))); |
| view.reloadDiffInfo(); |
| } |
| |
| @UiHandler("intralineDifference") |
| void onIntralineDifference(ValueChangeEvent<Boolean> e) { |
| prefs.intralineDifference(e.getValue()); |
| 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); |
| 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()); |
| } |
| 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))); |
| view.operation(new Runnable() { |
| @Override |
| public void run() { |
| int v = prefs.tabSize(); |
| view.getCmFromSide(DisplaySide.A).setOption("tabSize", v); |
| view.getCmFromSide(DisplaySide.B).setOption("tabSize", v); |
| } |
| }); |
| } |
| } |
| |
| @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))); |
| view.operation(new Runnable() { |
| @Override |
| public void run() { |
| view.setLineLength(prefs.lineLength()); |
| } |
| }); |
| } |
| } |
| @UiHandler("expandAllComments") |
| void onExpandAllComments(ValueChangeEvent<Boolean> e) { |
| prefs.expandAllComments(e.getValue()); |
| view.getCommentManager().setExpandAllComments(prefs.expandAllComments()); |
| } |
| |
| @UiHandler("showTabs") |
| void onShowTabs(ValueChangeEvent<Boolean> e) { |
| prefs.showTabs(e.getValue()); |
| view.setShowTabs(prefs.showTabs()); |
| } |
| |
| @UiHandler("lineNumbers") |
| void onLineNumbers(ValueChangeEvent<Boolean> e) { |
| prefs.showLineNumbers(e.getValue()); |
| view.setShowLineNumbers(prefs.showLineNumbers()); |
| } |
| |
| @UiHandler("leftSide") |
| void onLeftSide(ValueChangeEvent<Boolean> e) { |
| view.diffTable.setVisibleA(e.getValue()); |
| } |
| |
| @UiHandler("emptyPane") |
| void onHideEmptyPane(ValueChangeEvent<Boolean> e) { |
| prefs.hideEmptyPane(!e.getValue()); |
| view.diffTable.setHideEmptyPane(prefs.hideEmptyPane()); |
| if (prefs.hideEmptyPane()) { |
| if (view.diffTable.getChangeType() == ChangeType.ADDED) { |
| leftSide.setValue(false); |
| leftSide.setEnabled(false); |
| } |
| } else { |
| leftSide.setValue(view.diffTable.isVisibleA()); |
| leftSide.setEnabled(true); |
| } |
| } |
| |
| @UiHandler("topMenu") |
| void onTopMenu(ValueChangeEvent<Boolean> e) { |
| prefs.hideTopMenu(!e.getValue()); |
| Gerrit.setHeaderVisible(!prefs.hideTopMenu()); |
| view.resizeCodeMirror(); |
| } |
| |
| @UiHandler("manualReview") |
| void onManualReview(ValueChangeEvent<Boolean> e) { |
| prefs.manualReview(e.getValue()); |
| } |
| |
| @UiHandler("syntaxHighlighting") |
| void onSyntaxHighlighting(ValueChangeEvent<Boolean> e) { |
| prefs.syntaxHighlighting(e.getValue()); |
| mode.setEnabled(prefs.syntaxHighlighting()); |
| if (prefs.syntaxHighlighting()) { |
| setMode(view.getContentType()); |
| } |
| view.setSyntaxHighlighting(prefs.syntaxHighlighting()); |
| } |
| |
| @UiHandler("mode") |
| void onMode(ChangeEvent e) { |
| final String m = mode.getValue(mode.getSelectedIndex()); |
| prefs.syntaxHighlighting(true); |
| syntaxHighlighting.setValue(true, false); |
| Scheduler.get().scheduleFixedDelay(new RepeatingCommand() { |
| @Override |
| public boolean execute() { |
| if (prefs.syntaxHighlighting() && view.isAttached()) { |
| view.operation(new Runnable() { |
| @Override |
| public void run() { |
| String mode = m != null && !m.isEmpty() ? m : null; |
| view.getCmFromSide(DisplaySide.A).setOption("mode", mode); |
| view.getCmFromSide(DisplaySide.B).setOption("mode", mode); |
| } |
| }); |
| } |
| return false; |
| } |
| }, 50); |
| } |
| |
| @UiHandler("whitespaceErrors") |
| void onWhitespaceErrors(ValueChangeEvent<Boolean> e) { |
| prefs.showWhitespaceErrors(e.getValue()); |
| view.operation(new Runnable() { |
| @Override |
| public void run() { |
| boolean s = prefs.showWhitespaceErrors(); |
| view.getCmFromSide(DisplaySide.A).setOption("showTrailingSpace", s); |
| view.getCmFromSide(DisplaySide.B).setOption("showTrailingSpace", s); |
| } |
| }); |
| } |
| |
| @UiHandler("renderEntireFile") |
| void onRenderEntireFile(ValueChangeEvent<Boolean> e) { |
| prefs.renderEntireFile(e.getValue()); |
| view.updateRenderEntireFile(); |
| } |
| |
| @UiHandler("theme") |
| void onTheme(ChangeEvent e) { |
| prefs.theme(Theme.valueOf(theme.getValue(theme.getSelectedIndex()))); |
| view.setThemeStyles(prefs.theme().isDark()); |
| view.operation(new Runnable() { |
| @Override |
| public void run() { |
| String t = prefs.theme().name().toLowerCase(); |
| view.getCmFromSide(DisplaySide.A).setOption("theme", t); |
| view.getCmFromSide(DisplaySide.B).setOption("theme", t); |
| } |
| }); |
| } |
| |
| @UiHandler("apply") |
| void onApply(ClickEvent e) { |
| close(); |
| } |
| |
| @UiHandler("save") |
| void onSave(ClickEvent e) { |
| AccountApi.putDiffPreferences(prefs, new GerritCallback<DiffPreferences>() { |
| @Override |
| public void onSuccess(DiffPreferences result) { |
| AccountDiffPreference p = Gerrit.getAccountDiffPreference(); |
| if (p == null) { |
| p = AccountDiffPreference.createDefault(Gerrit.getUserAccount().getId()); |
| } |
| result.copyTo(p); |
| Gerrit.setAccountDiffPreference(p); |
| } |
| }); |
| 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_SPACE_AT_EOL(), |
| IGNORE_SPACE_AT_EOL.name()); |
| ignoreWhitespace.addItem( |
| PatchUtil.C.whitespaceIGNORE_SPACE_CHANGE(), |
| IGNORE_SPACE_CHANGE.name()); |
| ignoreWhitespace.addItem( |
| PatchUtil.C.whitespaceIGNORE_ALL_SPACE(), |
| IGNORE_ALL_SPACE.name()); |
| } |
| |
| private static final Map<String, String> NAME_TO_MODE; |
| private static final Map<String, String> NORMALIZED_MODES; |
| static { |
| NAME_TO_MODE = new TreeMap<>(); |
| NORMALIZED_MODES = new HashMap<>(); |
| for (String type : ModeInjector.getKnownMimeTypes()) { |
| String name = type; |
| if (name.startsWith("text/x-")) { |
| name = name.substring("text/x-".length()); |
| } else if (name.startsWith("text/")) { |
| name = name.substring("text/".length()); |
| } else if (name.startsWith("application/")) { |
| name = name.substring("application/".length()); |
| } |
| |
| String normalized = NAME_TO_MODE.get(name); |
| if (normalized == null) { |
| normalized = type; |
| NAME_TO_MODE.put(name, normalized); |
| } |
| NORMALIZED_MODES.put(type, normalized); |
| } |
| } |
| |
| private void initMode() { |
| mode.addItem("", ""); |
| for (Map.Entry<String, String> e : NAME_TO_MODE.entrySet()) { |
| mode.addItem(e.getKey(), e.getValue()); |
| } |
| } |
| |
| private void setMode(String modeType) { |
| if (modeType != null && !modeType.isEmpty()) { |
| if (NORMALIZED_MODES.containsKey(modeType)) { |
| modeType = NORMALIZED_MODES.get(modeType); |
| } |
| for (int i = 0; i < mode.getItemCount(); i++) { |
| if (mode.getValue(i).equals(modeType)) { |
| 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() { |
| theme.addItem( |
| Theme.DEFAULT.name().toLowerCase(), |
| Theme.DEFAULT.name()); |
| theme.addItem( |
| Theme.ECLIPSE.name().toLowerCase(), |
| Theme.ECLIPSE.name()); |
| theme.addItem( |
| Theme.ELEGANT.name().toLowerCase(), |
| Theme.ELEGANT.name()); |
| theme.addItem( |
| Theme.NEAT.name().toLowerCase(), |
| Theme.NEAT.name()); |
| theme.addItem( |
| Theme.MIDNIGHT.name().toLowerCase(), |
| Theme.MIDNIGHT.name()); |
| theme.addItem( |
| Theme.NIGHT.name().toLowerCase(), |
| Theme.NIGHT.name()); |
| theme.addItem( |
| Theme.TWILIGHT.name().toLowerCase(), |
| Theme.TWILIGHT.name()); |
| } |
| } |