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:
650
modules/juce_gui_basics/widgets/juce_ComboBox.cpp
Normal file
650
modules/juce_gui_basics/widgets/juce_ComboBox.cpp
Normal file
@ -0,0 +1,650 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
ComboBox::ComboBox (const String& name)
|
||||
: Component (name),
|
||||
noChoicesMessage (TRANS("(no choices)"))
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
lookAndFeelChanged();
|
||||
currentId.addListener (this);
|
||||
}
|
||||
|
||||
ComboBox::~ComboBox()
|
||||
{
|
||||
currentId.removeListener (this);
|
||||
hidePopup();
|
||||
label.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::setEditableText (const bool isEditable)
|
||||
{
|
||||
if (label->isEditableOnSingleClick() != isEditable || label->isEditableOnDoubleClick() != isEditable)
|
||||
{
|
||||
label->setEditable (isEditable, isEditable, false);
|
||||
labelEditableState = (isEditable ? labelIsEditable : labelIsNotEditable);
|
||||
|
||||
setWantsKeyboardFocus (labelEditableState == labelIsNotEditable);
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
bool ComboBox::isTextEditable() const noexcept
|
||||
{
|
||||
return label->isEditable();
|
||||
}
|
||||
|
||||
void ComboBox::setJustificationType (Justification justification)
|
||||
{
|
||||
label->setJustificationType (justification);
|
||||
}
|
||||
|
||||
Justification ComboBox::getJustificationType() const noexcept
|
||||
{
|
||||
return label->getJustificationType();
|
||||
}
|
||||
|
||||
void ComboBox::setTooltip (const String& newTooltip)
|
||||
{
|
||||
SettableTooltipClient::setTooltip (newTooltip);
|
||||
label->setTooltip (newTooltip);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::addItem (const String& newItemText, int newItemId)
|
||||
{
|
||||
// you can't add empty strings to the list..
|
||||
jassert (newItemText.isNotEmpty());
|
||||
|
||||
// IDs must be non-zero, as zero is used to indicate a lack of selecion.
|
||||
jassert (newItemId != 0);
|
||||
|
||||
// you shouldn't use duplicate item IDs!
|
||||
jassert (getItemForId (newItemId) == nullptr);
|
||||
|
||||
if (newItemText.isNotEmpty() && newItemId != 0)
|
||||
currentMenu.addItem (newItemId, newItemText, true, false);
|
||||
}
|
||||
|
||||
void ComboBox::addItemList (const StringArray& itemsToAdd, int firstItemID)
|
||||
{
|
||||
for (auto& i : itemsToAdd)
|
||||
currentMenu.addItem (firstItemID++, i);
|
||||
}
|
||||
|
||||
void ComboBox::addSeparator()
|
||||
{
|
||||
currentMenu.addSeparator();
|
||||
}
|
||||
|
||||
void ComboBox::addSectionHeading (const String& headingName)
|
||||
{
|
||||
// you can't add empty strings to the list..
|
||||
jassert (headingName.isNotEmpty());
|
||||
|
||||
if (headingName.isNotEmpty())
|
||||
currentMenu.addSectionHeader (headingName);
|
||||
}
|
||||
|
||||
void ComboBox::setItemEnabled (int itemId, bool shouldBeEnabled)
|
||||
{
|
||||
if (auto* item = getItemForId (itemId))
|
||||
item->isEnabled = shouldBeEnabled;
|
||||
}
|
||||
|
||||
bool ComboBox::isItemEnabled (int itemId) const noexcept
|
||||
{
|
||||
if (auto* item = getItemForId (itemId))
|
||||
return item->isEnabled;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ComboBox::changeItemText (int itemId, const String& newText)
|
||||
{
|
||||
if (auto* item = getItemForId (itemId))
|
||||
item->text = newText;
|
||||
else
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
void ComboBox::clear (const NotificationType notification)
|
||||
{
|
||||
currentMenu.clear();
|
||||
|
||||
if (! label->isEditable())
|
||||
setSelectedItemIndex (-1, notification);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
PopupMenu::Item* ComboBox::getItemForId (int itemId) const noexcept
|
||||
{
|
||||
if (itemId != 0)
|
||||
{
|
||||
for (PopupMenu::MenuItemIterator iterator (currentMenu, true); iterator.next();)
|
||||
{
|
||||
auto& item = iterator.getItem();
|
||||
|
||||
if (item.itemID == itemId)
|
||||
return &item;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PopupMenu::Item* ComboBox::getItemForIndex (const int index) const noexcept
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
for (PopupMenu::MenuItemIterator iterator (currentMenu, true); iterator.next();)
|
||||
{
|
||||
auto& item = iterator.getItem();
|
||||
|
||||
if (item.itemID != 0)
|
||||
if (n++ == index)
|
||||
return &item;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ComboBox::getNumItems() const noexcept
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
for (PopupMenu::MenuItemIterator iterator (currentMenu, true); iterator.next();)
|
||||
{
|
||||
auto& item = iterator.getItem();
|
||||
|
||||
if (item.itemID != 0)
|
||||
n++;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
String ComboBox::getItemText (const int index) const
|
||||
{
|
||||
if (auto* item = getItemForIndex (index))
|
||||
return item->text;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int ComboBox::getItemId (const int index) const noexcept
|
||||
{
|
||||
if (auto* item = getItemForIndex (index))
|
||||
return item->itemID;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ComboBox::indexOfItemId (const int itemId) const noexcept
|
||||
{
|
||||
if (itemId != 0)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
for (PopupMenu::MenuItemIterator iterator (currentMenu, true); iterator.next();)
|
||||
{
|
||||
auto& item = iterator.getItem();
|
||||
|
||||
if (item.itemID == itemId)
|
||||
return n;
|
||||
|
||||
else if (item.itemID != 0)
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int ComboBox::getSelectedItemIndex() const
|
||||
{
|
||||
auto index = indexOfItemId (currentId.getValue());
|
||||
|
||||
if (getText() != getItemText (index))
|
||||
index = -1;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void ComboBox::setSelectedItemIndex (const int index, const NotificationType notification)
|
||||
{
|
||||
setSelectedId (getItemId (index), notification);
|
||||
}
|
||||
|
||||
int ComboBox::getSelectedId() const noexcept
|
||||
{
|
||||
if (auto* item = getItemForId (currentId.getValue()))
|
||||
if (getText() == item->text)
|
||||
return item->itemID;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ComboBox::setSelectedId (const int newItemId, const NotificationType notification)
|
||||
{
|
||||
auto* item = getItemForId (newItemId);
|
||||
auto newItemText = item != nullptr ? item->text : String();
|
||||
|
||||
if (lastCurrentId != newItemId || label->getText() != newItemText)
|
||||
{
|
||||
label->setText (newItemText, dontSendNotification);
|
||||
lastCurrentId = newItemId;
|
||||
currentId = newItemId;
|
||||
|
||||
repaint(); // for the benefit of the 'none selected' text
|
||||
|
||||
sendChange (notification);
|
||||
}
|
||||
}
|
||||
|
||||
bool ComboBox::selectIfEnabled (const int index)
|
||||
{
|
||||
if (auto* item = getItemForIndex (index))
|
||||
{
|
||||
if (item->isEnabled)
|
||||
{
|
||||
setSelectedItemIndex (index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ComboBox::nudgeSelectedItem (int delta)
|
||||
{
|
||||
for (int i = getSelectedItemIndex() + delta; isPositiveAndBelow (i, getNumItems()); i += delta)
|
||||
if (selectIfEnabled (i))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ComboBox::valueChanged (Value&)
|
||||
{
|
||||
if (lastCurrentId != (int) currentId.getValue())
|
||||
setSelectedId (currentId.getValue());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String ComboBox::getText() const
|
||||
{
|
||||
return label->getText();
|
||||
}
|
||||
|
||||
void ComboBox::setText (const String& newText, const NotificationType notification)
|
||||
{
|
||||
for (PopupMenu::MenuItemIterator iterator (currentMenu, true); iterator.next();)
|
||||
{
|
||||
auto& item = iterator.getItem();
|
||||
|
||||
if (item.itemID != 0
|
||||
&& item.text == newText)
|
||||
{
|
||||
setSelectedId (item.itemID, notification);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lastCurrentId = 0;
|
||||
currentId = 0;
|
||||
repaint();
|
||||
|
||||
if (label->getText() != newText)
|
||||
{
|
||||
label->setText (newText, dontSendNotification);
|
||||
sendChange (notification);
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::showEditor()
|
||||
{
|
||||
jassert (isTextEditable()); // you probably shouldn't do this to a non-editable combo box?
|
||||
|
||||
label->showEditor();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::setTextWhenNothingSelected (const String& newMessage)
|
||||
{
|
||||
if (textWhenNothingSelected != newMessage)
|
||||
{
|
||||
textWhenNothingSelected = newMessage;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
String ComboBox::getTextWhenNothingSelected() const
|
||||
{
|
||||
return textWhenNothingSelected;
|
||||
}
|
||||
|
||||
void ComboBox::setTextWhenNoChoicesAvailable (const String& newMessage)
|
||||
{
|
||||
noChoicesMessage = newMessage;
|
||||
}
|
||||
|
||||
String ComboBox::getTextWhenNoChoicesAvailable() const
|
||||
{
|
||||
return noChoicesMessage;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawComboBox (g, getWidth(), getHeight(), isButtonDown,
|
||||
label->getRight(), 0, getWidth() - label->getRight(), getHeight(),
|
||||
*this);
|
||||
|
||||
if (textWhenNothingSelected.isNotEmpty()
|
||||
&& label->getText().isEmpty()
|
||||
&& ! label->isBeingEdited())
|
||||
{
|
||||
g.setColour (findColour (textColourId).withMultipliedAlpha (0.5f));
|
||||
g.setFont (label->getLookAndFeel().getLabelFont (*label));
|
||||
g.drawFittedText (textWhenNothingSelected, label->getBounds().reduced (2, 1),
|
||||
label->getJustificationType(),
|
||||
jmax (1, (int) (label->getHeight() / label->getFont().getHeight())));
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::resized()
|
||||
{
|
||||
if (getHeight() > 0 && getWidth() > 0)
|
||||
getLookAndFeel().positionComboBoxText (*this, *label);
|
||||
}
|
||||
|
||||
void ComboBox::enablementChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ComboBox::colourChanged()
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void ComboBox::parentHierarchyChanged()
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void ComboBox::lookAndFeelChanged()
|
||||
{
|
||||
repaint();
|
||||
|
||||
{
|
||||
std::unique_ptr<Label> newLabel (getLookAndFeel().createComboBoxTextBox (*this));
|
||||
jassert (newLabel != nullptr);
|
||||
|
||||
if (label != nullptr)
|
||||
{
|
||||
newLabel->setEditable (label->isEditable());
|
||||
newLabel->setJustificationType (label->getJustificationType());
|
||||
newLabel->setTooltip (label->getTooltip());
|
||||
newLabel->setText (label->getText(), dontSendNotification);
|
||||
}
|
||||
|
||||
std::swap (label, newLabel);
|
||||
}
|
||||
|
||||
addAndMakeVisible (label.get());
|
||||
|
||||
EditableState newEditableState = (label->isEditable() ? labelIsEditable : labelIsNotEditable);
|
||||
|
||||
if (newEditableState != labelEditableState)
|
||||
{
|
||||
labelEditableState = newEditableState;
|
||||
setWantsKeyboardFocus (labelEditableState == labelIsNotEditable);
|
||||
}
|
||||
|
||||
label->onTextChange = [this] { triggerAsyncUpdate(); };
|
||||
label->addMouseListener (this, false);
|
||||
|
||||
label->setColour (Label::backgroundColourId, Colours::transparentBlack);
|
||||
label->setColour (Label::textColourId, findColour (ComboBox::textColourId));
|
||||
|
||||
label->setColour (TextEditor::textColourId, findColour (ComboBox::textColourId));
|
||||
label->setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
|
||||
label->setColour (TextEditor::highlightColourId, findColour (TextEditor::highlightColourId));
|
||||
label->setColour (TextEditor::outlineColourId, Colours::transparentBlack);
|
||||
|
||||
resized();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ComboBox::keyPressed (const KeyPress& key)
|
||||
{
|
||||
if (key == KeyPress::upKey || key == KeyPress::leftKey)
|
||||
{
|
||||
nudgeSelectedItem (-1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == KeyPress::downKey || key == KeyPress::rightKey)
|
||||
{
|
||||
nudgeSelectedItem (1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == KeyPress::returnKey)
|
||||
{
|
||||
showPopupIfNotActive();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ComboBox::keyStateChanged (const bool isKeyDown)
|
||||
{
|
||||
// only forward key events that aren't used by this component
|
||||
return isKeyDown
|
||||
&& (KeyPress::isKeyCurrentlyDown (KeyPress::upKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::leftKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::downKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::rightKey));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::focusGained (FocusChangeType) { repaint(); }
|
||||
void ComboBox::focusLost (FocusChangeType) { repaint(); }
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::showPopupIfNotActive()
|
||||
{
|
||||
if (! menuActive)
|
||||
{
|
||||
menuActive = true;
|
||||
|
||||
SafePointer<ComboBox> safePointer (this);
|
||||
|
||||
// as this method was triggered by a mouse event, the same mouse event may have
|
||||
// exited the modal state of other popups currently on the screen. By calling
|
||||
// showPopup asynchronously, we are giving the other popups a chance to properly
|
||||
// close themselves
|
||||
MessageManager::callAsync ([safePointer]() mutable { if (safePointer != nullptr) safePointer->showPopup(); });
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::hidePopup()
|
||||
{
|
||||
if (menuActive)
|
||||
{
|
||||
menuActive = false;
|
||||
PopupMenu::dismissAllActiveMenus();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
static void comboBoxPopupMenuFinishedCallback (int result, ComboBox* combo)
|
||||
{
|
||||
if (combo != nullptr)
|
||||
{
|
||||
combo->hidePopup();
|
||||
|
||||
if (result != 0)
|
||||
combo->setSelectedId (result);
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::showPopup()
|
||||
{
|
||||
auto menu = currentMenu;
|
||||
|
||||
if (menu.getNumItems() > 0)
|
||||
{
|
||||
auto selectedId = getSelectedId();
|
||||
|
||||
for (PopupMenu::MenuItemIterator iterator (menu, true); iterator.next();)
|
||||
{
|
||||
auto& item = iterator.getItem();
|
||||
|
||||
if (item.itemID != 0)
|
||||
item.isTicked = (item.itemID == selectedId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.addItem (1, noChoicesMessage, false, false);
|
||||
}
|
||||
|
||||
menu.setLookAndFeel (&getLookAndFeel());
|
||||
menu.showMenuAsync (PopupMenu::Options().withTargetComponent (this)
|
||||
.withItemThatMustBeVisible (getSelectedId())
|
||||
.withMinimumWidth (getWidth())
|
||||
.withMaximumNumColumns (1)
|
||||
.withStandardItemHeight (label->getHeight()),
|
||||
ModalCallbackFunction::forComponent (comboBoxPopupMenuFinishedCallback, this));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
beginDragAutoRepeat (300);
|
||||
|
||||
isButtonDown = isEnabled() && ! e.mods.isPopupMenu();
|
||||
|
||||
if (isButtonDown && (e.eventComponent == this || ! label->isEditable()))
|
||||
showPopupIfNotActive();
|
||||
}
|
||||
|
||||
void ComboBox::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
beginDragAutoRepeat (50);
|
||||
|
||||
if (isButtonDown && e.mouseWasDraggedSinceMouseDown())
|
||||
showPopupIfNotActive();
|
||||
}
|
||||
|
||||
void ComboBox::mouseUp (const MouseEvent& e2)
|
||||
{
|
||||
if (isButtonDown)
|
||||
{
|
||||
isButtonDown = false;
|
||||
repaint();
|
||||
|
||||
auto e = e2.getEventRelativeTo (this);
|
||||
|
||||
if (reallyContains (e.getPosition(), true)
|
||||
&& (e2.eventComponent == this || ! label->isEditable()))
|
||||
{
|
||||
showPopupIfNotActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
|
||||
{
|
||||
if (! menuActive && scrollWheelEnabled && e.eventComponent == this && wheel.deltaY != 0.0f)
|
||||
{
|
||||
mouseWheelAccumulator += wheel.deltaY * 5.0f;
|
||||
|
||||
while (mouseWheelAccumulator > 1.0f)
|
||||
{
|
||||
mouseWheelAccumulator -= 1.0f;
|
||||
nudgeSelectedItem (-1);
|
||||
}
|
||||
|
||||
while (mouseWheelAccumulator < -1.0f)
|
||||
{
|
||||
mouseWheelAccumulator += 1.0f;
|
||||
nudgeSelectedItem (1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Component::mouseWheelMove (e, wheel);
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::setScrollWheelEnabled (bool enabled) noexcept
|
||||
{
|
||||
scrollWheelEnabled = enabled;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::addListener (ComboBox::Listener* l) { listeners.add (l); }
|
||||
void ComboBox::removeListener (ComboBox::Listener* l) { listeners.remove (l); }
|
||||
|
||||
void ComboBox::handleAsyncUpdate()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [this] (Listener& l) { l.comboBoxChanged (this); });
|
||||
|
||||
if (checker.shouldBailOut())
|
||||
return;
|
||||
|
||||
if (onChange != nullptr)
|
||||
onChange();
|
||||
}
|
||||
|
||||
void ComboBox::sendChange (const NotificationType notification)
|
||||
{
|
||||
if (notification != dontSendNotification)
|
||||
triggerAsyncUpdate();
|
||||
|
||||
if (notification == sendNotificationSync)
|
||||
handleUpdateNowIfNeeded();
|
||||
}
|
||||
|
||||
// Old deprecated methods - remove eventually...
|
||||
void ComboBox::clear (const bool dontSendChange) { clear (dontSendChange ? dontSendNotification : sendNotification); }
|
||||
void ComboBox::setSelectedItemIndex (const int index, const bool dontSendChange) { setSelectedItemIndex (index, dontSendChange ? dontSendNotification : sendNotification); }
|
||||
void ComboBox::setSelectedId (const int newItemId, const bool dontSendChange) { setSelectedId (newItemId, dontSendChange ? dontSendNotification : sendNotification); }
|
||||
void ComboBox::setText (const String& newText, const bool dontSendChange) { setText (newText, dontSendChange ? dontSendNotification : sendNotification); }
|
||||
|
||||
} // namespace juce
|
453
modules/juce_gui_basics/widgets/juce_ComboBox.h
Normal file
453
modules/juce_gui_basics/widgets/juce_ComboBox.h
Normal file
@ -0,0 +1,453 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that lets the user choose from a drop-down list of choices.
|
||||
|
||||
The combo-box has a list of text strings, each with an associated id number,
|
||||
that will be shown in the drop-down list when the user clicks on the component.
|
||||
|
||||
The currently selected choice is displayed in the combo-box, and this can
|
||||
either be read-only text, or editable.
|
||||
|
||||
To find out when the user selects a different item or edits the text, you
|
||||
can assign a lambda to the onChange member, or register a ComboBox::Listener
|
||||
to receive callbacks.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComboBox : public Component,
|
||||
public SettableTooltipClient,
|
||||
public Value::Listener,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a combo-box.
|
||||
|
||||
On construction, the text field will be empty, so you should call the
|
||||
setSelectedId() or setText() method to choose the initial value before
|
||||
displaying it.
|
||||
|
||||
@param componentName the name to set for the component (see Component::setName())
|
||||
*/
|
||||
explicit ComboBox (const String& componentName = String());
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ComboBox();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets whether the text in the combo-box is editable.
|
||||
|
||||
The default state for a new ComboBox is non-editable, and can only be changed
|
||||
by choosing from the drop-down list.
|
||||
*/
|
||||
void setEditableText (bool isEditable);
|
||||
|
||||
/** Returns true if the text is directly editable.
|
||||
@see setEditableText
|
||||
*/
|
||||
bool isTextEditable() const noexcept;
|
||||
|
||||
/** Sets the style of justification to be used for positioning the text.
|
||||
|
||||
The default is Justification::centredLeft. The text is displayed using a
|
||||
Label component inside the ComboBox.
|
||||
*/
|
||||
void setJustificationType (Justification justification);
|
||||
|
||||
/** Returns the current justification for the text box.
|
||||
@see setJustificationType
|
||||
*/
|
||||
Justification getJustificationType() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Adds an item to be shown in the drop-down list.
|
||||
|
||||
@param newItemText the text of the item to show in the list
|
||||
@param newItemId an associated ID number that can be set or retrieved - see
|
||||
getSelectedId() and setSelectedId(). Note that this value can not
|
||||
be 0!
|
||||
@see setItemEnabled, addSeparator, addSectionHeading, getNumItems, getItemText, getItemId
|
||||
*/
|
||||
void addItem (const String& newItemText, int newItemId);
|
||||
|
||||
/** Adds an array of items to the drop-down list.
|
||||
The item ID of each item will be its index in the StringArray + firstItemIdOffset.
|
||||
*/
|
||||
void addItemList (const StringArray& items, int firstItemIdOffset);
|
||||
|
||||
/** Adds a separator line to the drop-down list.
|
||||
|
||||
This is like adding a separator to a popup menu. See PopupMenu::addSeparator().
|
||||
*/
|
||||
void addSeparator();
|
||||
|
||||
/** Adds a heading to the drop-down list, so that you can group the items into
|
||||
different sections.
|
||||
|
||||
The headings are indented slightly differently to set them apart from the
|
||||
items on the list, and obviously can't be selected. You might want to add
|
||||
separators between your sections too.
|
||||
|
||||
@see addItem, addSeparator
|
||||
*/
|
||||
void addSectionHeading (const String& headingName);
|
||||
|
||||
/** This allows items in the drop-down list to be selectively disabled.
|
||||
|
||||
When you add an item, it's enabled by default, but you can call this
|
||||
method to change its status.
|
||||
|
||||
If you disable an item which is already selected, this won't change the
|
||||
current selection - it just stops the user choosing that item from the list.
|
||||
*/
|
||||
void setItemEnabled (int itemId, bool shouldBeEnabled);
|
||||
|
||||
/** Returns true if the given item is enabled. */
|
||||
bool isItemEnabled (int itemId) const noexcept;
|
||||
|
||||
/** Changes the text for an existing item.
|
||||
*/
|
||||
void changeItemText (int itemId, const String& newText);
|
||||
|
||||
/** Removes all the items from the drop-down list.
|
||||
|
||||
If this call causes the content to be cleared, and a change-message
|
||||
will be broadcast according to the notification parameter.
|
||||
|
||||
@see addItem, getNumItems
|
||||
*/
|
||||
void clear (NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Returns the number of items that have been added to the list.
|
||||
|
||||
Note that this doesn't include headers or separators.
|
||||
*/
|
||||
int getNumItems() const noexcept;
|
||||
|
||||
/** Returns the text for one of the items in the list.
|
||||
Note that this doesn't include headers or separators.
|
||||
@param index the item's index from 0 to (getNumItems() - 1)
|
||||
*/
|
||||
String getItemText (int index) const;
|
||||
|
||||
/** Returns the ID for one of the items in the list.
|
||||
Note that this doesn't include headers or separators.
|
||||
@param index the item's index from 0 to (getNumItems() - 1)
|
||||
*/
|
||||
int getItemId (int index) const noexcept;
|
||||
|
||||
/** Returns the index in the list of a particular item ID.
|
||||
If no such ID is found, this will return -1.
|
||||
*/
|
||||
int indexOfItemId (int itemId) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the ID of the item that's currently shown in the box.
|
||||
|
||||
If no item is selected, or if the text is editable and the user
|
||||
has entered something which isn't one of the items in the list, then
|
||||
this will return 0.
|
||||
|
||||
@see setSelectedId, getSelectedItemIndex, getText
|
||||
*/
|
||||
int getSelectedId() const noexcept;
|
||||
|
||||
/** Returns a Value object that can be used to get or set the selected item's ID.
|
||||
|
||||
You can call Value::referTo() on this object to make the combo box control
|
||||
another Value object.
|
||||
*/
|
||||
Value& getSelectedIdAsValue() { return currentId; }
|
||||
|
||||
/** Sets one of the items to be the current selection.
|
||||
|
||||
This will set the ComboBox's text to that of the item that matches
|
||||
this ID.
|
||||
|
||||
@param newItemId the new item to select
|
||||
@param notification determines the type of change notification that will
|
||||
be sent to listeners if the value changes
|
||||
@see getSelectedId, setSelectedItemIndex, setText
|
||||
*/
|
||||
void setSelectedId (int newItemId,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the index of the item that's currently shown in the box.
|
||||
|
||||
If no item is selected, or if the text is editable and the user
|
||||
has entered something which isn't one of the items in the list, then
|
||||
this will return -1.
|
||||
|
||||
@see setSelectedItemIndex, getSelectedId, getText
|
||||
*/
|
||||
int getSelectedItemIndex() const;
|
||||
|
||||
/** Sets one of the items to be the current selection.
|
||||
|
||||
This will set the ComboBox's text to that of the item at the given
|
||||
index in the list.
|
||||
|
||||
@param newItemIndex the new item to select
|
||||
@param notification determines the type of change notification that will
|
||||
be sent to listeners if the value changes
|
||||
@see getSelectedItemIndex, setSelectedId, setText
|
||||
*/
|
||||
void setSelectedItemIndex (int newItemIndex,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the text that is currently shown in the combo-box's text field.
|
||||
|
||||
If the ComboBox has editable text, then this text may have been edited
|
||||
by the user; otherwise it will be one of the items from the list, or
|
||||
possibly an empty string if nothing was selected.
|
||||
|
||||
@see setText, getSelectedId, getSelectedItemIndex
|
||||
*/
|
||||
String getText() const;
|
||||
|
||||
/** Sets the contents of the combo-box's text field.
|
||||
|
||||
The text passed-in will be set as the current text regardless of whether
|
||||
it is one of the items in the list. If the current text isn't one of the
|
||||
items, then getSelectedId() will return -1, otherwise it wil return
|
||||
the approriate ID.
|
||||
|
||||
@param newText the text to select
|
||||
@param notification determines the type of change notification that will
|
||||
be sent to listeners if the text changes
|
||||
@see getText
|
||||
*/
|
||||
void setText (const String& newText,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Programmatically opens the text editor to allow the user to edit the current item.
|
||||
|
||||
This is the same effect as when the box is clicked-on.
|
||||
@see Label::showEditor();
|
||||
*/
|
||||
void showEditor();
|
||||
|
||||
/** Pops up the combo box's list.
|
||||
This is virtual so that you can override it with your own custom popup
|
||||
mechanism if you need some really unusual behaviour.
|
||||
*/
|
||||
virtual void showPopup();
|
||||
|
||||
/** Hides the combo box's popup list, if it's currently visible. */
|
||||
void hidePopup();
|
||||
|
||||
/** Returns true if the popup menu is currently being shown. */
|
||||
bool isPopupActive() const noexcept { return menuActive; }
|
||||
|
||||
/** Returns the PopupMenu object associated with the ComboBox.
|
||||
Can be useful for adding sub-menus to the ComboBox standard PopupMenu
|
||||
*/
|
||||
PopupMenu* getRootMenu() { return ¤tMenu; }
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class for receiving events from a ComboBox.
|
||||
|
||||
You can register a ComboBox::Listener with a ComboBox using the ComboBox::addListener()
|
||||
method, and it will be called when the selected item in the box changes.
|
||||
|
||||
@see ComboBox::addListener, ComboBox::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called when a ComboBox has its selected item changed. */
|
||||
virtual void comboBoxChanged (ComboBox* comboBoxThatHasChanged) = 0;
|
||||
};
|
||||
|
||||
/** Registers a listener that will be called when the box's content changes. */
|
||||
void addListener (Listener* listener);
|
||||
|
||||
/** Deregisters a previously-registered listener. */
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** You can assign a lambda to this callback object to have it called when the selected ID is changed. */
|
||||
std::function<void()> onChange;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a message to display when there is no item currently selected.
|
||||
@see getTextWhenNothingSelected
|
||||
*/
|
||||
void setTextWhenNothingSelected (const String& newMessage);
|
||||
|
||||
/** Returns the text that is shown when no item is selected.
|
||||
@see setTextWhenNothingSelected
|
||||
*/
|
||||
String getTextWhenNothingSelected() const;
|
||||
|
||||
/** Sets the message to show when there are no items in the list, and the user clicks
|
||||
on the drop-down box.
|
||||
|
||||
By default it just says "no choices", but this lets you change it to something more
|
||||
meaningful.
|
||||
*/
|
||||
void setTextWhenNoChoicesAvailable (const String& newMessage);
|
||||
|
||||
/** Returns the text shown when no items have been added to the list.
|
||||
@see setTextWhenNoChoicesAvailable
|
||||
*/
|
||||
String getTextWhenNoChoicesAvailable() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Gives the ComboBox a tooltip. */
|
||||
void setTooltip (const String& newTooltip) override;
|
||||
|
||||
/** This can be used to allow the scroll-wheel to nudge the chosen item.
|
||||
By default it's disabled, and I'd recommend leaving it disabled if there's any
|
||||
chance that the control might be inside a scrollable list or viewport.
|
||||
*/
|
||||
void setScrollWheelEnabled (bool enabled) noexcept;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the combo box.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
To change the colours of the menu that pops up, you can set the colour IDs in PopupMenu::ColourIDs.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1000b00, /**< The background colour to fill the box with. */
|
||||
textColourId = 0x1000a00, /**< The colour for the text in the box. */
|
||||
outlineColourId = 0x1000c00, /**< The colour for an outline around the box. */
|
||||
buttonColourId = 0x1000d00, /**< The base colour for the button (a LookAndFeel class will probably use variations on this). */
|
||||
arrowColourId = 0x1000e00, /**< The colour for the arrow shape that pops up the menu */
|
||||
focusedOutlineColourId = 0x1000f00 /**< The colour that will be used to draw a box around the edge of the component when it has focus. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
ComboBox functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void drawComboBox (Graphics&, int width, int height, bool isButtonDown,
|
||||
int buttonX, int buttonY, int buttonW, int buttonH,
|
||||
ComboBox&) = 0;
|
||||
|
||||
virtual Font getComboBoxFont (ComboBox&) = 0;
|
||||
|
||||
virtual Label* createComboBoxTextBox (ComboBox&) = 0;
|
||||
|
||||
virtual void positionComboBoxText (ComboBox&, Label& labelToPosition) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
void focusGained (Component::FocusChangeType) override;
|
||||
/** @internal */
|
||||
void focusLost (Component::FocusChangeType) override;
|
||||
/** @internal */
|
||||
void handleAsyncUpdate() override;
|
||||
/** @internal */
|
||||
String getTooltip() override { return label->getTooltip(); }
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
bool keyStateChanged (bool) override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void valueChanged (Value&) override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
|
||||
// These methods' bool parameters have changed: see their new method signatures.
|
||||
JUCE_DEPRECATED (void clear (bool));
|
||||
JUCE_DEPRECATED (void setSelectedId (int, bool));
|
||||
JUCE_DEPRECATED (void setSelectedItemIndex (int, bool));
|
||||
JUCE_DEPRECATED (void setText (const String&, bool));
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
enum EditableState
|
||||
{
|
||||
editableUnknown,
|
||||
labelIsNotEditable,
|
||||
labelIsEditable
|
||||
};
|
||||
|
||||
PopupMenu currentMenu;
|
||||
Value currentId;
|
||||
int lastCurrentId = 0;
|
||||
bool isButtonDown = false, menuActive = false, scrollWheelEnabled = false;
|
||||
float mouseWheelAccumulator = 0;
|
||||
ListenerList<Listener> listeners;
|
||||
std::unique_ptr<Label> label;
|
||||
String textWhenNothingSelected, noChoicesMessage;
|
||||
EditableState labelEditableState = editableUnknown;
|
||||
|
||||
PopupMenu::Item* getItemForId (int) const noexcept;
|
||||
PopupMenu::Item* getItemForIndex (int) const noexcept;
|
||||
bool selectIfEnabled (int index);
|
||||
bool nudgeSelectedItem (int delta);
|
||||
void sendChange (NotificationType);
|
||||
void showPopupIfNotActive();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBox)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
84
modules/juce_gui_basics/widgets/juce_ImageComponent.cpp
Normal file
84
modules/juce_gui_basics/widgets/juce_ImageComponent.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
ImageComponent::ImageComponent (const String& name)
|
||||
: Component (name),
|
||||
placement (RectanglePlacement::centred)
|
||||
{
|
||||
}
|
||||
|
||||
ImageComponent::~ImageComponent()
|
||||
{
|
||||
}
|
||||
|
||||
void ImageComponent::setImage (const Image& newImage)
|
||||
{
|
||||
if (image != newImage)
|
||||
{
|
||||
image = newImage;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageComponent::setImage (const Image& newImage, RectanglePlacement placementToUse)
|
||||
{
|
||||
if (image != newImage || placement != placementToUse)
|
||||
{
|
||||
image = newImage;
|
||||
placement = placementToUse;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageComponent::setImagePlacement (RectanglePlacement newPlacement)
|
||||
{
|
||||
if (placement != newPlacement)
|
||||
{
|
||||
placement = newPlacement;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
const Image& ImageComponent::getImage() const
|
||||
{
|
||||
return image;
|
||||
}
|
||||
|
||||
RectanglePlacement ImageComponent::getImagePlacement() const
|
||||
{
|
||||
return placement;
|
||||
}
|
||||
|
||||
void ImageComponent::paint (Graphics& g)
|
||||
{
|
||||
g.setOpacity (1.0f);
|
||||
g.drawImage (image, getLocalBounds().toFloat(), placement);
|
||||
}
|
||||
|
||||
} // namespace juce
|
80
modules/juce_gui_basics/widgets/juce_ImageComponent.h
Normal file
80
modules/juce_gui_basics/widgets/juce_ImageComponent.h
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that simply displays an image.
|
||||
|
||||
Use setImage to give it an image, and it'll display it - simple as that!
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ImageComponent : public Component,
|
||||
public SettableTooltipClient
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ImageComponent. */
|
||||
ImageComponent (const String& componentName = String());
|
||||
|
||||
/** Destructor. */
|
||||
~ImageComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the image that should be displayed. */
|
||||
void setImage (const Image& newImage);
|
||||
|
||||
/** Sets the image that should be displayed, and its placement within the component. */
|
||||
void setImage (const Image& newImage,
|
||||
RectanglePlacement placementToUse);
|
||||
|
||||
/** Returns the current image. */
|
||||
const Image& getImage() const;
|
||||
|
||||
/** Sets the method of positioning that will be used to fit the image within the component's bounds.
|
||||
By default the positioning is centred, and will fit the image inside the component's bounds
|
||||
whilst keeping its aspect ratio correct, but you can change it to whatever layout you need.
|
||||
*/
|
||||
void setImagePlacement (RectanglePlacement newPlacement);
|
||||
|
||||
/** Returns the current image placement. */
|
||||
RectanglePlacement getImagePlacement() const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
|
||||
private:
|
||||
Image image;
|
||||
RectanglePlacement placement;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImageComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
481
modules/juce_gui_basics/widgets/juce_Label.cpp
Normal file
481
modules/juce_gui_basics/widgets/juce_Label.cpp
Normal file
@ -0,0 +1,481 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
Label::Label (const String& name, const String& labelText)
|
||||
: Component (name),
|
||||
textValue (labelText),
|
||||
lastTextValue (labelText)
|
||||
{
|
||||
setColour (TextEditor::textColourId, Colours::black);
|
||||
setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
|
||||
setColour (TextEditor::outlineColourId, Colours::transparentBlack);
|
||||
|
||||
textValue.addListener (this);
|
||||
}
|
||||
|
||||
Label::~Label()
|
||||
{
|
||||
textValue.removeListener (this);
|
||||
|
||||
if (ownerComponent != nullptr)
|
||||
ownerComponent->removeComponentListener (this);
|
||||
|
||||
editor.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Label::setText (const String& newText, NotificationType notification)
|
||||
{
|
||||
hideEditor (true);
|
||||
|
||||
if (lastTextValue != newText)
|
||||
{
|
||||
lastTextValue = newText;
|
||||
textValue = newText;
|
||||
repaint();
|
||||
|
||||
textWasChanged();
|
||||
|
||||
if (ownerComponent != nullptr)
|
||||
componentMovedOrResized (*ownerComponent, true, true);
|
||||
|
||||
if (notification != dontSendNotification)
|
||||
callChangeListeners();
|
||||
}
|
||||
}
|
||||
|
||||
String Label::getText (bool returnActiveEditorContents) const
|
||||
{
|
||||
return (returnActiveEditorContents && isBeingEdited())
|
||||
? editor->getText()
|
||||
: textValue.toString();
|
||||
}
|
||||
|
||||
void Label::valueChanged (Value&)
|
||||
{
|
||||
if (lastTextValue != textValue.toString())
|
||||
setText (textValue.toString(), sendNotification);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Label::setFont (const Font& newFont)
|
||||
{
|
||||
if (font != newFont)
|
||||
{
|
||||
font = newFont;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
Font Label::getFont() const noexcept
|
||||
{
|
||||
return font;
|
||||
}
|
||||
|
||||
void Label::setEditable (bool editOnSingleClick,
|
||||
bool editOnDoubleClick,
|
||||
bool lossOfFocusDiscards)
|
||||
{
|
||||
editSingleClick = editOnSingleClick;
|
||||
editDoubleClick = editOnDoubleClick;
|
||||
lossOfFocusDiscardsChanges = lossOfFocusDiscards;
|
||||
|
||||
setWantsKeyboardFocus (editOnSingleClick || editOnDoubleClick);
|
||||
setFocusContainer (editOnSingleClick || editOnDoubleClick);
|
||||
}
|
||||
|
||||
void Label::setJustificationType (Justification newJustification)
|
||||
{
|
||||
if (justification != newJustification)
|
||||
{
|
||||
justification = newJustification;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::setBorderSize (BorderSize<int> newBorder)
|
||||
{
|
||||
if (border != newBorder)
|
||||
{
|
||||
border = newBorder;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Component* Label::getAttachedComponent() const
|
||||
{
|
||||
return static_cast<Component*> (ownerComponent);
|
||||
}
|
||||
|
||||
void Label::attachToComponent (Component* owner, bool onLeft)
|
||||
{
|
||||
if (ownerComponent != nullptr)
|
||||
ownerComponent->removeComponentListener (this);
|
||||
|
||||
ownerComponent = owner;
|
||||
|
||||
leftOfOwnerComp = onLeft;
|
||||
|
||||
if (ownerComponent != nullptr)
|
||||
{
|
||||
setVisible (owner->isVisible());
|
||||
ownerComponent->addComponentListener (this);
|
||||
componentParentHierarchyChanged (*ownerComponent);
|
||||
componentMovedOrResized (*ownerComponent, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Label::componentMovedOrResized (Component& component, bool /*wasMoved*/, bool /*wasResized*/)
|
||||
{
|
||||
auto f = getLookAndFeel().getLabelFont (*this);
|
||||
|
||||
if (leftOfOwnerComp)
|
||||
{
|
||||
auto width = jmin (roundToInt (f.getStringWidthFloat (textValue.toString()) + 0.5f)
|
||||
+ getBorderSize().getLeftAndRight(),
|
||||
component.getX());
|
||||
|
||||
setBounds (component.getX() - width, component.getY(), width, component.getHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto height = getBorderSize().getTopAndBottom() + 6 + roundToInt (f.getHeight() + 0.5f);
|
||||
|
||||
setBounds (component.getX(), component.getY() - height, component.getWidth(), height);
|
||||
}
|
||||
}
|
||||
|
||||
void Label::componentParentHierarchyChanged (Component& component)
|
||||
{
|
||||
if (auto* parent = component.getParentComponent())
|
||||
parent->addChildComponent (this);
|
||||
}
|
||||
|
||||
void Label::componentVisibilityChanged (Component& component)
|
||||
{
|
||||
setVisible (component.isVisible());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Label::textWasEdited() {}
|
||||
void Label::textWasChanged() {}
|
||||
|
||||
void Label::editorShown (TextEditor* textEditor)
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [this, textEditor] (Label::Listener& l) { l.editorShown (this, *textEditor); });
|
||||
|
||||
if (checker.shouldBailOut())
|
||||
return;
|
||||
|
||||
if (onEditorShow != nullptr)
|
||||
onEditorShow();
|
||||
}
|
||||
|
||||
void Label::editorAboutToBeHidden (TextEditor* textEditor)
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
peer->dismissPendingTextInput();
|
||||
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [this, textEditor] (Label::Listener& l) { l.editorHidden (this, *textEditor); });
|
||||
|
||||
if (checker.shouldBailOut())
|
||||
return;
|
||||
|
||||
if (onEditorHide != nullptr)
|
||||
onEditorHide();
|
||||
}
|
||||
|
||||
void Label::showEditor()
|
||||
{
|
||||
if (editor == nullptr)
|
||||
{
|
||||
editor.reset (createEditorComponent());
|
||||
addAndMakeVisible (editor.get());
|
||||
editor->setText (getText(), false);
|
||||
editor->setKeyboardType (keyboardType);
|
||||
editor->addListener (this);
|
||||
editor->grabKeyboardFocus();
|
||||
|
||||
if (editor == nullptr) // may be deleted by a callback
|
||||
return;
|
||||
|
||||
editor->setHighlightedRegion (Range<int> (0, textValue.toString().length()));
|
||||
|
||||
resized();
|
||||
repaint();
|
||||
|
||||
editorShown (editor.get());
|
||||
|
||||
enterModalState (false);
|
||||
editor->grabKeyboardFocus();
|
||||
}
|
||||
}
|
||||
|
||||
bool Label::updateFromTextEditorContents (TextEditor& ed)
|
||||
{
|
||||
auto newText = ed.getText();
|
||||
|
||||
if (textValue.toString() != newText)
|
||||
{
|
||||
lastTextValue = newText;
|
||||
textValue = newText;
|
||||
repaint();
|
||||
|
||||
textWasChanged();
|
||||
|
||||
if (ownerComponent != nullptr)
|
||||
componentMovedOrResized (*ownerComponent, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Label::hideEditor (bool discardCurrentEditorContents)
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
WeakReference<Component> deletionChecker (this);
|
||||
std::unique_ptr<TextEditor> outgoingEditor;
|
||||
std::swap (outgoingEditor, editor);
|
||||
|
||||
editorAboutToBeHidden (outgoingEditor.get());
|
||||
|
||||
const bool changed = (! discardCurrentEditorContents)
|
||||
&& updateFromTextEditorContents (*outgoingEditor);
|
||||
outgoingEditor.reset();
|
||||
repaint();
|
||||
|
||||
if (changed)
|
||||
textWasEdited();
|
||||
|
||||
if (deletionChecker != nullptr)
|
||||
exitModalState (0);
|
||||
|
||||
if (changed && deletionChecker != nullptr)
|
||||
callChangeListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::inputAttemptWhenModal()
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
if (lossOfFocusDiscardsChanges)
|
||||
textEditorEscapeKeyPressed (*editor);
|
||||
else
|
||||
textEditorReturnKeyPressed (*editor);
|
||||
}
|
||||
}
|
||||
|
||||
bool Label::isBeingEdited() const noexcept
|
||||
{
|
||||
return editor != nullptr;
|
||||
}
|
||||
|
||||
static void copyColourIfSpecified (Label& l, TextEditor& ed, int colourID, int targetColourID)
|
||||
{
|
||||
if (l.isColourSpecified (colourID) || l.getLookAndFeel().isColourSpecified (colourID))
|
||||
ed.setColour (targetColourID, l.findColour (colourID));
|
||||
}
|
||||
|
||||
TextEditor* Label::createEditorComponent()
|
||||
{
|
||||
auto* ed = new TextEditor (getName());
|
||||
ed->applyFontToAllText (getLookAndFeel().getLabelFont (*this));
|
||||
copyAllExplicitColoursTo (*ed);
|
||||
|
||||
copyColourIfSpecified (*this, *ed, textWhenEditingColourId, TextEditor::textColourId);
|
||||
copyColourIfSpecified (*this, *ed, backgroundWhenEditingColourId, TextEditor::backgroundColourId);
|
||||
copyColourIfSpecified (*this, *ed, outlineWhenEditingColourId, TextEditor::focusedOutlineColourId);
|
||||
|
||||
return ed;
|
||||
}
|
||||
|
||||
TextEditor* Label::getCurrentTextEditor() const noexcept
|
||||
{
|
||||
return editor.get();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Label::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawLabel (g, *this);
|
||||
}
|
||||
|
||||
void Label::mouseUp (const MouseEvent& e)
|
||||
{
|
||||
if (editSingleClick
|
||||
&& isEnabled()
|
||||
&& contains (e.getPosition())
|
||||
&& ! (e.mouseWasDraggedSinceMouseDown() || e.mods.isPopupMenu()))
|
||||
{
|
||||
showEditor();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::mouseDoubleClick (const MouseEvent& e)
|
||||
{
|
||||
if (editDoubleClick
|
||||
&& isEnabled()
|
||||
&& ! e.mods.isPopupMenu())
|
||||
showEditor();
|
||||
}
|
||||
|
||||
void Label::resized()
|
||||
{
|
||||
if (editor != nullptr)
|
||||
editor->setBounds (getLocalBounds());
|
||||
}
|
||||
|
||||
void Label::focusGained (FocusChangeType cause)
|
||||
{
|
||||
if (editSingleClick
|
||||
&& isEnabled()
|
||||
&& cause == focusChangedByTabKey)
|
||||
showEditor();
|
||||
}
|
||||
|
||||
void Label::enablementChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Label::colourChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Label::setMinimumHorizontalScale (const float newScale)
|
||||
{
|
||||
if (minimumHorizontalScale != newScale)
|
||||
{
|
||||
minimumHorizontalScale = newScale;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// We'll use a custom focus traverser here to make sure focus goes from the
|
||||
// text editor to another component rather than back to the label itself.
|
||||
class LabelKeyboardFocusTraverser : public KeyboardFocusTraverser
|
||||
{
|
||||
public:
|
||||
LabelKeyboardFocusTraverser() {}
|
||||
|
||||
Component* getNextComponent (Component* c) override { return KeyboardFocusTraverser::getNextComponent (getComp (c)); }
|
||||
Component* getPreviousComponent (Component* c) override { return KeyboardFocusTraverser::getPreviousComponent (getComp (c)); }
|
||||
|
||||
static Component* getComp (Component* current)
|
||||
{
|
||||
return dynamic_cast<TextEditor*> (current) != nullptr
|
||||
? current->getParentComponent() : current;
|
||||
}
|
||||
};
|
||||
|
||||
KeyboardFocusTraverser* Label::createFocusTraverser()
|
||||
{
|
||||
return new LabelKeyboardFocusTraverser();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Label::addListener (Label::Listener* l) { listeners.add (l); }
|
||||
void Label::removeListener (Label::Listener* l) { listeners.remove (l); }
|
||||
|
||||
void Label::callChangeListeners()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [this] (Listener& l) { l.labelTextChanged (this); });
|
||||
|
||||
if (checker.shouldBailOut())
|
||||
return;
|
||||
|
||||
if (onTextChange != nullptr)
|
||||
onTextChange();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Label::textEditorTextChanged (TextEditor& ed)
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
jassert (&ed == editor.get());
|
||||
|
||||
if (! (hasKeyboardFocus (true) || isCurrentlyBlockedByAnotherModalComponent()))
|
||||
{
|
||||
if (lossOfFocusDiscardsChanges)
|
||||
textEditorEscapeKeyPressed (ed);
|
||||
else
|
||||
textEditorReturnKeyPressed (ed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Label::textEditorReturnKeyPressed (TextEditor& ed)
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
jassert (&ed == editor.get());
|
||||
|
||||
WeakReference<Component> deletionChecker (this);
|
||||
bool changed = updateFromTextEditorContents (ed);
|
||||
hideEditor (true);
|
||||
|
||||
if (changed && deletionChecker != nullptr)
|
||||
{
|
||||
textWasEdited();
|
||||
|
||||
if (deletionChecker != nullptr)
|
||||
callChangeListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Label::textEditorEscapeKeyPressed (TextEditor& ed)
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
jassert (&ed == editor.get());
|
||||
ignoreUnused (ed);
|
||||
|
||||
editor->setText (textValue.toString(), false);
|
||||
hideEditor (true);
|
||||
}
|
||||
}
|
||||
|
||||
void Label::textEditorFocusLost (TextEditor& ed)
|
||||
{
|
||||
textEditorTextChanged (ed);
|
||||
}
|
||||
|
||||
} // namespace juce
|
365
modules/juce_gui_basics/widgets/juce_Label.h
Normal file
365
modules/juce_gui_basics/widgets/juce_Label.h
Normal file
@ -0,0 +1,365 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that displays a text string, and can optionally become a text
|
||||
editor when clicked.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API Label : public Component,
|
||||
public SettableTooltipClient,
|
||||
protected TextEditor::Listener,
|
||||
private ComponentListener,
|
||||
private Value::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a Label.
|
||||
|
||||
@param componentName the name to give the component
|
||||
@param labelText the text to show in the label
|
||||
*/
|
||||
Label (const String& componentName = String(),
|
||||
const String& labelText = String());
|
||||
|
||||
/** Destructor. */
|
||||
~Label();
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the label text.
|
||||
|
||||
The NotificationType parameter indicates whether to send a change message to
|
||||
any Label::Listener objects if the new text is different.
|
||||
*/
|
||||
void setText (const String& newText,
|
||||
NotificationType notification);
|
||||
|
||||
/** Returns the label's current text.
|
||||
|
||||
@param returnActiveEditorContents if this is true and the label is currently
|
||||
being edited, then this method will return the
|
||||
text as it's being shown in the editor. If false,
|
||||
then the value returned here won't be updated until
|
||||
the user has finished typing and pressed the return
|
||||
key.
|
||||
*/
|
||||
String getText (bool returnActiveEditorContents = false) const;
|
||||
|
||||
/** Returns the text content as a Value object.
|
||||
You can call Value::referTo() on this object to make the label read and control
|
||||
a Value object that you supply.
|
||||
*/
|
||||
Value& getTextValue() noexcept { return textValue; }
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the font to use to draw the text.
|
||||
@see getFont
|
||||
*/
|
||||
void setFont (const Font& newFont);
|
||||
|
||||
/** Returns the font currently being used.
|
||||
This may be the one set by setFont(), unless it has been overridden by the current LookAndFeel
|
||||
@see setFont
|
||||
*/
|
||||
Font getFont() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the label.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
Note that you can also use the constants from TextEditor::ColourIds to change the
|
||||
colour of the text editor that is opened when a label is editable.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1000280, /**< The background colour to fill the label with. */
|
||||
textColourId = 0x1000281, /**< The colour for the text. */
|
||||
outlineColourId = 0x1000282, /**< An optional colour to use to draw a border around the label.
|
||||
Leave this transparent to not have an outline. */
|
||||
backgroundWhenEditingColourId = 0x1000283, /**< The background colour when the label is being edited. */
|
||||
textWhenEditingColourId = 0x1000284, /**< The colour for the text when the label is being edited. */
|
||||
outlineWhenEditingColourId = 0x1000285 /**< An optional border colour when the label is being edited. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the style of justification to be used for positioning the text.
|
||||
(The default is Justification::centredLeft)
|
||||
*/
|
||||
void setJustificationType (Justification justification);
|
||||
|
||||
/** Returns the type of justification, as set in setJustificationType(). */
|
||||
Justification getJustificationType() const noexcept { return justification; }
|
||||
|
||||
/** Changes the border that is left between the edge of the component and the text.
|
||||
By default there's a small gap left at the sides of the component to allow for
|
||||
the drawing of the border, but you can change this if necessary.
|
||||
*/
|
||||
void setBorderSize (BorderSize<int> newBorderSize);
|
||||
|
||||
/** Returns the size of the border to be left around the text. */
|
||||
BorderSize<int> getBorderSize() const noexcept { return border; }
|
||||
|
||||
/** Makes this label "stick to" another component.
|
||||
|
||||
This will cause the label to follow another component around, staying
|
||||
either to its left or above it.
|
||||
|
||||
@param owner the component to follow
|
||||
@param onLeft if true, the label will stay on the left of its component; if
|
||||
false, it will stay above it.
|
||||
*/
|
||||
void attachToComponent (Component* owner, bool onLeft);
|
||||
|
||||
/** If this label has been attached to another component using attachToComponent, this
|
||||
returns the other component.
|
||||
|
||||
Returns nullptr if the label is not attached.
|
||||
*/
|
||||
Component* getAttachedComponent() const;
|
||||
|
||||
/** If the label is attached to the left of another component, this returns true.
|
||||
|
||||
Returns false if the label is above the other component. This is only relevant if
|
||||
attachToComponent() has been called.
|
||||
*/
|
||||
bool isAttachedOnLeft() const noexcept { return leftOfOwnerComp; }
|
||||
|
||||
/** Specifies the minimum amount that the font can be squashed horizontally before it starts
|
||||
using ellipsis. Use a value of 0 for a default value.
|
||||
|
||||
@see Graphics::drawFittedText
|
||||
*/
|
||||
void setMinimumHorizontalScale (float newScale);
|
||||
|
||||
/** Specifies the amount that the font can be squashed horizontally. */
|
||||
float getMinimumHorizontalScale() const noexcept { return minimumHorizontalScale; }
|
||||
|
||||
/** Set a keyboard type for use when the text editor is shown. */
|
||||
void setKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept { keyboardType = type; }
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class for receiving events from a Label.
|
||||
|
||||
You can register a Label::Listener with a Label using the Label::addListener()
|
||||
method, and it will be called when the text of the label changes, either because
|
||||
of a call to Label::setText() or by the user editing the text (if the label is
|
||||
editable).
|
||||
|
||||
@see Label::addListener, Label::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called when a Label's text has changed. */
|
||||
virtual void labelTextChanged (Label* labelThatHasChanged) = 0;
|
||||
|
||||
/** Called when a Label goes into editing mode and displays a TextEditor. */
|
||||
virtual void editorShown (Label*, TextEditor&) {}
|
||||
|
||||
/** Called when a Label is about to delete its TextEditor and exit editing mode. */
|
||||
virtual void editorHidden (Label*, TextEditor&) {}
|
||||
};
|
||||
|
||||
/** Registers a listener that will be called when the label's text changes. */
|
||||
void addListener (Listener* listener);
|
||||
|
||||
/** Deregisters a previously-registered listener. */
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** You can assign a lambda to this callback object to have it called when the label text is changed. */
|
||||
std::function<void()> onTextChange;
|
||||
|
||||
/** You can assign a lambda to this callback object to have it called when the label's editor is shown. */
|
||||
std::function<void()> onEditorShow;
|
||||
|
||||
/** You can assign a lambda to this callback object to have it called when the label's editor is hidden. */
|
||||
std::function<void()> onEditorHide;
|
||||
|
||||
//==============================================================================
|
||||
/** Makes the label turn into a TextEditor when clicked.
|
||||
|
||||
By default this is turned off.
|
||||
|
||||
If turned on, then single- or double-clicking will turn the label into
|
||||
an editor. If the user then changes the text, then the ChangeBroadcaster
|
||||
base class will be used to send change messages to any listeners that
|
||||
have registered.
|
||||
|
||||
If the user changes the text, the textWasEdited() method will be called
|
||||
afterwards, and subclasses can override this if they need to do anything
|
||||
special.
|
||||
|
||||
@param editOnSingleClick if true, just clicking once on the label will start editing the text
|
||||
@param editOnDoubleClick if true, a double-click is needed to start editing
|
||||
@param lossOfFocusDiscardsChanges if true, clicking somewhere else while the text is being
|
||||
edited will discard any changes; if false, then this will
|
||||
commit the changes.
|
||||
@see showEditor, setEditorColours, TextEditor
|
||||
*/
|
||||
void setEditable (bool editOnSingleClick,
|
||||
bool editOnDoubleClick = false,
|
||||
bool lossOfFocusDiscardsChanges = false);
|
||||
|
||||
/** Returns true if this option was set using setEditable(). */
|
||||
bool isEditableOnSingleClick() const noexcept { return editSingleClick; }
|
||||
|
||||
/** Returns true if this option was set using setEditable(). */
|
||||
bool isEditableOnDoubleClick() const noexcept { return editDoubleClick; }
|
||||
|
||||
/** Returns true if this option has been set in a call to setEditable(). */
|
||||
bool doesLossOfFocusDiscardChanges() const noexcept { return lossOfFocusDiscardsChanges; }
|
||||
|
||||
/** Returns true if the user can edit this label's text. */
|
||||
bool isEditable() const noexcept { return editSingleClick || editDoubleClick; }
|
||||
|
||||
/** Makes the editor appear as if the label had been clicked by the user.
|
||||
@see textWasEdited, setEditable
|
||||
*/
|
||||
void showEditor();
|
||||
|
||||
/** Hides the editor if it was being shown.
|
||||
|
||||
@param discardCurrentEditorContents if true, the label's text will be
|
||||
reset to whatever it was before the editor
|
||||
was shown; if false, the current contents of the
|
||||
editor will be used to set the label's text
|
||||
before it is hidden.
|
||||
*/
|
||||
void hideEditor (bool discardCurrentEditorContents);
|
||||
|
||||
/** Returns true if the editor is currently focused and active. */
|
||||
bool isBeingEdited() const noexcept;
|
||||
|
||||
/** Returns the currently-visible text editor, or nullptr if none is open. */
|
||||
TextEditor* getCurrentTextEditor() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
label drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void drawLabel (Graphics&, Label&) = 0;
|
||||
virtual Font getLabelFont (Label&) = 0;
|
||||
};
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates the TextEditor component that will be used when the user has clicked on the label.
|
||||
Subclasses can override this if they need to customise this component in some way.
|
||||
*/
|
||||
virtual TextEditor* createEditorComponent();
|
||||
|
||||
/** Called after the user changes the text. */
|
||||
virtual void textWasEdited();
|
||||
|
||||
/** Called when the text has been altered. */
|
||||
virtual void textWasChanged();
|
||||
|
||||
/** Called when the text editor has just appeared, due to a user click or other focus change. */
|
||||
virtual void editorShown (TextEditor*);
|
||||
|
||||
/** Called when the text editor is going to be deleted, after editing has finished. */
|
||||
virtual void editorAboutToBeHidden (TextEditor*);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDoubleClick (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
|
||||
/** @internal */
|
||||
void componentParentHierarchyChanged (Component&) override;
|
||||
/** @internal */
|
||||
void componentVisibilityChanged (Component&) override;
|
||||
/** @internal */
|
||||
void inputAttemptWhenModal() override;
|
||||
/** @internal */
|
||||
void focusGained (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
KeyboardFocusTraverser* createFocusTraverser() override;
|
||||
/** @internal */
|
||||
void textEditorTextChanged (TextEditor&) override;
|
||||
/** @internal */
|
||||
void textEditorReturnKeyPressed (TextEditor&) override;
|
||||
/** @internal */
|
||||
void textEditorEscapeKeyPressed (TextEditor&) override;
|
||||
/** @internal */
|
||||
void textEditorFocusLost (TextEditor&) override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
void valueChanged (Value&) override;
|
||||
/** @internal */
|
||||
void callChangeListeners();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Value textValue;
|
||||
String lastTextValue;
|
||||
Font font { 15.0f };
|
||||
Justification justification = Justification::centredLeft;
|
||||
std::unique_ptr<TextEditor> editor;
|
||||
ListenerList<Listener> listeners;
|
||||
WeakReference<Component> ownerComponent;
|
||||
BorderSize<int> border { 1, 5, 1, 5 };
|
||||
float minimumHorizontalScale = 0;
|
||||
TextInputTarget::VirtualKeyboardType keyboardType = TextInputTarget::textKeyboard;
|
||||
bool editSingleClick = false;
|
||||
bool editDoubleClick = false;
|
||||
bool lossOfFocusDiscardsChanges = false;
|
||||
bool leftOfOwnerComp = false;
|
||||
|
||||
bool updateFromTextEditorContents (TextEditor&);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Label)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
957
modules/juce_gui_basics/widgets/juce_ListBox.cpp
Normal file
957
modules/juce_gui_basics/widgets/juce_ListBox.cpp
Normal file
@ -0,0 +1,957 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
606
modules/juce_gui_basics/widgets/juce_ListBox.h
Normal file
606
modules/juce_gui_basics/widgets/juce_ListBox.h
Normal file
@ -0,0 +1,606 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A subclass of this is used to drive a ListBox.
|
||||
|
||||
@see ListBox
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ListBoxModel
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~ListBoxModel() {}
|
||||
|
||||
//==============================================================================
|
||||
/** This has to return the number of items in the list.
|
||||
@see ListBox::getNumRows()
|
||||
*/
|
||||
virtual int getNumRows() = 0;
|
||||
|
||||
/** This method must be implemented to draw a row of the list.
|
||||
Note that the rowNumber value may be greater than the number of rows in your
|
||||
list, so be careful that you don't assume it's less than getNumRows().
|
||||
*/
|
||||
virtual void paintListBoxItem (int rowNumber,
|
||||
Graphics& g,
|
||||
int width, int height,
|
||||
bool rowIsSelected) = 0;
|
||||
|
||||
/** This is used to create or update a custom component to go in a row of the list.
|
||||
|
||||
Any row may contain a custom component, or can just be drawn with the paintListBoxItem() method
|
||||
and handle mouse clicks with listBoxItemClicked().
|
||||
|
||||
This method will be called whenever a custom component might need to be updated - e.g.
|
||||
when the list is changed, or ListBox::updateContent() is called.
|
||||
|
||||
If you don't need a custom component for the specified row, then return nullptr.
|
||||
(Bear in mind that even if you're not creating a new component, you may still need to
|
||||
delete existingComponentToUpdate if it's non-null).
|
||||
|
||||
If you do want a custom component, and the existingComponentToUpdate is null, then
|
||||
this method must create a suitable new component and return it.
|
||||
|
||||
If the existingComponentToUpdate is non-null, it will be a pointer to a component previously created
|
||||
by this method. In this case, the method must either update it to make sure it's correctly representing
|
||||
the given row (which may be different from the one that the component was created for), or it can
|
||||
delete this component and return a new one.
|
||||
|
||||
The component that your method returns will be deleted by the ListBox when it is no longer needed.
|
||||
|
||||
Bear in mind that if you put a custom component inside the row but still want the
|
||||
listbox to automatically handle clicking, selection, etc, then you'll need to make sure
|
||||
your custom component doesn't intercept all the mouse events that land on it, e.g by
|
||||
using Component::setInterceptsMouseClicks().
|
||||
*/
|
||||
virtual Component* refreshComponentForRow (int rowNumber, bool isRowSelected,
|
||||
Component* existingComponentToUpdate);
|
||||
|
||||
/** This can be overridden to react to the user clicking on a row.
|
||||
@see listBoxItemDoubleClicked
|
||||
*/
|
||||
virtual void listBoxItemClicked (int row, const MouseEvent&);
|
||||
|
||||
/** This can be overridden to react to the user double-clicking on a row.
|
||||
@see listBoxItemClicked
|
||||
*/
|
||||
virtual void listBoxItemDoubleClicked (int row, const MouseEvent&);
|
||||
|
||||
/** This can be overridden to react to the user clicking on a part of the list where
|
||||
there are no rows.
|
||||
@see listBoxItemClicked
|
||||
*/
|
||||
virtual void backgroundClicked (const MouseEvent&);
|
||||
|
||||
/** Override this to be informed when rows are selected or deselected.
|
||||
|
||||
This will be called whenever a row is selected or deselected. If a range of
|
||||
rows is selected all at once, this will just be called once for that event.
|
||||
|
||||
@param lastRowSelected the last row that the user selected. If no
|
||||
rows are currently selected, this may be -1.
|
||||
*/
|
||||
virtual void selectedRowsChanged (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the delete key is pressed.
|
||||
|
||||
If no rows are selected when they press the key, this won't be called.
|
||||
|
||||
@param lastRowSelected the last row that had been selected when they pressed the
|
||||
key - if there are multiple selections, this might not be
|
||||
very useful
|
||||
*/
|
||||
virtual void deleteKeyPressed (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the return key is pressed.
|
||||
|
||||
If no rows are selected when they press the key, this won't be called.
|
||||
|
||||
@param lastRowSelected the last row that had been selected when they pressed the
|
||||
key - if there are multiple selections, this might not be
|
||||
very useful
|
||||
*/
|
||||
virtual void returnKeyPressed (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the list is scrolled.
|
||||
|
||||
This might be caused by the user moving the scrollbar, or by programmatic changes
|
||||
to the list position.
|
||||
*/
|
||||
virtual void listWasScrolled();
|
||||
|
||||
/** To allow rows from your list to be dragged-and-dropped, implement this method.
|
||||
|
||||
If this returns a non-null variant then when the user drags a row, the listbox will
|
||||
try to find a DragAndDropContainer in its parent hierarchy, and will use it to trigger
|
||||
a drag-and-drop operation, using this string as the source description, with the listbox
|
||||
itself as the source component.
|
||||
|
||||
@see DragAndDropContainer::startDragging
|
||||
*/
|
||||
virtual var getDragSourceDescription (const SparseSet<int>& rowsToDescribe);
|
||||
|
||||
/** You can override this to provide tool tips for specific rows.
|
||||
@see TooltipClient
|
||||
*/
|
||||
virtual String getTooltipForRow (int row);
|
||||
|
||||
/** You can override this to return a custom mouse cursor for each row. */
|
||||
virtual MouseCursor getMouseCursorForRow (int row);
|
||||
|
||||
private:
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// This method's signature has changed to take a MouseEvent parameter - please update your code!
|
||||
JUCE_DEPRECATED_WITH_BODY (virtual int backgroundClicked(), { return 0; })
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A list of items that can be scrolled vertically.
|
||||
|
||||
To create a list, you'll need to create a subclass of ListBoxModel. This can
|
||||
either paint each row of the list and respond to events via callbacks, or for
|
||||
more specialised tasks, it can supply a custom component to fill each row.
|
||||
|
||||
@see ComboBox, TableListBox
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ListBox : public Component,
|
||||
public SettableTooltipClient
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ListBox.
|
||||
|
||||
The model pointer passed-in can be null, in which case you can set it later
|
||||
with setModel().
|
||||
*/
|
||||
ListBox (const String& componentName = String(),
|
||||
ListBoxModel* model = nullptr);
|
||||
|
||||
/** Destructor. */
|
||||
~ListBox();
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the current data model to display. */
|
||||
void setModel (ListBoxModel* newModel);
|
||||
|
||||
/** Returns the current list model. */
|
||||
ListBoxModel* getModel() const noexcept { return model; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Causes the list to refresh its content.
|
||||
|
||||
Call this when the number of rows in the list changes, or if you want it
|
||||
to call refreshComponentForRow() on all the row components.
|
||||
|
||||
This must only be called from the main message thread.
|
||||
*/
|
||||
void updateContent();
|
||||
|
||||
//==============================================================================
|
||||
/** Turns on multiple-selection of rows.
|
||||
|
||||
By default this is disabled.
|
||||
|
||||
When your row component gets clicked you'll need to call the
|
||||
selectRowsBasedOnModifierKeys() method to tell the list that it's been
|
||||
clicked and to get it to do the appropriate selection based on whether
|
||||
the ctrl/shift keys are held down.
|
||||
*/
|
||||
void setMultipleSelectionEnabled (bool shouldBeEnabled) noexcept;
|
||||
|
||||
/** If enabled, this makes the listbox flip the selection status of
|
||||
each row that the user clicks, without affecting other selected rows.
|
||||
|
||||
(This only has an effect if multiple selection is also enabled).
|
||||
If not enabled, you can still get the same row-flipping behaviour by holding
|
||||
down CMD or CTRL when clicking.
|
||||
*/
|
||||
void setClickingTogglesRowSelection (bool flipRowSelection) noexcept;
|
||||
|
||||
/** Sets whether a row should be selected when the mouse is pressed or released.
|
||||
By default this is true, but you may want to turn it off.
|
||||
*/
|
||||
void setRowSelectedOnMouseDown (bool isSelectedOnMouseDown) noexcept;
|
||||
|
||||
/** Makes the list react to mouse moves by selecting the row that the mouse if over.
|
||||
|
||||
This function is here primarily for the ComboBox class to use, but might be
|
||||
useful for some other purpose too.
|
||||
*/
|
||||
void setMouseMoveSelectsRows (bool shouldSelect);
|
||||
|
||||
//==============================================================================
|
||||
/** Selects a row.
|
||||
|
||||
If the row is already selected, this won't do anything.
|
||||
|
||||
@param rowNumber the row to select
|
||||
@param dontScrollToShowThisRow if true, the list's position won't change; if false and
|
||||
the selected row is off-screen, it'll scroll to make
|
||||
sure that row is on-screen
|
||||
@param deselectOthersFirst if true and there are multiple selections, these will
|
||||
first be deselected before this item is selected
|
||||
@see isRowSelected, selectRowsBasedOnModifierKeys, flipRowSelection, deselectRow,
|
||||
deselectAllRows, selectRangeOfRows
|
||||
*/
|
||||
void selectRow (int rowNumber,
|
||||
bool dontScrollToShowThisRow = false,
|
||||
bool deselectOthersFirst = true);
|
||||
|
||||
/** Selects a set of rows.
|
||||
|
||||
This will add these rows to the current selection, so you might need to
|
||||
clear the current selection first with deselectAllRows()
|
||||
|
||||
@param firstRow the first row to select (inclusive)
|
||||
@param lastRow the last row to select (inclusive)
|
||||
@param dontScrollToShowThisRange if true, the list's position won't change; if false and
|
||||
the selected range is off-screen, it'll scroll to make
|
||||
sure that the range of rows is on-screen
|
||||
*/
|
||||
void selectRangeOfRows (int firstRow,
|
||||
int lastRow,
|
||||
bool dontScrollToShowThisRange = false);
|
||||
|
||||
/** Deselects a row.
|
||||
If it's not currently selected, this will do nothing.
|
||||
@see selectRow, deselectAllRows
|
||||
*/
|
||||
void deselectRow (int rowNumber);
|
||||
|
||||
/** Deselects any currently selected rows.
|
||||
@see deselectRow
|
||||
*/
|
||||
void deselectAllRows();
|
||||
|
||||
/** Selects or deselects a row.
|
||||
If the row's currently selected, this deselects it, and vice-versa.
|
||||
*/
|
||||
void flipRowSelection (int rowNumber);
|
||||
|
||||
/** Returns a sparse set indicating the rows that are currently selected.
|
||||
@see setSelectedRows
|
||||
*/
|
||||
SparseSet<int> getSelectedRows() const;
|
||||
|
||||
/** Sets the rows that should be selected, based on an explicit set of ranges.
|
||||
|
||||
If sendNotificationEventToModel is true, the ListBoxModel::selectedRowsChanged()
|
||||
method will be called. If it's false, no notification will be sent to the model.
|
||||
|
||||
@see getSelectedRows
|
||||
*/
|
||||
void setSelectedRows (const SparseSet<int>& setOfRowsToBeSelected,
|
||||
NotificationType sendNotificationEventToModel = sendNotification);
|
||||
|
||||
/** Checks whether a row is selected.
|
||||
*/
|
||||
bool isRowSelected (int rowNumber) const;
|
||||
|
||||
/** Returns the number of rows that are currently selected.
|
||||
@see getSelectedRow, isRowSelected, getLastRowSelected
|
||||
*/
|
||||
int getNumSelectedRows() const;
|
||||
|
||||
/** Returns the row number of a selected row.
|
||||
|
||||
This will return the row number of the Nth selected row. The row numbers returned will
|
||||
be sorted in order from low to high.
|
||||
|
||||
@param index the index of the selected row to return, (from 0 to getNumSelectedRows() - 1)
|
||||
@returns the row number, or -1 if the index was out of range or if there aren't any rows
|
||||
selected
|
||||
@see getNumSelectedRows, isRowSelected, getLastRowSelected
|
||||
*/
|
||||
int getSelectedRow (int index = 0) const;
|
||||
|
||||
/** Returns the last row that the user selected.
|
||||
|
||||
This isn't the same as the highest row number that is currently selected - if the user
|
||||
had multiply-selected rows 10, 5 and then 6 in that order, this would return 6.
|
||||
|
||||
If nothing is selected, it will return -1.
|
||||
*/
|
||||
int getLastRowSelected() const;
|
||||
|
||||
/** Multiply-selects rows based on the modifier keys.
|
||||
|
||||
If no modifier keys are down, this will select the given row and
|
||||
deselect any others.
|
||||
|
||||
If the ctrl (or command on the Mac) key is down, it'll flip the
|
||||
state of the selected row.
|
||||
|
||||
If the shift key is down, it'll select up to the given row from the
|
||||
last row selected.
|
||||
|
||||
@see selectRow
|
||||
*/
|
||||
void selectRowsBasedOnModifierKeys (int rowThatWasClickedOn,
|
||||
ModifierKeys modifiers,
|
||||
bool isMouseUpEvent);
|
||||
|
||||
//==============================================================================
|
||||
/** Scrolls the list to a particular position.
|
||||
|
||||
The proportion is between 0 and 1.0, so 0 scrolls to the top of the list,
|
||||
1.0 scrolls to the bottom.
|
||||
|
||||
If the total number of rows all fit onto the screen at once, then this
|
||||
method won't do anything.
|
||||
|
||||
@see getVerticalPosition
|
||||
*/
|
||||
void setVerticalPosition (double newProportion);
|
||||
|
||||
/** Returns the current vertical position as a proportion of the total.
|
||||
|
||||
This can be used in conjunction with setVerticalPosition() to save and restore
|
||||
the list's position. It returns a value in the range 0 to 1.
|
||||
|
||||
@see setVerticalPosition
|
||||
*/
|
||||
double getVerticalPosition() const;
|
||||
|
||||
/** Scrolls if necessary to make sure that a particular row is visible. */
|
||||
void scrollToEnsureRowIsOnscreen (int row);
|
||||
|
||||
/** Returns a reference to the vertical scrollbar. */
|
||||
ScrollBar& getVerticalScrollBar() const noexcept;
|
||||
|
||||
/** Returns a reference to the horizontal scrollbar. */
|
||||
ScrollBar& getHorizontalScrollBar() const noexcept;
|
||||
|
||||
/** Finds the row index that contains a given x,y position.
|
||||
The position is relative to the ListBox's top-left.
|
||||
If no row exists at this position, the method will return -1.
|
||||
@see getComponentForRowNumber
|
||||
*/
|
||||
int getRowContainingPosition (int x, int y) const noexcept;
|
||||
|
||||
/** Finds a row index that would be the most suitable place to insert a new
|
||||
item for a given position.
|
||||
|
||||
This is useful when the user is e.g. dragging and dropping onto the listbox,
|
||||
because it lets you easily choose the best position to insert the item that
|
||||
they drop, based on where they drop it.
|
||||
|
||||
If the position is out of range, this will return -1. If the position is
|
||||
beyond the end of the list, it will return getNumRows() to indicate the end
|
||||
of the list.
|
||||
|
||||
@see getComponentForRowNumber
|
||||
*/
|
||||
int getInsertionIndexForPosition (int x, int y) const noexcept;
|
||||
|
||||
/** Returns the position of one of the rows, relative to the top-left of
|
||||
the listbox.
|
||||
|
||||
This may be off-screen, and the range of the row number that is passed-in is
|
||||
not checked to see if it's a valid row.
|
||||
*/
|
||||
Rectangle<int> getRowPosition (int rowNumber,
|
||||
bool relativeToComponentTopLeft) const noexcept;
|
||||
|
||||
/** Finds the row component for a given row in the list.
|
||||
|
||||
The component returned will have been created using ListBoxModel::refreshComponentForRow().
|
||||
|
||||
If the component for this row is off-screen or if the row is out-of-range,
|
||||
this will return nullptr.
|
||||
|
||||
@see getRowContainingPosition
|
||||
*/
|
||||
Component* getComponentForRowNumber (int rowNumber) const noexcept;
|
||||
|
||||
/** Returns the row number that the given component represents.
|
||||
If the component isn't one of the list's rows, this will return -1.
|
||||
*/
|
||||
int getRowNumberOfComponent (Component* rowComponent) const noexcept;
|
||||
|
||||
/** Returns the width of a row (which may be less than the width of this component
|
||||
if there's a scrollbar).
|
||||
*/
|
||||
int getVisibleRowWidth() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the height of each row in the list.
|
||||
The default height is 22 pixels.
|
||||
@see getRowHeight
|
||||
*/
|
||||
void setRowHeight (int newHeight);
|
||||
|
||||
/** Returns the height of a row in the list.
|
||||
@see setRowHeight
|
||||
*/
|
||||
int getRowHeight() const noexcept { return rowHeight; }
|
||||
|
||||
/** Returns the number of rows actually visible.
|
||||
|
||||
This is the number of whole rows which will fit on-screen, so the value might
|
||||
be more than the actual number of rows in the list.
|
||||
*/
|
||||
int getNumRowsOnScreen() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the label.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1002800, /**< The background colour to fill the list with.
|
||||
Make this transparent if you don't want the background to be filled. */
|
||||
outlineColourId = 0x1002810, /**< An optional colour to use to draw a border around the list.
|
||||
Make this transparent to not have an outline. */
|
||||
textColourId = 0x1002820 /**< The preferred colour to use for drawing text in the listbox. */
|
||||
};
|
||||
|
||||
/** Sets the thickness of a border that will be drawn around the box.
|
||||
|
||||
To set the colour of the outline, use @code setColour (ListBox::outlineColourId, colourXYZ); @endcode
|
||||
@see outlineColourId
|
||||
*/
|
||||
void setOutlineThickness (int outlineThickness);
|
||||
|
||||
/** Returns the thickness of outline that will be drawn around the listbox.
|
||||
@see setOutlineColour
|
||||
*/
|
||||
int getOutlineThickness() const noexcept { return outlineThickness; }
|
||||
|
||||
/** Sets a component that the list should use as a header.
|
||||
|
||||
This will position the given component at the top of the list, maintaining the
|
||||
height of the component passed-in, but rescaling it horizontally to match the
|
||||
width of the items in the listbox.
|
||||
|
||||
The component will be deleted when setHeaderComponent() is called with a
|
||||
different component, or when the listbox is deleted.
|
||||
*/
|
||||
void setHeaderComponent (Component* newHeaderComponent);
|
||||
|
||||
/** Returns whatever header component was set with setHeaderComponent(). */
|
||||
Component* getHeaderComponent() const noexcept { return headerComponent.get(); }
|
||||
|
||||
/** Changes the width of the rows in the list.
|
||||
|
||||
This can be used to make the list's row components wider than the list itself - the
|
||||
width of the rows will be either the width of the list or this value, whichever is
|
||||
greater, and if the rows become wider than the list, a horizontal scrollbar will
|
||||
appear.
|
||||
|
||||
The default value for this is 0, which means that the rows will always
|
||||
be the same width as the list.
|
||||
*/
|
||||
void setMinimumContentWidth (int newMinimumWidth);
|
||||
|
||||
/** Returns the space currently available for the row items, taking into account
|
||||
borders, scrollbars, etc.
|
||||
*/
|
||||
int getVisibleContentWidth() const noexcept;
|
||||
|
||||
/** Repaints one of the rows.
|
||||
|
||||
This does not invoke updateContent(), it just invokes a straightforward repaint
|
||||
for the area covered by this row.
|
||||
*/
|
||||
void repaintRow (int rowNumber) noexcept;
|
||||
|
||||
/** This fairly obscure method creates an image that shows the row components specified
|
||||
in rows (for example, these could be the currently selected row components).
|
||||
|
||||
It's a handy method for doing drag-and-drop, as it can be passed to the
|
||||
DragAndDropContainer for use as the drag image.
|
||||
|
||||
Note that it will make the row components temporarily invisible, so if you're
|
||||
using custom components this could affect them if they're sensitive to that
|
||||
sort of thing.
|
||||
|
||||
@see Component::createComponentSnapshot
|
||||
*/
|
||||
virtual Image createSnapshotOfRows (const SparseSet<int>& rows, int& x, int& y);
|
||||
|
||||
/** Returns the viewport that this ListBox uses.
|
||||
|
||||
You may need to use this to change parameters such as whether scrollbars
|
||||
are shown, etc.
|
||||
*/
|
||||
Viewport* getViewport() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
bool keyStateChanged (bool isKeyDown) override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void paintOverChildren (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void visibilityChanged() override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void startDragAndDrop (const MouseEvent&, const SparseSet<int>& rowsToDrag,
|
||||
const var& dragDescription, bool allowDraggingToOtherWindows);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (class ListViewport)
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (class RowComponent)
|
||||
friend class ListViewport;
|
||||
friend class TableListBox;
|
||||
ListBoxModel* model;
|
||||
std::unique_ptr<ListViewport> viewport;
|
||||
std::unique_ptr<Component> headerComponent;
|
||||
std::unique_ptr<MouseListener> mouseMoveSelector;
|
||||
SparseSet<int> selected;
|
||||
int totalItems = 0, rowHeight = 22, minimumRowWidth = 0;
|
||||
int outlineThickness = 0;
|
||||
int lastRowSelected = -1;
|
||||
bool multipleSelection = false, alwaysFlipSelection = false, hasDoneInitialUpdate = false, selectOnMouseDown = true;
|
||||
|
||||
void selectRowInternal (int rowNumber, bool dontScrollToShowThisRow,
|
||||
bool deselectOthersFirst, bool isMouseClick);
|
||||
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// This method's bool parameter has changed: see the new method signature.
|
||||
JUCE_DEPRECATED (void setSelectedRows (const SparseSet<int>&, bool));
|
||||
// This method has been replaced by the more flexible method createSnapshotOfRows.
|
||||
// Please call createSnapshotOfRows (getSelectedRows(), x, y) to get the same behaviour.
|
||||
JUCE_DEPRECATED (virtual void createSnapshotOfSelectedRows (int&, int&)) {}
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListBox)
|
||||
};
|
||||
|
||||
} // namespace juce
|
118
modules/juce_gui_basics/widgets/juce_ProgressBar.cpp
Normal file
118
modules/juce_gui_basics/widgets/juce_ProgressBar.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
ProgressBar::ProgressBar (double& progress_)
|
||||
: progress (progress_),
|
||||
displayPercentage (true),
|
||||
lastCallbackTime (0)
|
||||
{
|
||||
currentValue = jlimit (0.0, 1.0, progress);
|
||||
}
|
||||
|
||||
ProgressBar::~ProgressBar()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ProgressBar::setPercentageDisplay (const bool shouldDisplayPercentage)
|
||||
{
|
||||
displayPercentage = shouldDisplayPercentage;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ProgressBar::setTextToDisplay (const String& text)
|
||||
{
|
||||
displayPercentage = false;
|
||||
displayedMessage = text;
|
||||
}
|
||||
|
||||
void ProgressBar::lookAndFeelChanged()
|
||||
{
|
||||
setOpaque (getLookAndFeel().isProgressBarOpaque (*this));
|
||||
}
|
||||
|
||||
void ProgressBar::colourChanged()
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void ProgressBar::paint (Graphics& g)
|
||||
{
|
||||
String text;
|
||||
|
||||
if (displayPercentage)
|
||||
{
|
||||
if (currentValue >= 0 && currentValue <= 1.0)
|
||||
text << roundToInt (currentValue * 100.0) << '%';
|
||||
}
|
||||
else
|
||||
{
|
||||
text = displayedMessage;
|
||||
}
|
||||
|
||||
getLookAndFeel().drawProgressBar (g, *this,
|
||||
getWidth(), getHeight(),
|
||||
currentValue, text);
|
||||
}
|
||||
|
||||
void ProgressBar::visibilityChanged()
|
||||
{
|
||||
if (isVisible())
|
||||
startTimer (30);
|
||||
else
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
void ProgressBar::timerCallback()
|
||||
{
|
||||
double newProgress = progress;
|
||||
|
||||
const uint32 now = Time::getMillisecondCounter();
|
||||
const int timeSinceLastCallback = (int) (now - lastCallbackTime);
|
||||
lastCallbackTime = now;
|
||||
|
||||
if (currentValue != newProgress
|
||||
|| newProgress < 0 || newProgress >= 1.0
|
||||
|| currentMessage != displayedMessage)
|
||||
{
|
||||
if (currentValue < newProgress
|
||||
&& newProgress >= 0 && newProgress < 1.0
|
||||
&& currentValue >= 0 && currentValue < 1.0)
|
||||
{
|
||||
newProgress = jmin (currentValue + 0.0008 * timeSinceLastCallback,
|
||||
newProgress);
|
||||
}
|
||||
|
||||
currentValue = newProgress;
|
||||
currentMessage = displayedMessage;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
144
modules/juce_gui_basics/widgets/juce_ProgressBar.h
Normal file
144
modules/juce_gui_basics/widgets/juce_ProgressBar.h
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A progress bar component.
|
||||
|
||||
To use this, just create one and make it visible. It'll run its own timer
|
||||
to keep an eye on a variable that you give it, and will automatically
|
||||
redraw itself when the variable changes.
|
||||
|
||||
If using LookAndFeel_V4 a circular spinning progress bar will be drawn if
|
||||
the width and height of the ProgressBar are equal, otherwise the standard,
|
||||
linear ProgressBar will be drawn.
|
||||
|
||||
For an easy way of running a background task with a dialog box showing its
|
||||
progress, see the ThreadWithProgressWindow class.
|
||||
|
||||
@see ThreadWithProgressWindow
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ProgressBar : public Component,
|
||||
public SettableTooltipClient,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ProgressBar.
|
||||
|
||||
@param progress pass in a reference to a double that you're going to
|
||||
update with your task's progress. The ProgressBar will
|
||||
monitor the value of this variable and will redraw itself
|
||||
when the value changes. The range is from 0 to 1.0 and JUCE
|
||||
LookAndFeel classes will draw a spinning animation for values
|
||||
outside this range. Obviously you'd better be careful not to
|
||||
delete this variable while the ProgressBar still exists!
|
||||
*/
|
||||
explicit ProgressBar (double& progress);
|
||||
|
||||
/** Destructor. */
|
||||
~ProgressBar();
|
||||
|
||||
//==============================================================================
|
||||
/** Turns the percentage display on or off.
|
||||
|
||||
By default this is on, and the progress bar will display a text string showing
|
||||
its current percentage.
|
||||
*/
|
||||
void setPercentageDisplay (bool shouldDisplayPercentage);
|
||||
|
||||
/** Gives the progress bar a string to display inside it.
|
||||
|
||||
If you call this, it will turn off the percentage display.
|
||||
@see setPercentageDisplay
|
||||
*/
|
||||
void setTextToDisplay (const String& text);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the bar.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1001900, /**< The background colour, behind the bar. */
|
||||
foregroundColourId = 0x1001a00, /**< The colour to use to draw the bar itself. LookAndFeel
|
||||
classes will probably use variations on this colour. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
/** Draws a progress bar.
|
||||
|
||||
If the progress value is less than 0 or greater than 1.0, this should draw a spinning
|
||||
bar that fills the whole space (i.e. to say that the app is still busy but the progress
|
||||
isn't known). It can use the current time as a basis for playing an animation.
|
||||
|
||||
(Used by progress bars in AlertWindow).
|
||||
*/
|
||||
virtual void drawProgressBar (Graphics&, ProgressBar&, int width, int height,
|
||||
double progress, const String& textToShow) = 0;
|
||||
|
||||
virtual bool isProgressBarOpaque (ProgressBar&) = 0;
|
||||
};
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
void visibilityChanged() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
private:
|
||||
double& progress;
|
||||
double currentValue;
|
||||
bool displayPercentage;
|
||||
String displayedMessage, currentMessage;
|
||||
uint32 lastCallbackTime;
|
||||
|
||||
void timerCallback() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgressBar)
|
||||
};
|
||||
|
||||
} // namespace juce
|
1648
modules/juce_gui_basics/widgets/juce_Slider.cpp
Normal file
1648
modules/juce_gui_basics/widgets/juce_Slider.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1001
modules/juce_gui_basics/widgets/juce_Slider.h
Normal file
1001
modules/juce_gui_basics/widgets/juce_Slider.h
Normal file
File diff suppressed because it is too large
Load Diff
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
|
459
modules/juce_gui_basics/widgets/juce_TableHeaderComponent.h
Normal file
459
modules/juce_gui_basics/widgets/juce_TableHeaderComponent.h
Normal file
@ -0,0 +1,459 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that displays a strip of column headings for a table, and allows these
|
||||
to be resized, dragged around, etc.
|
||||
|
||||
This is just the component that goes at the top of a table. You can use it
|
||||
directly for custom components, or to create a simple table, use the
|
||||
TableListBox class.
|
||||
|
||||
To use one of these, create it and use addColumn() to add all the columns that you need.
|
||||
Each column must be given a unique ID number that's used to refer to it.
|
||||
|
||||
@see TableListBox, TableHeaderComponent::Listener
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TableHeaderComponent : public Component,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty table header.
|
||||
*/
|
||||
TableHeaderComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~TableHeaderComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** A combination of these flags are passed into the addColumn() method to specify
|
||||
the properties of a column.
|
||||
*/
|
||||
enum ColumnPropertyFlags
|
||||
{
|
||||
visible = 1, /**< If this is set, the column will be shown; if not, it will be hidden until the user enables it with the pop-up menu. */
|
||||
resizable = 2, /**< If this is set, the column can be resized by dragging it. */
|
||||
draggable = 4, /**< If this is set, the column can be dragged around to change its order in the table. */
|
||||
appearsOnColumnMenu = 8, /**< If this is set, the column will be shown on the pop-up menu allowing it to be hidden/shown. */
|
||||
sortable = 16, /**< If this is set, then clicking on the column header will set it to be the sort column, and clicking again will reverse the order. */
|
||||
sortedForwards = 32, /**< If this is set, the column is currently the one by which the table is sorted (forwards). */
|
||||
sortedBackwards = 64, /**< If this is set, the column is currently the one by which the table is sorted (backwards). */
|
||||
|
||||
/** This set of default flags is used as the default parameter value in addColumn(). */
|
||||
defaultFlags = (visible | resizable | draggable | appearsOnColumnMenu | sortable),
|
||||
|
||||
/** A quick way of combining flags for a column that's not resizable. */
|
||||
notResizable = (visible | draggable | appearsOnColumnMenu | sortable),
|
||||
|
||||
/** A quick way of combining flags for a column that's not resizable or sortable. */
|
||||
notResizableOrSortable = (visible | draggable | appearsOnColumnMenu),
|
||||
|
||||
/** A quick way of combining flags for a column that's not sortable. */
|
||||
notSortable = (visible | resizable | draggable | appearsOnColumnMenu)
|
||||
};
|
||||
|
||||
/** Adds a column to the table.
|
||||
|
||||
This will add a column, and asynchronously call the tableColumnsChanged() method of any
|
||||
registered listeners.
|
||||
|
||||
@param columnName the name of the new column. It's ok to have two or more columns with the same name
|
||||
@param columnId an ID for this column. The ID can be any number apart from 0, but every column must have
|
||||
a unique ID. This is used to identify the column later on, after the user may have
|
||||
changed the order that they appear in
|
||||
@param width the initial width of the column, in pixels
|
||||
@param maximumWidth a maximum width that the column can take when the user is resizing it. This only applies
|
||||
if the 'resizable' flag is specified for this column
|
||||
@param minimumWidth a minimum width that the column can take when the user is resizing it. This only applies
|
||||
if the 'resizable' flag is specified for this column
|
||||
@param propertyFlags a combination of some of the values from the ColumnPropertyFlags enum, to define the
|
||||
properties of this column
|
||||
@param insertIndex the index at which the column should be added. A value of 0 puts it at the start (left-hand side)
|
||||
and -1 puts it at the end (right-hand size) of the table. Note that the index the index within
|
||||
all columns, not just the index amongst those that are currently visible
|
||||
*/
|
||||
void addColumn (const String& columnName,
|
||||
int columnId,
|
||||
int width,
|
||||
int minimumWidth = 30,
|
||||
int maximumWidth = -1,
|
||||
int propertyFlags = defaultFlags,
|
||||
int insertIndex = -1);
|
||||
|
||||
/** Removes a column with the given ID.
|
||||
|
||||
If there is such a column, this will asynchronously call the tableColumnsChanged() method of any
|
||||
registered listeners.
|
||||
*/
|
||||
void removeColumn (int columnIdToRemove);
|
||||
|
||||
/** Deletes all columns from the table.
|
||||
|
||||
If there are any columns to remove, this will asynchronously call the tableColumnsChanged() method of any
|
||||
registered listeners.
|
||||
*/
|
||||
void removeAllColumns();
|
||||
|
||||
/** Returns the number of columns in the table.
|
||||
|
||||
If onlyCountVisibleColumns is true, this will return the number of visible columns; otherwise it'll
|
||||
return the total number of columns, including hidden ones.
|
||||
|
||||
@see isColumnVisible
|
||||
*/
|
||||
int getNumColumns (bool onlyCountVisibleColumns) const;
|
||||
|
||||
/** Returns the name for a column.
|
||||
@see setColumnName
|
||||
*/
|
||||
String getColumnName (int columnId) const;
|
||||
|
||||
/** Changes the name of a column. */
|
||||
void setColumnName (int columnId, const String& newName);
|
||||
|
||||
/** Moves a column to a different index in the table.
|
||||
|
||||
@param columnId the column to move
|
||||
@param newVisibleIndex the target index for it, from 0 to the number of columns currently visible.
|
||||
*/
|
||||
void moveColumn (int columnId, int newVisibleIndex);
|
||||
|
||||
/** Returns the width of one of the columns.
|
||||
*/
|
||||
int getColumnWidth (int columnId) const;
|
||||
|
||||
/** Changes the width of a column.
|
||||
|
||||
This will cause an asynchronous callback to the tableColumnsResized() method of any registered listeners.
|
||||
*/
|
||||
void setColumnWidth (int columnId, int newWidth);
|
||||
|
||||
/** Shows or hides a column.
|
||||
|
||||
This can cause an asynchronous callback to the tableColumnsChanged() method of any registered listeners.
|
||||
@see isColumnVisible
|
||||
*/
|
||||
void setColumnVisible (int columnId, bool shouldBeVisible);
|
||||
|
||||
/** Returns true if this column is currently visible.
|
||||
@see setColumnVisible
|
||||
*/
|
||||
bool isColumnVisible (int columnId) const;
|
||||
|
||||
/** Changes the column which is the sort column.
|
||||
|
||||
This can cause an asynchronous callback to the tableSortOrderChanged() method of any registered listeners.
|
||||
|
||||
If this method doesn't actually change the column ID, then no re-sort will take place (you can
|
||||
call reSortTable() to force a re-sort to happen if you've modified the table's contents).
|
||||
|
||||
@see getSortColumnId, isSortedForwards, reSortTable
|
||||
*/
|
||||
void setSortColumnId (int columnId, bool sortForwards);
|
||||
|
||||
/** Returns the column ID by which the table is currently sorted, or 0 if it is unsorted.
|
||||
|
||||
@see setSortColumnId, isSortedForwards
|
||||
*/
|
||||
int getSortColumnId() const;
|
||||
|
||||
/** Returns true if the table is currently sorted forwards, or false if it's backwards.
|
||||
@see setSortColumnId
|
||||
*/
|
||||
bool isSortedForwards() const;
|
||||
|
||||
/** Triggers a re-sort of the table according to the current sort-column.
|
||||
|
||||
If you modifiy the table's contents, you can call this to signal that the table needs
|
||||
to be re-sorted.
|
||||
|
||||
(This doesn't do any sorting synchronously - it just asynchronously sends a call to the
|
||||
tableSortOrderChanged() method of any listeners).
|
||||
*/
|
||||
void reSortTable();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the total width of all the visible columns in the table.
|
||||
*/
|
||||
int getTotalWidth() const;
|
||||
|
||||
/** Returns the index of a given column.
|
||||
|
||||
If there's no such column ID, this will return -1.
|
||||
|
||||
If onlyCountVisibleColumns is true, this will return the index amongst the visible columns;
|
||||
otherwise it'll return the index amongst all the columns, including any hidden ones.
|
||||
*/
|
||||
int getIndexOfColumnId (int columnId, bool onlyCountVisibleColumns) const;
|
||||
|
||||
/** Returns the ID of the column at a given index.
|
||||
|
||||
If onlyCountVisibleColumns is true, this will count the index amongst the visible columns;
|
||||
otherwise it'll count it amongst all the columns, including any hidden ones.
|
||||
|
||||
If the index is out-of-range, it'll return 0.
|
||||
*/
|
||||
int getColumnIdOfIndex (int index, bool onlyCountVisibleColumns) const;
|
||||
|
||||
/** Returns the rectangle containing of one of the columns.
|
||||
|
||||
The index is an index from 0 to the number of columns that are currently visible (hidden
|
||||
ones are not counted). It returns a rectangle showing the position of the column relative
|
||||
to this component's top-left. If the index is out-of-range, an empty rectangle is retrurned.
|
||||
*/
|
||||
Rectangle<int> getColumnPosition (int index) const;
|
||||
|
||||
/** Finds the column ID at a given x-position in the component.
|
||||
If there is a column at this point this returns its ID, or if not, it will return 0.
|
||||
*/
|
||||
int getColumnIdAtX (int xToFind) const;
|
||||
|
||||
/** If set to true, this indicates that the columns should be expanded or shrunk to fill the
|
||||
entire width of the component.
|
||||
|
||||
By default this is disabled. Turning it on also means that when resizing a column, those
|
||||
on the right will be squashed to fit.
|
||||
*/
|
||||
void setStretchToFitActive (bool shouldStretchToFit);
|
||||
|
||||
/** Returns true if stretch-to-fit has been enabled.
|
||||
@see setStretchToFitActive
|
||||
*/
|
||||
bool isStretchToFitActive() const;
|
||||
|
||||
/** If stretch-to-fit is enabled, this will resize all the columns to make them fit into the
|
||||
specified width, keeping their relative proportions the same.
|
||||
|
||||
If the minimum widths of the columns are too wide to fit into this space, it may
|
||||
actually end up wider.
|
||||
*/
|
||||
void resizeAllColumnsToFit (int targetTotalWidth);
|
||||
|
||||
//==============================================================================
|
||||
/** Enables or disables the pop-up menu.
|
||||
|
||||
The default menu allows the user to show or hide columns. You can add custom
|
||||
items to this menu by overloading the addMenuItems() and reactToMenuItem() methods.
|
||||
|
||||
By default the menu is enabled.
|
||||
|
||||
@see isPopupMenuActive, addMenuItems, reactToMenuItem
|
||||
*/
|
||||
void setPopupMenuActive (bool hasMenu);
|
||||
|
||||
/** Returns true if the pop-up menu is enabled.
|
||||
@see setPopupMenuActive
|
||||
*/
|
||||
bool isPopupMenuActive() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a string that encapsulates the table's current layout.
|
||||
|
||||
This can be restored later using restoreFromString(). It saves the order of
|
||||
the columns, the currently-sorted column, and the widths.
|
||||
|
||||
@see restoreFromString
|
||||
*/
|
||||
String toString() const;
|
||||
|
||||
/** Restores the state of the table, based on a string previously created with
|
||||
toString().
|
||||
|
||||
@see toString
|
||||
*/
|
||||
void restoreFromString (const String& storedVersion);
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Receives events from a TableHeaderComponent when columns are resized, moved, etc.
|
||||
|
||||
You can register one of these objects for table events using TableHeaderComponent::addListener()
|
||||
and TableHeaderComponent::removeListener().
|
||||
|
||||
@see TableHeaderComponent
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
Listener() {}
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
//==============================================================================
|
||||
/** This is called when some of the table's columns are added, removed, hidden,
|
||||
or rearranged.
|
||||
*/
|
||||
virtual void tableColumnsChanged (TableHeaderComponent* tableHeader) = 0;
|
||||
|
||||
/** This is called when one or more of the table's columns are resized. */
|
||||
virtual void tableColumnsResized (TableHeaderComponent* tableHeader) = 0;
|
||||
|
||||
/** This is called when the column by which the table should be sorted is changed. */
|
||||
virtual void tableSortOrderChanged (TableHeaderComponent* tableHeader) = 0;
|
||||
|
||||
/** This is called when the user begins or ends dragging one of the columns around.
|
||||
|
||||
When the user starts dragging a column, this is called with the ID of that
|
||||
column. When they finish dragging, it is called again with 0 as the ID.
|
||||
*/
|
||||
virtual void tableColumnDraggingChanged (TableHeaderComponent* tableHeader,
|
||||
int columnIdNowBeingDragged);
|
||||
};
|
||||
|
||||
/** Adds a listener to be informed about things that happen to the header. */
|
||||
void addListener (Listener* newListener);
|
||||
|
||||
/** Removes a previously-registered listener. */
|
||||
void removeListener (Listener* listenerToRemove);
|
||||
|
||||
//==============================================================================
|
||||
/** This can be overridden to handle a mouse-click on one of the column headers.
|
||||
|
||||
The default implementation will use this click to call getSortColumnId() and
|
||||
change the sort order.
|
||||
*/
|
||||
virtual void columnClicked (int columnId, const ModifierKeys& mods);
|
||||
|
||||
/** This can be overridden to add custom items to the pop-up menu.
|
||||
|
||||
If you override this, you should call the superclass's method to add its
|
||||
column show/hide items, if you want them on the menu as well.
|
||||
|
||||
Then to handle the result, override reactToMenuItem().
|
||||
|
||||
@see reactToMenuItem
|
||||
*/
|
||||
virtual void addMenuItems (PopupMenu& menu, int columnIdClicked);
|
||||
|
||||
/** Override this to handle any custom items that you have added to the
|
||||
pop-up menu with an addMenuItems() override.
|
||||
|
||||
If the menuReturnId isn't one of your own custom menu items, you'll need to
|
||||
call TableHeaderComponent::reactToMenuItem() to allow the base class to
|
||||
handle the items that it had added.
|
||||
|
||||
@see addMenuItems
|
||||
*/
|
||||
virtual void reactToMenuItem (int menuReturnId, int columnIdClicked);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the TableHeaderComponent.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
textColourId = 0x1003800, /**< The colour for the text in the header. */
|
||||
backgroundColourId = 0x1003810, /**< The colour of the table header background.
|
||||
It's up to the LookAndFeel how this is used. */
|
||||
outlineColourId = 0x1003820, /**< The colour of the table header's outline. */
|
||||
highlightColourId = 0x1003830, /**< The colour of the table header background when
|
||||
the mouse is over or down above the the table
|
||||
header. It's up to the LookAndFeel to use a
|
||||
variant of this colour to destiuish between
|
||||
the down and hover state. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void drawTableHeaderBackground (Graphics&, TableHeaderComponent&) = 0;
|
||||
|
||||
virtual void drawTableHeaderColumn (Graphics&, TableHeaderComponent&,
|
||||
const String& columnName, int columnId,
|
||||
int width, int height,
|
||||
bool isMouseOver, bool isMouseDown, int columnFlags) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseMove (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseEnter (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseExit (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
MouseCursor getMouseCursor() override;
|
||||
|
||||
/** Can be overridden for more control over the pop-up menu behaviour. */
|
||||
virtual void showColumnChooserMenu (int columnIdClicked);
|
||||
|
||||
private:
|
||||
struct ColumnInfo
|
||||
{
|
||||
String name;
|
||||
int id, propertyFlags, width, minimumWidth, maximumWidth;
|
||||
double lastDeliberateWidth;
|
||||
|
||||
bool isVisible() const;
|
||||
};
|
||||
|
||||
OwnedArray<ColumnInfo> columns;
|
||||
Array<Listener*> listeners;
|
||||
std::unique_ptr<Component> dragOverlayComp;
|
||||
class DragOverlayComp;
|
||||
|
||||
bool columnsChanged = false, columnsResized = false, sortChanged = false;
|
||||
bool menuActive = true, stretchToFit = false;
|
||||
int columnIdBeingResized = 0, columnIdBeingDragged = 0, initialColumnWidth = 0;
|
||||
int columnIdUnderMouse = 0, draggingColumnOffset = 0, draggingColumnOriginalIndex = 0, lastDeliberateWidth = 0;
|
||||
|
||||
ColumnInfo* getInfoForId (int columnId) const;
|
||||
int visibleIndexToTotalIndex (int visibleIndex) const;
|
||||
void sendColumnsChanged();
|
||||
void handleAsyncUpdate() override;
|
||||
void beginDrag (const MouseEvent&);
|
||||
void endDrag (int finalIndex);
|
||||
int getResizeDraggerAt (int mouseX) const;
|
||||
void updateColumnUnderMouse (const MouseEvent&);
|
||||
void setColumnUnderMouse (int columnId);
|
||||
void resizeColumnsToFit (int firstColumnIndex, int targetTotalWidth);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableHeaderComponent)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
488
modules/juce_gui_basics/widgets/juce_TableListBox.cpp
Normal file
488
modules/juce_gui_basics/widgets/juce_TableListBox.cpp
Normal file
@ -0,0 +1,488 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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 TableListBox::RowComp : public Component,
|
||||
public TooltipClient
|
||||
{
|
||||
public:
|
||||
RowComp (TableListBox& tlb) noexcept : owner (tlb) {}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (auto* tableModel = owner.getModel())
|
||||
{
|
||||
tableModel->paintRowBackground (g, row, getWidth(), getHeight(), isSelected);
|
||||
|
||||
auto& headerComp = owner.getHeader();
|
||||
auto numColumns = headerComp.getNumColumns (true);
|
||||
auto clipBounds = g.getClipBounds();
|
||||
|
||||
for (int i = 0; i < numColumns; ++i)
|
||||
{
|
||||
if (columnComponents[i] == nullptr)
|
||||
{
|
||||
auto columnRect = headerComp.getColumnPosition(i).withHeight (getHeight());
|
||||
|
||||
if (columnRect.getX() >= clipBounds.getRight())
|
||||
break;
|
||||
|
||||
if (columnRect.getRight() > clipBounds.getX())
|
||||
{
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
if (g.reduceClipRegion (columnRect))
|
||||
{
|
||||
g.setOrigin (columnRect.getX(), 0);
|
||||
tableModel->paintCell (g, row, headerComp.getColumnIdOfIndex (i, true),
|
||||
columnRect.getWidth(), columnRect.getHeight(), isSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update (int newRow, bool isNowSelected)
|
||||
{
|
||||
jassert (newRow >= 0);
|
||||
|
||||
if (newRow != row || isNowSelected != isSelected)
|
||||
{
|
||||
row = newRow;
|
||||
isSelected = isNowSelected;
|
||||
repaint();
|
||||
}
|
||||
|
||||
auto* tableModel = owner.getModel();
|
||||
|
||||
if (tableModel != nullptr && row < owner.getNumRows())
|
||||
{
|
||||
const Identifier columnProperty ("_tableColumnId");
|
||||
auto numColumns = owner.getHeader().getNumColumns (true);
|
||||
|
||||
for (int i = 0; i < numColumns; ++i)
|
||||
{
|
||||
auto columnId = owner.getHeader().getColumnIdOfIndex (i, true);
|
||||
auto* comp = columnComponents[i];
|
||||
|
||||
if (comp != nullptr && columnId != static_cast<int> (comp->getProperties() [columnProperty]))
|
||||
{
|
||||
columnComponents.set (i, nullptr);
|
||||
comp = nullptr;
|
||||
}
|
||||
|
||||
comp = tableModel->refreshComponentForCell (row, columnId, isSelected, comp);
|
||||
columnComponents.set (i, comp, false);
|
||||
|
||||
if (comp != nullptr)
|
||||
{
|
||||
comp->getProperties().set (columnProperty, columnId);
|
||||
|
||||
addAndMakeVisible (comp);
|
||||
resizeCustomComp (i);
|
||||
}
|
||||
}
|
||||
|
||||
columnComponents.removeRange (numColumns, columnComponents.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
columnComponents.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
for (int i = columnComponents.size(); --i >= 0;)
|
||||
resizeCustomComp (i);
|
||||
}
|
||||
|
||||
void resizeCustomComp (int index)
|
||||
{
|
||||
if (auto* c = columnComponents.getUnchecked (index))
|
||||
c->setBounds (owner.getHeader().getColumnPosition (index)
|
||||
.withY (0).withHeight (getHeight()));
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
isDragging = false;
|
||||
selectRowOnMouseUp = false;
|
||||
|
||||
if (isEnabled())
|
||||
{
|
||||
if (! isSelected)
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (row, e.mods, false);
|
||||
|
||||
auto columnId = owner.getHeader().getColumnIdAtX (e.x);
|
||||
|
||||
if (columnId != 0)
|
||||
if (auto* m = owner.getModel())
|
||||
m->cellClicked (row, columnId, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectRowOnMouseUp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
if (isEnabled()
|
||||
&& owner.getModel() != nullptr
|
||||
&& 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 = owner.getModel()->getDragSourceDescription (rowsToDrag);
|
||||
|
||||
if (! (dragDescription.isVoid() || (dragDescription.isString() && dragDescription.toString().isEmpty())))
|
||||
{
|
||||
isDragging = true;
|
||||
owner.startDragAndDrop (e, rowsToDrag, dragDescription, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mouseUp (const MouseEvent& e) override
|
||||
{
|
||||
if (selectRowOnMouseUp && e.mouseWasClicked() && isEnabled())
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (row, e.mods, true);
|
||||
|
||||
auto columnId = owner.getHeader().getColumnIdAtX (e.x);
|
||||
|
||||
if (columnId != 0)
|
||||
if (TableListBoxModel* m = owner.getModel())
|
||||
m->cellClicked (row, columnId, e);
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDoubleClick (const MouseEvent& e) override
|
||||
{
|
||||
auto columnId = owner.getHeader().getColumnIdAtX (e.x);
|
||||
|
||||
if (columnId != 0)
|
||||
if (auto* m = owner.getModel())
|
||||
m->cellDoubleClicked (row, columnId, e);
|
||||
}
|
||||
|
||||
String getTooltip() override
|
||||
{
|
||||
auto columnId = owner.getHeader().getColumnIdAtX (getMouseXYRelative().getX());
|
||||
|
||||
if (columnId != 0)
|
||||
if (auto* m = owner.getModel())
|
||||
return m->getCellTooltip (row, columnId);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Component* findChildComponentForColumn (int columnId) const
|
||||
{
|
||||
return columnComponents [owner.getHeader().getIndexOfColumnId (columnId, true)];
|
||||
}
|
||||
|
||||
private:
|
||||
TableListBox& owner;
|
||||
OwnedArray<Component> columnComponents;
|
||||
int row = -1;
|
||||
bool isSelected = false, isDragging = false, selectRowOnMouseUp = false;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RowComp)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class TableListBox::Header : public TableHeaderComponent
|
||||
{
|
||||
public:
|
||||
Header (TableListBox& tlb) : owner (tlb) {}
|
||||
|
||||
void addMenuItems (PopupMenu& menu, int columnIdClicked)
|
||||
{
|
||||
if (owner.isAutoSizeMenuOptionShown())
|
||||
{
|
||||
menu.addItem (autoSizeColumnId, TRANS("Auto-size this column"), columnIdClicked != 0);
|
||||
menu.addItem (autoSizeAllId, TRANS("Auto-size all columns"), owner.getHeader().getNumColumns (true) > 0);
|
||||
menu.addSeparator();
|
||||
}
|
||||
|
||||
TableHeaderComponent::addMenuItems (menu, columnIdClicked);
|
||||
}
|
||||
|
||||
void reactToMenuItem (int menuReturnId, int columnIdClicked)
|
||||
{
|
||||
switch (menuReturnId)
|
||||
{
|
||||
case autoSizeColumnId: owner.autoSizeColumn (columnIdClicked); break;
|
||||
case autoSizeAllId: owner.autoSizeAllColumns(); break;
|
||||
default: TableHeaderComponent::reactToMenuItem (menuReturnId, columnIdClicked); break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
TableListBox& owner;
|
||||
|
||||
enum { autoSizeColumnId = 0xf836743, autoSizeAllId = 0xf836744 };
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Header)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
TableListBox::TableListBox (const String& name, TableListBoxModel* const m)
|
||||
: ListBox (name, nullptr), model (m)
|
||||
{
|
||||
ListBox::model = this;
|
||||
|
||||
setHeader (new Header (*this));
|
||||
}
|
||||
|
||||
TableListBox::~TableListBox()
|
||||
{
|
||||
}
|
||||
|
||||
void TableListBox::setModel (TableListBoxModel* newModel)
|
||||
{
|
||||
if (model != newModel)
|
||||
{
|
||||
model = newModel;
|
||||
updateContent();
|
||||
}
|
||||
}
|
||||
|
||||
void TableListBox::setHeader (TableHeaderComponent* newHeader)
|
||||
{
|
||||
jassert (newHeader != nullptr); // you need to supply a real header for a table!
|
||||
|
||||
Rectangle<int> newBounds (100, 28);
|
||||
|
||||
if (header != nullptr)
|
||||
newBounds = header->getBounds();
|
||||
|
||||
header = newHeader;
|
||||
header->setBounds (newBounds);
|
||||
|
||||
setHeaderComponent (header);
|
||||
|
||||
header->addListener (this);
|
||||
}
|
||||
|
||||
int TableListBox::getHeaderHeight() const noexcept
|
||||
{
|
||||
return header->getHeight();
|
||||
}
|
||||
|
||||
void TableListBox::setHeaderHeight (int newHeight)
|
||||
{
|
||||
header->setSize (header->getWidth(), newHeight);
|
||||
resized();
|
||||
}
|
||||
|
||||
void TableListBox::autoSizeColumn (int columnId)
|
||||
{
|
||||
auto width = model != nullptr ? model->getColumnAutoSizeWidth (columnId) : 0;
|
||||
|
||||
if (width > 0)
|
||||
header->setColumnWidth (columnId, width);
|
||||
}
|
||||
|
||||
void TableListBox::autoSizeAllColumns()
|
||||
{
|
||||
for (int i = 0; i < header->getNumColumns (true); ++i)
|
||||
autoSizeColumn (header->getColumnIdOfIndex (i, true));
|
||||
}
|
||||
|
||||
void TableListBox::setAutoSizeMenuOptionShown (bool shouldBeShown) noexcept
|
||||
{
|
||||
autoSizeOptionsShown = shouldBeShown;
|
||||
}
|
||||
|
||||
Rectangle<int> TableListBox::getCellPosition (int columnId, int rowNumber, bool relativeToComponentTopLeft) const
|
||||
{
|
||||
auto headerCell = header->getColumnPosition (header->getIndexOfColumnId (columnId, true));
|
||||
|
||||
if (relativeToComponentTopLeft)
|
||||
headerCell.translate (header->getX(), 0);
|
||||
|
||||
return getRowPosition (rowNumber, relativeToComponentTopLeft)
|
||||
.withX (headerCell.getX())
|
||||
.withWidth (headerCell.getWidth());
|
||||
}
|
||||
|
||||
Component* TableListBox::getCellComponent (int columnId, int rowNumber) const
|
||||
{
|
||||
if (auto* rowComp = dynamic_cast<RowComp*> (getComponentForRowNumber (rowNumber)))
|
||||
return rowComp->findChildComponentForColumn (columnId);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TableListBox::scrollToEnsureColumnIsOnscreen (int columnId)
|
||||
{
|
||||
auto& scrollbar = getHorizontalScrollBar();
|
||||
auto pos = header->getColumnPosition (header->getIndexOfColumnId (columnId, true));
|
||||
|
||||
auto x = scrollbar.getCurrentRangeStart();
|
||||
auto w = scrollbar.getCurrentRangeSize();
|
||||
|
||||
if (pos.getX() < x)
|
||||
x = pos.getX();
|
||||
else if (pos.getRight() > x + w)
|
||||
x += jmax (0.0, pos.getRight() - (x + w));
|
||||
|
||||
scrollbar.setCurrentRangeStart (x);
|
||||
}
|
||||
|
||||
int TableListBox::getNumRows()
|
||||
{
|
||||
return model != nullptr ? model->getNumRows() : 0;
|
||||
}
|
||||
|
||||
void TableListBox::paintListBoxItem (int, Graphics&, int, int, bool)
|
||||
{
|
||||
}
|
||||
|
||||
Component* TableListBox::refreshComponentForRow (int rowNumber, bool rowSelected, Component* existingComponentToUpdate)
|
||||
{
|
||||
if (existingComponentToUpdate == nullptr)
|
||||
existingComponentToUpdate = new RowComp (*this);
|
||||
|
||||
static_cast<RowComp*> (existingComponentToUpdate)->update (rowNumber, rowSelected);
|
||||
|
||||
return existingComponentToUpdate;
|
||||
}
|
||||
|
||||
void TableListBox::selectedRowsChanged (int row)
|
||||
{
|
||||
if (model != nullptr)
|
||||
model->selectedRowsChanged (row);
|
||||
}
|
||||
|
||||
void TableListBox::deleteKeyPressed (int row)
|
||||
{
|
||||
if (model != nullptr)
|
||||
model->deleteKeyPressed (row);
|
||||
}
|
||||
|
||||
void TableListBox::returnKeyPressed (int row)
|
||||
{
|
||||
if (model != nullptr)
|
||||
model->returnKeyPressed (row);
|
||||
}
|
||||
|
||||
void TableListBox::backgroundClicked (const MouseEvent& e)
|
||||
{
|
||||
if (model != nullptr)
|
||||
model->backgroundClicked (e);
|
||||
}
|
||||
|
||||
void TableListBox::listWasScrolled()
|
||||
{
|
||||
if (model != nullptr)
|
||||
model->listWasScrolled();
|
||||
}
|
||||
|
||||
void TableListBox::tableColumnsChanged (TableHeaderComponent*)
|
||||
{
|
||||
setMinimumContentWidth (header->getTotalWidth());
|
||||
repaint();
|
||||
updateColumnComponents();
|
||||
}
|
||||
|
||||
void TableListBox::tableColumnsResized (TableHeaderComponent*)
|
||||
{
|
||||
setMinimumContentWidth (header->getTotalWidth());
|
||||
repaint();
|
||||
updateColumnComponents();
|
||||
}
|
||||
|
||||
void TableListBox::tableSortOrderChanged (TableHeaderComponent*)
|
||||
{
|
||||
if (model != nullptr)
|
||||
model->sortOrderChanged (header->getSortColumnId(),
|
||||
header->isSortedForwards());
|
||||
}
|
||||
|
||||
void TableListBox::tableColumnDraggingChanged (TableHeaderComponent*, int columnIdNowBeingDragged_)
|
||||
{
|
||||
columnIdNowBeingDragged = columnIdNowBeingDragged_;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void TableListBox::resized()
|
||||
{
|
||||
ListBox::resized();
|
||||
|
||||
header->resizeAllColumnsToFit (getVisibleContentWidth());
|
||||
setMinimumContentWidth (header->getTotalWidth());
|
||||
}
|
||||
|
||||
void TableListBox::updateColumnComponents() const
|
||||
{
|
||||
auto firstRow = getRowContainingPosition (0, 0);
|
||||
|
||||
for (int i = firstRow + getNumRowsOnScreen() + 2; --i >= firstRow;)
|
||||
if (auto* rowComp = dynamic_cast<RowComp*> (getComponentForRowNumber (i)))
|
||||
rowComp->resized();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableListBoxModel::cellClicked (int, int, const MouseEvent&) {}
|
||||
void TableListBoxModel::cellDoubleClicked (int, int, const MouseEvent&) {}
|
||||
void TableListBoxModel::backgroundClicked (const MouseEvent&) {}
|
||||
void TableListBoxModel::sortOrderChanged (int, bool) {}
|
||||
int TableListBoxModel::getColumnAutoSizeWidth (int) { return 0; }
|
||||
void TableListBoxModel::selectedRowsChanged (int) {}
|
||||
void TableListBoxModel::deleteKeyPressed (int) {}
|
||||
void TableListBoxModel::returnKeyPressed (int) {}
|
||||
void TableListBoxModel::listWasScrolled() {}
|
||||
|
||||
String TableListBoxModel::getCellTooltip (int /*rowNumber*/, int /*columnId*/) { return {}; }
|
||||
var TableListBoxModel::getDragSourceDescription (const SparseSet<int>&) { return {}; }
|
||||
|
||||
Component* TableListBoxModel::refreshComponentForCell (int, int, bool, Component* existingComponentToUpdate)
|
||||
{
|
||||
ignoreUnused (existingComponentToUpdate);
|
||||
jassert (existingComponentToUpdate == nullptr); // indicates a failure in the code that recycles the components
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
353
modules/juce_gui_basics/widgets/juce_TableListBox.h
Normal file
353
modules/juce_gui_basics/widgets/juce_TableListBox.h
Normal file
@ -0,0 +1,353 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
One of these is used by a TableListBox as the data model for the table's contents.
|
||||
|
||||
The virtual methods that you override in this class take care of drawing the
|
||||
table cells, and reacting to events.
|
||||
|
||||
@see TableListBox
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TableListBoxModel
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
TableListBoxModel() {}
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~TableListBoxModel() {}
|
||||
|
||||
//==============================================================================
|
||||
/** This must return the number of rows currently in the table.
|
||||
|
||||
If the number of rows changes, you must call TableListBox::updateContent() to
|
||||
cause it to refresh the list.
|
||||
*/
|
||||
virtual int getNumRows() = 0;
|
||||
|
||||
/** This must draw the background behind one of the rows in the table.
|
||||
|
||||
The graphics context has its origin at the row's top-left, and your method
|
||||
should fill the area specified by the width and height parameters.
|
||||
|
||||
Note that the rowNumber value may be greater than the number of rows in your
|
||||
list, so be careful that you don't assume it's less than getNumRows().
|
||||
*/
|
||||
virtual void paintRowBackground (Graphics&,
|
||||
int rowNumber,
|
||||
int width, int height,
|
||||
bool rowIsSelected) = 0;
|
||||
|
||||
/** This must draw one of the cells.
|
||||
|
||||
The graphics context's origin will already be set to the top-left of the cell,
|
||||
whose size is specified by (width, height).
|
||||
|
||||
Note that the rowNumber value may be greater than the number of rows in your
|
||||
list, so be careful that you don't assume it's less than getNumRows().
|
||||
*/
|
||||
virtual void paintCell (Graphics&,
|
||||
int rowNumber,
|
||||
int columnId,
|
||||
int width, int height,
|
||||
bool rowIsSelected) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** This is used to create or update a custom component to go in a cell.
|
||||
|
||||
Any cell may contain a custom component, or can just be drawn with the paintCell() method
|
||||
and handle mouse clicks with cellClicked().
|
||||
|
||||
This method will be called whenever a custom component might need to be updated - e.g.
|
||||
when the table is changed, or TableListBox::updateContent() is called.
|
||||
|
||||
If you don't need a custom component for the specified cell, then return nullptr.
|
||||
(Bear in mind that even if you're not creating a new component, you may still need to
|
||||
delete existingComponentToUpdate if it's non-null).
|
||||
|
||||
If you do want a custom component, and the existingComponentToUpdate is null, then
|
||||
this method must create a new component suitable for the cell, and return it.
|
||||
|
||||
If the existingComponentToUpdate is non-null, it will be a pointer to a component previously created
|
||||
by this method. In this case, the method must either update it to make sure it's correctly representing
|
||||
the given cell (which may be different from the one that the component was created for), or it can
|
||||
delete this component and return a new one.
|
||||
*/
|
||||
virtual Component* refreshComponentForCell (int rowNumber, int columnId, bool isRowSelected,
|
||||
Component* existingComponentToUpdate);
|
||||
|
||||
//==============================================================================
|
||||
/** This callback is made when the user clicks on one of the cells in the table.
|
||||
|
||||
The mouse event's coordinates will be relative to the entire table row.
|
||||
@see cellDoubleClicked, backgroundClicked
|
||||
*/
|
||||
virtual void cellClicked (int rowNumber, int columnId, const MouseEvent&);
|
||||
|
||||
/** This callback is made when the user clicks on one of the cells in the table.
|
||||
|
||||
The mouse event's coordinates will be relative to the entire table row.
|
||||
@see cellClicked, backgroundClicked
|
||||
*/
|
||||
virtual void cellDoubleClicked (int rowNumber, int columnId, const MouseEvent&);
|
||||
|
||||
/** This can be overridden to react to the user double-clicking on a part of the list where
|
||||
there are no rows.
|
||||
|
||||
@see cellClicked
|
||||
*/
|
||||
virtual void backgroundClicked (const MouseEvent&);
|
||||
|
||||
//==============================================================================
|
||||
/** This callback is made when the table's sort order is changed.
|
||||
|
||||
This could be because the user has clicked a column header, or because the
|
||||
TableHeaderComponent::setSortColumnId() method was called.
|
||||
|
||||
If you implement this, your method should re-sort the table using the given
|
||||
column as the key.
|
||||
*/
|
||||
virtual void sortOrderChanged (int newSortColumnId, bool isForwards);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the best width for one of the columns.
|
||||
|
||||
If you implement this method, you should measure the width of all the items
|
||||
in this column, and return the best size.
|
||||
|
||||
Returning 0 means that the column shouldn't be changed.
|
||||
|
||||
This is used by TableListBox::autoSizeColumn() and TableListBox::autoSizeAllColumns().
|
||||
*/
|
||||
virtual int getColumnAutoSizeWidth (int columnId);
|
||||
|
||||
/** Returns a tooltip for a particular cell in the table. */
|
||||
virtual String getCellTooltip (int rowNumber, int columnId);
|
||||
|
||||
//==============================================================================
|
||||
/** Override this to be informed when rows are selected or deselected.
|
||||
@see ListBox::selectedRowsChanged()
|
||||
*/
|
||||
virtual void selectedRowsChanged (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the delete key is pressed.
|
||||
@see ListBox::deleteKeyPressed()
|
||||
*/
|
||||
virtual void deleteKeyPressed (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the return key is pressed.
|
||||
@see ListBox::returnKeyPressed()
|
||||
*/
|
||||
virtual void returnKeyPressed (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the list is scrolled.
|
||||
|
||||
This might be caused by the user moving the scrollbar, or by programmatic changes
|
||||
to the list position.
|
||||
*/
|
||||
virtual void listWasScrolled();
|
||||
|
||||
/** To allow rows from your table to be dragged-and-dropped, implement this method.
|
||||
|
||||
If this returns a non-null variant then when the user drags a row, the table will try to
|
||||
find a DragAndDropContainer in its parent hierarchy, and will use it to trigger a
|
||||
drag-and-drop operation, using this string as the source description, and the listbox
|
||||
itself as the source component.
|
||||
|
||||
@see getDragSourceCustomData, DragAndDropContainer::startDragging
|
||||
*/
|
||||
virtual var getDragSourceDescription (const SparseSet<int>& currentlySelectedRows);
|
||||
|
||||
private:
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// This method's signature has changed to take a MouseEvent parameter - please update your code!
|
||||
JUCE_DEPRECATED_WITH_BODY (virtual int backgroundClicked(), { return 0; })
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A table of cells, using a TableHeaderComponent as its header.
|
||||
|
||||
This component makes it easy to create a table by providing a TableListBoxModel as
|
||||
the data source.
|
||||
|
||||
|
||||
@see TableListBoxModel, TableHeaderComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TableListBox : public ListBox,
|
||||
private ListBoxModel,
|
||||
private TableHeaderComponent::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a TableListBox.
|
||||
|
||||
The model pointer passed-in can be null, in which case you can set it later
|
||||
with setModel(). The TableListBox does not take ownership of the model - it's
|
||||
the caller's responsibility to manage its lifetime and make sure it
|
||||
doesn't get deleted while still being used.
|
||||
*/
|
||||
TableListBox (const String& componentName = String(),
|
||||
TableListBoxModel* model = nullptr);
|
||||
|
||||
/** Destructor. */
|
||||
~TableListBox();
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the TableListBoxModel that is being used for this table.
|
||||
The TableListBox does not take ownership of the model - it's the caller's responsibility
|
||||
to manage its lifetime and make sure it doesn't get deleted while still being used.
|
||||
*/
|
||||
void setModel (TableListBoxModel* newModel);
|
||||
|
||||
/** Returns the model currently in use. */
|
||||
TableListBoxModel* getModel() const noexcept { return model; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the header component being used in this table. */
|
||||
TableHeaderComponent& getHeader() const noexcept { return *header; }
|
||||
|
||||
/** Sets the header component to use for the table.
|
||||
The table will take ownership of the component that you pass in, and will delete it
|
||||
when it's no longer needed.
|
||||
The pointer passed in may not be null.
|
||||
*/
|
||||
void setHeader (TableHeaderComponent* newHeader);
|
||||
|
||||
/** Changes the height of the table header component.
|
||||
@see getHeaderHeight
|
||||
*/
|
||||
void setHeaderHeight (int newHeight);
|
||||
|
||||
/** Returns the height of the table header.
|
||||
@see setHeaderHeight
|
||||
*/
|
||||
int getHeaderHeight() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Resizes a column to fit its contents.
|
||||
|
||||
This uses TableListBoxModel::getColumnAutoSizeWidth() to find the best width,
|
||||
and applies that to the column.
|
||||
|
||||
@see autoSizeAllColumns, TableHeaderComponent::setColumnWidth
|
||||
*/
|
||||
void autoSizeColumn (int columnId);
|
||||
|
||||
/** Calls autoSizeColumn() for all columns in the table. */
|
||||
void autoSizeAllColumns();
|
||||
|
||||
/** Enables or disables the auto size options on the popup menu.
|
||||
By default, these are enabled.
|
||||
*/
|
||||
void setAutoSizeMenuOptionShown (bool shouldBeShown) noexcept;
|
||||
|
||||
/** True if the auto-size options should be shown on the menu.
|
||||
@see setAutoSizeMenuOptionShown
|
||||
*/
|
||||
bool isAutoSizeMenuOptionShown() const noexcept { return autoSizeOptionsShown; }
|
||||
|
||||
/** Returns the position of one of the cells in the table.
|
||||
|
||||
If relativeToComponentTopLeft is true, the coordinates are relative to
|
||||
the table component's top-left. The row number isn't checked to see if it's
|
||||
in-range, but the column ID must exist or this will return an empty rectangle.
|
||||
|
||||
If relativeToComponentTopLeft is false, the coordinates are relative to the
|
||||
top-left of the table's top-left cell.
|
||||
*/
|
||||
Rectangle<int> getCellPosition (int columnId, int rowNumber,
|
||||
bool relativeToComponentTopLeft) const;
|
||||
|
||||
/** Returns the component that currently represents a given cell.
|
||||
If the component for this cell is off-screen or if the position is out-of-range,
|
||||
this may return nullptr.
|
||||
@see getCellPosition
|
||||
*/
|
||||
Component* getCellComponent (int columnId, int rowNumber) const;
|
||||
|
||||
/** Scrolls horizontally if necessary to make sure that a particular column is visible.
|
||||
|
||||
@see ListBox::scrollToEnsureRowIsOnscreen
|
||||
*/
|
||||
void scrollToEnsureColumnIsOnscreen (int columnId);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
int getNumRows() override;
|
||||
/** @internal */
|
||||
void paintListBoxItem (int, Graphics&, int, int, bool) override;
|
||||
/** @internal */
|
||||
Component* refreshComponentForRow (int rowNumber, bool isRowSelected, Component* existingComponentToUpdate) override;
|
||||
/** @internal */
|
||||
void selectedRowsChanged (int row) override;
|
||||
/** @internal */
|
||||
void deleteKeyPressed (int currentSelectedRow) override;
|
||||
/** @internal */
|
||||
void returnKeyPressed (int currentSelectedRow) override;
|
||||
/** @internal */
|
||||
void backgroundClicked (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void listWasScrolled() override;
|
||||
/** @internal */
|
||||
void tableColumnsChanged (TableHeaderComponent*) override;
|
||||
/** @internal */
|
||||
void tableColumnsResized (TableHeaderComponent*) override;
|
||||
/** @internal */
|
||||
void tableSortOrderChanged (TableHeaderComponent*) override;
|
||||
/** @internal */
|
||||
void tableColumnDraggingChanged (TableHeaderComponent*, int) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class Header;
|
||||
class RowComp;
|
||||
|
||||
TableHeaderComponent* header = nullptr;
|
||||
TableListBoxModel* model;
|
||||
int columnIdNowBeingDragged = 0;
|
||||
bool autoSizeOptionsShown = true;
|
||||
|
||||
void updateColumnComponents() const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableListBox)
|
||||
};
|
||||
|
||||
} // namespace juce
|
2545
modules/juce_gui_basics/widgets/juce_TextEditor.cpp
Normal file
2545
modules/juce_gui_basics/widgets/juce_TextEditor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
790
modules/juce_gui_basics/widgets/juce_TextEditor.h
Normal file
790
modules/juce_gui_basics/widgets/juce_TextEditor.h
Normal file
@ -0,0 +1,790 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An editable text box.
|
||||
|
||||
A TextEditor can either be in single- or multi-line mode, and supports mixed
|
||||
fonts and colours.
|
||||
|
||||
@see TextEditor::Listener, Label
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TextEditor : public Component,
|
||||
public TextInputTarget,
|
||||
public SettableTooltipClient
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a new, empty text editor.
|
||||
|
||||
@param componentName the name to pass to the component for it to use as its name
|
||||
@param passwordCharacter if this is not zero, this character will be used as a replacement
|
||||
for all characters that are drawn on screen - e.g. to create
|
||||
a password-style textbox containing circular blobs instead of text,
|
||||
you could set this value to 0x25cf, which is the unicode character
|
||||
for a black splodge (not all fonts include this, though), or 0x2022,
|
||||
which is a bullet (probably the best choice for linux).
|
||||
*/
|
||||
explicit TextEditor (const String& componentName = String(),
|
||||
juce_wchar passwordCharacter = 0);
|
||||
|
||||
/** Destructor. */
|
||||
~TextEditor();
|
||||
|
||||
//==============================================================================
|
||||
/** Puts the editor into either multi- or single-line mode.
|
||||
|
||||
By default, the editor will be in single-line mode, so use this if you need a multi-line
|
||||
editor.
|
||||
|
||||
See also the setReturnKeyStartsNewLine() method, which will also need to be turned
|
||||
on if you want a multi-line editor with line-breaks.
|
||||
|
||||
@see isMultiLine, setReturnKeyStartsNewLine
|
||||
*/
|
||||
void setMultiLine (bool shouldBeMultiLine,
|
||||
bool shouldWordWrap = true);
|
||||
|
||||
/** Returns true if the editor is in multi-line mode. */
|
||||
bool isMultiLine() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the behaviour of the return key.
|
||||
|
||||
If set to true, the return key will insert a new-line into the text; if false
|
||||
it will trigger a call to the TextEditor::Listener::textEditorReturnKeyPressed()
|
||||
method. By default this is set to false, and when true it will only insert
|
||||
new-lines when in multi-line mode (see setMultiLine()).
|
||||
*/
|
||||
void setReturnKeyStartsNewLine (bool shouldStartNewLine);
|
||||
|
||||
/** Returns the value set by setReturnKeyStartsNewLine().
|
||||
See setReturnKeyStartsNewLine() for more info.
|
||||
*/
|
||||
bool getReturnKeyStartsNewLine() const { return returnKeyStartsNewLine; }
|
||||
|
||||
/** Indicates whether the tab key should be accepted and used to input a tab character,
|
||||
or whether it gets ignored.
|
||||
|
||||
By default the tab key is ignored, so that it can be used to switch keyboard focus
|
||||
between components.
|
||||
*/
|
||||
void setTabKeyUsedAsCharacter (bool shouldTabKeyBeUsed);
|
||||
|
||||
/** Returns true if the tab key is being used for input.
|
||||
@see setTabKeyUsedAsCharacter
|
||||
*/
|
||||
bool isTabKeyUsedAsCharacter() const { return tabKeyUsed; }
|
||||
|
||||
/** This can be used to change whether escape and return keypress events are
|
||||
propagated up to the parent component.
|
||||
The default here is true, meaning that these events are not allowed to reach the
|
||||
parent, but you may want to allow them through so that they can trigger other
|
||||
actions, e.g. closing a dialog box, etc.
|
||||
*/
|
||||
void setEscapeAndReturnKeysConsumed (bool shouldBeConsumed) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the editor to read-only mode.
|
||||
|
||||
By default, the text editor is not read-only. If you're making it read-only, you
|
||||
might also want to call setCaretVisible (false) to get rid of the caret.
|
||||
|
||||
The text can still be highlighted and copied when in read-only mode.
|
||||
|
||||
@see isReadOnly, setCaretVisible
|
||||
*/
|
||||
void setReadOnly (bool shouldBeReadOnly);
|
||||
|
||||
/** Returns true if the editor is in read-only mode. */
|
||||
bool isReadOnly() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Makes the caret visible or invisible.
|
||||
By default the caret is visible.
|
||||
@see setCaretColour, setCaretPosition
|
||||
*/
|
||||
void setCaretVisible (bool shouldBeVisible);
|
||||
|
||||
/** Returns true if the caret is enabled.
|
||||
@see setCaretVisible
|
||||
*/
|
||||
bool isCaretVisible() const noexcept { return caretVisible && ! isReadOnly(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Enables/disables a vertical scrollbar.
|
||||
|
||||
(This only applies when in multi-line mode). When the text gets too long to fit
|
||||
in the component, a scrollbar can appear to allow it to be scrolled. Even when
|
||||
this is enabled, the scrollbar will be hidden unless it's needed.
|
||||
|
||||
By default the scrollbar is enabled.
|
||||
*/
|
||||
void setScrollbarsShown (bool shouldBeEnabled);
|
||||
|
||||
/** Returns true if scrollbars are enabled.
|
||||
@see setScrollbarsShown
|
||||
*/
|
||||
bool areScrollbarsShown() const noexcept { return scrollbarVisible; }
|
||||
|
||||
|
||||
/** Changes the password character used to disguise the text.
|
||||
|
||||
@param passwordCharacter if this is not zero, this character will be used as a replacement
|
||||
for all characters that are drawn on screen - e.g. to create
|
||||
a password-style textbox containing circular blobs instead of text,
|
||||
you could set this value to 0x25cf, which is the unicode character
|
||||
for a black splodge (not all fonts include this, though), or 0x2022,
|
||||
which is a bullet (probably the best choice for linux).
|
||||
*/
|
||||
void setPasswordCharacter (juce_wchar passwordCharacter);
|
||||
|
||||
/** Returns the current password character.
|
||||
@see setPasswordCharacter
|
||||
*/
|
||||
juce_wchar getPasswordCharacter() const noexcept { return passwordCharacter; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Allows a right-click menu to appear for the editor.
|
||||
|
||||
(This defaults to being enabled).
|
||||
|
||||
If enabled, right-clicking (or command-clicking on the Mac) will pop up a menu
|
||||
of options such as cut/copy/paste, undo/redo, etc.
|
||||
*/
|
||||
void setPopupMenuEnabled (bool menuEnabled);
|
||||
|
||||
/** Returns true if the right-click menu is enabled.
|
||||
@see setPopupMenuEnabled
|
||||
*/
|
||||
bool isPopupMenuEnabled() const noexcept { return popupMenuEnabled; }
|
||||
|
||||
/** Returns true if a popup-menu is currently being displayed. */
|
||||
bool isPopupMenuCurrentlyActive() const noexcept { return menuActive; }
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the editor.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
NB: You can also set the caret colour using CaretComponent::caretColourId
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1000200, /**< The colour to use for the text component's background - this can be
|
||||
transparent if necessary. */
|
||||
|
||||
textColourId = 0x1000201, /**< The colour that will be used when text is added to the editor. Note
|
||||
that because the editor can contain multiple colours, calling this
|
||||
method won't change the colour of existing text - to do that, use
|
||||
the applyColourToAllText() method */
|
||||
|
||||
highlightColourId = 0x1000202, /**< The colour with which to fill the background of highlighted sections of
|
||||
the text - this can be transparent if you don't want to show any
|
||||
highlighting.*/
|
||||
|
||||
highlightedTextColourId = 0x1000203, /**< The colour with which to draw the text in highlighted sections. */
|
||||
|
||||
outlineColourId = 0x1000205, /**< If this is non-transparent, it will be used to draw a box around
|
||||
the edge of the component. */
|
||||
|
||||
focusedOutlineColourId = 0x1000206, /**< If this is non-transparent, it will be used to draw a box around
|
||||
the edge of the component when it has focus. */
|
||||
|
||||
shadowColourId = 0x1000207, /**< If this is non-transparent, it'll be used to draw an inner shadow
|
||||
around the edge of the editor. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the font to use for newly added text.
|
||||
|
||||
This will change the font that will be used next time any text is added or entered
|
||||
into the editor. It won't change the font of any existing text - to do that, use
|
||||
applyFontToAllText() instead.
|
||||
|
||||
@see applyFontToAllText
|
||||
*/
|
||||
void setFont (const Font& newFont);
|
||||
|
||||
/** Applies a font to all the text in the editor.
|
||||
|
||||
If the changeCurrentFont argument is true then this will also set the
|
||||
new font as the font to be used for any new text that's added.
|
||||
|
||||
@see setFont
|
||||
*/
|
||||
void applyFontToAllText (const Font& newFont, bool changeCurrentFont = true);
|
||||
|
||||
/** Returns the font that's currently being used for new text.
|
||||
|
||||
@see setFont
|
||||
*/
|
||||
const Font& getFont() const noexcept { return currentFont; }
|
||||
|
||||
/** Applies a colour to all the text in the editor.
|
||||
|
||||
If the changeCurrentTextColour argument is true then this will also set the
|
||||
new colour as the colour to be used for any new text that's added.
|
||||
*/
|
||||
void applyColourToAllText (const Colour& newColour, bool changeCurrentTextColour = true);
|
||||
|
||||
//==============================================================================
|
||||
/** If set to true, focusing on the editor will highlight all its text.
|
||||
|
||||
(Set to false by default).
|
||||
|
||||
This is useful for boxes where you expect the user to re-enter all the
|
||||
text when they focus on the component, rather than editing what's already there.
|
||||
*/
|
||||
void setSelectAllWhenFocused (bool shouldSelectAll);
|
||||
|
||||
/** When the text editor is empty, it can be set to display a message.
|
||||
|
||||
This is handy for things like telling the user what to type in the box - the
|
||||
string is only displayed, it's not taken to actually be the contents of
|
||||
the editor.
|
||||
*/
|
||||
void setTextToShowWhenEmpty (const String& text, Colour colourToUse);
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the size of the scrollbars that are used.
|
||||
Handy if you need smaller scrollbars for a small text box.
|
||||
*/
|
||||
void setScrollBarThickness (int newThicknessPixels);
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Receives callbacks from a TextEditor component when it changes.
|
||||
|
||||
@see TextEditor::addListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called when the user changes the text in some way. */
|
||||
virtual void textEditorTextChanged (TextEditor&) {}
|
||||
|
||||
/** Called when the user presses the return key. */
|
||||
virtual void textEditorReturnKeyPressed (TextEditor&) {}
|
||||
|
||||
/** Called when the user presses the escape key. */
|
||||
virtual void textEditorEscapeKeyPressed (TextEditor&) {}
|
||||
|
||||
/** Called when the text editor loses focus. */
|
||||
virtual void textEditorFocusLost (TextEditor&) {}
|
||||
};
|
||||
|
||||
/** Registers a listener to be told when things happen to the text.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (Listener* newListener);
|
||||
|
||||
/** Deregisters a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (Listener* listenerToRemove);
|
||||
|
||||
//==============================================================================
|
||||
/** You can assign a lambda to this callback object to have it called when the text is changed. */
|
||||
std::function<void()> onTextChange;
|
||||
|
||||
/** You can assign a lambda to this callback object to have it called when the return key is pressed. */
|
||||
std::function<void()> onReturnKey;
|
||||
|
||||
/** You can assign a lambda to this callback object to have it called when the escape key is pressed. */
|
||||
std::function<void()> onEscapeKey;
|
||||
|
||||
/** You can assign a lambda to this callback object to have it called when the editor loses key focus. */
|
||||
std::function<void()> onFocusLost;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the entire contents of the editor. */
|
||||
String getText() const;
|
||||
|
||||
/** Returns a section of the contents of the editor. */
|
||||
String getTextInRange (const Range<int>& textRange) const override;
|
||||
|
||||
/** Returns true if there are no characters in the editor.
|
||||
This is far more efficient than calling getText().isEmpty().
|
||||
*/
|
||||
bool isEmpty() const;
|
||||
|
||||
/** Sets the entire content of the editor.
|
||||
|
||||
This will clear the editor and insert the given text (using the current text colour
|
||||
and font). You can set the current text colour using
|
||||
@code setColour (TextEditor::textColourId, ...);
|
||||
@endcode
|
||||
|
||||
@param newText the text to add
|
||||
@param sendTextChangeMessage if true, this will cause a change message to
|
||||
be sent to all the listeners.
|
||||
@see insertTextAtCaret
|
||||
*/
|
||||
void setText (const String& newText,
|
||||
bool sendTextChangeMessage = true);
|
||||
|
||||
/** Returns a Value object that can be used to get or set the text.
|
||||
|
||||
Bear in mind that this operate quite slowly if your text box contains large
|
||||
amounts of text, as it needs to dynamically build the string that's involved.
|
||||
It's best used for small text boxes.
|
||||
*/
|
||||
Value& getTextValue();
|
||||
|
||||
/** Inserts some text at the current caret position.
|
||||
|
||||
If a section of the text is highlighted, it will be replaced by
|
||||
this string, otherwise it will be inserted.
|
||||
|
||||
To delete a section of text, you can use setHighlightedRegion() to
|
||||
highlight it, and call insertTextAtCaret (String()).
|
||||
|
||||
@see setCaretPosition, getCaretPosition, setHighlightedRegion
|
||||
*/
|
||||
void insertTextAtCaret (const String& textToInsert) override;
|
||||
|
||||
/** Deletes all the text from the editor. */
|
||||
void clear();
|
||||
|
||||
/** Deletes the currently selected region.
|
||||
This doesn't copy the deleted section to the clipboard - if you need to do that, call copy() first.
|
||||
@see copy, paste, SystemClipboard
|
||||
*/
|
||||
void cut();
|
||||
|
||||
/** Copies the currently selected region to the clipboard.
|
||||
@see cut, paste, SystemClipboard
|
||||
*/
|
||||
void copy();
|
||||
|
||||
/** Pastes the contents of the clipboard into the editor at the caret position.
|
||||
@see cut, copy, SystemClipboard
|
||||
*/
|
||||
void paste();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the current index of the caret.
|
||||
@see setCaretPosition
|
||||
*/
|
||||
int getCaretPosition() const;
|
||||
|
||||
/** Moves the caret to be in front of a given character.
|
||||
@see getCaretPosition, moveCaretToEnd
|
||||
*/
|
||||
void setCaretPosition (int newIndex);
|
||||
|
||||
/** Attempts to scroll the text editor so that the caret ends up at
|
||||
a specified position.
|
||||
|
||||
This won't affect the caret's position within the text, it tries to scroll
|
||||
the entire editor vertically and horizontally so that the caret is sitting
|
||||
at the given position (relative to the top-left of this component).
|
||||
|
||||
Depending on the amount of text available, it might not be possible to
|
||||
scroll far enough for the caret to reach this exact position, but it
|
||||
will go as far as it can in that direction.
|
||||
*/
|
||||
void scrollEditorToPositionCaret (int desiredCaretX, int desiredCaretY);
|
||||
|
||||
/** Get the graphical position of the caret.
|
||||
|
||||
The rectangle returned is relative to the component's top-left corner.
|
||||
@see scrollEditorToPositionCaret
|
||||
*/
|
||||
Rectangle<int> getCaretRectangle() override;
|
||||
|
||||
/** Selects a section of the text. */
|
||||
void setHighlightedRegion (const Range<int>& newSelection) override;
|
||||
|
||||
/** Returns the range of characters that are selected.
|
||||
If nothing is selected, this will return an empty range.
|
||||
@see setHighlightedRegion
|
||||
*/
|
||||
Range<int> getHighlightedRegion() const override { return selection; }
|
||||
|
||||
/** Returns the section of text that is currently selected. */
|
||||
String getHighlightedText() const;
|
||||
|
||||
/** Finds the index of the character at a given position.
|
||||
The coordinates are relative to the component's top-left.
|
||||
*/
|
||||
int getTextIndexAt (int x, int y);
|
||||
|
||||
/** Counts the number of characters in the text.
|
||||
|
||||
This is quicker than getting the text as a string if you just need to know
|
||||
the length.
|
||||
*/
|
||||
int getTotalNumChars() const;
|
||||
|
||||
/** Returns the total width of the text, as it is currently laid-out.
|
||||
|
||||
This may be larger than the size of the TextEditor, and can change when
|
||||
the TextEditor is resized or the text changes.
|
||||
*/
|
||||
int getTextWidth() const;
|
||||
|
||||
/** Returns the maximum height of the text, as it is currently laid-out.
|
||||
|
||||
This may be larger than the size of the TextEditor, and can change when
|
||||
the TextEditor is resized or the text changes.
|
||||
*/
|
||||
int getTextHeight() const;
|
||||
|
||||
/** Changes the size of the gap at the top and left-edge of the editor.
|
||||
By default there's a gap of 4 pixels.
|
||||
*/
|
||||
void setIndents (int newLeftIndent, int newTopIndent);
|
||||
|
||||
/** Changes the size of border left around the edge of the component.
|
||||
@see getBorder
|
||||
*/
|
||||
void setBorder (const BorderSize<int>& border);
|
||||
|
||||
/** Returns the size of border around the edge of the component.
|
||||
@see setBorder
|
||||
*/
|
||||
BorderSize<int> getBorder() const;
|
||||
|
||||
/** Used to disable the auto-scrolling which keeps the caret visible.
|
||||
|
||||
If true (the default), the editor will scroll when the caret moves offscreen. If
|
||||
set to false, it won't.
|
||||
*/
|
||||
void setScrollToShowCursor (bool shouldScrollToShowCaret);
|
||||
|
||||
/** Modifies the horizontal justification of the text within the editor window. */
|
||||
void setJustification (Justification newJustification);
|
||||
|
||||
/** Sets the line spacing of the TextEditor.
|
||||
The default (and minimum) value is 1.0 and values > 1.0 will increase the line spacing as a
|
||||
multiple of the line height e.g. for double-spacing call this method with an argument of 2.0.
|
||||
*/
|
||||
void setLineSpacing (float newLineSpacing) noexcept { lineSpacing = jmax (1.0f, newLineSpacing); }
|
||||
|
||||
/** Returns the current line spacing of the TextEditor. */
|
||||
float getLineSpacing() const noexcept { return lineSpacing; }
|
||||
|
||||
//==============================================================================
|
||||
void moveCaretToEnd();
|
||||
bool moveCaretLeft (bool moveInWholeWordSteps, bool selecting);
|
||||
bool moveCaretRight (bool moveInWholeWordSteps, bool selecting);
|
||||
bool moveCaretUp (bool selecting);
|
||||
bool moveCaretDown (bool selecting);
|
||||
bool pageUp (bool selecting);
|
||||
bool pageDown (bool selecting);
|
||||
bool scrollDown();
|
||||
bool scrollUp();
|
||||
bool moveCaretToTop (bool selecting);
|
||||
bool moveCaretToStartOfLine (bool selecting);
|
||||
bool moveCaretToEnd (bool selecting);
|
||||
bool moveCaretToEndOfLine (bool selecting);
|
||||
bool deleteBackwards (bool moveInWholeWordSteps);
|
||||
bool deleteForwards (bool moveInWholeWordSteps);
|
||||
bool copyToClipboard();
|
||||
bool cutToClipboard();
|
||||
bool pasteFromClipboard();
|
||||
bool selectAll();
|
||||
bool undo();
|
||||
bool redo();
|
||||
|
||||
//==============================================================================
|
||||
/** This adds the items to the popup menu.
|
||||
|
||||
By default it adds the cut/copy/paste items, but you can override this if
|
||||
you need to replace these with your own items.
|
||||
|
||||
If you want to add your own items to the existing ones, you can override this,
|
||||
call the base class's addPopupMenuItems() method, then append your own items.
|
||||
|
||||
When the menu has been shown, performPopupMenuAction() will be called to
|
||||
perform the item that the user has chosen.
|
||||
|
||||
The default menu items will be added using item IDs from the
|
||||
StandardApplicationCommandIDs namespace.
|
||||
|
||||
If this was triggered by a mouse-click, the mouseClickEvent parameter will be
|
||||
a pointer to the info about it, or may be null if the menu is being triggered
|
||||
by some other means.
|
||||
|
||||
@see performPopupMenuAction, setPopupMenuEnabled, isPopupMenuEnabled
|
||||
*/
|
||||
virtual void addPopupMenuItems (PopupMenu& menuToAddTo,
|
||||
const MouseEvent* mouseClickEvent);
|
||||
|
||||
/** This is called to perform one of the items that was shown on the popup menu.
|
||||
|
||||
If you've overridden addPopupMenuItems(), you should also override this
|
||||
to perform the actions that you've added.
|
||||
|
||||
If you've overridden addPopupMenuItems() but have still left the default items
|
||||
on the menu, remember to call the superclass's performPopupMenuAction()
|
||||
so that it can perform the default actions if that's what the user clicked on.
|
||||
|
||||
@see addPopupMenuItems, setPopupMenuEnabled, isPopupMenuEnabled
|
||||
*/
|
||||
virtual void performPopupMenuAction (int menuItemID);
|
||||
|
||||
//==============================================================================
|
||||
/** Base class for input filters that can be applied to a TextEditor to restrict
|
||||
the text that can be entered.
|
||||
*/
|
||||
class JUCE_API InputFilter
|
||||
{
|
||||
public:
|
||||
InputFilter() {}
|
||||
virtual ~InputFilter() {}
|
||||
|
||||
/** This method is called whenever text is entered into the editor.
|
||||
An implementation of this class should should check the input string,
|
||||
and return an edited version of it that should be used.
|
||||
*/
|
||||
virtual String filterNewText (TextEditor&, const String& newInput) = 0;
|
||||
};
|
||||
|
||||
/** An input filter for a TextEditor that limits the length of text and/or the
|
||||
characters that it may contain.
|
||||
*/
|
||||
class JUCE_API LengthAndCharacterRestriction : public InputFilter
|
||||
{
|
||||
public:
|
||||
/** Creates a filter that limits the length of text, and/or the characters that it can contain.
|
||||
@param maxNumChars if this is > 0, it sets a maximum length limit; if <= 0, no
|
||||
limit is set
|
||||
@param allowedCharacters if this is non-empty, then only characters that occur in
|
||||
this string are allowed to be entered into the editor.
|
||||
*/
|
||||
LengthAndCharacterRestriction (int maxNumChars, const String& allowedCharacters);
|
||||
|
||||
String filterNewText (TextEditor&, const String&) override;
|
||||
|
||||
private:
|
||||
String allowedCharacters;
|
||||
int maxLength;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LengthAndCharacterRestriction)
|
||||
};
|
||||
|
||||
/** Sets an input filter that should be applied to this editor.
|
||||
The filter can be nullptr, to remove any existing filters.
|
||||
If takeOwnership is true, then the filter will be owned and deleted by the editor
|
||||
when no longer needed.
|
||||
*/
|
||||
void setInputFilter (InputFilter* newFilter, bool takeOwnership);
|
||||
|
||||
/** Returns the current InputFilter, as set by setInputFilter(). */
|
||||
InputFilter* getInputFilter() const noexcept { return inputFilter; }
|
||||
|
||||
/** Sets limits on the characters that can be entered.
|
||||
This is just a shortcut that passes an instance of the LengthAndCharacterRestriction
|
||||
class to setInputFilter().
|
||||
|
||||
@param maxTextLength if this is > 0, it sets a maximum length limit; if 0, no
|
||||
limit is set
|
||||
@param allowedCharacters if this is non-empty, then only characters that occur in
|
||||
this string are allowed to be entered into the editor.
|
||||
*/
|
||||
void setInputRestrictions (int maxTextLength,
|
||||
const String& allowedCharacters = String());
|
||||
|
||||
void setKeyboardType (VirtualKeyboardType type) noexcept { keyboardType = type; }
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
TextEditor drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void fillTextEditorBackground (Graphics&, int width, int height, TextEditor&) = 0;
|
||||
virtual void drawTextEditorOutline (Graphics&, int width, int height, TextEditor&) = 0;
|
||||
|
||||
virtual CaretComponent* createCaretComponent (Component* keyFocusOwner) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void paintOverChildren (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDoubleClick (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
bool keyStateChanged (bool) override;
|
||||
/** @internal */
|
||||
void focusGained (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void focusLost (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
bool isTextInputActive() const override;
|
||||
/** @internal */
|
||||
void setTemporaryUnderlining (const Array<Range<int>>&) override;
|
||||
/** @internal */
|
||||
VirtualKeyboardType getKeyboardType() override { return keyboardType; }
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Scrolls the minimum distance needed to get the caret into view. */
|
||||
void scrollToMakeSureCursorIsVisible();
|
||||
|
||||
/** Used internally to dispatch a text-change message. */
|
||||
void textChanged();
|
||||
|
||||
/** Begins a new transaction in the UndoManager. */
|
||||
void newTransaction();
|
||||
|
||||
/** Can be overridden to intercept return key presses directly */
|
||||
virtual void returnPressed();
|
||||
|
||||
/** Can be overridden to intercept escape key presses directly */
|
||||
virtual void escapePressed();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (class UniformTextSection)
|
||||
struct Iterator;
|
||||
struct TextHolderComponent;
|
||||
struct TextEditorViewport;
|
||||
struct InsertAction;
|
||||
struct RemoveAction;
|
||||
|
||||
std::unique_ptr<Viewport> viewport;
|
||||
TextHolderComponent* textHolder;
|
||||
BorderSize<int> borderSize { 1, 1, 1, 3 };
|
||||
Justification justification { Justification::left };
|
||||
|
||||
bool readOnly = false;
|
||||
bool caretVisible = true;
|
||||
bool multiline = false;
|
||||
bool wordWrap = false;
|
||||
bool returnKeyStartsNewLine = false;
|
||||
bool popupMenuEnabled = true;
|
||||
bool selectAllTextWhenFocused = false;
|
||||
bool scrollbarVisible = true;
|
||||
bool wasFocused = false;
|
||||
bool keepCaretOnScreen = true;
|
||||
bool tabKeyUsed = false;
|
||||
bool menuActive = false;
|
||||
bool valueTextNeedsUpdating = false;
|
||||
bool consumeEscAndReturnKeys = true;
|
||||
|
||||
UndoManager undoManager;
|
||||
std::unique_ptr<CaretComponent> caret;
|
||||
Range<int> selection;
|
||||
int leftIndent = 4, topIndent = 4;
|
||||
unsigned int lastTransactionTime = 0;
|
||||
Font currentFont { 14.0f };
|
||||
mutable int totalNumChars = 0;
|
||||
int caretPosition = 0;
|
||||
OwnedArray<UniformTextSection> sections;
|
||||
String textToShowWhenEmpty;
|
||||
Colour colourForTextWhenEmpty;
|
||||
juce_wchar passwordCharacter;
|
||||
OptionalScopedPointer<InputFilter> inputFilter;
|
||||
Value textValue;
|
||||
VirtualKeyboardType keyboardType = TextInputTarget::textKeyboard;
|
||||
float lineSpacing = 1.0f;
|
||||
|
||||
enum DragType
|
||||
{
|
||||
notDragging,
|
||||
draggingSelectionStart,
|
||||
draggingSelectionEnd
|
||||
};
|
||||
|
||||
DragType dragType = notDragging;
|
||||
|
||||
ListenerList<Listener> listeners;
|
||||
Array<Range<int>> underlinedSections;
|
||||
|
||||
void moveCaret (int newCaretPos);
|
||||
void moveCaretTo (int newPosition, bool isSelecting);
|
||||
void recreateCaret();
|
||||
void handleCommandMessage (int) override;
|
||||
void coalesceSimilarSections();
|
||||
void splitSection (int sectionIndex, int charToSplitAt);
|
||||
void clearInternal (UndoManager*);
|
||||
void insert (const String&, int insertIndex, const Font&, Colour, UndoManager*, int newCaretPos);
|
||||
void reinsert (int insertIndex, const OwnedArray<UniformTextSection>&);
|
||||
void remove (Range<int>, UndoManager*, int caretPositionToMoveTo);
|
||||
void getCharPosition (int index, Point<float>&, float& lineHeight) const;
|
||||
Rectangle<float> getCaretRectangleFloat() const;
|
||||
void updateCaretPosition();
|
||||
void updateValueFromText();
|
||||
void textWasChangedByValue();
|
||||
int indexAtPosition (float x, float y);
|
||||
int findWordBreakAfter (int position) const;
|
||||
int findWordBreakBefore (int position) const;
|
||||
bool moveCaretWithTransaction (int newPos, bool selecting);
|
||||
void drawContent (Graphics&);
|
||||
void updateTextHolderSize();
|
||||
float getWordWrapWidth() const;
|
||||
float getJustificationWidth() const;
|
||||
void timerCallbackInt();
|
||||
void checkFocus();
|
||||
void repaintText (Range<int>);
|
||||
void scrollByLines (int deltaLines);
|
||||
bool undoOrRedo (bool shouldUndo);
|
||||
UndoManager* getUndoManager() noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextEditor)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
805
modules/juce_gui_basics/widgets/juce_Toolbar.cpp
Normal file
805
modules/juce_gui_basics/widgets/juce_Toolbar.cpp
Normal file
@ -0,0 +1,805 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
const char* const Toolbar::toolbarDragDescriptor = "_toolbarItem_";
|
||||
|
||||
//==============================================================================
|
||||
class Toolbar::Spacer : public ToolbarItemComponent
|
||||
{
|
||||
public:
|
||||
Spacer (int itemID, float sizeToUse, bool shouldDrawBar)
|
||||
: ToolbarItemComponent (itemID, {}, false),
|
||||
fixedSize (sizeToUse),
|
||||
drawBar (shouldDrawBar)
|
||||
{
|
||||
setWantsKeyboardFocus (false);
|
||||
}
|
||||
|
||||
bool getToolbarItemSizes (int toolbarThickness, bool /*isToolbarVertical*/,
|
||||
int& preferredSize, int& minSize, int& maxSize) override
|
||||
{
|
||||
if (fixedSize <= 0)
|
||||
{
|
||||
preferredSize = toolbarThickness * 2;
|
||||
minSize = 4;
|
||||
maxSize = 32768;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxSize = roundToInt (toolbarThickness * fixedSize);
|
||||
minSize = drawBar ? maxSize : jmin (4, maxSize);
|
||||
preferredSize = maxSize;
|
||||
|
||||
if (getEditingMode() == editableOnPalette)
|
||||
preferredSize = maxSize = toolbarThickness / (drawBar ? 3 : 2);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void paintButtonArea (Graphics&, int, int, bool, bool) override
|
||||
{
|
||||
}
|
||||
|
||||
void contentAreaChanged (const Rectangle<int>&) override
|
||||
{
|
||||
}
|
||||
|
||||
int getResizeOrder() const noexcept
|
||||
{
|
||||
return fixedSize <= 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
auto w = getWidth();
|
||||
auto h = getHeight();
|
||||
|
||||
if (drawBar)
|
||||
{
|
||||
g.setColour (findColour (Toolbar::separatorColourId, true));
|
||||
|
||||
auto thickness = 0.2f;
|
||||
|
||||
if (isToolbarVertical())
|
||||
g.fillRect (w * 0.1f, h * (0.5f - thickness * 0.5f), w * 0.8f, h * thickness);
|
||||
else
|
||||
g.fillRect (w * (0.5f - thickness * 0.5f), h * 0.1f, w * thickness, h * 0.8f);
|
||||
}
|
||||
|
||||
if (getEditingMode() != normalMode && ! drawBar)
|
||||
{
|
||||
g.setColour (findColour (Toolbar::separatorColourId, true));
|
||||
|
||||
auto indentX = jmin (2, (w - 3) / 2);
|
||||
auto indentY = jmin (2, (h - 3) / 2);
|
||||
g.drawRect (indentX, indentY, w - indentX * 2, h - indentY * 2, 1);
|
||||
|
||||
if (fixedSize <= 0)
|
||||
{
|
||||
float x1, y1, x2, y2, x3, y3, x4, y4, hw, hl;
|
||||
|
||||
if (isToolbarVertical())
|
||||
{
|
||||
x1 = w * 0.5f;
|
||||
y1 = h * 0.4f;
|
||||
x2 = x1;
|
||||
y2 = indentX * 2.0f;
|
||||
|
||||
x3 = x1;
|
||||
y3 = h * 0.6f;
|
||||
x4 = x1;
|
||||
y4 = h - y2;
|
||||
|
||||
hw = w * 0.15f;
|
||||
hl = w * 0.2f;
|
||||
}
|
||||
else
|
||||
{
|
||||
x1 = w * 0.4f;
|
||||
y1 = h * 0.5f;
|
||||
x2 = indentX * 2.0f;
|
||||
y2 = y1;
|
||||
|
||||
x3 = w * 0.6f;
|
||||
y3 = y1;
|
||||
x4 = w - x2;
|
||||
y4 = y1;
|
||||
|
||||
hw = h * 0.15f;
|
||||
hl = h * 0.2f;
|
||||
}
|
||||
|
||||
Path p;
|
||||
p.addArrow ({ x1, y1, x2, y2 }, 1.5f, hw, hl);
|
||||
p.addArrow ({ x3, y3, x4, y4 }, 1.5f, hw, hl);
|
||||
g.fillPath (p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const float fixedSize;
|
||||
const bool drawBar;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Spacer)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class Toolbar::MissingItemsComponent : public PopupMenu::CustomComponent
|
||||
{
|
||||
public:
|
||||
MissingItemsComponent (Toolbar& bar, int h)
|
||||
: PopupMenu::CustomComponent (true),
|
||||
owner (&bar),
|
||||
height (h)
|
||||
{
|
||||
for (int i = bar.items.size(); --i >= 0;)
|
||||
{
|
||||
auto* tc = bar.items.getUnchecked(i);
|
||||
|
||||
if (dynamic_cast<Spacer*> (tc) == nullptr && ! tc->isVisible())
|
||||
{
|
||||
oldIndexes.insert (0, i);
|
||||
addAndMakeVisible (tc, 0);
|
||||
}
|
||||
}
|
||||
|
||||
layout (400);
|
||||
}
|
||||
|
||||
~MissingItemsComponent()
|
||||
{
|
||||
if (owner != nullptr)
|
||||
{
|
||||
for (int i = 0; i < getNumChildComponents(); ++i)
|
||||
{
|
||||
if (auto* tc = dynamic_cast<ToolbarItemComponent*> (getChildComponent (i)))
|
||||
{
|
||||
tc->setVisible (false);
|
||||
auto index = oldIndexes.removeAndReturn (i);
|
||||
owner->addChildComponent (tc, index);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
|
||||
owner->resized();
|
||||
}
|
||||
}
|
||||
|
||||
void layout (const int preferredWidth)
|
||||
{
|
||||
const int indent = 8;
|
||||
auto x = indent;
|
||||
auto y = indent;
|
||||
int maxX = 0;
|
||||
|
||||
for (auto* c : getChildren())
|
||||
{
|
||||
if (auto* tc = dynamic_cast<ToolbarItemComponent*> (c))
|
||||
{
|
||||
int preferredSize = 1, minSize = 1, maxSize = 1;
|
||||
|
||||
if (tc->getToolbarItemSizes (height, false, preferredSize, minSize, maxSize))
|
||||
{
|
||||
if (x + preferredSize > preferredWidth && x > indent)
|
||||
{
|
||||
x = indent;
|
||||
y += height;
|
||||
}
|
||||
|
||||
tc->setBounds (x, y, preferredSize, height);
|
||||
|
||||
x += preferredSize;
|
||||
maxX = jmax (maxX, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSize (maxX + 8, y + height + 8);
|
||||
}
|
||||
|
||||
void getIdealSize (int& idealWidth, int& idealHeight) override
|
||||
{
|
||||
idealWidth = getWidth();
|
||||
idealHeight = getHeight();
|
||||
}
|
||||
|
||||
private:
|
||||
Component::SafePointer<Toolbar> owner;
|
||||
const int height;
|
||||
Array<int> oldIndexes;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MissingItemsComponent)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
Toolbar::Toolbar()
|
||||
{
|
||||
missingItemsButton.reset (getLookAndFeel().createToolbarMissingItemsButton (*this));
|
||||
addChildComponent (missingItemsButton.get());
|
||||
|
||||
missingItemsButton->setAlwaysOnTop (true);
|
||||
missingItemsButton->onClick = [this] { showMissingItems(); };
|
||||
}
|
||||
|
||||
Toolbar::~Toolbar()
|
||||
{
|
||||
items.clear();
|
||||
}
|
||||
|
||||
void Toolbar::setVertical (const bool shouldBeVertical)
|
||||
{
|
||||
if (vertical != shouldBeVertical)
|
||||
{
|
||||
vertical = shouldBeVertical;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void Toolbar::clear()
|
||||
{
|
||||
items.clear();
|
||||
resized();
|
||||
}
|
||||
|
||||
ToolbarItemComponent* Toolbar::createItem (ToolbarItemFactory& factory, const int itemId)
|
||||
{
|
||||
if (itemId == ToolbarItemFactory::separatorBarId) return new Spacer (itemId, 0.1f, true);
|
||||
if (itemId == ToolbarItemFactory::spacerId) return new Spacer (itemId, 0.5f, false);
|
||||
if (itemId == ToolbarItemFactory::flexibleSpacerId) return new Spacer (itemId, 0.0f, false);
|
||||
|
||||
return factory.createItem (itemId);
|
||||
}
|
||||
|
||||
void Toolbar::addItemInternal (ToolbarItemFactory& factory,
|
||||
const int itemId,
|
||||
const int insertIndex)
|
||||
{
|
||||
// An ID can't be zero - this might indicate a mistake somewhere?
|
||||
jassert (itemId != 0);
|
||||
|
||||
if (auto* tc = createItem (factory, itemId))
|
||||
{
|
||||
#if JUCE_DEBUG
|
||||
Array<int> allowedIds;
|
||||
factory.getAllToolbarItemIds (allowedIds);
|
||||
|
||||
// If your factory can create an item for a given ID, it must also return
|
||||
// that ID from its getAllToolbarItemIds() method!
|
||||
jassert (allowedIds.contains (itemId));
|
||||
#endif
|
||||
|
||||
items.insert (insertIndex, tc);
|
||||
addAndMakeVisible (tc, insertIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void Toolbar::addItem (ToolbarItemFactory& factory, int itemId, int insertIndex)
|
||||
{
|
||||
addItemInternal (factory, itemId, insertIndex);
|
||||
resized();
|
||||
}
|
||||
|
||||
void Toolbar::addDefaultItems (ToolbarItemFactory& factoryToUse)
|
||||
{
|
||||
Array<int> ids;
|
||||
factoryToUse.getDefaultItemSet (ids);
|
||||
|
||||
clear();
|
||||
|
||||
for (auto i : ids)
|
||||
addItemInternal (factoryToUse, i, -1);
|
||||
|
||||
resized();
|
||||
}
|
||||
|
||||
void Toolbar::removeToolbarItem (const int itemIndex)
|
||||
{
|
||||
items.remove (itemIndex);
|
||||
resized();
|
||||
}
|
||||
|
||||
ToolbarItemComponent* Toolbar::removeAndReturnItem (const int itemIndex)
|
||||
{
|
||||
if (auto* tc = items.removeAndReturn (itemIndex))
|
||||
{
|
||||
removeChildComponent (tc);
|
||||
resized();
|
||||
return tc;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Toolbar::getNumItems() const noexcept
|
||||
{
|
||||
return items.size();
|
||||
}
|
||||
|
||||
int Toolbar::getItemId (const int itemIndex) const noexcept
|
||||
{
|
||||
if (auto* tc = getItemComponent (itemIndex))
|
||||
return tc->getItemId();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ToolbarItemComponent* Toolbar::getItemComponent (const int itemIndex) const noexcept
|
||||
{
|
||||
return items[itemIndex];
|
||||
}
|
||||
|
||||
ToolbarItemComponent* Toolbar::getNextActiveComponent (int index, const int delta) const
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
index += delta;
|
||||
|
||||
if (auto* tc = getItemComponent (index))
|
||||
{
|
||||
if (tc->isActive)
|
||||
return tc;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Toolbar::setStyle (const ToolbarItemStyle& newStyle)
|
||||
{
|
||||
if (toolbarStyle != newStyle)
|
||||
{
|
||||
toolbarStyle = newStyle;
|
||||
updateAllItemPositions (false);
|
||||
}
|
||||
}
|
||||
|
||||
String Toolbar::toString() const
|
||||
{
|
||||
String s ("TB:");
|
||||
|
||||
for (int i = 0; i < getNumItems(); ++i)
|
||||
s << getItemId(i) << ' ';
|
||||
|
||||
return s.trimEnd();
|
||||
}
|
||||
|
||||
bool Toolbar::restoreFromString (ToolbarItemFactory& factoryToUse,
|
||||
const String& savedVersion)
|
||||
{
|
||||
if (! savedVersion.startsWith ("TB:"))
|
||||
return false;
|
||||
|
||||
StringArray tokens;
|
||||
tokens.addTokens (savedVersion.substring (3), false);
|
||||
|
||||
clear();
|
||||
|
||||
for (auto& t : tokens)
|
||||
addItemInternal (factoryToUse, t.getIntValue(), -1);
|
||||
|
||||
resized();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Toolbar::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().paintToolbarBackground (g, getWidth(), getHeight(), *this);
|
||||
}
|
||||
|
||||
int Toolbar::getThickness() const noexcept
|
||||
{
|
||||
return vertical ? getWidth() : getHeight();
|
||||
}
|
||||
|
||||
int Toolbar::getLength() const noexcept
|
||||
{
|
||||
return vertical ? getHeight() : getWidth();
|
||||
}
|
||||
|
||||
void Toolbar::setEditingActive (const bool active)
|
||||
{
|
||||
if (isEditingActive != active)
|
||||
{
|
||||
isEditingActive = active;
|
||||
updateAllItemPositions (false);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Toolbar::resized()
|
||||
{
|
||||
updateAllItemPositions (false);
|
||||
}
|
||||
|
||||
void Toolbar::updateAllItemPositions (bool animate)
|
||||
{
|
||||
if (getWidth() > 0 && getHeight() > 0)
|
||||
{
|
||||
StretchableObjectResizer resizer;
|
||||
|
||||
for (auto* tc : items)
|
||||
{
|
||||
tc->setEditingMode (isEditingActive ? ToolbarItemComponent::editableOnToolbar
|
||||
: ToolbarItemComponent::normalMode);
|
||||
|
||||
tc->setStyle (toolbarStyle);
|
||||
|
||||
auto* spacer = dynamic_cast<Spacer*> (tc);
|
||||
|
||||
int preferredSize = 1, minSize = 1, maxSize = 1;
|
||||
|
||||
if (tc->getToolbarItemSizes (getThickness(), isVertical(),
|
||||
preferredSize, minSize, maxSize))
|
||||
{
|
||||
tc->isActive = true;
|
||||
resizer.addItem (preferredSize, minSize, maxSize,
|
||||
spacer != nullptr ? spacer->getResizeOrder() : 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
tc->isActive = false;
|
||||
tc->setVisible (false);
|
||||
}
|
||||
}
|
||||
|
||||
resizer.resizeToFit (getLength());
|
||||
|
||||
int totalLength = 0;
|
||||
|
||||
for (int i = 0; i < resizer.getNumItems(); ++i)
|
||||
totalLength += (int) resizer.getItemSize (i);
|
||||
|
||||
const bool itemsOffTheEnd = totalLength > getLength();
|
||||
|
||||
auto extrasButtonSize = getThickness() / 2;
|
||||
missingItemsButton->setSize (extrasButtonSize, extrasButtonSize);
|
||||
missingItemsButton->setVisible (itemsOffTheEnd);
|
||||
missingItemsButton->setEnabled (! isEditingActive);
|
||||
|
||||
if (vertical)
|
||||
missingItemsButton->setCentrePosition (getWidth() / 2,
|
||||
getHeight() - 4 - extrasButtonSize / 2);
|
||||
else
|
||||
missingItemsButton->setCentrePosition (getWidth() - 4 - extrasButtonSize / 2,
|
||||
getHeight() / 2);
|
||||
|
||||
auto maxLength = itemsOffTheEnd ? (vertical ? missingItemsButton->getY()
|
||||
: missingItemsButton->getX()) - 4
|
||||
: getLength();
|
||||
|
||||
int pos = 0, activeIndex = 0;
|
||||
|
||||
for (auto* tc : items)
|
||||
{
|
||||
if (tc->isActive)
|
||||
{
|
||||
auto size = (int) resizer.getItemSize (activeIndex++);
|
||||
|
||||
Rectangle<int> newBounds;
|
||||
|
||||
if (vertical)
|
||||
newBounds.setBounds (0, pos, getWidth(), size);
|
||||
else
|
||||
newBounds.setBounds (pos, 0, size, getHeight());
|
||||
|
||||
auto& animator = Desktop::getInstance().getAnimator();
|
||||
|
||||
if (animate)
|
||||
{
|
||||
animator.animateComponent (tc, newBounds, 1.0f, 200, false, 3.0, 0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
animator.cancelAnimation (tc, false);
|
||||
tc->setBounds (newBounds);
|
||||
}
|
||||
|
||||
pos += size;
|
||||
tc->setVisible (pos <= maxLength
|
||||
&& ((! tc->isBeingDragged)
|
||||
|| tc->getEditingMode() == ToolbarItemComponent::editableOnPalette));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Toolbar::showMissingItems()
|
||||
{
|
||||
jassert (missingItemsButton->isShowing());
|
||||
|
||||
if (missingItemsButton->isShowing())
|
||||
{
|
||||
PopupMenu m;
|
||||
m.addCustomItem (1, new MissingItemsComponent (*this, getThickness()));
|
||||
m.showMenuAsync (PopupMenu::Options().withTargetComponent (missingItemsButton.get()), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool Toolbar::isInterestedInDragSource (const SourceDetails& dragSourceDetails)
|
||||
{
|
||||
return dragSourceDetails.description == toolbarDragDescriptor && isEditingActive;
|
||||
}
|
||||
|
||||
void Toolbar::itemDragMove (const SourceDetails& dragSourceDetails)
|
||||
{
|
||||
if (auto* tc = dynamic_cast<ToolbarItemComponent*> (dragSourceDetails.sourceComponent.get()))
|
||||
{
|
||||
if (! items.contains (tc))
|
||||
{
|
||||
if (tc->getEditingMode() == ToolbarItemComponent::editableOnPalette)
|
||||
{
|
||||
if (auto* palette = tc->findParentComponentOfClass<ToolbarItemPalette>())
|
||||
palette->replaceComponent (*tc);
|
||||
}
|
||||
else
|
||||
{
|
||||
jassert (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar);
|
||||
}
|
||||
|
||||
items.add (tc);
|
||||
addChildComponent (tc);
|
||||
updateAllItemPositions (true);
|
||||
}
|
||||
|
||||
auto& animator = Desktop::getInstance().getAnimator();
|
||||
|
||||
for (int i = getNumItems(); --i >= 0;)
|
||||
{
|
||||
auto currentIndex = items.indexOf (tc);
|
||||
auto newIndex = currentIndex;
|
||||
|
||||
auto dragObjectLeft = vertical ? (dragSourceDetails.localPosition.getY() - tc->dragOffsetY)
|
||||
: (dragSourceDetails.localPosition.getX() - tc->dragOffsetX);
|
||||
auto dragObjectRight = dragObjectLeft + (vertical ? tc->getHeight() : tc->getWidth());
|
||||
|
||||
auto current = animator.getComponentDestination (getChildComponent (newIndex));
|
||||
|
||||
if (auto* prev = getNextActiveComponent (newIndex, -1))
|
||||
{
|
||||
auto previousPos = animator.getComponentDestination (prev);
|
||||
|
||||
if (std::abs (dragObjectLeft - (vertical ? previousPos.getY() : previousPos.getX()))
|
||||
< std::abs (dragObjectRight - (vertical ? current.getBottom() : current.getRight())))
|
||||
{
|
||||
newIndex = getIndexOfChildComponent (prev);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* next = getNextActiveComponent (newIndex, 1))
|
||||
{
|
||||
auto nextPos = animator.getComponentDestination (next);
|
||||
|
||||
if (std::abs (dragObjectLeft - (vertical ? current.getY() : current.getX()))
|
||||
> std::abs (dragObjectRight - (vertical ? nextPos.getBottom() : nextPos.getRight())))
|
||||
{
|
||||
newIndex = getIndexOfChildComponent (next) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (newIndex == currentIndex)
|
||||
break;
|
||||
|
||||
items.removeObject (tc, false);
|
||||
removeChildComponent (tc);
|
||||
addChildComponent (tc, newIndex);
|
||||
items.insert (newIndex, tc);
|
||||
updateAllItemPositions (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Toolbar::itemDragExit (const SourceDetails& dragSourceDetails)
|
||||
{
|
||||
if (auto* tc = dynamic_cast<ToolbarItemComponent*> (dragSourceDetails.sourceComponent.get()))
|
||||
{
|
||||
if (isParentOf (tc))
|
||||
{
|
||||
items.removeObject (tc, false);
|
||||
removeChildComponent (tc);
|
||||
updateAllItemPositions (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Toolbar::itemDropped (const SourceDetails& dragSourceDetails)
|
||||
{
|
||||
if (auto* tc = dynamic_cast<ToolbarItemComponent*> (dragSourceDetails.sourceComponent.get()))
|
||||
tc->setState (Button::buttonNormal);
|
||||
}
|
||||
|
||||
void Toolbar::mouseDown (const MouseEvent&) {}
|
||||
|
||||
//==============================================================================
|
||||
class Toolbar::CustomisationDialog : public DialogWindow
|
||||
{
|
||||
public:
|
||||
CustomisationDialog (ToolbarItemFactory& factory, Toolbar& bar, int optionFlags)
|
||||
: DialogWindow (TRANS("Add/remove items from toolbar"), Colours::white, true, true),
|
||||
toolbar (bar)
|
||||
{
|
||||
setContentOwned (new CustomiserPanel (factory, toolbar, optionFlags), true);
|
||||
setResizable (true, true);
|
||||
setResizeLimits (400, 300, 1500, 1000);
|
||||
positionNearBar();
|
||||
}
|
||||
|
||||
~CustomisationDialog()
|
||||
{
|
||||
toolbar.setEditingActive (false);
|
||||
}
|
||||
|
||||
void closeButtonPressed() override
|
||||
{
|
||||
setVisible (false);
|
||||
}
|
||||
|
||||
bool canModalEventBeSentToComponent (const Component* comp) override
|
||||
{
|
||||
return toolbar.isParentOf (comp)
|
||||
|| dynamic_cast<const ToolbarItemComponent::ItemDragAndDropOverlayComponent*> (comp) != nullptr;
|
||||
}
|
||||
|
||||
void positionNearBar()
|
||||
{
|
||||
auto screenSize = toolbar.getParentMonitorArea();
|
||||
auto pos = toolbar.getScreenPosition();
|
||||
const int gap = 8;
|
||||
|
||||
if (toolbar.isVertical())
|
||||
{
|
||||
if (pos.x > screenSize.getCentreX())
|
||||
pos.x -= getWidth() - gap;
|
||||
else
|
||||
pos.x += toolbar.getWidth() + gap;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos.x += (toolbar.getWidth() - getWidth()) / 2;
|
||||
|
||||
if (pos.y > screenSize.getCentreY())
|
||||
pos.y -= getHeight() - gap;
|
||||
else
|
||||
pos.y += toolbar.getHeight() + gap;
|
||||
}
|
||||
|
||||
setTopLeftPosition (pos);
|
||||
}
|
||||
|
||||
private:
|
||||
Toolbar& toolbar;
|
||||
|
||||
class CustomiserPanel : public Component
|
||||
{
|
||||
public:
|
||||
CustomiserPanel (ToolbarItemFactory& tbf, Toolbar& bar, int optionFlags)
|
||||
: factory (tbf), toolbar (bar), palette (tbf, bar),
|
||||
instructions ({}, TRANS ("You can drag the items above and drop them onto a toolbar to add them.")
|
||||
+ "\n\n"
|
||||
+ TRANS ("Items on the toolbar can also be dragged around to change their order, or dragged off the edge to delete them.")),
|
||||
defaultButton (TRANS ("Restore to default set of items"))
|
||||
{
|
||||
addAndMakeVisible (palette);
|
||||
|
||||
if ((optionFlags & (Toolbar::allowIconsOnlyChoice
|
||||
| Toolbar::allowIconsWithTextChoice
|
||||
| Toolbar::allowTextOnlyChoice)) != 0)
|
||||
{
|
||||
addAndMakeVisible (styleBox);
|
||||
styleBox.setEditableText (false);
|
||||
|
||||
if ((optionFlags & Toolbar::allowIconsOnlyChoice) != 0) styleBox.addItem (TRANS("Show icons only"), 1);
|
||||
if ((optionFlags & Toolbar::allowIconsWithTextChoice) != 0) styleBox.addItem (TRANS("Show icons and descriptions"), 2);
|
||||
if ((optionFlags & Toolbar::allowTextOnlyChoice) != 0) styleBox.addItem (TRANS("Show descriptions only"), 3);
|
||||
|
||||
int selectedStyle = 0;
|
||||
switch (bar.getStyle())
|
||||
{
|
||||
case Toolbar::iconsOnly: selectedStyle = 1; break;
|
||||
case Toolbar::iconsWithText: selectedStyle = 2; break;
|
||||
case Toolbar::textOnly: selectedStyle = 3; break;
|
||||
}
|
||||
|
||||
styleBox.setSelectedId (selectedStyle);
|
||||
|
||||
styleBox.onChange = [this] { updateStyle(); };
|
||||
}
|
||||
|
||||
if ((optionFlags & Toolbar::showResetToDefaultsButton) != 0)
|
||||
{
|
||||
addAndMakeVisible (defaultButton);
|
||||
defaultButton.onClick = [this] { toolbar.addDefaultItems (factory); };
|
||||
}
|
||||
|
||||
addAndMakeVisible (instructions);
|
||||
instructions.setFont (Font (13.0f));
|
||||
|
||||
setSize (500, 300);
|
||||
}
|
||||
|
||||
void updateStyle()
|
||||
{
|
||||
switch (styleBox.getSelectedId())
|
||||
{
|
||||
case 1: toolbar.setStyle (Toolbar::iconsOnly); break;
|
||||
case 2: toolbar.setStyle (Toolbar::iconsWithText); break;
|
||||
case 3: toolbar.setStyle (Toolbar::textOnly); break;
|
||||
}
|
||||
|
||||
palette.resized(); // to make it update the styles
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
Colour background;
|
||||
|
||||
if (auto* dw = findParentComponentOfClass<DialogWindow>())
|
||||
background = dw->getBackgroundColour();
|
||||
|
||||
g.setColour (background.contrasting().withAlpha (0.3f));
|
||||
g.fillRect (palette.getX(), palette.getBottom() - 1, palette.getWidth(), 1);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
palette.setBounds (0, 0, getWidth(), getHeight() - 120);
|
||||
styleBox.setBounds (10, getHeight() - 110, 200, 22);
|
||||
|
||||
defaultButton.changeWidthToFitText (22);
|
||||
defaultButton.setTopLeftPosition (240, getHeight() - 110);
|
||||
|
||||
instructions.setBounds (10, getHeight() - 80, getWidth() - 20, 80);
|
||||
}
|
||||
|
||||
private:
|
||||
ToolbarItemFactory& factory;
|
||||
Toolbar& toolbar;
|
||||
|
||||
ToolbarItemPalette palette;
|
||||
Label instructions;
|
||||
ComboBox styleBox;
|
||||
TextButton defaultButton;
|
||||
};
|
||||
};
|
||||
|
||||
void Toolbar::showCustomisationDialog (ToolbarItemFactory& factory, const int optionFlags)
|
||||
{
|
||||
setEditingActive (true);
|
||||
|
||||
(new CustomisationDialog (factory, *this, optionFlags))
|
||||
->enterModalState (true, nullptr, true);
|
||||
}
|
||||
|
||||
} // namespace juce
|
332
modules/juce_gui_basics/widgets/juce_Toolbar.h
Normal file
332
modules/juce_gui_basics/widgets/juce_Toolbar.h
Normal file
@ -0,0 +1,332 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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 ToolbarItemComponent;
|
||||
class ToolbarItemFactory;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A toolbar component.
|
||||
|
||||
A toolbar contains a horizontal or vertical strip of ToolbarItemComponents,
|
||||
and looks after their order and layout.
|
||||
|
||||
Items (icon buttons or other custom components) are added to a toolbar using a
|
||||
ToolbarItemFactory - each type of item is given a unique ID number, and a
|
||||
toolbar might contain more than one instance of a particular item type.
|
||||
|
||||
Toolbars can be interactively customised, allowing the user to drag the items
|
||||
around, and to drag items onto or off the toolbar, using the ToolbarItemPalette
|
||||
component as a source of new items.
|
||||
|
||||
@see ToolbarItemFactory, ToolbarItemComponent, ToolbarItemPalette
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API Toolbar : public Component,
|
||||
public DragAndDropContainer,
|
||||
public DragAndDropTarget
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty toolbar component.
|
||||
|
||||
To add some icons or other components to your toolbar, you'll need to
|
||||
create a ToolbarItemFactory class that can create a suitable set of
|
||||
ToolbarItemComponents.
|
||||
|
||||
@see ToolbarItemFactory, ToolbarItemComponents
|
||||
*/
|
||||
Toolbar();
|
||||
|
||||
/** Destructor.
|
||||
|
||||
Any items on the bar will be deleted when the toolbar is deleted.
|
||||
*/
|
||||
~Toolbar();
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the bar's orientation.
|
||||
@see isVertical
|
||||
*/
|
||||
void setVertical (bool shouldBeVertical);
|
||||
|
||||
/** Returns true if the bar is set to be vertical, or false if it's horizontal.
|
||||
|
||||
You can change the bar's orientation with setVertical().
|
||||
*/
|
||||
bool isVertical() const noexcept { return vertical; }
|
||||
|
||||
/** Returns the depth of the bar.
|
||||
|
||||
If the bar is horizontal, this will return its height; if it's vertical, it
|
||||
will return its width.
|
||||
|
||||
@see getLength
|
||||
*/
|
||||
int getThickness() const noexcept;
|
||||
|
||||
/** Returns the length of the bar.
|
||||
|
||||
If the bar is horizontal, this will return its width; if it's vertical, it
|
||||
will return its height.
|
||||
|
||||
@see getThickness
|
||||
*/
|
||||
int getLength() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Deletes all items from the bar.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/** Adds an item to the toolbar.
|
||||
|
||||
The factory's ToolbarItemFactory::createItem() will be called by this method
|
||||
to create the component that will actually be added to the bar.
|
||||
|
||||
The new item will be inserted at the specified index (if the index is -1, it
|
||||
will be added to the right-hand or bottom end of the bar).
|
||||
|
||||
Once added, the component will be automatically deleted by this object when it
|
||||
is no longer needed.
|
||||
|
||||
@see ToolbarItemFactory
|
||||
*/
|
||||
void addItem (ToolbarItemFactory& factory,
|
||||
int itemId,
|
||||
int insertIndex = -1);
|
||||
|
||||
/** Deletes one of the items from the bar. */
|
||||
void removeToolbarItem (int itemIndex);
|
||||
|
||||
/** Removes an item from the bar and returns it. */
|
||||
ToolbarItemComponent* removeAndReturnItem (int itemIndex);
|
||||
|
||||
/** Returns the number of items currently on the toolbar.
|
||||
|
||||
@see getItemId, getItemComponent
|
||||
*/
|
||||
int getNumItems() const noexcept;
|
||||
|
||||
/** Returns the ID of the item with the given index.
|
||||
|
||||
If the index is less than zero or greater than the number of items,
|
||||
this will return nullptr.
|
||||
|
||||
@see getNumItems
|
||||
*/
|
||||
int getItemId (int itemIndex) const noexcept;
|
||||
|
||||
/** Returns the component being used for the item with the given index.
|
||||
|
||||
If the index is less than zero or greater than the number of items,
|
||||
this will return nullptr.
|
||||
|
||||
@see getNumItems
|
||||
*/
|
||||
ToolbarItemComponent* getItemComponent (int itemIndex) const noexcept;
|
||||
|
||||
/** Clears this toolbar and adds to it the default set of items that the specified
|
||||
factory creates.
|
||||
|
||||
@see ToolbarItemFactory::getDefaultItemSet
|
||||
*/
|
||||
void addDefaultItems (ToolbarItemFactory& factoryToUse);
|
||||
|
||||
//==============================================================================
|
||||
/** Options for the way items should be displayed.
|
||||
@see setStyle, getStyle
|
||||
*/
|
||||
enum ToolbarItemStyle
|
||||
{
|
||||
iconsOnly, /**< Means that the toolbar should just contain icons. */
|
||||
iconsWithText, /**< Means that the toolbar should have text labels under each icon. */
|
||||
textOnly /**< Means that the toolbar only display text labels for each item. */
|
||||
};
|
||||
|
||||
/** Returns the toolbar's current style.
|
||||
@see ToolbarItemStyle, setStyle
|
||||
*/
|
||||
ToolbarItemStyle getStyle() const noexcept { return toolbarStyle; }
|
||||
|
||||
/** Changes the toolbar's current style.
|
||||
@see ToolbarItemStyle, getStyle, ToolbarItemComponent::setStyle
|
||||
*/
|
||||
void setStyle (const ToolbarItemStyle& newStyle);
|
||||
|
||||
//==============================================================================
|
||||
/** Flags used by the showCustomisationDialog() method. */
|
||||
enum CustomisationFlags
|
||||
{
|
||||
allowIconsOnlyChoice = 1, /**< If this flag is specified, the customisation dialog can
|
||||
show the "icons only" option on its choice of toolbar styles. */
|
||||
allowIconsWithTextChoice = 2, /**< If this flag is specified, the customisation dialog can
|
||||
show the "icons with text" option on its choice of toolbar styles. */
|
||||
allowTextOnlyChoice = 4, /**< If this flag is specified, the customisation dialog can
|
||||
show the "text only" option on its choice of toolbar styles. */
|
||||
showResetToDefaultsButton = 8, /**< If this flag is specified, the customisation dialog can
|
||||
show a button to reset the toolbar to its default set of items. */
|
||||
|
||||
allCustomisationOptionsEnabled = (allowIconsOnlyChoice | allowIconsWithTextChoice | allowTextOnlyChoice | showResetToDefaultsButton)
|
||||
};
|
||||
|
||||
/** Pops up a modal dialog box that allows this toolbar to be customised by the user.
|
||||
|
||||
The dialog contains a ToolbarItemPalette and various controls for editing other
|
||||
aspects of the toolbar. The dialog box will be opened modally, but the method will
|
||||
return immediately.
|
||||
|
||||
The factory is used to determine the set of items that will be shown on the
|
||||
palette.
|
||||
|
||||
The optionFlags parameter is a bitwise-or of values from the CustomisationFlags
|
||||
enum.
|
||||
|
||||
@see ToolbarItemPalette
|
||||
*/
|
||||
void showCustomisationDialog (ToolbarItemFactory& factory,
|
||||
int optionFlags = allCustomisationOptionsEnabled);
|
||||
|
||||
/** Turns on or off the toolbar's editing mode, in which its items can be
|
||||
rearranged by the user.
|
||||
|
||||
(In most cases it's easier just to use showCustomisationDialog() instead of
|
||||
trying to enable editing directly).
|
||||
|
||||
@see ToolbarItemPalette
|
||||
*/
|
||||
void setEditingActive (bool editingEnabled);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the toolbar.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1003200, /**< A colour to use to fill the toolbar's background. For
|
||||
more control over this, override LookAndFeel::paintToolbarBackground(). */
|
||||
separatorColourId = 0x1003210, /**< A colour to use to draw the separator lines. */
|
||||
|
||||
buttonMouseOverBackgroundColourId = 0x1003220, /**< A colour used to paint the background of buttons when the mouse is
|
||||
over them. */
|
||||
buttonMouseDownBackgroundColourId = 0x1003230, /**< A colour used to paint the background of buttons when the mouse is
|
||||
held down on them. */
|
||||
|
||||
labelTextColourId = 0x1003240, /**< A colour to use for drawing the text under buttons
|
||||
when the style is set to iconsWithText or textOnly. */
|
||||
|
||||
editingModeOutlineColourId = 0x1003250 /**< A colour to use for an outline around buttons when
|
||||
the customisation dialog is active and the mouse moves over them. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a string that represents the toolbar's current set of items.
|
||||
|
||||
This lets you later restore the same item layout using restoreFromString().
|
||||
|
||||
@see restoreFromString
|
||||
*/
|
||||
String toString() const;
|
||||
|
||||
/** Restores a set of items that was previously stored in a string by the toString()
|
||||
method.
|
||||
|
||||
The factory object is used to create any item components that are needed.
|
||||
|
||||
@see toString
|
||||
*/
|
||||
bool restoreFromString (ToolbarItemFactory& factoryToUse,
|
||||
const String& savedVersion);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void paintToolbarBackground (Graphics&, int width, int height, Toolbar&) = 0;
|
||||
|
||||
virtual Button* createToolbarMissingItemsButton (Toolbar&) = 0;
|
||||
|
||||
virtual void paintToolbarButtonBackground (Graphics&, int width, int height,
|
||||
bool isMouseOver, bool isMouseDown,
|
||||
ToolbarItemComponent&) = 0;
|
||||
|
||||
virtual void paintToolbarButtonLabel (Graphics&, int x, int y, int width, int height,
|
||||
const String& text, ToolbarItemComponent&) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
bool isInterestedInDragSource (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDragMove (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDragExit (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDropped (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void updateAllItemPositions (bool animate);
|
||||
/** @internal */
|
||||
static ToolbarItemComponent* createItem (ToolbarItemFactory&, int itemId);
|
||||
/** @internal */
|
||||
static const char* const toolbarDragDescriptor;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::unique_ptr<Button> missingItemsButton;
|
||||
bool vertical = false, isEditingActive = false;
|
||||
ToolbarItemStyle toolbarStyle = iconsOnly;
|
||||
class MissingItemsComponent;
|
||||
friend class MissingItemsComponent;
|
||||
OwnedArray<ToolbarItemComponent> items;
|
||||
class Spacer;
|
||||
class CustomisationDialog;
|
||||
|
||||
void showMissingItems();
|
||||
void addItemInternal (ToolbarItemFactory& factory, int itemId, int insertIndex);
|
||||
|
||||
ToolbarItemComponent* getNextActiveComponent (int index, int delta) const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Toolbar)
|
||||
};
|
||||
|
||||
} // namespace juce
|
243
modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp
Normal file
243
modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp
Normal file
@ -0,0 +1,243 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
ToolbarItemFactory::ToolbarItemFactory() {}
|
||||
ToolbarItemFactory::~ToolbarItemFactory() {}
|
||||
|
||||
//==============================================================================
|
||||
class ToolbarItemComponent::ItemDragAndDropOverlayComponent : public Component
|
||||
{
|
||||
public:
|
||||
ItemDragAndDropOverlayComponent()
|
||||
: isDragging (false)
|
||||
{
|
||||
setAlwaysOnTop (true);
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setMouseCursor (MouseCursor::DraggingHandCursor);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
|
||||
{
|
||||
if (isMouseOverOrDragging()
|
||||
&& tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar)
|
||||
{
|
||||
g.setColour (findColour (Toolbar::editingModeOutlineColourId, true));
|
||||
g.drawRect (getLocalBounds(), jmin (2, (getWidth() - 1) / 2,
|
||||
(getHeight() - 1) / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
isDragging = false;
|
||||
|
||||
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
|
||||
{
|
||||
tc->dragOffsetX = e.x;
|
||||
tc->dragOffsetY = e.y;
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
if (e.mouseWasDraggedSinceMouseDown() && ! isDragging)
|
||||
{
|
||||
isDragging = true;
|
||||
|
||||
if (DragAndDropContainer* const dnd = DragAndDropContainer::findParentDragContainerFor (this))
|
||||
{
|
||||
dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), Image(), true, nullptr, &e.source);
|
||||
|
||||
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
|
||||
{
|
||||
tc->isBeingDragged = true;
|
||||
|
||||
if (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar)
|
||||
tc->setVisible (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mouseUp (const MouseEvent&) override
|
||||
{
|
||||
isDragging = false;
|
||||
|
||||
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
|
||||
{
|
||||
tc->isBeingDragged = false;
|
||||
|
||||
if (Toolbar* const tb = tc->getToolbar())
|
||||
tb->updateAllItemPositions (true);
|
||||
else if (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar)
|
||||
delete tc;
|
||||
}
|
||||
}
|
||||
|
||||
void parentSizeChanged() override
|
||||
{
|
||||
setBounds (0, 0, getParentWidth(), getParentHeight());
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
bool isDragging;
|
||||
|
||||
ToolbarItemComponent* getToolbarItemComponent() const noexcept
|
||||
{
|
||||
return dynamic_cast<ToolbarItemComponent*> (getParentComponent());
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemDragAndDropOverlayComponent)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
ToolbarItemComponent::ToolbarItemComponent (const int itemId_,
|
||||
const String& labelText,
|
||||
const bool isBeingUsedAsAButton_)
|
||||
: Button (labelText),
|
||||
itemId (itemId_),
|
||||
mode (normalMode),
|
||||
toolbarStyle (Toolbar::iconsOnly),
|
||||
dragOffsetX (0),
|
||||
dragOffsetY (0),
|
||||
isActive (true),
|
||||
isBeingDragged (false),
|
||||
isBeingUsedAsAButton (isBeingUsedAsAButton_)
|
||||
{
|
||||
// Your item ID can't be 0!
|
||||
jassert (itemId_ != 0);
|
||||
}
|
||||
|
||||
ToolbarItemComponent::~ToolbarItemComponent()
|
||||
{
|
||||
overlayComp.reset();
|
||||
}
|
||||
|
||||
Toolbar* ToolbarItemComponent::getToolbar() const
|
||||
{
|
||||
return dynamic_cast<Toolbar*> (getParentComponent());
|
||||
}
|
||||
|
||||
bool ToolbarItemComponent::isToolbarVertical() const
|
||||
{
|
||||
const Toolbar* const t = getToolbar();
|
||||
return t != nullptr && t->isVertical();
|
||||
}
|
||||
|
||||
void ToolbarItemComponent::setStyle (const Toolbar::ToolbarItemStyle& newStyle)
|
||||
{
|
||||
if (toolbarStyle != newStyle)
|
||||
{
|
||||
toolbarStyle = newStyle;
|
||||
repaint();
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void ToolbarItemComponent::paintButton (Graphics& g, const bool over, const bool down)
|
||||
{
|
||||
if (isBeingUsedAsAButton)
|
||||
getLookAndFeel().paintToolbarButtonBackground (g, getWidth(), getHeight(),
|
||||
over, down, *this);
|
||||
|
||||
if (toolbarStyle != Toolbar::iconsOnly)
|
||||
{
|
||||
auto indent = contentArea.getX();
|
||||
auto y = indent;
|
||||
auto h = getHeight() - indent * 2;
|
||||
|
||||
if (toolbarStyle == Toolbar::iconsWithText)
|
||||
{
|
||||
y = contentArea.getBottom() + indent / 2;
|
||||
h -= contentArea.getHeight();
|
||||
}
|
||||
|
||||
getLookAndFeel().paintToolbarButtonLabel (g, indent, y, getWidth() - indent * 2, h,
|
||||
getButtonText(), *this);
|
||||
}
|
||||
|
||||
if (! contentArea.isEmpty())
|
||||
{
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
g.reduceClipRegion (contentArea);
|
||||
g.setOrigin (contentArea.getPosition());
|
||||
|
||||
paintButtonArea (g, contentArea.getWidth(), contentArea.getHeight(), over, down);
|
||||
}
|
||||
}
|
||||
|
||||
void ToolbarItemComponent::resized()
|
||||
{
|
||||
if (toolbarStyle != Toolbar::textOnly)
|
||||
{
|
||||
const int indent = jmin (proportionOfWidth (0.08f),
|
||||
proportionOfHeight (0.08f));
|
||||
|
||||
contentArea = Rectangle<int> (indent, indent,
|
||||
getWidth() - indent * 2,
|
||||
toolbarStyle == Toolbar::iconsWithText ? proportionOfHeight (0.55f)
|
||||
: (getHeight() - indent * 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
contentArea = {};
|
||||
}
|
||||
|
||||
contentAreaChanged (contentArea);
|
||||
}
|
||||
|
||||
void ToolbarItemComponent::setEditingMode (const ToolbarEditingMode newMode)
|
||||
{
|
||||
if (mode != newMode)
|
||||
{
|
||||
mode = newMode;
|
||||
repaint();
|
||||
|
||||
if (mode == normalMode)
|
||||
{
|
||||
overlayComp.reset();
|
||||
}
|
||||
else if (overlayComp == nullptr)
|
||||
{
|
||||
overlayComp.reset (new ItemDragAndDropOverlayComponent());
|
||||
addAndMakeVisible (overlayComp.get());
|
||||
overlayComp->parentSizeChanged();
|
||||
}
|
||||
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
207
modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.h
Normal file
207
modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.h
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that can be used as one of the items in a Toolbar.
|
||||
|
||||
Each of the items on a toolbar must be a component derived from ToolbarItemComponent,
|
||||
and these objects are always created by a ToolbarItemFactory - see the ToolbarItemFactory
|
||||
class for further info about creating them.
|
||||
|
||||
The ToolbarItemComponent class is actually a button, but can be used to hold non-button
|
||||
components too. To do this, set the value of isBeingUsedAsAButton to false when
|
||||
calling the constructor, and override contentAreaChanged(), in which you can position
|
||||
any sub-components you need to add.
|
||||
|
||||
To add basic buttons without writing a special subclass, have a look at the
|
||||
ToolbarButton class.
|
||||
|
||||
@see ToolbarButton, Toolbar, ToolbarItemFactory
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ToolbarItemComponent : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Constructor.
|
||||
|
||||
@param itemId the ID of the type of toolbar item which this represents
|
||||
@param labelText the text to display if the toolbar's style is set to
|
||||
Toolbar::iconsWithText or Toolbar::textOnly
|
||||
@param isBeingUsedAsAButton set this to false if you don't want the button
|
||||
to draw itself with button over/down states when the mouse
|
||||
moves over it or clicks
|
||||
*/
|
||||
ToolbarItemComponent (int itemId,
|
||||
const String& labelText,
|
||||
bool isBeingUsedAsAButton);
|
||||
|
||||
/** Destructor. */
|
||||
~ToolbarItemComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the item type ID that this component represents.
|
||||
This value is in the constructor.
|
||||
*/
|
||||
int getItemId() const noexcept { return itemId; }
|
||||
|
||||
/** Returns the toolbar that contains this component, or nullptr if it's not currently
|
||||
inside one.
|
||||
*/
|
||||
Toolbar* getToolbar() const;
|
||||
|
||||
/** Returns true if this component is currently inside a toolbar which is vertical.
|
||||
@see Toolbar::isVertical
|
||||
*/
|
||||
bool isToolbarVertical() const;
|
||||
|
||||
/** Returns the current style setting of this item.
|
||||
|
||||
Styles are listed in the Toolbar::ToolbarItemStyle enum.
|
||||
@see setStyle, Toolbar::getStyle
|
||||
*/
|
||||
Toolbar::ToolbarItemStyle getStyle() const noexcept { return toolbarStyle; }
|
||||
|
||||
/** Changes the current style setting of this item.
|
||||
|
||||
Styles are listed in the Toolbar::ToolbarItemStyle enum, and are automatically updated
|
||||
by the toolbar that holds this item.
|
||||
|
||||
@see setStyle, Toolbar::setStyle
|
||||
*/
|
||||
virtual void setStyle (const Toolbar::ToolbarItemStyle& newStyle);
|
||||
|
||||
/** Returns the area of the component that should be used to display the button image or
|
||||
other contents of the item.
|
||||
|
||||
This content area may change when the item's style changes, and may leave a space around the
|
||||
edge of the component where the text label can be shown.
|
||||
|
||||
@see contentAreaChanged
|
||||
*/
|
||||
Rectangle<int> getContentArea() const noexcept { return contentArea; }
|
||||
|
||||
//==============================================================================
|
||||
/** This method must return the size criteria for this item, based on a given toolbar
|
||||
size and orientation.
|
||||
|
||||
The preferredSize, minSize and maxSize values must all be set by your implementation
|
||||
method. If the toolbar is horizontal, these will be the width of the item; for a vertical
|
||||
toolbar, they refer to the item's height.
|
||||
|
||||
The preferredSize is the size that the component would like to be, and this must be
|
||||
between the min and max sizes. For a fixed-size item, simply set all three variables to
|
||||
the same value.
|
||||
|
||||
The toolbarThickness parameter tells you the depth of the toolbar - the same as calling
|
||||
Toolbar::getThickness().
|
||||
|
||||
The isToolbarVertical parameter tells you whether the bar is oriented horizontally or
|
||||
vertically.
|
||||
*/
|
||||
virtual bool getToolbarItemSizes (int toolbarThickness,
|
||||
bool isToolbarVertical,
|
||||
int& preferredSize,
|
||||
int& minSize,
|
||||
int& maxSize) = 0;
|
||||
|
||||
/** Your subclass should use this method to draw its content area.
|
||||
|
||||
The graphics object that is passed-in will have been clipped and had its origin
|
||||
moved to fit the content area as specified get getContentArea(). The width and height
|
||||
parameters are the width and height of the content area.
|
||||
|
||||
If the component you're writing isn't a button, you can just do nothing in this method.
|
||||
*/
|
||||
virtual void paintButtonArea (Graphics& g,
|
||||
int width, int height,
|
||||
bool isMouseOver, bool isMouseDown) = 0;
|
||||
|
||||
/** Callback to indicate that the content area of this item has changed.
|
||||
|
||||
This might be because the component was resized, or because the style changed and
|
||||
the space needed for the text label is different.
|
||||
|
||||
See getContentArea() for a description of what the area is.
|
||||
*/
|
||||
virtual void contentAreaChanged (const Rectangle<int>& newBounds) = 0;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Editing modes.
|
||||
These are used by setEditingMode(), but will be rarely needed in user code.
|
||||
*/
|
||||
enum ToolbarEditingMode
|
||||
{
|
||||
normalMode = 0, /**< Means that the component is active, inside a toolbar. */
|
||||
editableOnToolbar, /**< Means that the component is on a toolbar, but the toolbar is in
|
||||
customisation mode, and the items can be dragged around. */
|
||||
editableOnPalette /**< Means that the component is on an new-item palette, so it can be
|
||||
dragged onto a toolbar to add it to that bar.*/
|
||||
};
|
||||
|
||||
/** Changes the editing mode of this component.
|
||||
|
||||
This is used by the ToolbarItemPalette and related classes for making the items draggable,
|
||||
and is unlikely to be of much use in end-user-code.
|
||||
*/
|
||||
void setEditingMode (const ToolbarEditingMode newMode);
|
||||
|
||||
/** Returns the current editing mode of this component.
|
||||
|
||||
This is used by the ToolbarItemPalette and related classes for making the items draggable,
|
||||
and is unlikely to be of much use in end-user-code.
|
||||
*/
|
||||
ToolbarEditingMode getEditingMode() const noexcept { return mode; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool isMouseOver, bool isMouseDown) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
friend class Toolbar;
|
||||
class ItemDragAndDropOverlayComponent;
|
||||
friend class ItemDragAndDropOverlayComponent;
|
||||
const int itemId;
|
||||
ToolbarEditingMode mode;
|
||||
Toolbar::ToolbarItemStyle toolbarStyle;
|
||||
std::unique_ptr<Component> overlayComp;
|
||||
int dragOffsetX, dragOffsetY;
|
||||
bool isActive, isBeingDragged, isBeingUsedAsAButton;
|
||||
Rectangle<int> contentArea;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarItemComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
110
modules/juce_gui_basics/widgets/juce_ToolbarItemFactory.h
Normal file
110
modules/juce_gui_basics/widgets/juce_ToolbarItemFactory.h
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A factory object which can create ToolbarItemComponent objects.
|
||||
|
||||
A subclass of ToolbarItemFactory publishes a set of types of toolbar item
|
||||
that it can create.
|
||||
|
||||
Each type of item is identified by a unique ID, and multiple instances of an
|
||||
item type can exist at once (even on the same toolbar, e.g. spacers or separator
|
||||
bars).
|
||||
|
||||
@see Toolbar, ToolbarItemComponent, ToolbarButton
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ToolbarItemFactory
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
ToolbarItemFactory();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ToolbarItemFactory();
|
||||
|
||||
//==============================================================================
|
||||
/** A set of reserved item ID values, used for the built-in item types.
|
||||
*/
|
||||
enum SpecialItemIds
|
||||
{
|
||||
separatorBarId = -1, /**< The item ID for a vertical (or horizontal) separator bar that
|
||||
can be placed between sets of items to break them into groups. */
|
||||
spacerId = -2, /**< The item ID for a fixed-width space that can be placed between
|
||||
items.*/
|
||||
flexibleSpacerId = -3 /**< The item ID for a gap that pushes outwards against the things on
|
||||
either side of it, filling any available space. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Must return a list of the IDs for all the item types that this factory can create.
|
||||
|
||||
The ids should be added to the array that is passed-in.
|
||||
|
||||
An item ID can be any integer you choose, except for 0, which is considered a null ID,
|
||||
and the predefined IDs in the SpecialItemIds enum.
|
||||
|
||||
You should also add the built-in types (separatorBarId, spacerId and flexibleSpacerId)
|
||||
to this list if you want your toolbar to be able to contain those items.
|
||||
|
||||
The list returned here is used by the ToolbarItemPalette class to obtain its list
|
||||
of available items, and their order on the palette will reflect the order in which
|
||||
they appear on this list.
|
||||
|
||||
@see ToolbarItemPalette
|
||||
*/
|
||||
virtual void getAllToolbarItemIds (Array <int>& ids) = 0;
|
||||
|
||||
/** Must return the set of items that should be added to a toolbar as its default set.
|
||||
|
||||
This method is used by Toolbar::addDefaultItems() to determine which items to
|
||||
create.
|
||||
|
||||
The items that your method adds to the array that is passed-in will be added to the
|
||||
toolbar in the same order. Items can appear in the list more than once.
|
||||
*/
|
||||
virtual void getDefaultItemSet (Array <int>& ids) = 0;
|
||||
|
||||
/** Must create an instance of one of the items that the factory lists in its
|
||||
getAllToolbarItemIds() method.
|
||||
|
||||
The itemId parameter can be any of the values listed by your getAllToolbarItemIds()
|
||||
method, except for the built-in item types from the SpecialItemIds enum, which
|
||||
are created internally by the toolbar code.
|
||||
|
||||
Try not to keep a pointer to the object that is returned, as it will be deleted
|
||||
automatically by the toolbar, and remember that multiple instances of the same
|
||||
item type are likely to exist at the same time.
|
||||
*/
|
||||
virtual ToolbarItemComponent* createItem (int itemId) = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
111
modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.cpp
Normal file
111
modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
ToolbarItemPalette::ToolbarItemPalette (ToolbarItemFactory& tbf, Toolbar& bar)
|
||||
: factory (tbf), toolbar (bar)
|
||||
{
|
||||
auto* itemHolder = new Component();
|
||||
viewport.setViewedComponent (itemHolder);
|
||||
|
||||
Array<int> allIds;
|
||||
factory.getAllToolbarItemIds (allIds);
|
||||
|
||||
for (auto& i : allIds)
|
||||
addComponent (i, -1);
|
||||
|
||||
addAndMakeVisible (viewport);
|
||||
}
|
||||
|
||||
ToolbarItemPalette::~ToolbarItemPalette()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ToolbarItemPalette::addComponent (const int itemId, const int index)
|
||||
{
|
||||
if (auto* tc = Toolbar::createItem (factory, itemId))
|
||||
{
|
||||
items.insert (index, tc);
|
||||
viewport.getViewedComponent()->addAndMakeVisible (tc, index);
|
||||
tc->setEditingMode (ToolbarItemComponent::editableOnPalette);
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
|
||||
void ToolbarItemPalette::replaceComponent (ToolbarItemComponent& comp)
|
||||
{
|
||||
auto index = items.indexOf (&comp);
|
||||
jassert (index >= 0);
|
||||
items.removeObject (&comp, false);
|
||||
|
||||
addComponent (comp.getItemId(), index);
|
||||
resized();
|
||||
}
|
||||
|
||||
void ToolbarItemPalette::resized()
|
||||
{
|
||||
viewport.setBoundsInset (BorderSize<int> (1));
|
||||
|
||||
auto* itemHolder = viewport.getViewedComponent();
|
||||
|
||||
const int indent = 8;
|
||||
const int preferredWidth = viewport.getWidth() - viewport.getScrollBarThickness() - indent;
|
||||
const int height = toolbar.getThickness();
|
||||
auto x = indent;
|
||||
auto y = indent;
|
||||
int maxX = 0;
|
||||
|
||||
for (auto* tc : items)
|
||||
{
|
||||
tc->setStyle (toolbar.getStyle());
|
||||
|
||||
int preferredSize = 1, minSize = 1, maxSize = 1;
|
||||
|
||||
if (tc->getToolbarItemSizes (height, false, preferredSize, minSize, maxSize))
|
||||
{
|
||||
if (x + preferredSize > preferredWidth && x > indent)
|
||||
{
|
||||
x = indent;
|
||||
y += height;
|
||||
}
|
||||
|
||||
tc->setBounds (x, y, preferredSize, height);
|
||||
|
||||
x += preferredSize + 8;
|
||||
maxX = jmax (maxX, x);
|
||||
}
|
||||
}
|
||||
|
||||
itemHolder->setSize (maxX, y + height + 8);
|
||||
}
|
||||
|
||||
} // namespace juce
|
78
modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.h
Normal file
78
modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.h
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component containing a list of toolbar items, which the user can drag onto
|
||||
a toolbar to add them.
|
||||
|
||||
You can use this class directly, but it's a lot easier to call Toolbar::showCustomisationDialog(),
|
||||
which automatically shows one of these in a dialog box with lots of extra controls.
|
||||
|
||||
@see Toolbar
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ToolbarItemPalette : public Component,
|
||||
public DragAndDropContainer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a palette of items for a given factory, with the aim of adding them
|
||||
to the specified toolbar.
|
||||
|
||||
The ToolbarItemFactory::getAllToolbarItemIds() method is used to create the
|
||||
set of items that are shown in this palette.
|
||||
|
||||
The toolbar and factory must not be deleted while this object exists.
|
||||
*/
|
||||
ToolbarItemPalette (ToolbarItemFactory& factory,
|
||||
Toolbar& toolbar);
|
||||
|
||||
/** Destructor. */
|
||||
~ToolbarItemPalette();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
ToolbarItemFactory& factory;
|
||||
Toolbar& toolbar;
|
||||
Viewport viewport;
|
||||
OwnedArray<ToolbarItemComponent> items;
|
||||
|
||||
friend class Toolbar;
|
||||
void replaceComponent (ToolbarItemComponent&);
|
||||
void addComponent (int itemId, int index);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarItemPalette)
|
||||
};
|
||||
|
||||
} // namespace juce
|
1915
modules/juce_gui_basics/widgets/juce_TreeView.cpp
Normal file
1915
modules/juce_gui_basics/widgets/juce_TreeView.cpp
Normal file
File diff suppressed because it is too large
Load Diff
943
modules/juce_gui_basics/widgets/juce_TreeView.h
Normal file
943
modules/juce_gui_basics/widgets/juce_TreeView.h
Normal file
@ -0,0 +1,943 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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 TreeView;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An item in a treeview.
|
||||
|
||||
A TreeViewItem can either be a leaf-node in the tree, or it can contain its
|
||||
own sub-items.
|
||||
|
||||
To implement an item that contains sub-items, override the itemOpennessChanged()
|
||||
method so that when it is opened, it adds the new sub-items to itself using the
|
||||
addSubItem method. Depending on the nature of the item it might choose to only
|
||||
do this the first time it's opened, or it might want to refresh itself each time.
|
||||
It also has the option of deleting its sub-items when it is closed, or leaving them
|
||||
in place.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TreeViewItem
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
TreeViewItem();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~TreeViewItem();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of sub-items that have been added to this item.
|
||||
Note that this doesn't mean much if the node isn't open.
|
||||
@see getSubItem, mightContainSubItems, addSubItem
|
||||
*/
|
||||
int getNumSubItems() const noexcept;
|
||||
|
||||
/** Returns one of the item's sub-items.
|
||||
Remember that the object returned might get deleted at any time when its parent
|
||||
item is closed or refreshed, depending on the nature of the items you're using.
|
||||
|
||||
@see getNumSubItems
|
||||
*/
|
||||
TreeViewItem* getSubItem (int index) const noexcept;
|
||||
|
||||
/** Removes any sub-items. */
|
||||
void clearSubItems();
|
||||
|
||||
/** Adds a sub-item.
|
||||
|
||||
@param newItem the object to add to the item's sub-item list. Once added, these can be
|
||||
found using getSubItem(). When the items are later removed with
|
||||
removeSubItem() (or when this item is deleted), they will be deleted.
|
||||
@param insertPosition the index which the new item should have when it's added. If this
|
||||
value is less than 0, the item will be added to the end of the list.
|
||||
*/
|
||||
void addSubItem (TreeViewItem* newItem, int insertPosition = -1);
|
||||
|
||||
/** Adds a sub-item with a sort-comparator, assuming that the existing items are already sorted.
|
||||
|
||||
@param comparator the comparator object for sorting - see sortSubItems() for details about
|
||||
the methods this class must provide.
|
||||
@param newItem the object to add to the item's sub-item list. Once added, these can be
|
||||
found using getSubItem(). When the items are later removed with
|
||||
removeSubItem() (or when this item is deleted), they will be deleted.
|
||||
*/
|
||||
template <class ElementComparator>
|
||||
void addSubItemSorted (ElementComparator& comparator, TreeViewItem* newItem)
|
||||
{
|
||||
addSubItem (newItem, findInsertIndexInSortedArray (comparator, subItems.begin(), newItem, 0, subItems.size()));
|
||||
}
|
||||
|
||||
/** Removes one of the sub-items.
|
||||
|
||||
@param index the item to remove
|
||||
@param deleteItem if true, the item that is removed will also be deleted.
|
||||
*/
|
||||
void removeSubItem (int index, bool deleteItem = true);
|
||||
|
||||
/** Sorts the list of sub-items using a standard array comparator.
|
||||
|
||||
This will use a comparator object to sort the elements into order. The comparator
|
||||
object must have a method of the form:
|
||||
@code
|
||||
int compareElements (TreeViewItem* first, TreeViewItem* second);
|
||||
@endcode
|
||||
|
||||
..and this method must return:
|
||||
- a value of < 0 if the first comes before the second
|
||||
- a value of 0 if the two objects are equivalent
|
||||
- a value of > 0 if the second comes before the first
|
||||
|
||||
To improve performance, the compareElements() method can be declared as static or const.
|
||||
*/
|
||||
template <class ElementComparator>
|
||||
void sortSubItems (ElementComparator& comparator)
|
||||
{
|
||||
subItems.sort (comparator);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the TreeView to which this item belongs. */
|
||||
TreeView* getOwnerView() const noexcept { return ownerView; }
|
||||
|
||||
/** Returns the item within which this item is contained. */
|
||||
TreeViewItem* getParentItem() const noexcept { return parentItem; }
|
||||
|
||||
//==============================================================================
|
||||
/** True if this item is currently open in the treeview.
|
||||
@see getOpenness
|
||||
*/
|
||||
bool isOpen() const noexcept;
|
||||
|
||||
/** Opens or closes the item.
|
||||
|
||||
When opened or closed, the item's itemOpennessChanged() method will be called,
|
||||
and a subclass should use this callback to create and add any sub-items that
|
||||
it needs to.
|
||||
|
||||
Note that if this is called when the item is in its default openness state, and
|
||||
this call would not change whether it's open or closed, then no change will be
|
||||
stored. If you want to explicitly set the openness state to be non-default then
|
||||
you should use setOpenness instead.
|
||||
|
||||
@see setOpenness, itemOpennessChanged, mightContainSubItems
|
||||
*/
|
||||
void setOpen (bool shouldBeOpen);
|
||||
|
||||
/** An enum of states to describe the explicit or implicit openness of an item. */
|
||||
enum Openness
|
||||
{
|
||||
opennessDefault = 0,
|
||||
opennessClosed = 1,
|
||||
opennessOpen = 2
|
||||
};
|
||||
|
||||
/** Returns the openness state of this item.
|
||||
@see isOpen
|
||||
*/
|
||||
Openness getOpenness() const noexcept;
|
||||
|
||||
/** Opens or closes the item.
|
||||
|
||||
If this causes the value of isOpen() to change, then the item's itemOpennessChanged()
|
||||
method will be called, and a subclass should use this callback to create and add any
|
||||
sub-items that it needs to.
|
||||
|
||||
@see setOpen
|
||||
*/
|
||||
void setOpenness (Openness newOpenness);
|
||||
|
||||
/** True if this item is currently selected.
|
||||
Use this when painting the node, to decide whether to draw it as selected or not.
|
||||
*/
|
||||
bool isSelected() const noexcept;
|
||||
|
||||
/** Selects or deselects the item.
|
||||
If shouldNotify == sendNotification, then a callback will be made
|
||||
to itemSelectionChanged() if the item's selection has changed.
|
||||
*/
|
||||
void setSelected (bool shouldBeSelected,
|
||||
bool deselectOtherItemsFirst,
|
||||
NotificationType shouldNotify = sendNotification);
|
||||
|
||||
/** Returns the rectangle that this item occupies.
|
||||
|
||||
If relativeToTreeViewTopLeft is true, the coordinates are relative to the
|
||||
top-left of the TreeView comp, so this will depend on the scroll-position of
|
||||
the tree. If false, it is relative to the top-left of the topmost item in the
|
||||
tree (so this would be unaffected by scrolling the view).
|
||||
*/
|
||||
Rectangle<int> getItemPosition (bool relativeToTreeViewTopLeft) const noexcept;
|
||||
|
||||
/** Sends a signal to the treeview to make it refresh itself.
|
||||
Call this if your items have changed and you want the tree to update to reflect this.
|
||||
*/
|
||||
void treeHasChanged() const noexcept;
|
||||
|
||||
/** Sends a repaint message to redraw just this item.
|
||||
|
||||
Note that you should only call this if you want to repaint a superficial change. If
|
||||
you're altering the tree's nodes, you should instead call treeHasChanged().
|
||||
*/
|
||||
void repaintItem() const;
|
||||
|
||||
/** Returns the row number of this item in the tree.
|
||||
The row number of an item will change according to which items are open.
|
||||
@see TreeView::getNumRowsInTree(), TreeView::getItemOnRow()
|
||||
*/
|
||||
int getRowNumberInTree() const noexcept;
|
||||
|
||||
/** Returns true if all the item's parent nodes are open.
|
||||
This is useful to check whether the item might actually be visible or not.
|
||||
*/
|
||||
bool areAllParentsOpen() const noexcept;
|
||||
|
||||
/** Changes whether lines are drawn to connect any sub-items to this item.
|
||||
By default, line-drawing is turned on according to LookAndFeel::areLinesDrawnForTreeView().
|
||||
*/
|
||||
void setLinesDrawnForSubItems (bool shouldDrawLines) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Tells the tree whether this item can potentially be opened.
|
||||
|
||||
If your item could contain sub-items, this should return true; if it returns
|
||||
false then the tree will not try to open the item. This determines whether or
|
||||
not the item will be drawn with a 'plus' button next to it.
|
||||
*/
|
||||
virtual bool mightContainSubItems() = 0;
|
||||
|
||||
/** Returns a string to uniquely identify this item.
|
||||
|
||||
If you're planning on using the TreeView::getOpennessState() method, then
|
||||
these strings will be used to identify which nodes are open. The string
|
||||
should be unique amongst the item's sibling items, but it's ok for there
|
||||
to be duplicates at other levels of the tree.
|
||||
|
||||
If you're not going to store the state, then it's ok not to bother implementing
|
||||
this method.
|
||||
*/
|
||||
virtual String getUniqueName() const;
|
||||
|
||||
/** Called when an item is opened or closed.
|
||||
|
||||
When setOpen() is called and the item has specified that it might
|
||||
have sub-items with the mightContainSubItems() method, this method
|
||||
is called to let the item create or manage its sub-items.
|
||||
|
||||
So when this is called with isNowOpen set to true (i.e. when the item is being
|
||||
opened), a subclass might choose to use clearSubItems() and addSubItem() to
|
||||
refresh its sub-item list.
|
||||
|
||||
When this is called with isNowOpen set to false, the subclass might want
|
||||
to use clearSubItems() to save on space, or it might choose to leave them,
|
||||
depending on the nature of the tree.
|
||||
|
||||
You could also use this callback as a trigger to start a background process
|
||||
which asynchronously creates sub-items and adds them, if that's more
|
||||
appropriate for the task in hand.
|
||||
|
||||
@see mightContainSubItems
|
||||
*/
|
||||
virtual void itemOpennessChanged (bool isNowOpen);
|
||||
|
||||
/** Must return the width required by this item.
|
||||
|
||||
If your item needs to have a particular width in pixels, return that value; if
|
||||
you'd rather have it just fill whatever space is available in the treeview,
|
||||
return -1.
|
||||
|
||||
If all your items return -1, no horizontal scrollbar will be shown, but if any
|
||||
items have fixed widths and extend beyond the width of the treeview, a
|
||||
scrollbar will appear.
|
||||
|
||||
Each item can be a different width, but if they change width, you should call
|
||||
treeHasChanged() to update the tree.
|
||||
*/
|
||||
virtual int getItemWidth() const { return -1; }
|
||||
|
||||
/** Must return the height required by this item.
|
||||
|
||||
This is the height in pixels that the item will take up. Items in the tree
|
||||
can be different heights, but if they change height, you should call
|
||||
treeHasChanged() to update the tree.
|
||||
*/
|
||||
virtual int getItemHeight() const { return 20; }
|
||||
|
||||
/** You can override this method to return false if you don't want to allow the
|
||||
user to select this item.
|
||||
*/
|
||||
virtual bool canBeSelected() const { return true; }
|
||||
|
||||
/** Creates a component that will be used to represent this item.
|
||||
|
||||
You don't have to implement this method - if it returns nullptr then no component
|
||||
will be used for the item, and you can just draw it using the paintItem()
|
||||
callback. But if you do return a component, it will be positioned in the
|
||||
treeview so that it can be used to represent this item.
|
||||
|
||||
The component returned will be managed by the treeview, so always return
|
||||
a new component, and don't keep a reference to it, as the treeview will
|
||||
delete it later when it goes off the screen or is no longer needed. Also
|
||||
bear in mind that if the component keeps a reference to the item that
|
||||
created it, that item could be deleted before the component. Its position
|
||||
and size will be completely managed by the tree, so don't attempt to move it
|
||||
around.
|
||||
|
||||
Something you may want to do with your component is to give it a pointer to
|
||||
the TreeView that created it. This is perfectly safe, and there's no danger
|
||||
of it becoming a dangling pointer because the TreeView will always delete
|
||||
the component before it is itself deleted.
|
||||
|
||||
As long as you stick to these rules you can return whatever kind of
|
||||
component you like. It's most useful if you're doing things like drag-and-drop
|
||||
of items, or want to use a Label component to edit item names, etc.
|
||||
*/
|
||||
virtual Component* createItemComponent() { return nullptr; }
|
||||
|
||||
//==============================================================================
|
||||
/** Draws the item's contents.
|
||||
|
||||
You can choose to either implement this method and draw each item, or you
|
||||
can use createItemComponent() to create a component that will represent the
|
||||
item.
|
||||
|
||||
If all you need in your tree is to be able to draw the items and detect when
|
||||
the user selects or double-clicks one of them, it's probably enough to
|
||||
use paintItem(), itemClicked() and itemDoubleClicked(). If you need more
|
||||
complicated interactions, you may need to use createItemComponent() instead.
|
||||
|
||||
@param g the graphics context to draw into
|
||||
@param width the width of the area available for drawing
|
||||
@param height the height of the area available for drawing
|
||||
*/
|
||||
virtual void paintItem (Graphics& g, int width, int height);
|
||||
|
||||
/** Draws the item's open/close button.
|
||||
|
||||
If you don't implement this method, the default behaviour is to call
|
||||
LookAndFeel::drawTreeviewPlusMinusBox(), but you can override it for custom
|
||||
effects. You may want to override it and call the base-class implementation
|
||||
with a different backgroundColour parameter, if your implementation has a
|
||||
background colour other than the default (white).
|
||||
*/
|
||||
virtual void paintOpenCloseButton (Graphics&, const Rectangle<float>& area,
|
||||
Colour backgroundColour, bool isMouseOver);
|
||||
|
||||
/** Draws the line that connects this item to the vertical line extending below its parent. */
|
||||
virtual void paintHorizontalConnectingLine (Graphics&, const Line<float>& line);
|
||||
|
||||
/** Draws the line that extends vertically up towards one of its parents, or down to one of its children. */
|
||||
virtual void paintVerticalConnectingLine (Graphics&, const Line<float>& line);
|
||||
|
||||
/** Called when the user clicks on this item.
|
||||
|
||||
If you're using createItemComponent() to create a custom component for the
|
||||
item, the mouse-clicks might not make it through to the treeview, but this
|
||||
is how you find out about clicks when just drawing each item individually.
|
||||
|
||||
The associated mouse-event details are passed in, so you can find out about
|
||||
which button, where it was, etc.
|
||||
|
||||
@see itemDoubleClicked
|
||||
*/
|
||||
virtual void itemClicked (const MouseEvent&);
|
||||
|
||||
/** Called when the user double-clicks on this item.
|
||||
|
||||
If you're using createItemComponent() to create a custom component for the
|
||||
item, the mouse-clicks might not make it through to the treeview, but this
|
||||
is how you find out about clicks when just drawing each item individually.
|
||||
|
||||
The associated mouse-event details are passed in, so you can find out about
|
||||
which button, where it was, etc.
|
||||
|
||||
If not overridden, the base class method here will open or close the item as
|
||||
if the 'plus' button had been clicked.
|
||||
|
||||
@see itemClicked
|
||||
*/
|
||||
virtual void itemDoubleClicked (const MouseEvent&);
|
||||
|
||||
/** Called when the item is selected or deselected.
|
||||
|
||||
Use this if you want to do something special when the item's selectedness
|
||||
changes. By default it'll get repainted when this happens.
|
||||
*/
|
||||
virtual void itemSelectionChanged (bool isNowSelected);
|
||||
|
||||
/** Called when the owner view changes */
|
||||
virtual void ownerViewChanged (TreeView* newOwner);
|
||||
|
||||
/** The item can return a tool tip string here if it wants to.
|
||||
@see TooltipClient
|
||||
*/
|
||||
virtual String getTooltip();
|
||||
|
||||
//==============================================================================
|
||||
/** To allow items from your treeview to be dragged-and-dropped, implement this method.
|
||||
|
||||
If this returns a non-null variant then when the user drags an item, the treeview will
|
||||
try to find a DragAndDropContainer in its parent hierarchy, and will use it to trigger
|
||||
a drag-and-drop operation, using this string as the source description, with the treeview
|
||||
itself as the source component.
|
||||
|
||||
If you need more complex drag-and-drop behaviour, you can use custom components for
|
||||
the items, and use those to trigger the drag.
|
||||
|
||||
To accept drag-and-drop in your tree, see isInterestedInDragSource(),
|
||||
isInterestedInFileDrag(), etc.
|
||||
|
||||
@see DragAndDropContainer::startDragging
|
||||
*/
|
||||
virtual var getDragSourceDescription();
|
||||
|
||||
/** If you want your item to be able to have files drag-and-dropped onto it, implement this
|
||||
method and return true.
|
||||
|
||||
If you return true and allow some files to be dropped, you'll also need to implement the
|
||||
filesDropped() method to do something with them.
|
||||
|
||||
Note that this will be called often, so make your implementation very quick! There's
|
||||
certainly no time to try opening the files and having a think about what's inside them!
|
||||
|
||||
For responding to internal drag-and-drop of other types of object, see isInterestedInDragSource().
|
||||
@see FileDragAndDropTarget::isInterestedInFileDrag, isInterestedInDragSource
|
||||
*/
|
||||
virtual bool isInterestedInFileDrag (const StringArray& files);
|
||||
|
||||
/** When files are dropped into this item, this callback is invoked.
|
||||
|
||||
For this to work, you'll need to have also implemented isInterestedInFileDrag().
|
||||
The insertIndex value indicates where in the list of sub-items the files were dropped.
|
||||
If files are dropped onto an area of the tree where there are no visible items, this
|
||||
method is called on the root item of the tree, with an insert index of 0.
|
||||
@see FileDragAndDropTarget::filesDropped, isInterestedInFileDrag
|
||||
*/
|
||||
virtual void filesDropped (const StringArray& files, int insertIndex);
|
||||
|
||||
/** If you want your item to act as a DragAndDropTarget, implement this method and return true.
|
||||
|
||||
If you implement this method, you'll also need to implement itemDropped() in order to handle
|
||||
the items when they are dropped.
|
||||
To respond to drag-and-drop of files from external applications, see isInterestedInFileDrag().
|
||||
@see DragAndDropTarget::isInterestedInDragSource, itemDropped
|
||||
*/
|
||||
virtual bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails);
|
||||
|
||||
/** When a things are dropped into this item, this callback is invoked.
|
||||
|
||||
For this to work, you need to have also implemented isInterestedInDragSource().
|
||||
The insertIndex value indicates where in the list of sub-items the new items should be placed.
|
||||
If files are dropped onto an area of the tree where there are no visible items, this
|
||||
method is called on the root item of the tree, with an insert index of 0.
|
||||
@see isInterestedInDragSource, DragAndDropTarget::itemDropped
|
||||
*/
|
||||
virtual void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex);
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a flag to indicate that the item wants to be allowed
|
||||
to draw all the way across to the left edge of the treeview.
|
||||
|
||||
By default this is false, which means that when the paintItem()
|
||||
method is called, its graphics context is clipped to only allow
|
||||
drawing within the item's rectangle. If this flag is set to true,
|
||||
then the graphics context isn't clipped on its left side, so it
|
||||
can draw all the way across to the left margin. Note that the
|
||||
context will still have its origin in the same place though, so
|
||||
the coordinates of anything to its left will be negative. It's
|
||||
mostly useful if you want to draw a wider bar behind the
|
||||
highlighted item.
|
||||
*/
|
||||
void setDrawsInLeftMargin (bool canDrawInLeftMargin) noexcept;
|
||||
|
||||
/** Sets a flag to indicate that the item wants to be allowed
|
||||
to draw all the way across to the right edge of the treeview.
|
||||
|
||||
Similar to setDrawsInLeftMargin: when this flag is set to true,
|
||||
then the graphics context isn't clipped on the right side. Unlike
|
||||
setDrawsInLeftMargin, you will very rarely need to use this function,
|
||||
as this method won't clip the right margin unless your TreeViewItem
|
||||
overrides getItemWidth to return a positive value.
|
||||
|
||||
@see setDrawsInLeftMargin, getItemWidth
|
||||
*/
|
||||
void setDrawsInRightMargin (bool canDrawInRightMargin) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Saves the current state of open/closed nodes so it can be restored later.
|
||||
|
||||
This takes a snapshot of which sub-nodes have been explicitly opened or closed,
|
||||
and records it as XML. To identify node objects it uses the
|
||||
TreeViewItem::getUniqueName() method to create named paths. This
|
||||
means that the same state of open/closed nodes can be restored to a
|
||||
completely different instance of the tree, as long as it contains nodes
|
||||
whose unique names are the same.
|
||||
|
||||
You'd normally want to use TreeView::getOpennessState() rather than call it
|
||||
for a specific item, but this can be handy if you need to briefly save the state
|
||||
for a section of the tree.
|
||||
|
||||
The caller is responsible for deleting the object that is returned.
|
||||
|
||||
Note that if all nodes of the tree are in their default state, then this may
|
||||
return a nullptr.
|
||||
|
||||
@see TreeView::getOpennessState, restoreOpennessState
|
||||
*/
|
||||
XmlElement* getOpennessState() const;
|
||||
|
||||
/** Restores the openness of this item and all its sub-items from a saved state.
|
||||
|
||||
See TreeView::restoreOpennessState for more details.
|
||||
|
||||
You'd normally want to use TreeView::restoreOpennessState() rather than call it
|
||||
for a specific item, but this can be handy if you need to briefly save the state
|
||||
for a section of the tree.
|
||||
|
||||
@see TreeView::restoreOpennessState, getOpennessState
|
||||
*/
|
||||
void restoreOpennessState (const XmlElement& xml);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the index of this item in its parent's sub-items. */
|
||||
int getIndexInParent() const noexcept;
|
||||
|
||||
/** Returns true if this item is the last of its parent's sub-itens. */
|
||||
bool isLastOfSiblings() const noexcept;
|
||||
|
||||
/** Creates a string that can be used to uniquely retrieve this item in the tree.
|
||||
|
||||
The string that is returned can be passed to TreeView::findItemFromIdentifierString().
|
||||
The string takes the form of a path, constructed from the getUniqueName() of this
|
||||
item and all its parents, so these must all be correctly implemented for it to work.
|
||||
@see TreeView::findItemFromIdentifierString, getUniqueName
|
||||
*/
|
||||
String getItemIdentifierString() const;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This handy class takes a copy of a TreeViewItem's openness when you create it,
|
||||
and restores that openness state when its destructor is called.
|
||||
|
||||
This can very handy when you're refreshing sub-items - e.g.
|
||||
@code
|
||||
void MyTreeViewItem::updateChildItems()
|
||||
{
|
||||
OpennessRestorer openness (*this); // saves the openness state here..
|
||||
|
||||
clearSubItems();
|
||||
|
||||
// add a bunch of sub-items here which may or may not be the same as the ones that
|
||||
// were previously there
|
||||
addSubItem (...
|
||||
|
||||
// ..and at this point, the old openness is restored, so any items that haven't
|
||||
// changed will have their old openness retained.
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
class OpennessRestorer
|
||||
{
|
||||
public:
|
||||
OpennessRestorer (TreeViewItem&);
|
||||
~OpennessRestorer();
|
||||
|
||||
private:
|
||||
TreeViewItem& treeViewItem;
|
||||
std::unique_ptr<XmlElement> oldOpenness;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpennessRestorer)
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
TreeView* ownerView = nullptr;
|
||||
TreeViewItem* parentItem = nullptr;
|
||||
OwnedArray<TreeViewItem> subItems;
|
||||
int y = 0, itemHeight = 0, totalHeight = 0, itemWidth = 0, totalWidth = 0;
|
||||
int uid = 0;
|
||||
bool selected : 1;
|
||||
bool redrawNeeded : 1;
|
||||
bool drawLinesInside : 1;
|
||||
bool drawLinesSet : 1;
|
||||
bool drawsInLeftMargin : 1;
|
||||
bool drawsInRightMargin : 1;
|
||||
unsigned int openness : 2;
|
||||
|
||||
friend class TreeView;
|
||||
|
||||
void updatePositions (int newY);
|
||||
int getIndentX() const noexcept;
|
||||
void setOwnerView (TreeView*) noexcept;
|
||||
void paintRecursively (Graphics&, int width);
|
||||
TreeViewItem* getTopLevelItem() noexcept;
|
||||
TreeViewItem* findItemRecursively (int y) noexcept;
|
||||
TreeViewItem* getDeepestOpenParentItem() noexcept;
|
||||
int getNumRows() const noexcept;
|
||||
TreeViewItem* getItemOnRow (int index) noexcept;
|
||||
void deselectAllRecursively (TreeViewItem* itemToIgnore);
|
||||
int countSelectedItemsRecursively (int depth) const noexcept;
|
||||
TreeViewItem* getSelectedItemWithIndex (int index) noexcept;
|
||||
TreeViewItem* getNextVisibleItem (bool recurse) const noexcept;
|
||||
TreeViewItem* findItemFromIdentifierString (const String&);
|
||||
void restoreToDefaultOpenness();
|
||||
bool isFullyOpen() const noexcept;
|
||||
XmlElement* getOpennessState (bool canReturnNull) const;
|
||||
bool removeSubItemFromList (int index, bool deleteItem);
|
||||
void removeAllSubItemsFromList();
|
||||
bool areLinesDrawn() const;
|
||||
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// The parameters for these methods have changed - please update your code!
|
||||
virtual void isInterestedInDragSource (const String&, Component*) {}
|
||||
virtual int itemDropped (const String&, Component*, int) { return 0; }
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewItem)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A tree-view component.
|
||||
|
||||
Use one of these to hold and display a structure of TreeViewItem objects.
|
||||
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TreeView : public Component,
|
||||
public SettableTooltipClient,
|
||||
public FileDragAndDropTarget,
|
||||
public DragAndDropTarget
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty treeview.
|
||||
|
||||
Once you've got a treeview component, you'll need to give it something to
|
||||
display, using the setRootItem() method.
|
||||
*/
|
||||
TreeView (const String& componentName = String());
|
||||
|
||||
/** Destructor. */
|
||||
~TreeView();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the item that is displayed in the treeview.
|
||||
|
||||
A tree has a single root item which contains as many sub-items as it needs. If
|
||||
you want the tree to contain a number of root items, you should still use a single
|
||||
root item above these, but hide it using setRootItemVisible().
|
||||
|
||||
You can pass nullptr to this method to clear the tree and remove its current root item.
|
||||
|
||||
The object passed in will not be deleted by the treeview, it's up to the caller
|
||||
to delete it when no longer needed. BUT make absolutely sure that you don't delete
|
||||
this item until you've removed it from the tree, either by calling setRootItem (nullptr),
|
||||
or by deleting the tree first. You can also use deleteRootItem() as a quick way
|
||||
to delete it.
|
||||
*/
|
||||
void setRootItem (TreeViewItem* newRootItem);
|
||||
|
||||
/** Returns the tree's root item.
|
||||
|
||||
This will be the last object passed to setRootItem(), or nullptr if none has been set.
|
||||
*/
|
||||
TreeViewItem* getRootItem() const noexcept { return rootItem; }
|
||||
|
||||
/** This will remove and delete the current root item.
|
||||
It's a convenient way of deleting the item and calling setRootItem (nullptr).
|
||||
*/
|
||||
void deleteRootItem();
|
||||
|
||||
/** Changes whether the tree's root item is shown or not.
|
||||
|
||||
If the root item is hidden, only its sub-items will be shown in the treeview - this
|
||||
lets you make the tree look as if it's got many root items. If it's hidden, this call
|
||||
will also make sure the root item is open (otherwise the treeview would look empty).
|
||||
*/
|
||||
void setRootItemVisible (bool shouldBeVisible);
|
||||
|
||||
/** Returns true if the root item is visible.
|
||||
|
||||
@see setRootItemVisible
|
||||
*/
|
||||
bool isRootItemVisible() const noexcept { return rootItemVisible; }
|
||||
|
||||
/** Sets whether items are open or closed by default.
|
||||
|
||||
Normally, items are closed until the user opens them, but you can use this
|
||||
to make them default to being open until explicitly closed.
|
||||
|
||||
@see areItemsOpenByDefault
|
||||
*/
|
||||
void setDefaultOpenness (bool isOpenByDefault);
|
||||
|
||||
/** Returns true if the tree's items default to being open.
|
||||
|
||||
@see setDefaultOpenness
|
||||
*/
|
||||
bool areItemsOpenByDefault() const noexcept { return defaultOpenness; }
|
||||
|
||||
/** This sets a flag to indicate that the tree can be used for multi-selection.
|
||||
|
||||
You can always select multiple items internally by calling the
|
||||
TreeViewItem::setSelected() method, but this flag indicates whether the user
|
||||
is allowed to multi-select by clicking on the tree.
|
||||
|
||||
By default it is disabled.
|
||||
|
||||
@see isMultiSelectEnabled
|
||||
*/
|
||||
void setMultiSelectEnabled (bool canMultiSelect);
|
||||
|
||||
/** Returns whether multi-select has been enabled for the tree.
|
||||
|
||||
@see setMultiSelectEnabled
|
||||
*/
|
||||
bool isMultiSelectEnabled() const noexcept { return multiSelectEnabled; }
|
||||
|
||||
/** Sets a flag to indicate whether to hide the open/close buttons.
|
||||
|
||||
@see areOpenCloseButtonsVisible
|
||||
*/
|
||||
void setOpenCloseButtonsVisible (bool shouldBeVisible);
|
||||
|
||||
/** Returns whether open/close buttons are shown.
|
||||
|
||||
@see setOpenCloseButtonsVisible
|
||||
*/
|
||||
bool areOpenCloseButtonsVisible() const noexcept { return openCloseButtonsVisible; }
|
||||
|
||||
//==============================================================================
|
||||
/** Deselects any items that are currently selected. */
|
||||
void clearSelectedItems();
|
||||
|
||||
/** Returns the number of items that are currently selected.
|
||||
If maximumDepthToSearchTo is >= 0, it lets you specify a maximum depth to which the
|
||||
tree will be recursed.
|
||||
@see getSelectedItem, clearSelectedItems
|
||||
*/
|
||||
int getNumSelectedItems (int maximumDepthToSearchTo = -1) const noexcept;
|
||||
|
||||
/** Returns one of the selected items in the tree.
|
||||
@param index the index, 0 to (getNumSelectedItems() - 1)
|
||||
*/
|
||||
TreeViewItem* getSelectedItem (int index) const noexcept;
|
||||
|
||||
/** Moves the selected row up or down by the specified number of rows. */
|
||||
void moveSelectedRow (int deltaRows);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of rows the tree is using.
|
||||
This will depend on which items are open.
|
||||
@see TreeViewItem::getRowNumberInTree()
|
||||
*/
|
||||
int getNumRowsInTree() const;
|
||||
|
||||
/** Returns the item on a particular row of the tree.
|
||||
If the index is out of range, this will return nullptr.
|
||||
@see getNumRowsInTree, TreeViewItem::getRowNumberInTree()
|
||||
*/
|
||||
TreeViewItem* getItemOnRow (int index) const;
|
||||
|
||||
/** Returns the item that contains a given y position.
|
||||
The y is relative to the top of the TreeView component.
|
||||
*/
|
||||
TreeViewItem* getItemAt (int yPosition) const noexcept;
|
||||
|
||||
/** Tries to scroll the tree so that this item is on-screen somewhere. */
|
||||
void scrollToKeepItemVisible (TreeViewItem* item);
|
||||
|
||||
/** Returns the treeview's Viewport object. */
|
||||
Viewport* getViewport() const noexcept;
|
||||
|
||||
/** Returns the number of pixels by which each nested level of the tree is indented.
|
||||
@see setIndentSize
|
||||
*/
|
||||
int getIndentSize() noexcept;
|
||||
|
||||
/** Changes the distance by which each nested level of the tree is indented.
|
||||
@see getIndentSize
|
||||
*/
|
||||
void setIndentSize (int newIndentSize);
|
||||
|
||||
/** Searches the tree for an item with the specified identifier.
|
||||
The identifier string must have been created by calling TreeViewItem::getItemIdentifierString().
|
||||
If no such item exists, this will return false. If the item is found, all of its items
|
||||
will be automatically opened.
|
||||
*/
|
||||
TreeViewItem* findItemFromIdentifierString (const String& identifierString) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Saves the current state of open/closed nodes so it can be restored later.
|
||||
|
||||
This takes a snapshot of which nodes have been explicitly opened or closed,
|
||||
and records it as XML. To identify node objects it uses the
|
||||
TreeViewItem::getUniqueName() method to create named paths. This
|
||||
means that the same state of open/closed nodes can be restored to a
|
||||
completely different instance of the tree, as long as it contains nodes
|
||||
whose unique names are the same.
|
||||
|
||||
The caller is responsible for deleting the object that is returned.
|
||||
|
||||
@param alsoIncludeScrollPosition if this is true, the state will also
|
||||
include information about where the
|
||||
tree has been scrolled to vertically,
|
||||
so this can also be restored
|
||||
@see restoreOpennessState
|
||||
*/
|
||||
XmlElement* getOpennessState (bool alsoIncludeScrollPosition) const;
|
||||
|
||||
/** Restores a previously saved arrangement of open/closed nodes.
|
||||
|
||||
This will try to restore a snapshot of the tree's state that was created by
|
||||
the getOpennessState() method. If any of the nodes named in the original
|
||||
XML aren't present in this tree, they will be ignored.
|
||||
|
||||
If restoreStoredSelection is true, it will also try to re-select any items that
|
||||
were selected in the stored state.
|
||||
|
||||
@see getOpennessState
|
||||
*/
|
||||
void restoreOpennessState (const XmlElement& newState,
|
||||
bool restoreStoredSelection);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the treeview.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1000500, /**< A background colour to fill the component with. */
|
||||
linesColourId = 0x1000501, /**< The colour to draw the lines with.*/
|
||||
dragAndDropIndicatorColourId = 0x1000502, /**< The colour to use for the drag-and-drop target position indicator. */
|
||||
selectedItemBackgroundColourId = 0x1000503, /**< The colour to use to fill the background of any selected items. */
|
||||
oddItemsColourId = 0x1000504, /**< The colour to use to fill the backround of the odd numbered items. */
|
||||
evenItemsColourId = 0x1000505 /**< The colour to use to fill the backround of the even numbered items. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
treeview drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void drawTreeviewPlusMinusBox (Graphics&, const Rectangle<float>& area,
|
||||
Colour backgroundColour, bool isItemOpen, bool isMouseOver) = 0;
|
||||
|
||||
virtual bool areLinesDrawnForTreeView (TreeView&) = 0;
|
||||
virtual int getTreeViewIndentSize (TreeView&) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
bool isInterestedInFileDrag (const StringArray& files) override;
|
||||
/** @internal */
|
||||
void fileDragEnter (const StringArray& files, int x, int y) override;
|
||||
/** @internal */
|
||||
void fileDragMove (const StringArray& files, int x, int y) override;
|
||||
/** @internal */
|
||||
void fileDragExit (const StringArray& files) override;
|
||||
/** @internal */
|
||||
void filesDropped (const StringArray& files, int x, int y) override;
|
||||
/** @internal */
|
||||
bool isInterestedInDragSource (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDragEnter (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDragMove (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDragExit (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDropped (const SourceDetails&) override;
|
||||
|
||||
private:
|
||||
class ContentComponent;
|
||||
class TreeViewport;
|
||||
class InsertPointHighlight;
|
||||
class TargetGroupHighlight;
|
||||
friend class TreeViewItem;
|
||||
friend class ContentComponent;
|
||||
friend struct ContainerDeletePolicy<TreeViewport>;
|
||||
friend struct ContainerDeletePolicy<InsertPointHighlight>;
|
||||
friend struct ContainerDeletePolicy<TargetGroupHighlight>;
|
||||
|
||||
std::unique_ptr<TreeViewport> viewport;
|
||||
CriticalSection nodeAlterationLock;
|
||||
TreeViewItem* rootItem = nullptr;
|
||||
std::unique_ptr<InsertPointHighlight> dragInsertPointHighlight;
|
||||
std::unique_ptr<TargetGroupHighlight> dragTargetGroupHighlight;
|
||||
int indentSize = -1;
|
||||
bool defaultOpenness = false, needsRecalculating = true, rootItemVisible = true;
|
||||
bool multiSelectEnabled = false, openCloseButtonsVisible = true;
|
||||
|
||||
void itemsChanged() noexcept;
|
||||
void recalculateIfNeeded();
|
||||
void updateButtonUnderMouse (const MouseEvent&);
|
||||
struct InsertPoint;
|
||||
void showDragHighlight (const InsertPoint&) noexcept;
|
||||
void hideDragHighlight() noexcept;
|
||||
void handleDrag (const StringArray&, const SourceDetails&);
|
||||
void handleDrop (const StringArray&, const SourceDetails&);
|
||||
bool toggleOpenSelectedItem();
|
||||
void moveOutOfSelectedItem();
|
||||
void moveIntoSelectedItem();
|
||||
void moveByPages (int numPages);
|
||||
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// this method has been deprecated - see the new version..
|
||||
virtual int paintOpenCloseButton (Graphics&, int, int, bool) { return 0; }
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeView)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user