mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-13 00:04:19 +00:00
Added Animated App template and examples
This commit is contained in:
parent
fefcf7aca6
commit
ff6520a89a
1141 changed files with 438491 additions and 94 deletions
|
|
@ -0,0 +1,632 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ComboBox::ItemInfo::ItemInfo (const String& nm, int iid, bool enabled, bool heading)
|
||||
: name (nm), itemId (iid), isEnabled (enabled), isHeading (heading)
|
||||
{
|
||||
}
|
||||
|
||||
bool ComboBox::ItemInfo::isSeparator() const noexcept
|
||||
{
|
||||
return name.isEmpty();
|
||||
}
|
||||
|
||||
bool ComboBox::ItemInfo::isRealItem() const noexcept
|
||||
{
|
||||
return ! (isHeading || name.isEmpty());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ComboBox::ComboBox (const String& name)
|
||||
: Component (name),
|
||||
lastCurrentId (0),
|
||||
isButtonDown (false),
|
||||
separatorPending (false),
|
||||
menuActive (false),
|
||||
scrollWheelEnabled (false),
|
||||
mouseWheelAccumulator (0),
|
||||
noChoicesMessage (TRANS("(no choices)"))
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
lookAndFeelChanged();
|
||||
currentId.addListener (this);
|
||||
}
|
||||
|
||||
ComboBox::~ComboBox()
|
||||
{
|
||||
currentId.removeListener (this);
|
||||
|
||||
if (menuActive)
|
||||
PopupMenu::dismissAllActiveMenus();
|
||||
|
||||
label = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::setEditableText (const bool isEditable)
|
||||
{
|
||||
if (label->isEditableOnSingleClick() != isEditable || label->isEditableOnDoubleClick() != isEditable)
|
||||
{
|
||||
label->setEditable (isEditable, isEditable, false);
|
||||
setWantsKeyboardFocus (! isEditable);
|
||||
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, const int newItemId)
|
||||
{
|
||||
// you can't add empty strings to the list..
|
||||
jassert (newItemText.isNotEmpty());
|
||||
|
||||
// IDs must be non-zero, as zero is used to indicate a lack of selecion.
|
||||
jassert (newItemId != 0);
|
||||
|
||||
// you shouldn't use duplicate item IDs!
|
||||
jassert (getItemForId (newItemId) == nullptr);
|
||||
|
||||
if (newItemText.isNotEmpty() && newItemId != 0)
|
||||
{
|
||||
if (separatorPending)
|
||||
{
|
||||
separatorPending = false;
|
||||
items.add (new ItemInfo (String::empty, 0, false, false));
|
||||
}
|
||||
|
||||
items.add (new ItemInfo (newItemText, newItemId, true, false));
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::addItemList (const StringArray& itemsToAdd, const int firstItemIdOffset)
|
||||
{
|
||||
for (int i = 0; i < itemsToAdd.size(); ++i)
|
||||
addItem (itemsToAdd[i], i + firstItemIdOffset);
|
||||
}
|
||||
|
||||
void ComboBox::addSeparator()
|
||||
{
|
||||
separatorPending = (items.size() > 0);
|
||||
}
|
||||
|
||||
void ComboBox::addSectionHeading (const String& headingName)
|
||||
{
|
||||
// you can't add empty strings to the list..
|
||||
jassert (headingName.isNotEmpty());
|
||||
|
||||
if (headingName.isNotEmpty())
|
||||
{
|
||||
if (separatorPending)
|
||||
{
|
||||
separatorPending = false;
|
||||
items.add (new ItemInfo (String::empty, 0, false, false));
|
||||
}
|
||||
|
||||
items.add (new ItemInfo (headingName, 0, true, true));
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::setItemEnabled (const int itemId, const bool shouldBeEnabled)
|
||||
{
|
||||
if (ItemInfo* const item = getItemForId (itemId))
|
||||
item->isEnabled = shouldBeEnabled;
|
||||
}
|
||||
|
||||
bool ComboBox::isItemEnabled (int itemId) const noexcept
|
||||
{
|
||||
const ItemInfo* const item = getItemForId (itemId);
|
||||
return item != nullptr && item->isEnabled;
|
||||
}
|
||||
|
||||
void ComboBox::changeItemText (const int itemId, const String& newText)
|
||||
{
|
||||
if (ItemInfo* const item = getItemForId (itemId))
|
||||
item->name = newText;
|
||||
else
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
void ComboBox::clear (const NotificationType notification)
|
||||
{
|
||||
items.clear();
|
||||
separatorPending = false;
|
||||
|
||||
if (! label->isEditable())
|
||||
setSelectedItemIndex (-1, notification);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ComboBox::ItemInfo* ComboBox::getItemForId (const int itemId) const noexcept
|
||||
{
|
||||
if (itemId != 0)
|
||||
{
|
||||
for (int i = items.size(); --i >= 0;)
|
||||
if (items.getUnchecked(i)->itemId == itemId)
|
||||
return items.getUnchecked(i);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ComboBox::ItemInfo* ComboBox::getItemForIndex (const int index) const noexcept
|
||||
{
|
||||
for (int n = 0, i = 0; i < items.size(); ++i)
|
||||
{
|
||||
ItemInfo* const item = items.getUnchecked(i);
|
||||
|
||||
if (item->isRealItem())
|
||||
if (n++ == index)
|
||||
return item;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ComboBox::getNumItems() const noexcept
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
for (int i = items.size(); --i >= 0;)
|
||||
if (items.getUnchecked(i)->isRealItem())
|
||||
++n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
String ComboBox::getItemText (const int index) const
|
||||
{
|
||||
if (const ItemInfo* const item = getItemForIndex (index))
|
||||
return item->name;
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
int ComboBox::getItemId (const int index) const noexcept
|
||||
{
|
||||
if (const ItemInfo* const item = getItemForIndex (index))
|
||||
return item->itemId;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ComboBox::indexOfItemId (const int itemId) const noexcept
|
||||
{
|
||||
for (int n = 0, i = 0; i < items.size(); ++i)
|
||||
{
|
||||
const ItemInfo* const item = items.getUnchecked(i);
|
||||
|
||||
if (item->isRealItem())
|
||||
{
|
||||
if (item->itemId == itemId)
|
||||
return n;
|
||||
|
||||
++n;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int ComboBox::getSelectedItemIndex() const
|
||||
{
|
||||
int 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
|
||||
{
|
||||
const ItemInfo* const item = getItemForId (currentId.getValue());
|
||||
|
||||
return (item != nullptr && getText() == item->name) ? item->itemId : 0;
|
||||
}
|
||||
|
||||
void ComboBox::setSelectedId (const int newItemId, const NotificationType notification)
|
||||
{
|
||||
const ItemInfo* const item = getItemForId (newItemId);
|
||||
const String newItemText (item != nullptr ? item->name : String::empty);
|
||||
|
||||
if (lastCurrentId != newItemId || label->getText() != newItemText)
|
||||
{
|
||||
label->setText (newItemText, dontSendNotification);
|
||||
lastCurrentId = newItemId;
|
||||
currentId = newItemId;
|
||||
|
||||
repaint(); // for the benefit of the 'none selected' text
|
||||
|
||||
sendChange (notification);
|
||||
}
|
||||
}
|
||||
|
||||
bool ComboBox::selectIfEnabled (const int index)
|
||||
{
|
||||
if (const ItemInfo* const 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 (int i = items.size(); --i >= 0;)
|
||||
{
|
||||
const ItemInfo* const item = items.getUnchecked(i);
|
||||
|
||||
if (item->isRealItem()
|
||||
&& item->name == newText)
|
||||
{
|
||||
setSelectedId (item->itemId, notification);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lastCurrentId = 0;
|
||||
currentId = 0;
|
||||
repaint();
|
||||
|
||||
if (label->getText() != newText)
|
||||
{
|
||||
label->setText (newText, dontSendNotification);
|
||||
sendChange (notification);
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::showEditor()
|
||||
{
|
||||
jassert (isTextEditable()); // you probably shouldn't do this to a non-editable combo box?
|
||||
|
||||
label->showEditor();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::setTextWhenNothingSelected (const String& newMessage)
|
||||
{
|
||||
if (textWhenNothingSelected != newMessage)
|
||||
{
|
||||
textWhenNothingSelected = newMessage;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
String ComboBox::getTextWhenNothingSelected() const
|
||||
{
|
||||
return textWhenNothingSelected;
|
||||
}
|
||||
|
||||
void ComboBox::setTextWhenNoChoicesAvailable (const String& newMessage)
|
||||
{
|
||||
noChoicesMessage = newMessage;
|
||||
}
|
||||
|
||||
String ComboBox::getTextWhenNoChoicesAvailable() const
|
||||
{
|
||||
return noChoicesMessage;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawComboBox (g, getWidth(), getHeight(), isButtonDown,
|
||||
label->getRight(), 0, getWidth() - label->getRight(), getHeight(),
|
||||
*this);
|
||||
|
||||
if (textWhenNothingSelected.isNotEmpty()
|
||||
&& label->getText().isEmpty()
|
||||
&& ! label->isBeingEdited())
|
||||
{
|
||||
g.setColour (findColour (textColourId).withMultipliedAlpha (0.5f));
|
||||
g.setFont (label->getFont());
|
||||
g.drawFittedText (textWhenNothingSelected, label->getBounds().reduced (2, 1),
|
||||
label->getJustificationType(),
|
||||
jmax (1, (int) (label->getHeight() / label->getFont().getHeight())));
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::resized()
|
||||
{
|
||||
if (getHeight() > 0 && getWidth() > 0)
|
||||
getLookAndFeel().positionComboBoxText (*this, *label);
|
||||
}
|
||||
|
||||
void ComboBox::enablementChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ComboBox::colourChanged()
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void ComboBox::parentHierarchyChanged()
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void ComboBox::lookAndFeelChanged()
|
||||
{
|
||||
repaint();
|
||||
|
||||
{
|
||||
ScopedPointer<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);
|
||||
}
|
||||
|
||||
label = newLabel;
|
||||
}
|
||||
|
||||
addAndMakeVisible (label);
|
||||
setWantsKeyboardFocus (! label->isEditable());
|
||||
|
||||
label->addListener (this);
|
||||
label->addMouseListener (this, false);
|
||||
|
||||
label->setColour (Label::backgroundColourId, Colours::transparentBlack);
|
||||
label->setColour (Label::textColourId, findColour (ComboBox::textColourId));
|
||||
|
||||
label->setColour (TextEditor::textColourId, findColour (ComboBox::textColourId));
|
||||
label->setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
|
||||
label->setColour (TextEditor::highlightColourId, findColour (TextEditor::highlightColourId));
|
||||
label->setColour (TextEditor::outlineColourId, Colours::transparentBlack);
|
||||
|
||||
resized();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ComboBox::keyPressed (const KeyPress& key)
|
||||
{
|
||||
if (key == KeyPress::upKey || key == KeyPress::leftKey)
|
||||
{
|
||||
nudgeSelectedItem (-1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == KeyPress::downKey || key == KeyPress::rightKey)
|
||||
{
|
||||
nudgeSelectedItem (1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == KeyPress::returnKey)
|
||||
{
|
||||
showPopup();
|
||||
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::labelTextChanged (Label*)
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::popupMenuFinishedCallback (int result, ComboBox* box)
|
||||
{
|
||||
if (box != nullptr)
|
||||
{
|
||||
box->menuActive = false;
|
||||
|
||||
if (result != 0)
|
||||
box->setSelectedId (result);
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::showPopup()
|
||||
{
|
||||
if (! menuActive)
|
||||
{
|
||||
const int selectedId = getSelectedId();
|
||||
|
||||
PopupMenu menu;
|
||||
menu.setLookAndFeel (&getLookAndFeel());
|
||||
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
const ItemInfo* const item = items.getUnchecked(i);
|
||||
|
||||
if (item->isSeparator())
|
||||
menu.addSeparator();
|
||||
else if (item->isHeading)
|
||||
menu.addSectionHeader (item->name);
|
||||
else
|
||||
menu.addItem (item->itemId, item->name,
|
||||
item->isEnabled, item->itemId == selectedId);
|
||||
}
|
||||
|
||||
if (items.size() == 0)
|
||||
menu.addItem (1, noChoicesMessage, false);
|
||||
|
||||
menuActive = true;
|
||||
|
||||
menu.showMenuAsync (PopupMenu::Options().withTargetComponent (this)
|
||||
.withItemThatMustBeVisible (selectedId)
|
||||
.withMinimumWidth (getWidth())
|
||||
.withMaximumNumColumns (1)
|
||||
.withStandardItemHeight (jlimit (12, 24, getHeight())),
|
||||
ModalCallbackFunction::forComponent (popupMenuFinishedCallback, this));
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
beginDragAutoRepeat (300);
|
||||
|
||||
isButtonDown = isEnabled() && ! e.mods.isPopupMenu();
|
||||
|
||||
if (isButtonDown && (e.eventComponent == this || ! label->isEditable()))
|
||||
showPopup();
|
||||
}
|
||||
|
||||
void ComboBox::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
beginDragAutoRepeat (50);
|
||||
|
||||
if (isButtonDown && ! e.mouseWasClicked())
|
||||
showPopup();
|
||||
}
|
||||
|
||||
void ComboBox::mouseUp (const MouseEvent& e2)
|
||||
{
|
||||
if (isButtonDown)
|
||||
{
|
||||
isButtonDown = false;
|
||||
repaint();
|
||||
|
||||
const MouseEvent e (e2.getEventRelativeTo (this));
|
||||
|
||||
if (reallyContains (e.getPosition(), true)
|
||||
&& (e2.eventComponent == this || ! label->isEditable()))
|
||||
{
|
||||
showPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
|
||||
{
|
||||
if (! menuActive && scrollWheelEnabled && e.eventComponent == this && wheel.deltaY != 0)
|
||||
{
|
||||
const int oldPos = (int) mouseWheelAccumulator;
|
||||
mouseWheelAccumulator += wheel.deltaY * 5.0f;
|
||||
const int delta = oldPos - (int) mouseWheelAccumulator;
|
||||
|
||||
if (delta != 0)
|
||||
nudgeSelectedItem (delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
Component::mouseWheelMove (e, wheel);
|
||||
}
|
||||
}
|
||||
|
||||
void ComboBox::setScrollWheelEnabled (bool enabled) noexcept
|
||||
{
|
||||
scrollWheelEnabled = enabled;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComboBox::addListener (ComboBoxListener* listener) { listeners.add (listener); }
|
||||
void ComboBox::removeListener (ComboBoxListener* listener) { listeners.remove (listener); }
|
||||
|
||||
void ComboBox::handleAsyncUpdate()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, &ComboBoxListener::comboBoxChanged, this); // (can't use ComboBox::Listener due to idiotic VC2005 bug)
|
||||
}
|
||||
|
||||
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); }
|
||||
|
|
@ -0,0 +1,443 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_COMBOBOX_H_INCLUDED
|
||||
#define JUCE_COMBOBOX_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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 register a ComboBox::Listener to receive callbacks.
|
||||
|
||||
@see ComboBox::Listener
|
||||
*/
|
||||
class JUCE_API ComboBox : public Component,
|
||||
public SettableTooltipClient,
|
||||
public LabelListener, // (can't use Label::Listener due to idiotic VC2005 bug)
|
||||
public ValueListener,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a combo-box.
|
||||
|
||||
On construction, the text field will be empty, so you should call the
|
||||
setSelectedId() or setText() method to choose the initial value before
|
||||
displaying it.
|
||||
|
||||
@param componentName the name to set for the component (see Component::setName())
|
||||
*/
|
||||
explicit ComboBox (const String& componentName = String::empty);
|
||||
|
||||
/** Destructor. */
|
||||
~ComboBox();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets whether the test in the combo-box is editable.
|
||||
|
||||
The default state for a new ComboBox is non-editable, and can only be changed
|
||||
by choosing from the drop-down list.
|
||||
*/
|
||||
void setEditableText (bool isEditable);
|
||||
|
||||
/** Returns true if the text is directly editable.
|
||||
@see setEditableText
|
||||
*/
|
||||
bool isTextEditable() const noexcept;
|
||||
|
||||
/** Sets the style of justification to be used for positioning the text.
|
||||
|
||||
The default is Justification::centredLeft. The text is displayed using a
|
||||
Label component inside the ComboBox.
|
||||
*/
|
||||
void setJustificationType (Justification justification);
|
||||
|
||||
/** Returns the current justification for the text box.
|
||||
@see setJustificationType
|
||||
*/
|
||||
Justification getJustificationType() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Adds an item to be shown in the drop-down list.
|
||||
|
||||
@param newItemText the text of the item to show in the list
|
||||
@param newItemId an associated ID number that can be set or retrieved - see
|
||||
getSelectedId() and setSelectedId(). Note that this value can not
|
||||
be 0!
|
||||
@see setItemEnabled, addSeparator, addSectionHeading, getNumItems, getItemText, getItemId
|
||||
*/
|
||||
void addItem (const String& newItemText, int newItemId);
|
||||
|
||||
/** Adds an array of items to the drop-down list.
|
||||
The item ID of each item will be its index in the StringArray + firstItemIdOffset.
|
||||
*/
|
||||
void addItemList (const StringArray& items, int firstItemIdOffset);
|
||||
|
||||
/** Adds a separator line to the drop-down list.
|
||||
|
||||
This is like adding a separator to a popup menu. See PopupMenu::addSeparator().
|
||||
*/
|
||||
void addSeparator();
|
||||
|
||||
/** Adds a heading to the drop-down list, so that you can group the items into
|
||||
different sections.
|
||||
|
||||
The headings are indented slightly differently to set them apart from the
|
||||
items on the list, and obviously can't be selected. You might want to add
|
||||
separators between your sections too.
|
||||
|
||||
@see addItem, addSeparator
|
||||
*/
|
||||
void addSectionHeading (const String& headingName);
|
||||
|
||||
/** This allows items in the drop-down list to be selectively disabled.
|
||||
|
||||
When you add an item, it's enabled by default, but you can call this
|
||||
method to change its status.
|
||||
|
||||
If you disable an item which is already selected, this won't change the
|
||||
current selection - it just stops the user choosing that item from the list.
|
||||
*/
|
||||
void setItemEnabled (int itemId, bool shouldBeEnabled);
|
||||
|
||||
/** Returns true if the given item is enabled. */
|
||||
bool isItemEnabled (int itemId) const noexcept;
|
||||
|
||||
/** Changes the text for an existing item.
|
||||
*/
|
||||
void changeItemText (int itemId, const String& newText);
|
||||
|
||||
/** Removes all the items from the drop-down list.
|
||||
|
||||
If this call causes the content to be cleared, and a change-message
|
||||
will be broadcast according to the notification parameter.
|
||||
|
||||
@see addItem, getNumItems
|
||||
*/
|
||||
void clear (NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Returns the number of items that have been added to the list.
|
||||
|
||||
Note that this doesn't include headers or separators.
|
||||
*/
|
||||
int getNumItems() const noexcept;
|
||||
|
||||
/** Returns the text for one of the items in the list.
|
||||
Note that this doesn't include headers or separators.
|
||||
@param index the item's index from 0 to (getNumItems() - 1)
|
||||
*/
|
||||
String getItemText (int index) const;
|
||||
|
||||
/** Returns the ID for one of the items in the list.
|
||||
Note that this doesn't include headers or separators.
|
||||
@param index the item's index from 0 to (getNumItems() - 1)
|
||||
*/
|
||||
int getItemId (int index) const noexcept;
|
||||
|
||||
/** Returns the index in the list of a particular item ID.
|
||||
If no such ID is found, this will return -1.
|
||||
*/
|
||||
int indexOfItemId (int itemId) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the ID of the item that's currently shown in the box.
|
||||
|
||||
If no item is selected, or if the text is editable and the user
|
||||
has entered something which isn't one of the items in the list, then
|
||||
this will return 0.
|
||||
|
||||
@see setSelectedId, getSelectedItemIndex, getText
|
||||
*/
|
||||
int getSelectedId() const noexcept;
|
||||
|
||||
/** Returns a Value object that can be used to get or set the selected item's ID.
|
||||
|
||||
You can call Value::referTo() on this object to make the combo box control
|
||||
another Value object.
|
||||
*/
|
||||
Value& getSelectedIdAsValue() { return currentId; }
|
||||
|
||||
/** Sets one of the items to be the current selection.
|
||||
|
||||
This will set the ComboBox's text to that of the item that matches
|
||||
this ID.
|
||||
|
||||
@param newItemId the new item to select
|
||||
@param notification determines the type of change notification that will
|
||||
be sent to listeners if the value changes
|
||||
@see getSelectedId, setSelectedItemIndex, setText
|
||||
*/
|
||||
void setSelectedId (int newItemId,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the index of the item that's currently shown in the box.
|
||||
|
||||
If no item is selected, or if the text is editable and the user
|
||||
has entered something which isn't one of the items in the list, then
|
||||
this will return -1.
|
||||
|
||||
@see setSelectedItemIndex, getSelectedId, getText
|
||||
*/
|
||||
int getSelectedItemIndex() const;
|
||||
|
||||
/** Sets one of the items to be the current selection.
|
||||
|
||||
This will set the ComboBox's text to that of the item at the given
|
||||
index in the list.
|
||||
|
||||
@param newItemIndex the new item to select
|
||||
@param notification determines the type of change notification that will
|
||||
be sent to listeners if the value changes
|
||||
@see getSelectedItemIndex, setSelectedId, setText
|
||||
*/
|
||||
void setSelectedItemIndex (int newItemIndex,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the text that is currently shown in the combo-box's text field.
|
||||
|
||||
If the ComboBox has editable text, then this text may have been edited
|
||||
by the user; otherwise it will be one of the items from the list, or
|
||||
possibly an empty string if nothing was selected.
|
||||
|
||||
@see setText, getSelectedId, getSelectedItemIndex
|
||||
*/
|
||||
String getText() const;
|
||||
|
||||
/** Sets the contents of the combo-box's text field.
|
||||
|
||||
The text passed-in will be set as the current text regardless of whether
|
||||
it is one of the items in the list. If the current text isn't one of the
|
||||
items, then getSelectedId() will return -1, otherwise it wil return
|
||||
the approriate ID.
|
||||
|
||||
@param newText the text to select
|
||||
@param notification determines the type of change notification that will
|
||||
be sent to listeners if the text changes
|
||||
@see getText
|
||||
*/
|
||||
void setText (const String& newText,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Programmatically opens the text editor to allow the user to edit the current item.
|
||||
|
||||
This is the same effect as when the box is clicked-on.
|
||||
@see Label::showEditor();
|
||||
*/
|
||||
void showEditor();
|
||||
|
||||
/** Pops up the combo box's list.
|
||||
This is virtual so that you can override it with your own custom popup
|
||||
mechanism if you need some really unusual behaviour.
|
||||
*/
|
||||
virtual void showPopup();
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class for receiving events from a ComboBox.
|
||||
|
||||
You can register a ComboBox::Listener with a ComboBox using the ComboBox::addListener()
|
||||
method, and it will be called when the selected item in the box changes.
|
||||
|
||||
@see ComboBox::addListener, ComboBox::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called when a ComboBox has its selected item changed. */
|
||||
virtual void comboBoxChanged (ComboBox* comboBoxThatHasChanged) = 0;
|
||||
};
|
||||
|
||||
/** Registers a listener that will be called when the box's content changes. */
|
||||
void addListener (Listener* listener);
|
||||
|
||||
/** Deregisters a previously-registered listener. */
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** 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
|
||||
|
||||
@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 */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
ComboBox functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void drawComboBox (Graphics&, int width, int height, bool isButtonDown,
|
||||
int buttonX, int buttonY, int buttonW, int buttonH,
|
||||
ComboBox&) = 0;
|
||||
|
||||
virtual Font getComboBoxFont (ComboBox&) = 0;
|
||||
|
||||
virtual Label* createComboBoxTextBox (ComboBox&) = 0;
|
||||
|
||||
virtual void positionComboBoxText (ComboBox&, Label& labelToPosition) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void labelTextChanged (Label*) override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
void focusGained (Component::FocusChangeType) override;
|
||||
/** @internal */
|
||||
void focusLost (Component::FocusChangeType) override;
|
||||
/** @internal */
|
||||
void handleAsyncUpdate() override;
|
||||
/** @internal */
|
||||
String getTooltip() override { return label->getTooltip(); }
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
bool keyStateChanged (bool) override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void valueChanged (Value&) override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
|
||||
// These methods' bool parameters have changed: see their new method signatures.
|
||||
JUCE_DEPRECATED (void clear (bool));
|
||||
JUCE_DEPRECATED (void setSelectedId (int, bool));
|
||||
JUCE_DEPRECATED (void setSelectedItemIndex (int, bool));
|
||||
JUCE_DEPRECATED (void setText (const String&, bool));
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct ItemInfo
|
||||
{
|
||||
ItemInfo (const String&, int itemId, bool isEnabled, bool isHeading);
|
||||
bool isSeparator() const noexcept;
|
||||
bool isRealItem() const noexcept;
|
||||
|
||||
String name;
|
||||
int itemId;
|
||||
bool isEnabled : 1, isHeading : 1;
|
||||
};
|
||||
|
||||
OwnedArray<ItemInfo> items;
|
||||
Value currentId;
|
||||
int lastCurrentId;
|
||||
bool isButtonDown, separatorPending, menuActive, scrollWheelEnabled;
|
||||
float mouseWheelAccumulator;
|
||||
ListenerList <Listener> listeners;
|
||||
ScopedPointer<Label> label;
|
||||
String textWhenNothingSelected, noChoicesMessage;
|
||||
|
||||
ItemInfo* getItemForId (int) const noexcept;
|
||||
ItemInfo* getItemForIndex (int) const noexcept;
|
||||
bool selectIfEnabled (int index);
|
||||
bool nudgeSelectedItem (int delta);
|
||||
void sendChange (NotificationType);
|
||||
static void popupMenuFinishedCallback (int, ComboBox*);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBox)
|
||||
};
|
||||
|
||||
/** This typedef is just for compatibility with old code - newer code should use the ComboBox::Listener class directly. */
|
||||
typedef ComboBox::Listener ComboBoxListener;
|
||||
|
||||
#endif // JUCE_COMBOBOX_H_INCLUDED
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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.drawImageWithin (image, 0, 0, getWidth(), getHeight(), placement, false);
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_IMAGECOMPONENT_H_INCLUDED
|
||||
#define JUCE_IMAGECOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that simply displays an image.
|
||||
|
||||
Use setImage to give it an image, and it'll display it - simple as that!
|
||||
*/
|
||||
class JUCE_API ImageComponent : public Component,
|
||||
public SettableTooltipClient
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ImageComponent. */
|
||||
ImageComponent (const String& componentName = String::empty);
|
||||
|
||||
/** Destructor. */
|
||||
~ImageComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the image that should be displayed. */
|
||||
void setImage (const Image& newImage);
|
||||
|
||||
/** Sets the image that should be displayed, and its placement within the component. */
|
||||
void setImage (const Image& newImage,
|
||||
RectanglePlacement placementToUse);
|
||||
|
||||
/** Returns the current image. */
|
||||
const Image& getImage() const;
|
||||
|
||||
/** Sets the method of positioning that will be used to fit the image within the component's bounds.
|
||||
By default the positioning is centred, and will fit the image inside the component's bounds
|
||||
whilst keeping its aspect ratio correct, but you can change it to whatever layout you need.
|
||||
*/
|
||||
void setImagePlacement (RectanglePlacement newPlacement);
|
||||
|
||||
/** Returns the current image placement. */
|
||||
RectanglePlacement getImagePlacement() const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
|
||||
private:
|
||||
Image image;
|
||||
RectanglePlacement placement;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImageComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_IMAGECOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
Label::Label (const String& name, const String& labelText)
|
||||
: Component (name),
|
||||
textValue (labelText),
|
||||
lastTextValue (labelText),
|
||||
font (15.0f),
|
||||
justification (Justification::centredLeft),
|
||||
border (1, 5, 1, 5),
|
||||
minimumHorizontalScale (0.7f),
|
||||
editSingleClick (false),
|
||||
editDoubleClick (false),
|
||||
lossOfFocusDiscardsChanges (false)
|
||||
{
|
||||
setColour (TextEditor::textColourId, Colours::black);
|
||||
setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
|
||||
setColour (TextEditor::outlineColourId, Colours::transparentBlack);
|
||||
|
||||
textValue.addListener (this);
|
||||
}
|
||||
|
||||
Label::~Label()
|
||||
{
|
||||
textValue.removeListener (this);
|
||||
|
||||
if (ownerComponent != nullptr)
|
||||
ownerComponent->removeComponentListener (this);
|
||||
|
||||
editor = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Label::setText (const String& newText,
|
||||
const 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 (const 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 (const bool editOnSingleClick,
|
||||
const bool editOnDoubleClick,
|
||||
const bool lossOfFocusDiscardsChanges_)
|
||||
{
|
||||
editSingleClick = editOnSingleClick;
|
||||
editDoubleClick = editOnDoubleClick;
|
||||
lossOfFocusDiscardsChanges = lossOfFocusDiscardsChanges_;
|
||||
|
||||
setWantsKeyboardFocus (editOnSingleClick || editOnDoubleClick);
|
||||
setFocusContainer (editOnSingleClick || editOnDoubleClick);
|
||||
}
|
||||
|
||||
void Label::setJustificationType (Justification newJustification)
|
||||
{
|
||||
if (justification != newJustification)
|
||||
{
|
||||
justification = newJustification;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::setBorderSize (BorderSize<int> newBorder)
|
||||
{
|
||||
if (border != newBorder)
|
||||
{
|
||||
border = newBorder;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Component* Label::getAttachedComponent() const
|
||||
{
|
||||
return static_cast<Component*> (ownerComponent);
|
||||
}
|
||||
|
||||
void Label::attachToComponent (Component* owner, const bool onLeft)
|
||||
{
|
||||
if (ownerComponent != nullptr)
|
||||
ownerComponent->removeComponentListener (this);
|
||||
|
||||
ownerComponent = owner;
|
||||
|
||||
leftOfOwnerComp = onLeft;
|
||||
|
||||
if (ownerComponent != nullptr)
|
||||
{
|
||||
setVisible (owner->isVisible());
|
||||
ownerComponent->addComponentListener (this);
|
||||
componentParentHierarchyChanged (*ownerComponent);
|
||||
componentMovedOrResized (*ownerComponent, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Label::componentMovedOrResized (Component& component, bool /*wasMoved*/, bool /*wasResized*/)
|
||||
{
|
||||
const Font f (getLookAndFeel().getLabelFont (*this));
|
||||
|
||||
if (leftOfOwnerComp)
|
||||
{
|
||||
setSize (jmin (f.getStringWidth (textValue.toString()) + 8, component.getX()),
|
||||
component.getHeight());
|
||||
|
||||
setTopRightPosition (component.getX(), component.getY());
|
||||
}
|
||||
else
|
||||
{
|
||||
setSize (component.getWidth(),
|
||||
8 + roundToInt (f.getHeight()));
|
||||
|
||||
setTopLeftPosition (component.getX(), component.getY() - getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
void Label::componentParentHierarchyChanged (Component& component)
|
||||
{
|
||||
if (Component* 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, &LabelListener::editorShown, this, *textEditor);
|
||||
}
|
||||
|
||||
void Label::editorAboutToBeHidden (TextEditor*)
|
||||
{
|
||||
if (ComponentPeer* const peer = getPeer())
|
||||
peer->dismissPendingTextInput();
|
||||
}
|
||||
|
||||
void Label::showEditor()
|
||||
{
|
||||
if (editor == nullptr)
|
||||
{
|
||||
addAndMakeVisible (editor = createEditorComponent());
|
||||
editor->setText (getText(), false);
|
||||
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);
|
||||
|
||||
enterModalState (false);
|
||||
editor->grabKeyboardFocus();
|
||||
}
|
||||
}
|
||||
|
||||
bool Label::updateFromTextEditorContents (TextEditor& ed)
|
||||
{
|
||||
const String 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 (const bool discardCurrentEditorContents)
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
WeakReference<Component> deletionChecker (this);
|
||||
|
||||
ScopedPointer<TextEditor> outgoingEditor (editor);
|
||||
|
||||
editorAboutToBeHidden (outgoingEditor);
|
||||
|
||||
const bool changed = (! discardCurrentEditorContents)
|
||||
&& updateFromTextEditorContents (*outgoingEditor);
|
||||
outgoingEditor = 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()
|
||||
{
|
||||
TextEditor* const 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::outlineColourId);
|
||||
|
||||
return ed;
|
||||
}
|
||||
|
||||
TextEditor* Label::getCurrentTextEditor() const noexcept
|
||||
{
|
||||
return editor;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Label::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawLabel (g, *this);
|
||||
}
|
||||
|
||||
void Label::mouseUp (const MouseEvent& e)
|
||||
{
|
||||
if (editSingleClick
|
||||
&& e.mouseWasClicked()
|
||||
&& contains (e.getPosition())
|
||||
&& ! e.mods.isPopupMenu())
|
||||
{
|
||||
showEditor();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::mouseDoubleClick (const MouseEvent& e)
|
||||
{
|
||||
if (editDoubleClick && ! e.mods.isPopupMenu())
|
||||
showEditor();
|
||||
}
|
||||
|
||||
void Label::resized()
|
||||
{
|
||||
if (editor != nullptr)
|
||||
editor->setBounds (getLocalBounds());
|
||||
}
|
||||
|
||||
void Label::focusGained (FocusChangeType cause)
|
||||
{
|
||||
if (editSingleClick && cause == focusChangedByTabKey)
|
||||
showEditor();
|
||||
}
|
||||
|
||||
void Label::enablementChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Label::colourChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Label::setMinimumHorizontalScale (const float newScale)
|
||||
{
|
||||
if (minimumHorizontalScale != newScale)
|
||||
{
|
||||
minimumHorizontalScale = newScale;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// We'll use a custom focus traverser here to make sure focus goes from the
|
||||
// text editor to another component rather than back to the label itself.
|
||||
class LabelKeyboardFocusTraverser : public KeyboardFocusTraverser
|
||||
{
|
||||
public:
|
||||
LabelKeyboardFocusTraverser() {}
|
||||
|
||||
Component* getNextComponent (Component* c) { return KeyboardFocusTraverser::getNextComponent (getComp (c)); }
|
||||
Component* getPreviousComponent (Component* c) { return KeyboardFocusTraverser::getPreviousComponent (getComp (c)); }
|
||||
|
||||
static Component* getComp (Component* current)
|
||||
{
|
||||
return dynamic_cast <TextEditor*> (current) != nullptr
|
||||
? current->getParentComponent() : current;
|
||||
}
|
||||
};
|
||||
|
||||
KeyboardFocusTraverser* Label::createFocusTraverser()
|
||||
{
|
||||
return new LabelKeyboardFocusTraverser();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Label::addListener (LabelListener* const listener)
|
||||
{
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
void Label::removeListener (LabelListener* const listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
void Label::callChangeListeners()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, &LabelListener::labelTextChanged, this); // (can't use Label::Listener due to idiotic VC2005 bug)
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Label::textEditorTextChanged (TextEditor& ed)
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
jassert (&ed == editor);
|
||||
|
||||
if (! (hasKeyboardFocus (true) || isCurrentlyBlockedByAnotherModalComponent()))
|
||||
{
|
||||
if (lossOfFocusDiscardsChanges)
|
||||
textEditorEscapeKeyPressed (ed);
|
||||
else
|
||||
textEditorReturnKeyPressed (ed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Label::textEditorReturnKeyPressed (TextEditor& ed)
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
jassert (&ed == editor);
|
||||
|
||||
const bool changed = updateFromTextEditorContents (ed);
|
||||
hideEditor (true);
|
||||
|
||||
if (changed)
|
||||
{
|
||||
WeakReference<Component> deletionChecker (this);
|
||||
textWasEdited();
|
||||
|
||||
if (deletionChecker != nullptr)
|
||||
callChangeListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Label::textEditorEscapeKeyPressed (TextEditor& ed)
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
jassert (&ed == editor);
|
||||
(void) ed;
|
||||
|
||||
editor->setText (textValue.toString(), false);
|
||||
hideEditor (true);
|
||||
}
|
||||
}
|
||||
|
||||
void Label::textEditorFocusLost (TextEditor& ed)
|
||||
{
|
||||
textEditorTextChanged (ed);
|
||||
}
|
||||
|
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_LABEL_H_INCLUDED
|
||||
#define JUCE_LABEL_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that displays a text string, and can optionally become a text
|
||||
editor when clicked.
|
||||
*/
|
||||
class JUCE_API Label : public Component,
|
||||
public SettableTooltipClient,
|
||||
protected TextEditorListener,
|
||||
private ComponentListener,
|
||||
private ValueListener
|
||||
{
|
||||
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::empty,
|
||||
const String& labelText = String::empty);
|
||||
|
||||
/** Destructor. */
|
||||
~Label();
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the label text.
|
||||
|
||||
The NotificationType parameter indicates whether to send a change message to
|
||||
any Label::Listener objects if the new text is different.
|
||||
*/
|
||||
void setText (const String& newText,
|
||||
NotificationType notification);
|
||||
|
||||
/** Returns the label's current text.
|
||||
|
||||
@param returnActiveEditorContents if this is true and the label is currently
|
||||
being edited, then this method will return the
|
||||
text as it's being shown in the editor. If false,
|
||||
then the value returned here won't be updated until
|
||||
the user has finished typing and pressed the return
|
||||
key.
|
||||
*/
|
||||
String getText (bool returnActiveEditorContents = false) const;
|
||||
|
||||
/** Returns the text content as a Value object.
|
||||
You can call Value::referTo() on this object to make the label read and control
|
||||
a Value object that you supply.
|
||||
*/
|
||||
Value& getTextValue() noexcept { return textValue; }
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the font to use to draw the text.
|
||||
@see getFont
|
||||
*/
|
||||
void setFont (const Font& newFont);
|
||||
|
||||
/** Returns the font currently being used.
|
||||
This may be the one set by setFont(), unless it has been overridden by the current LookAndFeel
|
||||
@see setFont
|
||||
*/
|
||||
Font getFont() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the label.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
Note that you can also use the constants from TextEditor::ColourIds to change the
|
||||
colour of the text editor that is opened when a label is editable.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1000280, /**< The background colour to fill the label with. */
|
||||
textColourId = 0x1000281, /**< The colour for the text. */
|
||||
outlineColourId = 0x1000282, /**< An optional colour to use to draw a border around the label.
|
||||
Leave this transparent to not have an outline. */
|
||||
backgroundWhenEditingColourId = 0x1000283, /**< The background colour when the label is being edited. */
|
||||
textWhenEditingColourId = 0x1000284, /**< The colour for the text when the label is being edited. */
|
||||
outlineWhenEditingColourId = 0x1000285 /**< An optional border colour when the label is being edited. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the style of justification to be used for positioning the text.
|
||||
(The default is Justification::centredLeft)
|
||||
*/
|
||||
void setJustificationType (Justification justification);
|
||||
|
||||
/** Returns the type of justification, as set in setJustificationType(). */
|
||||
Justification getJustificationType() const noexcept { return justification; }
|
||||
|
||||
/** Changes the border that is left between the edge of the component and the text.
|
||||
By default there's a small gap left at the sides of the component to allow for
|
||||
the drawing of the border, but you can change this if necessary.
|
||||
*/
|
||||
void setBorderSize (BorderSize<int> newBorderSize);
|
||||
|
||||
/** Returns the size of the border to be left around the text. */
|
||||
BorderSize<int> getBorderSize() const noexcept { return border; }
|
||||
|
||||
/** Makes this label "stick to" another component.
|
||||
|
||||
This will cause the label to follow another component around, staying
|
||||
either to its left or above it.
|
||||
|
||||
@param owner the component to follow
|
||||
@param onLeft if true, the label will stay on the left of its component; if
|
||||
false, it will stay above it.
|
||||
*/
|
||||
void attachToComponent (Component* owner, bool onLeft);
|
||||
|
||||
/** If this label has been attached to another component using attachToComponent, this
|
||||
returns the other component.
|
||||
|
||||
Returns nullptr if the label is not attached.
|
||||
*/
|
||||
Component* getAttachedComponent() const;
|
||||
|
||||
/** If the label is attached to the left of another component, this returns true.
|
||||
|
||||
Returns false if the label is above the other component. This is only relevent 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.
|
||||
|
||||
@see Graphics::drawFittedText
|
||||
*/
|
||||
void setMinimumHorizontalScale (float newScale);
|
||||
|
||||
/** Specifies the amount that the font can be squashed horizontally. */
|
||||
float getMinimumHorizontalScale() const noexcept { return minimumHorizontalScale; }
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class for receiving events from a Label.
|
||||
|
||||
You can register a Label::Listener with a Label using the Label::addListener()
|
||||
method, and it will be called when the text of the label changes, either because
|
||||
of a call to Label::setText() or by the user editing the text (if the label is
|
||||
editable).
|
||||
|
||||
@see Label::addListener, Label::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called when a Label's text has changed. */
|
||||
virtual void labelTextChanged (Label* labelThatHasChanged) = 0;
|
||||
|
||||
/** Called when a Label goes into editing mode and displays a TextEditor. */
|
||||
virtual void editorShown (Label*, TextEditor&) {}
|
||||
};
|
||||
|
||||
/** 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);
|
||||
|
||||
//==============================================================================
|
||||
/** Makes the label turn into a TextEditor when clicked.
|
||||
|
||||
By default this is turned off.
|
||||
|
||||
If turned on, then single- or double-clicking will turn the label into
|
||||
an editor. If the user then changes the text, then the ChangeBroadcaster
|
||||
base class will be used to send change messages to any listeners that
|
||||
have registered.
|
||||
|
||||
If the user changes the text, the textWasEdited() method will be called
|
||||
afterwards, and subclasses can override this if they need to do anything
|
||||
special.
|
||||
|
||||
@param editOnSingleClick if true, just clicking once on the label will start editing the text
|
||||
@param editOnDoubleClick if true, a double-click is needed to start editing
|
||||
@param lossOfFocusDiscardsChanges if true, clicking somewhere else while the text is being
|
||||
edited will discard any changes; if false, then this will
|
||||
commit the changes.
|
||||
@see showEditor, setEditorColours, TextEditor
|
||||
*/
|
||||
void setEditable (bool editOnSingleClick,
|
||||
bool editOnDoubleClick = false,
|
||||
bool lossOfFocusDiscardsChanges = false);
|
||||
|
||||
/** Returns true if this option was set using setEditable(). */
|
||||
bool isEditableOnSingleClick() const noexcept { return editSingleClick; }
|
||||
|
||||
/** Returns true if this option was set using setEditable(). */
|
||||
bool isEditableOnDoubleClick() const noexcept { return editDoubleClick; }
|
||||
|
||||
/** Returns true if this option has been set in a call to setEditable(). */
|
||||
bool doesLossOfFocusDiscardChanges() const noexcept { return lossOfFocusDiscardsChanges; }
|
||||
|
||||
/** Returns true if the user can edit this label's text. */
|
||||
bool isEditable() const noexcept { return editSingleClick || editDoubleClick; }
|
||||
|
||||
/** Makes the editor appear as if the label had been clicked by the user.
|
||||
@see textWasEdited, setEditable
|
||||
*/
|
||||
void showEditor();
|
||||
|
||||
/** Hides the editor if it was being shown.
|
||||
|
||||
@param discardCurrentEditorContents if true, the label's text will be
|
||||
reset to whatever it was before the editor
|
||||
was shown; if false, the current contents of the
|
||||
editor will be used to set the label's text
|
||||
before it is hidden.
|
||||
*/
|
||||
void hideEditor (bool discardCurrentEditorContents);
|
||||
|
||||
/** Returns true if the editor is currently focused and active. */
|
||||
bool isBeingEdited() const noexcept;
|
||||
|
||||
/** Returns the currently-visible text editor, or nullptr if none is open. */
|
||||
TextEditor* getCurrentTextEditor() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
label drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void drawLabel (Graphics&, Label&) = 0;
|
||||
virtual Font getLabelFont (Label&) = 0;
|
||||
};
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates the TextEditor component that will be used when the user has clicked on the label.
|
||||
Subclasses can override this if they need to customise this component in some way.
|
||||
*/
|
||||
virtual TextEditor* createEditorComponent();
|
||||
|
||||
/** Called after the user changes the text. */
|
||||
virtual void textWasEdited();
|
||||
|
||||
/** Called when the text has been altered. */
|
||||
virtual void textWasChanged();
|
||||
|
||||
/** Called when the text editor has just appeared, due to a user click or other focus change. */
|
||||
virtual void editorShown (TextEditor*);
|
||||
|
||||
/** Called when the text editor is going to be deleted, after editing has finished. */
|
||||
virtual void editorAboutToBeHidden (TextEditor*);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDoubleClick (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
|
||||
/** @internal */
|
||||
void componentParentHierarchyChanged (Component&) override;
|
||||
/** @internal */
|
||||
void componentVisibilityChanged (Component&) override;
|
||||
/** @internal */
|
||||
void inputAttemptWhenModal() override;
|
||||
/** @internal */
|
||||
void focusGained (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
KeyboardFocusTraverser* createFocusTraverser() override;
|
||||
/** @internal */
|
||||
void textEditorTextChanged (TextEditor&) override;
|
||||
/** @internal */
|
||||
void textEditorReturnKeyPressed (TextEditor&) override;
|
||||
/** @internal */
|
||||
void textEditorEscapeKeyPressed (TextEditor&) override;
|
||||
/** @internal */
|
||||
void textEditorFocusLost (TextEditor&) override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
void valueChanged (Value&) override;
|
||||
/** @internal */
|
||||
void callChangeListeners();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Value textValue;
|
||||
String lastTextValue;
|
||||
Font font;
|
||||
Justification justification;
|
||||
ScopedPointer<TextEditor> editor;
|
||||
ListenerList<Listener> listeners;
|
||||
WeakReference<Component> ownerComponent;
|
||||
BorderSize<int> border;
|
||||
float minimumHorizontalScale;
|
||||
bool editSingleClick;
|
||||
bool editDoubleClick;
|
||||
bool lossOfFocusDiscardsChanges;
|
||||
bool leftOfOwnerComp;
|
||||
|
||||
bool updateFromTextEditorContents (TextEditor&);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Label)
|
||||
};
|
||||
|
||||
/** This typedef is just for compatibility with old code - newer code should use the Label::Listener class directly. */
|
||||
typedef Label::Listener LabelListener;
|
||||
|
||||
#endif // JUCE_LABEL_H_INCLUDED
|
||||
|
|
@ -0,0 +1,966 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class ListBox::RowComponent : public Component,
|
||||
public TooltipClient
|
||||
{
|
||||
public:
|
||||
RowComponent (ListBox& lb)
|
||||
: owner (lb), row (-1),
|
||||
selected (false), isDragging (false), selectRowOnMouseUp (false)
|
||||
{
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (ListBoxModel* m = owner.getModel())
|
||||
m->paintListBoxItem (row, g, getWidth(), getHeight(), selected);
|
||||
}
|
||||
|
||||
void update (const int newRow, const bool nowSelected)
|
||||
{
|
||||
if (row != newRow || selected != nowSelected)
|
||||
{
|
||||
repaint();
|
||||
row = newRow;
|
||||
selected = nowSelected;
|
||||
}
|
||||
|
||||
if (ListBoxModel* m = owner.getModel())
|
||||
{
|
||||
setMouseCursor (m->getMouseCursorForRow (row));
|
||||
|
||||
customComponent = m->refreshComponentForRow (newRow, nowSelected, customComponent.release());
|
||||
|
||||
if (customComponent != nullptr)
|
||||
{
|
||||
addAndMakeVisible (customComponent);
|
||||
customComponent->setBounds (getLocalBounds());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
isDragging = false;
|
||||
selectRowOnMouseUp = false;
|
||||
|
||||
if (isEnabled())
|
||||
{
|
||||
if (! selected)
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (row, e.mods, false);
|
||||
|
||||
if (ListBoxModel* m = owner.getModel())
|
||||
m->listBoxItemClicked (row, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectRowOnMouseUp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mouseUp (const MouseEvent& e) override
|
||||
{
|
||||
if (isEnabled() && selectRowOnMouseUp && ! isDragging)
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (row, e.mods, true);
|
||||
|
||||
if (ListBoxModel* m = owner.getModel())
|
||||
m->listBoxItemClicked (row, e);
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDoubleClick (const MouseEvent& e) override
|
||||
{
|
||||
if (ListBoxModel* m = owner.getModel())
|
||||
if (isEnabled())
|
||||
m->listBoxItemDoubleClicked (row, e);
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
if (ListBoxModel* m = owner.getModel())
|
||||
{
|
||||
if (isEnabled() && ! (e.mouseWasClicked() || isDragging))
|
||||
{
|
||||
const SparseSet<int> selectedRows (owner.getSelectedRows());
|
||||
|
||||
if (selectedRows.size() > 0)
|
||||
{
|
||||
const var dragDescription (m->getDragSourceDescription (selectedRows));
|
||||
|
||||
if (! (dragDescription.isVoid() || (dragDescription.isString() && dragDescription.toString().isEmpty())))
|
||||
{
|
||||
isDragging = true;
|
||||
owner.startDragAndDrop (e, dragDescription, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
if (customComponent != nullptr)
|
||||
customComponent->setBounds (getLocalBounds());
|
||||
}
|
||||
|
||||
String getTooltip() override
|
||||
{
|
||||
if (ListBoxModel* m = owner.getModel())
|
||||
return m->getTooltipForRow (row);
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
ScopedPointer<Component> customComponent;
|
||||
|
||||
private:
|
||||
ListBox& owner;
|
||||
int row;
|
||||
bool selected, isDragging, selectRowOnMouseUp;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RowComponent)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class ListBox::ListViewport : public Viewport
|
||||
{
|
||||
public:
|
||||
ListViewport (ListBox& lb)
|
||||
: owner (lb)
|
||||
{
|
||||
setWantsKeyboardFocus (false);
|
||||
|
||||
Component* const content = new Component();
|
||||
setViewedComponent (content);
|
||||
content->setWantsKeyboardFocus (false);
|
||||
}
|
||||
|
||||
RowComponent* getComponentForRow (const int row) const noexcept
|
||||
{
|
||||
return rows [row % jmax (1, rows.size())];
|
||||
}
|
||||
|
||||
RowComponent* getComponentForRowIfOnscreen (const int row) const noexcept
|
||||
{
|
||||
return (row >= firstIndex && row < firstIndex + rows.size())
|
||||
? getComponentForRow (row) : nullptr;
|
||||
}
|
||||
|
||||
int getRowNumberOfComponent (Component* const rowComponent) const noexcept
|
||||
{
|
||||
const int index = getIndexOfChildComponent (rowComponent);
|
||||
const int num = rows.size();
|
||||
|
||||
for (int i = num; --i >= 0;)
|
||||
if (((firstIndex + i) % jmax (1, num)) == index)
|
||||
return firstIndex + i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void visibleAreaChanged (const Rectangle<int>&) override
|
||||
{
|
||||
updateVisibleArea (true);
|
||||
|
||||
if (ListBoxModel* m = owner.getModel())
|
||||
m->listWasScrolled();
|
||||
}
|
||||
|
||||
void updateVisibleArea (const bool makeSureItUpdatesContent)
|
||||
{
|
||||
hasUpdated = false;
|
||||
|
||||
const int newX = getViewedComponent()->getX();
|
||||
int newY = getViewedComponent()->getY();
|
||||
const int newW = jmax (owner.minimumRowWidth, getMaximumVisibleWidth());
|
||||
const int newH = owner.totalItems * owner.getRowHeight();
|
||||
|
||||
if (newY + newH < getMaximumVisibleHeight() && newH > getMaximumVisibleHeight())
|
||||
newY = getMaximumVisibleHeight() - newH;
|
||||
|
||||
getViewedComponent()->setBounds (newX, newY, newW, newH);
|
||||
|
||||
if (makeSureItUpdatesContent && ! hasUpdated)
|
||||
updateContents();
|
||||
}
|
||||
|
||||
void updateContents()
|
||||
{
|
||||
hasUpdated = true;
|
||||
const int rowH = owner.getRowHeight();
|
||||
|
||||
if (rowH > 0)
|
||||
{
|
||||
const int y = getViewPositionY();
|
||||
const int w = getViewedComponent()->getWidth();
|
||||
|
||||
const int numNeeded = 2 + getMaximumVisibleHeight() / rowH;
|
||||
rows.removeRange (numNeeded, rows.size());
|
||||
|
||||
while (numNeeded > rows.size())
|
||||
{
|
||||
RowComponent* newRow = new RowComponent (owner);
|
||||
rows.add (newRow);
|
||||
getViewedComponent()->addAndMakeVisible (newRow);
|
||||
}
|
||||
|
||||
firstIndex = y / rowH;
|
||||
firstWholeIndex = (y + rowH - 1) / rowH;
|
||||
lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowH;
|
||||
|
||||
for (int i = 0; i < numNeeded; ++i)
|
||||
{
|
||||
const int row = i + firstIndex;
|
||||
|
||||
if (RowComponent* const rowComp = getComponentForRow (row))
|
||||
{
|
||||
rowComp->setBounds (0, row * rowH, w, rowH);
|
||||
rowComp->update (row, owner.isRowSelected (row));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (owner.headerComponent != nullptr)
|
||||
owner.headerComponent->setBounds (owner.outlineThickness + getViewedComponent()->getX(),
|
||||
owner.outlineThickness,
|
||||
jmax (owner.getWidth() - owner.outlineThickness * 2,
|
||||
getViewedComponent()->getWidth()),
|
||||
owner.headerComponent->getHeight());
|
||||
}
|
||||
|
||||
void selectRow (const int row, const int rowH, const bool dontScroll,
|
||||
const int lastSelectedRow, const int totalRows, const bool isMouseClick)
|
||||
{
|
||||
hasUpdated = false;
|
||||
|
||||
if (row < firstWholeIndex && ! dontScroll)
|
||||
{
|
||||
setViewPosition (getViewPositionX(), row * rowH);
|
||||
}
|
||||
else if (row >= lastWholeIndex && ! dontScroll)
|
||||
{
|
||||
const int rowsOnScreen = lastWholeIndex - firstWholeIndex;
|
||||
|
||||
if (row >= lastSelectedRow + rowsOnScreen
|
||||
&& rowsOnScreen < totalRows - 1
|
||||
&& ! isMouseClick)
|
||||
{
|
||||
setViewPosition (getViewPositionX(),
|
||||
jlimit (0, jmax (0, totalRows - rowsOnScreen), row) * rowH);
|
||||
}
|
||||
else
|
||||
{
|
||||
setViewPosition (getViewPositionX(),
|
||||
jmax (0, (row + 1) * rowH - getMaximumVisibleHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
if (! hasUpdated)
|
||||
updateContents();
|
||||
}
|
||||
|
||||
void scrollToEnsureRowIsOnscreen (const int row, const int rowH)
|
||||
{
|
||||
if (row < firstWholeIndex)
|
||||
{
|
||||
setViewPosition (getViewPositionX(), row * rowH);
|
||||
}
|
||||
else if (row >= lastWholeIndex)
|
||||
{
|
||||
setViewPosition (getViewPositionX(),
|
||||
jmax (0, (row + 1) * rowH - getMaximumVisibleHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (isOpaque())
|
||||
g.fillAll (owner.findColour (ListBox::backgroundColourId));
|
||||
}
|
||||
|
||||
bool keyPressed (const KeyPress& key) override
|
||||
{
|
||||
if (Viewport::respondsToKey (key))
|
||||
{
|
||||
const int allowableMods = owner.multipleSelection ? ModifierKeys::shiftModifier : 0;
|
||||
|
||||
if ((key.getModifiers().getRawFlags() & ~allowableMods) == 0)
|
||||
{
|
||||
// we want to avoid these keypresses going to the viewport, and instead allow
|
||||
// them to pass up to our listbox..
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return Viewport::keyPressed (key);
|
||||
}
|
||||
|
||||
private:
|
||||
ListBox& owner;
|
||||
OwnedArray<RowComponent> rows;
|
||||
int firstIndex, firstWholeIndex, lastWholeIndex;
|
||||
bool hasUpdated;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListViewport)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ListBoxMouseMoveSelector : public MouseListener
|
||||
{
|
||||
public:
|
||||
ListBoxMouseMoveSelector (ListBox& lb) : owner (lb)
|
||||
{
|
||||
owner.addMouseListener (this, true);
|
||||
}
|
||||
|
||||
void mouseMove (const MouseEvent& e) override
|
||||
{
|
||||
const MouseEvent e2 (e.getEventRelativeTo (&owner));
|
||||
owner.selectRow (owner.getRowContainingPosition (e2.x, e2.y), true);
|
||||
}
|
||||
|
||||
void mouseExit (const MouseEvent& e) override
|
||||
{
|
||||
mouseMove (e);
|
||||
}
|
||||
|
||||
private:
|
||||
ListBox& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListBoxMouseMoveSelector)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
ListBox::ListBox (const String& name, ListBoxModel* const m)
|
||||
: Component (name),
|
||||
model (m),
|
||||
totalItems (0),
|
||||
rowHeight (22),
|
||||
minimumRowWidth (0),
|
||||
outlineThickness (0),
|
||||
lastRowSelected (-1),
|
||||
multipleSelection (false),
|
||||
alwaysFlipSelection (false),
|
||||
hasDoneInitialUpdate (false)
|
||||
{
|
||||
addAndMakeVisible (viewport = new ListViewport (*this));
|
||||
|
||||
ListBox::setWantsKeyboardFocus (true);
|
||||
ListBox::colourChanged();
|
||||
}
|
||||
|
||||
ListBox::~ListBox()
|
||||
{
|
||||
headerComponent = nullptr;
|
||||
viewport = nullptr;
|
||||
}
|
||||
|
||||
void ListBox::setModel (ListBoxModel* const newModel)
|
||||
{
|
||||
if (model != newModel)
|
||||
{
|
||||
model = newModel;
|
||||
repaint();
|
||||
updateContent();
|
||||
}
|
||||
}
|
||||
|
||||
void ListBox::setMultipleSelectionEnabled (bool b) noexcept
|
||||
{
|
||||
multipleSelection = b;
|
||||
}
|
||||
|
||||
void ListBox::setClickingTogglesRowSelection (bool b) noexcept
|
||||
{
|
||||
alwaysFlipSelection = b;
|
||||
}
|
||||
|
||||
void ListBox::setMouseMoveSelectsRows (bool b)
|
||||
{
|
||||
if (b)
|
||||
{
|
||||
if (mouseMoveSelector == nullptr)
|
||||
mouseMoveSelector = new ListBoxMouseMoveSelector (*this);
|
||||
}
|
||||
else
|
||||
{
|
||||
mouseMoveSelector = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ListBox::paint (Graphics& g)
|
||||
{
|
||||
if (! hasDoneInitialUpdate)
|
||||
updateContent();
|
||||
|
||||
g.fillAll (findColour (backgroundColourId));
|
||||
}
|
||||
|
||||
void ListBox::paintOverChildren (Graphics& g)
|
||||
{
|
||||
if (outlineThickness > 0)
|
||||
{
|
||||
g.setColour (findColour (outlineColourId));
|
||||
g.drawRect (getLocalBounds(), outlineThickness);
|
||||
}
|
||||
}
|
||||
|
||||
void ListBox::resized()
|
||||
{
|
||||
viewport->setBoundsInset (BorderSize<int> (outlineThickness + (headerComponent != nullptr ? headerComponent->getHeight() : 0),
|
||||
outlineThickness, outlineThickness, outlineThickness));
|
||||
|
||||
viewport->setSingleStepSizes (20, getRowHeight());
|
||||
|
||||
viewport->updateVisibleArea (false);
|
||||
}
|
||||
|
||||
void ListBox::visibilityChanged()
|
||||
{
|
||||
viewport->updateVisibleArea (true);
|
||||
}
|
||||
|
||||
Viewport* ListBox::getViewport() const noexcept
|
||||
{
|
||||
return viewport;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ListBox::updateContent()
|
||||
{
|
||||
hasDoneInitialUpdate = true;
|
||||
totalItems = (model != nullptr) ? model->getNumRows() : 0;
|
||||
|
||||
bool selectionChanged = false;
|
||||
|
||||
if (selected.size() > 0 && selected [selected.size() - 1] >= totalItems)
|
||||
{
|
||||
selected.removeRange (Range<int> (totalItems, std::numeric_limits<int>::max()));
|
||||
lastRowSelected = getSelectedRow (0);
|
||||
selectionChanged = true;
|
||||
}
|
||||
|
||||
viewport->updateVisibleArea (isVisible());
|
||||
viewport->resized();
|
||||
|
||||
if (selectionChanged && model != nullptr)
|
||||
model->selectedRowsChanged (lastRowSelected);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ListBox::selectRow (int row, bool dontScroll, bool deselectOthersFirst)
|
||||
{
|
||||
selectRowInternal (row, dontScroll, deselectOthersFirst, false);
|
||||
}
|
||||
|
||||
void ListBox::selectRowInternal (const int row,
|
||||
bool dontScroll,
|
||||
bool deselectOthersFirst,
|
||||
bool isMouseClick)
|
||||
{
|
||||
if (! multipleSelection)
|
||||
deselectOthersFirst = true;
|
||||
|
||||
if ((! isRowSelected (row))
|
||||
|| (deselectOthersFirst && getNumSelectedRows() > 1))
|
||||
{
|
||||
if (isPositiveAndBelow (row, totalItems))
|
||||
{
|
||||
if (deselectOthersFirst)
|
||||
selected.clear();
|
||||
|
||||
selected.addRange (Range<int> (row, row + 1));
|
||||
|
||||
if (getHeight() == 0 || getWidth() == 0)
|
||||
dontScroll = true;
|
||||
|
||||
viewport->selectRow (row, getRowHeight(), dontScroll,
|
||||
lastRowSelected, totalItems, isMouseClick);
|
||||
|
||||
lastRowSelected = row;
|
||||
model->selectedRowsChanged (row);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (deselectOthersFirst)
|
||||
deselectAllRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListBox::deselectRow (const int row)
|
||||
{
|
||||
if (selected.contains (row))
|
||||
{
|
||||
selected.removeRange (Range<int> (row, row + 1));
|
||||
|
||||
if (row == lastRowSelected)
|
||||
lastRowSelected = getSelectedRow (0);
|
||||
|
||||
viewport->updateContents();
|
||||
model->selectedRowsChanged (lastRowSelected);
|
||||
}
|
||||
}
|
||||
|
||||
void ListBox::setSelectedRows (const SparseSet<int>& setOfRowsToBeSelected,
|
||||
const NotificationType sendNotificationEventToModel)
|
||||
{
|
||||
selected = setOfRowsToBeSelected;
|
||||
selected.removeRange (Range<int> (totalItems, std::numeric_limits<int>::max()));
|
||||
|
||||
if (! isRowSelected (lastRowSelected))
|
||||
lastRowSelected = getSelectedRow (0);
|
||||
|
||||
viewport->updateContents();
|
||||
|
||||
if (model != nullptr && sendNotificationEventToModel == sendNotification)
|
||||
model->selectedRowsChanged (lastRowSelected);
|
||||
}
|
||||
|
||||
SparseSet<int> ListBox::getSelectedRows() const
|
||||
{
|
||||
return selected;
|
||||
}
|
||||
|
||||
void ListBox::selectRangeOfRows (int firstRow, int lastRow)
|
||||
{
|
||||
if (multipleSelection && (firstRow != lastRow))
|
||||
{
|
||||
const int numRows = totalItems - 1;
|
||||
firstRow = jlimit (0, jmax (0, numRows), firstRow);
|
||||
lastRow = jlimit (0, jmax (0, numRows), lastRow);
|
||||
|
||||
selected.addRange (Range<int> (jmin (firstRow, lastRow),
|
||||
jmax (firstRow, lastRow) + 1));
|
||||
|
||||
selected.removeRange (Range<int> (lastRow, lastRow + 1));
|
||||
}
|
||||
|
||||
selectRowInternal (lastRow, false, false, true);
|
||||
}
|
||||
|
||||
void ListBox::flipRowSelection (const int row)
|
||||
{
|
||||
if (isRowSelected (row))
|
||||
deselectRow (row);
|
||||
else
|
||||
selectRowInternal (row, false, false, true);
|
||||
}
|
||||
|
||||
void ListBox::deselectAllRows()
|
||||
{
|
||||
if (! selected.isEmpty())
|
||||
{
|
||||
selected.clear();
|
||||
lastRowSelected = -1;
|
||||
|
||||
viewport->updateContents();
|
||||
|
||||
if (model != nullptr)
|
||||
model->selectedRowsChanged (lastRowSelected);
|
||||
}
|
||||
}
|
||||
|
||||
void ListBox::selectRowsBasedOnModifierKeys (const int row,
|
||||
ModifierKeys mods,
|
||||
const bool isMouseUpEvent)
|
||||
{
|
||||
if (multipleSelection && (mods.isCommandDown() || alwaysFlipSelection))
|
||||
{
|
||||
flipRowSelection (row);
|
||||
}
|
||||
else if (multipleSelection && mods.isShiftDown() && lastRowSelected >= 0)
|
||||
{
|
||||
selectRangeOfRows (lastRowSelected, row);
|
||||
}
|
||||
else if ((! mods.isPopupMenu()) || ! isRowSelected (row))
|
||||
{
|
||||
selectRowInternal (row, false, ! (multipleSelection && (! isMouseUpEvent) && isRowSelected (row)), true);
|
||||
}
|
||||
}
|
||||
|
||||
int ListBox::getNumSelectedRows() const
|
||||
{
|
||||
return selected.size();
|
||||
}
|
||||
|
||||
int ListBox::getSelectedRow (const int index) const
|
||||
{
|
||||
return (isPositiveAndBelow (index, selected.size()))
|
||||
? selected [index] : -1;
|
||||
}
|
||||
|
||||
bool ListBox::isRowSelected (const int row) const
|
||||
{
|
||||
return selected.contains (row);
|
||||
}
|
||||
|
||||
int ListBox::getLastRowSelected() const
|
||||
{
|
||||
return isRowSelected (lastRowSelected) ? lastRowSelected : -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int ListBox::getRowContainingPosition (const int x, const int y) const noexcept
|
||||
{
|
||||
if (isPositiveAndBelow (x, getWidth()))
|
||||
{
|
||||
const int row = (viewport->getViewPositionY() + y - viewport->getY()) / rowHeight;
|
||||
|
||||
if (isPositiveAndBelow (row, totalItems))
|
||||
return row;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ListBox::getInsertionIndexForPosition (const int x, const int y) const noexcept
|
||||
{
|
||||
if (isPositiveAndBelow (x, getWidth()))
|
||||
{
|
||||
const int row = (viewport->getViewPositionY() + y + rowHeight / 2 - viewport->getY()) / rowHeight;
|
||||
return jlimit (0, totalItems, row);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Component* ListBox::getComponentForRowNumber (const int row) const noexcept
|
||||
{
|
||||
if (RowComponent* const listRowComp = viewport->getComponentForRowIfOnscreen (row))
|
||||
return static_cast<Component*> (listRowComp->customComponent);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ListBox::getRowNumberOfComponent (Component* const rowComponent) const noexcept
|
||||
{
|
||||
return viewport->getRowNumberOfComponent (rowComponent);
|
||||
}
|
||||
|
||||
Rectangle<int> ListBox::getRowPosition (const int rowNumber,
|
||||
const bool relativeToComponentTopLeft) const noexcept
|
||||
{
|
||||
int y = viewport->getY() + rowHeight * rowNumber;
|
||||
|
||||
if (relativeToComponentTopLeft)
|
||||
y -= viewport->getViewPositionY();
|
||||
|
||||
return Rectangle<int> (viewport->getX(), y,
|
||||
viewport->getViewedComponent()->getWidth(), rowHeight);
|
||||
}
|
||||
|
||||
void ListBox::setVerticalPosition (const double proportion)
|
||||
{
|
||||
const int offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
|
||||
|
||||
viewport->setViewPosition (viewport->getViewPositionX(),
|
||||
jmax (0, roundToInt (proportion * offscreen)));
|
||||
}
|
||||
|
||||
double ListBox::getVerticalPosition() const
|
||||
{
|
||||
const int offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
|
||||
|
||||
return (offscreen > 0) ? viewport->getViewPositionY() / (double) offscreen
|
||||
: 0;
|
||||
}
|
||||
|
||||
int ListBox::getVisibleRowWidth() const noexcept
|
||||
{
|
||||
return viewport->getViewWidth();
|
||||
}
|
||||
|
||||
void ListBox::scrollToEnsureRowIsOnscreen (const int row)
|
||||
{
|
||||
viewport->scrollToEnsureRowIsOnscreen (row, getRowHeight());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ListBox::keyPressed (const KeyPress& key)
|
||||
{
|
||||
const int numVisibleRows = viewport->getHeight() / getRowHeight();
|
||||
|
||||
const bool multiple = multipleSelection
|
||||
&& lastRowSelected >= 0
|
||||
&& key.getModifiers().isShiftDown();
|
||||
|
||||
if (key.isKeyCode (KeyPress::upKey))
|
||||
{
|
||||
if (multiple)
|
||||
selectRangeOfRows (lastRowSelected, lastRowSelected - 1);
|
||||
else
|
||||
selectRow (jmax (0, lastRowSelected - 1));
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::downKey))
|
||||
{
|
||||
if (multiple)
|
||||
selectRangeOfRows (lastRowSelected, lastRowSelected + 1);
|
||||
else
|
||||
selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected) + 1));
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::pageUpKey))
|
||||
{
|
||||
if (multiple)
|
||||
selectRangeOfRows (lastRowSelected, lastRowSelected - numVisibleRows);
|
||||
else
|
||||
selectRow (jmax (0, jmax (0, lastRowSelected) - numVisibleRows));
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::pageDownKey))
|
||||
{
|
||||
if (multiple)
|
||||
selectRangeOfRows (lastRowSelected, lastRowSelected + numVisibleRows);
|
||||
else
|
||||
selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected) + numVisibleRows));
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::homeKey))
|
||||
{
|
||||
if (multiple)
|
||||
selectRangeOfRows (lastRowSelected, 0);
|
||||
else
|
||||
selectRow (0);
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::endKey))
|
||||
{
|
||||
if (multiple)
|
||||
selectRangeOfRows (lastRowSelected, totalItems - 1);
|
||||
else
|
||||
selectRow (totalItems - 1);
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::returnKey) && isRowSelected (lastRowSelected))
|
||||
{
|
||||
if (model != nullptr)
|
||||
model->returnKeyPressed (lastRowSelected);
|
||||
}
|
||||
else if ((key.isKeyCode (KeyPress::deleteKey) || key.isKeyCode (KeyPress::backspaceKey))
|
||||
&& isRowSelected (lastRowSelected))
|
||||
{
|
||||
if (model != nullptr)
|
||||
model->deleteKeyPressed (lastRowSelected);
|
||||
}
|
||||
else if (multipleSelection && key == KeyPress ('a', ModifierKeys::commandModifier, 0))
|
||||
{
|
||||
selectRangeOfRows (0, std::numeric_limits<int>::max());
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ListBox::keyStateChanged (const bool isKeyDown)
|
||||
{
|
||||
return isKeyDown
|
||||
&& (KeyPress::isKeyCurrentlyDown (KeyPress::upKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::pageUpKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::downKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::pageDownKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::homeKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::endKey)
|
||||
|| KeyPress::isKeyCurrentlyDown (KeyPress::returnKey));
|
||||
}
|
||||
|
||||
void ListBox::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
|
||||
{
|
||||
bool eventWasUsed = false;
|
||||
|
||||
if (wheel.deltaX != 0 && viewport->getHorizontalScrollBar()->isVisible())
|
||||
{
|
||||
eventWasUsed = true;
|
||||
viewport->getHorizontalScrollBar()->mouseWheelMove (e, wheel);
|
||||
}
|
||||
|
||||
if (wheel.deltaY != 0 && viewport->getVerticalScrollBar()->isVisible())
|
||||
{
|
||||
eventWasUsed = true;
|
||||
viewport->getVerticalScrollBar()->mouseWheelMove (e, wheel);
|
||||
}
|
||||
|
||||
if (! eventWasUsed)
|
||||
Component::mouseWheelMove (e, wheel);
|
||||
}
|
||||
|
||||
void ListBox::mouseUp (const MouseEvent& e)
|
||||
{
|
||||
if (e.mouseWasClicked() && model != nullptr)
|
||||
model->backgroundClicked (e);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ListBox::setRowHeight (const int newHeight)
|
||||
{
|
||||
rowHeight = jmax (1, newHeight);
|
||||
viewport->setSingleStepSizes (20, rowHeight);
|
||||
updateContent();
|
||||
}
|
||||
|
||||
int ListBox::getNumRowsOnScreen() const noexcept
|
||||
{
|
||||
return viewport->getMaximumVisibleHeight() / rowHeight;
|
||||
}
|
||||
|
||||
void ListBox::setMinimumContentWidth (const int newMinimumWidth)
|
||||
{
|
||||
minimumRowWidth = newMinimumWidth;
|
||||
updateContent();
|
||||
}
|
||||
|
||||
int ListBox::getVisibleContentWidth() const noexcept
|
||||
{
|
||||
return viewport->getMaximumVisibleWidth();
|
||||
}
|
||||
|
||||
ScrollBar* ListBox::getVerticalScrollBar() const noexcept
|
||||
{
|
||||
return viewport->getVerticalScrollBar();
|
||||
}
|
||||
|
||||
ScrollBar* ListBox::getHorizontalScrollBar() const noexcept
|
||||
{
|
||||
return viewport->getHorizontalScrollBar();
|
||||
}
|
||||
|
||||
void ListBox::colourChanged()
|
||||
{
|
||||
setOpaque (findColour (backgroundColourId).isOpaque());
|
||||
viewport->setOpaque (isOpaque());
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ListBox::parentHierarchyChanged()
|
||||
{
|
||||
colourChanged();
|
||||
}
|
||||
|
||||
void ListBox::setOutlineThickness (const int newThickness)
|
||||
{
|
||||
outlineThickness = newThickness;
|
||||
resized();
|
||||
}
|
||||
|
||||
void ListBox::setHeaderComponent (Component* const newHeaderComponent)
|
||||
{
|
||||
if (headerComponent != newHeaderComponent)
|
||||
{
|
||||
headerComponent = newHeaderComponent;
|
||||
|
||||
addAndMakeVisible (newHeaderComponent);
|
||||
ListBox::resized();
|
||||
}
|
||||
}
|
||||
|
||||
void ListBox::repaintRow (const int rowNumber) noexcept
|
||||
{
|
||||
repaint (getRowPosition (rowNumber, true));
|
||||
}
|
||||
|
||||
Image ListBox::createSnapshotOfSelectedRows (int& imageX, int& imageY)
|
||||
{
|
||||
Rectangle<int> imageArea;
|
||||
const int firstRow = getRowContainingPosition (0, viewport->getY());
|
||||
|
||||
for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
|
||||
{
|
||||
Component* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i);
|
||||
|
||||
if (rowComp != nullptr && isRowSelected (firstRow + i))
|
||||
{
|
||||
const Point<int> pos (getLocalPoint (rowComp, Point<int>()));
|
||||
const Rectangle<int> rowRect (pos.getX(), pos.getY(), rowComp->getWidth(), rowComp->getHeight());
|
||||
imageArea = imageArea.getUnion (rowRect);
|
||||
}
|
||||
}
|
||||
|
||||
imageArea = imageArea.getIntersection (getLocalBounds());
|
||||
imageX = imageArea.getX();
|
||||
imageY = imageArea.getY();
|
||||
Image snapshot (Image::ARGB, imageArea.getWidth(), imageArea.getHeight(), true);
|
||||
|
||||
for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
|
||||
{
|
||||
Component* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i);
|
||||
|
||||
if (rowComp != nullptr && isRowSelected (firstRow + i))
|
||||
{
|
||||
Graphics g (snapshot);
|
||||
g.setOrigin (getLocalPoint (rowComp, Point<int>()) - imageArea.getPosition());
|
||||
|
||||
if (g.reduceClipRegion (rowComp->getLocalBounds()))
|
||||
{
|
||||
g.beginTransparencyLayer (0.6f);
|
||||
rowComp->paintEntireComponent (g, false);
|
||||
g.endTransparencyLayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void ListBox::startDragAndDrop (const MouseEvent& e, const var& dragDescription, bool allowDraggingToOtherWindows)
|
||||
{
|
||||
if (DragAndDropContainer* const dragContainer = DragAndDropContainer::findParentDragContainerFor (this))
|
||||
{
|
||||
int x, y;
|
||||
Image dragImage (createSnapshotOfSelectedRows (x, y));
|
||||
|
||||
MouseEvent e2 (e.getEventRelativeTo (this));
|
||||
const Point<int> p (x - e2.x, y - e2.y);
|
||||
dragContainer->startDragging (dragDescription, this, dragImage, allowDraggingToOtherWindows, &p);
|
||||
}
|
||||
else
|
||||
{
|
||||
// to be able to do a drag-and-drop operation, the listbox needs to
|
||||
// be inside a component which is also a DragAndDropContainer.
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingComponentToUpdate)
|
||||
{
|
||||
(void) existingComponentToUpdate;
|
||||
jassert (existingComponentToUpdate == nullptr); // indicates a failure in the code that recycles the components
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ListBoxModel::listBoxItemClicked (int, const MouseEvent&) {}
|
||||
void ListBoxModel::listBoxItemDoubleClicked (int, const MouseEvent&) {}
|
||||
void ListBoxModel::backgroundClicked (const MouseEvent&) {}
|
||||
void ListBoxModel::selectedRowsChanged (int) {}
|
||||
void ListBoxModel::deleteKeyPressed (int) {}
|
||||
void ListBoxModel::returnKeyPressed (int) {}
|
||||
void ListBoxModel::listWasScrolled() {}
|
||||
var ListBoxModel::getDragSourceDescription (const SparseSet<int>&) { return var(); }
|
||||
String ListBoxModel::getTooltipForRow (int) { return String::empty; }
|
||||
MouseCursor ListBoxModel::getMouseCursorForRow (int) { return MouseCursor::NormalCursor; }
|
||||
|
|
@ -0,0 +1,587 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_LISTBOX_H_INCLUDED
|
||||
#define JUCE_LISTBOX_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A subclass of this is used to drive a ListBox.
|
||||
|
||||
@see ListBox
|
||||
*/
|
||||
class JUCE_API ListBoxModel
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~ListBoxModel() {}
|
||||
|
||||
//==============================================================================
|
||||
/** This has to return the number of items in the list.
|
||||
@see ListBox::getNumRows()
|
||||
*/
|
||||
virtual int getNumRows() = 0;
|
||||
|
||||
/** This method must be implemented to draw a row of the list.
|
||||
Note that the rowNumber value may be greater than the number of rows in your
|
||||
list, so be careful that you don't assume it's less than getNumRows().
|
||||
*/
|
||||
virtual void paintListBoxItem (int rowNumber,
|
||||
Graphics& g,
|
||||
int width, int height,
|
||||
bool rowIsSelected) = 0;
|
||||
|
||||
/** This is used to create or update a custom component to go in a row of the list.
|
||||
|
||||
Any row may contain a custom component, or can just be drawn with the paintListBoxItem() method
|
||||
and handle mouse clicks with listBoxItemClicked().
|
||||
|
||||
This method will be called whenever a custom component might need to be updated - e.g.
|
||||
when the table is changed, or TableListBox::updateContent() is called.
|
||||
|
||||
If you don't need a custom component for the specified row, then return nullptr.
|
||||
(Bear in mind that even if you're not creating a new component, you may still need to
|
||||
delete existingComponentToUpdate if it's non-null).
|
||||
|
||||
If you do want a custom component, and the existingComponentToUpdate is null, then
|
||||
this method must create a suitable new component and return it.
|
||||
|
||||
If the existingComponentToUpdate is non-null, it will be a pointer to a component previously created
|
||||
by this method. In this case, the method must either update it to make sure it's correctly representing
|
||||
the given row (which may be different from the one that the component was created for), or it can
|
||||
delete this component and return a new one.
|
||||
|
||||
The component that your method returns will be deleted by the ListBox when it is no longer needed.
|
||||
|
||||
Bear in mind that if you put a custom component inside the row but still want the
|
||||
listbox to automatically handle clicking, selection, etc, then you'll need to make sure
|
||||
your custom component doesn't intercept all the mouse events that land on it, e.g by
|
||||
using Component::setInterceptsMouseClicks().
|
||||
*/
|
||||
virtual Component* refreshComponentForRow (int rowNumber, bool isRowSelected,
|
||||
Component* existingComponentToUpdate);
|
||||
|
||||
/** This can be overridden to react to the user clicking on a row.
|
||||
@see listBoxItemDoubleClicked
|
||||
*/
|
||||
virtual void listBoxItemClicked (int row, const MouseEvent&);
|
||||
|
||||
/** This can be overridden to react to the user double-clicking on a row.
|
||||
@see listBoxItemClicked
|
||||
*/
|
||||
virtual void listBoxItemDoubleClicked (int row, const MouseEvent&);
|
||||
|
||||
/** This can be overridden to react to the user clicking on a part of the list where
|
||||
there are no rows.
|
||||
@see listBoxItemClicked
|
||||
*/
|
||||
virtual void backgroundClicked (const MouseEvent&);
|
||||
|
||||
/** Override this to be informed when rows are selected or deselected.
|
||||
|
||||
This will be called whenever a row is selected or deselected. If a range of
|
||||
rows is selected all at once, this will just be called once for that event.
|
||||
|
||||
@param lastRowSelected the last row that the user selected. If no
|
||||
rows are currently selected, this may be -1.
|
||||
*/
|
||||
virtual void selectedRowsChanged (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the delete key is pressed.
|
||||
|
||||
If no rows are selected when they press the key, this won't be called.
|
||||
|
||||
@param lastRowSelected the last row that had been selected when they pressed the
|
||||
key - if there are multiple selections, this might not be
|
||||
very useful
|
||||
*/
|
||||
virtual void deleteKeyPressed (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the return key is pressed.
|
||||
|
||||
If no rows are selected when they press the key, this won't be called.
|
||||
|
||||
@param lastRowSelected the last row that had been selected when they pressed the
|
||||
key - if there are multiple selections, this might not be
|
||||
very useful
|
||||
*/
|
||||
virtual void returnKeyPressed (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the list is scrolled.
|
||||
|
||||
This might be caused by the user moving the scrollbar, or by programmatic changes
|
||||
to the list position.
|
||||
*/
|
||||
virtual void listWasScrolled();
|
||||
|
||||
/** To allow rows from your list to be dragged-and-dropped, implement this method.
|
||||
|
||||
If this returns a non-null variant then when the user drags a row, the listbox will
|
||||
try to find a DragAndDropContainer in its parent hierarchy, and will use it to trigger
|
||||
a drag-and-drop operation, using this string as the source description, with the listbox
|
||||
itself as the source component.
|
||||
|
||||
@see DragAndDropContainer::startDragging
|
||||
*/
|
||||
virtual var getDragSourceDescription (const SparseSet<int>& currentlySelectedRows);
|
||||
|
||||
/** You can override this to provide tool tips for specific rows.
|
||||
@see TooltipClient
|
||||
*/
|
||||
virtual String getTooltipForRow (int row);
|
||||
|
||||
/** You can override this to return a custom mouse cursor for each row. */
|
||||
virtual MouseCursor getMouseCursorForRow (int row);
|
||||
|
||||
private:
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// This method's signature has changed to take a MouseEvent parameter - please update your code!
|
||||
JUCE_DEPRECATED_WITH_BODY (virtual int backgroundClicked(), { return 0; })
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A list of items that can be scrolled vertically.
|
||||
|
||||
To create a list, you'll need to create a subclass of ListBoxModel. This can
|
||||
either paint each row of the list and respond to events via callbacks, or for
|
||||
more specialised tasks, it can supply a custom component to fill each row.
|
||||
|
||||
@see ComboBox, TableListBox
|
||||
*/
|
||||
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::empty,
|
||||
ListBoxModel* model = nullptr);
|
||||
|
||||
/** Destructor. */
|
||||
~ListBox();
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the current data model to display. */
|
||||
void setModel (ListBoxModel* newModel);
|
||||
|
||||
/** Returns the current list model. */
|
||||
ListBoxModel* getModel() const noexcept { return model; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Causes the list to refresh its content.
|
||||
|
||||
Call this when the number of rows in the list changes, or if you want it
|
||||
to call refreshComponentForRow() on all the row components.
|
||||
|
||||
This must only be called from the main message thread.
|
||||
*/
|
||||
void updateContent();
|
||||
|
||||
//==============================================================================
|
||||
/** Turns on multiple-selection of rows.
|
||||
|
||||
By default this is disabled.
|
||||
|
||||
When your row component gets clicked you'll need to call the
|
||||
selectRowsBasedOnModifierKeys() method to tell the list that it's been
|
||||
clicked and to get it to do the appropriate selection based on whether
|
||||
the ctrl/shift keys are held down.
|
||||
*/
|
||||
void setMultipleSelectionEnabled (bool shouldBeEnabled) noexcept;
|
||||
|
||||
/** If enabled, this makes the listbox flip the selection status of
|
||||
each row that the user clicks, without affecting other selected rows.
|
||||
|
||||
(This only has an effect if multiple selection is also enabled).
|
||||
If not enabled, you can still get the same row-flipping behaviour by holding
|
||||
down CMD or CTRL when clicking.
|
||||
*/
|
||||
void setClickingTogglesRowSelection (bool flipRowSelection) noexcept;
|
||||
|
||||
/** 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)
|
||||
*/
|
||||
void selectRangeOfRows (int firstRow,
|
||||
int lastRow);
|
||||
|
||||
/** 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 pointer to the vertical scrollbar. */
|
||||
ScrollBar* getVerticalScrollBar() const noexcept;
|
||||
|
||||
/** Returns a pointer 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 createRowComponent().
|
||||
|
||||
If the component for this row is off-screen or if the row is out-of-range,
|
||||
this will return 0.
|
||||
|
||||
@see getRowContainingPosition
|
||||
*/
|
||||
Component* getComponentForRowNumber (int rowNumber) const noexcept;
|
||||
|
||||
/** Returns the row number that the given component represents.
|
||||
If the component isn't one of the list's rows, this will return -1.
|
||||
*/
|
||||
int getRowNumberOfComponent (Component* rowComponent) const noexcept;
|
||||
|
||||
/** Returns the width of a row (which may be less than the width of this component
|
||||
if there's a scrollbar).
|
||||
*/
|
||||
int getVisibleRowWidth() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the height of each row in the list.
|
||||
The default height is 22 pixels.
|
||||
@see getRowHeight
|
||||
*/
|
||||
void setRowHeight (int newHeight);
|
||||
|
||||
/** Returns the height of a row in the list.
|
||||
@see setRowHeight
|
||||
*/
|
||||
int getRowHeight() const noexcept { return rowHeight; }
|
||||
|
||||
/** Returns the number of rows actually visible.
|
||||
|
||||
This is the number of whole rows which will fit on-screen, so the value might
|
||||
be more than the actual number of rows in the list.
|
||||
*/
|
||||
int getNumRowsOnScreen() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the label.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1002800, /**< The background colour to fill the list with.
|
||||
Make this transparent if you don't want the background to be filled. */
|
||||
outlineColourId = 0x1002810, /**< An optional colour to use to draw a border around the list.
|
||||
Make this transparent to not have an outline. */
|
||||
textColourId = 0x1002820 /**< The preferred colour to use for drawing text in the listbox. */
|
||||
};
|
||||
|
||||
/** Sets the thickness of a border that will be drawn around the box.
|
||||
|
||||
To set the colour of the outline, use @code setColour (ListBox::outlineColourId, colourXYZ); @endcode
|
||||
@see outlineColourId
|
||||
*/
|
||||
void setOutlineThickness (int outlineThickness);
|
||||
|
||||
/** Returns the thickness of outline that will be drawn around the listbox.
|
||||
|
||||
@see setOutlineColour
|
||||
*/
|
||||
int getOutlineThickness() const noexcept { return outlineThickness; }
|
||||
|
||||
/** Sets a component that the list should use as a header.
|
||||
|
||||
This will position the given component at the top of the list, maintaining the
|
||||
height of the component passed-in, but rescaling it horizontally to match the
|
||||
width of the items in the listbox.
|
||||
|
||||
The component will be deleted when setHeaderComponent() is called with a
|
||||
different component, or when the listbox is deleted.
|
||||
*/
|
||||
void setHeaderComponent (Component* newHeaderComponent);
|
||||
|
||||
/** 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 just shows 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 createSnapshotOfSelectedRows (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 var& dragDescription, bool allowDraggingToOtherWindows);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (class ListViewport)
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (class RowComponent)
|
||||
friend class ListViewport;
|
||||
friend class TableListBox;
|
||||
ListBoxModel* model;
|
||||
ScopedPointer<ListViewport> viewport;
|
||||
ScopedPointer<Component> headerComponent;
|
||||
ScopedPointer<MouseListener> mouseMoveSelector;
|
||||
int totalItems, rowHeight, minimumRowWidth;
|
||||
int outlineThickness;
|
||||
int lastRowSelected;
|
||||
bool multipleSelection, alwaysFlipSelection, hasDoneInitialUpdate;
|
||||
SparseSet<int> selected;
|
||||
|
||||
void selectRowInternal (int rowNumber, bool dontScrollToShowThisRow,
|
||||
bool deselectOthersFirst, bool isMouseClick);
|
||||
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// This method's bool parameter has changed: see the new method signature.
|
||||
JUCE_DEPRECATED (void setSelectedRows (const SparseSet<int>&, bool));
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListBox)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_LISTBOX_H_INCLUDED
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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 (findColour (backgroundColourId).isOpaque());
|
||||
}
|
||||
|
||||
void ProgressBar::colourChanged()
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void ProgressBar::paint (Graphics& g)
|
||||
{
|
||||
String text;
|
||||
|
||||
if (displayPercentage)
|
||||
{
|
||||
if (currentValue >= 0 && currentValue <= 1.0)
|
||||
text << roundToInt (currentValue * 100.0) << '%';
|
||||
}
|
||||
else
|
||||
{
|
||||
text = displayedMessage;
|
||||
}
|
||||
|
||||
getLookAndFeel().drawProgressBar (g, *this,
|
||||
getWidth(), getHeight(),
|
||||
currentValue, text);
|
||||
}
|
||||
|
||||
void ProgressBar::visibilityChanged()
|
||||
{
|
||||
if (isVisible())
|
||||
startTimer (30);
|
||||
else
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
void ProgressBar::timerCallback()
|
||||
{
|
||||
double newProgress = progress;
|
||||
|
||||
const uint32 now = Time::getMillisecondCounter();
|
||||
const int timeSinceLastCallback = (int) (now - lastCallbackTime);
|
||||
lastCallbackTime = now;
|
||||
|
||||
if (currentValue != newProgress
|
||||
|| newProgress < 0 || newProgress >= 1.0
|
||||
|| currentMessage != displayedMessage)
|
||||
{
|
||||
if (currentValue < newProgress
|
||||
&& newProgress >= 0 && newProgress < 1.0
|
||||
&& currentValue >= 0 && currentValue < 1.0)
|
||||
{
|
||||
newProgress = jmin (currentValue + 0.0008 * timeSinceLastCallback,
|
||||
newProgress);
|
||||
}
|
||||
|
||||
currentValue = newProgress;
|
||||
currentMessage = displayedMessage;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_PROGRESSBAR_H_INCLUDED
|
||||
#define JUCE_PROGRESSBAR_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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.
|
||||
|
||||
For an easy way of running a background task with a dialog box showing its
|
||||
progress, see the ThreadWithProgressWindow class.
|
||||
|
||||
@see ThreadWithProgressWindow
|
||||
*/
|
||||
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. Obviously
|
||||
you'd better be careful not to delete this variable while the
|
||||
ProgressBar still exists!
|
||||
*/
|
||||
explicit ProgressBar (double& progress);
|
||||
|
||||
/** Destructor. */
|
||||
~ProgressBar();
|
||||
|
||||
//==============================================================================
|
||||
/** Turns the percentage display on or off.
|
||||
|
||||
By default this is on, and the progress bar will display a text string showing
|
||||
its current percentage.
|
||||
*/
|
||||
void setPercentageDisplay (bool shouldDisplayPercentage);
|
||||
|
||||
/** Gives the progress bar a string to display inside it.
|
||||
|
||||
If you call this, it will turn off the percentage display.
|
||||
@see setPercentageDisplay
|
||||
*/
|
||||
void setTextToDisplay (const String& text);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the bar.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1001900, /**< The background colour, behind the bar. */
|
||||
foregroundColourId = 0x1001a00, /**< The colour to use to draw the bar itself. LookAndFeel
|
||||
classes will probably use variations on this colour. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
/** Draws a progress bar.
|
||||
|
||||
If the progress value is less than 0 or greater than 1.0, this should draw a spinning
|
||||
bar that fills the whole space (i.e. to say that the app is still busy but the progress
|
||||
isn't known). It can use the current time as a basis for playing an animation.
|
||||
|
||||
(Used by progress bars in AlertWindow).
|
||||
*/
|
||||
virtual void drawProgressBar (Graphics&, ProgressBar&, int width, int height,
|
||||
double progress, const String& textToShow) = 0;
|
||||
};
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
void visibilityChanged() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
private:
|
||||
double& progress;
|
||||
double currentValue;
|
||||
bool displayPercentage;
|
||||
String displayedMessage, currentMessage;
|
||||
uint32 lastCallbackTime;
|
||||
|
||||
void timerCallback() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgressBar)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_PROGRESSBAR_H_INCLUDED
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,896 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_SLIDER_H_INCLUDED
|
||||
#define JUCE_SLIDER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A slider control for changing a value.
|
||||
|
||||
The slider can be horizontal, vertical, or rotary, and can optionally have
|
||||
a text-box inside it to show an editable display of the current value.
|
||||
|
||||
To use it, create a Slider object and use the setSliderStyle() method
|
||||
to set up the type you want. To set up the text-entry box, use setTextBoxStyle().
|
||||
|
||||
To define the values that it can be set to, see the setRange() and setValue() methods.
|
||||
|
||||
There are also lots of custom tweaks you can do by subclassing and overriding
|
||||
some of the virtual methods, such as changing the scaling, changing the format of
|
||||
the text display, custom ways of limiting the values, etc.
|
||||
|
||||
You can register Slider::Listener objects with a slider, and they'll be called when
|
||||
the value changes.
|
||||
|
||||
@see Slider::Listener
|
||||
*/
|
||||
class JUCE_API Slider : public Component,
|
||||
public SettableTooltipClient
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** The types of slider available.
|
||||
|
||||
@see setSliderStyle, setRotaryParameters
|
||||
*/
|
||||
enum SliderStyle
|
||||
{
|
||||
LinearHorizontal, /**< A traditional horizontal slider. */
|
||||
LinearVertical, /**< A traditional vertical slider. */
|
||||
LinearBar, /**< A horizontal bar slider with the text label drawn on top of it. */
|
||||
LinearBarVertical,
|
||||
Rotary, /**< A rotary control that you move by dragging the mouse in a circular motion, like a knob.
|
||||
@see setRotaryParameters */
|
||||
RotaryHorizontalDrag, /**< A rotary control that you move by dragging the mouse left-to-right.
|
||||
@see setRotaryParameters */
|
||||
RotaryVerticalDrag, /**< A rotary control that you move by dragging the mouse up-and-down.
|
||||
@see setRotaryParameters */
|
||||
RotaryHorizontalVerticalDrag, /**< A rotary control that you move by dragging the mouse up-and-down or left-to-right.
|
||||
@see setRotaryParameters */
|
||||
IncDecButtons, /**< A pair of buttons that increment or decrement the slider's value by the increment set in setRange(). */
|
||||
|
||||
TwoValueHorizontal, /**< A horizontal slider that has two thumbs instead of one, so it can show a minimum and maximum value.
|
||||
@see setMinValue, setMaxValue */
|
||||
TwoValueVertical, /**< A vertical slider that has two thumbs instead of one, so it can show a minimum and maximum value.
|
||||
@see setMinValue, setMaxValue */
|
||||
|
||||
ThreeValueHorizontal, /**< A horizontal slider that has three thumbs instead of one, so it can show a minimum and maximum
|
||||
value, with the current value being somewhere between them.
|
||||
@see setMinValue, setMaxValue */
|
||||
ThreeValueVertical, /**< A vertical slider that has three thumbs instead of one, so it can show a minimum and maximum
|
||||
value, with the current value being somewhere between them.
|
||||
@see setMinValue, setMaxValue */
|
||||
};
|
||||
|
||||
/** The position of the slider's text-entry box.
|
||||
@see setTextBoxStyle
|
||||
*/
|
||||
enum TextEntryBoxPosition
|
||||
{
|
||||
NoTextBox, /**< Doesn't display a text box. */
|
||||
TextBoxLeft, /**< Puts the text box to the left of the slider, vertically centred. */
|
||||
TextBoxRight, /**< Puts the text box to the right of the slider, vertically centred. */
|
||||
TextBoxAbove, /**< Puts the text box above the slider, horizontally centred. */
|
||||
TextBoxBelow /**< Puts the text box below the slider, horizontally centred. */
|
||||
};
|
||||
|
||||
/** Describes the type of mouse-dragging that is happening when a value is being changed.
|
||||
@see snapValue
|
||||
*/
|
||||
enum DragMode
|
||||
{
|
||||
notDragging, /**< Dragging is not active. */
|
||||
absoluteDrag, /**< The dragging corresponds directly to the value that is displayed. */
|
||||
velocityDrag /**< The dragging value change is relative to the velocity of the mouse mouvement. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a slider.
|
||||
When created, you can set up the slider's style and range with setSliderStyle(), setRange(), etc.
|
||||
*/
|
||||
Slider();
|
||||
|
||||
/** Creates a slider.
|
||||
When created, you can set up the slider's style and range with setSliderStyle(), setRange(), etc.
|
||||
*/
|
||||
explicit Slider (const String& componentName);
|
||||
|
||||
/** Creates a slider with some explicit options. */
|
||||
Slider (SliderStyle style, TextEntryBoxPosition textBoxPosition);
|
||||
|
||||
/** Destructor. */
|
||||
~Slider();
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the type of slider interface being used.
|
||||
|
||||
@param newStyle the type of interface
|
||||
@see setRotaryParameters, setVelocityBasedMode,
|
||||
*/
|
||||
void setSliderStyle (SliderStyle newStyle);
|
||||
|
||||
/** Returns the slider's current style.
|
||||
@see setSliderStyle
|
||||
*/
|
||||
SliderStyle getSliderStyle() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the properties of a rotary slider.
|
||||
|
||||
@param startAngleRadians the angle (in radians, clockwise from the top) at which
|
||||
the slider's minimum value is represented
|
||||
@param endAngleRadians the angle (in radians, clockwise from the top) at which
|
||||
the slider's maximum value is represented. This must be
|
||||
greater than startAngleRadians
|
||||
@param stopAtEnd determines what happens when a circular drag action rotates beyond
|
||||
the minimum or maximum angle. If true, the value will stop changing
|
||||
until the mouse moves back the way it came; if false, the value
|
||||
will snap back to the value nearest to the mouse. Note that this has
|
||||
no effect if the drag mode is vertical or horizontal.
|
||||
*/
|
||||
void setRotaryParameters (float startAngleRadians,
|
||||
float endAngleRadians,
|
||||
bool stopAtEnd);
|
||||
|
||||
/** Sets the distance the mouse has to move to drag the slider across
|
||||
the full extent of its range.
|
||||
|
||||
This only applies when in modes like RotaryHorizontalDrag, where it's using
|
||||
relative mouse movements to adjust the slider.
|
||||
*/
|
||||
void setMouseDragSensitivity (int distanceForFullScaleDrag);
|
||||
|
||||
/** Returns the current sensitivity value set by setMouseDragSensitivity(). */
|
||||
int getMouseDragSensitivity() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the way the mouse is used when dragging the slider.
|
||||
|
||||
If true, this will turn on velocity-sensitive dragging, so that
|
||||
the faster the mouse moves, the bigger the movement to the slider. This
|
||||
helps when making accurate adjustments if the slider's range is quite large.
|
||||
|
||||
If false, the slider will just try to snap to wherever the mouse is.
|
||||
*/
|
||||
void setVelocityBasedMode (bool isVelocityBased);
|
||||
|
||||
/** Returns true if velocity-based mode is active.
|
||||
@see setVelocityBasedMode
|
||||
*/
|
||||
bool getVelocityBasedMode() const noexcept;
|
||||
|
||||
/** Changes aspects of the scaling used when in velocity-sensitive mode.
|
||||
|
||||
These apply when you've used setVelocityBasedMode() to turn on velocity mode,
|
||||
or if you're holding down ctrl.
|
||||
|
||||
@param sensitivity higher values than 1.0 increase the range of acceleration used
|
||||
@param threshold the minimum number of pixels that the mouse needs to move for it
|
||||
to be treated as a movement
|
||||
@param offset values greater than 0.0 increase the minimum speed that will be used when
|
||||
the threshold is reached
|
||||
@param userCanPressKeyToSwapMode if true, then the user can hold down the ctrl or command
|
||||
key to toggle velocity-sensitive mode
|
||||
*/
|
||||
void setVelocityModeParameters (double sensitivity = 1.0,
|
||||
int threshold = 1,
|
||||
double offset = 0.0,
|
||||
bool userCanPressKeyToSwapMode = true);
|
||||
|
||||
/** Returns the velocity sensitivity setting.
|
||||
@see setVelocityModeParameters
|
||||
*/
|
||||
double getVelocitySensitivity() const noexcept;
|
||||
|
||||
/** Returns the velocity threshold setting.
|
||||
@see setVelocityModeParameters
|
||||
*/
|
||||
int getVelocityThreshold() const noexcept;
|
||||
|
||||
/** Returns the velocity offset setting.
|
||||
@see setVelocityModeParameters
|
||||
*/
|
||||
double getVelocityOffset() const noexcept;
|
||||
|
||||
/** Returns the velocity user key setting.
|
||||
@see setVelocityModeParameters
|
||||
*/
|
||||
bool getVelocityModeIsSwappable() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets up a skew factor to alter the way values are distributed.
|
||||
|
||||
You may want to use a range of values on the slider where more accuracy
|
||||
is required towards one end of the range, so this will logarithmically
|
||||
spread the values across the length of the slider.
|
||||
|
||||
If the factor is < 1.0, the lower end of the range will fill more of the
|
||||
slider's length; if the factor is > 1.0, the upper end of the range
|
||||
will be expanded instead. A factor of 1.0 doesn't skew it at all.
|
||||
|
||||
To set the skew position by using a mid-point, use the setSkewFactorFromMidPoint()
|
||||
method instead.
|
||||
|
||||
@see getSkewFactor, setSkewFactorFromMidPoint
|
||||
*/
|
||||
void setSkewFactor (double factor);
|
||||
|
||||
/** Sets up a skew factor to alter the way values are distributed.
|
||||
|
||||
This allows you to specify the slider value that should appear in the
|
||||
centre of the slider's visible range.
|
||||
|
||||
@see setSkewFactor, getSkewFactor
|
||||
*/
|
||||
void setSkewFactorFromMidPoint (double sliderValueToShowAtMidPoint);
|
||||
|
||||
/** Returns the current skew factor.
|
||||
|
||||
See setSkewFactor for more info.
|
||||
|
||||
@see setSkewFactor, setSkewFactorFromMidPoint
|
||||
*/
|
||||
double getSkewFactor() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Used by setIncDecButtonsMode().
|
||||
*/
|
||||
enum IncDecButtonMode
|
||||
{
|
||||
incDecButtonsNotDraggable,
|
||||
incDecButtonsDraggable_AutoDirection,
|
||||
incDecButtonsDraggable_Horizontal,
|
||||
incDecButtonsDraggable_Vertical
|
||||
};
|
||||
|
||||
/** When the style is IncDecButtons, this lets you turn on a mode where the mouse
|
||||
can be dragged on the buttons to drag the values.
|
||||
|
||||
By default this is turned off. When enabled, clicking on the buttons still works
|
||||
them as normal, but by holding down the mouse on a button and dragging it a little
|
||||
distance, it flips into a mode where the value can be dragged. The drag direction can
|
||||
either be set explicitly to be vertical or horizontal, or can be set to
|
||||
incDecButtonsDraggable_AutoDirection so that it depends on whether the buttons
|
||||
are side-by-side or above each other.
|
||||
*/
|
||||
void setIncDecButtonsMode (IncDecButtonMode mode);
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the location and properties of the text-entry box.
|
||||
|
||||
@param newPosition where it should go (or NoTextBox to not have one at all)
|
||||
@param isReadOnly if true, it's a read-only display
|
||||
@param textEntryBoxWidth the width of the text-box in pixels. Make sure this leaves enough
|
||||
room for the slider as well!
|
||||
@param textEntryBoxHeight the height of the text-box in pixels. Make sure this leaves enough
|
||||
room for the slider as well!
|
||||
|
||||
@see setTextBoxIsEditable, getValueFromText, getTextFromValue
|
||||
*/
|
||||
void setTextBoxStyle (TextEntryBoxPosition newPosition,
|
||||
bool isReadOnly,
|
||||
int textEntryBoxWidth,
|
||||
int textEntryBoxHeight);
|
||||
|
||||
/** Returns the status of the text-box.
|
||||
@see setTextBoxStyle
|
||||
*/
|
||||
TextEntryBoxPosition getTextBoxPosition() const noexcept;
|
||||
|
||||
/** Returns the width used for the text-box.
|
||||
@see setTextBoxStyle
|
||||
*/
|
||||
int getTextBoxWidth() const noexcept;
|
||||
|
||||
/** Returns the height used for the text-box.
|
||||
@see setTextBoxStyle
|
||||
*/
|
||||
int getTextBoxHeight() const noexcept;
|
||||
|
||||
/** Makes the text-box editable.
|
||||
|
||||
By default this is true, and the user can enter values into the textbox,
|
||||
but it can be turned off if that's not suitable.
|
||||
|
||||
@see setTextBoxStyle, getValueFromText, getTextFromValue
|
||||
*/
|
||||
void setTextBoxIsEditable (bool shouldBeEditable);
|
||||
|
||||
/** Returns true if the text-box is read-only.
|
||||
@see setTextBoxStyle
|
||||
*/
|
||||
bool isTextBoxEditable() const noexcept;
|
||||
|
||||
/** If the text-box is editable, this will give it the focus so that the user can
|
||||
type directly into it.
|
||||
This is basically the effect as the user clicking on it.
|
||||
*/
|
||||
void showTextBox();
|
||||
|
||||
/** If the text-box currently has focus and is being edited, this resets it and takes keyboard
|
||||
focus away from it.
|
||||
|
||||
@param discardCurrentEditorContents if true, the slider's value will be left
|
||||
unchanged; if false, the current contents of the
|
||||
text editor will be used to set the slider position
|
||||
before it is hidden.
|
||||
*/
|
||||
void hideTextBox (bool discardCurrentEditorContents);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the slider's current value.
|
||||
|
||||
This will trigger a callback to Slider::Listener::sliderValueChanged() for any listeners
|
||||
that are registered, and will synchronously call the valueChanged() method in case subclasses
|
||||
want to handle it.
|
||||
|
||||
@param newValue the new value to set - this will be restricted by the
|
||||
minimum and maximum range, and will be snapped to the
|
||||
nearest interval if one has been set
|
||||
@param notification can be one of the NotificationType values, to request
|
||||
a synchronous or asynchronous call to the valueChanged() method
|
||||
of any Slider::Listeners that are registered.
|
||||
*/
|
||||
void setValue (double newValue, NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Returns the slider's current value. */
|
||||
double getValue() const;
|
||||
|
||||
/** Returns the Value object that represents the slider's current position.
|
||||
You can use this Value object to connect the slider's position to external values or setters,
|
||||
either by taking a copy of the Value, or by using Value::referTo() to make it point to
|
||||
your own Value object.
|
||||
@see Value, getMaxValue, getMinValueObject
|
||||
*/
|
||||
Value& getValueObject() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the limits that the slider's value can take.
|
||||
|
||||
@param newMinimum the lowest value allowed
|
||||
@param newMaximum the highest value allowed
|
||||
@param newInterval the steps in which the value is allowed to increase - if this
|
||||
is not zero, the value will always be (newMinimum + (newInterval * an integer)).
|
||||
*/
|
||||
void setRange (double newMinimum,
|
||||
double newMaximum,
|
||||
double newInterval = 0);
|
||||
|
||||
/** Returns the current maximum value.
|
||||
@see setRange
|
||||
*/
|
||||
double getMaximum() const noexcept;
|
||||
|
||||
/** Returns the current minimum value.
|
||||
@see setRange
|
||||
*/
|
||||
double getMinimum() const noexcept;
|
||||
|
||||
/** Returns the current step-size for values.
|
||||
@see setRange
|
||||
*/
|
||||
double getInterval() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** For a slider with two or three thumbs, this returns the lower of its values.
|
||||
|
||||
For a two-value slider, the values are controlled with getMinValue() and getMaxValue().
|
||||
A slider with three values also uses the normal getValue() and setValue() methods to
|
||||
control the middle value.
|
||||
|
||||
@see setMinValue, getMaxValue, TwoValueHorizontal, TwoValueVertical, ThreeValueHorizontal, ThreeValueVertical
|
||||
*/
|
||||
double getMinValue() const;
|
||||
|
||||
/** For a slider with two or three thumbs, this returns the lower of its values.
|
||||
You can use this Value object to connect the slider's position to external values or setters,
|
||||
either by taking a copy of the Value, or by using Value::referTo() to make it point to
|
||||
your own Value object.
|
||||
@see Value, getMinValue, getMaxValueObject
|
||||
*/
|
||||
Value& getMinValueObject() noexcept;
|
||||
|
||||
/** For a slider with two or three thumbs, this sets the lower of its values.
|
||||
|
||||
This will trigger a callback to Slider::Listener::sliderValueChanged() for any listeners
|
||||
that are registered, and will synchronously call the valueChanged() method in case subclasses
|
||||
want to handle it.
|
||||
|
||||
@param newValue the new value to set - this will be restricted by the
|
||||
minimum and maximum range, and will be snapped to the nearest
|
||||
interval if one has been set.
|
||||
@param notification can be one of the NotificationType values, to request
|
||||
a synchronous or asynchronous call to the valueChanged() method
|
||||
of any Slider::Listeners that are registered.
|
||||
@param allowNudgingOfOtherValues if false, this value will be restricted to being below the
|
||||
max value (in a two-value slider) or the mid value (in a three-value
|
||||
slider). If true, then if this value goes beyond those values,
|
||||
it will push them along with it.
|
||||
@see getMinValue, setMaxValue, setValue
|
||||
*/
|
||||
void setMinValue (double newValue,
|
||||
NotificationType notification = sendNotificationAsync,
|
||||
bool allowNudgingOfOtherValues = false);
|
||||
|
||||
/** For a slider with two or three thumbs, this returns the higher of its values.
|
||||
|
||||
For a two-value slider, the values are controlled with getMinValue() and getMaxValue().
|
||||
A slider with three values also uses the normal getValue() and setValue() methods to
|
||||
control the middle value.
|
||||
|
||||
@see getMinValue, TwoValueHorizontal, TwoValueVertical, ThreeValueHorizontal, ThreeValueVertical
|
||||
*/
|
||||
double getMaxValue() const;
|
||||
|
||||
/** For a slider with two or three thumbs, this returns the higher of its values.
|
||||
You can use this Value object to connect the slider's position to external values or setters,
|
||||
either by taking a copy of the Value, or by using Value::referTo() to make it point to
|
||||
your own Value object.
|
||||
@see Value, getMaxValue, getMinValueObject
|
||||
*/
|
||||
Value& getMaxValueObject() noexcept;
|
||||
|
||||
/** For a slider with two or three thumbs, this sets the lower of its values.
|
||||
|
||||
This will trigger a callback to Slider::Listener::sliderValueChanged() for any listeners
|
||||
that are registered, and will synchronously call the valueChanged() method in case subclasses
|
||||
want to handle it.
|
||||
|
||||
@param newValue the new value to set - this will be restricted by the
|
||||
minimum and maximum range, and will be snapped to the nearest
|
||||
interval if one has been set.
|
||||
@param notification can be one of the NotificationType values, to request
|
||||
a synchronous or asynchronous call to the valueChanged() method
|
||||
of any Slider::Listeners that are registered.
|
||||
@param allowNudgingOfOtherValues if false, this value will be restricted to being above the
|
||||
min value (in a two-value slider) or the mid value (in a three-value
|
||||
slider). If true, then if this value goes beyond those values,
|
||||
it will push them along with it.
|
||||
@see getMaxValue, setMinValue, setValue
|
||||
*/
|
||||
void setMaxValue (double newValue,
|
||||
NotificationType notification = sendNotificationAsync,
|
||||
bool allowNudgingOfOtherValues = false);
|
||||
|
||||
/** For a slider with two or three thumbs, this sets the minimum and maximum thumb positions.
|
||||
|
||||
This will trigger a callback to Slider::Listener::sliderValueChanged() for any listeners
|
||||
that are registered, and will synchronously call the valueChanged() method in case subclasses
|
||||
want to handle it.
|
||||
|
||||
@param newMinValue the new minimum value to set - this will be snapped to the
|
||||
nearest interval if one has been set.
|
||||
@param newMaxValue the new minimum value to set - this will be snapped to the
|
||||
nearest interval if one has been set.
|
||||
@param notification can be one of the NotificationType values, to request
|
||||
a synchronous or asynchronous call to the valueChanged() method
|
||||
of any Slider::Listeners that are registered.
|
||||
@see setMaxValue, setMinValue, setValue
|
||||
*/
|
||||
void setMinAndMaxValues (double newMinValue, double newMaxValue,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
//==============================================================================
|
||||
/** A class for receiving callbacks from a Slider.
|
||||
|
||||
To be told when a slider's value changes, you can register a Slider::Listener
|
||||
object using Slider::addListener().
|
||||
|
||||
@see Slider::addListener, Slider::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
//==============================================================================
|
||||
/** Called when the slider's value is changed.
|
||||
|
||||
This may be caused by dragging it, or by typing in its text entry box,
|
||||
or by a call to Slider::setValue().
|
||||
|
||||
You can find out the new value using Slider::getValue().
|
||||
|
||||
@see Slider::valueChanged
|
||||
*/
|
||||
virtual void sliderValueChanged (Slider* slider) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Called when the slider is about to be dragged.
|
||||
|
||||
This is called when a drag begins, then it's followed by multiple calls
|
||||
to sliderValueChanged(), and then sliderDragEnded() is called after the
|
||||
user lets go.
|
||||
|
||||
@see sliderDragEnded, Slider::startedDragging
|
||||
*/
|
||||
virtual void sliderDragStarted (Slider*) {}
|
||||
|
||||
/** Called after a drag operation has finished.
|
||||
@see sliderDragStarted, Slider::stoppedDragging
|
||||
*/
|
||||
virtual void sliderDragEnded (Slider*) {}
|
||||
};
|
||||
|
||||
/** Adds a listener to be called when this slider's value changes. */
|
||||
void addListener (Listener* listener);
|
||||
|
||||
/** Removes a previously-registered listener. */
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** This lets you choose whether double-clicking moves the slider to a given position.
|
||||
|
||||
By default this is turned off, but it's handy if you want a double-click to act
|
||||
as a quick way of resetting a slider. Just pass in the value you want it to
|
||||
go to when double-clicked.
|
||||
|
||||
@see getDoubleClickReturnValue
|
||||
*/
|
||||
void setDoubleClickReturnValue (bool isDoubleClickEnabled,
|
||||
double valueToSetOnDoubleClick);
|
||||
|
||||
/** Returns the values last set by setDoubleClickReturnValue() method.
|
||||
|
||||
Sets isEnabled to true if double-click is enabled, and returns the value
|
||||
that was set.
|
||||
|
||||
@see setDoubleClickReturnValue
|
||||
*/
|
||||
double getDoubleClickReturnValue (bool& isEnabled) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Tells the slider whether to keep sending change messages while the user
|
||||
is dragging the slider.
|
||||
|
||||
If set to true, a change message will only be sent when the user has
|
||||
dragged the slider and let go. If set to false (the default), then messages
|
||||
will be continuously sent as they drag it while the mouse button is still
|
||||
held down.
|
||||
*/
|
||||
void setChangeNotificationOnlyOnRelease (bool onlyNotifyOnRelease);
|
||||
|
||||
/** This lets you change whether the slider thumb jumps to the mouse position
|
||||
when you click.
|
||||
|
||||
By default, this is true. If it's false, then the slider moves with relative
|
||||
motion when you drag it.
|
||||
|
||||
This only applies to linear bars, and won't affect two- or three- value
|
||||
sliders.
|
||||
*/
|
||||
void setSliderSnapsToMousePosition (bool shouldSnapToMouse);
|
||||
|
||||
/** Returns true if setSliderSnapsToMousePosition() has been enabled. */
|
||||
bool getSliderSnapsToMousePosition() const noexcept;
|
||||
|
||||
/** If enabled, this gives the slider a pop-up bubble which appears while the
|
||||
slider is being dragged.
|
||||
|
||||
This can be handy if your slider doesn't have a text-box, so that users can
|
||||
see the value just when they're changing it.
|
||||
|
||||
If you pass a component as the parentComponentToUse parameter, the pop-up
|
||||
bubble will be added as a child of that component when it's needed. If you
|
||||
pass 0, the pop-up will be placed on the desktop instead (note that it's a
|
||||
transparent window, so if you're using an OS that can't do transparent windows
|
||||
you'll have to add it to a parent component instead).
|
||||
*/
|
||||
void setPopupDisplayEnabled (bool isEnabled, Component* parentComponentToUse);
|
||||
|
||||
/** If a popup display is enabled and is currently visible, this returns the component
|
||||
that is being shown, or nullptr if none is currently in use.
|
||||
@see setPopupDisplayEnabled
|
||||
*/
|
||||
Component* getCurrentPopupDisplay() const noexcept;
|
||||
|
||||
/** If this is set to true, then right-clicking on the slider will pop-up
|
||||
a menu to let the user change the way it works.
|
||||
|
||||
By default this is turned off, but when turned on, the menu will include
|
||||
things like velocity sensitivity, and for rotary sliders, whether they
|
||||
use a linear or rotary mouse-drag to move them.
|
||||
*/
|
||||
void setPopupMenuEnabled (bool menuEnabled);
|
||||
|
||||
/** This can be used to stop the mouse scroll-wheel from moving the slider.
|
||||
By default it's enabled.
|
||||
*/
|
||||
void setScrollWheelEnabled (bool enabled);
|
||||
|
||||
/** Returns a number to indicate which thumb is currently being dragged by the mouse.
|
||||
|
||||
This will return 0 for the main thumb, 1 for the minimum-value thumb, 2 for
|
||||
the maximum-value thumb, or -1 if none is currently down.
|
||||
*/
|
||||
int getThumbBeingDragged() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Callback to indicate that the user is about to start dragging the slider.
|
||||
@see Slider::Listener::sliderDragStarted
|
||||
*/
|
||||
virtual void startedDragging();
|
||||
|
||||
/** Callback to indicate that the user has just stopped dragging the slider.
|
||||
@see Slider::Listener::sliderDragEnded
|
||||
*/
|
||||
virtual void stoppedDragging();
|
||||
|
||||
/** Callback to indicate that the user has just moved the slider.
|
||||
@see Slider::Listener::sliderValueChanged
|
||||
*/
|
||||
virtual void valueChanged();
|
||||
|
||||
//==============================================================================
|
||||
/** Subclasses can override this to convert a text string to a value.
|
||||
|
||||
When the user enters something into the text-entry box, this method is
|
||||
called to convert it to a value.
|
||||
The default implementation just tries to convert it to a double.
|
||||
|
||||
@see getTextFromValue
|
||||
*/
|
||||
virtual double getValueFromText (const String& text);
|
||||
|
||||
/** Turns the slider's current value into a text string.
|
||||
|
||||
Subclasses can override this to customise the formatting of the text-entry box.
|
||||
|
||||
The default implementation just turns the value into a string, using
|
||||
a number of decimal places based on the range interval. If a suffix string
|
||||
has been set using setTextValueSuffix(), this will be appended to the text.
|
||||
|
||||
@see getValueFromText
|
||||
*/
|
||||
virtual String getTextFromValue (double value);
|
||||
|
||||
/** Sets a suffix to append to the end of the numeric value when it's displayed as
|
||||
a string.
|
||||
|
||||
This is used by the default implementation of getTextFromValue(), and is just
|
||||
appended to the numeric value. For more advanced formatting, you can override
|
||||
getTextFromValue() and do something else.
|
||||
*/
|
||||
void setTextValueSuffix (const String& suffix);
|
||||
|
||||
/** Returns the suffix that was set by setTextValueSuffix(). */
|
||||
String getTextValueSuffix() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Allows a user-defined mapping of distance along the slider to its value.
|
||||
|
||||
The default implementation for this performs the skewing operation that
|
||||
can be set up in the setSkewFactor() method. Override it if you need
|
||||
some kind of custom mapping instead, but make sure you also implement the
|
||||
inverse function in valueToProportionOfLength().
|
||||
|
||||
@param proportion a value 0 to 1.0, indicating a distance along the slider
|
||||
@returns the slider value that is represented by this position
|
||||
@see valueToProportionOfLength
|
||||
*/
|
||||
virtual double proportionOfLengthToValue (double proportion);
|
||||
|
||||
/** Allows a user-defined mapping of value to the position of the slider along its length.
|
||||
|
||||
The default implementation for this performs the skewing operation that
|
||||
can be set up in the setSkewFactor() method. Override it if you need
|
||||
some kind of custom mapping instead, but make sure you also implement the
|
||||
inverse function in proportionOfLengthToValue().
|
||||
|
||||
@param value a valid slider value, between the range of values specified in
|
||||
setRange()
|
||||
@returns a value 0 to 1.0 indicating the distance along the slider that
|
||||
represents this value
|
||||
@see proportionOfLengthToValue
|
||||
*/
|
||||
virtual double valueToProportionOfLength (double value);
|
||||
|
||||
/** Returns the X or Y coordinate of a value along the slider's length.
|
||||
|
||||
If the slider is horizontal, this will be the X coordinate of the given
|
||||
value, relative to the left of the slider. If it's vertical, then this will
|
||||
be the Y coordinate, relative to the top of the slider.
|
||||
|
||||
If the slider is rotary, this will throw an assertion and return 0. If the
|
||||
value is out-of-range, it will be constrained to the length of the slider.
|
||||
*/
|
||||
float getPositionOfValue (double value);
|
||||
|
||||
//==============================================================================
|
||||
/** This can be overridden to allow the slider to snap to user-definable values.
|
||||
|
||||
If overridden, it will be called when the user tries to move the slider to
|
||||
a given position, and allows a subclass to sanity-check this value, possibly
|
||||
returning a different value to use instead.
|
||||
|
||||
@param attemptedValue the value the user is trying to enter
|
||||
@param dragMode indicates whether the user is dragging with
|
||||
the mouse; notDragging if they are entering the value
|
||||
using the text box or other non-dragging interaction
|
||||
@returns the value to use instead
|
||||
*/
|
||||
virtual double snapValue (double attemptedValue, DragMode dragMode);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** This can be called to force the text box to update its contents.
|
||||
(Not normally needed, as this is done automatically).
|
||||
*/
|
||||
void updateText();
|
||||
|
||||
/** True if the slider moves horizontally. */
|
||||
bool isHorizontal() const noexcept;
|
||||
/** True if the slider moves vertically. */
|
||||
bool isVertical() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the slider.
|
||||
|
||||
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 = 0x1001200, /**< A colour to use to fill the slider's background. */
|
||||
thumbColourId = 0x1001300, /**< The colour to draw the thumb with. It's up to the look
|
||||
and feel class how this is used. */
|
||||
trackColourId = 0x1001310, /**< The colour to draw the groove that the thumb moves along. */
|
||||
rotarySliderFillColourId = 0x1001311, /**< For rotary sliders, this colour fills the outer curve. */
|
||||
rotarySliderOutlineColourId = 0x1001312, /**< For rotary sliders, this colour is used to draw the outer curve's outline. */
|
||||
|
||||
textBoxTextColourId = 0x1001400, /**< The colour for the text in the text-editor box used for editing the value. */
|
||||
textBoxBackgroundColourId = 0x1001500, /**< The background colour for the text-editor box. */
|
||||
textBoxHighlightColourId = 0x1001600, /**< The text highlight colour for the text-editor box. */
|
||||
textBoxOutlineColourId = 0x1001700 /**< The colour to use for a border around the text-editor box. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
slider drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
//==============================================================================
|
||||
virtual void drawLinearSlider (Graphics&,
|
||||
int x, int y, int width, int height,
|
||||
float sliderPos,
|
||||
float minSliderPos,
|
||||
float maxSliderPos,
|
||||
const Slider::SliderStyle,
|
||||
Slider&) = 0;
|
||||
|
||||
virtual void drawLinearSliderBackground (Graphics&,
|
||||
int x, int y, int width, int height,
|
||||
float sliderPos,
|
||||
float minSliderPos,
|
||||
float maxSliderPos,
|
||||
const Slider::SliderStyle style,
|
||||
Slider&) = 0;
|
||||
|
||||
virtual void drawLinearSliderThumb (Graphics&,
|
||||
int x, int y, int width, int height,
|
||||
float sliderPos,
|
||||
float minSliderPos,
|
||||
float maxSliderPos,
|
||||
const Slider::SliderStyle,
|
||||
Slider&) = 0;
|
||||
|
||||
virtual int getSliderThumbRadius (Slider&) = 0;
|
||||
|
||||
virtual void drawRotarySlider (Graphics&,
|
||||
int x, int y, int width, int height,
|
||||
float sliderPosProportional,
|
||||
float rotaryStartAngle,
|
||||
float rotaryEndAngle,
|
||||
Slider&) = 0;
|
||||
|
||||
virtual Button* createSliderButton (Slider&, bool isIncrement) = 0;
|
||||
virtual Label* createSliderTextBox (Slider&) = 0;
|
||||
|
||||
virtual ImageEffectFilter* getSliderEffect (Slider&) = 0;
|
||||
|
||||
virtual Font getSliderPopupFont (Slider&) = 0;
|
||||
virtual int getSliderPopupPlacement (Slider&) = 0;
|
||||
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// These methods' parameters have changed: see the new method signatures.
|
||||
virtual void createSliderButton (bool) {}
|
||||
virtual void getSliderEffect() {}
|
||||
virtual void getSliderPopupFont() {}
|
||||
virtual void getSliderPopupPlacement() {}
|
||||
#endif
|
||||
};
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() 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 */
|
||||
void modifierKeysChanged (const ModifierKeys&) override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
void focusOfChildComponentChanged (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
/** Returns the best number of decimal places to use when displaying numbers.
|
||||
This is calculated from the slider's interval setting.
|
||||
*/
|
||||
int getNumDecimalPlacesToDisplay() const noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (class Pimpl)
|
||||
friend class Pimpl;
|
||||
friend struct ContainerDeletePolicy<Pimpl>;
|
||||
ScopedPointer<Pimpl> pimpl;
|
||||
|
||||
void init (SliderStyle, TextEntryBoxPosition);
|
||||
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// These methods' bool parameters have changed: see the new method signature.
|
||||
JUCE_DEPRECATED (void setValue (double, bool));
|
||||
JUCE_DEPRECATED (void setValue (double, bool, bool));
|
||||
JUCE_DEPRECATED (void setMinValue (double, bool, bool, bool));
|
||||
JUCE_DEPRECATED (void setMinValue (double, bool, bool));
|
||||
JUCE_DEPRECATED (void setMinValue (double, bool));
|
||||
JUCE_DEPRECATED (void setMaxValue (double, bool, bool, bool));
|
||||
JUCE_DEPRECATED (void setMaxValue (double, bool, bool));
|
||||
JUCE_DEPRECATED (void setMaxValue (double, bool));
|
||||
JUCE_DEPRECATED (void setMinAndMaxValues (double, double, bool, bool));
|
||||
JUCE_DEPRECATED (void setMinAndMaxValues (double, double, bool));
|
||||
virtual void snapValue (double, bool) {}
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Slider)
|
||||
};
|
||||
|
||||
/** This typedef is just for compatibility with old code - newer code should use the Slider::Listener class directly. */
|
||||
typedef Slider::Listener SliderListener;
|
||||
|
||||
#endif // JUCE_SLIDER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,926 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class TableHeaderComponent::DragOverlayComp : public Component
|
||||
{
|
||||
public:
|
||||
DragOverlayComp (const Image& image_)
|
||||
: image (image_)
|
||||
{
|
||||
image.duplicateIfShared();
|
||||
image.multiplyAllAlphas (0.8f);
|
||||
setAlwaysOnTop (true);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.drawImageAt (image, 0, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
Image image;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (DragOverlayComp)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
TableHeaderComponent::TableHeaderComponent()
|
||||
: columnsChanged (false),
|
||||
columnsResized (false),
|
||||
sortChanged (false),
|
||||
menuActive (true),
|
||||
stretchToFit (false),
|
||||
columnIdBeingResized (0),
|
||||
columnIdBeingDragged (0),
|
||||
columnIdUnderMouse (0),
|
||||
lastDeliberateWidth (0)
|
||||
{
|
||||
}
|
||||
|
||||
TableHeaderComponent::~TableHeaderComponent()
|
||||
{
|
||||
dragOverlayComp = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableHeaderComponent::setPopupMenuActive (const bool hasMenu)
|
||||
{
|
||||
menuActive = hasMenu;
|
||||
}
|
||||
|
||||
bool TableHeaderComponent::isPopupMenuActive() const { return menuActive; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
int TableHeaderComponent::getNumColumns (const bool onlyCountVisibleColumns) const
|
||||
{
|
||||
if (onlyCountVisibleColumns)
|
||||
{
|
||||
int num = 0;
|
||||
|
||||
for (int i = columns.size(); --i >= 0;)
|
||||
if (columns.getUnchecked(i)->isVisible())
|
||||
++num;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
return columns.size();
|
||||
}
|
||||
|
||||
String TableHeaderComponent::getColumnName (const int columnId) const
|
||||
{
|
||||
if (const ColumnInfo* const ci = getInfoForId (columnId))
|
||||
return ci->name;
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
void TableHeaderComponent::setColumnName (const int columnId, const String& newName)
|
||||
{
|
||||
if (ColumnInfo* const ci = getInfoForId (columnId))
|
||||
{
|
||||
if (ci->name != newName)
|
||||
{
|
||||
ci->name = newName;
|
||||
sendColumnsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::addColumn (const String& columnName,
|
||||
const int columnId,
|
||||
const int width,
|
||||
const int minimumWidth,
|
||||
const int maximumWidth,
|
||||
const int propertyFlags,
|
||||
const int insertIndex)
|
||||
{
|
||||
// can't have a duplicate or null ID!
|
||||
jassert (columnId != 0 && getIndexOfColumnId (columnId, false) < 0);
|
||||
jassert (width > 0);
|
||||
|
||||
ColumnInfo* const ci = new ColumnInfo();
|
||||
ci->name = columnName;
|
||||
ci->id = columnId;
|
||||
ci->width = width;
|
||||
ci->lastDeliberateWidth = width;
|
||||
ci->minimumWidth = minimumWidth;
|
||||
ci->maximumWidth = maximumWidth;
|
||||
if (ci->maximumWidth < 0)
|
||||
ci->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)
|
||||
{
|
||||
const int 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)
|
||||
{
|
||||
const int currentIndex = getIndexOfColumnId (columnId, false);
|
||||
newIndex = visibleIndexToTotalIndex (newIndex);
|
||||
|
||||
if (columns [currentIndex] != 0 && currentIndex != newIndex)
|
||||
{
|
||||
columns.move (currentIndex, newIndex);
|
||||
sendColumnsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getColumnWidth (const int columnId) const
|
||||
{
|
||||
if (const ColumnInfo* const ci = getInfoForId (columnId))
|
||||
return ci->width;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TableHeaderComponent::setColumnWidth (const int columnId, const int newWidth)
|
||||
{
|
||||
ColumnInfo* const ci = getInfoForId (columnId);
|
||||
|
||||
if (ci != nullptr && ci->width != newWidth)
|
||||
{
|
||||
const int numColumns = getNumColumns (true);
|
||||
|
||||
ci->lastDeliberateWidth = ci->width
|
||||
= jlimit (ci->minimumWidth, ci->maximumWidth, newWidth);
|
||||
|
||||
if (stretchToFit)
|
||||
{
|
||||
const int index = getIndexOfColumnId (columnId, true) + 1;
|
||||
|
||||
if (isPositiveAndBelow (index, numColumns))
|
||||
{
|
||||
const int 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 (int i = 0; i < columns.size(); ++i)
|
||||
{
|
||||
if ((! onlyCountVisibleColumns) || columns.getUnchecked(i)->isVisible())
|
||||
{
|
||||
if (columns.getUnchecked(i)->id == columnId)
|
||||
return n;
|
||||
|
||||
++n;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getColumnIdOfIndex (int index, const bool onlyCountVisibleColumns) const
|
||||
{
|
||||
if (onlyCountVisibleColumns)
|
||||
index = visibleIndexToTotalIndex (index);
|
||||
|
||||
if (const ColumnInfo* const ci = columns [index])
|
||||
return ci->id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Rectangle<int> TableHeaderComponent::getColumnPosition (const int index) const
|
||||
{
|
||||
int x = 0, width = 0, n = 0;
|
||||
|
||||
for (int i = 0; i < columns.size(); ++i)
|
||||
{
|
||||
x += width;
|
||||
|
||||
if (columns.getUnchecked(i)->isVisible())
|
||||
{
|
||||
width = columns.getUnchecked(i)->width;
|
||||
|
||||
if (n++ == index)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return Rectangle<int> (x, 0, width, getHeight());
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getColumnIdAtX (const int xToFind) const
|
||||
{
|
||||
if (xToFind >= 0)
|
||||
{
|
||||
int x = 0;
|
||||
|
||||
for (int i = 0; i < columns.size(); ++i)
|
||||
{
|
||||
const ColumnInfo* const ci = columns.getUnchecked(i);
|
||||
|
||||
if (ci->isVisible())
|
||||
{
|
||||
x += ci->width;
|
||||
|
||||
if (xToFind < x)
|
||||
return ci->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getTotalWidth() const
|
||||
{
|
||||
int w = 0;
|
||||
|
||||
for (int i = columns.size(); --i >= 0;)
|
||||
if (columns.getUnchecked(i)->isVisible())
|
||||
w += columns.getUnchecked(i)->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)
|
||||
{
|
||||
ColumnInfo* const 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)
|
||||
{
|
||||
ColumnInfo* const ci = columns.getUnchecked(i);
|
||||
|
||||
if (ci->isVisible())
|
||||
{
|
||||
const int 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 (ColumnInfo* const 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
|
||||
{
|
||||
const ColumnInfo* const ci = getInfoForId (columnId);
|
||||
return ci != nullptr && ci->isVisible();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableHeaderComponent::setSortColumnId (const int columnId, const bool sortForwards)
|
||||
{
|
||||
if (getSortColumnId() != columnId || isSortedForwards() != sortForwards)
|
||||
{
|
||||
for (int i = columns.size(); --i >= 0;)
|
||||
columns.getUnchecked(i)->propertyFlags &= ~(sortedForwards | sortedBackwards);
|
||||
|
||||
if (ColumnInfo* const ci = getInfoForId (columnId))
|
||||
ci->propertyFlags |= (sortForwards ? sortedForwards : sortedBackwards);
|
||||
|
||||
reSortTable();
|
||||
}
|
||||
}
|
||||
|
||||
int TableHeaderComponent::getSortColumnId() const
|
||||
{
|
||||
for (int i = columns.size(); --i >= 0;)
|
||||
if ((columns.getUnchecked(i)->propertyFlags & (sortedForwards | sortedBackwards)) != 0)
|
||||
return columns.getUnchecked(i)->id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool TableHeaderComponent::isSortedForwards() const
|
||||
{
|
||||
for (int i = columns.size(); --i >= 0;)
|
||||
if ((columns.getUnchecked(i)->propertyFlags & (sortedForwards | sortedBackwards)) != 0)
|
||||
return (columns.getUnchecked(i)->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 (int i = 0; i < columns.size(); ++i)
|
||||
{
|
||||
const ColumnInfo* const ci = columns.getUnchecked (i);
|
||||
|
||||
XmlElement* const e = doc.createNewChildElement ("COLUMN");
|
||||
e->setAttribute ("id", ci->id);
|
||||
e->setAttribute ("visible", ci->isVisible());
|
||||
e->setAttribute ("width", ci->width);
|
||||
}
|
||||
|
||||
return doc.createDocument ("", true, false);
|
||||
}
|
||||
|
||||
void TableHeaderComponent::restoreFromString (const String& storedVersion)
|
||||
{
|
||||
ScopedPointer <XmlElement> storedXml (XmlDocument::parse (storedVersion));
|
||||
int index = 0;
|
||||
|
||||
if (storedXml != nullptr && storedXml->hasTagName ("TABLELAYOUT"))
|
||||
{
|
||||
forEachXmlChildElement (*storedXml, col)
|
||||
{
|
||||
const int tabId = col->getIntAttribute ("id");
|
||||
|
||||
if (ColumnInfo* const ci = getInfoForId (tabId))
|
||||
{
|
||||
columns.move (columns.indexOf (ci), index);
|
||||
ci->width = col->getIntAttribute ("width");
|
||||
setColumnVisible (tabId, col->getBoolAttribute ("visible"));
|
||||
}
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
columnsResized = true;
|
||||
sendColumnsChanged();
|
||||
|
||||
setSortColumnId (storedXml->getIntAttribute ("sortedCol"),
|
||||
storedXml->getBoolAttribute ("sortForwards", true));
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableHeaderComponent::addListener (Listener* const newListener)
|
||||
{
|
||||
listeners.addIfNotAlreadyThere (newListener);
|
||||
}
|
||||
|
||||
void TableHeaderComponent::removeListener (Listener* const listenerToRemove)
|
||||
{
|
||||
listeners.removeFirstMatchingValue (listenerToRemove);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableHeaderComponent::columnClicked (int columnId, const ModifierKeys& mods)
|
||||
{
|
||||
if (const ColumnInfo* const 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 (int i = 0; i < columns.size(); ++i)
|
||||
{
|
||||
const ColumnInfo* const ci = columns.getUnchecked(i);
|
||||
|
||||
if ((ci->propertyFlags & appearsOnColumnMenu) != 0)
|
||||
menu.addItem (ci->id, ci->name,
|
||||
(ci->propertyFlags & (sortedForwards | sortedBackwards)) == 0,
|
||||
isColumnVisible (ci->id));
|
||||
}
|
||||
}
|
||||
|
||||
void TableHeaderComponent::reactToMenuItem (const int menuReturnId, const int /*columnIdClicked*/)
|
||||
{
|
||||
if (getIndexOfColumnId (menuReturnId, false) >= 0)
|
||||
setColumnVisible (menuReturnId, ! isColumnVisible (menuReturnId));
|
||||
}
|
||||
|
||||
void TableHeaderComponent::paint (Graphics& g)
|
||||
{
|
||||
LookAndFeel& lf = getLookAndFeel();
|
||||
|
||||
lf.drawTableHeaderBackground (g, *this);
|
||||
|
||||
const Rectangle<int> clip (g.getClipBounds());
|
||||
|
||||
int x = 0;
|
||||
for (int i = 0; i < columns.size(); ++i)
|
||||
{
|
||||
const ColumnInfo* const ci = columns.getUnchecked(i);
|
||||
|
||||
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, 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::resized()
|
||||
{
|
||||
}
|
||||
|
||||
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.mouseWasClicked() || e.mods.isPopupMenu()))
|
||||
{
|
||||
dragOverlayComp = nullptr;
|
||||
|
||||
columnIdBeingResized = getResizeDraggerAt (e.getMouseDownX());
|
||||
|
||||
if (columnIdBeingResized != 0)
|
||||
{
|
||||
const ColumnInfo* const ci = getInfoForId (columnIdBeingResized);
|
||||
initialColumnWidth = ci->width;
|
||||
}
|
||||
else
|
||||
{
|
||||
beginDrag (e);
|
||||
}
|
||||
}
|
||||
|
||||
if (columnIdBeingResized != 0)
|
||||
{
|
||||
if (const ColumnInfo* const ci = getInfoForId (columnIdBeingResized))
|
||||
{
|
||||
int 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;
|
||||
|
||||
const Rectangle<int> 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..
|
||||
const ColumnInfo* const previous = columns.getUnchecked (newIndex - 1);
|
||||
|
||||
if ((previous->propertyFlags & draggable) != 0)
|
||||
{
|
||||
const int leftOfPrevious = getColumnPosition (newIndex - 1).getX();
|
||||
const int rightOfCurrent = getColumnPosition (newIndex).getRight();
|
||||
|
||||
if (abs (dragOverlayComp->getX() - leftOfPrevious)
|
||||
< 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..
|
||||
const ColumnInfo* const nextCol = columns.getUnchecked (newIndex + 1);
|
||||
|
||||
if ((nextCol->propertyFlags & draggable) != 0)
|
||||
{
|
||||
const int leftOfCurrent = getColumnPosition (newIndex).getX();
|
||||
const int rightOfNext = getColumnPosition (newIndex + 1).getRight();
|
||||
|
||||
if (abs (dragOverlayComp->getX() - leftOfCurrent)
|
||||
> 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());
|
||||
|
||||
const ColumnInfo* const ci = getInfoForId (columnIdBeingDragged);
|
||||
|
||||
if (ci == nullptr || (ci->propertyFlags & draggable) == 0)
|
||||
{
|
||||
columnIdBeingDragged = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
draggingColumnOriginalIndex = getIndexOfColumnId (columnIdBeingDragged, true);
|
||||
|
||||
const Rectangle<int> columnRect (getColumnPosition (draggingColumnOriginalIndex));
|
||||
|
||||
const int temp = columnIdBeingDragged;
|
||||
columnIdBeingDragged = 0;
|
||||
|
||||
addAndMakeVisible (dragOverlayComp = new DragOverlayComp (createComponentSnapshot (columnRect, false)));
|
||||
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 (int i = columns.size(); --i >= 0;)
|
||||
if (columns.getUnchecked (i)->isVisible())
|
||||
columns.getUnchecked (i)->lastDeliberateWidth = columns.getUnchecked (i)->width;
|
||||
|
||||
columnIdBeingResized = 0;
|
||||
repaint();
|
||||
|
||||
endDrag (getIndexOfColumnId (columnIdBeingDragged, true));
|
||||
|
||||
updateColumnUnderMouse (e);
|
||||
|
||||
if (columnIdUnderMouse != 0 && e.mouseWasClicked() && ! e.mods.isPopupMenu())
|
||||
columnClicked (columnIdUnderMouse, e.mods);
|
||||
|
||||
dragOverlayComp = nullptr;
|
||||
}
|
||||
|
||||
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 (const int id) const
|
||||
{
|
||||
for (int i = columns.size(); --i >= 0;)
|
||||
if (columns.getUnchecked(i)->id == id)
|
||||
return columns.getUnchecked(i);
|
||||
|
||||
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 (int i = 0; i < columns.size(); ++i)
|
||||
{
|
||||
const ColumnInfo* const ci = columns.getUnchecked(i);
|
||||
|
||||
if (ci->isVisible())
|
||||
{
|
||||
if (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)
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,442 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TABLEHEADERCOMPONENT_H_INCLUDED
|
||||
#define JUCE_TABLEHEADERCOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
class JUCE_API TableHeaderComponent : public Component,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty table header.
|
||||
*/
|
||||
TableHeaderComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~TableHeaderComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** A combination of these flags are passed into the addColumn() method to specify
|
||||
the properties of a column.
|
||||
*/
|
||||
enum ColumnPropertyFlags
|
||||
{
|
||||
visible = 1, /**< If this is set, the column will be shown; if not, it will be hidden until the user enables it with the pop-up menu. */
|
||||
resizable = 2, /**< If this is set, the column can be resized by dragging it. */
|
||||
draggable = 4, /**< If this is set, the column can be dragged around to change its order in the table. */
|
||||
appearsOnColumnMenu = 8, /**< If this is set, the column will be shown on the pop-up menu allowing it to be hidden/shown. */
|
||||
sortable = 16, /**< If this is set, then clicking on the column header will set it to be the sort column, and clicking again will reverse the order. */
|
||||
sortedForwards = 32, /**< If this is set, the column is currently the one by which the table is sorted (forwards). */
|
||||
sortedBackwards = 64, /**< If this is set, the column is currently the one by which the table is sorted (backwards). */
|
||||
|
||||
/** This set of default flags is used as the default parameter value in addColumn(). */
|
||||
defaultFlags = (visible | resizable | draggable | appearsOnColumnMenu | sortable),
|
||||
|
||||
/** A quick way of combining flags for a column that's not resizable. */
|
||||
notResizable = (visible | draggable | appearsOnColumnMenu | sortable),
|
||||
|
||||
/** A quick way of combining flags for a column that's not resizable or sortable. */
|
||||
notResizableOrSortable = (visible | draggable | appearsOnColumnMenu),
|
||||
|
||||
/** A quick way of combining flags for a column that's not sortable. */
|
||||
notSortable = (visible | resizable | draggable | appearsOnColumnMenu)
|
||||
};
|
||||
|
||||
/** Adds a column to the table.
|
||||
|
||||
This will add a column, and asynchronously call the tableColumnsChanged() method of any
|
||||
registered listeners.
|
||||
|
||||
@param columnName the name of the new column. It's ok to have two or more columns with the same name
|
||||
@param columnId an ID for this column. The ID can be any number apart from 0, but every column must have
|
||||
a unique ID. This is used to identify the column later on, after the user may have
|
||||
changed the order that they appear in
|
||||
@param width the initial width of the column, in pixels
|
||||
@param maximumWidth a maximum width that the column can take when the user is resizing it. This only applies
|
||||
if the 'resizable' flag is specified for this column
|
||||
@param minimumWidth a minimum width that the column can take when the user is resizing it. This only applies
|
||||
if the 'resizable' flag is specified for this column
|
||||
@param propertyFlags a combination of some of the values from the ColumnPropertyFlags enum, to define the
|
||||
properties of this column
|
||||
@param insertIndex the index at which the column should be added. A value of 0 puts it at the start (left-hand side)
|
||||
and -1 puts it at the end (right-hand size) of the table. Note that the index the index within
|
||||
all columns, not just the index amongst those that are currently visible
|
||||
*/
|
||||
void addColumn (const String& columnName,
|
||||
int columnId,
|
||||
int width,
|
||||
int minimumWidth = 30,
|
||||
int maximumWidth = -1,
|
||||
int propertyFlags = defaultFlags,
|
||||
int insertIndex = -1);
|
||||
|
||||
/** Removes a column with the given ID.
|
||||
|
||||
If there is such a column, this will asynchronously call the tableColumnsChanged() method of any
|
||||
registered listeners.
|
||||
*/
|
||||
void removeColumn (int columnIdToRemove);
|
||||
|
||||
/** Deletes all columns from the table.
|
||||
|
||||
If there are any columns to remove, this will asynchronously call the tableColumnsChanged() method of any
|
||||
registered listeners.
|
||||
*/
|
||||
void removeAllColumns();
|
||||
|
||||
/** Returns the number of columns in the table.
|
||||
|
||||
If onlyCountVisibleColumns is true, this will return the number of visible columns; otherwise it'll
|
||||
return the total number of columns, including hidden ones.
|
||||
|
||||
@see isColumnVisible
|
||||
*/
|
||||
int getNumColumns (bool onlyCountVisibleColumns) const;
|
||||
|
||||
/** Returns the name for a column.
|
||||
@see setColumnName
|
||||
*/
|
||||
String getColumnName (int columnId) const;
|
||||
|
||||
/** Changes the name of a column. */
|
||||
void setColumnName (int columnId, const String& newName);
|
||||
|
||||
/** Moves a column to a different index in the table.
|
||||
|
||||
@param columnId the column to move
|
||||
@param newVisibleIndex the target index for it, from 0 to the number of columns currently visible.
|
||||
*/
|
||||
void moveColumn (int columnId, int newVisibleIndex);
|
||||
|
||||
/** Returns the width of one of the columns.
|
||||
*/
|
||||
int getColumnWidth (int columnId) const;
|
||||
|
||||
/** Changes the width of a column.
|
||||
|
||||
This will cause an asynchronous callback to the tableColumnsResized() method of any registered listeners.
|
||||
*/
|
||||
void setColumnWidth (int columnId, int newWidth);
|
||||
|
||||
/** Shows or hides a column.
|
||||
|
||||
This can cause an asynchronous callback to the tableColumnsChanged() method of any registered listeners.
|
||||
@see isColumnVisible
|
||||
*/
|
||||
void setColumnVisible (int columnId, bool shouldBeVisible);
|
||||
|
||||
/** Returns true if this column is currently visible.
|
||||
@see setColumnVisible
|
||||
*/
|
||||
bool isColumnVisible (int columnId) const;
|
||||
|
||||
/** Changes the column which is the sort column.
|
||||
|
||||
This can cause an asynchronous callback to the tableSortOrderChanged() method of any registered listeners.
|
||||
|
||||
If this method doesn't actually change the column ID, then no re-sort will take place (you can
|
||||
call reSortTable() to force a re-sort to happen if you've modified the table's contents).
|
||||
|
||||
@see getSortColumnId, isSortedForwards, reSortTable
|
||||
*/
|
||||
void setSortColumnId (int columnId, bool sortForwards);
|
||||
|
||||
/** Returns the column ID by which the table is currently sorted, or 0 if it is unsorted.
|
||||
|
||||
@see setSortColumnId, isSortedForwards
|
||||
*/
|
||||
int getSortColumnId() const;
|
||||
|
||||
/** Returns true if the table is currently sorted forwards, or false if it's backwards.
|
||||
@see setSortColumnId
|
||||
*/
|
||||
bool isSortedForwards() const;
|
||||
|
||||
/** Triggers a re-sort of the table according to the current sort-column.
|
||||
|
||||
If you modifiy the table's contents, you can call this to signal that the table needs
|
||||
to be re-sorted.
|
||||
|
||||
(This doesn't do any sorting synchronously - it just asynchronously sends a call to the
|
||||
tableSortOrderChanged() method of any listeners).
|
||||
*/
|
||||
void reSortTable();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the total width of all the visible columns in the table.
|
||||
*/
|
||||
int getTotalWidth() const;
|
||||
|
||||
/** Returns the index of a given column.
|
||||
|
||||
If there's no such column ID, this will return -1.
|
||||
|
||||
If onlyCountVisibleColumns is true, this will return the index amoungst 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 amoungst the visible columns;
|
||||
otherwise it'll count it amongst all the columns, including any hidden ones.
|
||||
|
||||
If the index is out-of-range, it'll return 0.
|
||||
*/
|
||||
int getColumnIdOfIndex (int index, bool onlyCountVisibleColumns) const;
|
||||
|
||||
/** Returns the rectangle containing of one of the columns.
|
||||
|
||||
The index is an index from 0 to the number of columns that are currently visible (hidden
|
||||
ones are not counted). It returns a rectangle showing the position of the column relative
|
||||
to this component's top-left. If the index is out-of-range, an empty rectangle is retrurned.
|
||||
*/
|
||||
Rectangle<int> getColumnPosition (int index) const;
|
||||
|
||||
/** Finds the column ID at a given x-position in the component.
|
||||
|
||||
If there is a column at this point this returns its ID, or if not, it will return 0.
|
||||
*/
|
||||
int getColumnIdAtX (int xToFind) const;
|
||||
|
||||
/** If set to true, this indicates that the columns should be expanded or shrunk to fill the
|
||||
entire width of the component.
|
||||
|
||||
By default this is disabled. Turning it on also means that when resizing a column, those
|
||||
on the right will be squashed to fit.
|
||||
*/
|
||||
void setStretchToFitActive (bool shouldStretchToFit);
|
||||
|
||||
/** Returns true if stretch-to-fit has been enabled.
|
||||
@see setStretchToFitActive
|
||||
*/
|
||||
bool isStretchToFitActive() const;
|
||||
|
||||
/** If stretch-to-fit is enabled, this will resize all the columns to make them fit into the
|
||||
specified width, keeping their relative proportions the same.
|
||||
|
||||
If the minimum widths of the columns are too wide to fit into this space, it may
|
||||
actually end up wider.
|
||||
*/
|
||||
void resizeAllColumnsToFit (int targetTotalWidth);
|
||||
|
||||
//==============================================================================
|
||||
/** Enables or disables the pop-up menu.
|
||||
|
||||
The default menu allows the user to show or hide columns. You can add custom
|
||||
items to this menu by overloading the addMenuItems() and reactToMenuItem() methods.
|
||||
|
||||
By default the menu is enabled.
|
||||
|
||||
@see isPopupMenuActive, addMenuItems, reactToMenuItem
|
||||
*/
|
||||
void setPopupMenuActive (bool hasMenu);
|
||||
|
||||
/** Returns true if the pop-up menu is enabled.
|
||||
@see setPopupMenuActive
|
||||
*/
|
||||
bool isPopupMenuActive() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a string that encapsulates the table's current layout.
|
||||
|
||||
This can be restored later using restoreFromString(). It saves the order of
|
||||
the columns, the currently-sorted column, and the widths.
|
||||
|
||||
@see restoreFromString
|
||||
*/
|
||||
String toString() const;
|
||||
|
||||
/** Restores the state of the table, based on a string previously created with
|
||||
toString().
|
||||
|
||||
@see toString
|
||||
*/
|
||||
void restoreFromString (const String& storedVersion);
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Receives events from a TableHeaderComponent when columns are resized, moved, etc.
|
||||
|
||||
You can register one of these objects for table events using TableHeaderComponent::addListener()
|
||||
and TableHeaderComponent::removeListener().
|
||||
|
||||
@see TableHeaderComponent
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
Listener() {}
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
//==============================================================================
|
||||
/** This is called when some of the table's columns are added, removed, hidden,
|
||||
or rearranged.
|
||||
*/
|
||||
virtual void tableColumnsChanged (TableHeaderComponent* tableHeader) = 0;
|
||||
|
||||
/** This is called when one or more of the table's columns are resized. */
|
||||
virtual void tableColumnsResized (TableHeaderComponent* tableHeader) = 0;
|
||||
|
||||
/** This is called when the column by which the table should be sorted is changed. */
|
||||
virtual void tableSortOrderChanged (TableHeaderComponent* tableHeader) = 0;
|
||||
|
||||
/** This is called when the user begins or ends dragging one of the columns around.
|
||||
|
||||
When the user starts dragging a column, this is called with the ID of that
|
||||
column. When they finish dragging, it is called again with 0 as the ID.
|
||||
*/
|
||||
virtual void tableColumnDraggingChanged (TableHeaderComponent* tableHeader,
|
||||
int columnIdNowBeingDragged);
|
||||
};
|
||||
|
||||
/** Adds a listener to be informed about things that happen to the header. */
|
||||
void addListener (Listener* newListener);
|
||||
|
||||
/** Removes a previously-registered listener. */
|
||||
void removeListener (Listener* listenerToRemove);
|
||||
|
||||
//==============================================================================
|
||||
/** This can be overridden to handle a mouse-click on one of the column headers.
|
||||
|
||||
The default implementation will use this click to call getSortColumnId() and
|
||||
change the sort order.
|
||||
*/
|
||||
virtual void columnClicked (int columnId, const ModifierKeys& mods);
|
||||
|
||||
/** This can be overridden to add custom items to the pop-up menu.
|
||||
|
||||
If you override this, you should call the superclass's method to add its
|
||||
column show/hide items, if you want them on the menu as well.
|
||||
|
||||
Then to handle the result, override reactToMenuItem().
|
||||
|
||||
@see reactToMenuItem
|
||||
*/
|
||||
virtual void addMenuItems (PopupMenu& menu, int columnIdClicked);
|
||||
|
||||
/** Override this to handle any custom items that you have added to the
|
||||
pop-up menu with an addMenuItems() override.
|
||||
|
||||
If the menuReturnId isn't one of your own custom menu items, you'll need to
|
||||
call TableHeaderComponent::reactToMenuItem() to allow the base class to
|
||||
handle the items that it had added.
|
||||
|
||||
@see addMenuItems
|
||||
*/
|
||||
virtual void reactToMenuItem (int menuReturnId, int columnIdClicked);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void drawTableHeaderBackground (Graphics&, TableHeaderComponent&) = 0;
|
||||
|
||||
virtual void drawTableHeaderColumn (Graphics&, const String& columnName, int columnId,
|
||||
int width, int height,
|
||||
bool isMouseOver, bool isMouseDown, int columnFlags) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() 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;
|
||||
ScopedPointer <Component> dragOverlayComp;
|
||||
class DragOverlayComp;
|
||||
|
||||
bool columnsChanged, columnsResized, sortChanged, menuActive, stretchToFit;
|
||||
int columnIdBeingResized, columnIdBeingDragged, initialColumnWidth;
|
||||
int columnIdUnderMouse, draggingColumnOffset, draggingColumnOriginalIndex, lastDeliberateWidth;
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
/** This typedef is just for compatibility with old code - newer code should use the TableHeaderComponent::Listener class directly. */
|
||||
typedef TableHeaderComponent::Listener TableHeaderListener;
|
||||
|
||||
|
||||
#endif // JUCE_TABLEHEADERCOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,471 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class TableListBox::RowComp : public Component,
|
||||
public TooltipClient
|
||||
{
|
||||
public:
|
||||
RowComp (TableListBox& tlb) noexcept : owner (tlb), row (-1), isSelected (false)
|
||||
{
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (TableListBoxModel* const tableModel = owner.getModel())
|
||||
{
|
||||
tableModel->paintRowBackground (g, row, getWidth(), getHeight(), isSelected);
|
||||
|
||||
const TableHeaderComponent& headerComp = owner.getHeader();
|
||||
const int numColumns = headerComp.getNumColumns (true);
|
||||
|
||||
for (int i = 0; i < numColumns; ++i)
|
||||
{
|
||||
if (columnComponents[i] == nullptr)
|
||||
{
|
||||
const int columnId = headerComp.getColumnIdOfIndex (i, true);
|
||||
const Rectangle<int> columnRect (headerComp.getColumnPosition(i).withHeight (getHeight()));
|
||||
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
g.reduceClipRegion (columnRect);
|
||||
g.setOrigin (columnRect.getX(), 0);
|
||||
tableModel->paintCell (g, row, columnId, columnRect.getWidth(), columnRect.getHeight(), isSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update (const int newRow, const bool isNowSelected)
|
||||
{
|
||||
jassert (newRow >= 0);
|
||||
|
||||
if (newRow != row || isNowSelected != isSelected)
|
||||
{
|
||||
row = newRow;
|
||||
isSelected = isNowSelected;
|
||||
repaint();
|
||||
}
|
||||
|
||||
TableListBoxModel* const tableModel = owner.getModel();
|
||||
|
||||
if (tableModel != nullptr && row < owner.getNumRows())
|
||||
{
|
||||
const Identifier columnProperty ("_tableColumnId");
|
||||
const int numColumns = owner.getHeader().getNumColumns (true);
|
||||
|
||||
for (int i = 0; i < numColumns; ++i)
|
||||
{
|
||||
const int columnId = owner.getHeader().getColumnIdOfIndex (i, true);
|
||||
Component* comp = columnComponents[i];
|
||||
|
||||
if (comp != nullptr && columnId != (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 (const int index)
|
||||
{
|
||||
if (Component* const c = columnComponents.getUnchecked (index))
|
||||
c->setBounds (owner.getHeader().getColumnPosition (index)
|
||||
.withY (0).withHeight (getHeight()));
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
isDragging = false;
|
||||
selectRowOnMouseUp = false;
|
||||
|
||||
if (isEnabled())
|
||||
{
|
||||
if (! isSelected)
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (row, e.mods, false);
|
||||
|
||||
const int columnId = owner.getHeader().getColumnIdAtX (e.x);
|
||||
|
||||
if (columnId != 0)
|
||||
if (TableListBoxModel* m = owner.getModel())
|
||||
m->cellClicked (row, columnId, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectRowOnMouseUp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
if (isEnabled() && owner.getModel() != nullptr && ! (e.mouseWasClicked() || isDragging))
|
||||
{
|
||||
const SparseSet<int> selectedRows (owner.getSelectedRows());
|
||||
|
||||
if (selectedRows.size() > 0)
|
||||
{
|
||||
const var dragDescription (owner.getModel()->getDragSourceDescription (selectedRows));
|
||||
|
||||
if (! (dragDescription.isVoid() || (dragDescription.isString() && dragDescription.toString().isEmpty())))
|
||||
{
|
||||
isDragging = true;
|
||||
owner.startDragAndDrop (e, dragDescription, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mouseUp (const MouseEvent& e) override
|
||||
{
|
||||
if (selectRowOnMouseUp && e.mouseWasClicked() && isEnabled())
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (row, e.mods, true);
|
||||
|
||||
const int 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
|
||||
{
|
||||
const int columnId = owner.getHeader().getColumnIdAtX (e.x);
|
||||
|
||||
if (columnId != 0)
|
||||
if (TableListBoxModel* m = owner.getModel())
|
||||
m->cellDoubleClicked (row, columnId, e);
|
||||
}
|
||||
|
||||
String getTooltip() override
|
||||
{
|
||||
const int columnId = owner.getHeader().getColumnIdAtX (getMouseXYRelative().getX());
|
||||
|
||||
if (columnId != 0)
|
||||
if (TableListBoxModel* m = owner.getModel())
|
||||
return m->getCellTooltip (row, columnId);
|
||||
|
||||
return String();
|
||||
}
|
||||
|
||||
Component* findChildComponentForColumn (const int columnId) const
|
||||
{
|
||||
return columnComponents [owner.getHeader().getIndexOfColumnId (columnId, true)];
|
||||
}
|
||||
|
||||
private:
|
||||
TableListBox& owner;
|
||||
OwnedArray<Component> columnComponents;
|
||||
int row;
|
||||
bool isSelected, isDragging, selectRowOnMouseUp;
|
||||
|
||||
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),
|
||||
header (nullptr),
|
||||
model (m),
|
||||
autoSizeOptionsShown (true)
|
||||
{
|
||||
ListBox::model = this;
|
||||
|
||||
setHeader (new Header (*this));
|
||||
}
|
||||
|
||||
TableListBox::~TableListBox()
|
||||
{
|
||||
}
|
||||
|
||||
void TableListBox::setModel (TableListBoxModel* const newModel)
|
||||
{
|
||||
if (model != newModel)
|
||||
{
|
||||
model = newModel;
|
||||
updateContent();
|
||||
}
|
||||
}
|
||||
|
||||
void TableListBox::setHeader (TableHeaderComponent* newHeader)
|
||||
{
|
||||
jassert (newHeader != nullptr); // you need to supply a real header for a table!
|
||||
|
||||
Rectangle<int> newBounds (100, 28);
|
||||
if (header != nullptr)
|
||||
newBounds = header->getBounds();
|
||||
|
||||
header = newHeader;
|
||||
header->setBounds (newBounds);
|
||||
|
||||
setHeaderComponent (header);
|
||||
|
||||
header->addListener (this);
|
||||
}
|
||||
|
||||
int TableListBox::getHeaderHeight() const noexcept
|
||||
{
|
||||
return header->getHeight();
|
||||
}
|
||||
|
||||
void TableListBox::setHeaderHeight (const int newHeight)
|
||||
{
|
||||
header->setSize (header->getWidth(), newHeight);
|
||||
resized();
|
||||
}
|
||||
|
||||
void TableListBox::autoSizeColumn (const int columnId)
|
||||
{
|
||||
const int 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 (const bool shouldBeShown) noexcept
|
||||
{
|
||||
autoSizeOptionsShown = shouldBeShown;
|
||||
}
|
||||
|
||||
Rectangle<int> TableListBox::getCellPosition (const int columnId, const int rowNumber,
|
||||
const bool relativeToComponentTopLeft) const
|
||||
{
|
||||
Rectangle<int> 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 (RowComp* const rowComp = dynamic_cast<RowComp*> (getComponentForRowNumber (rowNumber)))
|
||||
return rowComp->findChildComponentForColumn (columnId);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TableListBox::scrollToEnsureColumnIsOnscreen (const int columnId)
|
||||
{
|
||||
if (ScrollBar* const scrollbar = getHorizontalScrollBar())
|
||||
{
|
||||
const Rectangle<int> pos (header->getColumnPosition (header->getIndexOfColumnId (columnId, true)));
|
||||
|
||||
double x = scrollbar->getCurrentRangeStart();
|
||||
const double 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
|
||||
{
|
||||
const int firstRow = getRowContainingPosition (0, 0);
|
||||
|
||||
for (int i = firstRow + getNumRowsOnScreen() + 2; --i >= firstRow;)
|
||||
if (RowComp* const rowComp = dynamic_cast<RowComp*> (getComponentForRowNumber (i)))
|
||||
rowComp->resized();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableListBoxModel::cellClicked (int, int, const MouseEvent&) {}
|
||||
void TableListBoxModel::cellDoubleClicked (int, int, const MouseEvent&) {}
|
||||
void TableListBoxModel::backgroundClicked (const MouseEvent&) {}
|
||||
void TableListBoxModel::sortOrderChanged (int, const 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 String(); }
|
||||
var TableListBoxModel::getDragSourceDescription (const SparseSet<int>&) { return var(); }
|
||||
|
||||
Component* TableListBoxModel::refreshComponentForCell (int, int, bool, Component* existingComponentToUpdate)
|
||||
{
|
||||
(void) existingComponentToUpdate;
|
||||
jassert (existingComponentToUpdate == nullptr); // indicates a failure in the code that recycles the components
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TABLELISTBOX_H_INCLUDED
|
||||
#define JUCE_TABLELISTBOX_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
class JUCE_API TableListBoxModel
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
TableListBoxModel() {}
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~TableListBoxModel() {}
|
||||
|
||||
//==============================================================================
|
||||
/** This must return the number of rows currently in the table.
|
||||
|
||||
If the number of rows changes, you must call TableListBox::updateContent() to
|
||||
cause it to refresh the list.
|
||||
*/
|
||||
virtual int getNumRows() = 0;
|
||||
|
||||
/** This must draw the background behind one of the rows in the table.
|
||||
|
||||
The graphics context has its origin at the row's top-left, and your method
|
||||
should fill the area specified by the width and height parameters.
|
||||
*/
|
||||
virtual void paintRowBackground (Graphics&,
|
||||
int rowNumber,
|
||||
int width, int height,
|
||||
bool rowIsSelected) = 0;
|
||||
|
||||
/** This must draw one of the cells.
|
||||
|
||||
The graphics context's origin will already be set to the top-left of the cell,
|
||||
whose size is specified by (width, height).
|
||||
|
||||
Note that the rowNumber value may be greater than the number of rows in your
|
||||
list, so be careful that you don't assume it's less than getNumRows().
|
||||
*/
|
||||
virtual void paintCell (Graphics&,
|
||||
int rowNumber,
|
||||
int columnId,
|
||||
int width, int height,
|
||||
bool rowIsSelected) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** This is used to create or update a custom component to go in a cell.
|
||||
|
||||
Any cell may contain a custom component, or can just be drawn with the paintCell() method
|
||||
and handle mouse clicks with cellClicked().
|
||||
|
||||
This method will be called whenever a custom component might need to be updated - e.g.
|
||||
when the table is changed, or TableListBox::updateContent() is called.
|
||||
|
||||
If you don't need a custom component for the specified cell, then return nullptr.
|
||||
(Bear in mind that even if you're not creating a new component, you may still need to
|
||||
delete existingComponentToUpdate if it's non-null).
|
||||
|
||||
If you do want a custom component, and the existingComponentToUpdate is null, then
|
||||
this method must create a new component suitable for the cell, and return it.
|
||||
|
||||
If the existingComponentToUpdate is non-null, it will be a pointer to a component previously created
|
||||
by this method. In this case, the method must either update it to make sure it's correctly representing
|
||||
the given cell (which may be different from the one that the component was created for), or it can
|
||||
delete this component and return a new one.
|
||||
*/
|
||||
virtual Component* refreshComponentForCell (int rowNumber, int columnId, bool isRowSelected,
|
||||
Component* existingComponentToUpdate);
|
||||
|
||||
//==============================================================================
|
||||
/** This callback is made when the user clicks on one of the cells in the table.
|
||||
|
||||
The mouse event's coordinates will be relative to the entire table row.
|
||||
@see cellDoubleClicked, backgroundClicked
|
||||
*/
|
||||
virtual void cellClicked (int rowNumber, int columnId, const MouseEvent&);
|
||||
|
||||
/** This callback is made when the user clicks on one of the cells in the table.
|
||||
|
||||
The mouse event's coordinates will be relative to the entire table row.
|
||||
@see cellClicked, backgroundClicked
|
||||
*/
|
||||
virtual void cellDoubleClicked (int rowNumber, int columnId, const MouseEvent&);
|
||||
|
||||
/** This can be overridden to react to the user double-clicking on a part of the list where
|
||||
there are no rows.
|
||||
|
||||
@see cellClicked
|
||||
*/
|
||||
virtual void backgroundClicked (const MouseEvent&);
|
||||
|
||||
//==============================================================================
|
||||
/** This callback is made when the table's sort order is changed.
|
||||
|
||||
This could be because the user has clicked a column header, or because the
|
||||
TableHeaderComponent::setSortColumnId() method was called.
|
||||
|
||||
If you implement this, your method should re-sort the table using the given
|
||||
column as the key.
|
||||
*/
|
||||
virtual void sortOrderChanged (int newSortColumnId, bool isForwards);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the best width for one of the columns.
|
||||
|
||||
If you implement this method, you should measure the width of all the items
|
||||
in this column, and return the best size.
|
||||
|
||||
Returning 0 means that the column shouldn't be changed.
|
||||
|
||||
This is used by TableListBox::autoSizeColumn() and TableListBox::autoSizeAllColumns().
|
||||
*/
|
||||
virtual int getColumnAutoSizeWidth (int columnId);
|
||||
|
||||
/** Returns a tooltip for a particular cell in the table. */
|
||||
virtual String getCellTooltip (int rowNumber, int columnId);
|
||||
|
||||
//==============================================================================
|
||||
/** Override this to be informed when rows are selected or deselected.
|
||||
@see ListBox::selectedRowsChanged()
|
||||
*/
|
||||
virtual void selectedRowsChanged (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the delete key is pressed.
|
||||
@see ListBox::deleteKeyPressed()
|
||||
*/
|
||||
virtual void deleteKeyPressed (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the return key is pressed.
|
||||
@see ListBox::returnKeyPressed()
|
||||
*/
|
||||
virtual void returnKeyPressed (int lastRowSelected);
|
||||
|
||||
/** Override this to be informed when the list is scrolled.
|
||||
|
||||
This might be caused by the user moving the scrollbar, or by programmatic changes
|
||||
to the list position.
|
||||
*/
|
||||
virtual void listWasScrolled();
|
||||
|
||||
/** To allow rows from your table to be dragged-and-dropped, implement this method.
|
||||
|
||||
If this returns a non-null variant then when the user drags a row, the table will try to
|
||||
find a DragAndDropContainer in its parent hierarchy, and will use it to trigger a
|
||||
drag-and-drop operation, using this string as the source description, and the listbox
|
||||
itself as the source component.
|
||||
|
||||
@see getDragSourceCustomData, DragAndDropContainer::startDragging
|
||||
*/
|
||||
virtual var getDragSourceDescription (const SparseSet<int>& currentlySelectedRows);
|
||||
|
||||
private:
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// This method's signature has changed to take a MouseEvent parameter - please update your code!
|
||||
JUCE_DEPRECATED_WITH_BODY (virtual int backgroundClicked(), { return 0; })
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A table of cells, using a TableHeaderComponent as its header.
|
||||
|
||||
This component makes it easy to create a table by providing a TableListBoxModel as
|
||||
the data source.
|
||||
|
||||
|
||||
@see TableListBoxModel, TableHeaderComponent
|
||||
*/
|
||||
class JUCE_API TableListBox : public ListBox,
|
||||
private ListBoxModel,
|
||||
private TableHeaderComponent::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a TableListBox.
|
||||
|
||||
The model pointer passed-in can be null, in which case you can set it later
|
||||
with setModel(). The TableListBox does not take ownership of the model - it's
|
||||
the caller's responsibility to manage its lifetime and make sure it
|
||||
doesn't get deleted while still being used.
|
||||
*/
|
||||
TableListBox (const String& componentName = String(),
|
||||
TableListBoxModel* model = nullptr);
|
||||
|
||||
/** Destructor. */
|
||||
~TableListBox();
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the TableListBoxModel that is being used for this table.
|
||||
The TableListBox does not take ownership of the model - it's the caller's responsibility
|
||||
to manage its lifetime and make sure it doesn't get deleted while still being used.
|
||||
*/
|
||||
void setModel (TableListBoxModel* newModel);
|
||||
|
||||
/** Returns the model currently in use. */
|
||||
TableListBoxModel* getModel() const noexcept { return model; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the header component being used in this table. */
|
||||
TableHeaderComponent& getHeader() const noexcept { return *header; }
|
||||
|
||||
/** Sets the header component to use for the table.
|
||||
The table will take ownership of the component that you pass in, and will delete it
|
||||
when it's no longer needed.
|
||||
The pointer passed in may not be null.
|
||||
*/
|
||||
void setHeader (TableHeaderComponent* newHeader);
|
||||
|
||||
/** Changes the height of the table header component.
|
||||
@see getHeaderHeight
|
||||
*/
|
||||
void setHeaderHeight (int newHeight);
|
||||
|
||||
/** Returns the height of the table header.
|
||||
@see setHeaderHeight
|
||||
*/
|
||||
int getHeaderHeight() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Resizes a column to fit its contents.
|
||||
|
||||
This uses TableListBoxModel::getColumnAutoSizeWidth() to find the best width,
|
||||
and applies that to the column.
|
||||
|
||||
@see autoSizeAllColumns, TableHeaderComponent::setColumnWidth
|
||||
*/
|
||||
void autoSizeColumn (int columnId);
|
||||
|
||||
/** Calls autoSizeColumn() for all columns in the table. */
|
||||
void autoSizeAllColumns();
|
||||
|
||||
/** Enables or disables the auto size options on the popup menu.
|
||||
By default, these are enabled.
|
||||
*/
|
||||
void setAutoSizeMenuOptionShown (bool shouldBeShown) noexcept;
|
||||
|
||||
/** True if the auto-size options should be shown on the menu.
|
||||
@see setAutoSizeMenuOptionShown
|
||||
*/
|
||||
bool isAutoSizeMenuOptionShown() const noexcept { return autoSizeOptionsShown; }
|
||||
|
||||
/** Returns the position of one of the cells in the table.
|
||||
|
||||
If relativeToComponentTopLeft is true, the coordinates are relative to
|
||||
the table component's top-left. The row number isn't checked to see if it's
|
||||
in-range, but the column ID must exist or this will return an empty rectangle.
|
||||
|
||||
If relativeToComponentTopLeft is false, the coordinates are relative to the
|
||||
top-left of the table's top-left cell.
|
||||
*/
|
||||
Rectangle<int> getCellPosition (int columnId, int rowNumber,
|
||||
bool relativeToComponentTopLeft) const;
|
||||
|
||||
/** Returns the component that currently represents a given cell.
|
||||
If the component for this cell is off-screen or if the position is out-of-range,
|
||||
this may return nullptr.
|
||||
@see getCellPosition
|
||||
*/
|
||||
Component* getCellComponent (int columnId, int rowNumber) const;
|
||||
|
||||
/** Scrolls horizontally if necessary to make sure that a particular column is visible.
|
||||
|
||||
@see ListBox::scrollToEnsureRowIsOnscreen
|
||||
*/
|
||||
void scrollToEnsureColumnIsOnscreen (int columnId);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
int getNumRows() override;
|
||||
/** @internal */
|
||||
void paintListBoxItem (int, Graphics&, int, int, bool) override;
|
||||
/** @internal */
|
||||
Component* refreshComponentForRow (int rowNumber, bool isRowSelected, Component* existingComponentToUpdate) override;
|
||||
/** @internal */
|
||||
void selectedRowsChanged (int lastRowSelected) 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;
|
||||
TableListBoxModel* model;
|
||||
int columnIdNowBeingDragged;
|
||||
bool autoSizeOptionsShown;
|
||||
|
||||
void updateColumnComponents() const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableListBox)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TABLELISTBOX_H_INCLUDED
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,741 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TEXTEDITOR_H_INCLUDED
|
||||
#define JUCE_TEXTEDITOR_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
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::empty,
|
||||
juce_wchar passwordCharacter = 0);
|
||||
|
||||
/** Destructor. */
|
||||
~TextEditor();
|
||||
|
||||
//==============================================================================
|
||||
/** Puts the editor into either multi- or single-line mode.
|
||||
|
||||
By default, the editor will be in single-line mode, so use this if you need a multi-line
|
||||
editor.
|
||||
|
||||
See also the setReturnKeyStartsNewLine() method, which will also need to be turned
|
||||
on if you want a multi-line editor with line-breaks.
|
||||
|
||||
@see isMultiLine, setReturnKeyStartsNewLine
|
||||
*/
|
||||
void setMultiLine (bool shouldBeMultiLine,
|
||||
bool shouldWordWrap = true);
|
||||
|
||||
/** Returns true if the editor is in multi-line mode. */
|
||||
bool isMultiLine() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the behaviour of the return key.
|
||||
|
||||
If set to true, the return key will insert a new-line into the text; if false
|
||||
it will trigger a call to the TextEditor::Listener::textEditorReturnKeyPressed()
|
||||
method. By default this is set to false, and when true it will only insert
|
||||
new-lines when in multi-line mode (see setMultiLine()).
|
||||
*/
|
||||
void setReturnKeyStartsNewLine (bool shouldStartNewLine);
|
||||
|
||||
/** Returns the value set by setReturnKeyStartsNewLine().
|
||||
See setReturnKeyStartsNewLine() for more info.
|
||||
*/
|
||||
bool getReturnKeyStartsNewLine() const { return returnKeyStartsNewLine; }
|
||||
|
||||
/** Indicates whether the tab key should be accepted and used to input a tab character,
|
||||
or whether it gets ignored.
|
||||
|
||||
By default the tab key is ignored, so that it can be used to switch keyboard focus
|
||||
between components.
|
||||
*/
|
||||
void setTabKeyUsedAsCharacter (bool shouldTabKeyBeUsed);
|
||||
|
||||
/** Returns true if the tab key is being used for input.
|
||||
@see setTabKeyUsedAsCharacter
|
||||
*/
|
||||
bool isTabKeyUsedAsCharacter() const { return tabKeyUsed; }
|
||||
|
||||
/** This can be used to change whether escape and return keypress events are
|
||||
propagated up to the parent component.
|
||||
The default here is true, meaning that these events are not allowed to reach the
|
||||
parent, but you may want to allow them through so that they can trigger other
|
||||
actions, e.g. closing a dialog box, etc.
|
||||
*/
|
||||
void setEscapeAndReturnKeysConsumed (bool shouldBeConsumed) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the editor to read-only mode.
|
||||
|
||||
By default, the text editor is not read-only. If you're making it read-only, you
|
||||
might also want to call setCaretVisible (false) to get rid of the caret.
|
||||
|
||||
The text can still be highlighted and copied when in read-only mode.
|
||||
|
||||
@see isReadOnly, setCaretVisible
|
||||
*/
|
||||
void setReadOnly (bool shouldBeReadOnly);
|
||||
|
||||
/** Returns true if the editor is in read-only mode. */
|
||||
bool isReadOnly() const;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 caret != nullptr; }
|
||||
|
||||
//==============================================================================
|
||||
/** Enables/disables a vertical scrollbar.
|
||||
|
||||
(This only applies when in multi-line mode). When the text gets too long to fit
|
||||
in the component, a scrollbar can appear to allow it to be scrolled. Even when
|
||||
this is enabled, the scrollbar will be hidden unless it's needed.
|
||||
|
||||
By default the scrollbar is enabled.
|
||||
*/
|
||||
void setScrollbarsShown (bool shouldBeEnabled);
|
||||
|
||||
/** Returns true if scrollbars are enabled.
|
||||
@see setScrollbarsShown
|
||||
*/
|
||||
bool areScrollbarsShown() const noexcept { return scrollbarVisible; }
|
||||
|
||||
|
||||
/** Changes the password character used to disguise the text.
|
||||
|
||||
@param passwordCharacter if this is not zero, this character will be used as a replacement
|
||||
for all characters that are drawn on screen - e.g. to create
|
||||
a password-style textbox containing circular blobs instead of text,
|
||||
you could set this value to 0x25cf, which is the unicode character
|
||||
for a black splodge (not all fonts include this, though), or 0x2022,
|
||||
which is a bullet (probably the best choice for linux).
|
||||
*/
|
||||
void setPasswordCharacter (juce_wchar passwordCharacter);
|
||||
|
||||
/** Returns the current password character.
|
||||
@see setPasswordCharacter
|
||||
*/
|
||||
juce_wchar getPasswordCharacter() const noexcept { return passwordCharacter; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Allows a right-click menu to appear for the editor.
|
||||
|
||||
(This defaults to being enabled).
|
||||
|
||||
If enabled, right-clicking (or command-clicking on the Mac) will pop up a menu
|
||||
of options such as cut/copy/paste, undo/redo, etc.
|
||||
*/
|
||||
void setPopupMenuEnabled (bool menuEnabled);
|
||||
|
||||
/** Returns true if the right-click menu is enabled.
|
||||
@see setPopupMenuEnabled
|
||||
*/
|
||||
bool isPopupMenuEnabled() const noexcept { return popupMenuEnabled; }
|
||||
|
||||
/** Returns true if a popup-menu is currently being displayed. */
|
||||
bool isPopupMenuCurrentlyActive() const noexcept { return menuActive; }
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the editor.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@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, call
|
||||
applyFontToAllText() after calling this 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.
|
||||
This will also set the current font to use for any new text that's added.
|
||||
@see setFont
|
||||
*/
|
||||
void applyFontToAllText (const Font& newFont);
|
||||
|
||||
/** Returns the font that's currently being used for new text.
|
||||
@see setFont
|
||||
*/
|
||||
const Font& getFont() const noexcept { return currentFont; }
|
||||
|
||||
//==============================================================================
|
||||
/** If set to true, focusing on the editor will highlight all its text.
|
||||
|
||||
(Set to false by default).
|
||||
|
||||
This is useful for boxes where you expect the user to re-enter all the
|
||||
text when they focus on the component, rather than editing what's already there.
|
||||
*/
|
||||
void setSelectAllWhenFocused (bool shouldSelectAll);
|
||||
|
||||
/** When the text editor is empty, it can be set to display a message.
|
||||
|
||||
This is handy for things like telling the user what to type in the box - the
|
||||
string is only displayed, it's not taken to actually be the contents of
|
||||
the editor.
|
||||
*/
|
||||
void setTextToShowWhenEmpty (const String& text, Colour colourToUse);
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the size of the scrollbars that are used.
|
||||
Handy if you need smaller scrollbars for a small text box.
|
||||
*/
|
||||
void setScrollBarThickness (int newThicknessPixels);
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Receives callbacks from a TextEditor component when it changes.
|
||||
|
||||
@see TextEditor::addListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called when the user changes the text in some way. */
|
||||
virtual void textEditorTextChanged (TextEditor&) {}
|
||||
|
||||
/** Called when the user presses the return key. */
|
||||
virtual void textEditorReturnKeyPressed (TextEditor&) {}
|
||||
|
||||
/** Called when the user presses the escape key. */
|
||||
virtual void textEditorEscapeKeyPressed (TextEditor&) {}
|
||||
|
||||
/** Called when the text editor loses focus. */
|
||||
virtual void textEditorFocusLost (TextEditor&) {}
|
||||
};
|
||||
|
||||
/** Registers a listener to be told when things happen to the text.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (Listener* newListener);
|
||||
|
||||
/** Deregisters a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (Listener* listenerToRemove);
|
||||
|
||||
//==============================================================================
|
||||
/** 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 insertText
|
||||
*/
|
||||
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 insertTextAtCursor (String::empty).
|
||||
|
||||
@see setCaretPosition, getCaretPosition, setHighlightedRegion
|
||||
*/
|
||||
void insertTextAtCaret (const String& textToInsert) override;
|
||||
|
||||
/** Deletes all the text from the editor. */
|
||||
void clear();
|
||||
|
||||
/** Deletes the currently selected region.
|
||||
This doesn't copy the deleted section to the clipboard - if you need to do that, call copy() first.
|
||||
@see copy, paste, SystemClipboard
|
||||
*/
|
||||
void cut();
|
||||
|
||||
/** Copies the currently selected region to the clipboard.
|
||||
@see cut, paste, SystemClipboard
|
||||
*/
|
||||
void copy();
|
||||
|
||||
/** Pastes the contents of the clipboard into the editor at the caret position.
|
||||
@see cut, copy, SystemClipboard
|
||||
*/
|
||||
void paste();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the current index of the caret.
|
||||
@see setCaretPosition
|
||||
*/
|
||||
int getCaretPosition() const;
|
||||
|
||||
/** Moves the caret to be in front of a given character.
|
||||
@see getCaretPosition, moveCaretToEnd
|
||||
*/
|
||||
void setCaretPosition (int newIndex);
|
||||
|
||||
/** Attempts to scroll the text editor so that the caret ends up at
|
||||
a specified position.
|
||||
|
||||
This won't affect the caret's position within the text, it tries to scroll
|
||||
the entire editor vertically and horizontally so that the caret is sitting
|
||||
at the given position (relative to the top-left of this component).
|
||||
|
||||
Depending on the amount of text available, it might not be possible to
|
||||
scroll far enough for the caret to reach this exact position, but it
|
||||
will go as far as it can in that direction.
|
||||
*/
|
||||
void scrollEditorToPositionCaret (int desiredCaretX, int desiredCaretY);
|
||||
|
||||
/** Get the graphical position of the caret.
|
||||
|
||||
The rectangle returned is relative to the component's top-left corner.
|
||||
@see scrollEditorToPositionCaret
|
||||
*/
|
||||
Rectangle<int> getCaretRectangle() override;
|
||||
|
||||
/** Selects a section of the text. */
|
||||
void setHighlightedRegion (const Range<int>& newSelection) override;
|
||||
|
||||
/** Returns the range of characters that are selected.
|
||||
If nothing is selected, this will return an empty range.
|
||||
@see setHighlightedRegion
|
||||
*/
|
||||
Range<int> getHighlightedRegion() const override { return selection; }
|
||||
|
||||
/** Returns the section of text that is currently selected. */
|
||||
String getHighlightedText() const;
|
||||
|
||||
/** Finds the index of the character at a given position.
|
||||
The coordinates are relative to the component's top-left.
|
||||
*/
|
||||
int getTextIndexAt (int x, int y);
|
||||
|
||||
/** Counts the number of characters in the text.
|
||||
|
||||
This is quicker than getting the text as a string if you just need to know
|
||||
the length.
|
||||
*/
|
||||
int getTotalNumChars() const;
|
||||
|
||||
/** Returns the total width of the text, as it is currently laid-out.
|
||||
|
||||
This may be larger than the size of the TextEditor, and can change when
|
||||
the TextEditor is resized or the text changes.
|
||||
*/
|
||||
int getTextWidth() const;
|
||||
|
||||
/** Returns the maximum height of the text, as it is currently laid-out.
|
||||
|
||||
This may be larger than the size of the TextEditor, and can change when
|
||||
the TextEditor is resized or the text changes.
|
||||
*/
|
||||
int getTextHeight() const;
|
||||
|
||||
/** Changes the size of the gap at the top and left-edge of the editor.
|
||||
By default there's a gap of 4 pixels.
|
||||
*/
|
||||
void setIndents (int newLeftIndent, int newTopIndent);
|
||||
|
||||
/** Changes the size of border left around the edge of the component.
|
||||
@see getBorder
|
||||
*/
|
||||
void setBorder (const BorderSize<int>& border);
|
||||
|
||||
/** Returns the size of border around the edge of the component.
|
||||
@see setBorder
|
||||
*/
|
||||
BorderSize<int> getBorder() const;
|
||||
|
||||
/** Used to disable the auto-scrolling which keeps the caret visible.
|
||||
|
||||
If true (the default), the editor will scroll when the caret moves offscreen. If
|
||||
set to false, it won't.
|
||||
*/
|
||||
void setScrollToShowCursor (bool shouldScrollToShowCaret);
|
||||
|
||||
//==============================================================================
|
||||
void moveCaretToEnd();
|
||||
bool moveCaretLeft (bool moveInWholeWordSteps, bool selecting);
|
||||
bool moveCaretRight (bool moveInWholeWordSteps, bool selecting);
|
||||
bool moveCaretUp (bool selecting);
|
||||
bool moveCaretDown (bool selecting);
|
||||
bool pageUp (bool selecting);
|
||||
bool pageDown (bool selecting);
|
||||
bool scrollDown();
|
||||
bool scrollUp();
|
||||
bool moveCaretToTop (bool selecting);
|
||||
bool moveCaretToStartOfLine (bool selecting);
|
||||
bool moveCaretToEnd (bool selecting);
|
||||
bool moveCaretToEndOfLine (bool selecting);
|
||||
bool deleteBackwards (bool moveInWholeWordSteps);
|
||||
bool deleteForwards (bool moveInWholeWordSteps);
|
||||
bool copyToClipboard();
|
||||
bool cutToClipboard();
|
||||
bool pasteFromClipboard();
|
||||
bool selectAll();
|
||||
bool undo();
|
||||
bool redo();
|
||||
|
||||
//==============================================================================
|
||||
/** This adds the items to the popup menu.
|
||||
|
||||
By default it adds the cut/copy/paste items, but you can override this if
|
||||
you need to replace these with your own items.
|
||||
|
||||
If you want to add your own items to the existing ones, you can override this,
|
||||
call the base class's addPopupMenuItems() method, then append your own items.
|
||||
|
||||
When the menu has been shown, performPopupMenuAction() will be called to
|
||||
perform the item that the user has chosen.
|
||||
|
||||
The default menu items will be added using item IDs from the
|
||||
StandardApplicationCommandIDs namespace.
|
||||
|
||||
If this was triggered by a mouse-click, the mouseClickEvent parameter will be
|
||||
a pointer to the info about it, or may be null if the menu is being triggered
|
||||
by some other means.
|
||||
|
||||
@see performPopupMenuAction, setPopupMenuEnabled, isPopupMenuEnabled
|
||||
*/
|
||||
virtual void addPopupMenuItems (PopupMenu& menuToAddTo,
|
||||
const MouseEvent* mouseClickEvent);
|
||||
|
||||
/** This is called to perform one of the items that was shown on the popup menu.
|
||||
|
||||
If you've overridden addPopupMenuItems(), you should also override this
|
||||
to perform the actions that you've added.
|
||||
|
||||
If you've overridden addPopupMenuItems() but have still left the default items
|
||||
on the menu, remember to call the superclass's performPopupMenuAction()
|
||||
so that it can perform the default actions if that's what the user clicked on.
|
||||
|
||||
@see addPopupMenuItems, setPopupMenuEnabled, isPopupMenuEnabled
|
||||
*/
|
||||
virtual void performPopupMenuAction (int menuItemID);
|
||||
|
||||
//==============================================================================
|
||||
/** Base class for input filters that can be applied to a TextEditor to restrict
|
||||
the text that can be entered.
|
||||
*/
|
||||
class JUCE_API InputFilter
|
||||
{
|
||||
public:
|
||||
InputFilter() {}
|
||||
virtual ~InputFilter() {}
|
||||
|
||||
/** This method is called whenever text is entered into the editor.
|
||||
An implementation of this class should should check the input string,
|
||||
and return an edited version of it that should be used.
|
||||
*/
|
||||
virtual String filterNewText (TextEditor&, const String& newInput) = 0;
|
||||
};
|
||||
|
||||
/** An input filter for a TextEditor that limits the length of text and/or the
|
||||
characters that it may contain.
|
||||
*/
|
||||
class JUCE_API LengthAndCharacterRestriction : public InputFilter
|
||||
{
|
||||
public:
|
||||
/** Creates a filter that limits the length of text, and/or the characters that it can contain.
|
||||
@param maxNumChars if this is > 0, it sets a maximum length limit; if <= 0, no
|
||||
limit is set
|
||||
@param allowedCharacters if this is non-empty, then only characters that occur in
|
||||
this string are allowed to be entered into the editor.
|
||||
*/
|
||||
LengthAndCharacterRestriction (int maxNumChars, const String& allowedCharacters);
|
||||
|
||||
private:
|
||||
String allowedCharacters;
|
||||
int maxLength;
|
||||
|
||||
String filterNewText (TextEditor&, const String&) override;
|
||||
|
||||
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::empty);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
TextEditor drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void fillTextEditorBackground (Graphics&, int width, int height, TextEditor&) = 0;
|
||||
virtual void drawTextEditorOutline (Graphics&, int width, int height, TextEditor&) = 0;
|
||||
|
||||
virtual CaretComponent* createCaretComponent (Component* keyFocusOwner) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void paintOverChildren (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDoubleClick (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
bool keyStateChanged (bool) override;
|
||||
/** @internal */
|
||||
void focusGained (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void focusLost (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
bool isTextInputActive() const override;
|
||||
/** @internal */
|
||||
void setTemporaryUnderlining (const Array <Range<int> >&) override;
|
||||
|
||||
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:
|
||||
//==============================================================================
|
||||
class Iterator;
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (class UniformTextSection)
|
||||
class TextHolderComponent;
|
||||
class InsertAction;
|
||||
class RemoveAction;
|
||||
friend class InsertAction;
|
||||
friend class RemoveAction;
|
||||
|
||||
ScopedPointer<Viewport> viewport;
|
||||
TextHolderComponent* textHolder;
|
||||
BorderSize<int> borderSize;
|
||||
|
||||
bool readOnly : 1;
|
||||
bool multiline : 1;
|
||||
bool wordWrap : 1;
|
||||
bool returnKeyStartsNewLine : 1;
|
||||
bool popupMenuEnabled : 1;
|
||||
bool selectAllTextWhenFocused : 1;
|
||||
bool scrollbarVisible : 1;
|
||||
bool wasFocused : 1;
|
||||
bool keepCaretOnScreen : 1;
|
||||
bool tabKeyUsed : 1;
|
||||
bool menuActive : 1;
|
||||
bool valueTextNeedsUpdating : 1;
|
||||
bool consumeEscAndReturnKeys : 1;
|
||||
|
||||
UndoManager undoManager;
|
||||
ScopedPointer<CaretComponent> caret;
|
||||
Range<int> selection;
|
||||
int leftIndent, topIndent;
|
||||
unsigned int lastTransactionTime;
|
||||
Font currentFont;
|
||||
mutable int totalNumChars;
|
||||
int caretPosition;
|
||||
OwnedArray<UniformTextSection> sections;
|
||||
String textToShowWhenEmpty;
|
||||
Colour colourForTextWhenEmpty;
|
||||
juce_wchar passwordCharacter;
|
||||
OptionalScopedPointer<InputFilter> inputFilter;
|
||||
Value textValue;
|
||||
|
||||
enum
|
||||
{
|
||||
notDragging,
|
||||
draggingSelectionStart,
|
||||
draggingSelectionEnd
|
||||
} dragType;
|
||||
|
||||
ListenerList <Listener> listeners;
|
||||
Array <Range<int> > underlinedSections;
|
||||
|
||||
void moveCaret (int newCaretPos);
|
||||
void moveCaretTo (int newPosition, bool isSelecting);
|
||||
void handleCommandMessage (int) override;
|
||||
void coalesceSimilarSections();
|
||||
void splitSection (int sectionIndex, int charToSplitAt);
|
||||
void clearInternal (UndoManager*);
|
||||
void insert (const String&, int insertIndex, const Font&, const Colour, UndoManager*, int newCaretPos);
|
||||
void reinsert (int insertIndex, const OwnedArray<UniformTextSection>&);
|
||||
void remove (Range<int> range, UndoManager*, int caretPositionToMoveTo);
|
||||
void getCharPosition (int index, float& x, float& y, float& lineHeight) const;
|
||||
void updateCaretPosition();
|
||||
void updateValueFromText();
|
||||
void textWasChangedByValue();
|
||||
int indexAtPosition (float x, float y);
|
||||
int findWordBreakAfter (int position) const;
|
||||
int findWordBreakBefore (int position) const;
|
||||
bool moveCaretWithTransaction (int newPos, bool selecting);
|
||||
friend class TextHolderComponent;
|
||||
friend class TextEditorViewport;
|
||||
void drawContent (Graphics&);
|
||||
void updateTextHolderSize();
|
||||
float getWordWrapWidth() const;
|
||||
void timerCallbackInt();
|
||||
void repaintText (Range<int>);
|
||||
void scrollByLines (int deltaLines);
|
||||
bool undoOrRedo (bool shouldUndo);
|
||||
UndoManager* getUndoManager() noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextEditor)
|
||||
};
|
||||
|
||||
/** This typedef is just for compatibility with old code - newer code should use the TextEditor::Listener class directly. */
|
||||
typedef TextEditor::Listener TextEditorListener;
|
||||
|
||||
|
||||
#endif // JUCE_TEXTEDITOR_H_INCLUDED
|
||||
|
|
@ -0,0 +1,810 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
const char* const Toolbar::toolbarDragDescriptor = "_toolbarItem_";
|
||||
|
||||
//==============================================================================
|
||||
class Toolbar::Spacer : public ToolbarItemComponent
|
||||
{
|
||||
public:
|
||||
Spacer (const int itemId_, const float fixedSize_, const bool drawBar_)
|
||||
: ToolbarItemComponent (itemId_, String::empty, false),
|
||||
fixedSize (fixedSize_),
|
||||
drawBar (drawBar_)
|
||||
{
|
||||
}
|
||||
|
||||
bool getToolbarItemSizes (int toolbarThickness, bool /*isToolbarVertical*/,
|
||||
int& preferredSize, int& minSize, int& maxSize)
|
||||
{
|
||||
if (fixedSize <= 0)
|
||||
{
|
||||
preferredSize = toolbarThickness * 2;
|
||||
minSize = 4;
|
||||
maxSize = 32768;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxSize = roundToInt (toolbarThickness * fixedSize);
|
||||
minSize = drawBar ? maxSize : jmin (4, maxSize);
|
||||
preferredSize = maxSize;
|
||||
|
||||
if (getEditingMode() == editableOnPalette)
|
||||
preferredSize = maxSize = toolbarThickness / (drawBar ? 3 : 2);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void paintButtonArea (Graphics&, int, int, bool, bool) override
|
||||
{
|
||||
}
|
||||
|
||||
void contentAreaChanged (const Rectangle<int>&) override
|
||||
{
|
||||
}
|
||||
|
||||
int getResizeOrder() const noexcept
|
||||
{
|
||||
return fixedSize <= 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
const int w = getWidth();
|
||||
const int h = getHeight();
|
||||
|
||||
if (drawBar)
|
||||
{
|
||||
g.setColour (findColour (Toolbar::separatorColourId, true));
|
||||
|
||||
const float thickness = 0.2f;
|
||||
|
||||
if (isToolbarVertical())
|
||||
g.fillRect (w * 0.1f, h * (0.5f - thickness * 0.5f), w * 0.8f, h * thickness);
|
||||
else
|
||||
g.fillRect (w * (0.5f - thickness * 0.5f), h * 0.1f, w * thickness, h * 0.8f);
|
||||
}
|
||||
|
||||
if (getEditingMode() != normalMode && ! drawBar)
|
||||
{
|
||||
g.setColour (findColour (Toolbar::separatorColourId, true));
|
||||
|
||||
const int indentX = jmin (2, (w - 3) / 2);
|
||||
const int indentY = jmin (2, (h - 3) / 2);
|
||||
g.drawRect (indentX, indentY, w - indentX * 2, h - indentY * 2, 1);
|
||||
|
||||
if (fixedSize <= 0)
|
||||
{
|
||||
float x1, y1, x2, y2, x3, y3, x4, y4, hw, hl;
|
||||
|
||||
if (isToolbarVertical())
|
||||
{
|
||||
x1 = w * 0.5f;
|
||||
y1 = h * 0.4f;
|
||||
x2 = x1;
|
||||
y2 = indentX * 2.0f;
|
||||
|
||||
x3 = x1;
|
||||
y3 = h * 0.6f;
|
||||
x4 = x1;
|
||||
y4 = h - y2;
|
||||
|
||||
hw = w * 0.15f;
|
||||
hl = w * 0.2f;
|
||||
}
|
||||
else
|
||||
{
|
||||
x1 = w * 0.4f;
|
||||
y1 = h * 0.5f;
|
||||
x2 = indentX * 2.0f;
|
||||
y2 = y1;
|
||||
|
||||
x3 = w * 0.6f;
|
||||
y3 = y1;
|
||||
x4 = w - x2;
|
||||
y4 = y1;
|
||||
|
||||
hw = h * 0.15f;
|
||||
hl = h * 0.2f;
|
||||
}
|
||||
|
||||
Path p;
|
||||
p.addArrow (Line<float> (x1, y1, x2, y2), 1.5f, hw, hl);
|
||||
p.addArrow (Line<float> (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, const int h)
|
||||
: PopupMenu::CustomComponent (true),
|
||||
owner (&bar),
|
||||
height (h)
|
||||
{
|
||||
for (int i = bar.items.size(); --i >= 0;)
|
||||
{
|
||||
ToolbarItemComponent* const tc = bar.items.getUnchecked(i);
|
||||
|
||||
if (dynamic_cast <Spacer*> (tc) == nullptr && ! tc->isVisible())
|
||||
{
|
||||
oldIndexes.insert (0, i);
|
||||
addAndMakeVisible (tc, 0);
|
||||
}
|
||||
}
|
||||
|
||||
layout (400);
|
||||
}
|
||||
|
||||
~MissingItemsComponent()
|
||||
{
|
||||
if (owner != nullptr)
|
||||
{
|
||||
for (int i = 0; i < getNumChildComponents(); ++i)
|
||||
{
|
||||
if (ToolbarItemComponent* const tc = dynamic_cast <ToolbarItemComponent*> (getChildComponent (i)))
|
||||
{
|
||||
tc->setVisible (false);
|
||||
const int index = oldIndexes.remove (i);
|
||||
owner->addChildComponent (tc, index);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
|
||||
owner->resized();
|
||||
}
|
||||
}
|
||||
|
||||
void layout (const int preferredWidth)
|
||||
{
|
||||
const int indent = 8;
|
||||
int x = indent;
|
||||
int y = indent;
|
||||
int maxX = 0;
|
||||
|
||||
for (int i = 0; i < getNumChildComponents(); ++i)
|
||||
{
|
||||
if (ToolbarItemComponent* const tc = dynamic_cast <ToolbarItemComponent*> (getChildComponent (i)))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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()
|
||||
: vertical (false),
|
||||
isEditingActive (false),
|
||||
toolbarStyle (Toolbar::iconsOnly)
|
||||
{
|
||||
addChildComponent (missingItemsButton = getLookAndFeel().createToolbarMissingItemsButton (*this));
|
||||
|
||||
missingItemsButton->setAlwaysOnTop (true);
|
||||
missingItemsButton->addListener (this);
|
||||
}
|
||||
|
||||
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 (ToolbarItemComponent* const 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,
|
||||
const int itemId,
|
||||
const int insertIndex)
|
||||
{
|
||||
addItemInternal (factory, itemId, insertIndex);
|
||||
resized();
|
||||
}
|
||||
|
||||
void Toolbar::addDefaultItems (ToolbarItemFactory& factoryToUse)
|
||||
{
|
||||
Array<int> ids;
|
||||
factoryToUse.getDefaultItemSet (ids);
|
||||
|
||||
clear();
|
||||
|
||||
for (int i = 0; i < ids.size(); ++i)
|
||||
addItemInternal (factoryToUse, ids.getUnchecked (i), -1);
|
||||
|
||||
resized();
|
||||
}
|
||||
|
||||
void Toolbar::removeToolbarItem (const int itemIndex)
|
||||
{
|
||||
items.remove (itemIndex);
|
||||
resized();
|
||||
}
|
||||
|
||||
ToolbarItemComponent* Toolbar::removeAndReturnItem (const int itemIndex)
|
||||
{
|
||||
if (ToolbarItemComponent* const 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 (ToolbarItemComponent* const 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 (ToolbarItemComponent* const 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 (int i = 0; i < tokens.size(); ++i)
|
||||
addItemInternal (factoryToUse, tokens[i].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 (const bool animate)
|
||||
{
|
||||
if (getWidth() > 0 && getHeight() > 0)
|
||||
{
|
||||
StretchableObjectResizer resizer;
|
||||
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
ToolbarItemComponent* const tc = items.getUnchecked(i);
|
||||
|
||||
tc->setEditingMode (isEditingActive ? ToolbarItemComponent::editableOnToolbar
|
||||
: ToolbarItemComponent::normalMode);
|
||||
|
||||
tc->setStyle (toolbarStyle);
|
||||
|
||||
Spacer* const 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();
|
||||
|
||||
const int 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);
|
||||
|
||||
const int maxLength = itemsOffTheEnd ? (vertical ? missingItemsButton->getY()
|
||||
: missingItemsButton->getX()) - 4
|
||||
: getLength();
|
||||
|
||||
int pos = 0, activeIndex = 0;
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
ToolbarItemComponent* const tc = items.getUnchecked(i);
|
||||
|
||||
if (tc->isActive)
|
||||
{
|
||||
const int size = (int) resizer.getItemSize (activeIndex++);
|
||||
|
||||
Rectangle<int> newBounds;
|
||||
if (vertical)
|
||||
newBounds.setBounds (0, pos, getWidth(), size);
|
||||
else
|
||||
newBounds.setBounds (pos, 0, size, getHeight());
|
||||
|
||||
ComponentAnimator& 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::buttonClicked (Button*)
|
||||
{
|
||||
jassert (missingItemsButton->isShowing());
|
||||
|
||||
if (missingItemsButton->isShowing())
|
||||
{
|
||||
PopupMenu m;
|
||||
m.addCustomItem (1, new MissingItemsComponent (*this, getThickness()));
|
||||
m.showMenuAsync (PopupMenu::Options().withTargetComponent (missingItemsButton), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool Toolbar::isInterestedInDragSource (const SourceDetails& dragSourceDetails)
|
||||
{
|
||||
return dragSourceDetails.description == toolbarDragDescriptor && isEditingActive;
|
||||
}
|
||||
|
||||
void Toolbar::itemDragMove (const SourceDetails& dragSourceDetails)
|
||||
{
|
||||
if (ToolbarItemComponent* const tc = dynamic_cast <ToolbarItemComponent*> (dragSourceDetails.sourceComponent.get()))
|
||||
{
|
||||
if (! items.contains (tc))
|
||||
{
|
||||
if (tc->getEditingMode() == ToolbarItemComponent::editableOnPalette)
|
||||
{
|
||||
if (ToolbarItemPalette* const palette = tc->findParentComponentOfClass<ToolbarItemPalette>())
|
||||
palette->replaceComponent (*tc);
|
||||
}
|
||||
else
|
||||
{
|
||||
jassert (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar);
|
||||
}
|
||||
|
||||
items.add (tc);
|
||||
addChildComponent (tc);
|
||||
updateAllItemPositions (true);
|
||||
}
|
||||
|
||||
ComponentAnimator& animator = Desktop::getInstance().getAnimator();
|
||||
|
||||
for (int i = getNumItems(); --i >= 0;)
|
||||
{
|
||||
const int currentIndex = items.indexOf (tc);
|
||||
int newIndex = currentIndex;
|
||||
|
||||
const int dragObjectLeft = vertical ? (dragSourceDetails.localPosition.getY() - tc->dragOffsetY)
|
||||
: (dragSourceDetails.localPosition.getX() - tc->dragOffsetX);
|
||||
const int dragObjectRight = dragObjectLeft + (vertical ? tc->getHeight() : tc->getWidth());
|
||||
|
||||
const Rectangle<int> current (animator.getComponentDestination (getChildComponent (newIndex)));
|
||||
|
||||
if (ToolbarItemComponent* const prev = getNextActiveComponent (newIndex, -1))
|
||||
{
|
||||
const Rectangle<int> 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 (ToolbarItemComponent* const next = getNextActiveComponent (newIndex, 1))
|
||||
{
|
||||
const Rectangle<int> 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 (ToolbarItemComponent* const 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 (ToolbarItemComponent* const tc = dynamic_cast <ToolbarItemComponent*> (dragSourceDetails.sourceComponent.get()))
|
||||
tc->setState (Button::buttonNormal);
|
||||
}
|
||||
|
||||
void Toolbar::mouseDown (const MouseEvent&) {}
|
||||
|
||||
//==============================================================================
|
||||
class Toolbar::CustomisationDialog : public DialogWindow
|
||||
{
|
||||
public:
|
||||
CustomisationDialog (ToolbarItemFactory& factory, Toolbar& bar, int optionFlags)
|
||||
: DialogWindow (TRANS("Add/remove items from toolbar"), Colours::white, true, true),
|
||||
toolbar (bar)
|
||||
{
|
||||
setContentOwned (new CustomiserPanel (factory, toolbar, optionFlags), true);
|
||||
setResizable (true, true);
|
||||
setResizeLimits (400, 300, 1500, 1000);
|
||||
positionNearBar();
|
||||
}
|
||||
|
||||
~CustomisationDialog()
|
||||
{
|
||||
toolbar.setEditingActive (false);
|
||||
}
|
||||
|
||||
void closeButtonPressed() override
|
||||
{
|
||||
setVisible (false);
|
||||
}
|
||||
|
||||
bool canModalEventBeSentToComponent (const Component* comp) override
|
||||
{
|
||||
return toolbar.isParentOf (comp)
|
||||
|| dynamic_cast <const ToolbarItemComponent::ItemDragAndDropOverlayComponent*> (comp) != nullptr;
|
||||
}
|
||||
|
||||
void positionNearBar()
|
||||
{
|
||||
const Rectangle<int> screenSize (toolbar.getParentMonitorArea());
|
||||
Point<int> 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,
|
||||
private ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug)
|
||||
private ButtonListener
|
||||
{
|
||||
public:
|
||||
CustomiserPanel (ToolbarItemFactory& tbf, Toolbar& bar, int optionFlags)
|
||||
: factory (tbf), toolbar (bar), palette (tbf, bar),
|
||||
instructions (String::empty, TRANS ("You can drag the items above and drop them onto a toolbar to add them.")
|
||||
+ "\n\n"
|
||||
+ TRANS ("Items on the toolbar can also be dragged around to change their order, or dragged off the edge to delete them.")),
|
||||
defaultButton (TRANS ("Restore to default set of items"))
|
||||
{
|
||||
addAndMakeVisible (palette);
|
||||
|
||||
if ((optionFlags & (Toolbar::allowIconsOnlyChoice
|
||||
| Toolbar::allowIconsWithTextChoice
|
||||
| Toolbar::allowTextOnlyChoice)) != 0)
|
||||
{
|
||||
addAndMakeVisible (styleBox);
|
||||
styleBox.setEditableText (false);
|
||||
|
||||
if ((optionFlags & Toolbar::allowIconsOnlyChoice) != 0) styleBox.addItem (TRANS("Show icons only"), 1);
|
||||
if ((optionFlags & Toolbar::allowIconsWithTextChoice) != 0) styleBox.addItem (TRANS("Show icons and descriptions"), 2);
|
||||
if ((optionFlags & Toolbar::allowTextOnlyChoice) != 0) styleBox.addItem (TRANS("Show descriptions only"), 3);
|
||||
|
||||
int selectedStyle = 0;
|
||||
switch (bar.getStyle())
|
||||
{
|
||||
case Toolbar::iconsOnly: selectedStyle = 1; break;
|
||||
case Toolbar::iconsWithText: selectedStyle = 2; break;
|
||||
case Toolbar::textOnly: selectedStyle = 3; break;
|
||||
}
|
||||
|
||||
styleBox.setSelectedId (selectedStyle);
|
||||
|
||||
styleBox.addListener (this);
|
||||
}
|
||||
|
||||
if ((optionFlags & Toolbar::showResetToDefaultsButton) != 0)
|
||||
{
|
||||
addAndMakeVisible (defaultButton);
|
||||
defaultButton.addListener (this);
|
||||
}
|
||||
|
||||
addAndMakeVisible (instructions);
|
||||
instructions.setFont (Font (13.0f));
|
||||
|
||||
setSize (500, 300);
|
||||
}
|
||||
|
||||
void comboBoxChanged (ComboBox*) override
|
||||
{
|
||||
switch (styleBox.getSelectedId())
|
||||
{
|
||||
case 1: toolbar.setStyle (Toolbar::iconsOnly); break;
|
||||
case 2: toolbar.setStyle (Toolbar::iconsWithText); break;
|
||||
case 3: toolbar.setStyle (Toolbar::textOnly); break;
|
||||
}
|
||||
|
||||
palette.resized(); // to make it update the styles
|
||||
}
|
||||
|
||||
void buttonClicked (Button*) override
|
||||
{
|
||||
toolbar.addDefaultItems (factory);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
Colour background;
|
||||
|
||||
if (DialogWindow* const 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);
|
||||
}
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TOOLBAR_H_INCLUDED
|
||||
#define JUCE_TOOLBAR_H_INCLUDED
|
||||
|
||||
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
|
||||
*/
|
||||
class JUCE_API Toolbar : public Component,
|
||||
public DragAndDropContainer,
|
||||
public DragAndDropTarget,
|
||||
private ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug)
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty toolbar component.
|
||||
|
||||
To add some icons or other components to your toolbar, you'll need to
|
||||
create a ToolbarItemFactory class that can create a suitable set of
|
||||
ToolbarItemComponents.
|
||||
|
||||
@see ToolbarItemFactory, ToolbarItemComponents
|
||||
*/
|
||||
Toolbar();
|
||||
|
||||
/** Destructor.
|
||||
|
||||
Any items on the bar will be deleted when the toolbar is deleted.
|
||||
*/
|
||||
~Toolbar();
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the bar's orientation.
|
||||
@see isVertical
|
||||
*/
|
||||
void setVertical (bool shouldBeVertical);
|
||||
|
||||
/** Returns true if the bar is set to be vertical, or false if it's horizontal.
|
||||
|
||||
You can change the bar's orientation with setVertical().
|
||||
*/
|
||||
bool isVertical() const noexcept { return vertical; }
|
||||
|
||||
/** Returns the depth of the bar.
|
||||
|
||||
If the bar is horizontal, this will return its height; if it's vertical, it
|
||||
will return its width.
|
||||
|
||||
@see getLength
|
||||
*/
|
||||
int getThickness() const noexcept;
|
||||
|
||||
/** Returns the length of the bar.
|
||||
|
||||
If the bar is horizontal, this will return its width; if it's vertical, it
|
||||
will return its height.
|
||||
|
||||
@see getThickness
|
||||
*/
|
||||
int getLength() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Deletes all items from the bar.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/** Adds an item to the toolbar.
|
||||
|
||||
The factory's ToolbarItemFactory::createItem() will be called by this method
|
||||
to create the component that will actually be added to the bar.
|
||||
|
||||
The new item will be inserted at the specified index (if the index is -1, it
|
||||
will be added to the right-hand or bottom end of the bar).
|
||||
|
||||
Once added, the component will be automatically deleted by this object when it
|
||||
is no longer needed.
|
||||
|
||||
@see ToolbarItemFactory
|
||||
*/
|
||||
void addItem (ToolbarItemFactory& factory,
|
||||
int itemId,
|
||||
int insertIndex = -1);
|
||||
|
||||
/** Deletes one of the items from the bar. */
|
||||
void removeToolbarItem (int itemIndex);
|
||||
|
||||
/** Removes an item from the bar and returns it. */
|
||||
ToolbarItemComponent* removeAndReturnItem (int itemIndex);
|
||||
|
||||
/** Returns the number of items currently on the toolbar.
|
||||
|
||||
@see getItemId, getItemComponent
|
||||
*/
|
||||
int getNumItems() const noexcept;
|
||||
|
||||
/** Returns the ID of the item with the given index.
|
||||
|
||||
If the index is less than zero or greater than the number of items,
|
||||
this will return 0.
|
||||
|
||||
@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 0.
|
||||
|
||||
@see getNumItems
|
||||
*/
|
||||
ToolbarItemComponent* getItemComponent (int itemIndex) const noexcept;
|
||||
|
||||
/** Clears this toolbar and adds to it the default set of items that the specified
|
||||
factory creates.
|
||||
|
||||
@see ToolbarItemFactory::getDefaultItemSet
|
||||
*/
|
||||
void addDefaultItems (ToolbarItemFactory& factoryToUse);
|
||||
|
||||
//==============================================================================
|
||||
/** Options for the way items should be displayed.
|
||||
@see setStyle, getStyle
|
||||
*/
|
||||
enum ToolbarItemStyle
|
||||
{
|
||||
iconsOnly, /**< Means that the toolbar should just contain icons. */
|
||||
iconsWithText, /**< Means that the toolbar should have text labels under each icon. */
|
||||
textOnly /**< Means that the toolbar only display text labels for each item. */
|
||||
};
|
||||
|
||||
/** Returns the toolbar's current style.
|
||||
@see ToolbarItemStyle, setStyle
|
||||
*/
|
||||
ToolbarItemStyle getStyle() const noexcept { return toolbarStyle; }
|
||||
|
||||
/** Changes the toolbar's current style.
|
||||
@see ToolbarItemStyle, getStyle, ToolbarItemComponent::setStyle
|
||||
*/
|
||||
void setStyle (const ToolbarItemStyle& newStyle);
|
||||
|
||||
//==============================================================================
|
||||
/** Flags used by the showCustomisationDialog() method. */
|
||||
enum CustomisationFlags
|
||||
{
|
||||
allowIconsOnlyChoice = 1, /**< If this flag is specified, the customisation dialog can
|
||||
show the "icons only" option on its choice of toolbar styles. */
|
||||
allowIconsWithTextChoice = 2, /**< If this flag is specified, the customisation dialog can
|
||||
show the "icons with text" option on its choice of toolbar styles. */
|
||||
allowTextOnlyChoice = 4, /**< If this flag is specified, the customisation dialog can
|
||||
show the "text only" option on its choice of toolbar styles. */
|
||||
showResetToDefaultsButton = 8, /**< If this flag is specified, the customisation dialog can
|
||||
show a button to reset the toolbar to its default set of items. */
|
||||
|
||||
allCustomisationOptionsEnabled = (allowIconsOnlyChoice | allowIconsWithTextChoice | allowTextOnlyChoice | showResetToDefaultsButton)
|
||||
};
|
||||
|
||||
/** Pops up a modal dialog box that allows this toolbar to be customised by the user.
|
||||
|
||||
The dialog contains a ToolbarItemPalette and various controls for editing other
|
||||
aspects of the toolbar. The dialog box will be opened modally, but the method will
|
||||
return immediately.
|
||||
|
||||
The factory is used to determine the set of items that will be shown on the
|
||||
palette.
|
||||
|
||||
The optionFlags parameter is a bitwise-or of values from the CustomisationFlags
|
||||
enum.
|
||||
|
||||
@see ToolbarItemPalette
|
||||
*/
|
||||
void showCustomisationDialog (ToolbarItemFactory& factory,
|
||||
int optionFlags = allCustomisationOptionsEnabled);
|
||||
|
||||
/** Turns on or off the toolbar's editing mode, in which its items can be
|
||||
rearranged by the user.
|
||||
|
||||
(In most cases it's easier just to use showCustomisationDialog() instead of
|
||||
trying to enable editing directly).
|
||||
|
||||
@see ToolbarItemPalette
|
||||
*/
|
||||
void setEditingActive (bool editingEnabled);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the toolbar.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1003200, /**< A colour to use to fill the toolbar's background. For
|
||||
more control over this, override LookAndFeel::paintToolbarBackground(). */
|
||||
separatorColourId = 0x1003210, /**< A colour to use to draw the separator lines. */
|
||||
|
||||
buttonMouseOverBackgroundColourId = 0x1003220, /**< A colour used to paint the background of buttons when the mouse is
|
||||
over them. */
|
||||
buttonMouseDownBackgroundColourId = 0x1003230, /**< A colour used to paint the background of buttons when the mouse is
|
||||
held down on them. */
|
||||
|
||||
labelTextColourId = 0x1003240, /**< A colour to use for drawing the text under buttons
|
||||
when the style is set to iconsWithText or textOnly. */
|
||||
|
||||
editingModeOutlineColourId = 0x1003250 /**< A colour to use for an outline around buttons when
|
||||
the customisation dialog is active and the mouse moves over them. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a string that represents the toolbar's current set of items.
|
||||
|
||||
This lets you later restore the same item layout using restoreFromString().
|
||||
|
||||
@see restoreFromString
|
||||
*/
|
||||
String toString() const;
|
||||
|
||||
/** Restores a set of items that was previously stored in a string by the toString()
|
||||
method.
|
||||
|
||||
The factory object is used to create any item components that are needed.
|
||||
|
||||
@see toString
|
||||
*/
|
||||
bool restoreFromString (ToolbarItemFactory& factoryToUse,
|
||||
const String& savedVersion);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void paintToolbarBackground (Graphics&, int width, int height, Toolbar&) = 0;
|
||||
|
||||
virtual Button* createToolbarMissingItemsButton (Toolbar&) = 0;
|
||||
|
||||
virtual void paintToolbarButtonBackground (Graphics&, int width, int height,
|
||||
bool isMouseOver, bool isMouseDown,
|
||||
ToolbarItemComponent&) = 0;
|
||||
|
||||
virtual void paintToolbarButtonLabel (Graphics&, int x, int y, int width, int height,
|
||||
const String& text, ToolbarItemComponent&) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
bool isInterestedInDragSource (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDragMove (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDragExit (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDropped (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void updateAllItemPositions (bool animate);
|
||||
/** @internal */
|
||||
static ToolbarItemComponent* createItem (ToolbarItemFactory&, int itemId);
|
||||
/** @internal */
|
||||
static const char* const toolbarDragDescriptor;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ScopedPointer<Button> missingItemsButton;
|
||||
bool vertical, isEditingActive;
|
||||
ToolbarItemStyle toolbarStyle;
|
||||
class MissingItemsComponent;
|
||||
friend class MissingItemsComponent;
|
||||
OwnedArray <ToolbarItemComponent> items;
|
||||
class Spacer;
|
||||
class CustomisationDialog;
|
||||
|
||||
void buttonClicked (Button*) override;
|
||||
void addItemInternal (ToolbarItemFactory& factory, int itemId, int insertIndex);
|
||||
|
||||
ToolbarItemComponent* getNextActiveComponent (int index, int delta) const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Toolbar)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TOOLBAR_H_INCLUDED
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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 (! (isDragging || e.mouseWasClicked()))
|
||||
{
|
||||
isDragging = true;
|
||||
|
||||
if (DragAndDropContainer* const dnd = DragAndDropContainer::findParentDragContainerFor (this))
|
||||
{
|
||||
dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), Image::null, true);
|
||||
|
||||
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 = nullptr;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
const int indent = contentArea.getX();
|
||||
int y = indent;
|
||||
int 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 = Rectangle<int>();
|
||||
}
|
||||
|
||||
contentAreaChanged (contentArea);
|
||||
}
|
||||
|
||||
void ToolbarItemComponent::setEditingMode (const ToolbarEditingMode newMode)
|
||||
{
|
||||
if (mode != newMode)
|
||||
{
|
||||
mode = newMode;
|
||||
repaint();
|
||||
|
||||
if (mode == normalMode)
|
||||
{
|
||||
overlayComp = nullptr;
|
||||
}
|
||||
else if (overlayComp == nullptr)
|
||||
{
|
||||
addAndMakeVisible (overlayComp = new ItemDragAndDropOverlayComponent());
|
||||
overlayComp->parentSizeChanged();
|
||||
}
|
||||
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TOOLBARITEMCOMPONENT_H_INCLUDED
|
||||
#define JUCE_TOOLBARITEMCOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
class JUCE_API ToolbarItemComponent : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Constructor.
|
||||
|
||||
@param itemId the ID of the type of toolbar item which this represents
|
||||
@param labelText the text to display if the toolbar's style is set to
|
||||
Toolbar::iconsWithText or Toolbar::textOnly
|
||||
@param isBeingUsedAsAButton set this to false if you don't want the button
|
||||
to draw itself with button over/down states when the mouse
|
||||
moves over it or clicks
|
||||
*/
|
||||
ToolbarItemComponent (int itemId,
|
||||
const String& labelText,
|
||||
bool isBeingUsedAsAButton);
|
||||
|
||||
/** Destructor. */
|
||||
~ToolbarItemComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the item type ID that this component represents.
|
||||
This value is in the constructor.
|
||||
*/
|
||||
int getItemId() const noexcept { return itemId; }
|
||||
|
||||
/** Returns the toolbar that contains this component, or nullptr if it's not currently
|
||||
inside one.
|
||||
*/
|
||||
Toolbar* getToolbar() const;
|
||||
|
||||
/** Returns true if this component is currently inside a toolbar which is vertical.
|
||||
@see Toolbar::isVertical
|
||||
*/
|
||||
bool isToolbarVertical() const;
|
||||
|
||||
/** Returns the current style setting of this item.
|
||||
|
||||
Styles are listed in the Toolbar::ToolbarItemStyle enum.
|
||||
@see setStyle, Toolbar::getStyle
|
||||
*/
|
||||
Toolbar::ToolbarItemStyle getStyle() const noexcept { return toolbarStyle; }
|
||||
|
||||
/** Changes the current style setting of this item.
|
||||
|
||||
Styles are listed in the Toolbar::ToolbarItemStyle enum, and are automatically updated
|
||||
by the toolbar that holds this item.
|
||||
|
||||
@see setStyle, Toolbar::setStyle
|
||||
*/
|
||||
virtual void setStyle (const Toolbar::ToolbarItemStyle& newStyle);
|
||||
|
||||
/** Returns the area of the component that should be used to display the button image or
|
||||
other contents of the item.
|
||||
|
||||
This content area may change when the item's style changes, and may leave a space around the
|
||||
edge of the component where the text label can be shown.
|
||||
|
||||
@see contentAreaChanged
|
||||
*/
|
||||
const Rectangle<int>& getContentArea() const noexcept { return contentArea; }
|
||||
|
||||
//==============================================================================
|
||||
/** This method must return the size criteria for this item, based on a given toolbar
|
||||
size and orientation.
|
||||
|
||||
The preferredSize, minSize and maxSize values must all be set by your implementation
|
||||
method. If the toolbar is horizontal, these will be the width of the item; for a vertical
|
||||
toolbar, they refer to the item's height.
|
||||
|
||||
The preferredSize is the size that the component would like to be, and this must be
|
||||
between the min and max sizes. For a fixed-size item, simply set all three variables to
|
||||
the same value.
|
||||
|
||||
The toolbarThickness parameter tells you the depth of the toolbar - the same as calling
|
||||
Toolbar::getThickness().
|
||||
|
||||
The isToolbarVertical parameter tells you whether the bar is oriented horizontally or
|
||||
vertically.
|
||||
*/
|
||||
virtual bool getToolbarItemSizes (int toolbarThickness,
|
||||
bool isToolbarVertical,
|
||||
int& preferredSize,
|
||||
int& minSize,
|
||||
int& maxSize) = 0;
|
||||
|
||||
/** Your subclass should use this method to draw its content area.
|
||||
|
||||
The graphics object that is passed-in will have been clipped and had its origin
|
||||
moved to fit the content area as specified get getContentArea(). The width and height
|
||||
parameters are the width and height of the content area.
|
||||
|
||||
If the component you're writing isn't a button, you can just do nothing in this method.
|
||||
*/
|
||||
virtual void paintButtonArea (Graphics& g,
|
||||
int width, int height,
|
||||
bool isMouseOver, bool isMouseDown) = 0;
|
||||
|
||||
/** Callback to indicate that the content area of this item has changed.
|
||||
|
||||
This might be because the component was resized, or because the style changed and
|
||||
the space needed for the text label is different.
|
||||
|
||||
See getContentArea() for a description of what the area is.
|
||||
*/
|
||||
virtual void contentAreaChanged (const Rectangle<int>& newBounds) = 0;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Editing modes.
|
||||
These are used by setEditingMode(), but will be rarely needed in user code.
|
||||
*/
|
||||
enum ToolbarEditingMode
|
||||
{
|
||||
normalMode = 0, /**< Means that the component is active, inside a toolbar. */
|
||||
editableOnToolbar, /**< Means that the component is on a toolbar, but the toolbar is in
|
||||
customisation mode, and the items can be dragged around. */
|
||||
editableOnPalette /**< Means that the component is on an new-item palette, so it can be
|
||||
dragged onto a toolbar to add it to that bar.*/
|
||||
};
|
||||
|
||||
/** Changes the editing mode of this component.
|
||||
|
||||
This is used by the ToolbarItemPalette and related classes for making the items draggable,
|
||||
and is unlikely to be of much use in end-user-code.
|
||||
*/
|
||||
void setEditingMode (const ToolbarEditingMode newMode);
|
||||
|
||||
/** Returns the current editing mode of this component.
|
||||
|
||||
This is used by the ToolbarItemPalette and related classes for making the items draggable,
|
||||
and is unlikely to be of much use in end-user-code.
|
||||
*/
|
||||
ToolbarEditingMode getEditingMode() const noexcept { return mode; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool isMouseOver, bool isMouseDown) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
friend class Toolbar;
|
||||
class ItemDragAndDropOverlayComponent;
|
||||
friend class ItemDragAndDropOverlayComponent;
|
||||
const int itemId;
|
||||
ToolbarEditingMode mode;
|
||||
Toolbar::ToolbarItemStyle toolbarStyle;
|
||||
ScopedPointer <Component> overlayComp;
|
||||
int dragOffsetX, dragOffsetY;
|
||||
bool isActive, isBeingDragged, isBeingUsedAsAButton;
|
||||
Rectangle<int> contentArea;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarItemComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TOOLBARITEMCOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TOOLBARITEMFACTORY_H_INCLUDED
|
||||
#define JUCE_TOOLBARITEMFACTORY_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TOOLBARITEMFACTORY_H_INCLUDED
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ToolbarItemPalette::ToolbarItemPalette (ToolbarItemFactory& tbf, Toolbar& bar)
|
||||
: factory (tbf), toolbar (bar)
|
||||
{
|
||||
Component* const itemHolder = new Component();
|
||||
viewport.setViewedComponent (itemHolder);
|
||||
|
||||
Array<int> allIds;
|
||||
factory.getAllToolbarItemIds (allIds);
|
||||
|
||||
for (int i = 0; i < allIds.size(); ++i)
|
||||
addComponent (allIds.getUnchecked (i), -1);
|
||||
|
||||
addAndMakeVisible (viewport);
|
||||
}
|
||||
|
||||
ToolbarItemPalette::~ToolbarItemPalette()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ToolbarItemPalette::addComponent (const int itemId, const int index)
|
||||
{
|
||||
if (ToolbarItemComponent* const 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)
|
||||
{
|
||||
const int index = items.indexOf (&comp);
|
||||
jassert (index >= 0);
|
||||
items.removeObject (&comp, false);
|
||||
|
||||
addComponent (comp.getItemId(), index);
|
||||
resized();
|
||||
}
|
||||
|
||||
void ToolbarItemPalette::resized()
|
||||
{
|
||||
viewport.setBoundsInset (BorderSize<int> (1));
|
||||
|
||||
Component* const itemHolder = viewport.getViewedComponent();
|
||||
|
||||
const int indent = 8;
|
||||
const int preferredWidth = viewport.getWidth() - viewport.getScrollBarThickness() - indent;
|
||||
const int height = toolbar.getThickness();
|
||||
int x = indent;
|
||||
int y = indent;
|
||||
int maxX = 0;
|
||||
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
ToolbarItemComponent* const tc = items.getUnchecked(i);
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TOOLBARITEMPALETTE_H_INCLUDED
|
||||
#define JUCE_TOOLBARITEMPALETTE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
class JUCE_API ToolbarItemPalette : public Component,
|
||||
public DragAndDropContainer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a palette of items for a given factory, with the aim of adding them
|
||||
to the specified toolbar.
|
||||
|
||||
The ToolbarItemFactory::getAllToolbarItemIds() method is used to create the
|
||||
set of items that are shown in this palette.
|
||||
|
||||
The toolbar and factory must not be deleted while this object exists.
|
||||
*/
|
||||
ToolbarItemPalette (ToolbarItemFactory& factory,
|
||||
Toolbar& toolbar);
|
||||
|
||||
/** Destructor. */
|
||||
~ToolbarItemPalette();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
ToolbarItemFactory& factory;
|
||||
Toolbar& toolbar;
|
||||
Viewport viewport;
|
||||
OwnedArray <ToolbarItemComponent> items;
|
||||
|
||||
friend class Toolbar;
|
||||
void replaceComponent (ToolbarItemComponent&);
|
||||
void addComponent (int itemId, int index);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarItemPalette)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TOOLBARITEMPALETTE_H_INCLUDED
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,891 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TREEVIEW_H_INCLUDED
|
||||
#define JUCE_TREEVIEW_H_INCLUDED
|
||||
|
||||
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.
|
||||
*/
|
||||
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. */
|
||||
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.
|
||||
|
||||
@see itemOpennessChanged, mightContainSubItems
|
||||
*/
|
||||
void setOpen (bool shouldBeOpen);
|
||||
|
||||
/** 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()
|
||||
*/
|
||||
void setSelected (bool shouldBeSelected,
|
||||
bool deselectOtherItemsFirst,
|
||||
NotificationType shouldNotify = sendNotification);
|
||||
|
||||
/** Returns the rectangle that this item occupies.
|
||||
|
||||
If relativeToTreeViewTopLeft is true, the coordinates are relative to the
|
||||
top-left of the TreeView comp, so this will depend on the scroll-position of
|
||||
the tree. If false, it is relative to the top-left of the topmost item in the
|
||||
tree (so this would be unaffected by scrolling the view).
|
||||
*/
|
||||
Rectangle<int> getItemPosition (bool relativeToTreeViewTopLeft) const noexcept;
|
||||
|
||||
/** Sends a signal to the treeview to make it refresh itself.
|
||||
Call this if your items have changed and you want the tree to update to reflect this.
|
||||
*/
|
||||
void treeHasChanged() const noexcept;
|
||||
|
||||
/** Sends a repaint message to redraw just this item.
|
||||
|
||||
Note that you should only call this if you want to repaint a superficial change. If
|
||||
you're altering the tree's nodes, you should instead call treeHasChanged().
|
||||
*/
|
||||
void repaintItem() const;
|
||||
|
||||
/** Returns the row number of this item in the tree.
|
||||
The row number of an item will change according to which items are open.
|
||||
@see TreeView::getNumRowsInTree(), TreeView::getItemOnRow()
|
||||
*/
|
||||
int getRowNumberInTree() const noexcept;
|
||||
|
||||
/** Returns true if all the item's parent nodes are open.
|
||||
This is useful to check whether the item might actually be visible or not.
|
||||
*/
|
||||
bool areAllParentsOpen() const noexcept;
|
||||
|
||||
/** Changes whether lines are drawn to connect any sub-items to this item.
|
||||
By default, line-drawing is turned on according to LookAndFeel::areLinesDrawnForTreeView().
|
||||
*/
|
||||
void setLinesDrawnForSubItems (bool shouldDrawLines) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Tells the tree whether this item can potentially be opened.
|
||||
|
||||
If your item could contain sub-items, this should return true; if it returns
|
||||
false then the tree will not try to open the item. This determines whether or
|
||||
not the item will be drawn with a 'plus' button next to it.
|
||||
*/
|
||||
virtual bool mightContainSubItems() = 0;
|
||||
|
||||
/** Returns a string to uniquely identify this item.
|
||||
|
||||
If you're planning on using the TreeView::getOpennessState() method, then
|
||||
these strings will be used to identify which nodes are open. The string
|
||||
should be unique amongst the item's sibling items, but it's ok for there
|
||||
to be duplicates at other levels of the tree.
|
||||
|
||||
If you're not going to store the state, then it's ok not to bother implementing
|
||||
this method.
|
||||
*/
|
||||
virtual String getUniqueName() const;
|
||||
|
||||
/** Called when an item is opened or closed.
|
||||
|
||||
When setOpen() is called and the item has specified that it might
|
||||
have sub-items with the mightContainSubItems() method, this method
|
||||
is called to let the item create or manage its sub-items.
|
||||
|
||||
So when this is called with isNowOpen set to true (i.e. when the item is being
|
||||
opened), a subclass might choose to use clearSubItems() and addSubItem() to
|
||||
refresh its sub-item list.
|
||||
|
||||
When this is called with isNowOpen set to false, the subclass might want
|
||||
to use clearSubItems() to save on space, or it might choose to leave them,
|
||||
depending on the nature of the tree.
|
||||
|
||||
You could also use this callback as a trigger to start a background process
|
||||
which asynchronously creates sub-items and adds them, if that's more
|
||||
appropriate for the task in hand.
|
||||
|
||||
@see mightContainSubItems
|
||||
*/
|
||||
virtual void itemOpennessChanged (bool isNowOpen);
|
||||
|
||||
/** Must return the width required by this item.
|
||||
|
||||
If your item needs to have a particular width in pixels, return that value; if
|
||||
you'd rather have it just fill whatever space is available in the treeview,
|
||||
return -1.
|
||||
|
||||
If all your items return -1, no horizontal scrollbar will be shown, but if any
|
||||
items have fixed widths and extend beyond the width of the treeview, a
|
||||
scrollbar will appear.
|
||||
|
||||
Each item can be a different width, but if they change width, you should call
|
||||
treeHasChanged() to update the tree.
|
||||
*/
|
||||
virtual int getItemWidth() const { return -1; }
|
||||
|
||||
/** Must return the height required by this item.
|
||||
|
||||
This is the height in pixels that the item will take up. Items in the tree
|
||||
can be different heights, but if they change height, you should call
|
||||
treeHasChanged() to update the tree.
|
||||
*/
|
||||
virtual int getItemHeight() const { return 20; }
|
||||
|
||||
/** You can override this method to return false if you don't want to allow the
|
||||
user to select this item.
|
||||
*/
|
||||
virtual bool canBeSelected() const { return true; }
|
||||
|
||||
/** Creates a component that will be used to represent this item.
|
||||
|
||||
You don't have to implement this method - if it returns nullptr then no component
|
||||
will be used for the item, and you can just draw it using the paintItem()
|
||||
callback. But if you do return a component, it will be positioned in the
|
||||
treeview so that it can be used to represent this item.
|
||||
|
||||
The component returned will be managed by the treeview, so always return
|
||||
a new component, and don't keep a reference to it, as the treeview will
|
||||
delete it later when it goes off the screen or is no longer needed. Also
|
||||
bear in mind that if the component keeps a reference to the item that
|
||||
created it, that item could be deleted before the component. Its position
|
||||
and size will be completely managed by the tree, so don't attempt to move it
|
||||
around.
|
||||
|
||||
Something you may want to do with your component is to give it a pointer to
|
||||
the TreeView that created it. This is perfectly safe, and there's no danger
|
||||
of it becoming a dangling pointer because the TreeView will always delete
|
||||
the component before it is itself deleted.
|
||||
|
||||
As long as you stick to these rules you can return whatever kind of
|
||||
component you like. It's most useful if you're doing things like drag-and-drop
|
||||
of items, or want to use a Label component to edit item names, etc.
|
||||
*/
|
||||
virtual Component* createItemComponent() { return nullptr; }
|
||||
|
||||
//==============================================================================
|
||||
/** Draws the item's contents.
|
||||
|
||||
You can choose to either implement this method and draw each item, or you
|
||||
can use createItemComponent() to create a component that will represent the
|
||||
item.
|
||||
|
||||
If all you need in your tree is to be able to draw the items and detect when
|
||||
the user selects or double-clicks one of them, it's probably enough to
|
||||
use paintItem(), itemClicked() and itemDoubleClicked(). If you need more
|
||||
complicated interactions, you may need to use createItemComponent() instead.
|
||||
|
||||
@param g the graphics context to draw into
|
||||
@param width the width of the area available for drawing
|
||||
@param height the height of the area available for drawing
|
||||
*/
|
||||
virtual void paintItem (Graphics& g, int width, int height);
|
||||
|
||||
/** Draws the item's open/close button.
|
||||
|
||||
If you don't implement this method, the default behaviour is to call
|
||||
LookAndFeel::drawTreeviewPlusMinusBox(), but you can override it for custom
|
||||
effects. You may want to override it and call the base-class implementation
|
||||
with a different backgroundColour parameter, if your implementation has a
|
||||
background colour other than the default (white).
|
||||
*/
|
||||
virtual void paintOpenCloseButton (Graphics&, const Rectangle<float>& area,
|
||||
Colour backgroundColour, bool isMouseOver);
|
||||
|
||||
/** Draws the line that connects this item to the vertical line extending below its parent. */
|
||||
virtual void paintHorizontalConnectingLine (Graphics&, const Line<float>& line);
|
||||
|
||||
/** Draws the line that extends vertically up towards one of its parents, or down to one of its children. */
|
||||
virtual void paintVerticalConnectingLine (Graphics&, const Line<float>& line);
|
||||
|
||||
/** Called when the user clicks on this item.
|
||||
|
||||
If you're using createItemComponent() to create a custom component for the
|
||||
item, the mouse-clicks might not make it through to the treeview, but this
|
||||
is how you find out about clicks when just drawing each item individually.
|
||||
|
||||
The associated mouse-event details are passed in, so you can find out about
|
||||
which button, where it was, etc.
|
||||
|
||||
@see itemDoubleClicked
|
||||
*/
|
||||
virtual void itemClicked (const MouseEvent& e);
|
||||
|
||||
/** 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& e);
|
||||
|
||||
/** 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);
|
||||
|
||||
/** The item can return a tool tip string here if it wants to.
|
||||
@see TooltipClient
|
||||
*/
|
||||
virtual String getTooltip();
|
||||
|
||||
//==============================================================================
|
||||
/** To allow items from your treeview to be dragged-and-dropped, implement this method.
|
||||
|
||||
If this returns a non-null variant then when the user drags an item, the treeview will
|
||||
try to find a DragAndDropContainer in its parent hierarchy, and will use it to trigger
|
||||
a drag-and-drop operation, using this string as the source description, with the treeview
|
||||
itself as the source component.
|
||||
|
||||
If you need more complex drag-and-drop behaviour, you can use custom components for
|
||||
the items, and use those to trigger the drag.
|
||||
|
||||
To accept drag-and-drop in your tree, see isInterestedInDragSource(),
|
||||
isInterestedInFileDrag(), etc.
|
||||
|
||||
@see DragAndDropContainer::startDragging
|
||||
*/
|
||||
virtual var getDragSourceDescription();
|
||||
|
||||
/** If you want your item to be able to have files drag-and-dropped onto it, implement this
|
||||
method and return true.
|
||||
|
||||
If you return true and allow some files to be dropped, you'll also need to implement the
|
||||
filesDropped() method to do something with them.
|
||||
|
||||
Note that this will be called often, so make your implementation very quick! There's
|
||||
certainly no time to try opening the files and having a think about what's inside them!
|
||||
|
||||
For responding to internal drag-and-drop of other types of object, see isInterestedInDragSource().
|
||||
@see FileDragAndDropTarget::isInterestedInFileDrag, isInterestedInDragSource
|
||||
*/
|
||||
virtual bool isInterestedInFileDrag (const StringArray& files);
|
||||
|
||||
/** When files are dropped into this item, this callback is invoked.
|
||||
|
||||
For this to work, you'll need to have also implemented isInterestedInFileDrag().
|
||||
The insertIndex value indicates where in the list of sub-items the files were dropped.
|
||||
If files are dropped onto an area of the tree where there are no visible items, this
|
||||
method is called on the root item of the tree, with an insert index of 0.
|
||||
@see FileDragAndDropTarget::filesDropped, isInterestedInFileDrag
|
||||
*/
|
||||
virtual void filesDropped (const StringArray& files, int insertIndex);
|
||||
|
||||
/** If you want your item to act as a DragAndDropTarget, implement this method and return true.
|
||||
|
||||
If you implement this method, you'll also need to implement itemDropped() in order to handle
|
||||
the items when they are dropped.
|
||||
To respond to drag-and-drop of files from external applications, see isInterestedInFileDrag().
|
||||
@see DragAndDropTarget::isInterestedInDragSource, itemDropped
|
||||
*/
|
||||
virtual bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails);
|
||||
|
||||
/** When a things are dropped into this item, this callback is invoked.
|
||||
|
||||
For this to work, you need to have also implemented isInterestedInDragSource().
|
||||
The insertIndex value indicates where in the list of sub-items the new items should be placed.
|
||||
If files are dropped onto an area of the tree where there are no visible items, this
|
||||
method is called on the root item of the tree, with an insert index of 0.
|
||||
@see isInterestedInDragSource, DragAndDropTarget::itemDropped
|
||||
*/
|
||||
virtual void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex);
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a flag to indicate that the item wants to be allowed
|
||||
to draw all the way across to the left edge of the treeview.
|
||||
|
||||
By default this is false, which means that when the paintItem()
|
||||
method is called, its graphics context is clipped to only allow
|
||||
drawing within the item's rectangle. If this flag is set to true,
|
||||
then the graphics context isn't clipped on its left side, so it
|
||||
can draw all the way across to the left margin. Note that the
|
||||
context will still have its origin in the same place though, so
|
||||
the coordinates of anything to its left will be negative. It's
|
||||
mostly useful if you want to draw a wider bar behind the
|
||||
highlighted item.
|
||||
*/
|
||||
void setDrawsInLeftMargin (bool canDrawInLeftMargin) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Saves the current state of open/closed nodes so it can be restored later.
|
||||
|
||||
This takes a snapshot of which sub-nodes have been explicitly opened or closed,
|
||||
and records it as XML. To identify node objects it uses the
|
||||
TreeViewItem::getUniqueName() method to create named paths. This
|
||||
means that the same state of open/closed nodes can be restored to a
|
||||
completely different instance of the tree, as long as it contains nodes
|
||||
whose unique names are the same.
|
||||
|
||||
You'd normally want to use TreeView::getOpennessState() rather than call it
|
||||
for a specific item, but this can be handy if you need to briefly save the state
|
||||
for a section of the tree.
|
||||
|
||||
The caller is responsible for deleting the object that is returned.
|
||||
|
||||
Note that if all nodes of the tree are in their default state, then this may
|
||||
return a nullptr.
|
||||
|
||||
@see TreeView::getOpennessState, restoreOpennessState
|
||||
*/
|
||||
XmlElement* getOpennessState() const;
|
||||
|
||||
/** Restores the openness of this item and all its sub-items from a saved state.
|
||||
|
||||
See TreeView::restoreOpennessState for more details.
|
||||
|
||||
You'd normally want to use TreeView::restoreOpennessState() rather than call it
|
||||
for a specific item, but this can be handy if you need to briefly save the state
|
||||
for a section of the tree.
|
||||
|
||||
@see TreeView::restoreOpennessState, getOpennessState
|
||||
*/
|
||||
void restoreOpennessState (const XmlElement& xml);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the index of this item in its parent's sub-items. */
|
||||
int getIndexInParent() const noexcept;
|
||||
|
||||
/** Returns true if this item is the last of its parent's sub-itens. */
|
||||
bool isLastOfSiblings() const noexcept;
|
||||
|
||||
/** Creates a string that can be used to uniquely retrieve this item in the tree.
|
||||
|
||||
The string that is returned can be passed to TreeView::findItemFromIdentifierString().
|
||||
The string takes the form of a path, constructed from the getUniqueName() of this
|
||||
item and all its parents, so these must all be correctly implemented for it to work.
|
||||
@see TreeView::findItemFromIdentifierString, getUniqueName
|
||||
*/
|
||||
String getItemIdentifierString() const;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This handy class takes a copy of a TreeViewItem's openness when you create it,
|
||||
and restores that openness state when its destructor is called.
|
||||
|
||||
This can very handy when you're refreshing sub-items - e.g.
|
||||
@code
|
||||
void MyTreeViewItem::updateChildItems()
|
||||
{
|
||||
OpennessRestorer openness (*this); // saves the openness state here..
|
||||
|
||||
clearSubItems();
|
||||
|
||||
// add a bunch of sub-items here which may or may not be the same as the ones that
|
||||
// were previously there
|
||||
addSubItem (...
|
||||
|
||||
// ..and at this point, the old openness is restored, so any items that haven't
|
||||
// changed will have their old openness retained.
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
class OpennessRestorer
|
||||
{
|
||||
public:
|
||||
OpennessRestorer (TreeViewItem& treeViewItem);
|
||||
~OpennessRestorer();
|
||||
|
||||
private:
|
||||
TreeViewItem& treeViewItem;
|
||||
ScopedPointer <XmlElement> oldOpenness;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpennessRestorer)
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
TreeView* ownerView;
|
||||
TreeViewItem* parentItem;
|
||||
OwnedArray<TreeViewItem> subItems;
|
||||
int y, itemHeight, totalHeight, itemWidth, totalWidth;
|
||||
int uid;
|
||||
bool selected : 1;
|
||||
bool redrawNeeded : 1;
|
||||
bool drawLinesInside : 1;
|
||||
bool drawLinesSet : 1;
|
||||
bool drawsInLeftMargin : 1;
|
||||
unsigned int openness : 2;
|
||||
|
||||
friend class TreeView;
|
||||
|
||||
void updatePositions (int newY);
|
||||
int getIndentX() const noexcept;
|
||||
void setOwnerView (TreeView*) noexcept;
|
||||
void paintRecursively (Graphics&, int width);
|
||||
TreeViewItem* getTopLevelItem() noexcept;
|
||||
TreeViewItem* findItemRecursively (int y) noexcept;
|
||||
TreeViewItem* getDeepestOpenParentItem() noexcept;
|
||||
int getNumRows() const noexcept;
|
||||
TreeViewItem* getItemOnRow (int index) noexcept;
|
||||
void deselectAllRecursively (TreeViewItem* itemToIgnore);
|
||||
int countSelectedItemsRecursively (int depth) const noexcept;
|
||||
TreeViewItem* getSelectedItemWithIndex (int index) noexcept;
|
||||
TreeViewItem* getNextVisibleItem (bool recurse) const noexcept;
|
||||
TreeViewItem* findItemFromIdentifierString (const String&);
|
||||
void restoreToDefaultOpenness();
|
||||
bool isFullyOpen() const noexcept;
|
||||
XmlElement* getOpennessState (bool canReturnNull) const;
|
||||
bool removeSubItemFromList (int index, bool deleteItem);
|
||||
void removeAllSubItemsFromList();
|
||||
bool areLinesDrawn() const;
|
||||
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// The parameters for these methods have changed - please update your code!
|
||||
virtual void isInterestedInDragSource (const String&, Component*) {}
|
||||
virtual int itemDropped (const String&, Component*, int) { return 0; }
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewItem)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A tree-view component.
|
||||
|
||||
Use one of these to hold and display a structure of TreeViewItem objects.
|
||||
|
||||
*/
|
||||
class JUCE_API TreeView : public Component,
|
||||
public SettableTooltipClient,
|
||||
public FileDragAndDropTarget,
|
||||
public DragAndDropTarget
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty treeview.
|
||||
|
||||
Once you've got a treeview component, you'll need to give it something to
|
||||
display, using the setRootItem() method.
|
||||
*/
|
||||
TreeView (const String& componentName = String::empty);
|
||||
|
||||
/** Destructor. */
|
||||
~TreeView();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the item that is displayed in the treeview.
|
||||
|
||||
A tree has a single root item which contains as many sub-items as it needs. If
|
||||
you want the tree to contain a number of root items, you should still use a single
|
||||
root item above these, but hide it using setRootItemVisible().
|
||||
|
||||
You can pass nullptr to this method to clear the tree and remove its current root item.
|
||||
|
||||
The object passed in will not be deleted by the treeview, it's up to the caller
|
||||
to delete it when no longer needed. BUT make absolutely sure that you don't delete
|
||||
this item until you've removed it from the tree, either by calling setRootItem (nullptr),
|
||||
or by deleting the tree first. You can also use deleteRootItem() as a quick way
|
||||
to delete it.
|
||||
*/
|
||||
void setRootItem (TreeViewItem* newRootItem);
|
||||
|
||||
/** Returns the tree's root item.
|
||||
|
||||
This will be the last object passed to setRootItem(), or nullptr if none has been set.
|
||||
*/
|
||||
TreeViewItem* getRootItem() const noexcept { return rootItem; }
|
||||
|
||||
/** This will remove and delete the current root item.
|
||||
It's a convenient way of deleting the item and calling setRootItem (nullptr).
|
||||
*/
|
||||
void deleteRootItem();
|
||||
|
||||
/** Changes whether the tree's root item is shown or not.
|
||||
|
||||
If the root item is hidden, only its sub-items will be shown in the treeview - this
|
||||
lets you make the tree look as if it's got many root items. If it's hidden, this call
|
||||
will also make sure the root item is open (otherwise the treeview would look empty).
|
||||
*/
|
||||
void setRootItemVisible (bool shouldBeVisible);
|
||||
|
||||
/** Returns true if the root item is visible.
|
||||
|
||||
@see setRootItemVisible
|
||||
*/
|
||||
bool isRootItemVisible() const noexcept { return rootItemVisible; }
|
||||
|
||||
/** Sets whether items are open or closed by default.
|
||||
|
||||
Normally, items are closed until the user opens them, but you can use this
|
||||
to make them default to being open until explicitly closed.
|
||||
|
||||
@see areItemsOpenByDefault
|
||||
*/
|
||||
void setDefaultOpenness (bool isOpenByDefault);
|
||||
|
||||
/** Returns true if the tree's items default to being open.
|
||||
|
||||
@see setDefaultOpenness
|
||||
*/
|
||||
bool areItemsOpenByDefault() const noexcept { return defaultOpenness; }
|
||||
|
||||
/** This sets a flag to indicate that the tree can be used for multi-selection.
|
||||
|
||||
You can always select multiple items internally by calling the
|
||||
TreeViewItem::setSelected() method, but this flag indicates whether the user
|
||||
is allowed to multi-select by clicking on the tree.
|
||||
|
||||
By default it is disabled.
|
||||
|
||||
@see isMultiSelectEnabled
|
||||
*/
|
||||
void setMultiSelectEnabled (bool canMultiSelect);
|
||||
|
||||
/** Returns whether multi-select has been enabled for the tree.
|
||||
|
||||
@see setMultiSelectEnabled
|
||||
*/
|
||||
bool isMultiSelectEnabled() const noexcept { return multiSelectEnabled; }
|
||||
|
||||
/** Sets a flag to indicate whether to hide the open/close buttons.
|
||||
|
||||
@see areOpenCloseButtonsVisible
|
||||
*/
|
||||
void setOpenCloseButtonsVisible (bool shouldBeVisible);
|
||||
|
||||
/** Returns whether open/close buttons are shown.
|
||||
|
||||
@see setOpenCloseButtonsVisible
|
||||
*/
|
||||
bool areOpenCloseButtonsVisible() const noexcept { return openCloseButtonsVisible; }
|
||||
|
||||
//==============================================================================
|
||||
/** Deselects any items that are currently selected. */
|
||||
void clearSelectedItems();
|
||||
|
||||
/** Returns the number of items that are currently selected.
|
||||
If maximumDepthToSearchTo is >= 0, it lets you specify a maximum depth to which the
|
||||
tree will be recursed.
|
||||
@see getSelectedItem, clearSelectedItems
|
||||
*/
|
||||
int getNumSelectedItems (int maximumDepthToSearchTo = -1) const noexcept;
|
||||
|
||||
/** Returns one of the selected items in the tree.
|
||||
@param index the index, 0 to (getNumSelectedItems() - 1)
|
||||
*/
|
||||
TreeViewItem* getSelectedItem (int index) const noexcept;
|
||||
|
||||
/** Moves the selected row up or down by the specified number of rows. */
|
||||
void moveSelectedRow (int deltaRows);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of rows the tree is using.
|
||||
This will depend on which items are open.
|
||||
@see TreeViewItem::getRowNumberInTree()
|
||||
*/
|
||||
int getNumRowsInTree() const;
|
||||
|
||||
/** Returns the item on a particular row of the tree.
|
||||
If the index is out of range, this will return 0.
|
||||
@see getNumRowsInTree, TreeViewItem::getRowNumberInTree()
|
||||
*/
|
||||
TreeViewItem* getItemOnRow (int index) const;
|
||||
|
||||
/** Returns the item that contains a given y position.
|
||||
The y is relative to the top of the TreeView component.
|
||||
*/
|
||||
TreeViewItem* getItemAt (int yPosition) const noexcept;
|
||||
|
||||
/** Tries to scroll the tree so that this item is on-screen somewhere. */
|
||||
void scrollToKeepItemVisible (TreeViewItem* item);
|
||||
|
||||
/** Returns the treeview's Viewport object. */
|
||||
Viewport* getViewport() const noexcept;
|
||||
|
||||
/** Returns the number of pixels by which each nested level of the tree is indented.
|
||||
@see setIndentSize
|
||||
*/
|
||||
int getIndentSize() noexcept;
|
||||
|
||||
/** Changes the distance by which each nested level of the tree is indented.
|
||||
@see getIndentSize
|
||||
*/
|
||||
void setIndentSize (int newIndentSize);
|
||||
|
||||
/** Searches the tree for an item with the specified identifier.
|
||||
The identifier string must have been created by calling TreeViewItem::getItemIdentifierString().
|
||||
If no such item exists, this will return false. If the item is found, all of its items
|
||||
will be automatically opened.
|
||||
*/
|
||||
TreeViewItem* findItemFromIdentifierString (const String& identifierString) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Saves the current state of open/closed nodes so it can be restored later.
|
||||
|
||||
This takes a snapshot of which nodes have been explicitly opened or closed,
|
||||
and records it as XML. To identify node objects it uses the
|
||||
TreeViewItem::getUniqueName() method to create named paths. This
|
||||
means that the same state of open/closed nodes can be restored to a
|
||||
completely different instance of the tree, as long as it contains nodes
|
||||
whose unique names are the same.
|
||||
|
||||
The caller is responsible for deleting the object that is returned.
|
||||
|
||||
@param alsoIncludeScrollPosition if this is true, the state will also
|
||||
include information about where the
|
||||
tree has been scrolled to vertically,
|
||||
so this can also be restored
|
||||
@see restoreOpennessState
|
||||
*/
|
||||
XmlElement* getOpennessState (bool alsoIncludeScrollPosition) const;
|
||||
|
||||
/** Restores a previously saved arrangement of open/closed nodes.
|
||||
|
||||
This will try to restore a snapshot of the tree's state that was created by
|
||||
the getOpennessState() method. If any of the nodes named in the original
|
||||
XML aren't present in this tree, they will be ignored.
|
||||
|
||||
If restoreStoredSelection is true, it will also try to re-select any items that
|
||||
were selected in the stored state.
|
||||
|
||||
@see getOpennessState
|
||||
*/
|
||||
void restoreOpennessState (const XmlElement& newState,
|
||||
bool restoreStoredSelection);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the treeview.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1000500, /**< A background colour to fill the component with. */
|
||||
linesColourId = 0x1000501, /**< The colour to draw the lines with.*/
|
||||
dragAndDropIndicatorColourId = 0x1000502, /**< The colour to use for the drag-and-drop target position indicator. */
|
||||
selectedItemBackgroundColourId = 0x1000503 /**< The colour to use to fill the background of any selected items. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
treeview drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void drawTreeviewPlusMinusBox (Graphics&, const Rectangle<float>& area,
|
||||
Colour backgroundColour, bool isItemOpen, bool isMouseOver) = 0;
|
||||
|
||||
virtual bool areLinesDrawnForTreeView (TreeView&) = 0;
|
||||
virtual int getTreeViewIndentSize (TreeView&) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
bool isInterestedInFileDrag (const StringArray& files) override;
|
||||
/** @internal */
|
||||
void fileDragEnter (const StringArray& files, int x, int y) override;
|
||||
/** @internal */
|
||||
void fileDragMove (const StringArray& files, int x, int y) override;
|
||||
/** @internal */
|
||||
void fileDragExit (const StringArray& files) override;
|
||||
/** @internal */
|
||||
void filesDropped (const StringArray& files, int x, int y) override;
|
||||
/** @internal */
|
||||
bool isInterestedInDragSource (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDragEnter (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDragMove (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDragExit (const SourceDetails&) override;
|
||||
/** @internal */
|
||||
void itemDropped (const SourceDetails&) override;
|
||||
|
||||
private:
|
||||
class ContentComponent;
|
||||
class TreeViewport;
|
||||
class InsertPointHighlight;
|
||||
class TargetGroupHighlight;
|
||||
friend class TreeViewItem;
|
||||
friend class ContentComponent;
|
||||
friend struct ContainerDeletePolicy<TreeViewport>;
|
||||
friend struct ContainerDeletePolicy<InsertPointHighlight>;
|
||||
friend struct ContainerDeletePolicy<TargetGroupHighlight>;
|
||||
|
||||
ScopedPointer<TreeViewport> viewport;
|
||||
CriticalSection nodeAlterationLock;
|
||||
TreeViewItem* rootItem;
|
||||
ScopedPointer<InsertPointHighlight> dragInsertPointHighlight;
|
||||
ScopedPointer<TargetGroupHighlight> dragTargetGroupHighlight;
|
||||
int indentSize;
|
||||
bool defaultOpenness, needsRecalculating, rootItemVisible;
|
||||
bool multiSelectEnabled, openCloseButtonsVisible;
|
||||
|
||||
void itemsChanged() noexcept;
|
||||
void recalculateIfNeeded();
|
||||
void updateButtonUnderMouse (const MouseEvent&);
|
||||
struct InsertPoint;
|
||||
void showDragHighlight (const InsertPoint&) noexcept;
|
||||
void hideDragHighlight() noexcept;
|
||||
void handleDrag (const StringArray&, const SourceDetails&);
|
||||
void handleDrop (const StringArray&, const SourceDetails&);
|
||||
bool toggleOpenSelectedItem();
|
||||
void moveOutOfSelectedItem();
|
||||
void moveIntoSelectedItem();
|
||||
void moveByPages (int numPages);
|
||||
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// this method has been deprecated - see the new version..
|
||||
virtual int paintOpenCloseButton (Graphics&, int, int, bool) { return 0; }
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeView)
|
||||
};
|
||||
|
||||
#endif // JUCE_TREEVIEW_H_INCLUDED
|
||||
Loading…
Add table
Add a link
Reference in a new issue