/*
 * Copyright (C) 2017 Google Inc.
 *
 * 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.gapid.util;

import static com.google.gapid.util.GeoUtils.bottom;
import static java.util.Collections.emptySet;

import com.google.common.collect.Sets;

import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import java.util.Set;

/**
 * Utilities for dealing with {@link Tree Trees} and {@link TreeItem TreeItems}.
 */
public class Trees {
  private Trees() {
  }

  /**
   * @return the {@link TreeItem TreeItems} that are currently visible in the tree.
   */
  public static Set<TreeItem> getVisibleItems(Tree tree) {
    TreeItem top = tree.getTopItem();
    if (top == null) {
      // Work around bug where getTopItem() returns null when scrolling
      // up past the top item (elastic scroll).
      return (tree.getItemCount() != 0) ? null : emptySet();
    }

    int treeBottom = bottom(tree.getClientArea());
    Set<TreeItem> visible = Sets.newIdentityHashSet();
    if (!getVisibleItems(top, treeBottom, visible)) {
      do {
        top = getVisibleSiblings(top, treeBottom, visible);
      } while (top != null);
    }
    return visible;
  }

  /**
   * Adds the given item and all visible descendants into the given set.
   * @return whether the bottom has been reached.
   */
  private static boolean getVisibleItems(TreeItem item, int treeBottom, Set<TreeItem> visible) {
    visible.add(item);
    if (bottom(item.getBounds()) > treeBottom) {
      return true;
    }

    if (item.getExpanded()) {
      for (TreeItem child : item.getItems()) {
        if (getVisibleItems(child, treeBottom, visible)) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Adds all visible siblings (and their visible descendants) into the given set.
   * @return the next parent or {@code null} if the bottom has been reached.
   */
  private static TreeItem getVisibleSiblings(TreeItem item, int treeBottom, Set<TreeItem> visible) {
    TreeItem parent = item.getParentItem();
    TreeItem[] siblings = (parent == null) ? item.getParent().getItems() : parent.getItems();
    int idx = 0;
    for (; idx < siblings.length && siblings[idx] != item; idx++) {
      // Do nothing.
    }
    for (idx++; idx < siblings.length; idx++) {
      if (getVisibleItems(siblings[idx], treeBottom, visible)) {
        return null;
      }
    }
    return parent;
  }
}

@@ -765,16 +765,59 @@
 .Sets;%0A%0A
+import org.eclipse.swt.graphics.Rectangle;%0A
 import o
@@ -845,16 +845,16 @@
 s.Tree;%0A
-
 import o
@@ -1041,24 +1041,1096 @@
 es() %7B%0A  %7D%0A%0A
+  public static TreeItem getTopItem(Tree tree) %7B%0A    if (!OS.isLinux) %7B%0A      return tree.getTopItem();%0A    %7D%0A%0A    // Dirty work around for https://bugs.eclipse.org/bugs/show_bug.cgi?id=563232.%0A    // Note this will often return an item that is above the visible one, as it only considers%0A    // root items. The actual correct top item may be a child of the returned item.%0A    int count = tree.getItemCount();%0A    if (count == 0) %7B%0A      return null;%0A    %7D%0A%0A    TreeItem item = tree.getItem(0);%0A    if (count %3C= 2) %7B%0A      return item;%0A    %7D%0A    Rectangle bounds = item.getBounds();%0A    if (bounds.y %3E= 0 %7C%7C (bounds.y %3C= 0 && bounds.y + bounds.height %3E= 0)) %7B%0A      return item;%0A    %7D%0A%0A    int start = 0, end = count - 1;%0A    while (end - start %3E 1) %7B%0A      int mid = (start + end) / 2;%0A      item = tree.getItem(mid);%0A      bounds = item.getBounds();%0A      if (bounds.y %3C= 0 && bounds.y + bounds.height %3E= 0) %7B%0A        return item;%0A      %7D else if (bounds.y %3E 0) %7B%0A        end = mid;%0A      %7D else %7B%0A        start = mid;%0A      %7D%0A    %7D%0A    return tree.getItem(start);%0A  %7D%0A%0A
   /**%0A   * @
@@ -2291,21 +2291,16 @@
 m top = 
-tree.
 getTopIt
@@ -2298,24 +2298,28 @@
  getTopItem(
+tree
 );%0A    if (t

Fix tree visibility tracking on Linux.