fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
901
modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp
Normal file
901
modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp
Normal file
@ -0,0 +1,901 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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 TableHeaderComponent::DragOverlayComp : public Component
|
||||
{
|
||||
public:
|
||||
DragOverlayComp (const Image& i) : image (i)
|
||||
{
|
||||
image.duplicateIfShared();
|
||||
image.multiplyAllAlphas (0.8f);
|
||||
setAlwaysOnTop (true);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.drawImageAt (image, 0, 0);
|
||||
}
|
||||
|
||||
Image image;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (DragOverlayComp)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
TableHeaderComponent::TableHeaderComponent()
|
||||
{
|
||||
}
|
||||
|
||||
TableHeaderComponent::~TableHeaderComponent()
|
||||
{
|
||||
dragOverlayComp.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableHeaderComponent::setPopupMenuActive (bool hasMenu)
|
||||
{
|
||||
menuActive = hasMenu;
|
||||
}
|
||||
|
||||
bool TableHeaderComponent::isPopupMenuActive() const { return menuActive; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
int TableHeaderComponent::getNumColumns (const bool onlyCountVisibleColumns) const
|
||||
{
|
||||
if (onlyCountVisibleColumns)
|
||||
{
|
||||
int num = 0;
|
||||
|
||||
for (auto* c : columns)
|
||||
if (c->isVisible())
|
||||
++num;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
return columns.size();
|
||||
}
|
||||
|
||||
String TableHeaderComponent::getColumnName (const int columnId) const
|
||||
{
|
||||
if (auto* ci = getInfoForId (columnId))
|
||||
return ci->name;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void TableHeaderComponent::setColumnName (const int columnId, const String& newName)
|
||||
{
|
||||
if (auto* ci = getInfoForId (columnId))
|
||||
{
|
||||
if (ci->name != newName)
|
||||
{
|
||||
ci->name = newName;
|
||||
sendColumnsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::addColumn (const String& columnName,
|
||||
int columnId,
|
||||
int width,
|
||||
int minimumWidth,
|
||||
int maximumWidth,
|
||||
int propertyFlags,
|
||||
int insertIndex)
|
||||
{
|
||||
// can't have a duplicate or zero ID!
|
||||
jassert (columnId != 0 && getIndexOfColumnId (columnId, false) < 0);
|
||||
jassert (width > 0);
|
||||
|
||||
auto ci = new ColumnInfo();
|
||||
ci->name = columnName;
|
||||
ci->id = columnId;
|
||||
ci->width = width;
|
||||
ci->lastDeliberateWidth = width;
|
||||
ci->minimumWidth = minimumWidth;
|
||||
ci->maximumWidth = maximumWidth >= 0 ? maximumWidth : std::numeric_limits<int>::max();
|
||||
jassert (ci->maximumWidth >= ci->minimumWidth);
|
||||
ci->propertyFlags = propertyFlags;
|
||||
|
||||
columns.insert (insertIndex, ci);
|
||||
sendColumnsChanged();
|
||||
}
|
||||
|
||||
void TableHeaderComponent::removeColumn (const int columnIdToRemove)
|
||||
{
|
||||
auto index = getIndexOfColumnId (columnIdToRemove, false);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
columns.remove (index);
|
||||
sortChanged = true;
|
||||
sendColumnsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::removeAllColumns()
|
||||
{
|
||||
if (columns.size() > 0)
|
||||
{
|
||||
columns.clear();
|
||||
sendColumnsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::moveColumn (const int columnId, int newIndex)
|
||||
{
|
||||
auto currentIndex = getIndexOfColumnId (columnId, false);
|
||||
newIndex = visibleIndexToTotalIndex (newIndex);
|
||||
|
||||
if (columns [currentIndex] != 0 && currentIndex != newIndex)
|
||||
{
|
||||
columns.move (currentIndex, newIndex);
|
||||
sendColumnsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getColumnWidth (const int columnId) const
|
||||
{
|
||||
if (auto* ci = getInfoForId (columnId))
|
||||
return ci->width;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TableHeaderComponent::setColumnWidth (const int columnId, const int newWidth)
|
||||
{
|
||||
if (auto* ci = getInfoForId (columnId))
|
||||
{
|
||||
if (ci->width != newWidth)
|
||||
{
|
||||
auto numColumns = getNumColumns (true);
|
||||
|
||||
ci->lastDeliberateWidth = ci->width
|
||||
= jlimit (ci->minimumWidth, ci->maximumWidth, newWidth);
|
||||
|
||||
if (stretchToFit)
|
||||
{
|
||||
auto index = getIndexOfColumnId (columnId, true) + 1;
|
||||
|
||||
if (isPositiveAndBelow (index, numColumns))
|
||||
{
|
||||
auto x = getColumnPosition (index).getX();
|
||||
|
||||
if (lastDeliberateWidth == 0)
|
||||
lastDeliberateWidth = getTotalWidth();
|
||||
|
||||
resizeColumnsToFit (visibleIndexToTotalIndex (index), lastDeliberateWidth - x);
|
||||
}
|
||||
}
|
||||
|
||||
repaint();
|
||||
columnsResized = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int TableHeaderComponent::getIndexOfColumnId (const int columnId, const bool onlyCountVisibleColumns) const
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
for (auto* c : columns)
|
||||
{
|
||||
if ((! onlyCountVisibleColumns) || c->isVisible())
|
||||
{
|
||||
if (c->id == columnId)
|
||||
return n;
|
||||
|
||||
++n;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getColumnIdOfIndex (int index, const bool onlyCountVisibleColumns) const
|
||||
{
|
||||
if (onlyCountVisibleColumns)
|
||||
index = visibleIndexToTotalIndex (index);
|
||||
|
||||
if (auto* ci = columns [index])
|
||||
return ci->id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Rectangle<int> TableHeaderComponent::getColumnPosition (const int index) const
|
||||
{
|
||||
int x = 0, width = 0, n = 0;
|
||||
|
||||
for (auto* c : columns)
|
||||
{
|
||||
x += width;
|
||||
|
||||
if (c->isVisible())
|
||||
{
|
||||
width = c->width;
|
||||
|
||||
if (n++ == index)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return { x, 0, width, getHeight() };
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getColumnIdAtX (const int xToFind) const
|
||||
{
|
||||
if (xToFind >= 0)
|
||||
{
|
||||
int x = 0;
|
||||
|
||||
for (auto* ci : columns)
|
||||
{
|
||||
if (ci->isVisible())
|
||||
{
|
||||
x += ci->width;
|
||||
|
||||
if (xToFind < x)
|
||||
return ci->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getTotalWidth() const
|
||||
{
|
||||
int w = 0;
|
||||
|
||||
for (auto* c : columns)
|
||||
if (c->isVisible())
|
||||
w += c->width;
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
void TableHeaderComponent::setStretchToFitActive (const bool shouldStretchToFit)
|
||||
{
|
||||
stretchToFit = shouldStretchToFit;
|
||||
lastDeliberateWidth = getTotalWidth();
|
||||
resized();
|
||||
}
|
||||
|
||||
bool TableHeaderComponent::isStretchToFitActive() const
|
||||
{
|
||||
return stretchToFit;
|
||||
}
|
||||
|
||||
void TableHeaderComponent::resizeAllColumnsToFit (int targetTotalWidth)
|
||||
{
|
||||
if (stretchToFit && getWidth() > 0
|
||||
&& columnIdBeingResized == 0 && columnIdBeingDragged == 0)
|
||||
{
|
||||
lastDeliberateWidth = targetTotalWidth;
|
||||
resizeColumnsToFit (0, targetTotalWidth);
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::resizeColumnsToFit (int firstColumnIndex, int targetTotalWidth)
|
||||
{
|
||||
targetTotalWidth = jmax (targetTotalWidth, 0);
|
||||
StretchableObjectResizer sor;
|
||||
|
||||
for (int i = firstColumnIndex; i < columns.size(); ++i)
|
||||
{
|
||||
auto* ci = columns.getUnchecked(i);
|
||||
|
||||
if (ci->isVisible())
|
||||
sor.addItem (ci->lastDeliberateWidth, ci->minimumWidth, ci->maximumWidth);
|
||||
}
|
||||
|
||||
sor.resizeToFit (targetTotalWidth);
|
||||
int visIndex = 0;
|
||||
|
||||
for (int i = firstColumnIndex; i < columns.size(); ++i)
|
||||
{
|
||||
auto* ci = columns.getUnchecked(i);
|
||||
|
||||
if (ci->isVisible())
|
||||
{
|
||||
auto newWidth = jlimit (ci->minimumWidth, ci->maximumWidth,
|
||||
(int) std::floor (sor.getItemSize (visIndex++)));
|
||||
|
||||
if (newWidth != ci->width)
|
||||
{
|
||||
ci->width = newWidth;
|
||||
repaint();
|
||||
columnsResized = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::setColumnVisible (const int columnId, const bool shouldBeVisible)
|
||||
{
|
||||
if (auto* ci = getInfoForId (columnId))
|
||||
{
|
||||
if (shouldBeVisible != ci->isVisible())
|
||||
{
|
||||
if (shouldBeVisible)
|
||||
ci->propertyFlags |= visible;
|
||||
else
|
||||
ci->propertyFlags &= ~visible;
|
||||
|
||||
sendColumnsChanged();
|
||||
resized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TableHeaderComponent::isColumnVisible (const int columnId) const
|
||||
{
|
||||
if (auto* ci = getInfoForId (columnId))
|
||||
return ci->isVisible();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableHeaderComponent::setSortColumnId (const int columnId, const bool sortForwards)
|
||||
{
|
||||
if (getSortColumnId() != columnId || isSortedForwards() != sortForwards)
|
||||
{
|
||||
for (auto* c : columns)
|
||||
c->propertyFlags &= ~(sortedForwards | sortedBackwards);
|
||||
|
||||
if (auto* ci = getInfoForId (columnId))
|
||||
ci->propertyFlags |= (sortForwards ? sortedForwards : sortedBackwards);
|
||||
|
||||
reSortTable();
|
||||
}
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getSortColumnId() const
|
||||
{
|
||||
for (auto* c : columns)
|
||||
if ((c->propertyFlags & (sortedForwards | sortedBackwards)) != 0)
|
||||
return c->id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool TableHeaderComponent::isSortedForwards() const
|
||||
{
|
||||
for (auto* c : columns)
|
||||
if ((c->propertyFlags & (sortedForwards | sortedBackwards)) != 0)
|
||||
return (c->propertyFlags & sortedForwards) != 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TableHeaderComponent::reSortTable()
|
||||
{
|
||||
sortChanged = true;
|
||||
repaint();
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String TableHeaderComponent::toString() const
|
||||
{
|
||||
String s;
|
||||
|
||||
XmlElement doc ("TABLELAYOUT");
|
||||
|
||||
doc.setAttribute ("sortedCol", getSortColumnId());
|
||||
doc.setAttribute ("sortForwards", isSortedForwards());
|
||||
|
||||
for (auto* ci : columns)
|
||||
{
|
||||
auto* e = doc.createNewChildElement ("COLUMN");
|
||||
e->setAttribute ("id", ci->id);
|
||||
e->setAttribute ("visible", ci->isVisible());
|
||||
e->setAttribute ("width", ci->width);
|
||||
}
|
||||
|
||||
return doc.createDocument ({}, true, false);
|
||||
}
|
||||
|
||||
void TableHeaderComponent::restoreFromString (const String& storedVersion)
|
||||
{
|
||||
std::unique_ptr<XmlElement> storedXml (XmlDocument::parse (storedVersion));
|
||||
int index = 0;
|
||||
|
||||
if (storedXml != nullptr && storedXml->hasTagName ("TABLELAYOUT"))
|
||||
{
|
||||
forEachXmlChildElement (*storedXml, col)
|
||||
{
|
||||
auto tabId = col->getIntAttribute ("id");
|
||||
|
||||
if (auto* ci = getInfoForId (tabId))
|
||||
{
|
||||
columns.move (columns.indexOf (ci), index);
|
||||
ci->width = col->getIntAttribute ("width");
|
||||
setColumnVisible (tabId, col->getBoolAttribute ("visible"));
|
||||
}
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
columnsResized = true;
|
||||
sendColumnsChanged();
|
||||
|
||||
setSortColumnId (storedXml->getIntAttribute ("sortedCol"),
|
||||
storedXml->getBoolAttribute ("sortForwards", true));
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableHeaderComponent::addListener (Listener* const newListener)
|
||||
{
|
||||
listeners.addIfNotAlreadyThere (newListener);
|
||||
}
|
||||
|
||||
void TableHeaderComponent::removeListener (Listener* const listenerToRemove)
|
||||
{
|
||||
listeners.removeFirstMatchingValue (listenerToRemove);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableHeaderComponent::columnClicked (int columnId, const ModifierKeys& mods)
|
||||
{
|
||||
if (auto* ci = getInfoForId (columnId))
|
||||
if ((ci->propertyFlags & sortable) != 0 && ! mods.isPopupMenu())
|
||||
setSortColumnId (columnId, (ci->propertyFlags & sortedForwards) == 0);
|
||||
}
|
||||
|
||||
void TableHeaderComponent::addMenuItems (PopupMenu& menu, const int /*columnIdClicked*/)
|
||||
{
|
||||
for (auto* ci : columns)
|
||||
if ((ci->propertyFlags & appearsOnColumnMenu) != 0)
|
||||
menu.addItem (ci->id, ci->name,
|
||||
(ci->propertyFlags & (sortedForwards | sortedBackwards)) == 0,
|
||||
isColumnVisible (ci->id));
|
||||
}
|
||||
|
||||
void TableHeaderComponent::reactToMenuItem (const int menuReturnId, const int /*columnIdClicked*/)
|
||||
{
|
||||
if (getIndexOfColumnId (menuReturnId, false) >= 0)
|
||||
setColumnVisible (menuReturnId, ! isColumnVisible (menuReturnId));
|
||||
}
|
||||
|
||||
void TableHeaderComponent::paint (Graphics& g)
|
||||
{
|
||||
LookAndFeel& lf = getLookAndFeel();
|
||||
|
||||
lf.drawTableHeaderBackground (g, *this);
|
||||
|
||||
const Rectangle<int> clip (g.getClipBounds());
|
||||
|
||||
int x = 0;
|
||||
|
||||
for (auto* ci : columns)
|
||||
{
|
||||
if (ci->isVisible())
|
||||
{
|
||||
if (x + ci->width > clip.getX()
|
||||
&& (ci->id != columnIdBeingDragged
|
||||
|| dragOverlayComp == nullptr
|
||||
|| ! dragOverlayComp->isVisible()))
|
||||
{
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
g.setOrigin (x, 0);
|
||||
g.reduceClipRegion (0, 0, ci->width, getHeight());
|
||||
|
||||
lf.drawTableHeaderColumn (g, *this, ci->name, ci->id, ci->width, getHeight(),
|
||||
ci->id == columnIdUnderMouse,
|
||||
ci->id == columnIdUnderMouse && isMouseButtonDown(),
|
||||
ci->propertyFlags);
|
||||
}
|
||||
|
||||
x += ci->width;
|
||||
|
||||
if (x >= clip.getRight())
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::mouseMove (const MouseEvent& e) { updateColumnUnderMouse (e); }
|
||||
void TableHeaderComponent::mouseEnter (const MouseEvent& e) { updateColumnUnderMouse (e); }
|
||||
void TableHeaderComponent::mouseExit (const MouseEvent&) { setColumnUnderMouse (0); }
|
||||
|
||||
void TableHeaderComponent::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
repaint();
|
||||
columnIdBeingResized = 0;
|
||||
columnIdBeingDragged = 0;
|
||||
|
||||
if (columnIdUnderMouse != 0)
|
||||
{
|
||||
draggingColumnOffset = e.x - getColumnPosition (getIndexOfColumnId (columnIdUnderMouse, true)).getX();
|
||||
|
||||
if (e.mods.isPopupMenu())
|
||||
columnClicked (columnIdUnderMouse, e.mods);
|
||||
}
|
||||
|
||||
if (menuActive && e.mods.isPopupMenu())
|
||||
showColumnChooserMenu (columnIdUnderMouse);
|
||||
}
|
||||
|
||||
void TableHeaderComponent::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
if (columnIdBeingResized == 0
|
||||
&& columnIdBeingDragged == 0
|
||||
&& e.mouseWasDraggedSinceMouseDown()
|
||||
&& ! e.mods.isPopupMenu())
|
||||
{
|
||||
dragOverlayComp.reset();
|
||||
|
||||
columnIdBeingResized = getResizeDraggerAt (e.getMouseDownX());
|
||||
|
||||
if (columnIdBeingResized != 0)
|
||||
{
|
||||
if (auto* ci = getInfoForId (columnIdBeingResized))
|
||||
initialColumnWidth = ci->width;
|
||||
else
|
||||
jassertfalse;
|
||||
}
|
||||
else
|
||||
{
|
||||
beginDrag (e);
|
||||
}
|
||||
}
|
||||
|
||||
if (columnIdBeingResized != 0)
|
||||
{
|
||||
if (auto* ci = getInfoForId (columnIdBeingResized))
|
||||
{
|
||||
auto w = jlimit (ci->minimumWidth, ci->maximumWidth,
|
||||
initialColumnWidth + e.getDistanceFromDragStartX());
|
||||
|
||||
if (stretchToFit)
|
||||
{
|
||||
// prevent us dragging a column too far right if we're in stretch-to-fit mode
|
||||
int minWidthOnRight = 0;
|
||||
|
||||
for (int i = getIndexOfColumnId (columnIdBeingResized, false) + 1; i < columns.size(); ++i)
|
||||
if (columns.getUnchecked (i)->isVisible())
|
||||
minWidthOnRight += columns.getUnchecked (i)->minimumWidth;
|
||||
|
||||
auto currentPos = getColumnPosition (getIndexOfColumnId (columnIdBeingResized, true));
|
||||
w = jmax (ci->minimumWidth, jmin (w, lastDeliberateWidth - minWidthOnRight - currentPos.getX()));
|
||||
}
|
||||
|
||||
setColumnWidth (columnIdBeingResized, w);
|
||||
}
|
||||
}
|
||||
else if (columnIdBeingDragged != 0)
|
||||
{
|
||||
if (e.y >= -50 && e.y < getHeight() + 50)
|
||||
{
|
||||
if (dragOverlayComp != nullptr)
|
||||
{
|
||||
dragOverlayComp->setVisible (true);
|
||||
dragOverlayComp->setBounds (jlimit (0,
|
||||
jmax (0, getTotalWidth() - dragOverlayComp->getWidth()),
|
||||
e.x - draggingColumnOffset),
|
||||
0,
|
||||
dragOverlayComp->getWidth(),
|
||||
getHeight());
|
||||
|
||||
for (int i = columns.size(); --i >= 0;)
|
||||
{
|
||||
const int currentIndex = getIndexOfColumnId (columnIdBeingDragged, true);
|
||||
int newIndex = currentIndex;
|
||||
|
||||
if (newIndex > 0)
|
||||
{
|
||||
// if the previous column isn't draggable, we can't move our column
|
||||
// past it, because that'd change the undraggable column's position..
|
||||
auto* previous = columns.getUnchecked (newIndex - 1);
|
||||
|
||||
if ((previous->propertyFlags & draggable) != 0)
|
||||
{
|
||||
auto leftOfPrevious = getColumnPosition (newIndex - 1).getX();
|
||||
auto rightOfCurrent = getColumnPosition (newIndex).getRight();
|
||||
|
||||
if (std::abs (dragOverlayComp->getX() - leftOfPrevious)
|
||||
< std::abs (dragOverlayComp->getRight() - rightOfCurrent))
|
||||
{
|
||||
--newIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newIndex < columns.size() - 1)
|
||||
{
|
||||
// if the next column isn't draggable, we can't move our column
|
||||
// past it, because that'd change the undraggable column's position..
|
||||
auto* nextCol = columns.getUnchecked (newIndex + 1);
|
||||
|
||||
if ((nextCol->propertyFlags & draggable) != 0)
|
||||
{
|
||||
auto leftOfCurrent = getColumnPosition (newIndex).getX();
|
||||
auto rightOfNext = getColumnPosition (newIndex + 1).getRight();
|
||||
|
||||
if (std::abs (dragOverlayComp->getX() - leftOfCurrent)
|
||||
> std::abs (dragOverlayComp->getRight() - rightOfNext))
|
||||
{
|
||||
++newIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newIndex != currentIndex)
|
||||
moveColumn (columnIdBeingDragged, newIndex);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
endDrag (draggingColumnOriginalIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::beginDrag (const MouseEvent& e)
|
||||
{
|
||||
if (columnIdBeingDragged == 0)
|
||||
{
|
||||
columnIdBeingDragged = getColumnIdAtX (e.getMouseDownX());
|
||||
|
||||
auto* ci = getInfoForId (columnIdBeingDragged);
|
||||
|
||||
if (ci == nullptr || (ci->propertyFlags & draggable) == 0)
|
||||
{
|
||||
columnIdBeingDragged = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
draggingColumnOriginalIndex = getIndexOfColumnId (columnIdBeingDragged, true);
|
||||
|
||||
auto columnRect = getColumnPosition (draggingColumnOriginalIndex);
|
||||
auto temp = columnIdBeingDragged;
|
||||
columnIdBeingDragged = 0;
|
||||
|
||||
dragOverlayComp.reset (new DragOverlayComp (createComponentSnapshot (columnRect, false)));
|
||||
addAndMakeVisible (dragOverlayComp.get());
|
||||
columnIdBeingDragged = temp;
|
||||
|
||||
dragOverlayComp->setBounds (columnRect);
|
||||
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
{
|
||||
listeners.getUnchecked(i)->tableColumnDraggingChanged (this, columnIdBeingDragged);
|
||||
i = jmin (i, listeners.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::endDrag (const int finalIndex)
|
||||
{
|
||||
if (columnIdBeingDragged != 0)
|
||||
{
|
||||
moveColumn (columnIdBeingDragged, finalIndex);
|
||||
|
||||
columnIdBeingDragged = 0;
|
||||
repaint();
|
||||
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
{
|
||||
listeners.getUnchecked(i)->tableColumnDraggingChanged (this, 0);
|
||||
i = jmin (i, listeners.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::mouseUp (const MouseEvent& e)
|
||||
{
|
||||
mouseDrag (e);
|
||||
|
||||
for (auto* c : columns)
|
||||
if (c->isVisible())
|
||||
c->lastDeliberateWidth = c->width;
|
||||
|
||||
columnIdBeingResized = 0;
|
||||
repaint();
|
||||
|
||||
endDrag (getIndexOfColumnId (columnIdBeingDragged, true));
|
||||
|
||||
updateColumnUnderMouse (e);
|
||||
|
||||
if (columnIdUnderMouse != 0 && ! (e.mouseWasDraggedSinceMouseDown() || e.mods.isPopupMenu()))
|
||||
columnClicked (columnIdUnderMouse, e.mods);
|
||||
|
||||
dragOverlayComp.reset();
|
||||
}
|
||||
|
||||
MouseCursor TableHeaderComponent::getMouseCursor()
|
||||
{
|
||||
if (columnIdBeingResized != 0 || (getResizeDraggerAt (getMouseXYRelative().getX()) != 0 && ! isMouseButtonDown()))
|
||||
return MouseCursor (MouseCursor::LeftRightResizeCursor);
|
||||
|
||||
return Component::getMouseCursor();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool TableHeaderComponent::ColumnInfo::isVisible() const
|
||||
{
|
||||
return (propertyFlags & TableHeaderComponent::visible) != 0;
|
||||
}
|
||||
|
||||
TableHeaderComponent::ColumnInfo* TableHeaderComponent::getInfoForId (int id) const
|
||||
{
|
||||
for (auto* c : columns)
|
||||
if (c->id == id)
|
||||
return c;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int TableHeaderComponent::visibleIndexToTotalIndex (const int visibleIndex) const
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
for (int i = 0; i < columns.size(); ++i)
|
||||
{
|
||||
if (columns.getUnchecked(i)->isVisible())
|
||||
{
|
||||
if (n == visibleIndex)
|
||||
return i;
|
||||
|
||||
++n;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TableHeaderComponent::sendColumnsChanged()
|
||||
{
|
||||
if (stretchToFit && lastDeliberateWidth > 0)
|
||||
resizeAllColumnsToFit (lastDeliberateWidth);
|
||||
|
||||
repaint();
|
||||
columnsChanged = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void TableHeaderComponent::handleAsyncUpdate()
|
||||
{
|
||||
const bool changed = columnsChanged || sortChanged;
|
||||
const bool sized = columnsResized || changed;
|
||||
const bool sorted = sortChanged;
|
||||
columnsChanged = false;
|
||||
columnsResized = false;
|
||||
sortChanged = false;
|
||||
|
||||
if (sorted)
|
||||
{
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
{
|
||||
listeners.getUnchecked(i)->tableSortOrderChanged (this);
|
||||
i = jmin (i, listeners.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
{
|
||||
listeners.getUnchecked(i)->tableColumnsChanged (this);
|
||||
i = jmin (i, listeners.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (sized)
|
||||
{
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
{
|
||||
listeners.getUnchecked(i)->tableColumnsResized (this);
|
||||
i = jmin (i, listeners.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getResizeDraggerAt (const int mouseX) const
|
||||
{
|
||||
if (isPositiveAndBelow (mouseX, getWidth()))
|
||||
{
|
||||
const int draggableDistance = 3;
|
||||
int x = 0;
|
||||
|
||||
for (auto* ci : columns)
|
||||
{
|
||||
if (ci->isVisible())
|
||||
{
|
||||
if (std::abs (mouseX - (x + ci->width)) <= draggableDistance
|
||||
&& (ci->propertyFlags & resizable) != 0)
|
||||
return ci->id;
|
||||
|
||||
x += ci->width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TableHeaderComponent::setColumnUnderMouse (const int newCol)
|
||||
{
|
||||
if (newCol != columnIdUnderMouse)
|
||||
{
|
||||
columnIdUnderMouse = newCol;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::updateColumnUnderMouse (const MouseEvent& e)
|
||||
{
|
||||
setColumnUnderMouse (reallyContains (e.getPosition(), true) && getResizeDraggerAt (e.x) == 0
|
||||
? getColumnIdAtX (e.x) : 0);
|
||||
}
|
||||
|
||||
static void tableHeaderMenuCallback (int result, TableHeaderComponent* tableHeader, int columnIdClicked)
|
||||
{
|
||||
if (tableHeader != nullptr && result != 0)
|
||||
tableHeader->reactToMenuItem (result, columnIdClicked);
|
||||
}
|
||||
|
||||
void TableHeaderComponent::showColumnChooserMenu (const int columnIdClicked)
|
||||
{
|
||||
PopupMenu m;
|
||||
addMenuItems (m, columnIdClicked);
|
||||
|
||||
if (m.getNumItems() > 0)
|
||||
{
|
||||
m.setLookAndFeel (&getLookAndFeel());
|
||||
|
||||
m.showMenuAsync (PopupMenu::Options(),
|
||||
ModalCallbackFunction::forComponent (tableHeaderMenuCallback, this, columnIdClicked));
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::Listener::tableColumnDraggingChanged (TableHeaderComponent*, int)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user