text: /*
    ==============================================================================
  
     This file is part of the JUCE library.
     Copyright (c) 2017 - ROLI Ltd.
  
     JUCE is an open source library subject to commercial or open-source
     licensing.
  
     By using JUCE, you agree to the terms of both the JUCE 5 End-User License
     Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
     27th April 2017).
  
     End User License Agreement: www.juce.com/juce-5-licence
     Privacy Policy: www.juce.com/juce-5-privacy-policy
  
     Or: You may also use this code under the terms of the GPL v3 (see
     www.gnu.org/licenses).
  
     JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
     EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
     DISCLAIMED.
  
    ==============================================================================
  */
  
  namespace juce
  {
  
  class ListBox::RowComponent  : public Component,
                                 public TooltipClient
  {
  public:
      RowComponent (ListBox& lb) : owner (lb) {}
  
      void paint (Graphics& g) override
      {
          if (auto* m = owner.getModel())
              m->paintListBoxItem (row, g, getWidth(), getHeight(), selected);
      }
  
      void update (const int newRow, const bool nowSelected)
      {
          if (row != newRow || selected != nowSelected)
          {
              repaint();
              row = newRow;
              selected = nowSelected;
          }
  
          if (auto* m = owner.getModel())
          {
              setMouseCursor (m->getMouseCursorForRow (row));
  
              customComponent.reset (m->refreshComponentForRow (newRow, nowSelected, customComponent.release()));
  
              if (customComponent != nullptr)
              {
                  addAndMakeVisible (customComponent.get());
                  customComponent->setBounds (getLocalBounds());
              }
          }
      }
  
      void performSelection (const MouseEvent& e, bool isMouseUp)
      {
          owner.selectRowsBasedOnModifierKeys (row, e.mods, isMouseUp);
  
          if (auto* m = owner.getModel())
              m->listBoxItemClicked (row, e);
      }
  
      bool isInDragToScrollViewport() const noexcept
      {
          if (auto* vp = owner.getViewport())
              return vp->isScrollOnDragEnabled() && (vp->canScrollVertically() || vp->canScrollHorizontally());
  
          return false;
      }
  
      void mouseDown (const MouseEvent& e) override
      {
          isDragging = false;
          isDraggingToScroll = false;
          selectRowOnMouseUp = false;
  
          if (isEnabled())
          {
              if (owner.selectOnMouseDown && ! (selected || isInDragToScrollViewport()))
                  performSelection (e, false);
              else
                  selectRowOnMouseUp = true;
          }
      }
  
      void mouseUp (const MouseEvent& e) override
      {
          if (isEnabled() && selectRowOnMouseUp && ! (isDragging || isDraggingToScroll))
              performSelection (e, true);
      }
  
      void mouseDoubleClick (const MouseEvent& e) override
      {
          if (isEnabled())
              if (auto* m = owner.getModel())
                  m->listBoxItemDoubleClicked (row, e);
      }
  
      void mouseDrag (const MouseEvent& e) override
      {
          if (auto* m = owner.getModel())
          {
              if (isEnabled() && e.mouseWasDraggedSinceMouseDown() && ! isDragging)
              {
                  SparseSet<int> rowsToDrag;
  
                  if (owner.selectOnMouseDown || owner.isRowSelected (row))
                      rowsToDrag = owner.getSelectedRows();
                  else
                      rowsToDrag.addRange (Range<int>::withStartAndLength (row, 1));
  
                  if (rowsToDrag.size() > 0)
                  {
                      auto dragDescription = m->getDragSourceDescription (rowsToDrag);
  
                      if (! (dragDescription.isVoid() || (dragDescription.isString() && dragDescription.toString().isEmpty())))
                      {
                          isDragging = true;
                          owner.startDragAndDrop (e, rowsToDrag, dragDescription, true);
                      }
                  }
              }
          }
  
          if (! isDraggingToScroll)
              if (auto* vp = owner.getViewport())
                  isDraggingToScroll = vp->isCurrentlyScrollingOnDrag();
      }
  
      void resized() override
      {
          if (customComponent != nullptr)
              customComponent->setBounds (getLocalBounds());
      }
  
      String getTooltip() override
      {
          if (auto* m = owner.getModel())
              return m->getTooltipForRow (row);
  
          return {};
      }
  
      ListBox& owner;
      std::unique_ptr<Component> customComponent;
      int row = -1;
      bool selected = false, isDragging = false, isDraggingToScroll = false, selectRowOnMouseUp = false;
  
      JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RowComponent)
  };
  
  
  //==============================================================================
  class ListBox::ListViewport  : public Viewport
  {
  public:
      ListViewport (ListBox& lb)  : owner (lb)
      {
          setWantsKeyboardFocus (false);
  
          auto content = new Component();
          setViewedComponent (content);
          content->setWantsKeyboardFocus (false);
      }
  
      RowComponent* getComponentForRow (const int row) const noexcept
      {
          return rows [row % jmax (1, rows.size())];
      }
  
      RowComponent* getComponentForRowIfOnscreen (const int row) const noexcept
      {
          return (row >= firstIndex && row < firstIndex + rows.size())
                   ? getComponentForRow (row) : nullptr;
      }
  
      int getRowNumberOfComponent (Component* const rowComponent) const noexcept
      {
          const int index = getViewedComponent()->getIndexOfChildComponent (rowComponent);
          const int num = rows.size();
  
          for (int i = num; --i >= 0;)
              if (((firstIndex + i) % jmax (1, num)) == index)
                  return firstIndex + i;
  
          return -1;
      }
  
      void visibleAreaChanged (const Rectangle<int>&) override
      {
          updateVisibleArea (true);
  
          if (auto* m = owner.getModel())
              m->listWasScrolled();
      }
  
      void updateVisibleArea (const bool makeSureItUpdatesContent)
      {
          hasUpdated = false;
  
          auto& content = *getViewedComponent();
          auto newX = content.getX();
          auto newY = content.getY();
          auto newW = jmax (owner.minimumRowWidth, getMaximumVisibleWidth());
          auto newH = owner.totalItems * owner.getRowHeight();
  
          if (newY + newH < getMaximumVisibleHeight() && newH > getMaximumVisibleHeight())
              newY = getMaximumVisibleHeight() - newH;
  
          content.setBounds (newX, newY, newW, newH);
  
          if (makeSureItUpdatesContent && ! hasUpdated)
              updateContents();
      }
  
      void updateContents()
      {
          hasUpdated = true;
          auto rowH = owner.getRowHeight();
          auto& content = *getViewedComponent();
  
          if (rowH > 0)
          {
              auto y = getViewPositionY();
              auto w = content.getWidth();
  
              const int numNeeded = 2 + getMaximumVisibleHeight() / rowH;
              rows.removeRange (numNeeded, rows.size());
  
              while (numNeeded > rows.size())
              {
                  auto newRow = new RowComponent (owner);
                  rows.add (newRow);
                  content.addAndMakeVisible (newRow);
              }
  
              firstIndex = y / rowH;
              firstWholeIndex = (y + rowH - 1) / rowH;
              lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowH;
  
              for (int i = 0; i < numNeeded; ++i)
              {
                  const int row = i + firstIndex;
  
                  if (auto* rowComp = getComponentForRow (row))
                  {
                      rowComp->setBounds (0, row * rowH, w, rowH);
                      rowComp->update (row, owner.isRowSelected (row));
                  }
              }
          }
  
          if (owner.headerComponent != nullptr)
              owner.headerComponent->setBounds (owner.outlineThickness + content.getX(),
                                                owner.outlineThickness,
                                                jmax (owner.getWidth() - owner.outlineThickness * 2,
                                                      content.getWidth()),
                                                owner.headerComponent->getHeight());
      }
  
      void selectRow (const int row, const int rowH, const bool dontScroll,
                      const int lastSelectedRow, const int totalRows, const bool isMouseClick)
      {
          hasUpdated = false;
  
          if (row < firstWholeIndex && ! dontScroll)
          {
              setViewPosition (getViewPositionX(), row * rowH);
          }
          else if (row >= lastWholeIndex && ! dontScroll)
          {
              const int rowsOnScreen = lastWholeIndex - firstWholeIndex;
  
              if (row >= lastSelectedRow + rowsOnScreen
                   && rowsOnScreen < totalRows - 1
                   && ! isMouseClick)
              {
                  setViewPosition (getViewPositionX(),
                                   jlimit (0, jmax (0, totalRows - rowsOnScreen), row) * rowH);
              }
              else
              {
                  setViewPosition (getViewPositionX(),
                                   jmax (0, (row  + 1) * rowH - getMaximumVisibleHeight()));
              }
          }
  
          if (! hasUpdated)
              updateContents();
      }
  
      void scrollToEnsureRowIsOnscreen (const int row, const int rowH)
      {
          if (row < firstWholeIndex)
          {
              setViewPosition (getViewPositionX(), row * rowH);
          }
          else if (row >= lastWholeIndex)
          {
              setViewPosition (getViewPositionX(),
                               jmax (0, (row  + 1) * rowH - getMaximumVisibleHeight()));
          }
      }
  
      void paint (Graphics& g) override
      {
          if (isOpaque())
              g.fillAll (owner.findColour (ListBox::backgroundColourId));
      }
  
      bool keyPressed (const KeyPress& key) override
      {
          if (Viewport::respondsToKey (key))
          {
              const int allowableMods = owner.multipleSelection ? ModifierKeys::shiftModifier : 0;
  
              if ((key.getModifiers().getRawFlags() & ~allowableMods) == 0)
              {
                  // we want to avoid these keypresses going to the viewport, and instead allow
                  // them to pass up to our listbox..
                  return false;
              }
          }
  
          return Viewport::keyPressed (key);
      }
  
  private:
      ListBox& owner;
      OwnedArray<RowComponent> rows;
      int firstIndex = 0, firstWholeIndex = 0, lastWholeIndex = 0;
      bool hasUpdated = false;
  
      JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListViewport)
  };
  
  //==============================================================================
  struct ListBoxMouseMoveSelector  : public MouseListener
  {
      ListBoxMouseMoveSelector (ListBox& lb) : owner (lb)
      {
          owner.addMouseListener (this, true);
      }
  
      ~ListBoxMouseMoveSelector()
      {
          owner.removeMouseListener (this);
      }
  
      void mouseMove (const MouseEvent& e) override
      {
          auto pos = e.getEventRelativeTo (&owner).position.toInt();
          owner.selectRow (owner.getRowContainingPosition (pos.x, pos.y), true);
      }
  
      void mouseExit (const MouseEvent& e) override
      {
          mouseMove (e);
      }
  
      ListBox& owner;
      JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListBoxMouseMoveSelector)
  };
  
  
  //==============================================================================
  ListBox::ListBox (const String& name, ListBoxModel* const m)
      : Component (name), model (m)
  {
      viewport.reset (new ListViewport (*this));
      addAndMakeVisible (viewport.get());
  
      ListBox::setWantsKeyboardFocus (true);
      ListBox::colourChanged();
  }
  
  ListBox::~ListBox()
  {
      headerComponent.reset();
      viewport.reset();
  }
  
  void ListBox::setModel (ListBoxModel* const newModel)
  {
      if (model != newModel)
      {
          model = newModel;
          repaint();
          updateContent();
      }
  }
  
  void ListBox::setMultipleSelectionEnabled (bool b) noexcept         { multipleSelection = b; }
  void ListBox::setClickingTogglesRowSelection (bool b) noexcept      { alwaysFlipSelection = b; }
  void ListBox::setRowSelectedOnMouseDown (bool b) noexcept           { selectOnMouseDown = b; }
  
  void ListBox::setMouseMoveSelectsRows (bool b)
  {
      if (b)
      {
          if (mouseMoveSelector == nullptr)
              mouseMoveSelector.reset (new ListBoxMouseMoveSelector (*this));
      }
      else
      {
          mouseMoveSelector.reset();
      }
  }
  
  //==============================================================================
  void ListBox::paint (Graphics& g)
  {
      if (! hasDoneInitialUpdate)
          updateContent();
  
      g.fillAll (findColour (backgroundColourId));
  }
  
  void ListBox::paintOverChildren (Graphics& g)
  {
      if (outlineThickness > 0)
      {
          g.setColour (findColour (outlineColourId));
          g.drawRect (getLocalBounds(), outlineThickness);
      }
  }
  
  void ListBox::resized()
  {
      viewport->setBoundsInset (BorderSize<int> (outlineThickness + (headerComponent != nullptr ? headerComponent->getHeight() : 0),
                                                 outlineThickness, outlineThickness, outlineThickness));
  
      viewport->setSingleStepSizes (20, getRowHeight());
  
      viewport->updateVisibleArea (false);
  }
  
  void ListBox::visibilityChanged()
  {
      viewport->updateVisibleArea (true);
  }
  
  Viewport* ListBox::getViewport() const noexcept
  {
      return viewport.get();
  }
  
  //==============================================================================
  void ListBox::updateContent()
  {
      hasDoneInitialUpdate = true;
      totalItems = (model != nullptr) ? model->getNumRows() : 0;
  
      bool selectionChanged = false;
  
      if (selected.size() > 0 && selected [selected.size() - 1] >= totalItems)
      {
          selected.removeRange ({ totalItems, std::numeric_limits<int>::max() });
          lastRowSelected = getSelectedRow (0);
          selectionChanged = true;
      }
  
      viewport->updateVisibleArea (isVisible());
      viewport->resized();
  
      if (selectionChanged && model != nullptr)
          model->selectedRowsChanged (lastRowSelected);
  }
  
  //==============================================================================
  void ListBox::selectRow (int row, bool dontScroll, bool deselectOthersFirst)
  {
      selectRowInternal (row, dontScroll, deselectOthersFirst, false);
  }
  
  void ListBox::selectRowInternal (const int row,
                                   bool dontScroll,
                                   bool deselectOthersFirst,
                                   bool isMouseClick)
  {
      if (! multipleSelection)
          deselectOthersFirst = true;
  
      if ((! isRowSelected (row))
           || (deselectOthersFirst && getNumSelectedRows() > 1))
      {
          if (isPositiveAndBelow (row, totalItems))
          {
              if (deselectOthersFirst)
                  selected.clear();
  
              selected.addRange ({ row, row + 1 });
  
              if (getHeight() == 0 || getWidth() == 0)
                  dontScroll = true;
  
              viewport->selectRow (row, getRowHeight(), dontScroll,
                                   lastRowSelected, totalItems, isMouseClick);
  
              lastRowSelected = row;
              model->selectedRowsChanged (row);
          }
          else
          {
              if (deselectOthersFirst)
                  deselectAllRows();
          }
      }
  }
  
  void ListBox::deselectRow (const int row)
  {
      if (selected.contains (row))
      {
          selected.removeRange ({ row, row + 1 });
  
          if (row == lastRowSelected)
              lastRowSelected = getSelectedRow (0);
  
          viewport->updateContents();
          model->selectedRowsChanged (lastRowSelected);
      }
  }
  
  void ListBox::setSelectedRows (const SparseSet<int>& setOfRowsToBeSelected,
                                 const NotificationType sendNotificationEventToModel)
  {
      selected = setOfRowsToBeSelected;
      selected.removeRange ({ totalItems, std::numeric_limits<int>::max() });
  
      if (! isRowSelected (lastRowSelected))
          lastRowSelected = getSelectedRow (0);
  
      viewport->updateContents();
  
      if (model != nullptr && sendNotificationEventToModel == sendNotification)
          model->selectedRowsChanged (lastRowSelected);
  }
  
  SparseSet<int> ListBox::getSelectedRows() const
  {
      return selected;
  }
  
  void ListBox::selectRangeOfRows (int firstRow, int lastRow, bool dontScrollToShowThisRange)
  {
      if (multipleSelection && (firstRow != lastRow))
      {
          const int numRows = totalItems - 1;
          firstRow = jlimit (0, jmax (0, numRows), firstRow);
          lastRow  = jlimit (0, jmax (0, numRows), lastRow);
  
          selected.addRange ({ jmin (firstRow, lastRow),
                               jmax (firstRow, lastRow) + 1 });
  
          selected.removeRange ({ lastRow, lastRow + 1 });
      }
  
      selectRowInternal (lastRow, dontScrollToShowThisRange, false, true);
  }
  
  void ListBox::flipRowSelection (const int row)
  {
      if (isRowSelected (row))
          deselectRow (row);
      else
          selectRowInternal (row, false, false, true);
  }
  
  void ListBox::deselectAllRows()
  {
      if (! selected.isEmpty())
      {
          selected.clear();
          lastRowSelected = -1;
  
          viewport->updateContents();
  
          if (model != nullptr)
              model->selectedRowsChanged (lastRowSelected);
      }
  }
  
  void ListBox::selectRowsBasedOnModifierKeys (const int row,
                                               ModifierKeys mods,
                                               const bool isMouseUpEvent)
  {
      if (multipleSelection && (mods.isCommandDown() || alwaysFlipSelection))
      {
          flipRowSelection (row);
      }
      else if (multipleSelection && mods.isShiftDown() && lastRowSelected >= 0)
      {
          selectRangeOfRows (lastRowSelected, row);
      }
      else if ((! mods.isPopupMenu()) || ! isRowSelected (row))
      {
          selectRowInternal (row, false, ! (multipleSelection && (! isMouseUpEvent) && isRowSelected (row)), true);
      }
  }
  
  int ListBox::getNumSelectedRows() const
  {
      return selected.size();
  }
  
  int ListBox::getSelectedRow (const int index) const
  {
      return (isPositiveAndBelow (index, selected.size()))
                  ? selected [index] : -1;
  }
  
  bool ListBox::isRowSelected (const int row) const
  {
      return selected.contains (row);
  }
  
  int ListBox::getLastRowSelected() const
  {
      return isRowSelected (lastRowSelected) ? lastRowSelected : -1;
  }
  
  //==============================================================================
  int ListBox::getRowContainingPosition (const int x, const int y) const noexcept
  {
      if (isPositiveAndBelow (x, getWidth()))
      {
          const int row = (viewport->getViewPositionY() + y - viewport->getY()) / rowHeight;
  
          if (isPositiveAndBelow (row, totalItems))
              return row;
      }
  
      return -1;
  }
  
  int ListBox::getInsertionIndexForPosition (const int x, const int y) const noexcept
  {
      if (isPositiveAndBelow (x, getWidth()))
          return jlimit (0, totalItems, (viewport->getViewPositionY() + y + rowHeight / 2 - viewport->getY()) / rowHeight);
  
      return -1;
  }
  
  Component* ListBox::getComponentForRowNumber (const int row) const noexcept
  {
      if (auto* listRowComp = viewport->getComponentForRowIfOnscreen (row))
          return listRowComp->customComponent.get();
  
      return nullptr;
  }
  
  int ListBox::getRowNumberOfComponent (Component* const rowComponent) const noexcept
  {
      return viewport->getRowNumberOfComponent (rowComponent);
  }
  
  Rectangle<int> ListBox::getRowPosition (int rowNumber, bool relativeToComponentTopLeft) const noexcept
  {
      auto y = viewport->getY() + rowHeight * rowNumber;
  
      if (relativeToComponentTopLeft)
          y -= viewport->getViewPositionY();
  
      return { viewport->getX(), y,
               viewport->getViewedComponent()->getWidth(), rowHeight };
  }
  
  void ListBox::setVerticalPosition (const double proportion)
  {
      auto offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
  
      viewport->setViewPosition (viewport->getViewPositionX(),
                                 jmax (0, roundToInt (proportion * offscreen)));
  }
  
  double ListBox::getVerticalPosition() const
  {
      auto offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
  
      return offscreen > 0 ? viewport->getViewPositionY() / (double) offscreen
                           : 0;
  }
  
  int ListBox::getVisibleRowWidth() const noexcept
  {
      return viewport->getViewWidth();
  }
  
  void ListBox::scrollToEnsureRowIsOnscreen (const int row)
  {
      viewport->scrollToEnsureRowIsOnscreen (row, getRowHeight());
  }
  
  //==============================================================================
  bool ListBox::keyPressed (const KeyPress& key)
  {
      const int numVisibleRows = viewport->getHeight() / getRowHeight();
  
      const bool multiple = multipleSelection
                              && lastRowSelected >= 0
                              && key.getModifiers().isShiftDown();
  
      if (key.isKeyCode (KeyPress::upKey))
      {
          if (multiple)
              selectRangeOfRows (lastRowSelected, lastRowSelected - 1);
          else
              selectRow (jmax (0, lastRowSelected - 1));
      }
      else if (key.isKeyCode (KeyPress::downKey))
      {
          if (multiple)
              selectRangeOfRows (lastRowSelected, lastRowSelected + 1);
          else
              selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected) + 1));
      }
      else if (key.isKeyCode (KeyPress::pageUpKey))
      {
          if (multiple)
              selectRangeOfRows (lastRowSelected, lastRowSelected - numVisibleRows);
          else
              selectRow (jmax (0, jmax (0, lastRowSelected) - numVisibleRows));
      }
      else if (key.isKeyCode (KeyPress::pageDownKey))
      {
          if (multiple)
              selectRangeOfRows (lastRowSelected, lastRowSelected + numVisibleRows);
          else
              selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected) + numVisibleRows));
      }
      else if (key.isKeyCode (KeyPress::homeKey))
      {
          if (multiple)
              selectRangeOfRows (lastRowSelected, 0);
          else
              selectRow (0);
      }
      else if (key.isKeyCode (KeyPress::endKey))
      {
          if (multiple)
              selectRangeOfRows (lastRowSelected, totalItems - 1);
          else
              selectRow (totalItems - 1);
      }
      else if (key.isKeyCode (KeyPress::returnKey) && isRowSelected (lastRowSelected))
      {
          if (model != nullptr)
              model->returnKeyPressed (lastRowSelected);
      }
      else if ((key.isKeyCode (KeyPress::deleteKey) || key.isKeyCode (KeyPress::backspaceKey))
                 && isRowSelected (lastRowSelected))
      {
          if (model != nullptr)
              model->deleteKeyPressed (lastRowSelected);
      }
      else if (multipleSelection && key == KeyPress ('a', ModifierKeys::commandModifier, 0))
      {
          selectRangeOfRows (0, std::numeric_limits<int>::max());
      }
      else
      {
          return false;
      }
  
      return true;
  }
  
  bool ListBox::keyStateChanged (const bool isKeyDown)
  {
      return isKeyDown
              && (KeyPress::isKeyCurrentlyDown (KeyPress::upKey)
                  || KeyPress::isKeyCurrentlyDown (KeyPress::pageUpKey)
                  || KeyPress::isKeyCurrentlyDown (KeyPress::downKey)
                  || KeyPress::isKeyCurrentlyDown (KeyPress::pageDownKey)
                  || KeyPress::isKeyCurrentlyDown (KeyPress::homeKey)
                  || KeyPress::isKeyCurrentlyDown (KeyPress::endKey)
                  || KeyPress::isKeyCurrentlyDown (KeyPress::returnKey));
  }
  
  void ListBox::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
  {
      bool eventWasUsed = false;
  
      if (wheel.deltaX != 0.0f && getHorizontalScrollBar().isVisible())
      {
          eventWasUsed = true;
          getHorizontalScrollBar().mouseWheelMove (e, wheel);
      }
  
      if (wheel.deltaY != 0.0f && getVerticalScrollBar().isVisible())
      {
          eventWasUsed = true;
          getVerticalScrollBar().mouseWheelMove (e, wheel);
      }
  
      if (! eventWasUsed)
          Component::mouseWheelMove (e, wheel);
  }
  
  void ListBox::mouseUp (const MouseEvent& e)
  {
      if (e.mouseWasClicked() && model != nullptr)
          model->backgroundClicked (e);
  }
  
  //==============================================================================
  void ListBox::setRowHeight (const int newHeight)
  {
      rowHeight = jmax (1, newHeight);
      viewport->setSingleStepSizes (20, rowHeight);
      updateContent();
  }
  
  int ListBox::getNumRowsOnScreen() const noexcept
  {
      return viewport->getMaximumVisibleHeight() / rowHeight;
  }
  
  void ListBox::setMinimumContentWidth (const int newMinimumWidth)
  {
      minimumRowWidth = newMinimumWidth;
      updateContent();
  }
  
  int ListBox::getVisibleContentWidth() const noexcept            { return viewport->getMaximumVisibleWidth(); }
  
  ScrollBar& ListBox::getVerticalScrollBar() const noexcept       { return viewport->getVerticalScrollBar(); }
  ScrollBar& ListBox::getHorizontalScrollBar() const noexcept     { return viewport->getHorizontalScrollBar(); }
  
  void ListBox::colourChanged()
  {
      setOpaque (findColour (backgroundColourId).isOpaque());
      viewport->setOpaque (isOpaque());
      repaint();
  }
  
  void ListBox::parentHierarchyChanged()
  {
      colourChanged();
  }
  
  void ListBox::setOutlineThickness (int newThickness)
  {
      outlineThickness = newThickness;
      resized();
  }
  
  void ListBox::setHeaderComponent (Component* newHeaderComponent)
  {
      if (headerComponent.get() != newHeaderComponent)
      {
          headerComponent.reset (newHeaderComponent);
          addAndMakeVisible (newHeaderComponent);
          ListBox::resized();
      }
  }
  
  void ListBox::repaintRow (const int rowNumber) noexcept
  {
      repaint (getRowPosition (rowNumber, true));
  }
  
  Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, int& imageY)
  {
      Rectangle<int> imageArea;
      auto firstRow = getRowContainingPosition (0, viewport->getY());
  
      for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
      {
          if (rows.contains (firstRow + i))
          {
              if (auto* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i))
              {
                  auto pos = getLocalPoint (rowComp, Point<int>());
  
                  imageArea = imageArea.getUnion ({ pos.x, pos.y, rowComp->getWidth(), rowComp->getHeight() });
              }
          }
      }
  
      imageArea = imageArea.getIntersection (getLocalBounds());
      imageX = imageArea.getX();
      imageY = imageArea.getY();
  
      Image snapshot (Image::ARGB, imageArea.getWidth(), imageArea.getHeight(), true);
  
      for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
      {
          if (rows.contains (firstRow + i))
          {
              if (auto* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i))
              {
                  Graphics g (snapshot);
                  g.setOrigin (getLocalPoint (rowComp, Point<int>()) - imageArea.getPosition());
  
                  if (g.reduceClipRegion (rowComp->getLocalBounds()))
                  {
                      g.beginTransparencyLayer (0.6f);
                      rowComp->paintEntireComponent (g, false);
                      g.endTransparencyLayer();
                  }
              }
          }
      }
  
      return snapshot;
  }
  
  void ListBox::startDragAndDrop (const MouseEvent& e, const SparseSet<int>& rowsToDrag, const var& dragDescription, bool allowDraggingToOtherWindows)
  {
      if (auto* dragContainer = DragAndDropContainer::findParentDragContainerFor (this))
      {
          int x, y;
          auto dragImage = createSnapshotOfRows (rowsToDrag, x, y);
  
          auto p = Point<int> (x, y) - e.getEventRelativeTo (this).position.toInt();
          dragContainer->startDragging (dragDescription, this, dragImage, allowDraggingToOtherWindows, &p, &e.source);
      }
      else
      {
          // to be able to do a drag-and-drop operation, the listbox needs to
          // be inside a component which is also a DragAndDropContainer.
          jassertfalse;
      }
  }
  
  //==============================================================================
  Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingComponentToUpdate)
  {
      ignoreUnused (existingComponentToUpdate);
      jassert (existingComponentToUpdate == nullptr); // indicates a failure in the code that recycles the components
      return nullptr;
  }
  
  void ListBoxModel::listBoxItemClicked (int, const MouseEvent&) {}
  void ListBoxModel::listBoxItemDoubleClicked (int, const MouseEvent&) {}
  void ListBoxModel::backgroundClicked (const MouseEvent&) {}
  void ListBoxModel::selectedRowsChanged (int) {}
  void ListBoxModel::deleteKeyPressed (int) {}
  void ListBoxModel::returnKeyPressed (int) {}
  void ListBoxModel::listWasScrolled() {}
  var ListBoxModel::getDragSourceDescription (const SparseSet<int>&)      { return {}; }
  String ListBoxModel::getTooltipForRow (int)                             { return {}; }
  MouseCursor ListBoxModel::getMouseCursorForRow (int)                    { return MouseCursor::NormalCursor; }
  
  } // namespace juce
  