| //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 com.google.gwt.core.client.GWT; |
| import com.google.gwt.dom.client.Element; |
| import com.google.gwt.dom.client.Style.Unit; |
| import com.google.gwt.event.dom.client.ClickEvent; |
| import com.google.gwt.event.dom.client.ClickHandler; |
| import com.google.gwt.event.dom.client.MouseDownEvent; |
| import com.google.gwt.event.dom.client.MouseMoveEvent; |
| import com.google.gwt.event.dom.client.MouseUpEvent; |
| 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.DOM; |
| import com.google.gwt.user.client.ui.Composite; |
| import com.google.gwt.user.client.ui.HTMLPanel; |
| import com.google.gwt.user.client.ui.Label; |
| import com.google.gwt.user.client.ui.Widget; |
| |
| import net.codemirror.lib.CodeMirror; |
| import net.codemirror.lib.LineCharacter; |
| import net.codemirror.lib.ScrollInfo; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** Displays overview of all edits and comments in this file. */ |
| class OverviewBar extends Composite implements ClickHandler { |
| interface Binder extends UiBinder<HTMLPanel, OverviewBar> {} |
| private static final Binder uiBinder = GWT.create(Binder.class); |
| |
| interface Style extends CssResource { |
| String gutter(); |
| String halfGutter(); |
| String comment(); |
| String draft(); |
| String insert(); |
| String delete(); |
| String viewportDrag(); |
| } |
| |
| enum MarkType { |
| COMMENT, DRAFT, INSERT, DELETE, EDIT |
| } |
| |
| @UiField Style style; |
| @UiField Label viewport; |
| |
| private final List<MarkHandle> diff; |
| private final Set<MarkHandle> comments; |
| private CodeMirror cmB; |
| |
| private boolean dragging; |
| private int startY; |
| private double ratio; |
| |
| OverviewBar() { |
| initWidget(uiBinder.createAndBindUi(this)); |
| diff = new ArrayList<>(); |
| comments = new HashSet<>(); |
| addDomHandler(this, ClickEvent.getType()); |
| } |
| |
| void init(CodeMirror cmB) { |
| this.cmB = cmB; |
| } |
| |
| void refresh() { |
| update(cmB.getScrollInfo()); |
| } |
| |
| void update(ScrollInfo si) { |
| double viewHeight = si.getClientHeight(); |
| double r = ratio(si); |
| |
| com.google.gwt.dom.client.Style style = viewport.getElement().getStyle(); |
| style.setTop(si.getTop() * r, Unit.PX); |
| style.setHeight(Math.max(10, viewHeight * r), Unit.PX); |
| getElement().getStyle().setHeight(viewHeight, Unit.PX); |
| for (MarkHandle info : diff) { |
| info.position(r); |
| } |
| for (MarkHandle info : comments) { |
| info.position(r); |
| } |
| } |
| |
| @Override |
| protected void onUnload() { |
| super.onUnload(); |
| if (dragging) { |
| DOM.releaseCapture(viewport.getElement()); |
| } |
| } |
| |
| @Override |
| public void onClick(ClickEvent e) { |
| if (e.getY() < viewport.getElement().getOffsetTop()) { |
| CodeMirror.handleVimKey(cmB, "<PageUp>"); |
| } else { |
| CodeMirror.handleVimKey(cmB, "<PageDown>"); |
| } |
| cmB.focus(); |
| } |
| |
| @UiHandler("viewport") |
| void onMouseDown(MouseDownEvent e) { |
| if (cmB != null) { |
| dragging = true; |
| ratio = ratio(cmB.getScrollInfo()); |
| startY = e.getY(); |
| viewport.addStyleName(style.viewportDrag()); |
| DOM.setCapture(viewport.getElement()); |
| e.preventDefault(); |
| e.stopPropagation(); |
| } |
| } |
| |
| @UiHandler("viewport") |
| void onMouseMove(MouseMoveEvent e) { |
| if (dragging) { |
| int y = e.getRelativeY(getElement()) - startY; |
| cmB.scrollToY(Math.max(0, y / ratio)); |
| e.preventDefault(); |
| e.stopPropagation(); |
| } |
| } |
| |
| @UiHandler("viewport") |
| void onMouseUp(MouseUpEvent e) { |
| if (dragging) { |
| dragging = false; |
| DOM.releaseCapture(viewport.getElement()); |
| viewport.removeStyleName(style.viewportDrag()); |
| e.preventDefault(); |
| e.stopPropagation(); |
| } |
| } |
| |
| private double ratio(ScrollInfo si) { |
| double barHeight = si.getClientHeight(); |
| double contentHeight = si.getHeight(); |
| return barHeight / contentHeight; |
| } |
| |
| MarkHandle add(CodeMirror cm, int line, int height, MarkType type) { |
| MarkHandle mark = new MarkHandle(cm, line, height); |
| switch (type) { |
| case COMMENT: |
| mark.addStyleName(style.comment()); |
| comments.add(mark); |
| break; |
| case DRAFT: |
| mark.addStyleName(style.draft()); |
| mark.getElement().setInnerText("*"); |
| comments.add(mark); |
| break; |
| case INSERT: |
| mark.addStyleName(style.insert()); |
| diff.add(mark); |
| break; |
| case DELETE: |
| mark.addStyleName(style.delete()); |
| diff.add(mark); |
| break; |
| case EDIT: |
| mark.edit = DOM.createDiv(); |
| mark.edit.setClassName(style.halfGutter()); |
| mark.getElement().appendChild(mark.edit); |
| mark.addStyleName(style.insert()); |
| diff.add(mark); |
| break; |
| } |
| if (cmB != null) { |
| mark.position(ratio(cmB.getScrollInfo())); |
| } |
| ((HTMLPanel) getWidget()).add(mark); |
| return mark; |
| } |
| |
| void clearDiffMarkers() { |
| for (MarkHandle mark : diff) { |
| mark.removeFromParent(); |
| } |
| diff.clear(); |
| } |
| |
| class MarkHandle extends Widget implements ClickHandler { |
| private static final int MIN_HEIGHT = 3; |
| |
| private final CodeMirror cm; |
| private final int line; |
| private final int height; |
| private Element edit; |
| |
| MarkHandle(CodeMirror cm, int line, int height) { |
| this.cm = cm; |
| this.line = line; |
| this.height = height; |
| |
| setElement((Element)(DOM.createDiv())); |
| setStyleName(style.gutter()); |
| addDomHandler(this, ClickEvent.getType()); |
| } |
| |
| void position(double ratio) { |
| double y = cm.heightAtLine(line, "local"); |
| getElement().getStyle().setTop(y * ratio, Unit.PX); |
| if (height > 1) { |
| double e = cm.heightAtLine(line + height, "local"); |
| double h = Math.max(MIN_HEIGHT, (e - y) * ratio); |
| getElement().getStyle().setHeight(h, Unit.PX); |
| if (edit != null) { |
| edit.getStyle().setHeight(h, Unit.PX); |
| } |
| } |
| } |
| |
| @Override |
| public void onClick(ClickEvent e) { |
| if (height == 1 || !visible()) { |
| e.stopPropagation(); |
| |
| double y = cm.heightAtLine(line, "local"); |
| double viewport = cm.getScrollInfo().getClientHeight(); |
| cm.setCursor(LineCharacter.create(line)); |
| cm.scrollToY(y - 0.5 * viewport); |
| cm.focus(); |
| } |
| } |
| |
| private boolean visible() { |
| int markT = getElement().getOffsetTop(); |
| int markE = markT + getElement().getOffsetHeight(); |
| |
| int viewT = viewport.getElement().getOffsetTop(); |
| int viewE = viewT + viewport.getElement().getOffsetHeight(); |
| |
| return (viewT <= markT && markT < viewE) // mark top within viewport |
| || (viewT <= markE && markE < viewE) // mark end within viewport |
| || (markT <= viewT && viewE <= markE); // mark contains viewport |
| } |
| |
| void remove() { |
| removeFromParent(); |
| comments.remove(this); |
| } |
| } |
| } |