// 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.dom.client.Element; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Timer; import java.util.PriorityQueue; import net.codemirror.lib.CodeMirror; /** * LineWidget attached to a CodeMirror container. * * <p>When a comment is placed on a line a CommentWidget is created on both sides. The group tracks * all comment boxes on that same line, and also includes an empty padding element to keep * subsequent lines vertically aligned. */ class SideBySideCommentGroup extends CommentGroup implements Comparable<SideBySideCommentGroup> { static void pair(SideBySideCommentGroup a, SideBySideCommentGroup b) { a.peers.add(b); b.peers.add(a); } private final Element padding; private final PriorityQueue<SideBySideCommentGroup> peers; SideBySideCommentGroup( SideBySideCommentManager manager, CodeMirror cm, DisplaySide side, int line) { super(manager, cm, side, line); padding = DOM.createDiv(); padding.setClassName(SideBySideTable.style.padding()); SideBySideChunkManager.focusOnClick(padding, cm.side()); getElement().appendChild(padding); peers = new PriorityQueue<>(); } SideBySideCommentGroup getPeer() { return peers.peek(); } @Override void remove(DraftBox box) { super.remove(box); if (getBoxCount() == 0 && peers.size() == 1 && peers.peek().peers.size() > 1) { SideBySideCommentGroup peer = peers.peek(); peer.peers.remove(this); detach(); if (peer.getBoxCount() == 0 && peer.peers.size() == 1 && peer.peers.peek().getBoxCount() == 0) { peer.detach(); } else { peer.resize(); } } else { resize(); } } @Override void init(DiffTable parent) { if (getLineWidget() == null) { attach(parent); } for (CommentGroup peer : peers) { if (peer.getLineWidget() == null) { peer.attach(parent); } } } @Override void handleRedraw() { getLineWidget() .onRedraw( () -> { if (canComputeHeight() && peers.peek().canComputeHeight()) { if (getResizeTimer() != null) { getResizeTimer().cancel(); setResizeTimer(null); } adjustPadding(SideBySideCommentGroup.this, peers.peek()); } else if (getResizeTimer() == null) { setResizeTimer( new Timer() { @Override public void run() { if (canComputeHeight() && peers.peek().canComputeHeight()) { cancel(); setResizeTimer(null); adjustPadding(SideBySideCommentGroup.this, peers.peek()); } } }); getResizeTimer().scheduleRepeating(5); } }); } @Override void resize() { if (getLineWidget() != null) { adjustPadding(this, peers.peek()); } } private int computeHeight() { if (getComments().isVisible()) { // Include margin-bottom: 5px from CSS class. return getComments().getOffsetHeight() + 5; } return 0; } private static void adjustPadding(SideBySideCommentGroup a, SideBySideCommentGroup b) { int apx = a.computeHeight(); int bpx = b.computeHeight(); for (SideBySideCommentGroup otherPeer : a.peers) { if (otherPeer != b) { bpx += otherPeer.computeHeight(); } } for (SideBySideCommentGroup otherPeer : b.peers) { if (otherPeer != a) { apx += otherPeer.computeHeight(); } } int h = Math.max(apx, bpx); a.padding.getStyle().setHeight(Math.max(0, h - apx), Unit.PX); b.padding.getStyle().setHeight(Math.max(0, h - bpx), Unit.PX); a.getLineWidget().changed(); b.getLineWidget().changed(); a.updateSelection(); b.updateSelection(); } @Override public int compareTo(SideBySideCommentGroup o) { if (side == o.side) { return line - o.line; } throw new IllegalStateException("Cannot compare SideBySideCommentGroup with different sides"); } }