git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
709
deps/juce/modules/juce_gui_basics/widgets/juce_ComboBox.cpp
vendored
Normal file
709
deps/juce/modules/juce_gui_basics/widgets/juce_ComboBox.cpp
vendored
Normal file
@ -0,0 +1,709 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 selection.
|
||||
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::addItem (const String& newItemText, int newItemId, const Image & newItemImage)
|
||||
{
|
||||
// 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, newItemImage);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (image) {
|
||||
removeChildComponent(image.get());
|
||||
image.reset();
|
||||
}
|
||||
if (item != nullptr && item->image != nullptr) {
|
||||
image = item->image->createCopy();
|
||||
addAndMakeVisible(image.get());
|
||||
resized();
|
||||
}
|
||||
|
||||
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())
|
||||
getLookAndFeel().drawComboBoxTextWhenNothingSelected (g, *this, *label);
|
||||
}
|
||||
|
||||
void ComboBox::resized()
|
||||
{
|
||||
if (getHeight() > 0 && getWidth() > 0)
|
||||
getLookAndFeel().positionComboBoxText (*this, *label, image.get());
|
||||
}
|
||||
|
||||
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->setAccessible (labelEditableState == labelIsEditable);
|
||||
|
||||
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;
|
||||
|
||||
// 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 = SafePointer<ComboBox> { this }]() mutable { if (safePointer != nullptr) safePointer->showPopup(); });
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (! menuActive)
|
||||
menuActive = true;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
auto& lf = getLookAndFeel();
|
||||
|
||||
menu.setLookAndFeel (&lf);
|
||||
menu.showMenuAsync (lf.getOptionsForComboBoxPopupMenu (*this, *label),
|
||||
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); }
|
||||
|
||||
//==============================================================================
|
||||
class ComboBoxAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit ComboBoxAccessibilityHandler (ComboBox& comboBoxToWrap)
|
||||
: AccessibilityHandler (comboBoxToWrap,
|
||||
AccessibilityRole::comboBox,
|
||||
getAccessibilityActions (comboBoxToWrap)),
|
||||
comboBox (comboBoxToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
AccessibleState getCurrentState() const override
|
||||
{
|
||||
auto state = AccessibilityHandler::getCurrentState().withExpandable();
|
||||
|
||||
return comboBox.isPopupActive() ? state.withExpanded() : state.withCollapsed();
|
||||
}
|
||||
|
||||
String getTitle() const override { return comboBox.getText(); }
|
||||
String getHelp() const override { return comboBox.getTooltip(); }
|
||||
|
||||
private:
|
||||
static AccessibilityActions getAccessibilityActions (ComboBox& comboBox)
|
||||
{
|
||||
return AccessibilityActions().addAction (AccessibilityActionType::press, [&comboBox] { comboBox.showPopup(); })
|
||||
.addAction (AccessibilityActionType::showMenu, [&comboBox] { comboBox.showPopup(); });
|
||||
}
|
||||
|
||||
ComboBox& comboBox;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxAccessibilityHandler)
|
||||
};
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> ComboBox::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<ComboBoxAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
} // namespace juce
|
477
deps/juce/modules/juce_gui_basics/widgets/juce_ComboBox.h
vendored
Normal file
477
deps/juce/modules/juce_gui_basics/widgets/juce_ComboBox.h
vendored
Normal file
@ -0,0 +1,477 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 = {});
|
||||
|
||||
/** Destructor. */
|
||||
~ComboBox() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 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!
|
||||
@param newItemImage an icon image that is shown to the left of text
|
||||
@see setItemEnabled, addSeparator, addSectionHeading, getNumItems, getItemText, getItemId
|
||||
*/
|
||||
void addItem (const String& newItemText, int newItemId, const Image & newItemImage);
|
||||
|
||||
|
||||
/** 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 0, otherwise it will return
|
||||
the appropriate 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() noexcept { return ¤tMenu; }
|
||||
|
||||
/** Returns the PopupMenu object associated with the ComboBox. */
|
||||
const PopupMenu* getRootMenu() const noexcept { 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() = default;
|
||||
|
||||
/** 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() = default;
|
||||
|
||||
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, Drawable * image = nullptr) = 0;
|
||||
|
||||
virtual PopupMenu::Options getOptionsForComboBoxPopupMenu (ComboBox&, Label&) = 0;
|
||||
|
||||
virtual void drawComboBoxTextWhenNothingSelected (Graphics&, ComboBox&, Label&) = 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;
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
// These methods' bool parameters have changed: see their new method signatures.
|
||||
[[deprecated]] void clear (bool);
|
||||
[[deprecated]] void setSelectedId (int, bool);
|
||||
[[deprecated]] void setSelectedItemIndex (int, bool);
|
||||
[[deprecated]] void setText (const String&, bool);
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
std::unique_ptr<Drawable> image;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
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
|
107
deps/juce/modules/juce_gui_basics/widgets/juce_ImageComponent.cpp
vendored
Normal file
107
deps/juce/modules/juce_gui_basics/widgets/juce_ImageComponent.cpp
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ImageComponent::createAccessibilityHandler()
|
||||
{
|
||||
class ImageComponentAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit ImageComponentAccessibilityHandler (ImageComponent& imageComponentToWrap)
|
||||
: AccessibilityHandler (imageComponentToWrap, AccessibilityRole::image),
|
||||
imageComponent (imageComponentToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
String getHelp() const override { return imageComponent.getTooltip(); }
|
||||
|
||||
private:
|
||||
ImageComponent& imageComponent;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImageComponentAccessibilityHandler)
|
||||
};
|
||||
|
||||
return std::make_unique<ImageComponentAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
} // namespace juce
|
81
deps/juce/modules/juce_gui_basics/widgets/juce_ImageComponent.h
vendored
Normal file
81
deps/juce/modules/juce_gui_basics/widgets/juce_ImageComponent.h
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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:
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
Image image;
|
||||
RectanglePlacement placement;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImageComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
583
deps/juce/modules/juce_gui_basics/widgets/juce_Label.cpp
vendored
Normal file
583
deps/juce/modules/juce_gui_basics/widgets/juce_Label.cpp
vendored
Normal file
@ -0,0 +1,583 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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);
|
||||
|
||||
setInterceptsMouseClicks(editSingleClick || editDoubleClick, editSingleClick || editDoubleClick);
|
||||
|
||||
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;
|
||||
|
||||
const auto isKeybordFocusable = (editOnSingleClick || editOnDoubleClick);
|
||||
|
||||
setWantsKeyboardFocus (isKeybordFocusable);
|
||||
setFocusContainerType (isKeybordFocusable ? FocusContainerType::keyboardFocusContainer
|
||||
: FocusContainerType::none);
|
||||
setInterceptsMouseClicks(isKeybordFocusable, isKeybordFocusable);
|
||||
invalidateAccessibilityHandler();
|
||||
}
|
||||
|
||||
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 ownerComponent.get();
|
||||
}
|
||||
|
||||
void Label::attachToComponent (Component* owner, bool onLeft)
|
||||
{
|
||||
jassert (owner != this); // Not a great idea to try to attach it to itself!
|
||||
|
||||
if (ownerComponent != nullptr)
|
||||
ownerComponent->removeComponentListener (this);
|
||||
|
||||
ownerComponent = owner;
|
||||
leftOfOwnerComp = onLeft;
|
||||
|
||||
if (ownerComponent != nullptr)
|
||||
{
|
||||
setVisible (ownerComponent->isVisible());
|
||||
ownerComponent->addComponentListener (this);
|
||||
componentParentHierarchyChanged (*ownerComponent);
|
||||
componentMovedOrResized (*ownerComponent, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Label::componentMovedOrResized (Component& component, bool /*wasMoved*/, bool /*wasResized*/)
|
||||
{
|
||||
auto& lf = getLookAndFeel();
|
||||
auto f = lf.getLabelFont (*this);
|
||||
auto borderSize = lf.getLabelBorderSize (*this);
|
||||
|
||||
if (leftOfOwnerComp)
|
||||
{
|
||||
auto width = jmin (roundToInt (f.getStringWidthFloat (textValue.toString()) + 0.5f)
|
||||
+ borderSize.getLeftAndRight(),
|
||||
component.getX());
|
||||
|
||||
setBounds (component.getX() - width, component.getY(), width, component.getHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto height = borderSize.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());
|
||||
editor->setSize (10, 10);
|
||||
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();
|
||||
|
||||
if (deletionChecker != nullptr)
|
||||
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:
|
||||
explicit LabelKeyboardFocusTraverser (Label& l) : owner (l) {}
|
||||
|
||||
Component* getDefaultComponent (Component* parent) override
|
||||
{
|
||||
auto getContainer = [&]
|
||||
{
|
||||
if (owner.getCurrentTextEditor() != nullptr && parent == &owner)
|
||||
return owner.findKeyboardFocusContainer();
|
||||
|
||||
return parent;
|
||||
};
|
||||
|
||||
if (auto* container = getContainer())
|
||||
return KeyboardFocusTraverser::getDefaultComponent (container);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Component* getNextComponent (Component* c) override { return KeyboardFocusTraverser::getNextComponent (getComp (c)); }
|
||||
Component* getPreviousComponent (Component* c) override { return KeyboardFocusTraverser::getPreviousComponent (getComp (c)); }
|
||||
|
||||
private:
|
||||
Component* getComp (Component* current) const
|
||||
{
|
||||
if (auto* ed = owner.getCurrentTextEditor())
|
||||
if (current == ed)
|
||||
return current->getParentComponent();
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
Label& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LabelKeyboardFocusTraverser)
|
||||
};
|
||||
|
||||
std::unique_ptr<ComponentTraverser> Label::createKeyboardFocusTraverser()
|
||||
{
|
||||
return std::make_unique<LabelKeyboardFocusTraverser> (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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)
|
||||
{
|
||||
jassertquiet (&ed == editor.get());
|
||||
|
||||
editor->setText (textValue.toString(), false);
|
||||
hideEditor (true);
|
||||
}
|
||||
}
|
||||
|
||||
void Label::textEditorFocusLost (TextEditor& ed)
|
||||
{
|
||||
textEditorTextChanged (ed);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class LabelAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit LabelAccessibilityHandler (Label& labelToWrap)
|
||||
: AccessibilityHandler (labelToWrap,
|
||||
labelToWrap.isEditable() ? AccessibilityRole::editableText : AccessibilityRole::label,
|
||||
getAccessibilityActions (labelToWrap),
|
||||
{ std::make_unique<LabelValueInterface> (labelToWrap) }),
|
||||
label (labelToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
String getTitle() const override { return label.getText(); }
|
||||
String getHelp() const override { return label.getTooltip(); }
|
||||
|
||||
AccessibleState getCurrentState() const override
|
||||
{
|
||||
if (label.isBeingEdited())
|
||||
return {}; // allow focus to pass through to the TextEditor
|
||||
|
||||
return AccessibilityHandler::getCurrentState();
|
||||
}
|
||||
|
||||
private:
|
||||
class LabelValueInterface : public AccessibilityTextValueInterface
|
||||
{
|
||||
public:
|
||||
explicit LabelValueInterface (Label& labelToWrap)
|
||||
: label (labelToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
bool isReadOnly() const override { return true; }
|
||||
String getCurrentValueAsString() const override { return label.getText(); }
|
||||
void setValueAsString (const String&) override {}
|
||||
|
||||
private:
|
||||
Label& label;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LabelValueInterface)
|
||||
};
|
||||
|
||||
static AccessibilityActions getAccessibilityActions (Label& label)
|
||||
{
|
||||
if (label.isEditable())
|
||||
return AccessibilityActions().addAction (AccessibilityActionType::press, [&label] { label.showEditor(); });
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Label& label;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LabelAccessibilityHandler)
|
||||
};
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> Label::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<LabelAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
} // namespace juce
|
366
deps/juce/modules/juce_gui_basics/widgets/juce_Label.h
vendored
Normal file
366
deps/juce/modules/juce_gui_basics/widgets/juce_Label.h
vendored
Normal file
@ -0,0 +1,366 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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() = default;
|
||||
|
||||
/** 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() = default;
|
||||
|
||||
virtual void drawLabel (Graphics&, Label&) = 0;
|
||||
virtual Font getLabelFont (Label&) = 0;
|
||||
virtual BorderSize<int> getLabelBorderSize (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 */
|
||||
std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser() 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;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
bool updateFromTextEditorContents (TextEditor&);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Label)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
1211
deps/juce/modules/juce_gui_basics/widgets/juce_ListBox.cpp
vendored
Normal file
1211
deps/juce/modules/juce_gui_basics/widgets/juce_ListBox.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
60
deps/juce/modules/juce_gui_basics/widgets/juce_ListBox.cpp.rej
vendored
Normal file
60
deps/juce/modules/juce_gui_basics/widgets/juce_ListBox.cpp.rej
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
diff a/modules/juce_gui_basics/widgets/juce_ListBox.cpp b/modules/juce_gui_basics/widgets/juce_ListBox.cpp (rejected hunks)
|
||||
@@ -63,12 +63,15 @@ public:
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
+ DBG("Listbox row mousedown");
|
||||
isDragging = false;
|
||||
selectRowOnMouseUp = false;
|
||||
|
||||
+ owner.stopInertialScroll();
|
||||
+
|
||||
if (isEnabled())
|
||||
{
|
||||
- if (owner.selectOnMouseDown && ! selected)
|
||||
+ if (! selected && owner.selectOnMouseDown && !owner.getTouchScrollEnabled())
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (row, e.mods, false);
|
||||
|
||||
@@ -84,7 +87,16 @@ public:
|
||||
|
||||
void mouseUp (const MouseEvent& e) override
|
||||
{
|
||||
- if (isEnabled() && selectRowOnMouseUp && ! isDragging)
|
||||
+ bool wasTouchScrolling = false;
|
||||
+ if (owner.isTouchScrollActive()) {
|
||||
+ wasTouchScrolling = true;
|
||||
+ if (owner.getTouchScrollInertia() > 0.0f) {
|
||||
+ owner.startInertialScroll();
|
||||
+ }
|
||||
+ owner.touchScrollDone();
|
||||
+ }
|
||||
+
|
||||
+ if (isEnabled() && !wasTouchScrolling && selectRowOnMouseUp && !isDragging)
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (row, e.mods, true);
|
||||
|
||||
@@ -102,9 +114,10 @@ public:
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
+ bool touchdrag = owner.touchScrollDrag(e);
|
||||
if (ListBoxModel* m = owner.getModel())
|
||||
{
|
||||
- if (isEnabled() && e.mouseWasDraggedSinceMouseDown() && ! isDragging)
|
||||
+ if (!touchdrag && isEnabled() && e.mouseWasDraggedSinceMouseDown() && ! isDragging)
|
||||
{
|
||||
SparseSet<int> rowsToDrag;
|
||||
|
||||
@@ -367,6 +380,11 @@ private:
|
||||
//==============================================================================
|
||||
ListBox::ListBox (const String& name, ListBoxModel* const m)
|
||||
: Component (name),
|
||||
+ touchScrollEnabled(false),
|
||||
+ touchScrolling(false),
|
||||
+ scrollRate(0.0f),
|
||||
+ touchScrollInertia(0.25f),
|
||||
+ touchScrollScale(1.0f),
|
||||
model (m),
|
||||
totalItems (0),
|
||||
rowHeight (22),
|
615
deps/juce/modules/juce_gui_basics/widgets/juce_ListBox.h
vendored
Normal file
615
deps/juce/modules/juce_gui_basics/widgets/juce_ListBox.h
vendored
Normal file
@ -0,0 +1,615 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 return a name for the specified row.
|
||||
|
||||
By default this will just return a string containing the row number.
|
||||
*/
|
||||
virtual String getNameForRow (int rowNumber);
|
||||
|
||||
/** 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);
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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() override;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
bool getMultipleSelectionEnabled () const noexcept { return multipleSelection; }
|
||||
|
||||
/** 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;
|
||||
bool getClickingTogglesRowSelection () const noexcept { return alwaysFlipSelection; }
|
||||
|
||||
/** 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;
|
||||
bool getRowSelectedOnMouseDown () const noexcept { return selectOnMouseDown; }
|
||||
|
||||
/** 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 setRowClickedOnMouseDown (bool clicksOnMouseDown) noexcept;
|
||||
bool getRowClickedOnMouseDown () const noexcept { return clickOnMouseDown; }
|
||||
|
||||
|
||||
/** 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 (std::unique_ptr<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);
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
[[deprecated ("This method's bool parameter has changed: see the new method signature.")]]
|
||||
void setSelectedRows (const SparseSet<int>&, bool);
|
||||
#endif
|
||||
|
||||
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, clickOnMouseDown = true;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
bool hasAccessibleHeaderComponent() const;
|
||||
void selectRowInternal (int rowNumber, bool dontScrollToShowThisRow,
|
||||
bool deselectOthersFirst, bool isMouseClick);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListBox)
|
||||
};
|
||||
|
||||
} // namespace juce
|
172
deps/juce/modules/juce_gui_basics/widgets/juce_ProgressBar.cpp
vendored
Normal file
172
deps/juce/modules/juce_gui_basics/widgets/juce_ProgressBar.cpp
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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()) {
|
||||
lastCallbackTime = Time::getMillisecondCounter();
|
||||
currentValue = progress;
|
||||
currentMessage = displayedMessage;
|
||||
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.004 * timeSinceLastCallback,
|
||||
newProgress);
|
||||
}
|
||||
|
||||
currentValue = newProgress;
|
||||
currentMessage = displayedMessage;
|
||||
repaint();
|
||||
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
handler->notifyAccessibilityEvent (AccessibilityEvent::valueChanged);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ProgressBar::createAccessibilityHandler()
|
||||
{
|
||||
class ProgressBarAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit ProgressBarAccessibilityHandler (ProgressBar& progressBarToWrap)
|
||||
: AccessibilityHandler (progressBarToWrap,
|
||||
AccessibilityRole::progressBar,
|
||||
AccessibilityActions{},
|
||||
AccessibilityHandler::Interfaces { std::make_unique<ValueInterface> (progressBarToWrap) }),
|
||||
progressBar (progressBarToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
String getHelp() const override { return progressBar.getTooltip(); }
|
||||
|
||||
private:
|
||||
class ValueInterface : public AccessibilityRangedNumericValueInterface
|
||||
{
|
||||
public:
|
||||
explicit ValueInterface (ProgressBar& progressBarToWrap)
|
||||
: progressBar (progressBarToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
bool isReadOnly() const override { return true; }
|
||||
void setValue (double) override { jassertfalse; }
|
||||
double getCurrentValue() const override { return progressBar.progress; }
|
||||
AccessibleValueRange getRange() const override { return { { 0.0, 1.0 }, 0.001 }; }
|
||||
|
||||
private:
|
||||
ProgressBar& progressBar;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueInterface)
|
||||
};
|
||||
|
||||
ProgressBar& progressBar;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgressBarAccessibilityHandler)
|
||||
};
|
||||
|
||||
return std::make_unique<ProgressBarAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
} // namespace juce
|
144
deps/juce/modules/juce_gui_basics/widgets/juce_ProgressBar.h
vendored
Normal file
144
deps/juce/modules/juce_gui_basics/widgets/juce_ProgressBar.h
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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() = default;
|
||||
|
||||
/** 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;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void timerCallback() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgressBar)
|
||||
};
|
||||
|
||||
} // namespace juce
|
1813
deps/juce/modules/juce_gui_basics/widgets/juce_Slider.cpp
vendored
Normal file
1813
deps/juce/modules/juce_gui_basics/widgets/juce_Slider.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1025
deps/juce/modules/juce_gui_basics/widgets/juce_Slider.h
vendored
Normal file
1025
deps/juce/modules/juce_gui_basics/widgets/juce_Slider.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
906
deps/juce/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp
vendored
Normal file
906
deps/juce/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp
vendored
Normal file
@ -0,0 +1,906 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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] != nullptr && 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))
|
||||
{
|
||||
const auto newWidthToUse = jlimit (ci->minimumWidth, ci->maximumWidth, newWidth);
|
||||
|
||||
if (ci->width != newWidthToUse)
|
||||
{
|
||||
auto numColumns = getNumColumns (true);
|
||||
|
||||
ci->lastDeliberateWidth = ci->width = newWidthToUse;
|
||||
|
||||
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.toString (XmlElement::TextFormat().singleLine().withoutHeader());
|
||||
}
|
||||
|
||||
void TableHeaderComponent::restoreFromString (const String& storedVersion)
|
||||
{
|
||||
if (auto storedXML = parseXMLIfTagMatches (storedVersion, "TABLELAYOUT"))
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
for (auto* col : storedXML->getChildIterator())
|
||||
{
|
||||
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* newListener)
|
||||
{
|
||||
listeners.addIfNotAlreadyThere (newListener);
|
||||
}
|
||||
|
||||
void TableHeaderComponent::removeListener (Listener* 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)
|
||||
{
|
||||
auto& lf = getLookAndFeel();
|
||||
|
||||
lf.drawTableHeaderBackground (g, *this);
|
||||
|
||||
auto 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)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> TableHeaderComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::tableHeader);
|
||||
}
|
||||
|
||||
} // namespace juce
|
459
deps/juce/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.h
vendored
Normal file
459
deps/juce/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.h
vendored
Normal file
@ -0,0 +1,459 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 modify 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 returned.
|
||||
*/
|
||||
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() = default;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~Listener() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 distinguish between
|
||||
the down and hover state. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
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;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
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
|
626
deps/juce/modules/juce_gui_basics/widgets/juce_TableListBox.cpp
vendored
Normal file
626
deps/juce/modules/juce_gui_basics/widgets/juce_TableListBox.cpp
vendored
Normal file
@ -0,0 +1,626 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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)
|
||||
{
|
||||
setFocusContainerType (FocusContainerType::focusContainer);
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
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;
|
||||
selectRowOnMouseUp = false;
|
||||
|
||||
if (isEnabled())
|
||||
{
|
||||
if (! (isSelected || isInDragToScrollViewport()) && owner.selectOnMouseDown)
|
||||
{
|
||||
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)];
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return std::make_unique<RowAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class RowAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
RowAccessibilityHandler (RowComp& rowComp)
|
||||
: AccessibilityHandler (rowComp,
|
||||
AccessibilityRole::row,
|
||||
getListRowAccessibilityActions (rowComp),
|
||||
{ std::make_unique<RowComponentCellInterface> (*this) }),
|
||||
rowComponent (rowComp)
|
||||
{
|
||||
}
|
||||
|
||||
String getTitle() const override
|
||||
{
|
||||
if (auto* m = rowComponent.owner.ListBox::model)
|
||||
return m->getNameForRow (rowComponent.row);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
String getHelp() const override { return rowComponent.getTooltip(); }
|
||||
|
||||
AccessibleState getCurrentState() const override
|
||||
{
|
||||
if (auto* m = rowComponent.owner.getModel())
|
||||
if (rowComponent.row >= m->getNumRows())
|
||||
return AccessibleState().withIgnored();
|
||||
|
||||
auto state = AccessibilityHandler::getCurrentState();
|
||||
|
||||
if (rowComponent.owner.multipleSelection)
|
||||
state = state.withMultiSelectable();
|
||||
else
|
||||
state = state.withSelectable();
|
||||
|
||||
if (rowComponent.isSelected)
|
||||
return state.withSelected();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
class RowComponentCellInterface : public AccessibilityCellInterface
|
||||
{
|
||||
public:
|
||||
RowComponentCellInterface (RowAccessibilityHandler& handler)
|
||||
: owner (handler)
|
||||
{
|
||||
}
|
||||
|
||||
int getColumnIndex() const override { return 0; }
|
||||
int getColumnSpan() const override { return 1; }
|
||||
|
||||
int getRowIndex() const override { return owner.rowComponent.row; }
|
||||
int getRowSpan() const override { return 1; }
|
||||
|
||||
int getDisclosureLevel() const override { return 0; }
|
||||
|
||||
const AccessibilityHandler* getTableHandler() const override { return owner.rowComponent.owner.getAccessibilityHandler(); }
|
||||
|
||||
private:
|
||||
RowAccessibilityHandler& owner;
|
||||
};
|
||||
|
||||
private:
|
||||
RowComp& rowComponent;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
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 (std::make_unique<Header> (*this));
|
||||
}
|
||||
|
||||
TableListBox::~TableListBox()
|
||||
{
|
||||
}
|
||||
|
||||
void TableListBox::setModel (TableListBoxModel* newModel)
|
||||
{
|
||||
if (model != newModel)
|
||||
{
|
||||
model = newModel;
|
||||
updateContent();
|
||||
}
|
||||
}
|
||||
|
||||
void TableListBox::setHeader (std::unique_ptr<TableHeaderComponent> newHeader)
|
||||
{
|
||||
if (newHeader == nullptr)
|
||||
{
|
||||
jassertfalse; // you need to supply a real header for a table!
|
||||
return;
|
||||
}
|
||||
|
||||
Rectangle<int> newBounds (100, 28);
|
||||
|
||||
if (header != nullptr)
|
||||
newBounds = header->getBounds();
|
||||
|
||||
header = newHeader.get();
|
||||
header->setBounds (newBounds);
|
||||
|
||||
setHeaderComponent (std::move (newHeader));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> TableListBox::createAccessibilityHandler()
|
||||
{
|
||||
class TableInterface : public AccessibilityTableInterface
|
||||
{
|
||||
public:
|
||||
explicit TableInterface (TableListBox& tableListBoxToWrap)
|
||||
: tableListBox (tableListBoxToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
int getNumRows() const override
|
||||
{
|
||||
if (auto* tableModel = tableListBox.getModel())
|
||||
return tableModel->getNumRows();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getNumColumns() const override
|
||||
{
|
||||
return tableListBox.getHeader().getNumColumns (false);
|
||||
}
|
||||
|
||||
const AccessibilityHandler* getCellHandler (int row, int column) const override
|
||||
{
|
||||
if (isPositiveAndBelow (row, getNumRows()))
|
||||
{
|
||||
if (isPositiveAndBelow (column, getNumColumns()))
|
||||
if (auto* cellComponent = tableListBox.getCellComponent (tableListBox.getHeader().getColumnIdOfIndex (column, false), row))
|
||||
return cellComponent->getAccessibilityHandler();
|
||||
|
||||
if (auto* rowComp = tableListBox.getComponentForRowNumber (row))
|
||||
return rowComp->getAccessibilityHandler();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
TableListBox& tableListBox;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableInterface)
|
||||
};
|
||||
|
||||
return std::make_unique<AccessibilityHandler> (*this,
|
||||
AccessibilityRole::list,
|
||||
AccessibilityActions{},
|
||||
AccessibilityHandler::Interfaces { std::make_unique<TableInterface> (*this) });
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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
|
346
deps/juce/modules/juce_gui_basics/widgets/juce_TableListBox.h
vendored
Normal file
346
deps/juce/modules/juce_gui_basics/widgets/juce_TableListBox.h
vendored
Normal file
@ -0,0 +1,346 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() = default;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~TableListBoxModel() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** 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);
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 (std::unique_ptr<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;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void updateColumnComponents() const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableListBox)
|
||||
};
|
||||
|
||||
} // namespace juce
|
2752
deps/juce/modules/juce_gui_basics/widgets/juce_TextEditor.cpp
vendored
Normal file
2752
deps/juce/modules/juce_gui_basics/widgets/juce_TextEditor.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
836
deps/juce/modules/juce_gui_basics/widgets/juce_TextEditor.h
vendored
Normal file
836
deps/juce/modules/juce_gui_basics/widgets/juce_TextEditor.h
vendored
Normal file
@ -0,0 +1,836 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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.
|
||||
|
||||
@param shouldBeMultiLine whether the editor should be multi- or single-line.
|
||||
@param shouldWordWrap sets whether long lines should be broken up in multi-line editors.
|
||||
If this is false and scrollbars are enabled a horizontal scrollbar
|
||||
will be shown.
|
||||
|
||||
@see isMultiLine, setReturnKeyStartsNewLine, setScrollbarsShown
|
||||
*/
|
||||
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 or disables scrollbars (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 scrollbars are 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);
|
||||
|
||||
/** Sets whether whitespace should be underlined when the editor font is underlined.
|
||||
|
||||
@see isWhitespaceUnderlined
|
||||
*/
|
||||
void setWhitespaceUnderlined (bool shouldUnderlineWhitespace) noexcept { underlineWhitespace = shouldUnderlineWhitespace; }
|
||||
|
||||
/** Returns true if whitespace is underlined for underlined fonts.
|
||||
|
||||
@see setWhitespaceIsUnderlined
|
||||
*/
|
||||
bool isWhitespaceUnderlined() const noexcept { return underlineWhitespace; }
|
||||
|
||||
//==============================================================================
|
||||
/** 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);
|
||||
|
||||
/** Returns the text that will be shown when the text editor is empty.
|
||||
|
||||
@see setTextToShowWhenEmpty
|
||||
*/
|
||||
String getTextToShowWhenEmpty() const noexcept { return textToShowWhenEmpty; }
|
||||
|
||||
//==============================================================================
|
||||
/** 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() = default;
|
||||
|
||||
/** 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) const;
|
||||
|
||||
/** 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);
|
||||
|
||||
/** Returns the gap at the top edge of the editor.
|
||||
@see setIndents
|
||||
*/
|
||||
int getTopIndent() const noexcept { return topIndent; }
|
||||
|
||||
/** Returns the gap at the left edge of the editor.
|
||||
@see setIndents
|
||||
*/
|
||||
int getLeftIndent() const noexcept { return leftIndent; }
|
||||
|
||||
/** Changes the size of border left around the edge of the component.
|
||||
@see getBorder
|
||||
*/
|
||||
void setBorder (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 justification of the text within the editor window. */
|
||||
void setJustification (Justification newJustification);
|
||||
|
||||
/** Returns the type of justification, as set in setJustification(). */
|
||||
Justification getJustificationType() const noexcept { return justification; }
|
||||
|
||||
/** 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; }
|
||||
|
||||
/** Returns the bounding box for a range of text in the editor. As the range may span
|
||||
multiple lines, this method returns a RectangleList.
|
||||
|
||||
The bounds are relative to the component's top-left and may extend beyond the bounds
|
||||
of the component if the text is long and word wrapping is disabled.
|
||||
*/
|
||||
RectangleList<int> getTextBounds (Range<int> textRange);
|
||||
|
||||
//==============================================================================
|
||||
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() = default;
|
||||
virtual ~InputFilter() = default;
|
||||
|
||||
/** 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() = default;
|
||||
|
||||
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::topLeft };
|
||||
|
||||
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;
|
||||
bool underlineWhitespace = 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;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
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) const;
|
||||
int findWordBreakAfter (int position) const;
|
||||
int findWordBreakBefore (int position) const;
|
||||
bool moveCaretWithTransaction (int newPos, bool selecting);
|
||||
void drawContent (Graphics&);
|
||||
void checkLayout();
|
||||
int getWordWrapWidth() const;
|
||||
int getMaximumTextWidth() const;
|
||||
int getMaximumTextHeight() const;
|
||||
void timerCallbackInt();
|
||||
void checkFocus();
|
||||
void repaintText (Range<int>);
|
||||
void scrollByLines (int deltaLines);
|
||||
bool undoOrRedo (bool shouldUndo);
|
||||
UndoManager* getUndoManager() noexcept;
|
||||
void setSelection (Range<int>) noexcept;
|
||||
Point<int> getTextOffset() const noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextEditor)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
818
deps/juce/modules/juce_gui_basics/widgets/juce_Toolbar.cpp
vendored
Normal file
818
deps/juce/modules/juce_gui_basics/widgets/juce_Toolbar.cpp
vendored
Normal file
@ -0,0 +1,818 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 ((float) 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 ((float) w * 0.1f, (float) h * (0.5f - thickness * 0.5f), (float) w * 0.8f, (float) h * thickness);
|
||||
else
|
||||
g.fillRect ((float) w * (0.5f - thickness * 0.5f), (float) h * 0.1f, (float) w * thickness, (float) 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 = (float) w * 0.5f;
|
||||
y1 = (float) h * 0.4f;
|
||||
x2 = x1;
|
||||
y2 = (float) indentX * 2.0f;
|
||||
|
||||
x3 = x1;
|
||||
y3 = (float) h * 0.6f;
|
||||
x4 = x1;
|
||||
y4 = (float) h - y2;
|
||||
|
||||
hw = (float) w * 0.15f;
|
||||
hl = (float) w * 0.2f;
|
||||
}
|
||||
else
|
||||
{
|
||||
x1 = (float) w * 0.4f;
|
||||
y1 = (float) h * 0.5f;
|
||||
x2 = (float) indentX * 2.0f;
|
||||
y2 = y1;
|
||||
|
||||
x3 = (float) w * 0.6f;
|
||||
y3 = y1;
|
||||
x4 = (float) w - x2;
|
||||
y4 = y1;
|
||||
|
||||
hw = (float) h * 0.15f;
|
||||
hl = (float) 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 (tc != nullptr && dynamic_cast<Spacer*> (tc) == nullptr && ! tc->isVisible())
|
||||
{
|
||||
oldIndexes.insert (0, i);
|
||||
addAndMakeVisible (tc, 0);
|
||||
}
|
||||
}
|
||||
|
||||
layout (400);
|
||||
}
|
||||
|
||||
~MissingItemsComponent() override
|
||||
{
|
||||
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()
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
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;
|
||||
auto comp = std::make_unique<MissingItemsComponent> (*this, getThickness());
|
||||
m.addCustomItem (1, std::move (comp));
|
||||
m.showMenuAsync (PopupMenu::Options().withTargetComponent (missingItemsButton.get()));
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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::lookAndFeelChanged()
|
||||
{
|
||||
missingItemsButton.reset (getLookAndFeel().createToolbarMissingItemsButton (*this));
|
||||
}
|
||||
|
||||
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() override
|
||||
{
|
||||
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;
|
||||
default: 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;
|
||||
default: 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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> Toolbar::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
334
deps/juce/modules/juce_gui_basics/widgets/juce_Toolbar.h
vendored
Normal file
334
deps/juce/modules/juce_gui_basics/widgets/juce_Toolbar.h
vendored
Normal file
@ -0,0 +1,334 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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() = default;
|
||||
|
||||
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 lookAndFeelChanged() 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;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
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
|
255
deps/juce/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp
vendored
Normal file
255
deps/juce/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp
vendored
Normal file
@ -0,0 +1,255 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ToolbarItemComponent::createAccessibilityHandler()
|
||||
{
|
||||
const auto shouldItemBeAccessible = (itemId != ToolbarItemFactory::separatorBarId
|
||||
&& itemId != ToolbarItemFactory::spacerId
|
||||
&& itemId != ToolbarItemFactory::flexibleSpacerId);
|
||||
|
||||
if (! shouldItemBeAccessible)
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<ButtonAccessibilityHandler> (*this, AccessibilityRole::button);
|
||||
}
|
||||
|
||||
} // namespace juce
|
209
deps/juce/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.h
vendored
Normal file
209
deps/juce/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.h
vendored
Normal file
@ -0,0 +1,209 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
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
|
109
deps/juce/modules/juce_gui_basics/widgets/juce_ToolbarItemFactory.h
vendored
Normal file
109
deps/juce/modules/juce_gui_basics/widgets/juce_ToolbarItemFactory.h
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
116
deps/juce/modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.cpp
vendored
Normal file
116
deps/juce/modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.cpp
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ToolbarItemPalette::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
78
deps/juce/modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.h
vendored
Normal file
78
deps/juce/modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.h
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() override;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
ToolbarItemFactory& factory;
|
||||
Toolbar& toolbar;
|
||||
Viewport viewport;
|
||||
OwnedArray<ToolbarItemComponent> items;
|
||||
|
||||
friend class Toolbar;
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void replaceComponent (ToolbarItemComponent&);
|
||||
void addComponent (int itemId, int index);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarItemPalette)
|
||||
};
|
||||
|
||||
} // namespace juce
|
2131
deps/juce/modules/juce_gui_basics/widgets/juce_TreeView.cpp
vendored
Normal file
2131
deps/juce/modules/juce_gui_basics/widgets/juce_TreeView.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
952
deps/juce/modules/juce_gui_basics/widgets/juce_TreeView.h
vendored
Normal file
952
deps/juce/modules/juce_gui_basics/widgets/juce_TreeView.h
vendored
Normal file
@ -0,0 +1,952 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 class Openness
|
||||
{
|
||||
opennessDefault,
|
||||
opennessClosed,
|
||||
opennessOpen
|
||||
};
|
||||
|
||||
/** 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 and will be deleted
|
||||
later when it goes off the screen or is no longer needed. 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 std::unique_ptr<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();
|
||||
|
||||
/** Use this to set the name for this item that will be read out by accessibility
|
||||
clients.
|
||||
|
||||
The default implementation will return the tooltip string from getTooltip()
|
||||
if it is not empty, otherwise it will return a description of the nested level
|
||||
and row number of the item.
|
||||
|
||||
@see AccessibilityHandler
|
||||
*/
|
||||
virtual String getAccessibilityName();
|
||||
|
||||
//==============================================================================
|
||||
/** 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.
|
||||
|
||||
Note that if all nodes of the tree are in their default state, then this may
|
||||
return a nullptr.
|
||||
|
||||
@see TreeView::getOpennessState, restoreOpennessState
|
||||
*/
|
||||
std::unique_ptr<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-items. */
|
||||
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 JUCE_API OpennessRestorer
|
||||
{
|
||||
public:
|
||||
OpennessRestorer (TreeViewItem&);
|
||||
~OpennessRestorer();
|
||||
|
||||
private:
|
||||
TreeViewItem& treeViewItem;
|
||||
std::unique_ptr<XmlElement> oldOpenness;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpennessRestorer)
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class TreeView;
|
||||
|
||||
void updatePositions (int);
|
||||
int getIndentX() const noexcept;
|
||||
void setOwnerView (TreeView*) noexcept;
|
||||
TreeViewItem* getTopLevelItem() noexcept;
|
||||
TreeViewItem* getDeepestOpenParentItem() noexcept;
|
||||
int getNumRows() const noexcept;
|
||||
TreeViewItem* getItemOnRow (int) noexcept;
|
||||
void deselectAllRecursively (TreeViewItem*);
|
||||
int countSelectedItemsRecursively (int) const noexcept;
|
||||
TreeViewItem* getSelectedItemWithIndex (int) noexcept;
|
||||
TreeViewItem* findItemFromIdentifierString (const String&);
|
||||
void restoreToDefaultOpenness();
|
||||
bool isFullyOpen() const noexcept;
|
||||
std::unique_ptr<XmlElement> getOpennessState (bool) const;
|
||||
bool removeSubItemFromList (int, bool);
|
||||
void removeAllSubItemsFromList();
|
||||
bool areLinesDrawn() const;
|
||||
void draw (Graphics&, int, bool);
|
||||
|
||||
//==============================================================================
|
||||
TreeView* ownerView = nullptr;
|
||||
TreeViewItem* parentItem = nullptr;
|
||||
OwnedArray<TreeViewItem> subItems;
|
||||
|
||||
Openness openness = Openness::opennessDefault;
|
||||
int y = 0, itemHeight = 0, totalHeight = 0, itemWidth = 0, totalWidth = 0, uid = 0;
|
||||
bool selected = false, redrawNeeded = true, drawLinesInside = false, drawLinesSet = false,
|
||||
drawsInLeftMargin = false, drawsInRightMargin = false;
|
||||
|
||||
//==============================================================================
|
||||
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 = {});
|
||||
|
||||
/** Destructor. */
|
||||
~TreeView() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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, depending 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 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;
|
||||
|
||||
/** Returns the component that currently represents a given TreeViewItem. */
|
||||
Component* getItemComponent (const TreeViewItem* item) 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.
|
||||
|
||||
@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
|
||||
*/
|
||||
std::unique_ptr<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 background of the odd numbered items. */
|
||||
evenItemsColourId = 0x1000505 /**< The colour to use to fill the background 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() = default;
|
||||
|
||||
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&) override;
|
||||
/** @internal */
|
||||
void fileDragEnter (const StringArray&, int, int) override;
|
||||
/** @internal */
|
||||
void fileDragMove (const StringArray&, int, int) override;
|
||||
/** @internal */
|
||||
void fileDragExit (const StringArray&) override;
|
||||
/** @internal */
|
||||
void filesDropped (const StringArray&, int, int) 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:
|
||||
friend class TreeViewItem;
|
||||
|
||||
class ItemComponent;
|
||||
class ContentComponent;
|
||||
class TreeViewport;
|
||||
class InsertPointHighlight;
|
||||
class TargetGroupHighlight;
|
||||
class TreeAccessibilityHandler;
|
||||
struct InsertPoint;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void itemsChanged() noexcept;
|
||||
void updateVisibleItems();
|
||||
void updateButtonUnderMouse (const MouseEvent&);
|
||||
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);
|
||||
|
||||
std::unique_ptr<TreeViewport> viewport;
|
||||
TreeViewItem* rootItem = nullptr;
|
||||
std::unique_ptr<InsertPointHighlight> dragInsertPointHighlight;
|
||||
std::unique_ptr<TargetGroupHighlight> dragTargetGroupHighlight;
|
||||
int indentSize = -1;
|
||||
bool defaultOpenness = false, rootItemVisible = true, multiSelectEnabled = false, openCloseButtonsVisible = true;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeView)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user