<commit_before>/*
 * Copyright 2000-2005 JetBrains s.r.o.
 *
 * 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.intellij.ide.navigationToolbar;

import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.ui.LayeredIcon;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.ui.popup.list.DottedBorder;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * User: anna
 * Date: 03-Nov-2005
 */
public class HorizontalList extends JPanel {
  private ArrayList<Object> myModel = new ArrayList<Object>();
  private ArrayList<MyCompositeLabel> myList = new ArrayList<MyCompositeLabel>();

  private int myFirstIndex = 0;
  private int mySelectedIndex = -1;

  private JButton myLeftButton = new JButton(IconLoader.getIcon("/general/splitLeft.png"));
  private JButton myRightButton = new JButton(IconLoader.getIcon("/general/splitRight.png"));
  private JPanel myScrollablePanel = new JPanel(new GridBagLayout());
  private JScrollPane myScrollPane = ScrollPaneFactory.createScrollPane(myScrollablePanel);
  private int myPreferredWidth = 400;

  public HorizontalList() {
    this(ArrayUtil.EMPTY_OBJECT_ARRAY);
  }

  public HorizontalList(Object [] objects) {
    setLayout(new BorderLayout());
    setBackground(UIUtil.getListBackground());

    myScrollablePanel.setBackground(UIUtil.getListBackground());
    myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);

    add(myScrollPane, BorderLayout.CENTER);
    add(myLeftButton, BorderLayout.WEST);
    add(myRightButton, BorderLayout.EAST);

    myModel.addAll(Arrays.asList(objects));

    myLeftButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (mySelectedIndex == -1 && !myModel.isEmpty()){
          mySelectedIndex = myModel.size() - 1;
          paintBorder();
          myList.get(mySelectedIndex).requestFocusInWindow();
        }
        scrollToVisible(1);
        shiftFocusToVisible(1);
      }
    });
    myLeftButton.setBorder(null);

    myRightButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (mySelectedIndex == -1 && !myModel.isEmpty()){
          mySelectedIndex = myModel.size() - 1;
          paintBorder();
          myList.get(mySelectedIndex).requestFocusInWindow();
        }
        scrollToVisible(-1);
        shiftFocusToVisible(-1);
      }
    });
    myRightButton.setBorder(null);

    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        shiftFocus(-1);
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), JComponent.WHEN_FOCUSED);

    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(final ActionEvent e) {
        shiftFocus(1);
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), JComponent.WHEN_FOCUSED);


    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        shiftFocus(- mySelectedIndex);
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), JComponent.WHEN_FOCUSED);

    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(final ActionEvent e) {
        shiftFocus(getModelSize() - 1 - mySelectedIndex);
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), JComponent.WHEN_FOCUSED);


    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (mySelectedIndex != -1) {
          getCtrlClickHandler(mySelectedIndex).run();
        }
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), JComponent.WHEN_FOCUSED);

    final ActionListener dblClickAction = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (mySelectedIndex != -1) {
          final Runnable doubleClickHandler = getDoubleClickHandler(mySelectedIndex);
          if (doubleClickHandler != null) {
            doubleClickHandler.run();
          }
        }
      }
    };

    registerKeyboardAction(dblClickAction, KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0), JComponent.WHEN_FOCUSED);

    registerKeyboardAction(new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        clearBorder();
        mySelectedIndex = -1;
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);


    updateList();
  }

  public void select() {
    if (!myList.isEmpty()){
      clearBorder();
      mySelectedIndex = myModel.size() - 1;
      while (getWidth() < getChainWidth()){        
        scrollToVisible(1);
      }
      paintBorder();
      requestFocusInWindow();
    }
  }

  private int getChainWidth() {
    int result = 0;
    for (int i = myFirstIndex; i <= mySelectedIndex; i++){
      result += myList.get(i).getWidth();
    }
    result += 2 * getDotsLabel().getWidth();
    return result;
  }

  private void shiftFocusToVisible(int direction){
    while (mySelectedIndex != -1 && !myList.get(mySelectedIndex).isShowing()) {
      shiftFocus(direction);
    }
  }

  private void scrollToVisible(int direction){
    myFirstIndex = getIndexByMode(myFirstIndex + direction);
    paintComponent();
  }

  /**
   * @return was model modified
   */
  protected boolean updateModel(){
    return false;
  }


  public void addElement(Object object){
    myModel.add(object);
  }

  public void removeAllElements(){
    myModel.clear();
  }

  public int getSelectedIndex(){
    return mySelectedIndex;
  }

  public Object getSelectedValue(){
    return getElement(mySelectedIndex);
  }

  public MyCompositeLabel getItem(int index){
    if (index != -1 && index < myModel.size()){
      return myList.get(index);
    }
    return null;
  }

  public Object getElement(int index){
    if (index != -1 && index < myModel.size()){
      return myModel.get(index);
    }
    return null;
  }

  public int getModelSize(){
    return myModel.size();
  }


  public int getIndexOf(Object object){
    return myModel.indexOf(object);
  }

  /**
   * to be invoked by alarm
   */
  protected void updateList() {
    immediateUpdateList(updateModel());
  }

  protected void immediateUpdateList(boolean update){
    if (update){
      myFirstIndex = 0;
      mySelectedIndex = -1;
      myList.clear();
      int index = 0;
      for (final Object object : myModel) {
        //noinspection NonStaticInitializer
        final MyCompositeLabel label = new MyCompositeLabel();
        label.getLabel().addMouseListener(new MouseAdapter() {
          public void mouseExited(MouseEvent e) {
            if (!hasChildren(object)) return;
            label.getLabel().setIcon(wrapIcon(object, Color.gray));
            label.repaint();
          }

          public void mouseClicked(MouseEvent e) {
            if (!hasChildren(object)) return;
            final int selectedIndex = myModel.indexOf(object);
            final ListPopup popup = getPopup();
            if (mySelectedIndex == selectedIndex && popup != null){
              cancelPopup();
              if (isInsideIcon(e.getPoint(), object)) {
                label.getLabel().setIcon(wrapIcon(object, Color.black));
                label.getLabel().repaint();
              }
              return;
            }
            if (isInsideIcon(e.getPoint(), object)) {
              getCtrlClickHandler(selectedIndex).run();
              clearBorder();
              mySelectedIndex = selectedIndex;
              paintBorder();
              label.getLabel().setIcon(wrapIcon(object, Color.black));
              label.getLabel().repaint();
            }
          }
        });
        label.getLabel().addMouseMotionListener(new MouseMotionAdapter() {
          public void mouseMoved(MouseEvent e) {
            if (!hasChildren(object)) return;
            if (isInsideIcon(e.getPoint(), object)) {
              label.getLabel().setIcon(wrapIcon(object, Color.black));
            } else {
              label.getLabel().setIcon(wrapIcon(object, Color.gray));
            }
            label.repaint();
          }
        });
        label.setFont(UIUtil.getLabelFont());
        label.getLabel().setIcon(wrapIcon(object, Color.gray));
        label.getColoredComponent().append(getPresentableText(object), getTextAttributes(object, false));
        clearBorder(label.getColoredComponent());
        label.getLabel().setOpaque(false);
        label.getColoredComponent().setOpaque(true);
        label.setBackground(UIUtil.getListBackground());
        myList.add(label);
        installActions(index);
        index ++;
      }
    }
    paintComponent();
  }

  private boolean isInsideIcon(final Point point, final Object object) {
    //noinspection ConstantConditions
    final int height = getIcon(object).getIconHeight();
    return point.x > 0 && point.x < 10 && point.y > height / 2 - 4 && point.y < height/ 2 + 4;
  }

  protected SimpleTextAttributes getTextAttributes(final Object object, final boolean selected){
    return SimpleTextAttributes.REGULAR_ATTRIBUTES;
  }

  private static void clearBorder(SimpleColoredComponent label){<commit_msg>navigation bar optimizations<commit_after>/*
 * Copyright 2000-2005 JetBrains s.r.o.
 *
 * 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.intellij.ide.navigationToolbar;

import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.ui.LayeredIcon;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.ui.popup.list.DottedBorder;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * User: anna
 * Date: 03-Nov-2005
 */
public class HorizontalList extends JPanel {
  private ArrayList<Object> myModel = new ArrayList<Object>();
  private ArrayList<MyCompositeLabel> myList = new ArrayList<MyCompositeLabel>();

  private int myFirstIndex = 0;
  private int mySelectedIndex = -1;

  private JButton myLeftButton = new JButton(IconLoader.getIcon("/general/splitLeft.png"));
  private JButton myRightButton = new JButton(IconLoader.getIcon("/general/splitRight.png"));
  private JPanel myScrollablePanel = new JPanel(new GridBagLayout());
  private JScrollPane myScrollPane = ScrollPaneFactory.createScrollPane(myScrollablePanel);
  private int myPreferredWidth = 400;

  public HorizontalList() {
    this(ArrayUtil.EMPTY_OBJECT_ARRAY);
  }

  public HorizontalList(Object [] objects) {
    setLayout(new BorderLayout());
    setBackground(UIUtil.getListBackground());

    myScrollablePanel.setBackground(UIUtil.getListBackground());
    myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);

    add(myScrollPane, BorderLayout.CENTER);
    add(myLeftButton, BorderLayout.WEST);
    add(myRightButton, BorderLayout.EAST);

    myModel.addAll(Arrays.asList(objects));

    myLeftButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (mySelectedIndex == -1 && !myModel.isEmpty()){
          mySelectedIndex = myModel.size() - 1;
          paintBorder();
          myList.get(mySelectedIndex).requestFocusInWindow();
        }
        scrollToVisible(1);
        shiftFocusToVisible(1);
      }
    });
    myLeftButton.setBorder(null);

    myRightButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (mySelectedIndex == -1 && !myModel.isEmpty()){
          mySelectedIndex = myModel.size() - 1;
          paintBorder();
          myList.get(mySelectedIndex).requestFocusInWindow();
        }
        scrollToVisible(-1);
        shiftFocusToVisible(-1);
      }
    });
    myRightButton.setBorder(null);

    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        shiftFocus(-1);
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), JComponent.WHEN_FOCUSED);

    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(final ActionEvent e) {
        shiftFocus(1);
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), JComponent.WHEN_FOCUSED);


    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        shiftFocus(- mySelectedIndex);
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), JComponent.WHEN_FOCUSED);

    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(final ActionEvent e) {
        shiftFocus(getModelSize() - 1 - mySelectedIndex);
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), JComponent.WHEN_FOCUSED);


    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (mySelectedIndex != -1) {
          getCtrlClickHandler(mySelectedIndex).run();
        }
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), JComponent.WHEN_FOCUSED);

    final ActionListener dblClickAction = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (mySelectedIndex != -1) {
          final Runnable doubleClickHandler = getDoubleClickHandler(mySelectedIndex);
          if (doubleClickHandler != null) {
            doubleClickHandler.run();
          }
        }
      }
    };

    registerKeyboardAction(dblClickAction, KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0), JComponent.WHEN_FOCUSED);

    registerKeyboardAction(new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        clearBorder();
        mySelectedIndex = -1;
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);


    updateList();
  }

  public void select() {
    if (!myList.isEmpty()){
      clearBorder();
      mySelectedIndex = myModel.size() - 1;
      while (getWidth() < getChainWidth()){        
        scrollToVisible(1);
      }
      paintBorder();
      requestFocusInWindow();
    }
  }

  private int getChainWidth() {
    int result = 0;
    for (int i = myFirstIndex; i <= mySelectedIndex; i++){
      result += myList.get(i).getWidth();
    }
    result += 2 * getDotsLabel().getWidth();
    return result;
  }

  private void shiftFocusToVisible(int direction){
    while (mySelectedIndex != -1 && !myList.get(mySelectedIndex).isShowing()) {
      shiftFocus(direction);
    }
  }

  private void scrollToVisible(int direction){
    myFirstIndex = getIndexByMode(myFirstIndex + direction);
    paintComponent();
  }

  /**
   * @return was model modified
   */
  protected boolean updateModel(){
    return false;
  }


  public void addElement(Object object){
    myModel.add(object);
  }

  public void removeAllElements(){
    myModel.clear();
  }

  public int getSelectedIndex(){
    return mySelectedIndex;
  }

  public Object getSelectedValue(){
    return getElement(mySelectedIndex);
  }

  public MyCompositeLabel getItem(int index){
    if (index != -1 && index < myModel.size()){
      return myList.get(index);
    }
    return null;
  }

  public Object getElement(int index){
    if (index != -1 && index < myModel.size()){
      return myModel.get(index);
    }
    return null;
  }

  public int getModelSize(){
    return myModel.size();
  }


  public int getIndexOf(Object object){
    return myModel.indexOf(object);
  }

  /**
   * to be invoked by alarm
   */
  protected void updateList() {
    immediateUpdateList(updateModel());
  }

  protected void immediateUpdateList(boolean update){
    if (update){
      myFirstIndex = 0;
      mySelectedIndex = -1;
      myList.clear();
      int index = 0;
      for (final Object object : myModel) {
        //noinspection NonStaticInitializer
        final MyCompositeLabel label = new MyCompositeLabel();
        label.getLabel().addMouseListener(new MouseAdapter() {
          public void mouseExited(MouseEvent e) {
            if (!hasChildren(object)) return;
            label.getLabel().setIcon(wrapIcon(object, Color.gray));
            label.repaint();
          }

          public void mouseClicked(MouseEvent e) {
            if (!hasChildren(object)) return;
            final int selectedIndex = myModel.indexOf(object);
            final ListPopup popup = getPopup();
            if (mySelectedIndex == selectedIndex && popup != null){
              cancelPopup();
              if (isInsideIcon(e.getPoint(), object)) {
                label.getLabel().setIcon(wrapIcon(object, Color.black));
                label.getLabel().repaint();
              }
              return;
            }
            if (isInsideIcon(e.getPoint(), object)) {
              getCtrlClickHandler(selectedIndex).run();
              clearBorder();
              mySelectedIndex = selectedIndex;
              paintBorder();
              label.getLabel().setIcon(wrapIcon(object, Color.black));
              label.getLabel().repaint();
            }
          }
        });
        label.getLabel().addMouseMotionListener(new MouseMotionAdapter() {
          public void mouseMoved(MouseEvent e) {
            if (!hasChildren(object)) return;
            if (isInsideIcon(e.getPoint(), object)) {
              label.getLabel().setIcon(wrapIcon(object, Color.black));
            } else {
              label.getLabel().setIcon(wrapIcon(object, Color.gray));
            }
            label.repaint();
          }
        });
        label.setFont(UIUtil.getLabelFont());
        label.getLabel().setIcon(wrapIcon(object, Color.gray));
        label.getColoredComponent().append(getPresentableText(object), getTextAttributes(object, false));
        clearBorder(label.getColoredComponent());
        label.getLabel().setOpaque(false);
        label.getColoredComponent().setOpaque(true);
        label.setBackground(UIUtil.getListBackground());
        myList.add(label);
        installActions(index);
        index ++;
      }
      paintComponent();
    }
  }

  private boolean isInsideIcon(final Point point, final Object object) {
    //noinspection ConstantConditions
    final int height = getIcon(object).getIconHeight();
    return point.x > 0 && point.x < 10 && point.y > height / 2 - 4 && point.y < height/ 2 + 4;
  }

  protected SimpleTextAttributes getTextAttributes(final Object object, final boolean selected){
    return SimpleTextAttributes.REGULAR_ATTRIBUTES;
  }

  private static void clearBorder(SimpleColoredComponent label){
{'fraction_non_alphanumeric': 0.11140190607871216, 'fraction_numerical': 0.006123154412127796, 'mean_word_length': 3.0544544544544543, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6646000', 'n_tokens_mistral': 5914, 'n_tokens_neox': 5763, 'n_words': 1195}