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:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View 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

View 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 &currentMenu; }
/** Returns the PopupMenu object associated with the ComboBox. */
const PopupMenu* getRootMenu() const noexcept { return &currentMenu; }
//==============================================================================
/**
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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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),

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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