1
0
Fork 0
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:
Felix Faire 2014-10-29 15:55:23 +00:00
parent fefcf7aca6
commit ff6520a89a
1141 changed files with 438491 additions and 94 deletions

View file

@ -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); }

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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; }

View file

@ -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

View file

@ -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();
}
}

View file

@ -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

View file

@ -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

View file

@ -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)
{
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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();
}
}

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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