958 lines
29 KiB
C++
958 lines
29 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
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
|