diff --git a/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp b/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp index 54230b4d8e..74373a719f 100644 --- a/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp +++ b/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp @@ -161,7 +161,7 @@ const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, if (e->getStringAttribute (T("audioDeviceName")).isNotEmpty()) { - setup.inputDeviceName = setup.outputDeviceName + setup.inputDeviceName = setup.outputDeviceName = e->getStringAttribute (T("audioDeviceName")); } else @@ -425,7 +425,7 @@ const String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& ne error = currentAudioDevice->open (inputChannels, outputChannels, - currentSetup.sampleRate, + currentSetup.sampleRate, currentSetup.bufferSize); if (error.isEmpty()) @@ -511,7 +511,7 @@ void AudioDeviceManager::restartLastAudioDevice() { if (currentAudioDevice == 0) { - if (currentSetup.inputDeviceName.isEmpty() + if (currentSetup.inputDeviceName.isEmpty() && currentSetup.outputDeviceName.isEmpty()) { // This method will only reload the last device that was running diff --git a/src/juce_appframework/events/juce_Message.h b/src/juce_appframework/events/juce_Message.h index 89ab18cd35..34d0aede25 100644 --- a/src/juce_appframework/events/juce_Message.h +++ b/src/juce_appframework/events/juce_Message.h @@ -33,7 +33,7 @@ #define __JUCE_MESSAGE_JUCEHEADER__ class MessageListener; - +class MessageManager; //============================================================================== /** The base class for objects that can be delivered to a MessageListener. diff --git a/src/juce_appframework/gui/components/controls/juce_Label.cpp b/src/juce_appframework/gui/components/controls/juce_Label.cpp index e94dec93e3..4d321d6041 100644 --- a/src/juce_appframework/gui/components/controls/juce_Label.cpp +++ b/src/juce_appframework/gui/components/controls/juce_Label.cpp @@ -1,433 +1,433 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_Label.h" -#include "../LookAndFeel/juce_LookAndFeel.h" - - -//============================================================================== -Label::Label (const String& componentName, - const String& labelText) - : Component (componentName), - text (labelText), - font (15.0f), - justification (Justification::centredLeft), - editor (0), - listeners (2), - ownerComponent (0), - deletionWatcher (0), - horizontalBorderSize (3), - verticalBorderSize (1), - minimumHorizontalScale (0.7f), - editSingleClick (false), - editDoubleClick (false), - lossOfFocusDiscardsChanges (false) -{ - setColour (TextEditor::textColourId, Colours::black); - setColour (TextEditor::backgroundColourId, Colours::transparentBlack); - setColour (TextEditor::outlineColourId, Colours::transparentBlack); -} - -Label::~Label() -{ - if (ownerComponent != 0 && ! deletionWatcher->hasBeenDeleted()) - ownerComponent->removeComponentListener (this); - - deleteAndZero (deletionWatcher); - - if (editor != 0) - delete editor; -} - -//============================================================================== -void Label::setText (const String& newText, - const bool broadcastChangeMessage) -{ - hideEditor (true); - - if (text != newText) - { - text = newText; - - if (broadcastChangeMessage) - triggerAsyncUpdate(); - - repaint(); - - if (ownerComponent != 0 && ! deletionWatcher->hasBeenDeleted()) - componentMovedOrResized (*ownerComponent, true, true); - } -} - -const String Label::getText (const bool returnActiveEditorContents) const throw() -{ - return (returnActiveEditorContents && isBeingEdited()) - ? editor->getText() - : text; -} - -void Label::setFont (const Font& newFont) throw() -{ - font = newFont; - repaint(); -} - -const Font& Label::getFont() const throw() -{ - return font; -} - -void Label::setEditable (const bool editOnSingleClick, - const bool editOnDoubleClick, - const bool lossOfFocusDiscardsChanges_) throw() -{ - editSingleClick = editOnSingleClick; - editDoubleClick = editOnDoubleClick; - lossOfFocusDiscardsChanges = lossOfFocusDiscardsChanges_; - - setWantsKeyboardFocus (editOnSingleClick || editOnDoubleClick); - setFocusContainer (editOnSingleClick || editOnDoubleClick); -} - -void Label::setJustificationType (const Justification& justification_) throw() -{ - justification = justification_; - repaint(); -} - -void Label::setBorderSize (int h, int v) -{ - horizontalBorderSize = h; - verticalBorderSize = v; - repaint(); -} - -//============================================================================== -void Label::attachToComponent (Component* owner, - const bool onLeft) -{ - if (ownerComponent != 0 && ! deletionWatcher->hasBeenDeleted()) - ownerComponent->removeComponentListener (this); - - deleteAndZero (deletionWatcher); - ownerComponent = owner; - - leftOfOwnerComp = onLeft; - - if (ownerComponent != 0) - { - deletionWatcher = new ComponentDeletionWatcher (owner); - - setVisible (owner->isVisible()); - ownerComponent->addComponentListener (this); - componentParentHierarchyChanged (*ownerComponent); - componentMovedOrResized (*ownerComponent, true, true); - } -} - -void Label::componentMovedOrResized (Component& component, - bool /*wasMoved*/, - bool /*wasResized*/) -{ - if (leftOfOwnerComp) - { - setSize (jmin (getFont().getStringWidth (text) + 8, component.getX()), - component.getHeight()); - - setTopRightPosition (component.getX(), component.getY()); - } - else - { - setSize (component.getWidth(), - 8 + roundFloatToInt (getFont().getHeight())); - - setTopLeftPosition (component.getX(), component.getY() - getHeight()); - } -} - -void Label::componentParentHierarchyChanged (Component& component) -{ - if (component.getParentComponent() != 0) - component.getParentComponent()->addChildComponent (this); -} - -void Label::componentVisibilityChanged (Component& component) -{ - setVisible (component.isVisible()); -} - -//============================================================================== -void Label::textWasEdited() -{ -} - -void Label::showEditor() -{ - if (editor == 0) - { - addAndMakeVisible (editor = createEditorComponent()); - editor->setText (getText()); - editor->addListener (this); - editor->grabKeyboardFocus(); - editor->setHighlightedRegion (0, text.length()); - editor->addListener (this); - - resized(); - repaint(); - - enterModalState(); - editor->grabKeyboardFocus(); - } -} - -bool Label::updateFromTextEditorContents() -{ - jassert (editor != 0); - const String newText (editor->getText()); - - if (text != newText) - { - text = newText; - - triggerAsyncUpdate(); - repaint(); - - if (ownerComponent != 0 && ! deletionWatcher->hasBeenDeleted()) - componentMovedOrResized (*ownerComponent, true, true); - - return true; - } - - return false; -} - -void Label::hideEditor (const bool discardCurrentEditorContents) -{ - if (editor != 0) - { - const bool changed = (! discardCurrentEditorContents) - && updateFromTextEditorContents(); - - deleteAndZero (editor); - repaint(); - - if (changed) - textWasEdited(); - - exitModalState (0); - } -} - -void Label::inputAttemptWhenModal() -{ - if (editor != 0) - { - if (lossOfFocusDiscardsChanges) - textEditorEscapeKeyPressed (*editor); - else - textEditorReturnKeyPressed (*editor); - } -} - -bool Label::isBeingEdited() const throw() -{ - return editor != 0; -} - -TextEditor* Label::createEditorComponent() -{ - TextEditor* const ed = new TextEditor (getName()); - ed->setFont (font); - - // copy these colours from our own settings.. - const int cols[] = { TextEditor::backgroundColourId, - TextEditor::textColourId, - TextEditor::highlightColourId, - TextEditor::highlightedTextColourId, - TextEditor::caretColourId, - TextEditor::outlineColourId, - TextEditor::focusedOutlineColourId, - TextEditor::shadowColourId }; - - for (int i = 0; i < numElementsInArray (cols); ++i) - ed->setColour (cols[i], findColour (cols[i])); - - return ed; -} - -//============================================================================== -void Label::paint (Graphics& g) -{ - getLookAndFeel().drawLabel (g, *this); -} - -void Label::mouseUp (const MouseEvent& e) -{ - if (editSingleClick - && e.mouseWasClicked() - && contains (e.x, e.y) - && ! e.mods.isPopupMenu()) - { - showEditor(); - } -} - -void Label::mouseDoubleClick (const MouseEvent& e) -{ - if (editDoubleClick && ! e.mods.isPopupMenu()) - showEditor(); -} - -void Label::resized() -{ - if (editor != 0) - editor->setBoundsInset (BorderSize (0)); -} - -void Label::focusGained (FocusChangeType cause) -{ - if (editSingleClick && cause == focusChangedByTabKey) - showEditor(); -} - -void Label::enablementChanged() -{ - repaint(); -} - -void Label::colourChanged() -{ - 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* current) - { - return KeyboardFocusTraverser::getNextComponent (dynamic_cast (current) != 0 - ? current->getParentComponent() : current); - } - - Component* getPreviousComponent (Component* current) - { - return KeyboardFocusTraverser::getPreviousComponent (dynamic_cast (current) != 0 - ? current->getParentComponent() : current); - } -}; - -KeyboardFocusTraverser* Label::createFocusTraverser() -{ - return new LabelKeyboardFocusTraverser(); -} - -//============================================================================== -void Label::addListener (LabelListener* const listener) throw() -{ - jassert (listener != 0); - if (listener != 0) - listeners.add (listener); -} - -void Label::removeListener (LabelListener* const listener) throw() -{ - listeners.removeValue (listener); -} - -void Label::handleAsyncUpdate() -{ - for (int i = listeners.size(); --i >= 0;) - { - ((LabelListener*) listeners.getUnchecked (i))->labelTextChanged (this); - i = jmin (i, listeners.size()); - } -} - -//============================================================================== -void Label::textEditorTextChanged (TextEditor& ed) -{ - if (editor != 0) - { - jassert (&ed == editor); - - if (! (hasKeyboardFocus (true) || isCurrentlyBlockedByAnotherModalComponent())) - { - if (lossOfFocusDiscardsChanges) - textEditorEscapeKeyPressed (ed); - else - textEditorReturnKeyPressed (ed); - } - } -} - -void Label::textEditorReturnKeyPressed (TextEditor& ed) -{ - if (editor != 0) - { - jassert (&ed == editor); - (void) ed; - - const bool changed = updateFromTextEditorContents(); - hideEditor (true); - - if (changed) - textWasEdited(); - } -} - -void Label::textEditorEscapeKeyPressed (TextEditor& ed) -{ - if (editor != 0) - { - jassert (&ed == editor); - (void) ed; - - editor->setText (text, false); - hideEditor (true); - } -} - -void Label::textEditorFocusLost (TextEditor& ed) -{ - textEditorTextChanged (ed); -} - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_Label.h" +#include "../LookAndFeel/juce_LookAndFeel.h" + + +//============================================================================== +Label::Label (const String& componentName, + const String& labelText) + : Component (componentName), + text (labelText), + font (15.0f), + justification (Justification::centredLeft), + editor (0), + listeners (2), + ownerComponent (0), + deletionWatcher (0), + horizontalBorderSize (3), + verticalBorderSize (1), + minimumHorizontalScale (0.7f), + editSingleClick (false), + editDoubleClick (false), + lossOfFocusDiscardsChanges (false) +{ + setColour (TextEditor::textColourId, Colours::black); + setColour (TextEditor::backgroundColourId, Colours::transparentBlack); + setColour (TextEditor::outlineColourId, Colours::transparentBlack); +} + +Label::~Label() +{ + if (ownerComponent != 0 && ! deletionWatcher->hasBeenDeleted()) + ownerComponent->removeComponentListener (this); + + deleteAndZero (deletionWatcher); + + if (editor != 0) + delete editor; +} + +//============================================================================== +void Label::setText (const String& newText, + const bool broadcastChangeMessage) +{ + hideEditor (true); + + if (text != newText) + { + text = newText; + + if (broadcastChangeMessage) + triggerAsyncUpdate(); + + repaint(); + + if (ownerComponent != 0 && ! deletionWatcher->hasBeenDeleted()) + componentMovedOrResized (*ownerComponent, true, true); + } +} + +const String Label::getText (const bool returnActiveEditorContents) const throw() +{ + return (returnActiveEditorContents && isBeingEdited()) + ? editor->getText() + : text; +} + +void Label::setFont (const Font& newFont) throw() +{ + font = newFont; + repaint(); +} + +const Font& Label::getFont() const throw() +{ + return font; +} + +void Label::setEditable (const bool editOnSingleClick, + const bool editOnDoubleClick, + const bool lossOfFocusDiscardsChanges_) throw() +{ + editSingleClick = editOnSingleClick; + editDoubleClick = editOnDoubleClick; + lossOfFocusDiscardsChanges = lossOfFocusDiscardsChanges_; + + setWantsKeyboardFocus (editOnSingleClick || editOnDoubleClick); + setFocusContainer (editOnSingleClick || editOnDoubleClick); +} + +void Label::setJustificationType (const Justification& justification_) throw() +{ + justification = justification_; + repaint(); +} + +void Label::setBorderSize (int h, int v) +{ + horizontalBorderSize = h; + verticalBorderSize = v; + repaint(); +} + +//============================================================================== +void Label::attachToComponent (Component* owner, + const bool onLeft) +{ + if (ownerComponent != 0 && ! deletionWatcher->hasBeenDeleted()) + ownerComponent->removeComponentListener (this); + + deleteAndZero (deletionWatcher); + ownerComponent = owner; + + leftOfOwnerComp = onLeft; + + if (ownerComponent != 0) + { + deletionWatcher = new ComponentDeletionWatcher (owner); + + setVisible (owner->isVisible()); + ownerComponent->addComponentListener (this); + componentParentHierarchyChanged (*ownerComponent); + componentMovedOrResized (*ownerComponent, true, true); + } +} + +void Label::componentMovedOrResized (Component& component, + bool /*wasMoved*/, + bool /*wasResized*/) +{ + if (leftOfOwnerComp) + { + setSize (jmin (getFont().getStringWidth (text) + 8, component.getX()), + component.getHeight()); + + setTopRightPosition (component.getX(), component.getY()); + } + else + { + setSize (component.getWidth(), + 8 + roundFloatToInt (getFont().getHeight())); + + setTopLeftPosition (component.getX(), component.getY() - getHeight()); + } +} + +void Label::componentParentHierarchyChanged (Component& component) +{ + if (component.getParentComponent() != 0) + component.getParentComponent()->addChildComponent (this); +} + +void Label::componentVisibilityChanged (Component& component) +{ + setVisible (component.isVisible()); +} + +//============================================================================== +void Label::textWasEdited() +{ +} + +void Label::showEditor() +{ + if (editor == 0) + { + addAndMakeVisible (editor = createEditorComponent()); + editor->setText (getText()); + editor->addListener (this); + editor->grabKeyboardFocus(); + editor->setHighlightedRegion (0, text.length()); + editor->addListener (this); + + resized(); + repaint(); + + enterModalState(); + editor->grabKeyboardFocus(); + } +} + +bool Label::updateFromTextEditorContents() +{ + jassert (editor != 0); + const String newText (editor->getText()); + + if (text != newText) + { + text = newText; + + triggerAsyncUpdate(); + repaint(); + + if (ownerComponent != 0 && ! deletionWatcher->hasBeenDeleted()) + componentMovedOrResized (*ownerComponent, true, true); + + return true; + } + + return false; +} + +void Label::hideEditor (const bool discardCurrentEditorContents) +{ + if (editor != 0) + { + const bool changed = (! discardCurrentEditorContents) + && updateFromTextEditorContents(); + + deleteAndZero (editor); + repaint(); + + if (changed) + textWasEdited(); + + exitModalState (0); + } +} + +void Label::inputAttemptWhenModal() +{ + if (editor != 0) + { + if (lossOfFocusDiscardsChanges) + textEditorEscapeKeyPressed (*editor); + else + textEditorReturnKeyPressed (*editor); + } +} + +bool Label::isBeingEdited() const throw() +{ + return editor != 0; +} + +TextEditor* Label::createEditorComponent() +{ + TextEditor* const ed = new TextEditor (getName()); + ed->setFont (font); + + // copy these colours from our own settings.. + const int cols[] = { TextEditor::backgroundColourId, + TextEditor::textColourId, + TextEditor::highlightColourId, + TextEditor::highlightedTextColourId, + TextEditor::caretColourId, + TextEditor::outlineColourId, + TextEditor::focusedOutlineColourId, + TextEditor::shadowColourId }; + + for (int i = 0; i < numElementsInArray (cols); ++i) + ed->setColour (cols[i], findColour (cols[i])); + + return ed; +} + +//============================================================================== +void Label::paint (Graphics& g) +{ + getLookAndFeel().drawLabel (g, *this); +} + +void Label::mouseUp (const MouseEvent& e) +{ + if (editSingleClick + && e.mouseWasClicked() + && contains (e.x, e.y) + && ! e.mods.isPopupMenu()) + { + showEditor(); + } +} + +void Label::mouseDoubleClick (const MouseEvent& e) +{ + if (editDoubleClick && ! e.mods.isPopupMenu()) + showEditor(); +} + +void Label::resized() +{ + if (editor != 0) + editor->setBoundsInset (BorderSize (0)); +} + +void Label::focusGained (FocusChangeType cause) +{ + if (editSingleClick && cause == focusChangedByTabKey) + showEditor(); +} + +void Label::enablementChanged() +{ + repaint(); +} + +void Label::colourChanged() +{ + 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* current) + { + return KeyboardFocusTraverser::getNextComponent (dynamic_cast (current) != 0 + ? current->getParentComponent() : current); + } + + Component* getPreviousComponent (Component* current) + { + return KeyboardFocusTraverser::getPreviousComponent (dynamic_cast (current) != 0 + ? current->getParentComponent() : current); + } +}; + +KeyboardFocusTraverser* Label::createFocusTraverser() +{ + return new LabelKeyboardFocusTraverser(); +} + +//============================================================================== +void Label::addListener (LabelListener* const listener) throw() +{ + jassert (listener != 0); + if (listener != 0) + listeners.add (listener); +} + +void Label::removeListener (LabelListener* const listener) throw() +{ + listeners.removeValue (listener); +} + +void Label::handleAsyncUpdate() +{ + for (int i = listeners.size(); --i >= 0;) + { + ((LabelListener*) listeners.getUnchecked (i))->labelTextChanged (this); + i = jmin (i, listeners.size()); + } +} + +//============================================================================== +void Label::textEditorTextChanged (TextEditor& ed) +{ + if (editor != 0) + { + jassert (&ed == editor); + + if (! (hasKeyboardFocus (true) || isCurrentlyBlockedByAnotherModalComponent())) + { + if (lossOfFocusDiscardsChanges) + textEditorEscapeKeyPressed (ed); + else + textEditorReturnKeyPressed (ed); + } + } +} + +void Label::textEditorReturnKeyPressed (TextEditor& ed) +{ + if (editor != 0) + { + jassert (&ed == editor); + (void) ed; + + const bool changed = updateFromTextEditorContents(); + hideEditor (true); + + if (changed) + textWasEdited(); + } +} + +void Label::textEditorEscapeKeyPressed (TextEditor& ed) +{ + if (editor != 0) + { + jassert (&ed == editor); + (void) ed; + + editor->setText (text, false); + hideEditor (true); + } +} + +void Label::textEditorFocusLost (TextEditor& ed) +{ + textEditorTextChanged (ed); +} + + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/controls/juce_Label.h b/src/juce_appframework/gui/components/controls/juce_Label.h index a70e50c118..b6ccd3b791 100644 --- a/src/juce_appframework/gui/components/controls/juce_Label.h +++ b/src/juce_appframework/gui/components/controls/juce_Label.h @@ -154,11 +154,11 @@ public: */ void setBorderSize (int horizontalBorder, int verticalBorder); - /** + /** Returns the size of the horizontal gap being left around the text. */ int getHorizontalBorderSize() const throw() { return horizontalBorderSize; } - /** + /** Returns the size of the vertical gap being left around the text. */ int getVerticalBorderSize() const throw() { return verticalBorderSize; } diff --git a/src/juce_appframework/gui/components/controls/juce_Slider.cpp b/src/juce_appframework/gui/components/controls/juce_Slider.cpp index 33759a34be..4bdffa7faa 100644 --- a/src/juce_appframework/gui/components/controls/juce_Slider.cpp +++ b/src/juce_appframework/gui/components/controls/juce_Slider.cpp @@ -1,1368 +1,1368 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_Slider.h" -#include "../lookandfeel/juce_LookAndFeel.h" -#include "../menus/juce_PopupMenu.h" -#include "../juce_Desktop.h" -#include "../special/juce_BubbleComponent.h" -#include "../../../../juce_core/text/juce_LocalisedStrings.h" - - -//============================================================================== -class SliderPopupDisplayComponent : public BubbleComponent -{ -public: - //============================================================================== - SliderPopupDisplayComponent (Slider* const owner_) - : owner (owner_), - font (15.0f, Font::bold) - { - setAlwaysOnTop (true); - } - - ~SliderPopupDisplayComponent() - { - } - - void paintContent (Graphics& g, int w, int h) - { - g.setFont (font); - g.setColour (Colours::black); - - g.drawFittedText (text, 0, 0, w, h, Justification::centred, 1); - } - - void getContentSize (int& w, int& h) - { - w = font.getStringWidth (text) + 18; - h = (int) (font.getHeight() * 1.6f); - } - - void updatePosition (const String& newText) - { - if (text != newText) - { - text = newText; - repaint(); - } - - BubbleComponent::setPosition (owner); - } - - //============================================================================== - juce_UseDebuggingNewOperator - -private: - Slider* owner; - Font font; - String text; - - SliderPopupDisplayComponent (const SliderPopupDisplayComponent&); - const SliderPopupDisplayComponent& operator= (const SliderPopupDisplayComponent&); -}; - -//============================================================================== -Slider::Slider (const String& name) - : Component (name), - listeners (2), - currentValue (0.0), - valueMin (0.0), - valueMax (0.0), - minimum (0), - maximum (10), - interval (0), - skewFactor (1.0), - velocityModeSensitivity (1.0), - velocityModeOffset (0.0), - velocityModeThreshold (1), - rotaryStart (float_Pi * 1.2f), - rotaryEnd (float_Pi * 2.8f), - numDecimalPlaces (7), - sliderRegionStart (0), - sliderRegionSize (1), - sliderBeingDragged (-1), - pixelsForFullDragExtent (250), - style (LinearHorizontal), - textBoxPos (TextBoxLeft), - textBoxWidth (80), - textBoxHeight (20), - incDecButtonMode (incDecButtonsNotDraggable), - editableText (true), - doubleClickToValue (false), - isVelocityBased (false), - userKeyOverridesVelocity (true), - rotaryStop (true), - incDecButtonsSideBySide (false), - sendChangeOnlyOnRelease (false), - popupDisplayEnabled (false), - menuEnabled (false), - menuShown (false), - scrollWheelEnabled (true), - snapsToMousePos (true), - valueBox (0), - incButton (0), - decButton (0), - popupDisplay (0), - parentForPopupDisplay (0) -{ - setWantsKeyboardFocus (false); - setRepaintsOnMouseActivity (true); - - lookAndFeelChanged(); - updateText(); -} - -Slider::~Slider() -{ - deleteAndZero (popupDisplay); - deleteAllChildren(); -} - - -//============================================================================== -void Slider::handleAsyncUpdate() -{ - cancelPendingUpdate(); - - for (int i = listeners.size(); --i >= 0;) - { - ((SliderListener*) listeners.getUnchecked (i))->sliderValueChanged (this); - i = jmin (i, listeners.size()); - } -} - -void Slider::sendDragStart() -{ - startedDragging(); - - for (int i = listeners.size(); --i >= 0;) - { - ((SliderListener*) listeners.getUnchecked (i))->sliderDragStarted (this); - i = jmin (i, listeners.size()); - } -} - -void Slider::sendDragEnd() -{ - stoppedDragging(); - - sliderBeingDragged = -1; - - for (int i = listeners.size(); --i >= 0;) - { - ((SliderListener*) listeners.getUnchecked (i))->sliderDragEnded (this); - i = jmin (i, listeners.size()); - } -} - -void Slider::addListener (SliderListener* const listener) throw() -{ - jassert (listener != 0); - if (listener != 0) - listeners.add (listener); -} - -void Slider::removeListener (SliderListener* const listener) throw() -{ - listeners.removeValue (listener); -} - -//============================================================================== -void Slider::setSliderStyle (const SliderStyle newStyle) -{ - if (style != newStyle) - { - style = newStyle; - repaint(); - lookAndFeelChanged(); - } -} - -void Slider::setRotaryParameters (const float startAngleRadians, - const float endAngleRadians, - const bool stopAtEnd) -{ - // make sure the values are sensible.. - jassert (rotaryStart >= 0 && rotaryEnd >= 0); - jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f); - jassert (rotaryStart < rotaryEnd); - - rotaryStart = startAngleRadians; - rotaryEnd = endAngleRadians; - rotaryStop = stopAtEnd; -} - -void Slider::setVelocityBasedMode (const bool velBased) throw() -{ - isVelocityBased = velBased; -} - -void Slider::setVelocityModeParameters (const double sensitivity, - const int threshold, - const double offset, - const bool userCanPressKeyToSwapMode) throw() -{ - jassert (threshold >= 0); - jassert (sensitivity > 0); - jassert (offset >= 0); - - velocityModeSensitivity = sensitivity; - velocityModeOffset = offset; - velocityModeThreshold = threshold; - userKeyOverridesVelocity = userCanPressKeyToSwapMode; -} - -void Slider::setSkewFactor (const double factor) throw() -{ - skewFactor = factor; -} - -void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) throw() -{ - if (maximum > minimum) - skewFactor = log (0.5) / log ((sliderValueToShowAtMidPoint - minimum) - / (maximum - minimum)); -} - -void Slider::setMouseDragSensitivity (const int distanceForFullScaleDrag) -{ - jassert (distanceForFullScaleDrag > 0); - - pixelsForFullDragExtent = distanceForFullScaleDrag; -} - -void Slider::setIncDecButtonsMode (const IncDecButtonMode mode) -{ - if (incDecButtonMode != mode) - { - incDecButtonMode = mode; - lookAndFeelChanged(); - } -} - -void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition, - const bool isReadOnly, - const int textEntryBoxWidth, - const int textEntryBoxHeight) -{ - textBoxPos = newPosition; - editableText = ! isReadOnly; - textBoxWidth = textEntryBoxWidth; - textBoxHeight = textEntryBoxHeight; - - repaint(); - lookAndFeelChanged(); -} - -void Slider::setTextBoxIsEditable (const bool shouldBeEditable) throw() -{ - editableText = shouldBeEditable; - - if (valueBox != 0) - valueBox->setEditable (shouldBeEditable && isEnabled()); -} - -void Slider::showTextBox() -{ - jassert (editableText); // this should probably be avoided in read-only sliders. - - if (valueBox != 0) - valueBox->showEditor(); -} - -void Slider::hideTextBox (const bool discardCurrentEditorContents) -{ - if (valueBox != 0) - { - valueBox->hideEditor (discardCurrentEditorContents); - - if (discardCurrentEditorContents) - updateText(); - } -} - -void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw() -{ - sendChangeOnlyOnRelease = onlyNotifyOnRelease; -} - -void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse) throw() -{ - snapsToMousePos = shouldSnapToMouse; -} - -void Slider::setPopupDisplayEnabled (const bool enabled, - Component* const parentComponentToUse) throw() -{ - popupDisplayEnabled = enabled; - parentForPopupDisplay = parentComponentToUse; -} - -//============================================================================== -void Slider::colourChanged() -{ - lookAndFeelChanged(); -} - -void Slider::lookAndFeelChanged() -{ - const String previousTextBoxContent (valueBox != 0 ? valueBox->getText() - : getTextFromValue (currentValue)); - - deleteAllChildren(); - valueBox = 0; - - LookAndFeel& lf = getLookAndFeel(); - - if (textBoxPos != NoTextBox) - { - addAndMakeVisible (valueBox = getLookAndFeel().createSliderTextBox (*this)); - - valueBox->setWantsKeyboardFocus (false); - valueBox->setText (previousTextBoxContent, false); - - valueBox->setEditable (editableText && isEnabled()); - valueBox->addListener (this); - - if (style == LinearBar) - valueBox->addMouseListener (this, false); - } - - if (style == IncDecButtons) - { - addAndMakeVisible (incButton = lf.createSliderButton (true)); - incButton->addButtonListener (this); - - addAndMakeVisible (decButton = lf.createSliderButton (false)); - decButton->addButtonListener (this); - - if (incDecButtonMode != incDecButtonsNotDraggable) - { - incButton->addMouseListener (this, false); - decButton->addMouseListener (this, false); - } - else - { - incButton->setRepeatSpeed (300, 100, 20); - incButton->addMouseListener (decButton, false); - - decButton->setRepeatSpeed (300, 100, 20); - decButton->addMouseListener (incButton, false); - } - } - - setComponentEffect (lf.getSliderEffect()); - - resized(); - repaint(); -} - -//============================================================================== -void Slider::setRange (const double newMin, - const double newMax, - const double newInt) -{ - if (minimum != newMin - || maximum != newMax - || interval != newInt) - { - minimum = newMin; - maximum = newMax; - interval = newInt; - - // figure out the number of DPs needed to display all values at this - // interval setting. - numDecimalPlaces = 7; - - if (newInt != 0) - { - int v = abs ((int) (newInt * 10000000)); - - while ((v % 10) == 0) - { - --numDecimalPlaces; - v /= 10; - } - } - - // keep the current values inside the new range.. - if (style != TwoValueHorizontal && style != TwoValueVertical) - { - setValue (currentValue, false, false); - } - else - { - setMinValue (getMinValue(), false, false); - setMaxValue (getMaxValue(), false, false); - } - - updateText(); - } -} - -void Slider::triggerChangeMessage (const bool synchronous) -{ - if (synchronous) - handleAsyncUpdate(); - else - triggerAsyncUpdate(); - - valueChanged(); -} - -double Slider::getValue() const throw() -{ - // for a two-value style slider, you should use the getMinValue() and getMaxValue() - // methods to get the two values. - jassert (style != TwoValueHorizontal && style != TwoValueVertical); - - return currentValue; -} - -void Slider::setValue (double newValue, - const bool sendUpdateMessage, - const bool sendMessageSynchronously) -{ - // for a two-value style slider, you should use the setMinValue() and setMaxValue() - // methods to set the two values. - jassert (style != TwoValueHorizontal && style != TwoValueVertical); - - newValue = constrainedValue (newValue); - - if (style == ThreeValueHorizontal || style == ThreeValueVertical) - { - jassert (valueMin <= valueMax); - newValue = jlimit (valueMin, valueMax, newValue); - } - - if (currentValue != newValue) - { - if (valueBox != 0) - valueBox->hideEditor (true); - - currentValue = newValue; - updateText(); - repaint(); - - if (popupDisplay != 0) - { - ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (currentValue)); - popupDisplay->repaint(); - } - - if (sendUpdateMessage) - triggerChangeMessage (sendMessageSynchronously); - } -} - -double Slider::getMinValue() const throw() -{ - // The minimum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - return valueMin; -} - -double Slider::getMaxValue() const throw() -{ - // The maximum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - return valueMax; -} - -void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) -{ - // The minimum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - newValue = constrainedValue (newValue); - - if (style == TwoValueHorizontal || style == TwoValueVertical) - newValue = jmin (valueMax, newValue); - else - newValue = jmin (currentValue, newValue); - - if (valueMin != newValue) - { - valueMin = newValue; - repaint(); - - if (popupDisplay != 0) - { - ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMin)); - popupDisplay->repaint(); - } - - if (sendUpdateMessage) - triggerChangeMessage (sendMessageSynchronously); - } -} - -void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) -{ - // The maximum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - newValue = constrainedValue (newValue); - - if (style == TwoValueHorizontal || style == TwoValueVertical) - newValue = jmax (valueMin, newValue); - else - newValue = jmax (currentValue, newValue); - - if (valueMax != newValue) - { - valueMax = newValue; - repaint(); - - if (popupDisplay != 0) - { - ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMax)); - popupDisplay->repaint(); - } - - if (sendUpdateMessage) - triggerChangeMessage (sendMessageSynchronously); - } -} - -void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled, - const double valueToSetOnDoubleClick) throw() -{ - doubleClickToValue = isDoubleClickEnabled; - doubleClickReturnValue = valueToSetOnDoubleClick; -} - -double Slider::getDoubleClickReturnValue (bool& isEnabled_) const throw() -{ - isEnabled_ = doubleClickToValue; - return doubleClickReturnValue; -} - -void Slider::updateText() -{ - if (valueBox != 0) - valueBox->setText (getTextFromValue (currentValue), false); -} - -void Slider::setTextValueSuffix (const String& suffix) -{ - if (textSuffix != suffix) - { - textSuffix = suffix; - updateText(); - } -} - -const String Slider::getTextFromValue (double v) -{ - if (numDecimalPlaces > 0) - return String (v, numDecimalPlaces) + textSuffix; - else - return String (roundDoubleToInt (v)) + textSuffix; -} - -double Slider::getValueFromText (const String& text) -{ - String t (text.trimStart()); - - if (t.endsWith (textSuffix)) - t = t.substring (0, t.length() - textSuffix.length()); - - while (t.startsWithChar (T('+'))) - t = t.substring (1).trimStart(); - - return t.initialSectionContainingOnly (T("0123456789.,-")) - .getDoubleValue(); -} - -double Slider::proportionOfLengthToValue (double proportion) -{ - if (skewFactor != 1.0 && proportion > 0.0) - proportion = exp (log (proportion) / skewFactor); - - return minimum + (maximum - minimum) * proportion; -} - -double Slider::valueToProportionOfLength (double value) -{ - const double n = (value - minimum) / (maximum - minimum); - - return skewFactor == 1.0 ? n : pow (n, skewFactor); -} - -double Slider::snapValue (double attemptedValue, const bool) -{ - return attemptedValue; -} - -//============================================================================== -void Slider::startedDragging() -{ -} - -void Slider::stoppedDragging() -{ -} - -void Slider::valueChanged() -{ -} - -//============================================================================== -void Slider::enablementChanged() -{ - repaint(); -} - -void Slider::setPopupMenuEnabled (const bool menuEnabled_) throw() -{ - menuEnabled = menuEnabled_; -} - -void Slider::setScrollWheelEnabled (const bool enabled) throw() -{ - scrollWheelEnabled = enabled; -} - -//============================================================================== -void Slider::labelTextChanged (Label* label) -{ - const double newValue = snapValue (getValueFromText (label->getText()), false); - - if (getValue() != newValue) - { - sendDragStart(); - setValue (newValue, true, true); - sendDragEnd(); - } - - updateText(); // force a clean-up of the text, needed in case setValue() hasn't done this. -} - -void Slider::buttonClicked (Button* button) -{ - if (style == IncDecButtons) - { - sendDragStart(); - - if (button == incButton) - setValue (snapValue (getValue() + interval, false), true, true); - else if (button == decButton) - setValue (snapValue (getValue() - interval, false), true, true); - - sendDragEnd(); - } -} - -//============================================================================== -double Slider::constrainedValue (double value) const throw() -{ - if (interval > 0) - value = minimum + interval * floor ((value - minimum) / interval + 0.5); - - if (value <= minimum || maximum <= minimum) - value = minimum; - else if (value >= maximum) - value = maximum; - - return value; -} - -float Slider::getLinearSliderPos (const double value) -{ - double sliderPosProportional; - - if (maximum > minimum) - { - if (value < minimum) - { - sliderPosProportional = 0.0; - } - else if (value > maximum) - { - sliderPosProportional = 1.0; - } - else - { - sliderPosProportional = valueToProportionOfLength (value); - jassert (sliderPosProportional >= 0 && sliderPosProportional <= 1.0); - } - } - else - { - sliderPosProportional = 0.5; - } - - if (isVertical() || style == IncDecButtons) - sliderPosProportional = 1.0 - sliderPosProportional; - - return (float) (sliderRegionStart + sliderPosProportional * sliderRegionSize); -} - -bool Slider::isHorizontal() const throw() -{ - return style == LinearHorizontal - || style == LinearBar - || style == TwoValueHorizontal - || style == ThreeValueHorizontal; -} - -bool Slider::isVertical() const throw() -{ - return style == LinearVertical - || style == TwoValueVertical - || style == ThreeValueVertical; -} - -bool Slider::incDecDragDirectionIsHorizontal() const throw() -{ - return incDecButtonMode == incDecButtonsDraggable_Horizontal - || (incDecButtonMode == incDecButtonsDraggable_AutoDirection && incDecButtonsSideBySide); -} - -float Slider::getPositionOfValue (const double value) -{ - if (isHorizontal() || isVertical()) - { - return getLinearSliderPos (value); - } - else - { - jassertfalse // not a valid call on a slider that doesn't work linearly! - return 0.0f; - } -} - -//============================================================================== -void Slider::paint (Graphics& g) -{ - if (style != IncDecButtons) - { - if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) - { - const float sliderPos = (float) valueToProportionOfLength (currentValue); - jassert (sliderPos >= 0 && sliderPos <= 1.0f); - - getLookAndFeel().drawRotarySlider (g, - sliderRect.getX(), - sliderRect.getY(), - sliderRect.getWidth(), - sliderRect.getHeight(), - sliderPos, - rotaryStart, rotaryEnd, - *this); - } - else - { - getLookAndFeel().drawLinearSlider (g, - sliderRect.getX(), - sliderRect.getY(), - sliderRect.getWidth(), - sliderRect.getHeight(), - getLinearSliderPos (currentValue), - getLinearSliderPos (valueMin), - getLinearSliderPos (valueMax), - style, - *this); - } - - if (style == LinearBar && valueBox == 0) - { - g.setColour (findColour (Slider::textBoxOutlineColourId)); - g.drawRect (0, 0, getWidth(), getHeight(), 1); - } - } -} - -void Slider::resized() -{ - int minXSpace = 0; - int minYSpace = 0; - - if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) - minXSpace = 30; - else - minYSpace = 15; - - const int tbw = jmax (0, jmin (textBoxWidth, getWidth() - minXSpace)); - const int tbh = jmax (0, jmin (textBoxHeight, getHeight() - minYSpace)); - - if (style == LinearBar) - { - if (valueBox != 0) - valueBox->setBounds (0, 0, getWidth(), getHeight()); - } - else - { - if (textBoxPos == NoTextBox) - { - sliderRect.setBounds (0, 0, getWidth(), getHeight()); - } - else if (textBoxPos == TextBoxLeft) - { - valueBox->setBounds (0, (getHeight() - tbh) / 2, tbw, tbh); - sliderRect.setBounds (tbw, 0, getWidth() - tbw, getHeight()); - } - else if (textBoxPos == TextBoxRight) - { - valueBox->setBounds (getWidth() - tbw, (getHeight() - tbh) / 2, tbw, tbh); - sliderRect.setBounds (0, 0, getWidth() - tbw, getHeight()); - } - else if (textBoxPos == TextBoxAbove) - { - valueBox->setBounds ((getWidth() - tbw) / 2, 0, tbw, tbh); - sliderRect.setBounds (0, tbh, getWidth(), getHeight() - tbh); - } - else if (textBoxPos == TextBoxBelow) - { - valueBox->setBounds ((getWidth() - tbw) / 2, getHeight() - tbh, tbw, tbh); - sliderRect.setBounds (0, 0, getWidth(), getHeight() - tbh); - } - } - - const int indent = getLookAndFeel().getSliderThumbRadius (*this); - - if (style == LinearBar) - { - const int barIndent = 1; - sliderRegionStart = barIndent; - sliderRegionSize = getWidth() - barIndent * 2; - - sliderRect.setBounds (sliderRegionStart, barIndent, - sliderRegionSize, getHeight() - barIndent * 2); - } - else if (isHorizontal()) - { - sliderRegionStart = sliderRect.getX() + indent; - sliderRegionSize = jmax (1, sliderRect.getWidth() - indent * 2); - - sliderRect.setBounds (sliderRegionStart, sliderRect.getY(), - sliderRegionSize, sliderRect.getHeight()); - } - else if (isVertical()) - { - sliderRegionStart = sliderRect.getY() + indent; - sliderRegionSize = jmax (1, sliderRect.getHeight() - indent * 2); - - sliderRect.setBounds (sliderRect.getX(), sliderRegionStart, - sliderRect.getWidth(), sliderRegionSize); - } - else - { - sliderRegionStart = 0; - sliderRegionSize = 100; - } - - if (style == IncDecButtons) - { - Rectangle buttonRect (sliderRect); - - if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) - buttonRect.expand (-2, 0); - else - buttonRect.expand (0, -2); - - incDecButtonsSideBySide = buttonRect.getWidth() > buttonRect.getHeight(); - - if (incDecButtonsSideBySide) - { - decButton->setBounds (buttonRect.getX(), - buttonRect.getY(), - buttonRect.getWidth() / 2, - buttonRect.getHeight()); - - decButton->setConnectedEdges (Button::ConnectedOnRight); - - incButton->setBounds (buttonRect.getCentreX(), - buttonRect.getY(), - buttonRect.getWidth() / 2, - buttonRect.getHeight()); - - incButton->setConnectedEdges (Button::ConnectedOnLeft); - } - else - { - incButton->setBounds (buttonRect.getX(), - buttonRect.getY(), - buttonRect.getWidth(), - buttonRect.getHeight() / 2); - - incButton->setConnectedEdges (Button::ConnectedOnBottom); - - decButton->setBounds (buttonRect.getX(), - buttonRect.getCentreY(), - buttonRect.getWidth(), - buttonRect.getHeight() / 2); - - decButton->setConnectedEdges (Button::ConnectedOnTop); - } - } -} - -void Slider::focusOfChildComponentChanged (FocusChangeType) -{ - repaint(); -} - -void Slider::mouseDown (const MouseEvent& e) -{ - mouseWasHidden = false; - incDecDragged = false; - - if (isEnabled()) - { - if (e.mods.isPopupMenu() && menuEnabled) - { - menuShown = true; - - PopupMenu m; - m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased); - m.addSeparator(); - - if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) - { - PopupMenu rotaryMenu; - rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary); - rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag); - rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag); - - m.addSubMenu (TRANS ("rotary mode"), rotaryMenu); - } - - const int r = m.show(); - - if (r == 1) - { - setVelocityBasedMode (! isVelocityBased); - } - else if (r == 2) - { - setSliderStyle (Rotary); - } - else if (r == 3) - { - setSliderStyle (RotaryHorizontalDrag); - } - else if (r == 4) - { - setSliderStyle (RotaryVerticalDrag); - } - } - else if (maximum > minimum) - { - menuShown = false; - - if (valueBox != 0) - valueBox->hideEditor (true); - - sliderBeingDragged = 0; - - if (style == TwoValueHorizontal - || style == TwoValueVertical - || style == ThreeValueHorizontal - || style == ThreeValueVertical) - { - const float mousePos = (float) (isVertical() ? e.y : e.x); - - const float normalPosDistance = fabsf (getLinearSliderPos (currentValue) - mousePos); - const float minPosDistance = fabsf (getLinearSliderPos (valueMin) - 0.1f - mousePos); - const float maxPosDistance = fabsf (getLinearSliderPos (valueMax) + 0.1f - mousePos); - - if (style == TwoValueHorizontal || style == TwoValueVertical) - { - if (maxPosDistance <= minPosDistance) - sliderBeingDragged = 2; - else - sliderBeingDragged = 1; - } - else if (style == ThreeValueHorizontal || style == ThreeValueVertical) - { - if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance) - sliderBeingDragged = 1; - else if (normalPosDistance >= maxPosDistance) - sliderBeingDragged = 2; - } - } - - minMaxDiff = valueMax - valueMin; - - mouseXWhenLastDragged = e.x; - mouseYWhenLastDragged = e.y; - lastAngle = rotaryStart + (rotaryEnd - rotaryStart) - * valueToProportionOfLength (currentValue); - - if (sliderBeingDragged == 2) - valueWhenLastDragged = valueMax; - else if (sliderBeingDragged == 1) - valueWhenLastDragged = valueMin; - else - valueWhenLastDragged = currentValue; - - valueOnMouseDown = valueWhenLastDragged; - - if (popupDisplayEnabled) - { - SliderPopupDisplayComponent* const popup = new SliderPopupDisplayComponent (this); - popupDisplay = popup; - - if (parentForPopupDisplay != 0) - { - parentForPopupDisplay->addChildComponent (popup); - } - else - { - popup->addToDesktop (0); - } - - popup->setVisible (true); - } - - sendDragStart(); - - mouseDrag (e); - } - } -} - -void Slider::mouseUp (const MouseEvent&) -{ - if (isEnabled() - && (! menuShown) - && (maximum > minimum) - && (style != IncDecButtons || incDecDragged)) - { - restoreMouseIfHidden(); - - if (sendChangeOnlyOnRelease && valueOnMouseDown != currentValue) - triggerChangeMessage (false); - - sendDragEnd(); - - deleteAndZero (popupDisplay); - - if (style == IncDecButtons) - { - incButton->setState (Button::buttonNormal); - decButton->setState (Button::buttonNormal); - } - } -} - -void Slider::restoreMouseIfHidden() -{ - if (mouseWasHidden) - { - mouseWasHidden = false; - - Component* c = Component::getComponentUnderMouse(); - - if (c == 0) - c = this; - - c->enableUnboundedMouseMovement (false); - - const double pos = (sliderBeingDragged == 2) ? getMaxValue() - : ((sliderBeingDragged == 1) ? getMinValue() - : currentValue); - - const int pixelPos = (int) getLinearSliderPos (pos); - - int x = isHorizontal() ? pixelPos : (getWidth() / 2); - int y = isVertical() ? pixelPos : (getHeight() / 2); - - relativePositionToGlobal (x, y); - Desktop::setMousePosition (x, y); - } -} - -void Slider::modifierKeysChanged (const ModifierKeys& modifiers) -{ - if (isEnabled() - && style != IncDecButtons - && style != Rotary - && isVelocityBased == modifiers.isAnyModifierKeyDown()) - { - restoreMouseIfHidden(); - } -} - -static double smallestAngleBetween (double a1, double a2) -{ - return jmin (fabs (a1 - a2), - fabs (a1 + double_Pi * 2.0 - a2), - fabs (a2 + double_Pi * 2.0 - a1)); -} - -void Slider::mouseDrag (const MouseEvent& e) -{ - if (isEnabled() - && (! menuShown) - && (maximum > minimum)) - { - if (style == Rotary) - { - int dx = e.x - sliderRect.getCentreX(); - int dy = e.y - sliderRect.getCentreY(); - - if (dx * dx + dy * dy > 25) - { - double angle = atan2 ((double) dx, (double) -dy); - while (angle < 0.0) - angle += double_Pi * 2.0; - - if (rotaryStop && ! e.mouseWasClicked()) - { - if (fabs (angle - lastAngle) > double_Pi) - { - if (angle >= lastAngle) - angle -= double_Pi * 2.0; - else - angle += double_Pi * 2.0; - } - - if (angle >= lastAngle) - angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd)); - else - angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd)); - } - else - { - while (angle < rotaryStart) - angle += double_Pi * 2.0; - - if (angle > rotaryEnd) - { - if (smallestAngleBetween (angle, rotaryStart) <= smallestAngleBetween (angle, rotaryEnd)) - angle = rotaryStart; - else - angle = rotaryEnd; - } - } - - const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart); - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion)); - - lastAngle = angle; - } - } - else - { - if (style == LinearBar && e.mouseWasClicked() - && valueBox != 0 && valueBox->isEditable()) - return; - - if (style == IncDecButtons) - { - if (! incDecDragged) - incDecDragged = e.getDistanceFromDragStart() > 10 && ! e.mouseWasClicked(); - - if (! incDecDragged) - return; - } - - - if ((isVelocityBased == (userKeyOverridesVelocity ? e.mods.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier | ModifierKeys::altModifier) - : false)) - || ((maximum - minimum) / sliderRegionSize < interval)) - { - const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y; - - double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize; - - if (style == RotaryHorizontalDrag - || style == RotaryVerticalDrag - || style == IncDecButtons - || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar) - && ! snapsToMousePos)) - { - const int mouseDiff = (style == RotaryHorizontalDrag - || style == LinearHorizontal - || style == LinearBar - || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) - ? e.getDistanceFromDragStartX() - : -e.getDistanceFromDragStartY(); - - double newPos = valueToProportionOfLength (valueOnMouseDown) - + mouseDiff * (1.0 / pixelsForFullDragExtent); - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos)); - - if (style == IncDecButtons) - { - incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown); - decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown); - } - } - else - { - if (isVertical()) - scaledMousePos = 1.0 - scaledMousePos; - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos)); - } - } - else - { - const int mouseDiff = (isHorizontal() || style == RotaryHorizontalDrag - || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) - ? e.x - mouseXWhenLastDragged - : e.y - mouseYWhenLastDragged; - - const double maxSpeed = jmax (200, sliderRegionSize); - double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff)); - - if (speed != 0) - { - speed = 0.2 * velocityModeSensitivity - * (1.0 + sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset - + jmax (0.0, (double) (speed - velocityModeThreshold)) - / maxSpeed)))); - - if (mouseDiff < 0) - speed = -speed; - - if (isVertical() || style == RotaryVerticalDrag - || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal())) - speed = -speed; - - const double currentPos = valueToProportionOfLength (valueWhenLastDragged); - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed)); - - e.originalComponent->enableUnboundedMouseMovement (true, false); - mouseWasHidden = true; - } - } - } - - valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged); - - if (sliderBeingDragged == 0) - { - setValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, true); - } - else if (sliderBeingDragged == 1) - { - setMinValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, false); - - if (e.mods.isShiftDown()) - setMaxValue (getMinValue() + minMaxDiff, false); - else - minMaxDiff = valueMax - valueMin; - } - else - { - jassert (sliderBeingDragged == 2); - - setMaxValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, false); - - if (e.mods.isShiftDown()) - setMinValue (getMaxValue() - minMaxDiff, false); - else - minMaxDiff = valueMax - valueMin; - } - - mouseXWhenLastDragged = e.x; - mouseYWhenLastDragged = e.y; - } -} - -void Slider::mouseDoubleClick (const MouseEvent&) -{ - if (doubleClickToValue - && isEnabled() - && style != IncDecButtons - && minimum <= doubleClickReturnValue - && maximum >= doubleClickReturnValue) - { - sendDragStart(); - setValue (doubleClickReturnValue, true, true); - sendDragEnd(); - } -} - -void Slider::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) -{ - if (scrollWheelEnabled && isEnabled() - && style != TwoValueHorizontal - && style != TwoValueVertical) - { - if (maximum > minimum && ! isMouseButtonDownAnywhere()) - { - if (valueBox != 0) - valueBox->hideEditor (false); - - const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f; - const double currentPos = valueToProportionOfLength (currentValue); - const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta)); - - double delta = (newValue != currentValue) - ? jmax (fabs (newValue - currentValue), interval) : 0; - - if (currentValue > newValue) - delta = -delta; - - sendDragStart(); - setValue (snapValue (currentValue + delta, false), true, true); - sendDragEnd(); - } - } - else - { - Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); - } -} - -void SliderListener::sliderDragStarted (Slider*) -{ -} - -void SliderListener::sliderDragEnded (Slider*) -{ -} - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_Slider.h" +#include "../lookandfeel/juce_LookAndFeel.h" +#include "../menus/juce_PopupMenu.h" +#include "../juce_Desktop.h" +#include "../special/juce_BubbleComponent.h" +#include "../../../../juce_core/text/juce_LocalisedStrings.h" + + +//============================================================================== +class SliderPopupDisplayComponent : public BubbleComponent +{ +public: + //============================================================================== + SliderPopupDisplayComponent (Slider* const owner_) + : owner (owner_), + font (15.0f, Font::bold) + { + setAlwaysOnTop (true); + } + + ~SliderPopupDisplayComponent() + { + } + + void paintContent (Graphics& g, int w, int h) + { + g.setFont (font); + g.setColour (Colours::black); + + g.drawFittedText (text, 0, 0, w, h, Justification::centred, 1); + } + + void getContentSize (int& w, int& h) + { + w = font.getStringWidth (text) + 18; + h = (int) (font.getHeight() * 1.6f); + } + + void updatePosition (const String& newText) + { + if (text != newText) + { + text = newText; + repaint(); + } + + BubbleComponent::setPosition (owner); + } + + //============================================================================== + juce_UseDebuggingNewOperator + +private: + Slider* owner; + Font font; + String text; + + SliderPopupDisplayComponent (const SliderPopupDisplayComponent&); + const SliderPopupDisplayComponent& operator= (const SliderPopupDisplayComponent&); +}; + +//============================================================================== +Slider::Slider (const String& name) + : Component (name), + listeners (2), + currentValue (0.0), + valueMin (0.0), + valueMax (0.0), + minimum (0), + maximum (10), + interval (0), + skewFactor (1.0), + velocityModeSensitivity (1.0), + velocityModeOffset (0.0), + velocityModeThreshold (1), + rotaryStart (float_Pi * 1.2f), + rotaryEnd (float_Pi * 2.8f), + numDecimalPlaces (7), + sliderRegionStart (0), + sliderRegionSize (1), + sliderBeingDragged (-1), + pixelsForFullDragExtent (250), + style (LinearHorizontal), + textBoxPos (TextBoxLeft), + textBoxWidth (80), + textBoxHeight (20), + incDecButtonMode (incDecButtonsNotDraggable), + editableText (true), + doubleClickToValue (false), + isVelocityBased (false), + userKeyOverridesVelocity (true), + rotaryStop (true), + incDecButtonsSideBySide (false), + sendChangeOnlyOnRelease (false), + popupDisplayEnabled (false), + menuEnabled (false), + menuShown (false), + scrollWheelEnabled (true), + snapsToMousePos (true), + valueBox (0), + incButton (0), + decButton (0), + popupDisplay (0), + parentForPopupDisplay (0) +{ + setWantsKeyboardFocus (false); + setRepaintsOnMouseActivity (true); + + lookAndFeelChanged(); + updateText(); +} + +Slider::~Slider() +{ + deleteAndZero (popupDisplay); + deleteAllChildren(); +} + + +//============================================================================== +void Slider::handleAsyncUpdate() +{ + cancelPendingUpdate(); + + for (int i = listeners.size(); --i >= 0;) + { + ((SliderListener*) listeners.getUnchecked (i))->sliderValueChanged (this); + i = jmin (i, listeners.size()); + } +} + +void Slider::sendDragStart() +{ + startedDragging(); + + for (int i = listeners.size(); --i >= 0;) + { + ((SliderListener*) listeners.getUnchecked (i))->sliderDragStarted (this); + i = jmin (i, listeners.size()); + } +} + +void Slider::sendDragEnd() +{ + stoppedDragging(); + + sliderBeingDragged = -1; + + for (int i = listeners.size(); --i >= 0;) + { + ((SliderListener*) listeners.getUnchecked (i))->sliderDragEnded (this); + i = jmin (i, listeners.size()); + } +} + +void Slider::addListener (SliderListener* const listener) throw() +{ + jassert (listener != 0); + if (listener != 0) + listeners.add (listener); +} + +void Slider::removeListener (SliderListener* const listener) throw() +{ + listeners.removeValue (listener); +} + +//============================================================================== +void Slider::setSliderStyle (const SliderStyle newStyle) +{ + if (style != newStyle) + { + style = newStyle; + repaint(); + lookAndFeelChanged(); + } +} + +void Slider::setRotaryParameters (const float startAngleRadians, + const float endAngleRadians, + const bool stopAtEnd) +{ + // make sure the values are sensible.. + jassert (rotaryStart >= 0 && rotaryEnd >= 0); + jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f); + jassert (rotaryStart < rotaryEnd); + + rotaryStart = startAngleRadians; + rotaryEnd = endAngleRadians; + rotaryStop = stopAtEnd; +} + +void Slider::setVelocityBasedMode (const bool velBased) throw() +{ + isVelocityBased = velBased; +} + +void Slider::setVelocityModeParameters (const double sensitivity, + const int threshold, + const double offset, + const bool userCanPressKeyToSwapMode) throw() +{ + jassert (threshold >= 0); + jassert (sensitivity > 0); + jassert (offset >= 0); + + velocityModeSensitivity = sensitivity; + velocityModeOffset = offset; + velocityModeThreshold = threshold; + userKeyOverridesVelocity = userCanPressKeyToSwapMode; +} + +void Slider::setSkewFactor (const double factor) throw() +{ + skewFactor = factor; +} + +void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) throw() +{ + if (maximum > minimum) + skewFactor = log (0.5) / log ((sliderValueToShowAtMidPoint - minimum) + / (maximum - minimum)); +} + +void Slider::setMouseDragSensitivity (const int distanceForFullScaleDrag) +{ + jassert (distanceForFullScaleDrag > 0); + + pixelsForFullDragExtent = distanceForFullScaleDrag; +} + +void Slider::setIncDecButtonsMode (const IncDecButtonMode mode) +{ + if (incDecButtonMode != mode) + { + incDecButtonMode = mode; + lookAndFeelChanged(); + } +} + +void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition, + const bool isReadOnly, + const int textEntryBoxWidth, + const int textEntryBoxHeight) +{ + textBoxPos = newPosition; + editableText = ! isReadOnly; + textBoxWidth = textEntryBoxWidth; + textBoxHeight = textEntryBoxHeight; + + repaint(); + lookAndFeelChanged(); +} + +void Slider::setTextBoxIsEditable (const bool shouldBeEditable) throw() +{ + editableText = shouldBeEditable; + + if (valueBox != 0) + valueBox->setEditable (shouldBeEditable && isEnabled()); +} + +void Slider::showTextBox() +{ + jassert (editableText); // this should probably be avoided in read-only sliders. + + if (valueBox != 0) + valueBox->showEditor(); +} + +void Slider::hideTextBox (const bool discardCurrentEditorContents) +{ + if (valueBox != 0) + { + valueBox->hideEditor (discardCurrentEditorContents); + + if (discardCurrentEditorContents) + updateText(); + } +} + +void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw() +{ + sendChangeOnlyOnRelease = onlyNotifyOnRelease; +} + +void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse) throw() +{ + snapsToMousePos = shouldSnapToMouse; +} + +void Slider::setPopupDisplayEnabled (const bool enabled, + Component* const parentComponentToUse) throw() +{ + popupDisplayEnabled = enabled; + parentForPopupDisplay = parentComponentToUse; +} + +//============================================================================== +void Slider::colourChanged() +{ + lookAndFeelChanged(); +} + +void Slider::lookAndFeelChanged() +{ + const String previousTextBoxContent (valueBox != 0 ? valueBox->getText() + : getTextFromValue (currentValue)); + + deleteAllChildren(); + valueBox = 0; + + LookAndFeel& lf = getLookAndFeel(); + + if (textBoxPos != NoTextBox) + { + addAndMakeVisible (valueBox = getLookAndFeel().createSliderTextBox (*this)); + + valueBox->setWantsKeyboardFocus (false); + valueBox->setText (previousTextBoxContent, false); + + valueBox->setEditable (editableText && isEnabled()); + valueBox->addListener (this); + + if (style == LinearBar) + valueBox->addMouseListener (this, false); + } + + if (style == IncDecButtons) + { + addAndMakeVisible (incButton = lf.createSliderButton (true)); + incButton->addButtonListener (this); + + addAndMakeVisible (decButton = lf.createSliderButton (false)); + decButton->addButtonListener (this); + + if (incDecButtonMode != incDecButtonsNotDraggable) + { + incButton->addMouseListener (this, false); + decButton->addMouseListener (this, false); + } + else + { + incButton->setRepeatSpeed (300, 100, 20); + incButton->addMouseListener (decButton, false); + + decButton->setRepeatSpeed (300, 100, 20); + decButton->addMouseListener (incButton, false); + } + } + + setComponentEffect (lf.getSliderEffect()); + + resized(); + repaint(); +} + +//============================================================================== +void Slider::setRange (const double newMin, + const double newMax, + const double newInt) +{ + if (minimum != newMin + || maximum != newMax + || interval != newInt) + { + minimum = newMin; + maximum = newMax; + interval = newInt; + + // figure out the number of DPs needed to display all values at this + // interval setting. + numDecimalPlaces = 7; + + if (newInt != 0) + { + int v = abs ((int) (newInt * 10000000)); + + while ((v % 10) == 0) + { + --numDecimalPlaces; + v /= 10; + } + } + + // keep the current values inside the new range.. + if (style != TwoValueHorizontal && style != TwoValueVertical) + { + setValue (currentValue, false, false); + } + else + { + setMinValue (getMinValue(), false, false); + setMaxValue (getMaxValue(), false, false); + } + + updateText(); + } +} + +void Slider::triggerChangeMessage (const bool synchronous) +{ + if (synchronous) + handleAsyncUpdate(); + else + triggerAsyncUpdate(); + + valueChanged(); +} + +double Slider::getValue() const throw() +{ + // for a two-value style slider, you should use the getMinValue() and getMaxValue() + // methods to get the two values. + jassert (style != TwoValueHorizontal && style != TwoValueVertical); + + return currentValue; +} + +void Slider::setValue (double newValue, + const bool sendUpdateMessage, + const bool sendMessageSynchronously) +{ + // for a two-value style slider, you should use the setMinValue() and setMaxValue() + // methods to set the two values. + jassert (style != TwoValueHorizontal && style != TwoValueVertical); + + newValue = constrainedValue (newValue); + + if (style == ThreeValueHorizontal || style == ThreeValueVertical) + { + jassert (valueMin <= valueMax); + newValue = jlimit (valueMin, valueMax, newValue); + } + + if (currentValue != newValue) + { + if (valueBox != 0) + valueBox->hideEditor (true); + + currentValue = newValue; + updateText(); + repaint(); + + if (popupDisplay != 0) + { + ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (currentValue)); + popupDisplay->repaint(); + } + + if (sendUpdateMessage) + triggerChangeMessage (sendMessageSynchronously); + } +} + +double Slider::getMinValue() const throw() +{ + // The minimum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + return valueMin; +} + +double Slider::getMaxValue() const throw() +{ + // The maximum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + return valueMax; +} + +void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) +{ + // The minimum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + newValue = constrainedValue (newValue); + + if (style == TwoValueHorizontal || style == TwoValueVertical) + newValue = jmin (valueMax, newValue); + else + newValue = jmin (currentValue, newValue); + + if (valueMin != newValue) + { + valueMin = newValue; + repaint(); + + if (popupDisplay != 0) + { + ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMin)); + popupDisplay->repaint(); + } + + if (sendUpdateMessage) + triggerChangeMessage (sendMessageSynchronously); + } +} + +void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) +{ + // The maximum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + newValue = constrainedValue (newValue); + + if (style == TwoValueHorizontal || style == TwoValueVertical) + newValue = jmax (valueMin, newValue); + else + newValue = jmax (currentValue, newValue); + + if (valueMax != newValue) + { + valueMax = newValue; + repaint(); + + if (popupDisplay != 0) + { + ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMax)); + popupDisplay->repaint(); + } + + if (sendUpdateMessage) + triggerChangeMessage (sendMessageSynchronously); + } +} + +void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled, + const double valueToSetOnDoubleClick) throw() +{ + doubleClickToValue = isDoubleClickEnabled; + doubleClickReturnValue = valueToSetOnDoubleClick; +} + +double Slider::getDoubleClickReturnValue (bool& isEnabled_) const throw() +{ + isEnabled_ = doubleClickToValue; + return doubleClickReturnValue; +} + +void Slider::updateText() +{ + if (valueBox != 0) + valueBox->setText (getTextFromValue (currentValue), false); +} + +void Slider::setTextValueSuffix (const String& suffix) +{ + if (textSuffix != suffix) + { + textSuffix = suffix; + updateText(); + } +} + +const String Slider::getTextFromValue (double v) +{ + if (numDecimalPlaces > 0) + return String (v, numDecimalPlaces) + textSuffix; + else + return String (roundDoubleToInt (v)) + textSuffix; +} + +double Slider::getValueFromText (const String& text) +{ + String t (text.trimStart()); + + if (t.endsWith (textSuffix)) + t = t.substring (0, t.length() - textSuffix.length()); + + while (t.startsWithChar (T('+'))) + t = t.substring (1).trimStart(); + + return t.initialSectionContainingOnly (T("0123456789.,-")) + .getDoubleValue(); +} + +double Slider::proportionOfLengthToValue (double proportion) +{ + if (skewFactor != 1.0 && proportion > 0.0) + proportion = exp (log (proportion) / skewFactor); + + return minimum + (maximum - minimum) * proportion; +} + +double Slider::valueToProportionOfLength (double value) +{ + const double n = (value - minimum) / (maximum - minimum); + + return skewFactor == 1.0 ? n : pow (n, skewFactor); +} + +double Slider::snapValue (double attemptedValue, const bool) +{ + return attemptedValue; +} + +//============================================================================== +void Slider::startedDragging() +{ +} + +void Slider::stoppedDragging() +{ +} + +void Slider::valueChanged() +{ +} + +//============================================================================== +void Slider::enablementChanged() +{ + repaint(); +} + +void Slider::setPopupMenuEnabled (const bool menuEnabled_) throw() +{ + menuEnabled = menuEnabled_; +} + +void Slider::setScrollWheelEnabled (const bool enabled) throw() +{ + scrollWheelEnabled = enabled; +} + +//============================================================================== +void Slider::labelTextChanged (Label* label) +{ + const double newValue = snapValue (getValueFromText (label->getText()), false); + + if (getValue() != newValue) + { + sendDragStart(); + setValue (newValue, true, true); + sendDragEnd(); + } + + updateText(); // force a clean-up of the text, needed in case setValue() hasn't done this. +} + +void Slider::buttonClicked (Button* button) +{ + if (style == IncDecButtons) + { + sendDragStart(); + + if (button == incButton) + setValue (snapValue (getValue() + interval, false), true, true); + else if (button == decButton) + setValue (snapValue (getValue() - interval, false), true, true); + + sendDragEnd(); + } +} + +//============================================================================== +double Slider::constrainedValue (double value) const throw() +{ + if (interval > 0) + value = minimum + interval * floor ((value - minimum) / interval + 0.5); + + if (value <= minimum || maximum <= minimum) + value = minimum; + else if (value >= maximum) + value = maximum; + + return value; +} + +float Slider::getLinearSliderPos (const double value) +{ + double sliderPosProportional; + + if (maximum > minimum) + { + if (value < minimum) + { + sliderPosProportional = 0.0; + } + else if (value > maximum) + { + sliderPosProportional = 1.0; + } + else + { + sliderPosProportional = valueToProportionOfLength (value); + jassert (sliderPosProportional >= 0 && sliderPosProportional <= 1.0); + } + } + else + { + sliderPosProportional = 0.5; + } + + if (isVertical() || style == IncDecButtons) + sliderPosProportional = 1.0 - sliderPosProportional; + + return (float) (sliderRegionStart + sliderPosProportional * sliderRegionSize); +} + +bool Slider::isHorizontal() const throw() +{ + return style == LinearHorizontal + || style == LinearBar + || style == TwoValueHorizontal + || style == ThreeValueHorizontal; +} + +bool Slider::isVertical() const throw() +{ + return style == LinearVertical + || style == TwoValueVertical + || style == ThreeValueVertical; +} + +bool Slider::incDecDragDirectionIsHorizontal() const throw() +{ + return incDecButtonMode == incDecButtonsDraggable_Horizontal + || (incDecButtonMode == incDecButtonsDraggable_AutoDirection && incDecButtonsSideBySide); +} + +float Slider::getPositionOfValue (const double value) +{ + if (isHorizontal() || isVertical()) + { + return getLinearSliderPos (value); + } + else + { + jassertfalse // not a valid call on a slider that doesn't work linearly! + return 0.0f; + } +} + +//============================================================================== +void Slider::paint (Graphics& g) +{ + if (style != IncDecButtons) + { + if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) + { + const float sliderPos = (float) valueToProportionOfLength (currentValue); + jassert (sliderPos >= 0 && sliderPos <= 1.0f); + + getLookAndFeel().drawRotarySlider (g, + sliderRect.getX(), + sliderRect.getY(), + sliderRect.getWidth(), + sliderRect.getHeight(), + sliderPos, + rotaryStart, rotaryEnd, + *this); + } + else + { + getLookAndFeel().drawLinearSlider (g, + sliderRect.getX(), + sliderRect.getY(), + sliderRect.getWidth(), + sliderRect.getHeight(), + getLinearSliderPos (currentValue), + getLinearSliderPos (valueMin), + getLinearSliderPos (valueMax), + style, + *this); + } + + if (style == LinearBar && valueBox == 0) + { + g.setColour (findColour (Slider::textBoxOutlineColourId)); + g.drawRect (0, 0, getWidth(), getHeight(), 1); + } + } +} + +void Slider::resized() +{ + int minXSpace = 0; + int minYSpace = 0; + + if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) + minXSpace = 30; + else + minYSpace = 15; + + const int tbw = jmax (0, jmin (textBoxWidth, getWidth() - minXSpace)); + const int tbh = jmax (0, jmin (textBoxHeight, getHeight() - minYSpace)); + + if (style == LinearBar) + { + if (valueBox != 0) + valueBox->setBounds (0, 0, getWidth(), getHeight()); + } + else + { + if (textBoxPos == NoTextBox) + { + sliderRect.setBounds (0, 0, getWidth(), getHeight()); + } + else if (textBoxPos == TextBoxLeft) + { + valueBox->setBounds (0, (getHeight() - tbh) / 2, tbw, tbh); + sliderRect.setBounds (tbw, 0, getWidth() - tbw, getHeight()); + } + else if (textBoxPos == TextBoxRight) + { + valueBox->setBounds (getWidth() - tbw, (getHeight() - tbh) / 2, tbw, tbh); + sliderRect.setBounds (0, 0, getWidth() - tbw, getHeight()); + } + else if (textBoxPos == TextBoxAbove) + { + valueBox->setBounds ((getWidth() - tbw) / 2, 0, tbw, tbh); + sliderRect.setBounds (0, tbh, getWidth(), getHeight() - tbh); + } + else if (textBoxPos == TextBoxBelow) + { + valueBox->setBounds ((getWidth() - tbw) / 2, getHeight() - tbh, tbw, tbh); + sliderRect.setBounds (0, 0, getWidth(), getHeight() - tbh); + } + } + + const int indent = getLookAndFeel().getSliderThumbRadius (*this); + + if (style == LinearBar) + { + const int barIndent = 1; + sliderRegionStart = barIndent; + sliderRegionSize = getWidth() - barIndent * 2; + + sliderRect.setBounds (sliderRegionStart, barIndent, + sliderRegionSize, getHeight() - barIndent * 2); + } + else if (isHorizontal()) + { + sliderRegionStart = sliderRect.getX() + indent; + sliderRegionSize = jmax (1, sliderRect.getWidth() - indent * 2); + + sliderRect.setBounds (sliderRegionStart, sliderRect.getY(), + sliderRegionSize, sliderRect.getHeight()); + } + else if (isVertical()) + { + sliderRegionStart = sliderRect.getY() + indent; + sliderRegionSize = jmax (1, sliderRect.getHeight() - indent * 2); + + sliderRect.setBounds (sliderRect.getX(), sliderRegionStart, + sliderRect.getWidth(), sliderRegionSize); + } + else + { + sliderRegionStart = 0; + sliderRegionSize = 100; + } + + if (style == IncDecButtons) + { + Rectangle buttonRect (sliderRect); + + if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) + buttonRect.expand (-2, 0); + else + buttonRect.expand (0, -2); + + incDecButtonsSideBySide = buttonRect.getWidth() > buttonRect.getHeight(); + + if (incDecButtonsSideBySide) + { + decButton->setBounds (buttonRect.getX(), + buttonRect.getY(), + buttonRect.getWidth() / 2, + buttonRect.getHeight()); + + decButton->setConnectedEdges (Button::ConnectedOnRight); + + incButton->setBounds (buttonRect.getCentreX(), + buttonRect.getY(), + buttonRect.getWidth() / 2, + buttonRect.getHeight()); + + incButton->setConnectedEdges (Button::ConnectedOnLeft); + } + else + { + incButton->setBounds (buttonRect.getX(), + buttonRect.getY(), + buttonRect.getWidth(), + buttonRect.getHeight() / 2); + + incButton->setConnectedEdges (Button::ConnectedOnBottom); + + decButton->setBounds (buttonRect.getX(), + buttonRect.getCentreY(), + buttonRect.getWidth(), + buttonRect.getHeight() / 2); + + decButton->setConnectedEdges (Button::ConnectedOnTop); + } + } +} + +void Slider::focusOfChildComponentChanged (FocusChangeType) +{ + repaint(); +} + +void Slider::mouseDown (const MouseEvent& e) +{ + mouseWasHidden = false; + incDecDragged = false; + + if (isEnabled()) + { + if (e.mods.isPopupMenu() && menuEnabled) + { + menuShown = true; + + PopupMenu m; + m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased); + m.addSeparator(); + + if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) + { + PopupMenu rotaryMenu; + rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary); + rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag); + rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag); + + m.addSubMenu (TRANS ("rotary mode"), rotaryMenu); + } + + const int r = m.show(); + + if (r == 1) + { + setVelocityBasedMode (! isVelocityBased); + } + else if (r == 2) + { + setSliderStyle (Rotary); + } + else if (r == 3) + { + setSliderStyle (RotaryHorizontalDrag); + } + else if (r == 4) + { + setSliderStyle (RotaryVerticalDrag); + } + } + else if (maximum > minimum) + { + menuShown = false; + + if (valueBox != 0) + valueBox->hideEditor (true); + + sliderBeingDragged = 0; + + if (style == TwoValueHorizontal + || style == TwoValueVertical + || style == ThreeValueHorizontal + || style == ThreeValueVertical) + { + const float mousePos = (float) (isVertical() ? e.y : e.x); + + const float normalPosDistance = fabsf (getLinearSliderPos (currentValue) - mousePos); + const float minPosDistance = fabsf (getLinearSliderPos (valueMin) - 0.1f - mousePos); + const float maxPosDistance = fabsf (getLinearSliderPos (valueMax) + 0.1f - mousePos); + + if (style == TwoValueHorizontal || style == TwoValueVertical) + { + if (maxPosDistance <= minPosDistance) + sliderBeingDragged = 2; + else + sliderBeingDragged = 1; + } + else if (style == ThreeValueHorizontal || style == ThreeValueVertical) + { + if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance) + sliderBeingDragged = 1; + else if (normalPosDistance >= maxPosDistance) + sliderBeingDragged = 2; + } + } + + minMaxDiff = valueMax - valueMin; + + mouseXWhenLastDragged = e.x; + mouseYWhenLastDragged = e.y; + lastAngle = rotaryStart + (rotaryEnd - rotaryStart) + * valueToProportionOfLength (currentValue); + + if (sliderBeingDragged == 2) + valueWhenLastDragged = valueMax; + else if (sliderBeingDragged == 1) + valueWhenLastDragged = valueMin; + else + valueWhenLastDragged = currentValue; + + valueOnMouseDown = valueWhenLastDragged; + + if (popupDisplayEnabled) + { + SliderPopupDisplayComponent* const popup = new SliderPopupDisplayComponent (this); + popupDisplay = popup; + + if (parentForPopupDisplay != 0) + { + parentForPopupDisplay->addChildComponent (popup); + } + else + { + popup->addToDesktop (0); + } + + popup->setVisible (true); + } + + sendDragStart(); + + mouseDrag (e); + } + } +} + +void Slider::mouseUp (const MouseEvent&) +{ + if (isEnabled() + && (! menuShown) + && (maximum > minimum) + && (style != IncDecButtons || incDecDragged)) + { + restoreMouseIfHidden(); + + if (sendChangeOnlyOnRelease && valueOnMouseDown != currentValue) + triggerChangeMessage (false); + + sendDragEnd(); + + deleteAndZero (popupDisplay); + + if (style == IncDecButtons) + { + incButton->setState (Button::buttonNormal); + decButton->setState (Button::buttonNormal); + } + } +} + +void Slider::restoreMouseIfHidden() +{ + if (mouseWasHidden) + { + mouseWasHidden = false; + + Component* c = Component::getComponentUnderMouse(); + + if (c == 0) + c = this; + + c->enableUnboundedMouseMovement (false); + + const double pos = (sliderBeingDragged == 2) ? getMaxValue() + : ((sliderBeingDragged == 1) ? getMinValue() + : currentValue); + + const int pixelPos = (int) getLinearSliderPos (pos); + + int x = isHorizontal() ? pixelPos : (getWidth() / 2); + int y = isVertical() ? pixelPos : (getHeight() / 2); + + relativePositionToGlobal (x, y); + Desktop::setMousePosition (x, y); + } +} + +void Slider::modifierKeysChanged (const ModifierKeys& modifiers) +{ + if (isEnabled() + && style != IncDecButtons + && style != Rotary + && isVelocityBased == modifiers.isAnyModifierKeyDown()) + { + restoreMouseIfHidden(); + } +} + +static double smallestAngleBetween (double a1, double a2) +{ + return jmin (fabs (a1 - a2), + fabs (a1 + double_Pi * 2.0 - a2), + fabs (a2 + double_Pi * 2.0 - a1)); +} + +void Slider::mouseDrag (const MouseEvent& e) +{ + if (isEnabled() + && (! menuShown) + && (maximum > minimum)) + { + if (style == Rotary) + { + int dx = e.x - sliderRect.getCentreX(); + int dy = e.y - sliderRect.getCentreY(); + + if (dx * dx + dy * dy > 25) + { + double angle = atan2 ((double) dx, (double) -dy); + while (angle < 0.0) + angle += double_Pi * 2.0; + + if (rotaryStop && ! e.mouseWasClicked()) + { + if (fabs (angle - lastAngle) > double_Pi) + { + if (angle >= lastAngle) + angle -= double_Pi * 2.0; + else + angle += double_Pi * 2.0; + } + + if (angle >= lastAngle) + angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd)); + else + angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd)); + } + else + { + while (angle < rotaryStart) + angle += double_Pi * 2.0; + + if (angle > rotaryEnd) + { + if (smallestAngleBetween (angle, rotaryStart) <= smallestAngleBetween (angle, rotaryEnd)) + angle = rotaryStart; + else + angle = rotaryEnd; + } + } + + const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart); + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion)); + + lastAngle = angle; + } + } + else + { + if (style == LinearBar && e.mouseWasClicked() + && valueBox != 0 && valueBox->isEditable()) + return; + + if (style == IncDecButtons) + { + if (! incDecDragged) + incDecDragged = e.getDistanceFromDragStart() > 10 && ! e.mouseWasClicked(); + + if (! incDecDragged) + return; + } + + + if ((isVelocityBased == (userKeyOverridesVelocity ? e.mods.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier | ModifierKeys::altModifier) + : false)) + || ((maximum - minimum) / sliderRegionSize < interval)) + { + const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y; + + double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize; + + if (style == RotaryHorizontalDrag + || style == RotaryVerticalDrag + || style == IncDecButtons + || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar) + && ! snapsToMousePos)) + { + const int mouseDiff = (style == RotaryHorizontalDrag + || style == LinearHorizontal + || style == LinearBar + || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) + ? e.getDistanceFromDragStartX() + : -e.getDistanceFromDragStartY(); + + double newPos = valueToProportionOfLength (valueOnMouseDown) + + mouseDiff * (1.0 / pixelsForFullDragExtent); + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos)); + + if (style == IncDecButtons) + { + incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown); + decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown); + } + } + else + { + if (isVertical()) + scaledMousePos = 1.0 - scaledMousePos; + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos)); + } + } + else + { + const int mouseDiff = (isHorizontal() || style == RotaryHorizontalDrag + || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) + ? e.x - mouseXWhenLastDragged + : e.y - mouseYWhenLastDragged; + + const double maxSpeed = jmax (200, sliderRegionSize); + double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff)); + + if (speed != 0) + { + speed = 0.2 * velocityModeSensitivity + * (1.0 + sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset + + jmax (0.0, (double) (speed - velocityModeThreshold)) + / maxSpeed)))); + + if (mouseDiff < 0) + speed = -speed; + + if (isVertical() || style == RotaryVerticalDrag + || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal())) + speed = -speed; + + const double currentPos = valueToProportionOfLength (valueWhenLastDragged); + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed)); + + e.originalComponent->enableUnboundedMouseMovement (true, false); + mouseWasHidden = true; + } + } + } + + valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged); + + if (sliderBeingDragged == 0) + { + setValue (snapValue (valueWhenLastDragged, true), + ! sendChangeOnlyOnRelease, true); + } + else if (sliderBeingDragged == 1) + { + setMinValue (snapValue (valueWhenLastDragged, true), + ! sendChangeOnlyOnRelease, false); + + if (e.mods.isShiftDown()) + setMaxValue (getMinValue() + minMaxDiff, false); + else + minMaxDiff = valueMax - valueMin; + } + else + { + jassert (sliderBeingDragged == 2); + + setMaxValue (snapValue (valueWhenLastDragged, true), + ! sendChangeOnlyOnRelease, false); + + if (e.mods.isShiftDown()) + setMinValue (getMaxValue() - minMaxDiff, false); + else + minMaxDiff = valueMax - valueMin; + } + + mouseXWhenLastDragged = e.x; + mouseYWhenLastDragged = e.y; + } +} + +void Slider::mouseDoubleClick (const MouseEvent&) +{ + if (doubleClickToValue + && isEnabled() + && style != IncDecButtons + && minimum <= doubleClickReturnValue + && maximum >= doubleClickReturnValue) + { + sendDragStart(); + setValue (doubleClickReturnValue, true, true); + sendDragEnd(); + } +} + +void Slider::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) +{ + if (scrollWheelEnabled && isEnabled() + && style != TwoValueHorizontal + && style != TwoValueVertical) + { + if (maximum > minimum && ! isMouseButtonDownAnywhere()) + { + if (valueBox != 0) + valueBox->hideEditor (false); + + const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f; + const double currentPos = valueToProportionOfLength (currentValue); + const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta)); + + double delta = (newValue != currentValue) + ? jmax (fabs (newValue - currentValue), interval) : 0; + + if (currentValue > newValue) + delta = -delta; + + sendDragStart(); + setValue (snapValue (currentValue + delta, false), true, true); + sendDragEnd(); + } + } + else + { + Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); + } +} + +void SliderListener::sliderDragStarted (Slider*) +{ +} + +void SliderListener::sliderDragEnded (Slider*) +{ +} + + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/controls/juce_TextEditor.cpp b/src/juce_appframework/gui/components/controls/juce_TextEditor.cpp index 241885af71..e849773354 100644 --- a/src/juce_appframework/gui/components/controls/juce_TextEditor.cpp +++ b/src/juce_appframework/gui/components/controls/juce_TextEditor.cpp @@ -1,2647 +1,2647 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_TextEditor.h" -#include "../../graphics/fonts/juce_GlyphArrangement.h" -#include "../../../application/juce_SystemClipboard.h" -#include "../../../../juce_core/basics/juce_Time.h" -#include "../../../../juce_core/text/juce_LocalisedStrings.h" -#include "../lookandfeel/juce_LookAndFeel.h" - -#define SHOULD_WRAP(x, wrapwidth) (((x) - 0.0001f) >= (wrapwidth)) - -//============================================================================== -// a word or space that can't be broken down any further -struct TextAtom -{ - //============================================================================== - String atomText; - float width; - uint16 numChars; - - //============================================================================== - bool isWhitespace() const throw() { return CharacterFunctions::isWhitespace (atomText[0]); } - bool isNewLine() const throw() { return atomText[0] == T('\r') || atomText[0] == T('\n'); } - - const String getText (const tchar passwordCharacter) const throw() - { - if (passwordCharacter == 0) - return atomText; - else - return String::repeatedString (String::charToString (passwordCharacter), - atomText.length()); - } - - const String getTrimmedText (const tchar passwordCharacter) const throw() - { - if (passwordCharacter == 0) - return atomText.substring (0, numChars); - else if (isNewLine()) - return String::empty; - else - return String::repeatedString (String::charToString (passwordCharacter), numChars); - } -}; - -//============================================================================== -// a run of text with a single font and colour -class UniformTextSection -{ -public: - //============================================================================== - UniformTextSection (const String& text, - const Font& font_, - const Colour& colour_, - const tchar passwordCharacter) throw() - : font (font_), - colour (colour_), - atoms (64) - { - initialiseAtoms (text, passwordCharacter); - } - - UniformTextSection (const UniformTextSection& other) throw() - : font (other.font), - colour (other.colour), - atoms (64) - { - for (int i = 0; i < other.atoms.size(); ++i) - atoms.add (new TextAtom (*(const TextAtom*) other.atoms.getUnchecked(i))); - } - - ~UniformTextSection() throw() - { - // (no need to delete the atoms, as they're explicitly deleted by the caller) - } - - void clear() throw() - { - for (int i = atoms.size(); --i >= 0;) - { - TextAtom* const atom = getAtom(i); - delete atom; - } - - atoms.clear(); - } - - int getNumAtoms() const throw() - { - return atoms.size(); - } - - TextAtom* getAtom (const int index) const throw() - { - return (TextAtom*) atoms.getUnchecked (index); - } - - void append (const UniformTextSection& other, const tchar passwordCharacter) throw() - { - if (other.atoms.size() > 0) - { - TextAtom* const lastAtom = (TextAtom*) atoms.getLast(); - int i = 0; - - if (lastAtom != 0) - { - if (! CharacterFunctions::isWhitespace (lastAtom->atomText.getLastCharacter())) - { - TextAtom* const first = other.getAtom(0); - - if (! CharacterFunctions::isWhitespace (first->atomText[0])) - { - lastAtom->atomText += first->atomText; - lastAtom->numChars = (uint16) (lastAtom->numChars + first->numChars); - lastAtom->width = font.getStringWidthFloat (lastAtom->getText (passwordCharacter)); - delete first; - ++i; - } - } - } - - while (i < other.atoms.size()) - { - atoms.add (other.getAtom(i)); - ++i; - } - } - } - - UniformTextSection* split (const int indexToBreakAt, - const tchar passwordCharacter) throw() - { - UniformTextSection* const section2 = new UniformTextSection (String::empty, - font, colour, - passwordCharacter); - int index = 0; - - for (int i = 0; i < atoms.size(); ++i) - { - TextAtom* const atom = getAtom(i); - - const int nextIndex = index + atom->numChars; - - if (index == indexToBreakAt) - { - int j; - for (j = i; j < atoms.size(); ++j) - section2->atoms.add (getAtom (j)); - - for (j = atoms.size(); --j >= i;) - atoms.remove (j); - - break; - } - else if (indexToBreakAt >= index && indexToBreakAt < nextIndex) - { - TextAtom* const secondAtom = new TextAtom(); - - secondAtom->atomText = atom->atomText.substring (indexToBreakAt - index); - secondAtom->width = font.getStringWidthFloat (secondAtom->getText (passwordCharacter)); - secondAtom->numChars = (uint16) secondAtom->atomText.length(); - - section2->atoms.add (secondAtom); - - atom->atomText = atom->atomText.substring (0, indexToBreakAt - index); - atom->width = font.getStringWidthFloat (atom->getText (passwordCharacter)); - atom->numChars = (uint16) (indexToBreakAt - index); - - int j; - for (j = i + 1; j < atoms.size(); ++j) - section2->atoms.add (getAtom (j)); - - for (j = atoms.size(); --j > i;) - atoms.remove (j); - - break; - } - - index = nextIndex; - } - - return section2; - } - - const String getAllText() const throw() - { - String s; - s.preallocateStorage (getTotalLength()); - - tchar* endOfString = (tchar*) &(s[0]); - - for (int i = 0; i < atoms.size(); ++i) - { - const TextAtom* const atom = getAtom(i); - - memcpy (endOfString, &(atom->atomText[0]), atom->numChars * sizeof (tchar)); - endOfString += atom->numChars; - } - - *endOfString = 0; - - jassert ((endOfString - (tchar*) &(s[0])) <= getTotalLength()); - return s; - } - - const String getTextSubstring (const int startCharacter, - const int endCharacter) const throw() - { - int index = 0; - int totalLen = 0; - int i; - - for (i = 0; i < atoms.size(); ++i) - { - const TextAtom* const atom = getAtom (i); - const int nextIndex = index + atom->numChars; - - if (startCharacter < nextIndex) - { - if (endCharacter <= index) - break; - - const int start = jmax (0, startCharacter - index); - const int end = jmin (endCharacter - index, atom->numChars); - jassert (end >= start); - - totalLen += end - start; - } - - index = nextIndex; - } - - String s; - s.preallocateStorage (totalLen + 1); - tchar* psz = (tchar*) (const tchar*) s; - - index = 0; - - for (i = 0; i < atoms.size(); ++i) - { - const TextAtom* const atom = getAtom (i); - const int nextIndex = index + atom->numChars; - - if (startCharacter < nextIndex) - { - if (endCharacter <= index) - break; - - const int start = jmax (0, startCharacter - index); - const int len = jmin (endCharacter - index, atom->numChars) - start; - - memcpy (psz, ((const tchar*) atom->atomText) + start, len * sizeof (tchar)); - psz += len; - *psz = 0; - } - - index = nextIndex; - } - - return s; - } - - int getTotalLength() const throw() - { - int c = 0; - - for (int i = atoms.size(); --i >= 0;) - c += getAtom(i)->numChars; - - return c; - } - - void setFont (const Font& newFont, - const tchar passwordCharacter) throw() - { - if (font != newFont) - { - font = newFont; - - for (int i = atoms.size(); --i >= 0;) - { - TextAtom* const atom = (TextAtom*) atoms.getUnchecked(i); - atom->width = newFont.getStringWidthFloat (atom->getText (passwordCharacter)); - } - } - } - - //============================================================================== - juce_UseDebuggingNewOperator - - Font font; - Colour colour; - -private: - VoidArray atoms; - - //============================================================================== - void initialiseAtoms (const String& textToParse, - const tchar passwordCharacter) throw() - { - int i = 0; - const int len = textToParse.length(); - const tchar* const text = (const tchar*) textToParse; - - while (i < len) - { - int start = i; - - // create a whitespace atom unless it starts with non-ws - if (CharacterFunctions::isWhitespace (text[i]) - && text[i] != T('\r') - && text[i] != T('\n')) - { - while (i < len - && CharacterFunctions::isWhitespace (text[i]) - && text[i] != T('\r') - && text[i] != T('\n')) - { - ++i; - } - } - else - { - if (text[i] == T('\r')) - { - ++i; - - if ((i < len) && (text[i] == T('\n'))) - { - ++start; - ++i; - } - } - else if (text[i] == T('\n')) - { - ++i; - } - else - { - while ((i < len) && ! CharacterFunctions::isWhitespace (text[i])) - ++i; - } - } - - TextAtom* const atom = new TextAtom(); - atom->atomText = String (text + start, i - start); - - atom->width = font.getStringWidthFloat (atom->getText (passwordCharacter)); - atom->numChars = (uint16) (i - start); - - atoms.add (atom); - } - } - - const UniformTextSection& operator= (const UniformTextSection& other); -}; - -//============================================================================== -class TextEditorIterator -{ -public: - //============================================================================== - TextEditorIterator (const VoidArray& sections_, - const float wordWrapWidth_, - const tchar passwordCharacter_) throw() - : indexInText (0), - lineY (0), - lineHeight (0), - maxDescent (0), - atomX (0), - atomRight (0), - atom (0), - currentSection (0), - sections (sections_), - sectionIndex (0), - atomIndex (0), - wordWrapWidth (wordWrapWidth_), - passwordCharacter (passwordCharacter_) - { - jassert (wordWrapWidth_ > 0); - - if (sections.size() > 0) - currentSection = (const UniformTextSection*) sections.getUnchecked (sectionIndex); - - if (currentSection != 0) - { - lineHeight = currentSection->font.getHeight(); - maxDescent = currentSection->font.getDescent(); - } - } - - TextEditorIterator (const TextEditorIterator& other) throw() - : indexInText (other.indexInText), - lineY (other.lineY), - lineHeight (other.lineHeight), - maxDescent (other.maxDescent), - atomX (other.atomX), - atomRight (other.atomRight), - atom (other.atom), - currentSection (other.currentSection), - sections (other.sections), - sectionIndex (other.sectionIndex), - atomIndex (other.atomIndex), - wordWrapWidth (other.wordWrapWidth), - passwordCharacter (other.passwordCharacter), - tempAtom (other.tempAtom) - { - } - - ~TextEditorIterator() throw() - { - } - - //============================================================================== - bool next() throw() - { - if (atom == &tempAtom) - { - const int numRemaining = tempAtom.atomText.length() - tempAtom.numChars; - - if (numRemaining > 0) - { - tempAtom.atomText = tempAtom.atomText.substring (tempAtom.numChars); - - atomX = 0; - - if (tempAtom.numChars > 0) - lineY += lineHeight; - - indexInText += tempAtom.numChars; - - GlyphArrangement g; - g.addLineOfText (currentSection->font, atom->getText (passwordCharacter), 0.0f, 0.0f); - - int split; - for (split = 0; split < g.getNumGlyphs(); ++split) - if (SHOULD_WRAP (g.getGlyph (split).getRight(), wordWrapWidth)) - break; - - if (split > 0 && split <= numRemaining) - { - tempAtom.numChars = (uint16) split; - tempAtom.width = g.getGlyph (split - 1).getRight(); - atomRight = atomX + tempAtom.width; - return true; - } - } - } - - bool forceNewLine = false; - - if (sectionIndex >= sections.size()) - { - moveToEndOfLastAtom(); - return false; - } - else if (atomIndex >= currentSection->getNumAtoms() - 1) - { - if (atomIndex >= currentSection->getNumAtoms()) - { - if (++sectionIndex >= sections.size()) - { - moveToEndOfLastAtom(); - return false; - } - - atomIndex = 0; - currentSection = (const UniformTextSection*) sections.getUnchecked (sectionIndex); - - lineHeight = jmax (lineHeight, currentSection->font.getHeight()); - maxDescent = jmax (maxDescent, currentSection->font.getDescent()); - } - else - { - const TextAtom* const lastAtom = currentSection->getAtom (atomIndex); - - if (! lastAtom->isWhitespace()) - { - // handle the case where the last atom in a section is actually part of the same - // word as the first atom of the next section... - float right = atomRight + lastAtom->width; - float lineHeight2 = lineHeight; - float maxDescent2 = maxDescent; - - for (int section = sectionIndex + 1; section < sections.size(); ++section) - { - const UniformTextSection* const s = (const UniformTextSection*) sections.getUnchecked (section); - - if (s->getNumAtoms() == 0) - break; - - const TextAtom* const nextAtom = s->getAtom (0); - - if (nextAtom->isWhitespace()) - break; - - right += nextAtom->width; - - lineHeight2 = jmax (lineHeight2, s->font.getHeight()); - maxDescent2 = jmax (maxDescent2, s->font.getDescent()); - - if (SHOULD_WRAP (right, wordWrapWidth)) - { - lineHeight = lineHeight2; - maxDescent = maxDescent2; - - forceNewLine = true; - break; - } - - if (s->getNumAtoms() > 1) - break; - } - } - } - } - - if (atom != 0) - { - atomX = atomRight; - indexInText += atom->numChars; - - if (atom->isNewLine()) - { - atomX = 0; - lineY += lineHeight; - } - } - - atom = currentSection->getAtom (atomIndex); - atomRight = atomX + atom->width; - ++atomIndex; - - if (SHOULD_WRAP (atomRight, wordWrapWidth) || forceNewLine) - { - if (atom->isWhitespace()) - { - // leave whitespace at the end of a line, but truncate it to avoid scrolling - atomRight = jmin (atomRight, wordWrapWidth); - } - else - { - return wrapCurrentAtom(); - } - } - - return true; - } - - bool wrapCurrentAtom() throw() - { - atomRight = atom->width; - - if (SHOULD_WRAP (atomRight, wordWrapWidth)) // atom too big to fit on a line, so break it up.. - { - tempAtom = *atom; - tempAtom.width = 0; - tempAtom.numChars = 0; - atom = &tempAtom; - - if (atomX > 0) - { - atomX = 0; - lineY += lineHeight; - } - - return next(); - } - - atomX = 0; - lineY += lineHeight; - return true; - } - - //============================================================================== - void draw (Graphics& g, const UniformTextSection*& lastSection) const throw() - { - if (passwordCharacter != 0 || ! atom->isWhitespace()) - { - if (lastSection != currentSection) - { - lastSection = currentSection; - g.setColour (currentSection->colour); - g.setFont (currentSection->font); - } - - jassert (atom->getTrimmedText (passwordCharacter).isNotEmpty()); - - GlyphArrangement ga; - ga.addLineOfText (currentSection->font, - atom->getTrimmedText (passwordCharacter), - atomX, - (float) roundFloatToInt (lineY + lineHeight - maxDescent)); - ga.draw (g); - } - } - - void drawSelection (Graphics& g, - const int selectionStart, - const int selectionEnd) const throw() - { - const int startX = roundFloatToInt (indexToX (selectionStart)); - const int endX = roundFloatToInt (indexToX (selectionEnd)); - - const int y = roundFloatToInt (lineY); - const int nextY = roundFloatToInt (lineY + lineHeight); - - g.fillRect (startX, y, endX - startX, nextY - y); - } - - void drawSelectedText (Graphics& g, - const int selectionStart, - const int selectionEnd, - const Colour& selectedTextColour) const throw() - { - if (passwordCharacter != 0 || ! atom->isWhitespace()) - { - GlyphArrangement ga; - ga.addLineOfText (currentSection->font, - atom->getTrimmedText (passwordCharacter), - atomX, - (float) roundFloatToInt (lineY + lineHeight - maxDescent)); - - if (selectionEnd < indexInText + atom->numChars) - { - GlyphArrangement ga2 (ga); - ga2.removeRangeOfGlyphs (0, selectionEnd - indexInText); - ga.removeRangeOfGlyphs (selectionEnd - indexInText, -1); - - g.setColour (currentSection->colour); - ga2.draw (g); - } - - if (selectionStart > indexInText) - { - GlyphArrangement ga2 (ga); - ga2.removeRangeOfGlyphs (selectionStart - indexInText, -1); - ga.removeRangeOfGlyphs (0, selectionStart - indexInText); - - g.setColour (currentSection->colour); - ga2.draw (g); - } - - g.setColour (selectedTextColour); - ga.draw (g); - } - } - - //============================================================================== - float indexToX (const int indexToFind) const throw() - { - if (indexToFind <= indexInText) - return atomX; - - if (indexToFind >= indexInText + atom->numChars) - return atomRight; - - GlyphArrangement g; - g.addLineOfText (currentSection->font, - atom->getText (passwordCharacter), - atomX, 0.0f); - - return jmin (atomRight, g.getGlyph (indexToFind - indexInText).getLeft()); - } - - int xToIndex (const float xToFind) const throw() - { - if (xToFind <= atomX || atom->isNewLine()) - return indexInText; - - if (xToFind >= atomRight) - return indexInText + atom->numChars; - - GlyphArrangement g; - g.addLineOfText (currentSection->font, - atom->getText (passwordCharacter), - atomX, 0.0f); - - int j; - for (j = 0; j < atom->numChars; ++j) - if ((g.getGlyph(j).getLeft() + g.getGlyph(j).getRight()) / 2 > xToFind) - break; - - return indexInText + j; - } - - //============================================================================== - void updateLineHeight() throw() - { - float x = atomRight; - - int tempSectionIndex = sectionIndex; - int tempAtomIndex = atomIndex; - const UniformTextSection* currentSection = (const UniformTextSection*) sections.getUnchecked (tempSectionIndex); - - while (! SHOULD_WRAP (x, wordWrapWidth)) - { - if (tempSectionIndex >= sections.size()) - break; - - bool checkSize = false; - - if (tempAtomIndex >= currentSection->getNumAtoms()) - { - if (++tempSectionIndex >= sections.size()) - break; - - tempAtomIndex = 0; - currentSection = (const UniformTextSection*) sections.getUnchecked (tempSectionIndex); - checkSize = true; - } - - const TextAtom* const atom = currentSection->getAtom (tempAtomIndex); - - if (atom == 0) - break; - - x += atom->width; - - if (SHOULD_WRAP (x, wordWrapWidth) || atom->isNewLine()) - break; - - if (checkSize) - { - lineHeight = jmax (lineHeight, currentSection->font.getHeight()); - maxDescent = jmax (maxDescent, currentSection->font.getDescent()); - } - - ++tempAtomIndex; - } - } - - bool getCharPosition (const int index, float& cx, float& cy, float& lineHeight_) throw() - { - while (next()) - { - if (indexInText + atom->numChars >= index) - { - updateLineHeight(); - - if (indexInText + atom->numChars > index) - { - cx = indexToX (index); - cy = lineY; - lineHeight_ = lineHeight; - return true; - } - } - } - - cx = atomX; - cy = lineY; - lineHeight_ = lineHeight; - return false; - } - - //============================================================================== - juce_UseDebuggingNewOperator - - int indexInText; - float lineY, lineHeight, maxDescent; - float atomX, atomRight; - const TextAtom* atom; - const UniformTextSection* currentSection; - -private: - const VoidArray& sections; - int sectionIndex, atomIndex; - const float wordWrapWidth; - const tchar passwordCharacter; - TextAtom tempAtom; - - const TextEditorIterator& operator= (const TextEditorIterator&); - - void moveToEndOfLastAtom() throw() - { - if (atom != 0) - { - atomX = atomRight; - - if (atom->isNewLine()) - { - atomX = 0.0f; - lineY += lineHeight; - } - } - } -}; - - -//============================================================================== -class TextEditorInsertAction : public UndoableAction -{ - TextEditor& owner; - const String text; - const int insertIndex, oldCaretPos, newCaretPos; - const Font font; - const Colour colour; - - TextEditorInsertAction (const TextEditorInsertAction&); - const TextEditorInsertAction& operator= (const TextEditorInsertAction&); - -public: - TextEditorInsertAction (TextEditor& owner_, - const String& text_, - const int insertIndex_, - const Font& font_, - const Colour& colour_, - const int oldCaretPos_, - const int newCaretPos_) throw() - : owner (owner_), - text (text_), - insertIndex (insertIndex_), - oldCaretPos (oldCaretPos_), - newCaretPos (newCaretPos_), - font (font_), - colour (colour_) - { - } - - ~TextEditorInsertAction() - { - } - - bool perform() - { - owner.insert (text, insertIndex, font, colour, 0, newCaretPos); - return true; - } - - bool undo() - { - owner.remove (insertIndex, insertIndex + text.length(), 0, oldCaretPos); - return true; - } - - int getSizeInUnits() - { - return text.length() + 16; - } -}; - -//============================================================================== -class TextEditorRemoveAction : public UndoableAction -{ - TextEditor& owner; - const int startIndex, endIndex, oldCaretPos, newCaretPos; - VoidArray removedSections; - - TextEditorRemoveAction (const TextEditorRemoveAction&); - const TextEditorRemoveAction& operator= (const TextEditorRemoveAction&); - -public: - TextEditorRemoveAction (TextEditor& owner_, - const int startIndex_, - const int endIndex_, - const int oldCaretPos_, - const int newCaretPos_, - const VoidArray& removedSections_) throw() - : owner (owner_), - startIndex (startIndex_), - endIndex (endIndex_), - oldCaretPos (oldCaretPos_), - newCaretPos (newCaretPos_), - removedSections (removedSections_) - { - } - - ~TextEditorRemoveAction() - { - for (int i = removedSections.size(); --i >= 0;) - { - UniformTextSection* const section = (UniformTextSection*) removedSections.getUnchecked (i); - section->clear(); - delete section; - } - } - - bool perform() - { - owner.remove (startIndex, endIndex, 0, newCaretPos); - return true; - } - - bool undo() - { - owner.reinsert (startIndex, removedSections); - owner.moveCursorTo (oldCaretPos, false); - return true; - } - - int getSizeInUnits() - { - int n = 0; - - for (int i = removedSections.size(); --i >= 0;) - { - UniformTextSection* const section = (UniformTextSection*) removedSections.getUnchecked (i); - n += section->getTotalLength(); - } - - return n + 16; - } -}; - -//============================================================================== -class TextHolderComponent : public Component, - public Timer -{ - TextEditor* const owner; - - TextHolderComponent (const TextHolderComponent&); - const TextHolderComponent& operator= (const TextHolderComponent&); - -public: - TextHolderComponent (TextEditor* const owner_) - : owner (owner_) - { - setWantsKeyboardFocus (false); - setInterceptsMouseClicks (false, true); - } - - ~TextHolderComponent() - { - } - - void paint (Graphics& g) - { - owner->drawContent (g); - } - - void timerCallback() - { - owner->timerCallbackInt(); - } - - const MouseCursor getMouseCursor() - { - return owner->getMouseCursor(); - } -}; - -//============================================================================== -class TextEditorViewport : public Viewport -{ - TextEditor* const owner; - float lastWordWrapWidth; - - TextEditorViewport (const TextEditorViewport&); - const TextEditorViewport& operator= (const TextEditorViewport&); - -public: - TextEditorViewport (TextEditor* const owner_) - : owner (owner_), - lastWordWrapWidth (0) - { - } - - ~TextEditorViewport() - { - } - - void visibleAreaChanged (int, int, int, int) - { - const float wordWrapWidth = owner->getWordWrapWidth(); - - if (wordWrapWidth != lastWordWrapWidth) - { - lastWordWrapWidth = wordWrapWidth; - owner->updateTextHolderSize(); - } - } -}; - -//============================================================================== -const int flashSpeedIntervalMs = 380; - -const int textChangeMessageId = 0x10003001; -const int returnKeyMessageId = 0x10003002; -const int escapeKeyMessageId = 0x10003003; -const int focusLossMessageId = 0x10003004; - - -//============================================================================== -TextEditor::TextEditor (const String& name, - const tchar passwordCharacter_) - : Component (name), - borderSize (1, 1, 1, 3), - readOnly (false), - multiline (false), - wordWrap (false), - returnKeyStartsNewLine (false), - caretVisible (true), - popupMenuEnabled (true), - selectAllTextWhenFocused (false), - scrollbarVisible (true), - wasFocused (false), - caretFlashState (true), - keepCursorOnScreen (true), - tabKeyUsed (false), - menuActive (false), - cursorX (0), - cursorY (0), - cursorHeight (0), - maxTextLength (0), - selectionStart (0), - selectionEnd (0), - leftIndent (4), - topIndent (4), - lastTransactionTime (0), - currentFont (14.0f), - totalNumChars (0), - caretPosition (0), - sections (8), - passwordCharacter (passwordCharacter_), - dragType (notDragging), - listeners (2) -{ - setOpaque (true); - - addAndMakeVisible (viewport = new TextEditorViewport (this)); - viewport->setViewedComponent (textHolder = new TextHolderComponent (this)); - viewport->setWantsKeyboardFocus (false); - viewport->setScrollBarsShown (false, false); - - setMouseCursor (MouseCursor::IBeamCursor); - setWantsKeyboardFocus (true); -} - -TextEditor::~TextEditor() -{ - clearInternal (0); - delete viewport; -} - -//============================================================================== -void TextEditor::newTransaction() throw() -{ - lastTransactionTime = Time::getApproximateMillisecondCounter(); - undoManager.beginNewTransaction(); -} - -void TextEditor::doUndoRedo (const bool isRedo) -{ - if (! isReadOnly()) - { - if ((isRedo) ? undoManager.redo() - : undoManager.undo()) - { - scrollToMakeSureCursorIsVisible(); - repaint(); - textChanged(); - } - } -} - -//============================================================================== -void TextEditor::setMultiLine (const bool shouldBeMultiLine, - const bool shouldWordWrap) -{ - multiline = shouldBeMultiLine; - wordWrap = shouldWordWrap && shouldBeMultiLine; - - setScrollbarsShown (scrollbarVisible); - - viewport->setViewPosition (0, 0); - - resized(); - scrollToMakeSureCursorIsVisible(); -} - -bool TextEditor::isMultiLine() const throw() -{ - return multiline; -} - -void TextEditor::setScrollbarsShown (bool enabled) throw() -{ - scrollbarVisible = enabled; - - enabled = enabled && isMultiLine(); - - viewport->setScrollBarsShown (enabled, enabled); -} - -void TextEditor::setReadOnly (const bool shouldBeReadOnly) -{ - readOnly = shouldBeReadOnly; - enablementChanged(); -} - -bool TextEditor::isReadOnly() const throw() -{ - return readOnly || ! isEnabled(); -} - -void TextEditor::setReturnKeyStartsNewLine (const bool shouldStartNewLine) -{ - returnKeyStartsNewLine = shouldStartNewLine; -} - -void TextEditor::setTabKeyUsedAsCharacter (const bool shouldTabKeyBeUsed) throw() -{ - tabKeyUsed = shouldTabKeyBeUsed; -} - -void TextEditor::setPopupMenuEnabled (const bool b) throw() -{ - popupMenuEnabled = b; -} - -void TextEditor::setSelectAllWhenFocused (const bool b) throw() -{ - selectAllTextWhenFocused = b; -} - -//============================================================================== -const Font TextEditor::getFont() const throw() -{ - return currentFont; -} - -void TextEditor::setFont (const Font& newFont) throw() -{ - currentFont = newFont; - scrollToMakeSureCursorIsVisible(); -} - -void TextEditor::applyFontToAllText (const Font& newFont) -{ - currentFont = newFont; - - const Colour overallColour (findColour (textColourId)); - - for (int i = sections.size(); --i >= 0;) - { - UniformTextSection* const uts = (UniformTextSection*) sections.getUnchecked(i); - uts->setFont (newFont, passwordCharacter); - uts->colour = overallColour; - } - - coalesceSimilarSections(); - updateTextHolderSize(); - scrollToMakeSureCursorIsVisible(); - repaint(); -} - -void TextEditor::colourChanged() -{ - setOpaque (findColour (backgroundColourId).isOpaque()); - repaint(); -} - -void TextEditor::setCaretVisible (const bool shouldCaretBeVisible) throw() -{ - caretVisible = shouldCaretBeVisible; - - if (shouldCaretBeVisible) - textHolder->startTimer (flashSpeedIntervalMs); - - setMouseCursor (shouldCaretBeVisible ? MouseCursor::IBeamCursor - : MouseCursor::NormalCursor); -} - -void TextEditor::setInputRestrictions (const int maxLen, - const String& chars) throw() -{ - maxTextLength = jmax (0, maxLen); - allowedCharacters = chars; -} - -void TextEditor::setTextToShowWhenEmpty (const String& text, const Colour& colourToUse) throw() -{ - textToShowWhenEmpty = text; - colourForTextWhenEmpty = colourToUse; -} - -void TextEditor::setPasswordCharacter (const tchar newPasswordCharacter) throw() -{ - if (passwordCharacter != newPasswordCharacter) - { - passwordCharacter = newPasswordCharacter; - resized(); - repaint(); - } -} - -void TextEditor::setScrollBarThickness (const int newThicknessPixels) -{ - viewport->setScrollBarThickness (newThicknessPixels); -} - -void TextEditor::setScrollBarButtonVisibility (const bool buttonsVisible) -{ - viewport->setScrollBarButtonVisibility (buttonsVisible); -} - -//============================================================================== -void TextEditor::clear() -{ - clearInternal (0); - updateTextHolderSize(); - undoManager.clearUndoHistory(); -} - -void TextEditor::setText (const String& newText, - const bool sendTextChangeMessage) -{ - const int newLength = newText.length(); - - if (newLength != getTotalNumChars() || getText() != newText) - { - const int oldCursorPos = caretPosition; - const bool cursorWasAtEnd = oldCursorPos >= getTotalNumChars(); - - clearInternal (0); - insert (newText, 0, currentFont, findColour (textColourId), 0, caretPosition); - - // if you're adding text with line-feeds to a single-line text editor, it - // ain't gonna look right! - jassert (multiline || ! newText.containsAnyOf (T("\r\n"))); - - if (cursorWasAtEnd && ! isMultiLine()) - moveCursorTo (getTotalNumChars(), false); - else - moveCursorTo (oldCursorPos, false); - - if (sendTextChangeMessage) - textChanged(); - - repaint(); - } - - updateTextHolderSize(); - scrollToMakeSureCursorIsVisible(); - undoManager.clearUndoHistory(); -} - -//============================================================================== -void TextEditor::textChanged() throw() -{ - updateTextHolderSize(); - postCommandMessage (textChangeMessageId); -} - -void TextEditor::returnPressed() -{ - postCommandMessage (returnKeyMessageId); -} - -void TextEditor::escapePressed() -{ - postCommandMessage (escapeKeyMessageId); -} - -void TextEditor::addListener (TextEditorListener* const newListener) throw() -{ - jassert (newListener != 0) - - if (newListener != 0) - listeners.add (newListener); -} - -void TextEditor::removeListener (TextEditorListener* const listenerToRemove) throw() -{ - listeners.removeValue (listenerToRemove); -} - -//============================================================================== -void TextEditor::timerCallbackInt() -{ - const bool newState = (! caretFlashState) && ! isCurrentlyBlockedByAnotherModalComponent(); - - if (caretFlashState != newState) - { - caretFlashState = newState; - - if (caretFlashState) - wasFocused = true; - - if (caretVisible - && hasKeyboardFocus (false) - && ! isReadOnly()) - { - repaintCaret(); - } - } - - const unsigned int now = Time::getApproximateMillisecondCounter(); - - if (now > lastTransactionTime + 200) - newTransaction(); -} - -void TextEditor::repaintCaret() -{ - if (! findColour (caretColourId).isTransparent()) - repaint (borderSize.getLeft() + textHolder->getX() + leftIndent + roundFloatToInt (cursorX) - 1, - borderSize.getTop() + textHolder->getY() + topIndent + roundFloatToInt (cursorY) - 1, - 4, - roundFloatToInt (cursorHeight) + 2); -} - -void TextEditor::repaintText (int textStartIndex, int textEndIndex) -{ - if (textStartIndex > textEndIndex && textEndIndex > 0) - swapVariables (textStartIndex, textEndIndex); - - float x = 0, y = 0, lh = currentFont.getHeight(); - - const float wordWrapWidth = getWordWrapWidth(); - - if (wordWrapWidth > 0) - { - TextEditorIterator i (sections, wordWrapWidth, passwordCharacter); - - i.getCharPosition (textStartIndex, x, y, lh); - - const int y1 = (int) y; - int y2; - - if (textEndIndex >= 0) - { - i.getCharPosition (textEndIndex, x, y, lh); - y2 = (int) (y + lh * 2.0f); - } - else - { - y2 = textHolder->getHeight(); - } - - textHolder->repaint (0, y1, textHolder->getWidth(), y2 - y1); - } -} - -//============================================================================== -void TextEditor::moveCaret (int newCaretPos) throw() -{ - if (newCaretPos < 0) - newCaretPos = 0; - else if (newCaretPos > getTotalNumChars()) - newCaretPos = getTotalNumChars(); - - if (newCaretPos != getCaretPosition()) - { - repaintCaret(); - caretFlashState = true; - caretPosition = newCaretPos; - textHolder->startTimer (flashSpeedIntervalMs); - scrollToMakeSureCursorIsVisible(); - repaintCaret(); - } -} - -void TextEditor::setCaretPosition (const int newIndex) throw() -{ - moveCursorTo (newIndex, false); -} - -int TextEditor::getCaretPosition() const throw() -{ - return caretPosition; -} - -void TextEditor::scrollEditorToPositionCaret (const int desiredCaretX, - const int desiredCaretY) throw() - -{ - updateCaretPosition(); - - int vx = roundFloatToInt (cursorX) - desiredCaretX; - int vy = roundFloatToInt (cursorY) - desiredCaretY; - - if (desiredCaretX < jmax (1, proportionOfWidth (0.05f))) - { - vx += desiredCaretX - proportionOfWidth (0.2f); - } - else if (desiredCaretX > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10))) - { - vx += desiredCaretX + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth(); - } - - vx = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), vx); - - if (! isMultiLine()) - { - vy = viewport->getViewPositionY(); - } - else - { - vy = jlimit (0, jmax (0, textHolder->getHeight() - viewport->getMaximumVisibleHeight()), vy); - - const int curH = roundFloatToInt (cursorHeight); - - if (desiredCaretY < 0) - { - vy = jmax (0, desiredCaretY + vy); - } - else if (desiredCaretY > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - curH)) - { - vy += desiredCaretY + 2 + curH + topIndent - viewport->getMaximumVisibleHeight(); - } - } - - viewport->setViewPosition (vx, vy); -} - -const Rectangle TextEditor::getCaretRectangle() throw() -{ - updateCaretPosition(); - - return Rectangle (roundFloatToInt (cursorX) - viewport->getX(), - roundFloatToInt (cursorY) - viewport->getY(), - 1, roundFloatToInt (cursorHeight)); -} - -//============================================================================== -float TextEditor::getWordWrapWidth() const throw() -{ - return (wordWrap) ? (float) (viewport->getMaximumVisibleWidth() - leftIndent - leftIndent / 2) - : 1.0e10f; -} - -void TextEditor::updateTextHolderSize() throw() -{ - const float wordWrapWidth = getWordWrapWidth(); - - if (wordWrapWidth > 0) - { - float maxWidth = 0.0f; - - TextEditorIterator i (sections, wordWrapWidth, passwordCharacter); - - while (i.next()) - maxWidth = jmax (maxWidth, i.atomRight); - - const int w = leftIndent + roundFloatToInt (maxWidth); - const int h = topIndent + roundFloatToInt (jmax (i.lineY + i.lineHeight, - currentFont.getHeight())); - - textHolder->setSize (w + 1, h + 1); - } -} - -int TextEditor::getTextWidth() const throw() -{ - return textHolder->getWidth(); -} - -int TextEditor::getTextHeight() const throw() -{ - return textHolder->getHeight(); -} - -void TextEditor::setIndents (const int newLeftIndent, - const int newTopIndent) throw() -{ - leftIndent = newLeftIndent; - topIndent = newTopIndent; -} - -void TextEditor::setBorder (const BorderSize& border) throw() -{ - borderSize = border; - resized(); -} - -const BorderSize TextEditor::getBorder() const throw() -{ - return borderSize; -} - -void TextEditor::setScrollToShowCursor (const bool shouldScrollToShowCursor) throw() -{ - keepCursorOnScreen = shouldScrollToShowCursor; -} - -void TextEditor::updateCaretPosition() throw() -{ - cursorHeight = currentFont.getHeight(); // (in case the text is empty and the call below doesn't set this value) - getCharPosition (caretPosition, cursorX, cursorY, cursorHeight); -} - -void TextEditor::scrollToMakeSureCursorIsVisible() throw() -{ - updateCaretPosition(); - - if (keepCursorOnScreen) - { - int x = viewport->getViewPositionX(); - int y = viewport->getViewPositionY(); - - const int relativeCursorX = roundFloatToInt (cursorX) - x; - const int relativeCursorY = roundFloatToInt (cursorY) - y; - - if (relativeCursorX < jmax (1, proportionOfWidth (0.05f))) - { - x += relativeCursorX - proportionOfWidth (0.2f); - } - else if (relativeCursorX > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10))) - { - x += relativeCursorX + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth(); - } - - x = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), x); - - if (! isMultiLine()) - { - y = (getHeight() - textHolder->getHeight() - topIndent) / -2; - } - else - { - const int curH = roundFloatToInt (cursorHeight); - - if (relativeCursorY < 0) - { - y = jmax (0, relativeCursorY + y); - } - else if (relativeCursorY > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - curH)) - { - y += relativeCursorY + 2 + curH + topIndent - viewport->getMaximumVisibleHeight(); - } - } - - viewport->setViewPosition (x, y); - } -} - -void TextEditor::moveCursorTo (const int newPosition, - const bool isSelecting) throw() -{ - if (isSelecting) - { - moveCaret (newPosition); - - const int oldSelStart = selectionStart; - const int oldSelEnd = selectionEnd; - - if (dragType == notDragging) - { - if (abs (getCaretPosition() - selectionStart) < abs (getCaretPosition() - selectionEnd)) - dragType = draggingSelectionStart; - else - dragType = draggingSelectionEnd; - } - - if (dragType == draggingSelectionStart) - { - selectionStart = getCaretPosition(); - - if (selectionEnd < selectionStart) - { - swapVariables (selectionStart, selectionEnd); - dragType = draggingSelectionEnd; - } - } - else - { - selectionEnd = getCaretPosition(); - - if (selectionEnd < selectionStart) - { - swapVariables (selectionStart, selectionEnd); - dragType = draggingSelectionStart; - } - } - - jassert (selectionStart <= selectionEnd); - jassert (oldSelStart <= oldSelEnd); - - repaintText (jmin (oldSelStart, selectionStart), - jmax (oldSelEnd, selectionEnd)); - } - else - { - dragType = notDragging; - - if (selectionEnd > selectionStart) - repaintText (selectionStart, selectionEnd); - - moveCaret (newPosition); - selectionStart = getCaretPosition(); - selectionEnd = getCaretPosition(); - } -} - -int TextEditor::getTextIndexAt (const int x, - const int y) throw() -{ - return indexAtPosition ((float) (x + viewport->getViewPositionX() - leftIndent), - (float) (y + viewport->getViewPositionY() - topIndent)); -} - -void TextEditor::insertTextAtCursor (String newText) -{ - if (allowedCharacters.isNotEmpty()) - newText = newText.retainCharacters (allowedCharacters); - - if (! isMultiLine()) - newText = newText.replaceCharacters (T("\r\n"), T(" ")); - else - newText = newText.replace (T("\r\n"), T("\n")); - - const int newCaretPos = selectionStart + newText.length(); - const int insertIndex = selectionStart; - - remove (selectionStart, selectionEnd, - &undoManager, - newText.isNotEmpty() ? newCaretPos - 1 : newCaretPos); - - if (maxTextLength > 0) - newText = newText.substring (0, maxTextLength - getTotalNumChars()); - - if (newText.isNotEmpty()) - insert (newText, - insertIndex, - currentFont, - findColour (textColourId), - &undoManager, - newCaretPos); - - textChanged(); -} - -void TextEditor::setHighlightedRegion (int startPos, int numChars) throw() -{ - moveCursorTo (startPos, false); - moveCursorTo (startPos + numChars, true); -} - -//============================================================================== -void TextEditor::copy() -{ - const String selection (getTextSubstring (selectionStart, selectionEnd)); - - if (selection.isNotEmpty()) - SystemClipboard::copyTextToClipboard (selection); -} - -void TextEditor::paste() -{ - if (! isReadOnly()) - { - const String clip (SystemClipboard::getTextFromClipboard()); - - if (clip.isNotEmpty()) - insertTextAtCursor (clip); - } -} - -void TextEditor::cut() -{ - if (! isReadOnly()) - { - moveCaret (selectionEnd); - insertTextAtCursor (String::empty); - } -} - -//============================================================================== -void TextEditor::drawContent (Graphics& g) -{ - const float wordWrapWidth = getWordWrapWidth(); - - if (wordWrapWidth > 0) - { - g.setOrigin (leftIndent, topIndent); - const Rectangle clip (g.getClipBounds()); - Colour selectedTextColour; - - TextEditorIterator i (sections, wordWrapWidth, passwordCharacter); - - while (i.lineY + 200.0 < clip.getY() && i.next()) - {} - - if (selectionStart < selectionEnd) - { - g.setColour (findColour (highlightColourId) - .withMultipliedAlpha (hasKeyboardFocus (true) ? 1.0f : 0.5f)); - - selectedTextColour = findColour (highlightedTextColourId); - - TextEditorIterator i2 (i); - - while (i2.next() && i2.lineY < clip.getBottom()) - { - i2.updateLineHeight(); - - if (i2.lineY + i2.lineHeight >= clip.getY() - && selectionEnd >= i2.indexInText - && selectionStart <= i2.indexInText + i2.atom->numChars) - { - i2.drawSelection (g, selectionStart, selectionEnd); - } - } - } - - const UniformTextSection* lastSection = 0; - - while (i.next() && i.lineY < clip.getBottom()) - { - i.updateLineHeight(); - - if (i.lineY + i.lineHeight >= clip.getY()) - { - if (selectionEnd >= i.indexInText - && selectionStart <= i.indexInText + i.atom->numChars) - { - i.drawSelectedText (g, selectionStart, selectionEnd, selectedTextColour); - lastSection = 0; - } - else - { - i.draw (g, lastSection); - } - } - } - } -} - -void TextEditor::paint (Graphics& g) -{ - getLookAndFeel().fillTextEditorBackground (g, getWidth(), getHeight(), *this); -} - -void TextEditor::paintOverChildren (Graphics& g) -{ - if (caretFlashState - && hasKeyboardFocus (false) - && caretVisible - && ! isReadOnly()) - { - g.setColour (findColour (caretColourId)); - - g.fillRect (borderSize.getLeft() + textHolder->getX() + leftIndent + cursorX, - borderSize.getTop() + textHolder->getY() + topIndent + cursorY, - 2.0f, cursorHeight); - } - - if (textToShowWhenEmpty.isNotEmpty() - && (! hasKeyboardFocus (false)) - && getTotalNumChars() == 0) - { - g.setColour (colourForTextWhenEmpty); - g.setFont (getFont()); - - if (isMultiLine()) - { - g.drawText (textToShowWhenEmpty, - 0, 0, getWidth(), getHeight(), - Justification::centred, true); - } - else - { - g.drawText (textToShowWhenEmpty, - leftIndent, topIndent, - viewport->getWidth() - leftIndent, - viewport->getHeight() - topIndent, - Justification::centredLeft, true); - } - } - - getLookAndFeel().drawTextEditorOutline (g, getWidth(), getHeight(), *this); -} - -//============================================================================== -void TextEditor::mouseDown (const MouseEvent& e) -{ - beginDragAutoRepeat (100); - newTransaction(); - - if (wasFocused || ! selectAllTextWhenFocused) - { - if (! (popupMenuEnabled && e.mods.isPopupMenu())) - { - moveCursorTo (getTextIndexAt (e.x, e.y), - e.mods.isShiftDown()); - } - else - { - PopupMenu m; - addPopupMenuItems (m, &e); - - menuActive = true; - const int result = m.show(); - menuActive = false; - - if (result != 0) - performPopupMenuAction (result); - } - } -} - -void TextEditor::mouseDrag (const MouseEvent& e) -{ - if (wasFocused || ! selectAllTextWhenFocused) - { - if (! (popupMenuEnabled && e.mods.isPopupMenu())) - { - moveCursorTo (getTextIndexAt (e.x, e.y), true); - } - } -} - -void TextEditor::mouseUp (const MouseEvent& e) -{ - newTransaction(); - textHolder->startTimer (flashSpeedIntervalMs); - - if (wasFocused || ! selectAllTextWhenFocused) - { - if (! (popupMenuEnabled && e.mods.isPopupMenu())) - { - moveCaret (getTextIndexAt (e.x, e.y)); - } - } - - wasFocused = true; -} - -void TextEditor::mouseDoubleClick (const MouseEvent& e) -{ - int tokenEnd = getTextIndexAt (e.x, e.y); - int tokenStart = tokenEnd; - - if (e.getNumberOfClicks() > 3) - { - tokenStart = 0; - tokenEnd = getTotalNumChars(); - } - else - { - const String t (getText()); - const int totalLength = getTotalNumChars(); - - while (tokenEnd < totalLength) - { - if (CharacterFunctions::isLetterOrDigit (t [tokenEnd])) - ++tokenEnd; - else - break; - } - - tokenStart = tokenEnd; - - while (tokenStart > 0) - { - if (CharacterFunctions::isLetterOrDigit (t [tokenStart - 1])) - --tokenStart; - else - break; - } - - if (e.getNumberOfClicks() > 2) - { - while (tokenEnd < totalLength) - { - if (t [tokenEnd] != T('\r') && t [tokenEnd] != T('\n')) - ++tokenEnd; - else - break; - } - - while (tokenStart > 0) - { - if (t [tokenStart - 1] != T('\r') && t [tokenStart - 1] != T('\n')) - --tokenStart; - else - break; - } - } - } - - moveCursorTo (tokenEnd, false); - moveCursorTo (tokenStart, true); -} - -void TextEditor::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) -{ - if (! viewport->useMouseWheelMoveIfNeeded (e, wheelIncrementX, wheelIncrementY)) - Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); -} - -//============================================================================== -bool TextEditor::keyPressed (const KeyPress& key) -{ - if (isReadOnly() && key != KeyPress (T('c'), ModifierKeys::commandModifier, 0)) - return false; - - const bool moveInWholeWordSteps = key.getModifiers().isCtrlDown() || key.getModifiers().isAltDown(); - - if (key.isKeyCode (KeyPress::leftKey) - || key.isKeyCode (KeyPress::upKey)) - { - newTransaction(); - - int newPos; - - if (isMultiLine() && key.isKeyCode (KeyPress::upKey)) - newPos = indexAtPosition (cursorX, cursorY - 1); - else if (moveInWholeWordSteps) - newPos = findWordBreakBefore (getCaretPosition()); - else - newPos = getCaretPosition() - 1; - - moveCursorTo (newPos, key.getModifiers().isShiftDown()); - } - else if (key.isKeyCode (KeyPress::rightKey) - || key.isKeyCode (KeyPress::downKey)) - { - newTransaction(); - - int newPos; - - if (isMultiLine() && key.isKeyCode (KeyPress::downKey)) - newPos = indexAtPosition (cursorX, cursorY + cursorHeight + 1); - else if (moveInWholeWordSteps) - newPos = findWordBreakAfter (getCaretPosition()); - else - newPos = getCaretPosition() + 1; - - moveCursorTo (newPos, key.getModifiers().isShiftDown()); - } - else if (key.isKeyCode (KeyPress::pageDownKey) && isMultiLine()) - { - newTransaction(); - - moveCursorTo (indexAtPosition (cursorX, cursorY + cursorHeight + viewport->getViewHeight()), - key.getModifiers().isShiftDown()); - } - else if (key.isKeyCode (KeyPress::pageUpKey) && isMultiLine()) - { - newTransaction(); - - moveCursorTo (indexAtPosition (cursorX, cursorY - viewport->getViewHeight()), - key.getModifiers().isShiftDown()); - } - else if (key.isKeyCode (KeyPress::homeKey)) - { - newTransaction(); - - if (isMultiLine() && ! moveInWholeWordSteps) - moveCursorTo (indexAtPosition (0.0f, cursorY), - key.getModifiers().isShiftDown()); - else - moveCursorTo (0, key.getModifiers().isShiftDown()); - } - else if (key.isKeyCode (KeyPress::endKey)) - { - newTransaction(); - - if (isMultiLine() && ! moveInWholeWordSteps) - moveCursorTo (indexAtPosition ((float) textHolder->getWidth(), cursorY), - key.getModifiers().isShiftDown()); - else - moveCursorTo (getTotalNumChars(), key.getModifiers().isShiftDown()); - } - else if (key.isKeyCode (KeyPress::backspaceKey)) - { - if (moveInWholeWordSteps) - { - moveCursorTo (findWordBreakBefore (getCaretPosition()), true); - } - else - { - if (selectionStart == selectionEnd && selectionStart > 0) - --selectionStart; - } - - cut(); - } - else if (key.isKeyCode (KeyPress::deleteKey)) - { - if (key.getModifiers().isShiftDown()) - copy(); - - if (selectionStart == selectionEnd - && selectionEnd < getTotalNumChars()) - { - ++selectionEnd; - } - - cut(); - } - else if (key == KeyPress (T('c'), ModifierKeys::commandModifier, 0) - || key == KeyPress (KeyPress::insertKey, ModifierKeys::ctrlModifier, 0)) - { - newTransaction(); - copy(); - } - else if (key == KeyPress (T('x'), ModifierKeys::commandModifier, 0)) - { - newTransaction(); - copy(); - cut(); - } - else if (key == KeyPress (T('v'), ModifierKeys::commandModifier, 0) - || key == KeyPress (KeyPress::insertKey, ModifierKeys::shiftModifier, 0)) - { - newTransaction(); - paste(); - } - else if (key == KeyPress (T('z'), ModifierKeys::commandModifier, 0)) - { - newTransaction(); - doUndoRedo (false); - } - else if (key == KeyPress (T('y'), ModifierKeys::commandModifier, 0)) - { - newTransaction(); - doUndoRedo (true); - } - else if (key == KeyPress (T('a'), ModifierKeys::commandModifier, 0)) - { - newTransaction(); - moveCursorTo (getTotalNumChars(), false); - moveCursorTo (0, true); - } - else if (key == KeyPress::returnKey) - { - newTransaction(); - - if (returnKeyStartsNewLine) - insertTextAtCursor (T("\n")); - else - returnPressed(); - } - else if (key.isKeyCode (KeyPress::escapeKey)) - { - newTransaction(); - moveCursorTo (getCaretPosition(), false); - escapePressed(); - } - else if (key.getTextCharacter() >= ' ' - || (tabKeyUsed && (key.getTextCharacter() == '\t'))) - { - insertTextAtCursor (String::charToString (key.getTextCharacter())); - - lastTransactionTime = Time::getApproximateMillisecondCounter(); - } - else - { - return false; - } - - return true; -} - -bool TextEditor::keyStateChanged() -{ - // (overridden to avoid forwarding key events to the parent) - return true; -} - -//============================================================================== -const int baseMenuItemID = 0x7fff0000; - -void TextEditor::addPopupMenuItems (PopupMenu& m, const MouseEvent*) -{ - const bool writable = ! isReadOnly(); - - m.addItem (baseMenuItemID + 1, TRANS("cut"), writable); - m.addItem (baseMenuItemID + 2, TRANS("copy"), selectionStart < selectionEnd); - m.addItem (baseMenuItemID + 3, TRANS("paste"), writable); - m.addItem (baseMenuItemID + 4, TRANS("delete"), writable); - m.addSeparator(); - m.addItem (baseMenuItemID + 5, TRANS("select all")); - m.addSeparator(); - m.addItem (baseMenuItemID + 6, TRANS("undo"), undoManager.canUndo()); - m.addItem (baseMenuItemID + 7, TRANS("redo"), undoManager.canRedo()); -} - -void TextEditor::performPopupMenuAction (const int menuItemID) -{ - switch (menuItemID) - { - case baseMenuItemID + 1: - copy(); - cut(); - break; - - case baseMenuItemID + 2: - copy(); - break; - - case baseMenuItemID + 3: - paste(); - break; - - case baseMenuItemID + 4: - cut(); - break; - - case baseMenuItemID + 5: - moveCursorTo (getTotalNumChars(), false); - moveCursorTo (0, true); - break; - - case baseMenuItemID + 6: - doUndoRedo (false); - break; - - case baseMenuItemID + 7: - doUndoRedo (true); - break; - - default: - break; - } -} - -//============================================================================== -void TextEditor::focusGained (FocusChangeType) -{ - newTransaction(); - - caretFlashState = true; - - if (selectAllTextWhenFocused) - { - moveCursorTo (0, false); - moveCursorTo (getTotalNumChars(), true); - } - - repaint(); - - if (caretVisible) - textHolder->startTimer (flashSpeedIntervalMs); - - ComponentPeer* const peer = getPeer(); - if (peer != 0) - peer->textInputRequired (getScreenX() - peer->getScreenX(), - getScreenY() - peer->getScreenY()); -} - -void TextEditor::focusLost (FocusChangeType) -{ - newTransaction(); - - wasFocused = false; - textHolder->stopTimer(); - caretFlashState = false; - - postCommandMessage (focusLossMessageId); - repaint(); -} - -//============================================================================== -void TextEditor::resized() -{ - viewport->setBoundsInset (borderSize); - viewport->setSingleStepSizes (16, roundFloatToInt (currentFont.getHeight())); - - updateTextHolderSize(); - - if (! isMultiLine()) - { - scrollToMakeSureCursorIsVisible(); - } - else - { - updateCaretPosition(); - } -} - -void TextEditor::handleCommandMessage (const int commandId) -{ - const ComponentDeletionWatcher deletionChecker (this); - - for (int i = listeners.size(); --i >= 0;) - { - TextEditorListener* const tl = (TextEditorListener*) listeners [i]; - - if (tl != 0) - { - switch (commandId) - { - case textChangeMessageId: - tl->textEditorTextChanged (*this); - break; - - case returnKeyMessageId: - tl->textEditorReturnKeyPressed (*this); - break; - - case escapeKeyMessageId: - tl->textEditorEscapeKeyPressed (*this); - break; - - case focusLossMessageId: - tl->textEditorFocusLost (*this); - break; - - default: - jassertfalse - break; - } - - if (i > 0 && deletionChecker.hasBeenDeleted()) - return; - } - } -} - -void TextEditor::enablementChanged() -{ - setMouseCursor (MouseCursor (isReadOnly() ? MouseCursor::NormalCursor - : MouseCursor::IBeamCursor)); - repaint(); -} - -//============================================================================== -void TextEditor::clearInternal (UndoManager* const um) throw() -{ - remove (0, getTotalNumChars(), um, caretPosition); -} - -void TextEditor::insert (const String& text, - const int insertIndex, - const Font& font, - const Colour& colour, - UndoManager* const um, - const int caretPositionToMoveTo) throw() -{ - if (text.isNotEmpty()) - { - if (um != 0) - { - um->perform (new TextEditorInsertAction (*this, - text, - insertIndex, - font, - colour, - caretPosition, - caretPositionToMoveTo)); - } - else - { - repaintText (insertIndex, -1); // must do this before and after changing the data, in case - // a line gets moved due to word wrap - - int index = 0; - int nextIndex = 0; - - for (int i = 0; i < sections.size(); ++i) - { - nextIndex = index + ((UniformTextSection*) sections.getUnchecked(i))->getTotalLength(); - - if (insertIndex == index) - { - sections.insert (i, new UniformTextSection (text, - font, colour, - passwordCharacter)); - break; - } - else if (insertIndex > index && insertIndex < nextIndex) - { - splitSection (i, insertIndex - index); - sections.insert (i + 1, new UniformTextSection (text, - font, colour, - passwordCharacter)); - break; - } - - index = nextIndex; - } - - if (nextIndex == insertIndex) - sections.add (new UniformTextSection (text, - font, colour, - passwordCharacter)); - - coalesceSimilarSections(); - totalNumChars = -1; - - moveCursorTo (caretPositionToMoveTo, false); - - repaintText (insertIndex, -1); - } - } -} - -void TextEditor::reinsert (const int insertIndex, - const VoidArray& sectionsToInsert) throw() -{ - int index = 0; - int nextIndex = 0; - - for (int i = 0; i < sections.size(); ++i) - { - nextIndex = index + ((UniformTextSection*) sections.getUnchecked(i))->getTotalLength(); - - if (insertIndex == index) - { - for (int j = sectionsToInsert.size(); --j >= 0;) - sections.insert (i, new UniformTextSection (*(UniformTextSection*) sectionsToInsert.getUnchecked(j))); - - break; - } - else if (insertIndex > index && insertIndex < nextIndex) - { - splitSection (i, insertIndex - index); - - for (int j = sectionsToInsert.size(); --j >= 0;) - sections.insert (i + 1, new UniformTextSection (*(UniformTextSection*) sectionsToInsert.getUnchecked(j))); - - break; - } - - index = nextIndex; - } - - if (nextIndex == insertIndex) - { - for (int j = 0; j < sectionsToInsert.size(); ++j) - sections.add (new UniformTextSection (*(UniformTextSection*) sectionsToInsert.getUnchecked(j))); - } - - coalesceSimilarSections(); - totalNumChars = -1; -} - -void TextEditor::remove (const int startIndex, - int endIndex, - UndoManager* const um, - const int caretPositionToMoveTo) throw() -{ - if (endIndex > startIndex) - { - int index = 0; - - for (int i = 0; i < sections.size(); ++i) - { - const int nextIndex = index + ((UniformTextSection*) sections[i])->getTotalLength(); - - if (startIndex > index && startIndex < nextIndex) - { - splitSection (i, startIndex - index); - --i; - } - else if (endIndex > index && endIndex < nextIndex) - { - splitSection (i, endIndex - index); - --i; - } - else - { - index = nextIndex; - - if (index > endIndex) - break; - } - } - - index = 0; - - if (um != 0) - { - VoidArray removedSections; - - for (int i = 0; i < sections.size(); ++i) - { - if (endIndex <= startIndex) - break; - - UniformTextSection* const section = (UniformTextSection*) sections.getUnchecked (i); - - const int nextIndex = index + section->getTotalLength(); - - if (startIndex <= index && endIndex >= nextIndex) - removedSections.add (new UniformTextSection (*section)); - - index = nextIndex; - } - - um->perform (new TextEditorRemoveAction (*this, - startIndex, - endIndex, - caretPosition, - caretPositionToMoveTo, - removedSections)); - } - else - { - for (int i = 0; i < sections.size(); ++i) - { - if (endIndex <= startIndex) - break; - - UniformTextSection* const section = (UniformTextSection*) sections.getUnchecked (i); - - const int nextIndex = index + section->getTotalLength(); - - if (startIndex <= index && endIndex >= nextIndex) - { - sections.remove(i); - endIndex -= (nextIndex - index); - section->clear(); - delete section; - --i; - } - else - { - index = nextIndex; - } - } - - coalesceSimilarSections(); - totalNumChars = -1; - - moveCursorTo (caretPositionToMoveTo, false); - - repaintText (startIndex, -1); - } - } -} - -//============================================================================== -const String TextEditor::getText() const throw() -{ - String t; - - for (int i = 0; i < sections.size(); ++i) - t += ((const UniformTextSection*) sections.getUnchecked(i))->getAllText(); - - return t; -} - -const String TextEditor::getTextSubstring (const int startCharacter, const int endCharacter) const throw() -{ - String t; - int index = 0; - - for (int i = 0; i < sections.size(); ++i) - { - const UniformTextSection* const s = (const UniformTextSection*) sections.getUnchecked(i); - const int nextIndex = index + s->getTotalLength(); - - if (startCharacter < nextIndex) - { - if (endCharacter <= index) - break; - - const int start = jmax (index, startCharacter); - t += s->getTextSubstring (start - index, endCharacter - index); - } - - index = nextIndex; - } - - return t; -} - -const String TextEditor::getHighlightedText() const throw() -{ - return getTextSubstring (getHighlightedRegionStart(), - getHighlightedRegionStart() + getHighlightedRegionLength()); -} - -int TextEditor::getTotalNumChars() throw() -{ - if (totalNumChars < 0) - { - totalNumChars = 0; - - for (int i = sections.size(); --i >= 0;) - totalNumChars += ((const UniformTextSection*) sections.getUnchecked(i))->getTotalLength(); - } - - return totalNumChars; -} - -bool TextEditor::isEmpty() const throw() -{ - if (totalNumChars != 0) - { - for (int i = sections.size(); --i >= 0;) - if (((const UniformTextSection*) sections.getUnchecked(i))->getTotalLength() > 0) - return false; - } - - return true; -} - -void TextEditor::getCharPosition (const int index, float& cx, float& cy, float& lineHeight) const throw() -{ - const float wordWrapWidth = getWordWrapWidth(); - - if (wordWrapWidth > 0 && sections.size() > 0) - { - TextEditorIterator i (sections, wordWrapWidth, passwordCharacter); - - i.getCharPosition (index, cx, cy, lineHeight); - } - else - { - cx = cy = 0; - lineHeight = currentFont.getHeight(); - } -} - -int TextEditor::indexAtPosition (const float x, const float y) throw() -{ - const float wordWrapWidth = getWordWrapWidth(); - - if (wordWrapWidth > 0) - { - TextEditorIterator i (sections, wordWrapWidth, passwordCharacter); - - while (i.next()) - { - if (i.lineY + getHeight() > y) - i.updateLineHeight(); - - if (i.lineY + i.lineHeight > y) - { - if (i.lineY > y) - return jmax (0, i.indexInText - 1); - - if (i.atomX >= x) - return i.indexInText; - - if (x < i.atomRight) - return i.xToIndex (x); - } - } - } - - return getTotalNumChars(); -} - -//============================================================================== -static int getCharacterCategory (const tchar character) throw() -{ - return CharacterFunctions::isLetterOrDigit (character) - ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1); -} - -int TextEditor::findWordBreakAfter (const int position) const throw() -{ - const String t (getTextSubstring (position, position + 512)); - const int totalLength = t.length(); - int i = 0; - - while (i < totalLength && CharacterFunctions::isWhitespace (t[i])) - ++i; - - const int type = getCharacterCategory (t[i]); - - while (i < totalLength && type == getCharacterCategory (t[i])) - ++i; - - while (i < totalLength && CharacterFunctions::isWhitespace (t[i])) - ++i; - - return position + i; -} - -int TextEditor::findWordBreakBefore (const int position) const throw() -{ - if (position <= 0) - return 0; - - const int startOfBuffer = jmax (0, position - 512); - const String t (getTextSubstring (startOfBuffer, position)); - - int i = position - startOfBuffer; - - while (i > 0 && CharacterFunctions::isWhitespace (t [i - 1])) - --i; - - if (i > 0) - { - const int type = getCharacterCategory (t [i - 1]); - - while (i > 0 && type == getCharacterCategory (t [i - 1])) - --i; - } - - jassert (startOfBuffer + i >= 0); - return startOfBuffer + i; -} - - -//============================================================================== -void TextEditor::splitSection (const int sectionIndex, - const int charToSplitAt) throw() -{ - jassert (sections[sectionIndex] != 0); - - sections.insert (sectionIndex + 1, - ((UniformTextSection*) sections.getUnchecked (sectionIndex)) - ->split (charToSplitAt, passwordCharacter)); -} - -void TextEditor::coalesceSimilarSections() throw() -{ - for (int i = 0; i < sections.size() - 1; ++i) - { - UniformTextSection* const s1 = (UniformTextSection*) (sections.getUnchecked (i)); - UniformTextSection* const s2 = (UniformTextSection*) (sections.getUnchecked (i + 1)); - - if (s1->font == s2->font - && s1->colour == s2->colour) - { - s1->append (*s2, passwordCharacter); - sections.remove (i + 1); - delete s2; - --i; - } - } -} - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_TextEditor.h" +#include "../../graphics/fonts/juce_GlyphArrangement.h" +#include "../../../application/juce_SystemClipboard.h" +#include "../../../../juce_core/basics/juce_Time.h" +#include "../../../../juce_core/text/juce_LocalisedStrings.h" +#include "../lookandfeel/juce_LookAndFeel.h" + +#define SHOULD_WRAP(x, wrapwidth) (((x) - 0.0001f) >= (wrapwidth)) + +//============================================================================== +// a word or space that can't be broken down any further +struct TextAtom +{ + //============================================================================== + String atomText; + float width; + uint16 numChars; + + //============================================================================== + bool isWhitespace() const throw() { return CharacterFunctions::isWhitespace (atomText[0]); } + bool isNewLine() const throw() { return atomText[0] == T('\r') || atomText[0] == T('\n'); } + + const String getText (const tchar passwordCharacter) const throw() + { + if (passwordCharacter == 0) + return atomText; + else + return String::repeatedString (String::charToString (passwordCharacter), + atomText.length()); + } + + const String getTrimmedText (const tchar passwordCharacter) const throw() + { + if (passwordCharacter == 0) + return atomText.substring (0, numChars); + else if (isNewLine()) + return String::empty; + else + return String::repeatedString (String::charToString (passwordCharacter), numChars); + } +}; + +//============================================================================== +// a run of text with a single font and colour +class UniformTextSection +{ +public: + //============================================================================== + UniformTextSection (const String& text, + const Font& font_, + const Colour& colour_, + const tchar passwordCharacter) throw() + : font (font_), + colour (colour_), + atoms (64) + { + initialiseAtoms (text, passwordCharacter); + } + + UniformTextSection (const UniformTextSection& other) throw() + : font (other.font), + colour (other.colour), + atoms (64) + { + for (int i = 0; i < other.atoms.size(); ++i) + atoms.add (new TextAtom (*(const TextAtom*) other.atoms.getUnchecked(i))); + } + + ~UniformTextSection() throw() + { + // (no need to delete the atoms, as they're explicitly deleted by the caller) + } + + void clear() throw() + { + for (int i = atoms.size(); --i >= 0;) + { + TextAtom* const atom = getAtom(i); + delete atom; + } + + atoms.clear(); + } + + int getNumAtoms() const throw() + { + return atoms.size(); + } + + TextAtom* getAtom (const int index) const throw() + { + return (TextAtom*) atoms.getUnchecked (index); + } + + void append (const UniformTextSection& other, const tchar passwordCharacter) throw() + { + if (other.atoms.size() > 0) + { + TextAtom* const lastAtom = (TextAtom*) atoms.getLast(); + int i = 0; + + if (lastAtom != 0) + { + if (! CharacterFunctions::isWhitespace (lastAtom->atomText.getLastCharacter())) + { + TextAtom* const first = other.getAtom(0); + + if (! CharacterFunctions::isWhitespace (first->atomText[0])) + { + lastAtom->atomText += first->atomText; + lastAtom->numChars = (uint16) (lastAtom->numChars + first->numChars); + lastAtom->width = font.getStringWidthFloat (lastAtom->getText (passwordCharacter)); + delete first; + ++i; + } + } + } + + while (i < other.atoms.size()) + { + atoms.add (other.getAtom(i)); + ++i; + } + } + } + + UniformTextSection* split (const int indexToBreakAt, + const tchar passwordCharacter) throw() + { + UniformTextSection* const section2 = new UniformTextSection (String::empty, + font, colour, + passwordCharacter); + int index = 0; + + for (int i = 0; i < atoms.size(); ++i) + { + TextAtom* const atom = getAtom(i); + + const int nextIndex = index + atom->numChars; + + if (index == indexToBreakAt) + { + int j; + for (j = i; j < atoms.size(); ++j) + section2->atoms.add (getAtom (j)); + + for (j = atoms.size(); --j >= i;) + atoms.remove (j); + + break; + } + else if (indexToBreakAt >= index && indexToBreakAt < nextIndex) + { + TextAtom* const secondAtom = new TextAtom(); + + secondAtom->atomText = atom->atomText.substring (indexToBreakAt - index); + secondAtom->width = font.getStringWidthFloat (secondAtom->getText (passwordCharacter)); + secondAtom->numChars = (uint16) secondAtom->atomText.length(); + + section2->atoms.add (secondAtom); + + atom->atomText = atom->atomText.substring (0, indexToBreakAt - index); + atom->width = font.getStringWidthFloat (atom->getText (passwordCharacter)); + atom->numChars = (uint16) (indexToBreakAt - index); + + int j; + for (j = i + 1; j < atoms.size(); ++j) + section2->atoms.add (getAtom (j)); + + for (j = atoms.size(); --j > i;) + atoms.remove (j); + + break; + } + + index = nextIndex; + } + + return section2; + } + + const String getAllText() const throw() + { + String s; + s.preallocateStorage (getTotalLength()); + + tchar* endOfString = (tchar*) &(s[0]); + + for (int i = 0; i < atoms.size(); ++i) + { + const TextAtom* const atom = getAtom(i); + + memcpy (endOfString, &(atom->atomText[0]), atom->numChars * sizeof (tchar)); + endOfString += atom->numChars; + } + + *endOfString = 0; + + jassert ((endOfString - (tchar*) &(s[0])) <= getTotalLength()); + return s; + } + + const String getTextSubstring (const int startCharacter, + const int endCharacter) const throw() + { + int index = 0; + int totalLen = 0; + int i; + + for (i = 0; i < atoms.size(); ++i) + { + const TextAtom* const atom = getAtom (i); + const int nextIndex = index + atom->numChars; + + if (startCharacter < nextIndex) + { + if (endCharacter <= index) + break; + + const int start = jmax (0, startCharacter - index); + const int end = jmin (endCharacter - index, atom->numChars); + jassert (end >= start); + + totalLen += end - start; + } + + index = nextIndex; + } + + String s; + s.preallocateStorage (totalLen + 1); + tchar* psz = (tchar*) (const tchar*) s; + + index = 0; + + for (i = 0; i < atoms.size(); ++i) + { + const TextAtom* const atom = getAtom (i); + const int nextIndex = index + atom->numChars; + + if (startCharacter < nextIndex) + { + if (endCharacter <= index) + break; + + const int start = jmax (0, startCharacter - index); + const int len = jmin (endCharacter - index, atom->numChars) - start; + + memcpy (psz, ((const tchar*) atom->atomText) + start, len * sizeof (tchar)); + psz += len; + *psz = 0; + } + + index = nextIndex; + } + + return s; + } + + int getTotalLength() const throw() + { + int c = 0; + + for (int i = atoms.size(); --i >= 0;) + c += getAtom(i)->numChars; + + return c; + } + + void setFont (const Font& newFont, + const tchar passwordCharacter) throw() + { + if (font != newFont) + { + font = newFont; + + for (int i = atoms.size(); --i >= 0;) + { + TextAtom* const atom = (TextAtom*) atoms.getUnchecked(i); + atom->width = newFont.getStringWidthFloat (atom->getText (passwordCharacter)); + } + } + } + + //============================================================================== + juce_UseDebuggingNewOperator + + Font font; + Colour colour; + +private: + VoidArray atoms; + + //============================================================================== + void initialiseAtoms (const String& textToParse, + const tchar passwordCharacter) throw() + { + int i = 0; + const int len = textToParse.length(); + const tchar* const text = (const tchar*) textToParse; + + while (i < len) + { + int start = i; + + // create a whitespace atom unless it starts with non-ws + if (CharacterFunctions::isWhitespace (text[i]) + && text[i] != T('\r') + && text[i] != T('\n')) + { + while (i < len + && CharacterFunctions::isWhitespace (text[i]) + && text[i] != T('\r') + && text[i] != T('\n')) + { + ++i; + } + } + else + { + if (text[i] == T('\r')) + { + ++i; + + if ((i < len) && (text[i] == T('\n'))) + { + ++start; + ++i; + } + } + else if (text[i] == T('\n')) + { + ++i; + } + else + { + while ((i < len) && ! CharacterFunctions::isWhitespace (text[i])) + ++i; + } + } + + TextAtom* const atom = new TextAtom(); + atom->atomText = String (text + start, i - start); + + atom->width = font.getStringWidthFloat (atom->getText (passwordCharacter)); + atom->numChars = (uint16) (i - start); + + atoms.add (atom); + } + } + + const UniformTextSection& operator= (const UniformTextSection& other); +}; + +//============================================================================== +class TextEditorIterator +{ +public: + //============================================================================== + TextEditorIterator (const VoidArray& sections_, + const float wordWrapWidth_, + const tchar passwordCharacter_) throw() + : indexInText (0), + lineY (0), + lineHeight (0), + maxDescent (0), + atomX (0), + atomRight (0), + atom (0), + currentSection (0), + sections (sections_), + sectionIndex (0), + atomIndex (0), + wordWrapWidth (wordWrapWidth_), + passwordCharacter (passwordCharacter_) + { + jassert (wordWrapWidth_ > 0); + + if (sections.size() > 0) + currentSection = (const UniformTextSection*) sections.getUnchecked (sectionIndex); + + if (currentSection != 0) + { + lineHeight = currentSection->font.getHeight(); + maxDescent = currentSection->font.getDescent(); + } + } + + TextEditorIterator (const TextEditorIterator& other) throw() + : indexInText (other.indexInText), + lineY (other.lineY), + lineHeight (other.lineHeight), + maxDescent (other.maxDescent), + atomX (other.atomX), + atomRight (other.atomRight), + atom (other.atom), + currentSection (other.currentSection), + sections (other.sections), + sectionIndex (other.sectionIndex), + atomIndex (other.atomIndex), + wordWrapWidth (other.wordWrapWidth), + passwordCharacter (other.passwordCharacter), + tempAtom (other.tempAtom) + { + } + + ~TextEditorIterator() throw() + { + } + + //============================================================================== + bool next() throw() + { + if (atom == &tempAtom) + { + const int numRemaining = tempAtom.atomText.length() - tempAtom.numChars; + + if (numRemaining > 0) + { + tempAtom.atomText = tempAtom.atomText.substring (tempAtom.numChars); + + atomX = 0; + + if (tempAtom.numChars > 0) + lineY += lineHeight; + + indexInText += tempAtom.numChars; + + GlyphArrangement g; + g.addLineOfText (currentSection->font, atom->getText (passwordCharacter), 0.0f, 0.0f); + + int split; + for (split = 0; split < g.getNumGlyphs(); ++split) + if (SHOULD_WRAP (g.getGlyph (split).getRight(), wordWrapWidth)) + break; + + if (split > 0 && split <= numRemaining) + { + tempAtom.numChars = (uint16) split; + tempAtom.width = g.getGlyph (split - 1).getRight(); + atomRight = atomX + tempAtom.width; + return true; + } + } + } + + bool forceNewLine = false; + + if (sectionIndex >= sections.size()) + { + moveToEndOfLastAtom(); + return false; + } + else if (atomIndex >= currentSection->getNumAtoms() - 1) + { + if (atomIndex >= currentSection->getNumAtoms()) + { + if (++sectionIndex >= sections.size()) + { + moveToEndOfLastAtom(); + return false; + } + + atomIndex = 0; + currentSection = (const UniformTextSection*) sections.getUnchecked (sectionIndex); + + lineHeight = jmax (lineHeight, currentSection->font.getHeight()); + maxDescent = jmax (maxDescent, currentSection->font.getDescent()); + } + else + { + const TextAtom* const lastAtom = currentSection->getAtom (atomIndex); + + if (! lastAtom->isWhitespace()) + { + // handle the case where the last atom in a section is actually part of the same + // word as the first atom of the next section... + float right = atomRight + lastAtom->width; + float lineHeight2 = lineHeight; + float maxDescent2 = maxDescent; + + for (int section = sectionIndex + 1; section < sections.size(); ++section) + { + const UniformTextSection* const s = (const UniformTextSection*) sections.getUnchecked (section); + + if (s->getNumAtoms() == 0) + break; + + const TextAtom* const nextAtom = s->getAtom (0); + + if (nextAtom->isWhitespace()) + break; + + right += nextAtom->width; + + lineHeight2 = jmax (lineHeight2, s->font.getHeight()); + maxDescent2 = jmax (maxDescent2, s->font.getDescent()); + + if (SHOULD_WRAP (right, wordWrapWidth)) + { + lineHeight = lineHeight2; + maxDescent = maxDescent2; + + forceNewLine = true; + break; + } + + if (s->getNumAtoms() > 1) + break; + } + } + } + } + + if (atom != 0) + { + atomX = atomRight; + indexInText += atom->numChars; + + if (atom->isNewLine()) + { + atomX = 0; + lineY += lineHeight; + } + } + + atom = currentSection->getAtom (atomIndex); + atomRight = atomX + atom->width; + ++atomIndex; + + if (SHOULD_WRAP (atomRight, wordWrapWidth) || forceNewLine) + { + if (atom->isWhitespace()) + { + // leave whitespace at the end of a line, but truncate it to avoid scrolling + atomRight = jmin (atomRight, wordWrapWidth); + } + else + { + return wrapCurrentAtom(); + } + } + + return true; + } + + bool wrapCurrentAtom() throw() + { + atomRight = atom->width; + + if (SHOULD_WRAP (atomRight, wordWrapWidth)) // atom too big to fit on a line, so break it up.. + { + tempAtom = *atom; + tempAtom.width = 0; + tempAtom.numChars = 0; + atom = &tempAtom; + + if (atomX > 0) + { + atomX = 0; + lineY += lineHeight; + } + + return next(); + } + + atomX = 0; + lineY += lineHeight; + return true; + } + + //============================================================================== + void draw (Graphics& g, const UniformTextSection*& lastSection) const throw() + { + if (passwordCharacter != 0 || ! atom->isWhitespace()) + { + if (lastSection != currentSection) + { + lastSection = currentSection; + g.setColour (currentSection->colour); + g.setFont (currentSection->font); + } + + jassert (atom->getTrimmedText (passwordCharacter).isNotEmpty()); + + GlyphArrangement ga; + ga.addLineOfText (currentSection->font, + atom->getTrimmedText (passwordCharacter), + atomX, + (float) roundFloatToInt (lineY + lineHeight - maxDescent)); + ga.draw (g); + } + } + + void drawSelection (Graphics& g, + const int selectionStart, + const int selectionEnd) const throw() + { + const int startX = roundFloatToInt (indexToX (selectionStart)); + const int endX = roundFloatToInt (indexToX (selectionEnd)); + + const int y = roundFloatToInt (lineY); + const int nextY = roundFloatToInt (lineY + lineHeight); + + g.fillRect (startX, y, endX - startX, nextY - y); + } + + void drawSelectedText (Graphics& g, + const int selectionStart, + const int selectionEnd, + const Colour& selectedTextColour) const throw() + { + if (passwordCharacter != 0 || ! atom->isWhitespace()) + { + GlyphArrangement ga; + ga.addLineOfText (currentSection->font, + atom->getTrimmedText (passwordCharacter), + atomX, + (float) roundFloatToInt (lineY + lineHeight - maxDescent)); + + if (selectionEnd < indexInText + atom->numChars) + { + GlyphArrangement ga2 (ga); + ga2.removeRangeOfGlyphs (0, selectionEnd - indexInText); + ga.removeRangeOfGlyphs (selectionEnd - indexInText, -1); + + g.setColour (currentSection->colour); + ga2.draw (g); + } + + if (selectionStart > indexInText) + { + GlyphArrangement ga2 (ga); + ga2.removeRangeOfGlyphs (selectionStart - indexInText, -1); + ga.removeRangeOfGlyphs (0, selectionStart - indexInText); + + g.setColour (currentSection->colour); + ga2.draw (g); + } + + g.setColour (selectedTextColour); + ga.draw (g); + } + } + + //============================================================================== + float indexToX (const int indexToFind) const throw() + { + if (indexToFind <= indexInText) + return atomX; + + if (indexToFind >= indexInText + atom->numChars) + return atomRight; + + GlyphArrangement g; + g.addLineOfText (currentSection->font, + atom->getText (passwordCharacter), + atomX, 0.0f); + + return jmin (atomRight, g.getGlyph (indexToFind - indexInText).getLeft()); + } + + int xToIndex (const float xToFind) const throw() + { + if (xToFind <= atomX || atom->isNewLine()) + return indexInText; + + if (xToFind >= atomRight) + return indexInText + atom->numChars; + + GlyphArrangement g; + g.addLineOfText (currentSection->font, + atom->getText (passwordCharacter), + atomX, 0.0f); + + int j; + for (j = 0; j < atom->numChars; ++j) + if ((g.getGlyph(j).getLeft() + g.getGlyph(j).getRight()) / 2 > xToFind) + break; + + return indexInText + j; + } + + //============================================================================== + void updateLineHeight() throw() + { + float x = atomRight; + + int tempSectionIndex = sectionIndex; + int tempAtomIndex = atomIndex; + const UniformTextSection* currentSection = (const UniformTextSection*) sections.getUnchecked (tempSectionIndex); + + while (! SHOULD_WRAP (x, wordWrapWidth)) + { + if (tempSectionIndex >= sections.size()) + break; + + bool checkSize = false; + + if (tempAtomIndex >= currentSection->getNumAtoms()) + { + if (++tempSectionIndex >= sections.size()) + break; + + tempAtomIndex = 0; + currentSection = (const UniformTextSection*) sections.getUnchecked (tempSectionIndex); + checkSize = true; + } + + const TextAtom* const atom = currentSection->getAtom (tempAtomIndex); + + if (atom == 0) + break; + + x += atom->width; + + if (SHOULD_WRAP (x, wordWrapWidth) || atom->isNewLine()) + break; + + if (checkSize) + { + lineHeight = jmax (lineHeight, currentSection->font.getHeight()); + maxDescent = jmax (maxDescent, currentSection->font.getDescent()); + } + + ++tempAtomIndex; + } + } + + bool getCharPosition (const int index, float& cx, float& cy, float& lineHeight_) throw() + { + while (next()) + { + if (indexInText + atom->numChars >= index) + { + updateLineHeight(); + + if (indexInText + atom->numChars > index) + { + cx = indexToX (index); + cy = lineY; + lineHeight_ = lineHeight; + return true; + } + } + } + + cx = atomX; + cy = lineY; + lineHeight_ = lineHeight; + return false; + } + + //============================================================================== + juce_UseDebuggingNewOperator + + int indexInText; + float lineY, lineHeight, maxDescent; + float atomX, atomRight; + const TextAtom* atom; + const UniformTextSection* currentSection; + +private: + const VoidArray& sections; + int sectionIndex, atomIndex; + const float wordWrapWidth; + const tchar passwordCharacter; + TextAtom tempAtom; + + const TextEditorIterator& operator= (const TextEditorIterator&); + + void moveToEndOfLastAtom() throw() + { + if (atom != 0) + { + atomX = atomRight; + + if (atom->isNewLine()) + { + atomX = 0.0f; + lineY += lineHeight; + } + } + } +}; + + +//============================================================================== +class TextEditorInsertAction : public UndoableAction +{ + TextEditor& owner; + const String text; + const int insertIndex, oldCaretPos, newCaretPos; + const Font font; + const Colour colour; + + TextEditorInsertAction (const TextEditorInsertAction&); + const TextEditorInsertAction& operator= (const TextEditorInsertAction&); + +public: + TextEditorInsertAction (TextEditor& owner_, + const String& text_, + const int insertIndex_, + const Font& font_, + const Colour& colour_, + const int oldCaretPos_, + const int newCaretPos_) throw() + : owner (owner_), + text (text_), + insertIndex (insertIndex_), + oldCaretPos (oldCaretPos_), + newCaretPos (newCaretPos_), + font (font_), + colour (colour_) + { + } + + ~TextEditorInsertAction() + { + } + + bool perform() + { + owner.insert (text, insertIndex, font, colour, 0, newCaretPos); + return true; + } + + bool undo() + { + owner.remove (insertIndex, insertIndex + text.length(), 0, oldCaretPos); + return true; + } + + int getSizeInUnits() + { + return text.length() + 16; + } +}; + +//============================================================================== +class TextEditorRemoveAction : public UndoableAction +{ + TextEditor& owner; + const int startIndex, endIndex, oldCaretPos, newCaretPos; + VoidArray removedSections; + + TextEditorRemoveAction (const TextEditorRemoveAction&); + const TextEditorRemoveAction& operator= (const TextEditorRemoveAction&); + +public: + TextEditorRemoveAction (TextEditor& owner_, + const int startIndex_, + const int endIndex_, + const int oldCaretPos_, + const int newCaretPos_, + const VoidArray& removedSections_) throw() + : owner (owner_), + startIndex (startIndex_), + endIndex (endIndex_), + oldCaretPos (oldCaretPos_), + newCaretPos (newCaretPos_), + removedSections (removedSections_) + { + } + + ~TextEditorRemoveAction() + { + for (int i = removedSections.size(); --i >= 0;) + { + UniformTextSection* const section = (UniformTextSection*) removedSections.getUnchecked (i); + section->clear(); + delete section; + } + } + + bool perform() + { + owner.remove (startIndex, endIndex, 0, newCaretPos); + return true; + } + + bool undo() + { + owner.reinsert (startIndex, removedSections); + owner.moveCursorTo (oldCaretPos, false); + return true; + } + + int getSizeInUnits() + { + int n = 0; + + for (int i = removedSections.size(); --i >= 0;) + { + UniformTextSection* const section = (UniformTextSection*) removedSections.getUnchecked (i); + n += section->getTotalLength(); + } + + return n + 16; + } +}; + +//============================================================================== +class TextHolderComponent : public Component, + public Timer +{ + TextEditor* const owner; + + TextHolderComponent (const TextHolderComponent&); + const TextHolderComponent& operator= (const TextHolderComponent&); + +public: + TextHolderComponent (TextEditor* const owner_) + : owner (owner_) + { + setWantsKeyboardFocus (false); + setInterceptsMouseClicks (false, true); + } + + ~TextHolderComponent() + { + } + + void paint (Graphics& g) + { + owner->drawContent (g); + } + + void timerCallback() + { + owner->timerCallbackInt(); + } + + const MouseCursor getMouseCursor() + { + return owner->getMouseCursor(); + } +}; + +//============================================================================== +class TextEditorViewport : public Viewport +{ + TextEditor* const owner; + float lastWordWrapWidth; + + TextEditorViewport (const TextEditorViewport&); + const TextEditorViewport& operator= (const TextEditorViewport&); + +public: + TextEditorViewport (TextEditor* const owner_) + : owner (owner_), + lastWordWrapWidth (0) + { + } + + ~TextEditorViewport() + { + } + + void visibleAreaChanged (int, int, int, int) + { + const float wordWrapWidth = owner->getWordWrapWidth(); + + if (wordWrapWidth != lastWordWrapWidth) + { + lastWordWrapWidth = wordWrapWidth; + owner->updateTextHolderSize(); + } + } +}; + +//============================================================================== +const int flashSpeedIntervalMs = 380; + +const int textChangeMessageId = 0x10003001; +const int returnKeyMessageId = 0x10003002; +const int escapeKeyMessageId = 0x10003003; +const int focusLossMessageId = 0x10003004; + + +//============================================================================== +TextEditor::TextEditor (const String& name, + const tchar passwordCharacter_) + : Component (name), + borderSize (1, 1, 1, 3), + readOnly (false), + multiline (false), + wordWrap (false), + returnKeyStartsNewLine (false), + caretVisible (true), + popupMenuEnabled (true), + selectAllTextWhenFocused (false), + scrollbarVisible (true), + wasFocused (false), + caretFlashState (true), + keepCursorOnScreen (true), + tabKeyUsed (false), + menuActive (false), + cursorX (0), + cursorY (0), + cursorHeight (0), + maxTextLength (0), + selectionStart (0), + selectionEnd (0), + leftIndent (4), + topIndent (4), + lastTransactionTime (0), + currentFont (14.0f), + totalNumChars (0), + caretPosition (0), + sections (8), + passwordCharacter (passwordCharacter_), + dragType (notDragging), + listeners (2) +{ + setOpaque (true); + + addAndMakeVisible (viewport = new TextEditorViewport (this)); + viewport->setViewedComponent (textHolder = new TextHolderComponent (this)); + viewport->setWantsKeyboardFocus (false); + viewport->setScrollBarsShown (false, false); + + setMouseCursor (MouseCursor::IBeamCursor); + setWantsKeyboardFocus (true); +} + +TextEditor::~TextEditor() +{ + clearInternal (0); + delete viewport; +} + +//============================================================================== +void TextEditor::newTransaction() throw() +{ + lastTransactionTime = Time::getApproximateMillisecondCounter(); + undoManager.beginNewTransaction(); +} + +void TextEditor::doUndoRedo (const bool isRedo) +{ + if (! isReadOnly()) + { + if ((isRedo) ? undoManager.redo() + : undoManager.undo()) + { + scrollToMakeSureCursorIsVisible(); + repaint(); + textChanged(); + } + } +} + +//============================================================================== +void TextEditor::setMultiLine (const bool shouldBeMultiLine, + const bool shouldWordWrap) +{ + multiline = shouldBeMultiLine; + wordWrap = shouldWordWrap && shouldBeMultiLine; + + setScrollbarsShown (scrollbarVisible); + + viewport->setViewPosition (0, 0); + + resized(); + scrollToMakeSureCursorIsVisible(); +} + +bool TextEditor::isMultiLine() const throw() +{ + return multiline; +} + +void TextEditor::setScrollbarsShown (bool enabled) throw() +{ + scrollbarVisible = enabled; + + enabled = enabled && isMultiLine(); + + viewport->setScrollBarsShown (enabled, enabled); +} + +void TextEditor::setReadOnly (const bool shouldBeReadOnly) +{ + readOnly = shouldBeReadOnly; + enablementChanged(); +} + +bool TextEditor::isReadOnly() const throw() +{ + return readOnly || ! isEnabled(); +} + +void TextEditor::setReturnKeyStartsNewLine (const bool shouldStartNewLine) +{ + returnKeyStartsNewLine = shouldStartNewLine; +} + +void TextEditor::setTabKeyUsedAsCharacter (const bool shouldTabKeyBeUsed) throw() +{ + tabKeyUsed = shouldTabKeyBeUsed; +} + +void TextEditor::setPopupMenuEnabled (const bool b) throw() +{ + popupMenuEnabled = b; +} + +void TextEditor::setSelectAllWhenFocused (const bool b) throw() +{ + selectAllTextWhenFocused = b; +} + +//============================================================================== +const Font TextEditor::getFont() const throw() +{ + return currentFont; +} + +void TextEditor::setFont (const Font& newFont) throw() +{ + currentFont = newFont; + scrollToMakeSureCursorIsVisible(); +} + +void TextEditor::applyFontToAllText (const Font& newFont) +{ + currentFont = newFont; + + const Colour overallColour (findColour (textColourId)); + + for (int i = sections.size(); --i >= 0;) + { + UniformTextSection* const uts = (UniformTextSection*) sections.getUnchecked(i); + uts->setFont (newFont, passwordCharacter); + uts->colour = overallColour; + } + + coalesceSimilarSections(); + updateTextHolderSize(); + scrollToMakeSureCursorIsVisible(); + repaint(); +} + +void TextEditor::colourChanged() +{ + setOpaque (findColour (backgroundColourId).isOpaque()); + repaint(); +} + +void TextEditor::setCaretVisible (const bool shouldCaretBeVisible) throw() +{ + caretVisible = shouldCaretBeVisible; + + if (shouldCaretBeVisible) + textHolder->startTimer (flashSpeedIntervalMs); + + setMouseCursor (shouldCaretBeVisible ? MouseCursor::IBeamCursor + : MouseCursor::NormalCursor); +} + +void TextEditor::setInputRestrictions (const int maxLen, + const String& chars) throw() +{ + maxTextLength = jmax (0, maxLen); + allowedCharacters = chars; +} + +void TextEditor::setTextToShowWhenEmpty (const String& text, const Colour& colourToUse) throw() +{ + textToShowWhenEmpty = text; + colourForTextWhenEmpty = colourToUse; +} + +void TextEditor::setPasswordCharacter (const tchar newPasswordCharacter) throw() +{ + if (passwordCharacter != newPasswordCharacter) + { + passwordCharacter = newPasswordCharacter; + resized(); + repaint(); + } +} + +void TextEditor::setScrollBarThickness (const int newThicknessPixels) +{ + viewport->setScrollBarThickness (newThicknessPixels); +} + +void TextEditor::setScrollBarButtonVisibility (const bool buttonsVisible) +{ + viewport->setScrollBarButtonVisibility (buttonsVisible); +} + +//============================================================================== +void TextEditor::clear() +{ + clearInternal (0); + updateTextHolderSize(); + undoManager.clearUndoHistory(); +} + +void TextEditor::setText (const String& newText, + const bool sendTextChangeMessage) +{ + const int newLength = newText.length(); + + if (newLength != getTotalNumChars() || getText() != newText) + { + const int oldCursorPos = caretPosition; + const bool cursorWasAtEnd = oldCursorPos >= getTotalNumChars(); + + clearInternal (0); + insert (newText, 0, currentFont, findColour (textColourId), 0, caretPosition); + + // if you're adding text with line-feeds to a single-line text editor, it + // ain't gonna look right! + jassert (multiline || ! newText.containsAnyOf (T("\r\n"))); + + if (cursorWasAtEnd && ! isMultiLine()) + moveCursorTo (getTotalNumChars(), false); + else + moveCursorTo (oldCursorPos, false); + + if (sendTextChangeMessage) + textChanged(); + + repaint(); + } + + updateTextHolderSize(); + scrollToMakeSureCursorIsVisible(); + undoManager.clearUndoHistory(); +} + +//============================================================================== +void TextEditor::textChanged() throw() +{ + updateTextHolderSize(); + postCommandMessage (textChangeMessageId); +} + +void TextEditor::returnPressed() +{ + postCommandMessage (returnKeyMessageId); +} + +void TextEditor::escapePressed() +{ + postCommandMessage (escapeKeyMessageId); +} + +void TextEditor::addListener (TextEditorListener* const newListener) throw() +{ + jassert (newListener != 0) + + if (newListener != 0) + listeners.add (newListener); +} + +void TextEditor::removeListener (TextEditorListener* const listenerToRemove) throw() +{ + listeners.removeValue (listenerToRemove); +} + +//============================================================================== +void TextEditor::timerCallbackInt() +{ + const bool newState = (! caretFlashState) && ! isCurrentlyBlockedByAnotherModalComponent(); + + if (caretFlashState != newState) + { + caretFlashState = newState; + + if (caretFlashState) + wasFocused = true; + + if (caretVisible + && hasKeyboardFocus (false) + && ! isReadOnly()) + { + repaintCaret(); + } + } + + const unsigned int now = Time::getApproximateMillisecondCounter(); + + if (now > lastTransactionTime + 200) + newTransaction(); +} + +void TextEditor::repaintCaret() +{ + if (! findColour (caretColourId).isTransparent()) + repaint (borderSize.getLeft() + textHolder->getX() + leftIndent + roundFloatToInt (cursorX) - 1, + borderSize.getTop() + textHolder->getY() + topIndent + roundFloatToInt (cursorY) - 1, + 4, + roundFloatToInt (cursorHeight) + 2); +} + +void TextEditor::repaintText (int textStartIndex, int textEndIndex) +{ + if (textStartIndex > textEndIndex && textEndIndex > 0) + swapVariables (textStartIndex, textEndIndex); + + float x = 0, y = 0, lh = currentFont.getHeight(); + + const float wordWrapWidth = getWordWrapWidth(); + + if (wordWrapWidth > 0) + { + TextEditorIterator i (sections, wordWrapWidth, passwordCharacter); + + i.getCharPosition (textStartIndex, x, y, lh); + + const int y1 = (int) y; + int y2; + + if (textEndIndex >= 0) + { + i.getCharPosition (textEndIndex, x, y, lh); + y2 = (int) (y + lh * 2.0f); + } + else + { + y2 = textHolder->getHeight(); + } + + textHolder->repaint (0, y1, textHolder->getWidth(), y2 - y1); + } +} + +//============================================================================== +void TextEditor::moveCaret (int newCaretPos) throw() +{ + if (newCaretPos < 0) + newCaretPos = 0; + else if (newCaretPos > getTotalNumChars()) + newCaretPos = getTotalNumChars(); + + if (newCaretPos != getCaretPosition()) + { + repaintCaret(); + caretFlashState = true; + caretPosition = newCaretPos; + textHolder->startTimer (flashSpeedIntervalMs); + scrollToMakeSureCursorIsVisible(); + repaintCaret(); + } +} + +void TextEditor::setCaretPosition (const int newIndex) throw() +{ + moveCursorTo (newIndex, false); +} + +int TextEditor::getCaretPosition() const throw() +{ + return caretPosition; +} + +void TextEditor::scrollEditorToPositionCaret (const int desiredCaretX, + const int desiredCaretY) throw() + +{ + updateCaretPosition(); + + int vx = roundFloatToInt (cursorX) - desiredCaretX; + int vy = roundFloatToInt (cursorY) - desiredCaretY; + + if (desiredCaretX < jmax (1, proportionOfWidth (0.05f))) + { + vx += desiredCaretX - proportionOfWidth (0.2f); + } + else if (desiredCaretX > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10))) + { + vx += desiredCaretX + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth(); + } + + vx = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), vx); + + if (! isMultiLine()) + { + vy = viewport->getViewPositionY(); + } + else + { + vy = jlimit (0, jmax (0, textHolder->getHeight() - viewport->getMaximumVisibleHeight()), vy); + + const int curH = roundFloatToInt (cursorHeight); + + if (desiredCaretY < 0) + { + vy = jmax (0, desiredCaretY + vy); + } + else if (desiredCaretY > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - curH)) + { + vy += desiredCaretY + 2 + curH + topIndent - viewport->getMaximumVisibleHeight(); + } + } + + viewport->setViewPosition (vx, vy); +} + +const Rectangle TextEditor::getCaretRectangle() throw() +{ + updateCaretPosition(); + + return Rectangle (roundFloatToInt (cursorX) - viewport->getX(), + roundFloatToInt (cursorY) - viewport->getY(), + 1, roundFloatToInt (cursorHeight)); +} + +//============================================================================== +float TextEditor::getWordWrapWidth() const throw() +{ + return (wordWrap) ? (float) (viewport->getMaximumVisibleWidth() - leftIndent - leftIndent / 2) + : 1.0e10f; +} + +void TextEditor::updateTextHolderSize() throw() +{ + const float wordWrapWidth = getWordWrapWidth(); + + if (wordWrapWidth > 0) + { + float maxWidth = 0.0f; + + TextEditorIterator i (sections, wordWrapWidth, passwordCharacter); + + while (i.next()) + maxWidth = jmax (maxWidth, i.atomRight); + + const int w = leftIndent + roundFloatToInt (maxWidth); + const int h = topIndent + roundFloatToInt (jmax (i.lineY + i.lineHeight, + currentFont.getHeight())); + + textHolder->setSize (w + 1, h + 1); + } +} + +int TextEditor::getTextWidth() const throw() +{ + return textHolder->getWidth(); +} + +int TextEditor::getTextHeight() const throw() +{ + return textHolder->getHeight(); +} + +void TextEditor::setIndents (const int newLeftIndent, + const int newTopIndent) throw() +{ + leftIndent = newLeftIndent; + topIndent = newTopIndent; +} + +void TextEditor::setBorder (const BorderSize& border) throw() +{ + borderSize = border; + resized(); +} + +const BorderSize TextEditor::getBorder() const throw() +{ + return borderSize; +} + +void TextEditor::setScrollToShowCursor (const bool shouldScrollToShowCursor) throw() +{ + keepCursorOnScreen = shouldScrollToShowCursor; +} + +void TextEditor::updateCaretPosition() throw() +{ + cursorHeight = currentFont.getHeight(); // (in case the text is empty and the call below doesn't set this value) + getCharPosition (caretPosition, cursorX, cursorY, cursorHeight); +} + +void TextEditor::scrollToMakeSureCursorIsVisible() throw() +{ + updateCaretPosition(); + + if (keepCursorOnScreen) + { + int x = viewport->getViewPositionX(); + int y = viewport->getViewPositionY(); + + const int relativeCursorX = roundFloatToInt (cursorX) - x; + const int relativeCursorY = roundFloatToInt (cursorY) - y; + + if (relativeCursorX < jmax (1, proportionOfWidth (0.05f))) + { + x += relativeCursorX - proportionOfWidth (0.2f); + } + else if (relativeCursorX > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10))) + { + x += relativeCursorX + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth(); + } + + x = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), x); + + if (! isMultiLine()) + { + y = (getHeight() - textHolder->getHeight() - topIndent) / -2; + } + else + { + const int curH = roundFloatToInt (cursorHeight); + + if (relativeCursorY < 0) + { + y = jmax (0, relativeCursorY + y); + } + else if (relativeCursorY > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - curH)) + { + y += relativeCursorY + 2 + curH + topIndent - viewport->getMaximumVisibleHeight(); + } + } + + viewport->setViewPosition (x, y); + } +} + +void TextEditor::moveCursorTo (const int newPosition, + const bool isSelecting) throw() +{ + if (isSelecting) + { + moveCaret (newPosition); + + const int oldSelStart = selectionStart; + const int oldSelEnd = selectionEnd; + + if (dragType == notDragging) + { + if (abs (getCaretPosition() - selectionStart) < abs (getCaretPosition() - selectionEnd)) + dragType = draggingSelectionStart; + else + dragType = draggingSelectionEnd; + } + + if (dragType == draggingSelectionStart) + { + selectionStart = getCaretPosition(); + + if (selectionEnd < selectionStart) + { + swapVariables (selectionStart, selectionEnd); + dragType = draggingSelectionEnd; + } + } + else + { + selectionEnd = getCaretPosition(); + + if (selectionEnd < selectionStart) + { + swapVariables (selectionStart, selectionEnd); + dragType = draggingSelectionStart; + } + } + + jassert (selectionStart <= selectionEnd); + jassert (oldSelStart <= oldSelEnd); + + repaintText (jmin (oldSelStart, selectionStart), + jmax (oldSelEnd, selectionEnd)); + } + else + { + dragType = notDragging; + + if (selectionEnd > selectionStart) + repaintText (selectionStart, selectionEnd); + + moveCaret (newPosition); + selectionStart = getCaretPosition(); + selectionEnd = getCaretPosition(); + } +} + +int TextEditor::getTextIndexAt (const int x, + const int y) throw() +{ + return indexAtPosition ((float) (x + viewport->getViewPositionX() - leftIndent), + (float) (y + viewport->getViewPositionY() - topIndent)); +} + +void TextEditor::insertTextAtCursor (String newText) +{ + if (allowedCharacters.isNotEmpty()) + newText = newText.retainCharacters (allowedCharacters); + + if (! isMultiLine()) + newText = newText.replaceCharacters (T("\r\n"), T(" ")); + else + newText = newText.replace (T("\r\n"), T("\n")); + + const int newCaretPos = selectionStart + newText.length(); + const int insertIndex = selectionStart; + + remove (selectionStart, selectionEnd, + &undoManager, + newText.isNotEmpty() ? newCaretPos - 1 : newCaretPos); + + if (maxTextLength > 0) + newText = newText.substring (0, maxTextLength - getTotalNumChars()); + + if (newText.isNotEmpty()) + insert (newText, + insertIndex, + currentFont, + findColour (textColourId), + &undoManager, + newCaretPos); + + textChanged(); +} + +void TextEditor::setHighlightedRegion (int startPos, int numChars) throw() +{ + moveCursorTo (startPos, false); + moveCursorTo (startPos + numChars, true); +} + +//============================================================================== +void TextEditor::copy() +{ + const String selection (getTextSubstring (selectionStart, selectionEnd)); + + if (selection.isNotEmpty()) + SystemClipboard::copyTextToClipboard (selection); +} + +void TextEditor::paste() +{ + if (! isReadOnly()) + { + const String clip (SystemClipboard::getTextFromClipboard()); + + if (clip.isNotEmpty()) + insertTextAtCursor (clip); + } +} + +void TextEditor::cut() +{ + if (! isReadOnly()) + { + moveCaret (selectionEnd); + insertTextAtCursor (String::empty); + } +} + +//============================================================================== +void TextEditor::drawContent (Graphics& g) +{ + const float wordWrapWidth = getWordWrapWidth(); + + if (wordWrapWidth > 0) + { + g.setOrigin (leftIndent, topIndent); + const Rectangle clip (g.getClipBounds()); + Colour selectedTextColour; + + TextEditorIterator i (sections, wordWrapWidth, passwordCharacter); + + while (i.lineY + 200.0 < clip.getY() && i.next()) + {} + + if (selectionStart < selectionEnd) + { + g.setColour (findColour (highlightColourId) + .withMultipliedAlpha (hasKeyboardFocus (true) ? 1.0f : 0.5f)); + + selectedTextColour = findColour (highlightedTextColourId); + + TextEditorIterator i2 (i); + + while (i2.next() && i2.lineY < clip.getBottom()) + { + i2.updateLineHeight(); + + if (i2.lineY + i2.lineHeight >= clip.getY() + && selectionEnd >= i2.indexInText + && selectionStart <= i2.indexInText + i2.atom->numChars) + { + i2.drawSelection (g, selectionStart, selectionEnd); + } + } + } + + const UniformTextSection* lastSection = 0; + + while (i.next() && i.lineY < clip.getBottom()) + { + i.updateLineHeight(); + + if (i.lineY + i.lineHeight >= clip.getY()) + { + if (selectionEnd >= i.indexInText + && selectionStart <= i.indexInText + i.atom->numChars) + { + i.drawSelectedText (g, selectionStart, selectionEnd, selectedTextColour); + lastSection = 0; + } + else + { + i.draw (g, lastSection); + } + } + } + } +} + +void TextEditor::paint (Graphics& g) +{ + getLookAndFeel().fillTextEditorBackground (g, getWidth(), getHeight(), *this); +} + +void TextEditor::paintOverChildren (Graphics& g) +{ + if (caretFlashState + && hasKeyboardFocus (false) + && caretVisible + && ! isReadOnly()) + { + g.setColour (findColour (caretColourId)); + + g.fillRect (borderSize.getLeft() + textHolder->getX() + leftIndent + cursorX, + borderSize.getTop() + textHolder->getY() + topIndent + cursorY, + 2.0f, cursorHeight); + } + + if (textToShowWhenEmpty.isNotEmpty() + && (! hasKeyboardFocus (false)) + && getTotalNumChars() == 0) + { + g.setColour (colourForTextWhenEmpty); + g.setFont (getFont()); + + if (isMultiLine()) + { + g.drawText (textToShowWhenEmpty, + 0, 0, getWidth(), getHeight(), + Justification::centred, true); + } + else + { + g.drawText (textToShowWhenEmpty, + leftIndent, topIndent, + viewport->getWidth() - leftIndent, + viewport->getHeight() - topIndent, + Justification::centredLeft, true); + } + } + + getLookAndFeel().drawTextEditorOutline (g, getWidth(), getHeight(), *this); +} + +//============================================================================== +void TextEditor::mouseDown (const MouseEvent& e) +{ + beginDragAutoRepeat (100); + newTransaction(); + + if (wasFocused || ! selectAllTextWhenFocused) + { + if (! (popupMenuEnabled && e.mods.isPopupMenu())) + { + moveCursorTo (getTextIndexAt (e.x, e.y), + e.mods.isShiftDown()); + } + else + { + PopupMenu m; + addPopupMenuItems (m, &e); + + menuActive = true; + const int result = m.show(); + menuActive = false; + + if (result != 0) + performPopupMenuAction (result); + } + } +} + +void TextEditor::mouseDrag (const MouseEvent& e) +{ + if (wasFocused || ! selectAllTextWhenFocused) + { + if (! (popupMenuEnabled && e.mods.isPopupMenu())) + { + moveCursorTo (getTextIndexAt (e.x, e.y), true); + } + } +} + +void TextEditor::mouseUp (const MouseEvent& e) +{ + newTransaction(); + textHolder->startTimer (flashSpeedIntervalMs); + + if (wasFocused || ! selectAllTextWhenFocused) + { + if (! (popupMenuEnabled && e.mods.isPopupMenu())) + { + moveCaret (getTextIndexAt (e.x, e.y)); + } + } + + wasFocused = true; +} + +void TextEditor::mouseDoubleClick (const MouseEvent& e) +{ + int tokenEnd = getTextIndexAt (e.x, e.y); + int tokenStart = tokenEnd; + + if (e.getNumberOfClicks() > 3) + { + tokenStart = 0; + tokenEnd = getTotalNumChars(); + } + else + { + const String t (getText()); + const int totalLength = getTotalNumChars(); + + while (tokenEnd < totalLength) + { + if (CharacterFunctions::isLetterOrDigit (t [tokenEnd])) + ++tokenEnd; + else + break; + } + + tokenStart = tokenEnd; + + while (tokenStart > 0) + { + if (CharacterFunctions::isLetterOrDigit (t [tokenStart - 1])) + --tokenStart; + else + break; + } + + if (e.getNumberOfClicks() > 2) + { + while (tokenEnd < totalLength) + { + if (t [tokenEnd] != T('\r') && t [tokenEnd] != T('\n')) + ++tokenEnd; + else + break; + } + + while (tokenStart > 0) + { + if (t [tokenStart - 1] != T('\r') && t [tokenStart - 1] != T('\n')) + --tokenStart; + else + break; + } + } + } + + moveCursorTo (tokenEnd, false); + moveCursorTo (tokenStart, true); +} + +void TextEditor::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) +{ + if (! viewport->useMouseWheelMoveIfNeeded (e, wheelIncrementX, wheelIncrementY)) + Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); +} + +//============================================================================== +bool TextEditor::keyPressed (const KeyPress& key) +{ + if (isReadOnly() && key != KeyPress (T('c'), ModifierKeys::commandModifier, 0)) + return false; + + const bool moveInWholeWordSteps = key.getModifiers().isCtrlDown() || key.getModifiers().isAltDown(); + + if (key.isKeyCode (KeyPress::leftKey) + || key.isKeyCode (KeyPress::upKey)) + { + newTransaction(); + + int newPos; + + if (isMultiLine() && key.isKeyCode (KeyPress::upKey)) + newPos = indexAtPosition (cursorX, cursorY - 1); + else if (moveInWholeWordSteps) + newPos = findWordBreakBefore (getCaretPosition()); + else + newPos = getCaretPosition() - 1; + + moveCursorTo (newPos, key.getModifiers().isShiftDown()); + } + else if (key.isKeyCode (KeyPress::rightKey) + || key.isKeyCode (KeyPress::downKey)) + { + newTransaction(); + + int newPos; + + if (isMultiLine() && key.isKeyCode (KeyPress::downKey)) + newPos = indexAtPosition (cursorX, cursorY + cursorHeight + 1); + else if (moveInWholeWordSteps) + newPos = findWordBreakAfter (getCaretPosition()); + else + newPos = getCaretPosition() + 1; + + moveCursorTo (newPos, key.getModifiers().isShiftDown()); + } + else if (key.isKeyCode (KeyPress::pageDownKey) && isMultiLine()) + { + newTransaction(); + + moveCursorTo (indexAtPosition (cursorX, cursorY + cursorHeight + viewport->getViewHeight()), + key.getModifiers().isShiftDown()); + } + else if (key.isKeyCode (KeyPress::pageUpKey) && isMultiLine()) + { + newTransaction(); + + moveCursorTo (indexAtPosition (cursorX, cursorY - viewport->getViewHeight()), + key.getModifiers().isShiftDown()); + } + else if (key.isKeyCode (KeyPress::homeKey)) + { + newTransaction(); + + if (isMultiLine() && ! moveInWholeWordSteps) + moveCursorTo (indexAtPosition (0.0f, cursorY), + key.getModifiers().isShiftDown()); + else + moveCursorTo (0, key.getModifiers().isShiftDown()); + } + else if (key.isKeyCode (KeyPress::endKey)) + { + newTransaction(); + + if (isMultiLine() && ! moveInWholeWordSteps) + moveCursorTo (indexAtPosition ((float) textHolder->getWidth(), cursorY), + key.getModifiers().isShiftDown()); + else + moveCursorTo (getTotalNumChars(), key.getModifiers().isShiftDown()); + } + else if (key.isKeyCode (KeyPress::backspaceKey)) + { + if (moveInWholeWordSteps) + { + moveCursorTo (findWordBreakBefore (getCaretPosition()), true); + } + else + { + if (selectionStart == selectionEnd && selectionStart > 0) + --selectionStart; + } + + cut(); + } + else if (key.isKeyCode (KeyPress::deleteKey)) + { + if (key.getModifiers().isShiftDown()) + copy(); + + if (selectionStart == selectionEnd + && selectionEnd < getTotalNumChars()) + { + ++selectionEnd; + } + + cut(); + } + else if (key == KeyPress (T('c'), ModifierKeys::commandModifier, 0) + || key == KeyPress (KeyPress::insertKey, ModifierKeys::ctrlModifier, 0)) + { + newTransaction(); + copy(); + } + else if (key == KeyPress (T('x'), ModifierKeys::commandModifier, 0)) + { + newTransaction(); + copy(); + cut(); + } + else if (key == KeyPress (T('v'), ModifierKeys::commandModifier, 0) + || key == KeyPress (KeyPress::insertKey, ModifierKeys::shiftModifier, 0)) + { + newTransaction(); + paste(); + } + else if (key == KeyPress (T('z'), ModifierKeys::commandModifier, 0)) + { + newTransaction(); + doUndoRedo (false); + } + else if (key == KeyPress (T('y'), ModifierKeys::commandModifier, 0)) + { + newTransaction(); + doUndoRedo (true); + } + else if (key == KeyPress (T('a'), ModifierKeys::commandModifier, 0)) + { + newTransaction(); + moveCursorTo (getTotalNumChars(), false); + moveCursorTo (0, true); + } + else if (key == KeyPress::returnKey) + { + newTransaction(); + + if (returnKeyStartsNewLine) + insertTextAtCursor (T("\n")); + else + returnPressed(); + } + else if (key.isKeyCode (KeyPress::escapeKey)) + { + newTransaction(); + moveCursorTo (getCaretPosition(), false); + escapePressed(); + } + else if (key.getTextCharacter() >= ' ' + || (tabKeyUsed && (key.getTextCharacter() == '\t'))) + { + insertTextAtCursor (String::charToString (key.getTextCharacter())); + + lastTransactionTime = Time::getApproximateMillisecondCounter(); + } + else + { + return false; + } + + return true; +} + +bool TextEditor::keyStateChanged() +{ + // (overridden to avoid forwarding key events to the parent) + return true; +} + +//============================================================================== +const int baseMenuItemID = 0x7fff0000; + +void TextEditor::addPopupMenuItems (PopupMenu& m, const MouseEvent*) +{ + const bool writable = ! isReadOnly(); + + m.addItem (baseMenuItemID + 1, TRANS("cut"), writable); + m.addItem (baseMenuItemID + 2, TRANS("copy"), selectionStart < selectionEnd); + m.addItem (baseMenuItemID + 3, TRANS("paste"), writable); + m.addItem (baseMenuItemID + 4, TRANS("delete"), writable); + m.addSeparator(); + m.addItem (baseMenuItemID + 5, TRANS("select all")); + m.addSeparator(); + m.addItem (baseMenuItemID + 6, TRANS("undo"), undoManager.canUndo()); + m.addItem (baseMenuItemID + 7, TRANS("redo"), undoManager.canRedo()); +} + +void TextEditor::performPopupMenuAction (const int menuItemID) +{ + switch (menuItemID) + { + case baseMenuItemID + 1: + copy(); + cut(); + break; + + case baseMenuItemID + 2: + copy(); + break; + + case baseMenuItemID + 3: + paste(); + break; + + case baseMenuItemID + 4: + cut(); + break; + + case baseMenuItemID + 5: + moveCursorTo (getTotalNumChars(), false); + moveCursorTo (0, true); + break; + + case baseMenuItemID + 6: + doUndoRedo (false); + break; + + case baseMenuItemID + 7: + doUndoRedo (true); + break; + + default: + break; + } +} + +//============================================================================== +void TextEditor::focusGained (FocusChangeType) +{ + newTransaction(); + + caretFlashState = true; + + if (selectAllTextWhenFocused) + { + moveCursorTo (0, false); + moveCursorTo (getTotalNumChars(), true); + } + + repaint(); + + if (caretVisible) + textHolder->startTimer (flashSpeedIntervalMs); + + ComponentPeer* const peer = getPeer(); + if (peer != 0) + peer->textInputRequired (getScreenX() - peer->getScreenX(), + getScreenY() - peer->getScreenY()); +} + +void TextEditor::focusLost (FocusChangeType) +{ + newTransaction(); + + wasFocused = false; + textHolder->stopTimer(); + caretFlashState = false; + + postCommandMessage (focusLossMessageId); + repaint(); +} + +//============================================================================== +void TextEditor::resized() +{ + viewport->setBoundsInset (borderSize); + viewport->setSingleStepSizes (16, roundFloatToInt (currentFont.getHeight())); + + updateTextHolderSize(); + + if (! isMultiLine()) + { + scrollToMakeSureCursorIsVisible(); + } + else + { + updateCaretPosition(); + } +} + +void TextEditor::handleCommandMessage (const int commandId) +{ + const ComponentDeletionWatcher deletionChecker (this); + + for (int i = listeners.size(); --i >= 0;) + { + TextEditorListener* const tl = (TextEditorListener*) listeners [i]; + + if (tl != 0) + { + switch (commandId) + { + case textChangeMessageId: + tl->textEditorTextChanged (*this); + break; + + case returnKeyMessageId: + tl->textEditorReturnKeyPressed (*this); + break; + + case escapeKeyMessageId: + tl->textEditorEscapeKeyPressed (*this); + break; + + case focusLossMessageId: + tl->textEditorFocusLost (*this); + break; + + default: + jassertfalse + break; + } + + if (i > 0 && deletionChecker.hasBeenDeleted()) + return; + } + } +} + +void TextEditor::enablementChanged() +{ + setMouseCursor (MouseCursor (isReadOnly() ? MouseCursor::NormalCursor + : MouseCursor::IBeamCursor)); + repaint(); +} + +//============================================================================== +void TextEditor::clearInternal (UndoManager* const um) throw() +{ + remove (0, getTotalNumChars(), um, caretPosition); +} + +void TextEditor::insert (const String& text, + const int insertIndex, + const Font& font, + const Colour& colour, + UndoManager* const um, + const int caretPositionToMoveTo) throw() +{ + if (text.isNotEmpty()) + { + if (um != 0) + { + um->perform (new TextEditorInsertAction (*this, + text, + insertIndex, + font, + colour, + caretPosition, + caretPositionToMoveTo)); + } + else + { + repaintText (insertIndex, -1); // must do this before and after changing the data, in case + // a line gets moved due to word wrap + + int index = 0; + int nextIndex = 0; + + for (int i = 0; i < sections.size(); ++i) + { + nextIndex = index + ((UniformTextSection*) sections.getUnchecked(i))->getTotalLength(); + + if (insertIndex == index) + { + sections.insert (i, new UniformTextSection (text, + font, colour, + passwordCharacter)); + break; + } + else if (insertIndex > index && insertIndex < nextIndex) + { + splitSection (i, insertIndex - index); + sections.insert (i + 1, new UniformTextSection (text, + font, colour, + passwordCharacter)); + break; + } + + index = nextIndex; + } + + if (nextIndex == insertIndex) + sections.add (new UniformTextSection (text, + font, colour, + passwordCharacter)); + + coalesceSimilarSections(); + totalNumChars = -1; + + moveCursorTo (caretPositionToMoveTo, false); + + repaintText (insertIndex, -1); + } + } +} + +void TextEditor::reinsert (const int insertIndex, + const VoidArray& sectionsToInsert) throw() +{ + int index = 0; + int nextIndex = 0; + + for (int i = 0; i < sections.size(); ++i) + { + nextIndex = index + ((UniformTextSection*) sections.getUnchecked(i))->getTotalLength(); + + if (insertIndex == index) + { + for (int j = sectionsToInsert.size(); --j >= 0;) + sections.insert (i, new UniformTextSection (*(UniformTextSection*) sectionsToInsert.getUnchecked(j))); + + break; + } + else if (insertIndex > index && insertIndex < nextIndex) + { + splitSection (i, insertIndex - index); + + for (int j = sectionsToInsert.size(); --j >= 0;) + sections.insert (i + 1, new UniformTextSection (*(UniformTextSection*) sectionsToInsert.getUnchecked(j))); + + break; + } + + index = nextIndex; + } + + if (nextIndex == insertIndex) + { + for (int j = 0; j < sectionsToInsert.size(); ++j) + sections.add (new UniformTextSection (*(UniformTextSection*) sectionsToInsert.getUnchecked(j))); + } + + coalesceSimilarSections(); + totalNumChars = -1; +} + +void TextEditor::remove (const int startIndex, + int endIndex, + UndoManager* const um, + const int caretPositionToMoveTo) throw() +{ + if (endIndex > startIndex) + { + int index = 0; + + for (int i = 0; i < sections.size(); ++i) + { + const int nextIndex = index + ((UniformTextSection*) sections[i])->getTotalLength(); + + if (startIndex > index && startIndex < nextIndex) + { + splitSection (i, startIndex - index); + --i; + } + else if (endIndex > index && endIndex < nextIndex) + { + splitSection (i, endIndex - index); + --i; + } + else + { + index = nextIndex; + + if (index > endIndex) + break; + } + } + + index = 0; + + if (um != 0) + { + VoidArray removedSections; + + for (int i = 0; i < sections.size(); ++i) + { + if (endIndex <= startIndex) + break; + + UniformTextSection* const section = (UniformTextSection*) sections.getUnchecked (i); + + const int nextIndex = index + section->getTotalLength(); + + if (startIndex <= index && endIndex >= nextIndex) + removedSections.add (new UniformTextSection (*section)); + + index = nextIndex; + } + + um->perform (new TextEditorRemoveAction (*this, + startIndex, + endIndex, + caretPosition, + caretPositionToMoveTo, + removedSections)); + } + else + { + for (int i = 0; i < sections.size(); ++i) + { + if (endIndex <= startIndex) + break; + + UniformTextSection* const section = (UniformTextSection*) sections.getUnchecked (i); + + const int nextIndex = index + section->getTotalLength(); + + if (startIndex <= index && endIndex >= nextIndex) + { + sections.remove(i); + endIndex -= (nextIndex - index); + section->clear(); + delete section; + --i; + } + else + { + index = nextIndex; + } + } + + coalesceSimilarSections(); + totalNumChars = -1; + + moveCursorTo (caretPositionToMoveTo, false); + + repaintText (startIndex, -1); + } + } +} + +//============================================================================== +const String TextEditor::getText() const throw() +{ + String t; + + for (int i = 0; i < sections.size(); ++i) + t += ((const UniformTextSection*) sections.getUnchecked(i))->getAllText(); + + return t; +} + +const String TextEditor::getTextSubstring (const int startCharacter, const int endCharacter) const throw() +{ + String t; + int index = 0; + + for (int i = 0; i < sections.size(); ++i) + { + const UniformTextSection* const s = (const UniformTextSection*) sections.getUnchecked(i); + const int nextIndex = index + s->getTotalLength(); + + if (startCharacter < nextIndex) + { + if (endCharacter <= index) + break; + + const int start = jmax (index, startCharacter); + t += s->getTextSubstring (start - index, endCharacter - index); + } + + index = nextIndex; + } + + return t; +} + +const String TextEditor::getHighlightedText() const throw() +{ + return getTextSubstring (getHighlightedRegionStart(), + getHighlightedRegionStart() + getHighlightedRegionLength()); +} + +int TextEditor::getTotalNumChars() throw() +{ + if (totalNumChars < 0) + { + totalNumChars = 0; + + for (int i = sections.size(); --i >= 0;) + totalNumChars += ((const UniformTextSection*) sections.getUnchecked(i))->getTotalLength(); + } + + return totalNumChars; +} + +bool TextEditor::isEmpty() const throw() +{ + if (totalNumChars != 0) + { + for (int i = sections.size(); --i >= 0;) + if (((const UniformTextSection*) sections.getUnchecked(i))->getTotalLength() > 0) + return false; + } + + return true; +} + +void TextEditor::getCharPosition (const int index, float& cx, float& cy, float& lineHeight) const throw() +{ + const float wordWrapWidth = getWordWrapWidth(); + + if (wordWrapWidth > 0 && sections.size() > 0) + { + TextEditorIterator i (sections, wordWrapWidth, passwordCharacter); + + i.getCharPosition (index, cx, cy, lineHeight); + } + else + { + cx = cy = 0; + lineHeight = currentFont.getHeight(); + } +} + +int TextEditor::indexAtPosition (const float x, const float y) throw() +{ + const float wordWrapWidth = getWordWrapWidth(); + + if (wordWrapWidth > 0) + { + TextEditorIterator i (sections, wordWrapWidth, passwordCharacter); + + while (i.next()) + { + if (i.lineY + getHeight() > y) + i.updateLineHeight(); + + if (i.lineY + i.lineHeight > y) + { + if (i.lineY > y) + return jmax (0, i.indexInText - 1); + + if (i.atomX >= x) + return i.indexInText; + + if (x < i.atomRight) + return i.xToIndex (x); + } + } + } + + return getTotalNumChars(); +} + +//============================================================================== +static int getCharacterCategory (const tchar character) throw() +{ + return CharacterFunctions::isLetterOrDigit (character) + ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1); +} + +int TextEditor::findWordBreakAfter (const int position) const throw() +{ + const String t (getTextSubstring (position, position + 512)); + const int totalLength = t.length(); + int i = 0; + + while (i < totalLength && CharacterFunctions::isWhitespace (t[i])) + ++i; + + const int type = getCharacterCategory (t[i]); + + while (i < totalLength && type == getCharacterCategory (t[i])) + ++i; + + while (i < totalLength && CharacterFunctions::isWhitespace (t[i])) + ++i; + + return position + i; +} + +int TextEditor::findWordBreakBefore (const int position) const throw() +{ + if (position <= 0) + return 0; + + const int startOfBuffer = jmax (0, position - 512); + const String t (getTextSubstring (startOfBuffer, position)); + + int i = position - startOfBuffer; + + while (i > 0 && CharacterFunctions::isWhitespace (t [i - 1])) + --i; + + if (i > 0) + { + const int type = getCharacterCategory (t [i - 1]); + + while (i > 0 && type == getCharacterCategory (t [i - 1])) + --i; + } + + jassert (startOfBuffer + i >= 0); + return startOfBuffer + i; +} + + +//============================================================================== +void TextEditor::splitSection (const int sectionIndex, + const int charToSplitAt) throw() +{ + jassert (sections[sectionIndex] != 0); + + sections.insert (sectionIndex + 1, + ((UniformTextSection*) sections.getUnchecked (sectionIndex)) + ->split (charToSplitAt, passwordCharacter)); +} + +void TextEditor::coalesceSimilarSections() throw() +{ + for (int i = 0; i < sections.size() - 1; ++i) + { + UniformTextSection* const s1 = (UniformTextSection*) (sections.getUnchecked (i)); + UniformTextSection* const s2 = (UniformTextSection*) (sections.getUnchecked (i + 1)); + + if (s1->font == s2->font + && s1->colour == s2->colour) + { + s1->append (*s2, passwordCharacter); + sections.remove (i + 1); + delete s2; + --i; + } + } +} + + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/controls/juce_TextEditor.h b/src/juce_appframework/gui/components/controls/juce_TextEditor.h index b4a7763165..fdadd47133 100644 --- a/src/juce_appframework/gui/components/controls/juce_TextEditor.h +++ b/src/juce_appframework/gui/components/controls/juce_TextEditor.h @@ -1,707 +1,707 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#ifndef __JUCE_TEXTEDITOR_JUCEHEADER__ -#define __JUCE_TEXTEDITOR_JUCEHEADER__ - -#include "../juce_Component.h" -#include "../../../events/juce_Timer.h" -#include "../../../documents/juce_UndoManager.h" -#include "../layout/juce_Viewport.h" -#include "../menus/juce_PopupMenu.h" -class TextEditor; -class TextHolderComponent; - - -//============================================================================== -/** - Receives callbacks from a TextEditor component when it changes. - - @see TextEditor::addListener -*/ -class JUCE_API TextEditorListener -{ -public: - /** Destructor. */ - virtual ~TextEditorListener() {} - - /** Called when the user changes the text in some way. */ - virtual void textEditorTextChanged (TextEditor& editor) = 0; - - /** Called when the user presses the return key. */ - virtual void textEditorReturnKeyPressed (TextEditor& editor) = 0; - - /** Called when the user presses the escape key. */ - virtual void textEditorEscapeKeyPressed (TextEditor& editor) = 0; - - /** Called when the text editor loses focus. */ - virtual void textEditorFocusLost (TextEditor& editor) = 0; -}; - - -//============================================================================== -/** - A component containing text that can be edited. - - A TextEditor can either be in single- or multi-line mode, and supports mixed - fonts and colours. - - @see TextEditorListener, Label -*/ -class JUCE_API TextEditor : public Component, - 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). - */ - TextEditor (const String& componentName = String::empty, - const tchar passwordCharacter = 0); - - /** Destructor. */ - virtual ~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 (const bool shouldBeMultiLine, - const bool shouldWordWrap = true); - - /** Returns true if the editor is in multi-line mode. - */ - bool isMultiLine() const throw(); - - //============================================================================== - /** 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 TextEditorListener::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 (const bool shouldStartNewLine); - - /** Returns the value set by setReturnKeyStartsNewLine(). - - See setReturnKeyStartsNewLine() for more info. - */ - bool getReturnKeyStartsNewLine() const throw() { 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 (const bool shouldTabKeyBeUsed) throw(); - - /** Returns true if the tab key is being used for input. - @see setTabKeyUsedAsCharacter - */ - bool isTabKeyUsedAsCharacter() const throw() { return tabKeyUsed; } - - //============================================================================== - /** 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 (const bool shouldBeReadOnly); - - /** Returns true if the editor is in read-only mode. - */ - bool isReadOnly() const throw(); - - //============================================================================== - /** Makes the caret visible or invisible. - - By default the caret is visible. - - @see setCaretColour, setCaretPosition - */ - void setCaretVisible (const bool shouldBeVisible) throw(); - - /** Returns true if the caret is enabled. - @see setCaretVisible - */ - bool isCaretVisible() const throw() { return caretVisible; } - - //============================================================================== - /** 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) throw(); - - /** Returns true if scrollbars are enabled. - @see setScrollbarsShown - */ - bool areScrollbarsShown() const throw() { 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 (const tchar passwordCharacter) throw(); - - /** Returns the current password character. - @see setPasswordCharacter -l */ - tchar getPasswordCharacter() const throw() { 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 (const bool menuEnabled) throw(); - - /** Returns true if the right-click menu is enabled. - @see setPopupMenuEnabled - */ - bool isPopupMenuEnabled() const throw() { return popupMenuEnabled; } - - /** Returns true if a popup-menu is currently being displayed. - */ - bool isPopupMenuCurrentlyActive() const throw() { 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. */ - - caretColourId = 0x1000204, /**< The colour with which to draw the caret. */ - - 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) throw(); - - /** 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 throw(); - - //============================================================================== - /** 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 (const bool b) throw(); - - /** Sets limits on the characters that can be entered. - - @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 (const int maxTextLength, - const String& allowedCharacters = String::empty) throw(); - - /** 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, const Colour& colourToUse) throw(); - - //============================================================================== - /** Changes the size of the scrollbars that are used. - - Handy if you need smaller scrollbars for a small text box. - */ - void setScrollBarThickness (const int newThicknessPixels); - - /** Shows or hides the buttons on any scrollbars that are used. - - @see ScrollBar::setButtonVisibility - */ - void setScrollBarButtonVisibility (const bool buttonsVisible); - - //============================================================================== - /** Registers a listener to be told when things happen to the text. - - @see removeListener - */ - void addListener (TextEditorListener* const newListener) throw(); - - /** Deregisters a listener. - - @see addListener - */ - void removeListener (TextEditorListener* const listenerToRemove) throw(); - - //============================================================================== - /** Returns the entire contents of the editor. */ - const String getText() const throw(); - - /** Returns a section of the contents of the editor. */ - const String getTextSubstring (const int startCharacter, const int endCharacter) const throw(); - - /** Returns true if there are no characters in the editor. - - This is more efficient than calling getText().isEmpty(). - */ - bool isEmpty() const throw(); - - /** 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, - const bool sendTextChangeMessage = true); - - /** Inserts some text at the current cursor 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 insertTextAtCursor (String textToInsert); - - /** Deletes all the text from the editor. */ - void clear(); - - /** Deletes the currently selected region, and puts it on the clipboard. - - @see copy, paste, SystemClipboard - */ - void cut(); - - /** Copies any currently selected region to the clipboard. - - @see cut, paste, SystemClipboard - */ - void copy(); - - /** Pastes the contents of the clipboard into the editor at the cursor position. - - @see cut, copy, SystemClipboard - */ - void paste(); - - //============================================================================== - /** Moves the caret to be in front of a given character. - - @see getCaretPosition - */ - void setCaretPosition (const int newIndex) throw(); - - /** Returns the current index of the caret. - - @see setCaretPosition - */ - int getCaretPosition() const throw(); - - /** 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 (const int desiredCaretX, - const int desiredCaretY) throw(); - - /** Get the graphical position of the caret. - - The rectangle returned is relative to the component's top-left corner. - @see scrollEditorToPositionCaret - */ - const Rectangle getCaretRectangle() throw(); - - /** Selects a section of the text. - */ - void setHighlightedRegion (int startIndex, - int numberOfCharactersToHighlight) throw(); - - /** Returns the first character that is selected. - - If nothing is selected, this will still return a character index, but getHighlightedRegionLength() - will return 0. - - @see setHighlightedRegion, getHighlightedRegionLength - */ - int getHighlightedRegionStart() const throw() { return selectionStart; } - - /** Returns the number of characters that are selected. - - @see setHighlightedRegion, getHighlightedRegionStart - */ - int getHighlightedRegionLength() const throw() { return jmax (0, selectionEnd - selectionStart); } - - /** Returns the section of text that is currently selected. */ - const String getHighlightedText() const throw(); - - /** Finds the index of the character at a given position. - - The co-ordinates are relative to the component's top-left. - */ - int getTextIndexAt (const int x, const int y) throw(); - - /** 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 throw(); - - /** 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 throw(); - - /** 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 (const int newLeftIndent, const int newTopIndent) throw(); - - /** Changes the size of border left around the edge of the component. - - @see getBorder - */ - void setBorder (const BorderSize& border) throw(); - - /** Returns the size of border around the edge of the component. - - @see setBorder - */ - const BorderSize getBorder() const throw(); - - /** Used to disable the auto-scrolling which keeps the cursor visible. - - If true (the default), the editor will scroll when the cursor moves offscreen. If - set to false, it won't. - */ - void setScrollToShowCursor (const bool shouldScrollToShowCursor) throw(); - - //============================================================================== - /** @internal */ - void paint (Graphics& g); - /** @internal */ - void paintOverChildren (Graphics& g); - /** @internal */ - void mouseDown (const MouseEvent& e); - /** @internal */ - void mouseUp (const MouseEvent& e); - /** @internal */ - void mouseDrag (const MouseEvent& e); - /** @internal */ - void mouseDoubleClick (const MouseEvent& e); - /** @internal */ - void mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY); - /** @internal */ - bool keyPressed (const KeyPress& key); - /** @internal */ - bool keyStateChanged(); - /** @internal */ - void focusGained (FocusChangeType cause); - /** @internal */ - void focusLost (FocusChangeType cause); - /** @internal */ - void resized(); - /** @internal */ - void enablementChanged(); - /** @internal */ - void colourChanged(); - - juce_UseDebuggingNewOperator - -protected: - //============================================================================== - /** 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 in the range - 0x7fff0000 - 0x7fff1000, so you should avoid those values for your own - menu IDs. - - 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 (const int menuItemID); - - //============================================================================== - /** Scrolls the minimum distance needed to get the caret into view. */ - void scrollToMakeSureCursorIsVisible() throw(); - - /** @internal */ - void moveCaret (int newCaretPos) throw(); - - /** @internal */ - void moveCursorTo (const int newPosition, const bool isSelecting) throw(); - - /** Used internally to dispatch a text-change message. */ - void textChanged() throw(); - - /** 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() throw(); - - /** Begins a new transaction in the UndoManager. - */ - void newTransaction() throw(); - - /** Used internally to trigger an undo or redo. */ - void doUndoRedo (const bool isRedo); - - /** Can be overridden to intercept return key presses directly */ - virtual void returnPressed(); - - /** Can be overridden to intercept escape key presses directly */ - virtual void escapePressed(); - - /** @internal */ - void handleCommandMessage (int commandId); - -private: - //============================================================================== - Viewport* viewport; - TextHolderComponent* textHolder; - BorderSize borderSize; - - bool readOnly : 1; - bool multiline : 1; - bool wordWrap : 1; - bool returnKeyStartsNewLine : 1; - bool caretVisible : 1; - bool popupMenuEnabled : 1; - bool selectAllTextWhenFocused : 1; - bool scrollbarVisible : 1; - bool wasFocused : 1; - bool caretFlashState : 1; - bool keepCursorOnScreen : 1; - bool tabKeyUsed : 1; - bool menuActive : 1; - - UndoManager undoManager; - float cursorX, cursorY, cursorHeight; - int maxTextLength; - int selectionStart, selectionEnd; - int leftIndent, topIndent; - unsigned int lastTransactionTime; - Font currentFont; - int totalNumChars, caretPosition; - VoidArray sections; - String textToShowWhenEmpty; - Colour colourForTextWhenEmpty; - tchar passwordCharacter; - - enum - { - notDragging, - draggingSelectionStart, - draggingSelectionEnd - } dragType; - - String allowedCharacters; - SortedSet listeners; - - friend class TextEditorInsertAction; - friend class TextEditorRemoveAction; - - void coalesceSimilarSections() throw(); - void splitSection (const int sectionIndex, const int charToSplitAt) throw(); - - void clearInternal (UndoManager* const um) throw(); - - void insert (const String& text, - const int insertIndex, - const Font& font, - const Colour& colour, - UndoManager* const um, - const int caretPositionToMoveTo) throw(); - - void reinsert (const int insertIndex, - const VoidArray& sections) throw(); - - void remove (const int startIndex, - int endIndex, - UndoManager* const um, - const int caretPositionToMoveTo) throw(); - - void getCharPosition (const int index, - float& x, float& y, - float& lineHeight) const throw(); - - void updateCaretPosition() throw(); - - int indexAtPosition (const float x, - const float y) throw(); - - int findWordBreakAfter (const int position) const throw(); - int findWordBreakBefore (const int position) const throw(); - - friend class TextHolderComponent; - friend class TextEditorViewport; - void drawContent (Graphics& g); - void updateTextHolderSize() throw(); - float getWordWrapWidth() const throw(); - void timerCallbackInt(); - void repaintCaret(); - void repaintText (int textStartIndex, int textEndIndex); - - TextEditor (const TextEditor&); - const TextEditor& operator= (const TextEditor&); -}; - -#endif // __JUCE_TEXTEDITOR_JUCEHEADER__ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#ifndef __JUCE_TEXTEDITOR_JUCEHEADER__ +#define __JUCE_TEXTEDITOR_JUCEHEADER__ + +#include "../juce_Component.h" +#include "../../../events/juce_Timer.h" +#include "../../../documents/juce_UndoManager.h" +#include "../layout/juce_Viewport.h" +#include "../menus/juce_PopupMenu.h" +class TextEditor; +class TextHolderComponent; + + +//============================================================================== +/** + Receives callbacks from a TextEditor component when it changes. + + @see TextEditor::addListener +*/ +class JUCE_API TextEditorListener +{ +public: + /** Destructor. */ + virtual ~TextEditorListener() {} + + /** Called when the user changes the text in some way. */ + virtual void textEditorTextChanged (TextEditor& editor) = 0; + + /** Called when the user presses the return key. */ + virtual void textEditorReturnKeyPressed (TextEditor& editor) = 0; + + /** Called when the user presses the escape key. */ + virtual void textEditorEscapeKeyPressed (TextEditor& editor) = 0; + + /** Called when the text editor loses focus. */ + virtual void textEditorFocusLost (TextEditor& editor) = 0; +}; + + +//============================================================================== +/** + A component containing text that can be edited. + + A TextEditor can either be in single- or multi-line mode, and supports mixed + fonts and colours. + + @see TextEditorListener, Label +*/ +class JUCE_API TextEditor : public Component, + 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). + */ + TextEditor (const String& componentName = String::empty, + const tchar passwordCharacter = 0); + + /** Destructor. */ + virtual ~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 (const bool shouldBeMultiLine, + const bool shouldWordWrap = true); + + /** Returns true if the editor is in multi-line mode. + */ + bool isMultiLine() const throw(); + + //============================================================================== + /** 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 TextEditorListener::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 (const bool shouldStartNewLine); + + /** Returns the value set by setReturnKeyStartsNewLine(). + + See setReturnKeyStartsNewLine() for more info. + */ + bool getReturnKeyStartsNewLine() const throw() { 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 (const bool shouldTabKeyBeUsed) throw(); + + /** Returns true if the tab key is being used for input. + @see setTabKeyUsedAsCharacter + */ + bool isTabKeyUsedAsCharacter() const throw() { return tabKeyUsed; } + + //============================================================================== + /** 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 (const bool shouldBeReadOnly); + + /** Returns true if the editor is in read-only mode. + */ + bool isReadOnly() const throw(); + + //============================================================================== + /** Makes the caret visible or invisible. + + By default the caret is visible. + + @see setCaretColour, setCaretPosition + */ + void setCaretVisible (const bool shouldBeVisible) throw(); + + /** Returns true if the caret is enabled. + @see setCaretVisible + */ + bool isCaretVisible() const throw() { return caretVisible; } + + //============================================================================== + /** 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) throw(); + + /** Returns true if scrollbars are enabled. + @see setScrollbarsShown + */ + bool areScrollbarsShown() const throw() { 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 (const tchar passwordCharacter) throw(); + + /** Returns the current password character. + @see setPasswordCharacter +l */ + tchar getPasswordCharacter() const throw() { 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 (const bool menuEnabled) throw(); + + /** Returns true if the right-click menu is enabled. + @see setPopupMenuEnabled + */ + bool isPopupMenuEnabled() const throw() { return popupMenuEnabled; } + + /** Returns true if a popup-menu is currently being displayed. + */ + bool isPopupMenuCurrentlyActive() const throw() { 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. */ + + caretColourId = 0x1000204, /**< The colour with which to draw the caret. */ + + 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) throw(); + + /** 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 throw(); + + //============================================================================== + /** 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 (const bool b) throw(); + + /** Sets limits on the characters that can be entered. + + @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 (const int maxTextLength, + const String& allowedCharacters = String::empty) throw(); + + /** 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, const Colour& colourToUse) throw(); + + //============================================================================== + /** Changes the size of the scrollbars that are used. + + Handy if you need smaller scrollbars for a small text box. + */ + void setScrollBarThickness (const int newThicknessPixels); + + /** Shows or hides the buttons on any scrollbars that are used. + + @see ScrollBar::setButtonVisibility + */ + void setScrollBarButtonVisibility (const bool buttonsVisible); + + //============================================================================== + /** Registers a listener to be told when things happen to the text. + + @see removeListener + */ + void addListener (TextEditorListener* const newListener) throw(); + + /** Deregisters a listener. + + @see addListener + */ + void removeListener (TextEditorListener* const listenerToRemove) throw(); + + //============================================================================== + /** Returns the entire contents of the editor. */ + const String getText() const throw(); + + /** Returns a section of the contents of the editor. */ + const String getTextSubstring (const int startCharacter, const int endCharacter) const throw(); + + /** Returns true if there are no characters in the editor. + + This is more efficient than calling getText().isEmpty(). + */ + bool isEmpty() const throw(); + + /** 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, + const bool sendTextChangeMessage = true); + + /** Inserts some text at the current cursor 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 insertTextAtCursor (String textToInsert); + + /** Deletes all the text from the editor. */ + void clear(); + + /** Deletes the currently selected region, and puts it on the clipboard. + + @see copy, paste, SystemClipboard + */ + void cut(); + + /** Copies any currently selected region to the clipboard. + + @see cut, paste, SystemClipboard + */ + void copy(); + + /** Pastes the contents of the clipboard into the editor at the cursor position. + + @see cut, copy, SystemClipboard + */ + void paste(); + + //============================================================================== + /** Moves the caret to be in front of a given character. + + @see getCaretPosition + */ + void setCaretPosition (const int newIndex) throw(); + + /** Returns the current index of the caret. + + @see setCaretPosition + */ + int getCaretPosition() const throw(); + + /** 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 (const int desiredCaretX, + const int desiredCaretY) throw(); + + /** Get the graphical position of the caret. + + The rectangle returned is relative to the component's top-left corner. + @see scrollEditorToPositionCaret + */ + const Rectangle getCaretRectangle() throw(); + + /** Selects a section of the text. + */ + void setHighlightedRegion (int startIndex, + int numberOfCharactersToHighlight) throw(); + + /** Returns the first character that is selected. + + If nothing is selected, this will still return a character index, but getHighlightedRegionLength() + will return 0. + + @see setHighlightedRegion, getHighlightedRegionLength + */ + int getHighlightedRegionStart() const throw() { return selectionStart; } + + /** Returns the number of characters that are selected. + + @see setHighlightedRegion, getHighlightedRegionStart + */ + int getHighlightedRegionLength() const throw() { return jmax (0, selectionEnd - selectionStart); } + + /** Returns the section of text that is currently selected. */ + const String getHighlightedText() const throw(); + + /** Finds the index of the character at a given position. + + The co-ordinates are relative to the component's top-left. + */ + int getTextIndexAt (const int x, const int y) throw(); + + /** 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 throw(); + + /** 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 throw(); + + /** 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 (const int newLeftIndent, const int newTopIndent) throw(); + + /** Changes the size of border left around the edge of the component. + + @see getBorder + */ + void setBorder (const BorderSize& border) throw(); + + /** Returns the size of border around the edge of the component. + + @see setBorder + */ + const BorderSize getBorder() const throw(); + + /** Used to disable the auto-scrolling which keeps the cursor visible. + + If true (the default), the editor will scroll when the cursor moves offscreen. If + set to false, it won't. + */ + void setScrollToShowCursor (const bool shouldScrollToShowCursor) throw(); + + //============================================================================== + /** @internal */ + void paint (Graphics& g); + /** @internal */ + void paintOverChildren (Graphics& g); + /** @internal */ + void mouseDown (const MouseEvent& e); + /** @internal */ + void mouseUp (const MouseEvent& e); + /** @internal */ + void mouseDrag (const MouseEvent& e); + /** @internal */ + void mouseDoubleClick (const MouseEvent& e); + /** @internal */ + void mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY); + /** @internal */ + bool keyPressed (const KeyPress& key); + /** @internal */ + bool keyStateChanged(); + /** @internal */ + void focusGained (FocusChangeType cause); + /** @internal */ + void focusLost (FocusChangeType cause); + /** @internal */ + void resized(); + /** @internal */ + void enablementChanged(); + /** @internal */ + void colourChanged(); + + juce_UseDebuggingNewOperator + +protected: + //============================================================================== + /** 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 in the range + 0x7fff0000 - 0x7fff1000, so you should avoid those values for your own + menu IDs. + + 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 (const int menuItemID); + + //============================================================================== + /** Scrolls the minimum distance needed to get the caret into view. */ + void scrollToMakeSureCursorIsVisible() throw(); + + /** @internal */ + void moveCaret (int newCaretPos) throw(); + + /** @internal */ + void moveCursorTo (const int newPosition, const bool isSelecting) throw(); + + /** Used internally to dispatch a text-change message. */ + void textChanged() throw(); + + /** 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() throw(); + + /** Begins a new transaction in the UndoManager. + */ + void newTransaction() throw(); + + /** Used internally to trigger an undo or redo. */ + void doUndoRedo (const bool isRedo); + + /** Can be overridden to intercept return key presses directly */ + virtual void returnPressed(); + + /** Can be overridden to intercept escape key presses directly */ + virtual void escapePressed(); + + /** @internal */ + void handleCommandMessage (int commandId); + +private: + //============================================================================== + Viewport* viewport; + TextHolderComponent* textHolder; + BorderSize borderSize; + + bool readOnly : 1; + bool multiline : 1; + bool wordWrap : 1; + bool returnKeyStartsNewLine : 1; + bool caretVisible : 1; + bool popupMenuEnabled : 1; + bool selectAllTextWhenFocused : 1; + bool scrollbarVisible : 1; + bool wasFocused : 1; + bool caretFlashState : 1; + bool keepCursorOnScreen : 1; + bool tabKeyUsed : 1; + bool menuActive : 1; + + UndoManager undoManager; + float cursorX, cursorY, cursorHeight; + int maxTextLength; + int selectionStart, selectionEnd; + int leftIndent, topIndent; + unsigned int lastTransactionTime; + Font currentFont; + int totalNumChars, caretPosition; + VoidArray sections; + String textToShowWhenEmpty; + Colour colourForTextWhenEmpty; + tchar passwordCharacter; + + enum + { + notDragging, + draggingSelectionStart, + draggingSelectionEnd + } dragType; + + String allowedCharacters; + SortedSet listeners; + + friend class TextEditorInsertAction; + friend class TextEditorRemoveAction; + + void coalesceSimilarSections() throw(); + void splitSection (const int sectionIndex, const int charToSplitAt) throw(); + + void clearInternal (UndoManager* const um) throw(); + + void insert (const String& text, + const int insertIndex, + const Font& font, + const Colour& colour, + UndoManager* const um, + const int caretPositionToMoveTo) throw(); + + void reinsert (const int insertIndex, + const VoidArray& sections) throw(); + + void remove (const int startIndex, + int endIndex, + UndoManager* const um, + const int caretPositionToMoveTo) throw(); + + void getCharPosition (const int index, + float& x, float& y, + float& lineHeight) const throw(); + + void updateCaretPosition() throw(); + + int indexAtPosition (const float x, + const float y) throw(); + + int findWordBreakAfter (const int position) const throw(); + int findWordBreakBefore (const int position) const throw(); + + friend class TextHolderComponent; + friend class TextEditorViewport; + void drawContent (Graphics& g); + void updateTextHolderSize() throw(); + float getWordWrapWidth() const throw(); + void timerCallbackInt(); + void repaintCaret(); + void repaintText (int textStartIndex, int textEndIndex); + + TextEditor (const TextEditor&); + const TextEditor& operator= (const TextEditor&); +}; + +#endif // __JUCE_TEXTEDITOR_JUCEHEADER__ diff --git a/src/juce_appframework/gui/components/filebrowser/juce_FileListComponent.cpp b/src/juce_appframework/gui/components/filebrowser/juce_FileListComponent.cpp index f5aa47f6e3..4dc281c127 100644 --- a/src/juce_appframework/gui/components/filebrowser/juce_FileListComponent.cpp +++ b/src/juce_appframework/gui/components/filebrowser/juce_FileListComponent.cpp @@ -44,7 +44,7 @@ Image* juce_createIconForFile (const File& file); //============================================================================== FileListComponent::FileListComponent (DirectoryContentsList& listToShow) : ListBox (String::empty, 0), - DirectoryContentsDisplayComponent (listToShow) + DirectoryContentsDisplayComponent (listToShow) { setModel (this); fileList.addChangeListener (this); diff --git a/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp b/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp index 4c964cc09a..d7dcf14ef9 100644 --- a/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp +++ b/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp @@ -1200,31 +1200,31 @@ void LookAndFeel::positionComboBoxText (ComboBox& box, Label& label) //============================================================================== void LookAndFeel::drawLabel (Graphics& g, Label& label) { - g.fillAll (label.findColour (Label::backgroundColourId)); - - if (! label.isBeingEdited()) - { - const float alpha = label.isEnabled() ? 1.0f : 0.5f; - - g.setColour (label.findColour (Label::textColourId).withMultipliedAlpha (alpha)); - g.setFont (label.getFont()); - g.drawFittedText (label.getText(), - label.getHorizontalBorderSize(), - label.getVerticalBorderSize(), - label.getWidth() - 2 * label.getHorizontalBorderSize(), - label.getHeight() - 2 * label.getVerticalBorderSize(), - label.getJustificationType(), - jmax (1, (int) (label.getHeight() / label.getFont().getHeight())), - label.getMinimumHorizontalScale()); - - g.setColour (label.findColour (Label::outlineColourId).withMultipliedAlpha (alpha)); - g.drawRect (0, 0, label.getWidth(), label.getHeight()); - } - else if (label.isEnabled()) - { - g.setColour (label.findColour (Label::outlineColourId)); - g.drawRect (0, 0, label.getWidth(), label.getHeight()); - } + g.fillAll (label.findColour (Label::backgroundColourId)); + + if (! label.isBeingEdited()) + { + const float alpha = label.isEnabled() ? 1.0f : 0.5f; + + g.setColour (label.findColour (Label::textColourId).withMultipliedAlpha (alpha)); + g.setFont (label.getFont()); + g.drawFittedText (label.getText(), + label.getHorizontalBorderSize(), + label.getVerticalBorderSize(), + label.getWidth() - 2 * label.getHorizontalBorderSize(), + label.getHeight() - 2 * label.getVerticalBorderSize(), + label.getJustificationType(), + jmax (1, (int) (label.getHeight() / label.getFont().getHeight())), + label.getMinimumHorizontalScale()); + + g.setColour (label.findColour (Label::outlineColourId).withMultipliedAlpha (alpha)); + g.drawRect (0, 0, label.getWidth(), label.getHeight()); + } + else if (label.isEnabled()) + { + g.setColour (label.findColour (Label::outlineColourId)); + g.drawRect (0, 0, label.getWidth(), label.getHeight()); + } } //============================================================================== diff --git a/src/juce_appframework/gui/components/properties/juce_PropertyPanel.cpp b/src/juce_appframework/gui/components/properties/juce_PropertyPanel.cpp index 074fe11092..559612cf53 100644 --- a/src/juce_appframework/gui/components/properties/juce_PropertyPanel.cpp +++ b/src/juce_appframework/gui/components/properties/juce_PropertyPanel.cpp @@ -1,442 +1,442 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_PropertyPanel.h" -#include "../lookandfeel/juce_LookAndFeel.h" -#include "../../../../juce_core/text/juce_LocalisedStrings.h" - - -//============================================================================== -class PropertyHolderComponent : public Component -{ -public: - PropertyHolderComponent() - { - } - - ~PropertyHolderComponent() - { - deleteAllChildren(); - } - - void paint (Graphics&) - { - } - - void updateLayout (const int width); - - void refreshAll() const; -}; - -//============================================================================== -class PropertySectionComponent : public Component -{ -public: - PropertySectionComponent (const String& sectionTitle, - const Array & newProperties, - const bool open) - : Component (sectionTitle), - titleHeight (sectionTitle.isNotEmpty() ? 22 : 0), - isOpen_ (open) - { - for (int i = newProperties.size(); --i >= 0;) - { - addAndMakeVisible (newProperties.getUnchecked(i)); - newProperties.getUnchecked(i)->refresh(); - } - } - - ~PropertySectionComponent() - { - deleteAllChildren(); - } - - void paint (Graphics& g) - { - if (titleHeight > 0) - getLookAndFeel().drawPropertyPanelSectionHeader (g, getName(), isOpen(), getWidth(), titleHeight); - } - - void resized() - { - int y = titleHeight; - - for (int i = getNumChildComponents(); --i >= 0;) - { - PropertyComponent* const pec = dynamic_cast (getChildComponent (i)); - - if (pec != 0) - { - const int prefH = pec->getPreferredHeight(); - pec->setBounds (1, y, getWidth() - 2, prefH); - y += prefH; - } - } - } - - int getPreferredHeight() const - { - int y = titleHeight; - - if (isOpen()) - { - for (int i = 0; i < getNumChildComponents(); ++i) - { - PropertyComponent* pec = dynamic_cast (getChildComponent (i)); - - if (pec != 0) - y += pec->getPreferredHeight(); - } - } - - return y; - } - - void setOpen (const bool open) - { - if (isOpen_ != open) - { - isOpen_ = open; - - for (int i = 0; i < getNumChildComponents(); ++i) - { - PropertyComponent* pec = dynamic_cast (getChildComponent (i)); - - if (pec != 0) - pec->setVisible (open); - } - - // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) - PropertyPanel* const pp = findParentComponentOfClass ((PropertyPanel*) 0); - - if (pp != 0) - pp->resized(); - } - } - - bool isOpen() const throw() - { - return isOpen_; - } - - void refreshAll() const - { - for (int i = 0; i < getNumChildComponents(); ++i) - { - PropertyComponent* pec = dynamic_cast (getChildComponent (i)); - - if (pec != 0) - pec->refresh(); - } - } - - void mouseDown (const MouseEvent&) - { - } - - void mouseUp (const MouseEvent& e) - { - if (e.getMouseDownX() < titleHeight - && e.x < titleHeight - && e.y < titleHeight - && e.getNumberOfClicks() != 2) - { - setOpen (! isOpen()); - } - } - - void mouseDoubleClick (const MouseEvent& e) - { - if (e.y < titleHeight) - setOpen (! isOpen()); - } - -private: - int titleHeight; - bool isOpen_; -}; - -void PropertyHolderComponent::updateLayout (const int width) -{ - int y = 0; - - for (int i = getNumChildComponents(); --i >= 0;) - { - PropertySectionComponent* const section - = dynamic_cast (getChildComponent (i)); - - if (section != 0) - { - const int prefH = section->getPreferredHeight(); - section->setBounds (0, y, width, prefH); - y += prefH; - } - } - - setSize (width, y); - repaint(); -} - -void PropertyHolderComponent::refreshAll() const -{ - for (int i = getNumChildComponents(); --i >= 0;) - { - PropertySectionComponent* const section - = dynamic_cast (getChildComponent (i)); - - if (section != 0) - section->refreshAll(); - } -} - -//============================================================================== -PropertyPanel::PropertyPanel() -{ - messageWhenEmpty = TRANS("(nothing selected)"); - - addAndMakeVisible (viewport = new Viewport()); - viewport->setViewedComponent (propertyHolderComponent = new PropertyHolderComponent()); - viewport->setFocusContainer (true); -} - -PropertyPanel::~PropertyPanel() -{ - clear(); - deleteAllChildren(); -} - -//============================================================================== -void PropertyPanel::paint (Graphics& g) -{ - if (propertyHolderComponent->getNumChildComponents() == 0) - { - g.setColour (Colours::black.withAlpha (0.5f)); - g.setFont (14.0f); - g.drawText (messageWhenEmpty, 0, 0, getWidth(), 30, - Justification::centred, true); - } -} - -void PropertyPanel::resized() -{ - viewport->setBounds (0, 0, getWidth(), getHeight()); - updatePropHolderLayout(); -} - -//============================================================================== -void PropertyPanel::clear() -{ - if (propertyHolderComponent->getNumChildComponents() > 0) - { - propertyHolderComponent->deleteAllChildren(); - repaint(); - } -} - -void PropertyPanel::addProperties (const Array & newProperties) -{ - if (propertyHolderComponent->getNumChildComponents() == 0) - repaint(); - - propertyHolderComponent->addAndMakeVisible (new PropertySectionComponent (String::empty, - newProperties, - true), 0); - updatePropHolderLayout(); -} - -void PropertyPanel::addSection (const String& sectionTitle, - const Array & newProperties, - const bool shouldBeOpen) -{ - jassert (sectionTitle.isNotEmpty()); - - if (propertyHolderComponent->getNumChildComponents() == 0) - repaint(); - - propertyHolderComponent->addAndMakeVisible (new PropertySectionComponent (sectionTitle, - newProperties, - shouldBeOpen), 0); - - updatePropHolderLayout(); -} - -void PropertyPanel::updatePropHolderLayout() const -{ - const int maxWidth = viewport->getMaximumVisibleWidth(); - ((PropertyHolderComponent*) propertyHolderComponent)->updateLayout (maxWidth); - - const int newMaxWidth = viewport->getMaximumVisibleWidth(); - if (maxWidth != newMaxWidth) - { - // need to do this twice because of scrollbars changing the size, etc. - ((PropertyHolderComponent*) propertyHolderComponent)->updateLayout (newMaxWidth); - } -} - -void PropertyPanel::refreshAll() const -{ - ((PropertyHolderComponent*) propertyHolderComponent)->refreshAll(); -} - -//============================================================================== -const StringArray PropertyPanel::getSectionNames() const -{ - StringArray s; - - for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) - { - PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); - - if (section != 0 && section->getName().isNotEmpty()) - s.add (section->getName()); - } - - return s; -} - -bool PropertyPanel::isSectionOpen (const int sectionIndex) const -{ - int index = 0; - - for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) - { - PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); - - if (section != 0 && section->getName().isNotEmpty()) - { - if (index == sectionIndex) - return section->isOpen(); - - ++index; - } - } - - return false; -} - -void PropertyPanel::setSectionOpen (const int sectionIndex, const bool shouldBeOpen) -{ - int index = 0; - - for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) - { - PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); - - if (section != 0 && section->getName().isNotEmpty()) - { - if (index == sectionIndex) - { - section->setOpen (shouldBeOpen); - break; - } - - ++index; - } - } -} - -void PropertyPanel::setSectionEnabled (const int sectionIndex, const bool shouldBeEnabled) -{ - int index = 0; - - for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) - { - PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); - - if (section != 0 && section->getName().isNotEmpty()) - { - if (index == sectionIndex) - { - section->setEnabled (shouldBeEnabled); - break; - } - - ++index; - } - } -} - -//============================================================================== -XmlElement* PropertyPanel::getOpennessState() const -{ - XmlElement* const xml = new XmlElement (T("PROPERTYPANELSTATE")); - - const StringArray sections (getSectionNames()); - - for (int i = 0; i < sections.size(); ++i) - { - if (sections[i].isNotEmpty()) - { - XmlElement* const e = new XmlElement (T("SECTION")); - e->setAttribute (T("name"), sections[i]); - e->setAttribute (T("open"), isSectionOpen (i) ? 1 : 0); - xml->addChildElement (e); - } - } - - return xml; -} - -void PropertyPanel::restoreOpennessState (const XmlElement& xml) -{ - if (xml.hasTagName (T("PROPERTYPANELSTATE"))) - { - const StringArray sections (getSectionNames()); - - forEachXmlChildElementWithTagName (xml, e, T("SECTION")) - { - setSectionOpen (sections.indexOf (e->getStringAttribute (T("name"))), - e->getBoolAttribute (T("open"))); - } - } -} - -//============================================================================== -void PropertyPanel::setMessageWhenEmpty (const String& newMessage) -{ - if (messageWhenEmpty != newMessage) - { - messageWhenEmpty = newMessage; - repaint(); - } -} - -const String& PropertyPanel::getMessageWhenEmpty() const throw() -{ - return messageWhenEmpty; -} - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_PropertyPanel.h" +#include "../lookandfeel/juce_LookAndFeel.h" +#include "../../../../juce_core/text/juce_LocalisedStrings.h" + + +//============================================================================== +class PropertyHolderComponent : public Component +{ +public: + PropertyHolderComponent() + { + } + + ~PropertyHolderComponent() + { + deleteAllChildren(); + } + + void paint (Graphics&) + { + } + + void updateLayout (const int width); + + void refreshAll() const; +}; + +//============================================================================== +class PropertySectionComponent : public Component +{ +public: + PropertySectionComponent (const String& sectionTitle, + const Array & newProperties, + const bool open) + : Component (sectionTitle), + titleHeight (sectionTitle.isNotEmpty() ? 22 : 0), + isOpen_ (open) + { + for (int i = newProperties.size(); --i >= 0;) + { + addAndMakeVisible (newProperties.getUnchecked(i)); + newProperties.getUnchecked(i)->refresh(); + } + } + + ~PropertySectionComponent() + { + deleteAllChildren(); + } + + void paint (Graphics& g) + { + if (titleHeight > 0) + getLookAndFeel().drawPropertyPanelSectionHeader (g, getName(), isOpen(), getWidth(), titleHeight); + } + + void resized() + { + int y = titleHeight; + + for (int i = getNumChildComponents(); --i >= 0;) + { + PropertyComponent* const pec = dynamic_cast (getChildComponent (i)); + + if (pec != 0) + { + const int prefH = pec->getPreferredHeight(); + pec->setBounds (1, y, getWidth() - 2, prefH); + y += prefH; + } + } + } + + int getPreferredHeight() const + { + int y = titleHeight; + + if (isOpen()) + { + for (int i = 0; i < getNumChildComponents(); ++i) + { + PropertyComponent* pec = dynamic_cast (getChildComponent (i)); + + if (pec != 0) + y += pec->getPreferredHeight(); + } + } + + return y; + } + + void setOpen (const bool open) + { + if (isOpen_ != open) + { + isOpen_ = open; + + for (int i = 0; i < getNumChildComponents(); ++i) + { + PropertyComponent* pec = dynamic_cast (getChildComponent (i)); + + if (pec != 0) + pec->setVisible (open); + } + + // (unable to use the syntax findParentComponentOfClass () because of a VC6 compiler bug) + PropertyPanel* const pp = findParentComponentOfClass ((PropertyPanel*) 0); + + if (pp != 0) + pp->resized(); + } + } + + bool isOpen() const throw() + { + return isOpen_; + } + + void refreshAll() const + { + for (int i = 0; i < getNumChildComponents(); ++i) + { + PropertyComponent* pec = dynamic_cast (getChildComponent (i)); + + if (pec != 0) + pec->refresh(); + } + } + + void mouseDown (const MouseEvent&) + { + } + + void mouseUp (const MouseEvent& e) + { + if (e.getMouseDownX() < titleHeight + && e.x < titleHeight + && e.y < titleHeight + && e.getNumberOfClicks() != 2) + { + setOpen (! isOpen()); + } + } + + void mouseDoubleClick (const MouseEvent& e) + { + if (e.y < titleHeight) + setOpen (! isOpen()); + } + +private: + int titleHeight; + bool isOpen_; +}; + +void PropertyHolderComponent::updateLayout (const int width) +{ + int y = 0; + + for (int i = getNumChildComponents(); --i >= 0;) + { + PropertySectionComponent* const section + = dynamic_cast (getChildComponent (i)); + + if (section != 0) + { + const int prefH = section->getPreferredHeight(); + section->setBounds (0, y, width, prefH); + y += prefH; + } + } + + setSize (width, y); + repaint(); +} + +void PropertyHolderComponent::refreshAll() const +{ + for (int i = getNumChildComponents(); --i >= 0;) + { + PropertySectionComponent* const section + = dynamic_cast (getChildComponent (i)); + + if (section != 0) + section->refreshAll(); + } +} + +//============================================================================== +PropertyPanel::PropertyPanel() +{ + messageWhenEmpty = TRANS("(nothing selected)"); + + addAndMakeVisible (viewport = new Viewport()); + viewport->setViewedComponent (propertyHolderComponent = new PropertyHolderComponent()); + viewport->setFocusContainer (true); +} + +PropertyPanel::~PropertyPanel() +{ + clear(); + deleteAllChildren(); +} + +//============================================================================== +void PropertyPanel::paint (Graphics& g) +{ + if (propertyHolderComponent->getNumChildComponents() == 0) + { + g.setColour (Colours::black.withAlpha (0.5f)); + g.setFont (14.0f); + g.drawText (messageWhenEmpty, 0, 0, getWidth(), 30, + Justification::centred, true); + } +} + +void PropertyPanel::resized() +{ + viewport->setBounds (0, 0, getWidth(), getHeight()); + updatePropHolderLayout(); +} + +//============================================================================== +void PropertyPanel::clear() +{ + if (propertyHolderComponent->getNumChildComponents() > 0) + { + propertyHolderComponent->deleteAllChildren(); + repaint(); + } +} + +void PropertyPanel::addProperties (const Array & newProperties) +{ + if (propertyHolderComponent->getNumChildComponents() == 0) + repaint(); + + propertyHolderComponent->addAndMakeVisible (new PropertySectionComponent (String::empty, + newProperties, + true), 0); + updatePropHolderLayout(); +} + +void PropertyPanel::addSection (const String& sectionTitle, + const Array & newProperties, + const bool shouldBeOpen) +{ + jassert (sectionTitle.isNotEmpty()); + + if (propertyHolderComponent->getNumChildComponents() == 0) + repaint(); + + propertyHolderComponent->addAndMakeVisible (new PropertySectionComponent (sectionTitle, + newProperties, + shouldBeOpen), 0); + + updatePropHolderLayout(); +} + +void PropertyPanel::updatePropHolderLayout() const +{ + const int maxWidth = viewport->getMaximumVisibleWidth(); + ((PropertyHolderComponent*) propertyHolderComponent)->updateLayout (maxWidth); + + const int newMaxWidth = viewport->getMaximumVisibleWidth(); + if (maxWidth != newMaxWidth) + { + // need to do this twice because of scrollbars changing the size, etc. + ((PropertyHolderComponent*) propertyHolderComponent)->updateLayout (newMaxWidth); + } +} + +void PropertyPanel::refreshAll() const +{ + ((PropertyHolderComponent*) propertyHolderComponent)->refreshAll(); +} + +//============================================================================== +const StringArray PropertyPanel::getSectionNames() const +{ + StringArray s; + + for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) + { + PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); + + if (section != 0 && section->getName().isNotEmpty()) + s.add (section->getName()); + } + + return s; +} + +bool PropertyPanel::isSectionOpen (const int sectionIndex) const +{ + int index = 0; + + for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) + { + PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); + + if (section != 0 && section->getName().isNotEmpty()) + { + if (index == sectionIndex) + return section->isOpen(); + + ++index; + } + } + + return false; +} + +void PropertyPanel::setSectionOpen (const int sectionIndex, const bool shouldBeOpen) +{ + int index = 0; + + for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) + { + PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); + + if (section != 0 && section->getName().isNotEmpty()) + { + if (index == sectionIndex) + { + section->setOpen (shouldBeOpen); + break; + } + + ++index; + } + } +} + +void PropertyPanel::setSectionEnabled (const int sectionIndex, const bool shouldBeEnabled) +{ + int index = 0; + + for (int i = 0; i < propertyHolderComponent->getNumChildComponents(); ++i) + { + PropertySectionComponent* const section = dynamic_cast (propertyHolderComponent->getChildComponent (i)); + + if (section != 0 && section->getName().isNotEmpty()) + { + if (index == sectionIndex) + { + section->setEnabled (shouldBeEnabled); + break; + } + + ++index; + } + } +} + +//============================================================================== +XmlElement* PropertyPanel::getOpennessState() const +{ + XmlElement* const xml = new XmlElement (T("PROPERTYPANELSTATE")); + + const StringArray sections (getSectionNames()); + + for (int i = 0; i < sections.size(); ++i) + { + if (sections[i].isNotEmpty()) + { + XmlElement* const e = new XmlElement (T("SECTION")); + e->setAttribute (T("name"), sections[i]); + e->setAttribute (T("open"), isSectionOpen (i) ? 1 : 0); + xml->addChildElement (e); + } + } + + return xml; +} + +void PropertyPanel::restoreOpennessState (const XmlElement& xml) +{ + if (xml.hasTagName (T("PROPERTYPANELSTATE"))) + { + const StringArray sections (getSectionNames()); + + forEachXmlChildElementWithTagName (xml, e, T("SECTION")) + { + setSectionOpen (sections.indexOf (e->getStringAttribute (T("name"))), + e->getBoolAttribute (T("open"))); + } + } +} + +//============================================================================== +void PropertyPanel::setMessageWhenEmpty (const String& newMessage) +{ + if (messageWhenEmpty != newMessage) + { + messageWhenEmpty = newMessage; + repaint(); + } +} + +const String& PropertyPanel::getMessageWhenEmpty() const throw() +{ + return messageWhenEmpty; +} + + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.cpp b/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.cpp index fb40cc956e..47e4a61bd7 100644 --- a/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.cpp +++ b/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.cpp @@ -42,7 +42,7 @@ BEGIN_JUCE_NAMESPACE //============================================================================== -class SimpleDeviceManagerInputLevelMeter : public Component, +class SimpleDeviceManagerInputLevelMeter : public Component, public Timer { public: @@ -214,7 +214,7 @@ class AudioDeviceSettingsPanel : public Component, public ButtonListener { public: - AudioDeviceSettingsPanel (AudioIODeviceType* type_, + AudioDeviceSettingsPanel (AudioIODeviceType* type_, AudioIODeviceType::DeviceSetupDetails& setup_, const bool hideAdvancedOptionsWithButton) : type (type_), @@ -279,7 +279,7 @@ public: outputDeviceDropDown->setBounds (lx, y, w, h); if (testButton != 0) - testButton->setBounds (proportionOfWidth (0.77f), + testButton->setBounds (proportionOfWidth (0.77f), outputDeviceDropDown->getY(), proportionOfWidth (0.18f), h); @@ -290,7 +290,7 @@ public: { inputDeviceDropDown->setBounds (lx, y, w, h); - inputLevelMeter->setBounds (proportionOfWidth (0.77f), + inputLevelMeter->setBounds (proportionOfWidth (0.77f), inputDeviceDropDown->getY(), proportionOfWidth (0.18f), h); @@ -323,7 +323,7 @@ public: if (sampleRateDropDown != 0) { - sampleRateDropDown->setVisible (showAdvancedSettingsButton == 0 + sampleRateDropDown->setVisible (showAdvancedSettingsButton == 0 || ! showAdvancedSettingsButton->isVisible()); sampleRateDropDown->setBounds (lx, y, w, h); @@ -360,11 +360,11 @@ public: || comboBoxThatHasChanged == inputDeviceDropDown) { if (outputDeviceDropDown != 0) - config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String::empty + config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String::empty : outputDeviceDropDown->getText(); if (inputDeviceDropDown != 0) - config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String::empty + config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String::empty : inputDeviceDropDown->getText(); if (! type->hasSeparateInputsAndOutputs()) @@ -460,7 +460,7 @@ public: outputDeviceDropDown->addListener (this); addAndMakeVisible (outputDeviceDropDown); - outputDeviceLabel = new Label (String::empty, + outputDeviceLabel = new Label (String::empty, type->hasSeparateInputsAndOutputs() ? TRANS ("output:") : TRANS ("device:")); outputDeviceLabel->attachToComponent (outputDeviceDropDown, true); @@ -486,7 +486,7 @@ public: inputDeviceLabel = new Label (String::empty, TRANS ("input:")); inputDeviceLabel->attachToComponent (inputDeviceDropDown, true); - addAndMakeVisible (inputLevelMeter + addAndMakeVisible (inputLevelMeter = new SimpleDeviceManagerInputLevelMeter (setup.manager)); } @@ -499,13 +499,13 @@ public: if (currentDevice != 0) { - if (setup.maxNumOutputChannels > 0 + if (setup.maxNumOutputChannels > 0 && setup.minNumOutputChannels < setup.manager->getCurrentAudioDevice()->getOutputChannelNames().size()) { if (outputChanList == 0) { - addAndMakeVisible (outputChanList - = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioOutputType, + addAndMakeVisible (outputChanList + = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioOutputType, TRANS ("(no audio output channels found)"))); outputChanLabel = new Label (String::empty, TRANS ("active output channels:")); outputChanLabel->attachToComponent (outputChanList, true); @@ -525,7 +525,7 @@ public: if (inputChanList == 0) { addAndMakeVisible (inputChanList - = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioInputType, + = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioInputType, TRANS ("(no audio input channels found)"))); inputChanLabel = new Label (String::empty, TRANS ("active input channels:")); inputChanLabel->attachToComponent (inputChanList, true); @@ -832,7 +832,7 @@ private: int getBestHeight (int maxHeight) { - return getRowHeight() * jlimit (2, jmax (2, maxHeight / getRowHeight()), + return getRowHeight() * jlimit (2, jmax (2, maxHeight / getRowHeight()), getNumRows()) + getOutlineThickness() * 2; } @@ -974,7 +974,7 @@ AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& for (int i = 0; i < deviceManager_.getAvailableDeviceTypes().size(); ++i) { deviceTypeDropDown - ->addItem (deviceManager_.getAvailableDeviceTypes().getUnchecked(i)->getTypeName(), + ->addItem (deviceManager_.getAvailableDeviceTypes().getUnchecked(i)->getTypeName(), i + 1); } @@ -1100,15 +1100,15 @@ void AudioDeviceSelectorComponent::changeListenerCallback (void*) deviceTypeDropDown->setText (deviceManager.getCurrentAudioDeviceType(), false); } - if (audioDeviceSettingsComp == 0 + if (audioDeviceSettingsComp == 0 || audioDeviceSettingsCompType != deviceManager.getCurrentAudioDeviceType()) { audioDeviceSettingsCompType = deviceManager.getCurrentAudioDeviceType(); deleteAndZero (audioDeviceSettingsComp); - AudioIODeviceType* const type - = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown == 0 + AudioIODeviceType* const type + = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown == 0 ? 0 : deviceTypeDropDown->getSelectedId() - 1]; if (type != 0) diff --git a/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.h b/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.h index fab0a65905..98297a9291 100644 --- a/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.h +++ b/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.h @@ -66,7 +66,7 @@ public: @param maxAudioOutputChannels the maximum number of audio output channels that the application needs @param showMidiInputOptions if true, the component will allow the user to select which midi inputs are enabled @param showMidiOutputSelector if true, the component will let the user choose a default midi output device - @param showChannelsAsStereoPairs if true, channels will be treated as pairs; if false, channels will be + @param showChannelsAsStereoPairs if true, channels will be treated as pairs; if false, channels will be treated as a set of separate mono channels. @param hideAdvancedOptionsWithButton if true, only the minimum amount of UI components are shown, with an "advanced" button that shows the rest of them diff --git a/src/juce_appframework/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp b/src/juce_appframework/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp index 6d27b67117..5611fa52fa 100644 --- a/src/juce_appframework/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp +++ b/src/juce_appframework/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp @@ -1,2228 +1,2228 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_LowLevelGraphicsSoftwareRenderer.h" -#include "juce_EdgeTable.h" -#include "../imaging/juce_Image.h" -#include "../colour/juce_PixelFormats.h" -#include "../geometry/juce_PathStrokeType.h" -#include "../geometry/juce_Rectangle.h" -#include "../../../../juce_core/basics/juce_SystemStats.h" - -#if ! (defined (JUCE_MAC) || (defined (JUCE_WIN32) && defined (JUCE_64BIT))) - #define JUCE_USE_SSE_INSTRUCTIONS 1 -#endif - -#if defined (JUCE_DEBUG) && JUCE_MSVC - #pragma warning (disable: 4714) -#endif - -#define MINIMUM_COORD -0x3fffffff -#define MAXIMUM_COORD 0x3fffffff - -#undef ASSERT_COORDS_ARE_SENSIBLE_NUMBERS -#define ASSERT_COORDS_ARE_SENSIBLE_NUMBERS(x, y, w, h) \ - jassert ((int) x >= MINIMUM_COORD \ - && (int) x <= MAXIMUM_COORD \ - && (int) y >= MINIMUM_COORD \ - && (int) y <= MAXIMUM_COORD \ - && (int) w >= 0 \ - && (int) w < MAXIMUM_COORD \ - && (int) h >= 0 \ - && (int) h < MAXIMUM_COORD); - -//============================================================================== -static void replaceRectRGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw() -{ - const PixelARGB blendColour (colour.getPixelARGB()); - - if (w < 32) - { - while (--h >= 0) - { - PixelRGB* dest = (PixelRGB*) pixels; - - for (int i = w; --i >= 0;) - (dest++)->set (blendColour); - - pixels += stride; - } - } - else - { - // for wider fills, it's worth using some optimisations.. - - const uint8 r = blendColour.getRed(); - const uint8 g = blendColour.getGreen(); - const uint8 b = blendColour.getBlue(); - - if (r == g && r == b) // if all the component values are the same, we can cheat.. - { - while (--h >= 0) - { - memset (pixels, r, w * 3); - pixels += stride; - } - } - else - { - PixelRGB filler [4]; - filler[0].set (blendColour); - filler[1].set (blendColour); - filler[2].set (blendColour); - filler[3].set (blendColour); - const int* const intFiller = (const int*) filler; - - while (--h >= 0) - { - uint8* dest = (uint8*) pixels; - - int i = w; - - while ((i > 8) && (((pointer_sized_int) dest & 7) != 0)) - { - ((PixelRGB*) dest)->set (blendColour); - dest += 3; - --i; - } - - while (i >= 4) - { - ((int*) dest) [0] = intFiller[0]; - ((int*) dest) [1] = intFiller[1]; - ((int*) dest) [2] = intFiller[2]; - - dest += 12; - i -= 4; - } - - while (--i >= 0) - { - ((PixelRGB*) dest)->set (blendColour); - dest += 3; - } - - pixels += stride; - } - } - } -} - -static void replaceRectARGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw() -{ - const PixelARGB blendColour (colour.getPixelARGB()); - - while (--h >= 0) - { - PixelARGB* const dest = (PixelARGB*) pixels; - - for (int i = 0; i < w; ++i) - dest[i] = blendColour; - - pixels += stride; - } -} - -static void blendRectRGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw() -{ - if (colour.isOpaque()) - { - replaceRectRGB (pixels, w, h, stride, colour); - } - else - { - const PixelARGB blendColour (colour.getPixelARGB()); - const int alpha = blendColour.getAlpha(); - - if (alpha <= 0) - return; - -#if defined (JUCE_USE_SSE_INSTRUCTIONS) && ! JUCE_64BIT - if (SystemStats::hasSSE()) - { - int64 rgb0 = (((int64) blendColour.getRed()) << 32) - | (int64) ((blendColour.getGreen() << 16) - | blendColour.getBlue()); - - const int invAlpha = 0xff - alpha; - int64 aaaa = (invAlpha << 16) | invAlpha; - aaaa = (aaaa << 16) | aaaa; - -#ifndef JUCE_GCC - __asm - { - movq mm1, aaaa - movq mm2, rgb0 - pxor mm7, mm7 - } - - while (--h >= 0) - { - __asm - { - mov edx, pixels - mov ebx, w - - pixloop: - prefetchnta [edx] - mov ax, [edx + 1] - shl eax, 8 - mov al, [edx] - movd mm0, eax - - punpcklbw mm0, mm7 - pmullw mm0, mm1 - psrlw mm0, 8 - paddw mm0, mm2 - packuswb mm0, mm7 - - movd eax, mm0 - mov [edx], al - inc edx - shr eax, 8 - mov [edx], ax - add edx, 2 - - dec ebx - jg pixloop - } - - pixels += stride; - } - - __asm emms -#else - __asm__ __volatile__ ( - "movq %[aaaa], %%mm1 \n" - "\tmovq %[rgb0], %%mm2 \n" - "\tpxor %%mm7, %%mm7 \n" - ".lineLoop2: \n" - "\tmovl %%esi,%%edx \n" - "\tmovl %[w], %%ebx \n" - ".pixLoop2: \n" - "\tprefetchnta (%%edx) \n" - "\tmov (%%edx), %%ax \n" - "\tshl $8, %%eax \n" - "\tmov 2(%%edx), %%al \n" - "\tmovd %%eax, %%mm0 \n" - "\tpunpcklbw %%mm7, %%mm0 \n" - "\tpmullw %%mm1, %%mm0 \n" - "\tpsrlw $8, %%mm0 \n" - "\tpaddw %%mm2, %%mm0 \n" - "\tpackuswb %%mm7, %%mm0 \n" - "\tmovd %%mm0, %%eax \n" - "\tmovb %%al, (%%edx) \n" - "\tinc %%edx \n" - "\tshr $8, %%eax \n" - "\tmovw %%ax, (%%edx) \n" - "\tadd $2, %%edx \n" - "\tdec %%ebx \n" - "\tjg .pixLoop2 \n" - "\tadd %%edi, %%esi \n" - "\tdec %%ecx \n" - "\tjg .lineLoop2 \n" - "\temms \n" - : /* No output registers */ - : [aaaa] "m" (aaaa), /* Input registers */ - [rgb0] "m" (rgb0), - [w] "m" (w), - "c" (h), - [stride] "D" (stride), - [pixels] "S" (pixels) - : "cc", "eax", "edx", "memory" /* Clobber list */ - ); -#endif - } - else -#endif - { - while (--h >= 0) - { - PixelRGB* dest = (PixelRGB*) pixels; - - for (int i = w; --i >= 0;) - (dest++)->blend (blendColour); - - pixels += stride; - } - } - } -} - -static void blendRectARGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw() -{ - if (colour.isOpaque()) - { - replaceRectARGB (pixels, w, h, stride, colour); - } - else - { - const PixelARGB blendColour (colour.getPixelARGB()); - const int alpha = blendColour.getAlpha(); - - if (alpha <= 0) - return; - - while (--h >= 0) - { - PixelARGB* dest = (PixelARGB*) pixels; - - for (int i = w; --i >= 0;) - (dest++)->blend (blendColour); - - pixels += stride; - } - } -} - -//============================================================================== -static void blendAlphaMapARGB (uint8* destPixel, const int imageStride, - const uint8* alphaValues, const int w, int h, - const int pixelStride, const int lineStride, - const Colour& colour) throw() -{ - const PixelARGB srcPix (colour.getPixelARGB()); - - while (--h >= 0) - { - PixelARGB* dest = (PixelARGB*) destPixel; - const uint8* src = alphaValues; - - int i = w; - while (--i >= 0) - { - unsigned int srcAlpha = *src; - src += pixelStride; - - if (srcAlpha > 0) - dest->blend (srcPix, srcAlpha); - - ++dest; - } - - alphaValues += lineStride; - destPixel += imageStride; - } -} - -static void blendAlphaMapRGB (uint8* destPixel, const int imageStride, - const uint8* alphaValues, int const width, int height, - const int pixelStride, const int lineStride, - const Colour& colour) throw() -{ - const PixelARGB srcPix (colour.getPixelARGB()); - - while (--height >= 0) - { - PixelRGB* dest = (PixelRGB*) destPixel; - const uint8* src = alphaValues; - - int i = width; - while (--i >= 0) - { - unsigned int srcAlpha = *src; - src += pixelStride; - - if (srcAlpha > 0) - dest->blend (srcPix, srcAlpha); - - ++dest; - } - - alphaValues += lineStride; - destPixel += imageStride; - } -} - -//============================================================================== -template -class SolidColourEdgeTableRenderer -{ - uint8* const data; - const int stride; - PixelType* linePixels; - PixelARGB sourceColour; - - SolidColourEdgeTableRenderer (const SolidColourEdgeTableRenderer&); - const SolidColourEdgeTableRenderer& operator= (const SolidColourEdgeTableRenderer&); - -public: - SolidColourEdgeTableRenderer (uint8* const data_, - const int stride_, - const Colour& colour) throw() - : data (data_), - stride (stride_), - sourceColour (colour.getPixelARGB()) - { - } - - forcedinline void setEdgeTableYPos (const int y) throw() - { - linePixels = (PixelType*) (data + stride * y); - } - - forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw() - { - linePixels[x].blend (sourceColour, alphaLevel); - } - - forcedinline void handleEdgeTableLine (const int x, int width, const int alphaLevel) const throw() - { - PixelARGB p (sourceColour); - p.multiplyAlpha (alphaLevel); - - PixelType* dest = linePixels + x; - - if (p.getAlpha() < 0xff) - { - do - { - dest->blend (p); - ++dest; - - } while (--width > 0); - } - else - { - do - { - dest->set (p); - ++dest; - - } while (--width > 0); - } - } -}; - -class AlphaBitmapRenderer -{ - uint8* data; - int stride; - uint8* lineStart; - - AlphaBitmapRenderer (const AlphaBitmapRenderer&); - const AlphaBitmapRenderer& operator= (const AlphaBitmapRenderer&); - -public: - AlphaBitmapRenderer (uint8* const data_, - const int stride_) throw() - : data (data_), - stride (stride_) - { - } - - forcedinline void setEdgeTableYPos (const int y) throw() - { - lineStart = data + (stride * y); - } - - forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw() - { - lineStart [x] = (uint8) alphaLevel; - } - - forcedinline void handleEdgeTableLine (const int x, int width, const int alphaLevel) const throw() - { - uint8* d = lineStart + x; - - while (--width >= 0) - *d++ = (uint8) alphaLevel; - } -}; - -//============================================================================== -static const int numScaleBits = 12; - -class LinearGradientPixelGenerator -{ - const PixelARGB* const lookupTable; - const int numEntries; - PixelARGB linePix; - int start, scale; - double grad, yTerm; - bool vertical, horizontal; - - LinearGradientPixelGenerator (const LinearGradientPixelGenerator&); - const LinearGradientPixelGenerator& operator= (const LinearGradientPixelGenerator&); - -public: - LinearGradientPixelGenerator (const ColourGradient& gradient, - const PixelARGB* const lookupTable_, const int numEntries_) - : lookupTable (lookupTable_), - numEntries (numEntries_) - { - jassert (numEntries_ >= 0); - float x1 = gradient.x1; - float y1 = gradient.y1; - float x2 = gradient.x2; - float y2 = gradient.y2; - - if (! gradient.transform.isIdentity()) - { - Line l (x2, y2, x1, y1); - const Point p3 = l.getPointAlongLine (0.0, 100.0f); - float x3 = p3.getX(); - float y3 = p3.getY(); - - gradient.transform.transformPoint (x1, y1); - gradient.transform.transformPoint (x2, y2); - gradient.transform.transformPoint (x3, y3); - - Line l2 (x2, y2, x3, y3); - float prop = l2.findNearestPointTo (x1, y1); - const Point newP2 (l2.getPointAlongLineProportionally (prop)); - - x2 = newP2.getX(); - y2 = newP2.getY(); - } - - vertical = fabs (x1 - x2) < 0.001f; - horizontal = fabs (y1 - y2) < 0.001f; - - if (vertical) - { - scale = roundDoubleToInt ((numEntries << numScaleBits) / (double) (y2 - y1)); - start = roundDoubleToInt (y1 * scale); - } - else if (horizontal) - { - scale = roundDoubleToInt ((numEntries << numScaleBits) / (double) (x2 - x1)); - start = roundDoubleToInt (x1 * scale); - } - else - { - grad = (y2 - y1) / (double) (x1 - x2); - yTerm = y1 - x1 / grad; - scale = roundDoubleToInt ((numEntries << numScaleBits) / (yTerm * grad - (y2 * grad - x2))); - grad *= scale; - } - } - - forcedinline void setY (const int y) throw() - { - if (vertical) - linePix = lookupTable [jlimit (0, numEntries, (y * scale - start) >> numScaleBits)]; - else if (! horizontal) - start = roundDoubleToInt ((y - yTerm) * grad); - } - - forcedinline const PixelARGB getPixel (const int x) const throw() - { - if (vertical) - return linePix; - - return lookupTable [jlimit (0, numEntries, (x * scale - start) >> numScaleBits)]; - } -}; - -class RadialGradientPixelGenerator -{ -protected: - const PixelARGB* const lookupTable; - const int numEntries; - const double gx1, gy1; - double maxDist, invScale; - double dy; - - RadialGradientPixelGenerator (const RadialGradientPixelGenerator&); - const RadialGradientPixelGenerator& operator= (const RadialGradientPixelGenerator&); - -public: - RadialGradientPixelGenerator (const ColourGradient& gradient, - const PixelARGB* const lookupTable_, const int numEntries_) throw() - : lookupTable (lookupTable_), - numEntries (numEntries_), - gx1 (gradient.x1), - gy1 (gradient.y1) - { - jassert (numEntries_ >= 0); - const float dx = gradient.x1 - gradient.x2; - const float dy = gradient.y1 - gradient.y2; - maxDist = dx * dx + dy * dy; - invScale = (numEntries + 1) / sqrt (maxDist); - } - - forcedinline void setY (const int y) throw() - { - dy = y - gy1; - dy *= dy; - } - - forcedinline const PixelARGB getPixel (const int px) const throw() - { - double x = px - gx1; - x *= x; - x += dy; - - if (x >= maxDist) - return lookupTable [numEntries]; - else - return lookupTable [jmin (numEntries, roundDoubleToInt (sqrt (x) * invScale))]; - } -}; - -class TransformedRadialGradientPixelGenerator : public RadialGradientPixelGenerator -{ - double tM10, tM00, lineYM01, lineYM11; - AffineTransform inverseTransform; - - TransformedRadialGradientPixelGenerator (const TransformedRadialGradientPixelGenerator&); - const TransformedRadialGradientPixelGenerator& operator= (const TransformedRadialGradientPixelGenerator&); - -public: - TransformedRadialGradientPixelGenerator (const ColourGradient& gradient, - const PixelARGB* const lookupTable_, const int numEntries_) throw() - : RadialGradientPixelGenerator (gradient, lookupTable_, numEntries_), - inverseTransform (gradient.transform.inverted()) - { - tM10 = inverseTransform.mat10; - tM00 = inverseTransform.mat00; - } - - forcedinline void setY (const int y) throw() - { - lineYM01 = inverseTransform.mat01 * y + inverseTransform.mat02 - gx1; - lineYM11 = inverseTransform.mat11 * y + inverseTransform.mat12 - gy1; - } - - forcedinline const PixelARGB getPixel (const int px) const throw() - { - double x = px; - const double y = tM10 * x + lineYM11; - x = tM00 * x + lineYM01; - x *= x; - x += y * y; - - if (x >= maxDist) - return lookupTable [numEntries]; - else - return lookupTable [jmin (numEntries, roundDoubleToInt (sqrt (x) * invScale))]; - } -}; - -template -class GradientEdgeTableRenderer : public GradientType -{ - uint8* const data; - const int stride; - PixelType* linePixels; - - GradientEdgeTableRenderer (const GradientEdgeTableRenderer&); - const GradientEdgeTableRenderer& operator= (const GradientEdgeTableRenderer&); - -public: - GradientEdgeTableRenderer (uint8* const data_, - const int stride_, - const ColourGradient& gradient, - const PixelARGB* const lookupTable, const int numEntries) throw() - : GradientType (gradient, lookupTable, numEntries - 1), - data (data_), - stride (stride_) - { - } - - forcedinline void setEdgeTableYPos (const int y) throw() - { - linePixels = (PixelType*) (data + stride * y); - GradientType::setY (y); - } - - forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw() - { - linePixels[x].blend (GradientType::getPixel (x), alphaLevel); - } - - forcedinline void handleEdgeTableLine (int x, int width, const int alphaLevel) const throw() - { - PixelType* dest = linePixels + x; - - if (alphaLevel < 0xff) - { - do - { - (dest++)->blend (GradientType::getPixel (x++), alphaLevel); - - } while (--width > 0); - } - else - { - do - { - (dest++)->blend (GradientType::getPixel (x++)); - - } while (--width > 0); - } - } -}; - -//============================================================================== -template -class ImageFillEdgeTableRenderer -{ - uint8* const destImageData; - const uint8* srcImageData; - int stride, srcStride, extraAlpha; - - DestPixelType* linePixels; - SrcPixelType* sourceLineStart; - - ImageFillEdgeTableRenderer (const ImageFillEdgeTableRenderer&); - const ImageFillEdgeTableRenderer& operator= (const ImageFillEdgeTableRenderer&); - -public: - ImageFillEdgeTableRenderer (uint8* const destImageData_, - const int stride_, - const uint8* srcImageData_, - const int srcStride_, - int extraAlpha_, - SrcPixelType*) throw() // dummy param to avoid compiler error - : destImageData (destImageData_), - srcImageData (srcImageData_), - stride (stride_), - srcStride (srcStride_), - extraAlpha (extraAlpha_) - { - } - - forcedinline void setEdgeTableYPos (int y) throw() - { - linePixels = (DestPixelType*) (destImageData + stride * y); - sourceLineStart = (SrcPixelType*) (srcImageData + srcStride * y); - } - - forcedinline void handleEdgeTablePixel (const int x, int alphaLevel) const throw() - { - alphaLevel = (alphaLevel * extraAlpha) >> 8; - - linePixels[x].blend (sourceLineStart [x], alphaLevel); - } - - forcedinline void handleEdgeTableLine (int x, int width, int alphaLevel) const throw() - { - DestPixelType* dest = linePixels + x; - alphaLevel = (alphaLevel * extraAlpha) >> 8; - - if (alphaLevel < 0xfe) - { - do - { - dest++ ->blend (sourceLineStart [x++], alphaLevel); - - } while (--width > 0); - } - else - { - do - { - dest++ ->blend (sourceLineStart [x++]); - - } while (--width > 0); - } - } -}; - -//============================================================================== -static void blendRowOfPixels (PixelARGB* dst, - const PixelRGB* src, - int width) throw() -{ - while (--width >= 0) - (dst++)->set (*src++); -} - -static void blendRowOfPixels (PixelRGB* dst, - const PixelRGB* src, - int width) throw() -{ - memcpy (dst, src, 3 * width); -} - -static void blendRowOfPixels (PixelRGB* dst, - const PixelARGB* src, - int width) throw() -{ - while (--width >= 0) - (dst++)->blend (*src++); -} - -static void blendRowOfPixels (PixelARGB* dst, - const PixelARGB* src, - int width) throw() -{ - while (--width >= 0) - (dst++)->blend (*src++); -} - -static void blendRowOfPixels (PixelARGB* dst, - const PixelRGB* src, - int width, - const uint8 alpha) throw() -{ - while (--width >= 0) - (dst++)->blend (*src++, alpha); -} - -static void blendRowOfPixels (PixelRGB* dst, - const PixelRGB* src, - int width, - const uint8 alpha) throw() -{ - uint8* d = (uint8*) dst; - const uint8* s = (const uint8*) src; - const int inverseAlpha = 0xff - alpha; - - while (--width >= 0) - { - d[0] = (uint8) (s[0] + (((d[0] - s[0]) * inverseAlpha) >> 8)); - d[1] = (uint8) (s[1] + (((d[1] - s[1]) * inverseAlpha) >> 8)); - d[2] = (uint8) (s[2] + (((d[2] - s[2]) * inverseAlpha) >> 8)); - - d += 3; - s += 3; - } -} - -static void blendRowOfPixels (PixelRGB* dst, - const PixelARGB* src, - int width, - const uint8 alpha) throw() -{ - while (--width >= 0) - (dst++)->blend (*src++, alpha); -} - -static void blendRowOfPixels (PixelARGB* dst, - const PixelARGB* src, - int width, - const uint8 alpha) throw() -{ - while (--width >= 0) - (dst++)->blend (*src++, alpha); -} - -template -static void overlayImage (DestPixelType* dest, - const int destStride, - const SrcPixelType* src, - const int srcStride, - const int width, - int height, - const uint8 alpha) throw() -{ - if (alpha < 0xff) - { - while (--height >= 0) - { - blendRowOfPixels (dest, src, width, alpha); - - dest = (DestPixelType*) (((uint8*) dest) + destStride); - src = (const SrcPixelType*) (((const uint8*) src) + srcStride); - } - } - else - { - while (--height >= 0) - { - blendRowOfPixels (dest, src, width); - - dest = (DestPixelType*) (((uint8*) dest) + destStride); - src = (const SrcPixelType*) (((const uint8*) src) + srcStride); - } - } -} - -template -static void transformedImageRender (Image& destImage, - const Image& sourceImage, - const int destClipX, const int destClipY, - const int destClipW, const int destClipH, - const int srcClipX, const int srcClipY, - const int srcClipWidth, const int srcClipHeight, - double srcX, double srcY, - const double lineDX, const double lineDY, - const double pixelDX, const double pixelDY, - const uint8 alpha, - const Graphics::ResamplingQuality quality, - DestPixelType*, - SrcPixelType*) throw() // forced by a compiler bug to include dummy - // parameters of the templated classes to - // make it use the correct instance of this function.. -{ - int destStride, destPixelStride; - uint8* const destPixels = destImage.lockPixelDataReadWrite (destClipX, destClipY, destClipW, destClipH, destStride, destPixelStride); - - int srcStride, srcPixelStride; - const uint8* const srcPixels = sourceImage.lockPixelDataReadOnly (srcClipX, srcClipY, srcClipWidth, srcClipHeight, srcStride, srcPixelStride); - - if (quality == Graphics::lowResamplingQuality) // nearest-neighbour.. - { - for (int y = 0; y < destClipH; ++y) - { - double sx = srcX; - double sy = srcY; - - DestPixelType* dest = (DestPixelType*) (destPixels + destStride * y); - - for (int x = 0; x < destClipW; ++x) - { - const int ix = roundDoubleToInt (floor (sx)) - srcClipX; - - if (((unsigned int) ix) < (unsigned int) srcClipWidth) - { - const int iy = roundDoubleToInt (floor (sy)) - srcClipY; - - if (((unsigned int) iy) < (unsigned int) srcClipHeight) - { - const SrcPixelType* const src = (const SrcPixelType*) (srcPixels + srcStride * iy + srcPixelStride * ix); - - dest->blend (*src, alpha); - } - } - - ++dest; - sx += pixelDX; - sy += pixelDY; - } - - srcX += lineDX; - srcY += lineDY; - } - } - else - { - jassert (quality == Graphics::mediumResamplingQuality); // (only bilinear is implemented, so that's what you'll get here..) - - for (int y = 0; y < destClipH; ++y) - { - double sx = srcX - 0.5; - double sy = srcY - 0.5; - DestPixelType* dest = (DestPixelType*) (destPixels + destStride * y); - - for (int x = 0; x < destClipW; ++x) - { - const double fx = floor (sx); - const double fy = floor (sy); - const int ix = roundDoubleToInt (fx) - srcClipX; - const int iy = roundDoubleToInt (fy) - srcClipY; - - if (ix < srcClipWidth && iy < srcClipHeight) - { - PixelARGB p1 (0), p2 (0), p3 (0), p4 (0); - - const SrcPixelType* src = (const SrcPixelType*) (srcPixels + srcStride * iy + srcPixelStride * ix); - - if (iy >= 0) - { - if (ix >= 0) - p1.set (src[0]); - - if (((unsigned int) (ix + 1)) < (unsigned int) srcClipWidth) - p2.set (src[1]); - } - - if (((unsigned int) (iy + 1)) < (unsigned int) srcClipHeight) - { - src = (const SrcPixelType*) (((const uint8*) src) + srcStride); - - if (ix >= 0) - p3.set (src[0]); - - if (((unsigned int) (ix + 1)) < (unsigned int) srcClipWidth) - p4.set (src[1]); - } - - const int dx = roundDoubleToInt ((sx - fx) * 255.0); - p1.tween (p2, dx); - p3.tween (p4, dx); - p1.tween (p3, roundDoubleToInt ((sy - fy) * 255.0)); - - if (p1.getAlpha() > 0) - dest->blend (p1, alpha); - } - - ++dest; - sx += pixelDX; - sy += pixelDY; - } - - srcX += lineDX; - srcY += lineDY; - } - } - - destImage.releasePixelDataReadWrite (destPixels); - sourceImage.releasePixelDataReadOnly (srcPixels); -} - -template -static void renderAlphaMap (DestPixelType* destPixels, - int destStride, - SrcPixelType* srcPixels, - int srcStride, - const uint8* alphaValues, - const int lineStride, const int pixelStride, - int width, int height, - const int extraAlpha) throw() -{ - while (--height >= 0) - { - SrcPixelType* srcPix = srcPixels; - srcPixels = (SrcPixelType*) (((const uint8*) srcPixels) + srcStride); - - DestPixelType* destPix = destPixels; - destPixels = (DestPixelType*) (((uint8*) destPixels) + destStride); - - const uint8* alpha = alphaValues; - alphaValues += lineStride; - - if (extraAlpha < 0x100) - { - for (int i = width; --i >= 0;) - { - destPix++ ->blend (*srcPix++, (extraAlpha * *alpha) >> 8); - alpha += pixelStride; - } - } - else - { - for (int i = width; --i >= 0;) - { - destPix++ ->blend (*srcPix++, *alpha); - alpha += pixelStride; - } - } - } -} - -//============================================================================== -LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (Image& image_) - : image (image_), - xOffset (0), - yOffset (0), - stateStack (20) -{ - clip = new RectangleList (Rectangle (0, 0, image_.getWidth(), image_.getHeight())); -} - -LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() -{ - delete clip; -} - -bool LowLevelGraphicsSoftwareRenderer::isVectorDevice() const -{ - return false; -} - -//============================================================================== -void LowLevelGraphicsSoftwareRenderer::setOrigin (int x, int y) -{ - xOffset += x; - yOffset += y; -} - -bool LowLevelGraphicsSoftwareRenderer::reduceClipRegion (int x, int y, int w, int h) -{ - return clip->clipTo (Rectangle (x + xOffset, y + yOffset, w, h)); -} - -bool LowLevelGraphicsSoftwareRenderer::reduceClipRegion (const RectangleList& clipRegion) -{ - RectangleList temp (clipRegion); - temp.offsetAll (xOffset, yOffset); - - return clip->clipTo (temp); -} - -void LowLevelGraphicsSoftwareRenderer::excludeClipRegion (int x, int y, int w, int h) -{ - clip->subtract (Rectangle (x + xOffset, y + yOffset, w, h)); -} - -bool LowLevelGraphicsSoftwareRenderer::clipRegionIntersects (int x, int y, int w, int h) -{ - return clip->intersectsRectangle (Rectangle (x + xOffset, y + yOffset, w, h)); -} - -const Rectangle LowLevelGraphicsSoftwareRenderer::getClipBounds() const -{ - return clip->getBounds().translated (-xOffset, -yOffset); -} - -bool LowLevelGraphicsSoftwareRenderer::isClipEmpty() const -{ - return clip->isEmpty(); -} - -//============================================================================== -LowLevelGraphicsSoftwareRenderer::SavedState::SavedState (RectangleList* const clip_, - const int xOffset_, const int yOffset_) - : clip (clip_), - xOffset (xOffset_), - yOffset (yOffset_) -{ -} - -LowLevelGraphicsSoftwareRenderer::SavedState::~SavedState() -{ - delete clip; -} - -void LowLevelGraphicsSoftwareRenderer::saveState() -{ - stateStack.add (new SavedState (new RectangleList (*clip), xOffset, yOffset)); -} - -void LowLevelGraphicsSoftwareRenderer::restoreState() -{ - SavedState* const top = stateStack.getLast(); - - if (top != 0) - { - clip->swapWith (*top->clip); - - xOffset = top->xOffset; - yOffset = top->yOffset; - - stateStack.removeLast(); - } - else - { - jassertfalse // trying to pop with an empty stack! - } -} - -//============================================================================== -void LowLevelGraphicsSoftwareRenderer::fillRectWithColour (int x, int y, int w, int h, const Colour& colour, const bool replaceExistingContents) -{ - x += xOffset; - y += yOffset; - - for (RectangleList::Iterator i (*clip); i.next();) - { - clippedFillRectWithColour (*i.getRectangle(), x, y, w, h, colour, replaceExistingContents); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedFillRectWithColour (const Rectangle& clipRect, - int x, int y, int w, int h, const Colour& colour, const bool replaceExistingContents) -{ - if (clipRect.intersectRectangle (x, y, w, h)) - { - int stride, pixelStride; - uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (x, y, w, h, stride, pixelStride); - - if (image.getFormat() == Image::RGB) - { - if (replaceExistingContents) - replaceRectRGB (pixels, w, h, stride, colour); - else - blendRectRGB (pixels, w, h, stride, colour); - } - else if (image.getFormat() == Image::ARGB) - { - if (replaceExistingContents) - replaceRectARGB (pixels, w, h, stride, colour); - else - blendRectARGB (pixels, w, h, stride, colour); - } - else - { - jassertfalse // not done! - } - - image.releasePixelDataReadWrite (pixels); - } -} - -void LowLevelGraphicsSoftwareRenderer::fillRectWithGradient (int x, int y, int w, int h, const ColourGradient& gradient) -{ - Path p; - p.addRectangle ((float) x, (float) y, (float) w, (float) h); - fillPathWithGradient (p, AffineTransform::identity, gradient, EdgeTable::Oversampling_none); -} - -//============================================================================== -bool LowLevelGraphicsSoftwareRenderer::getPathBounds (int clipX, int clipY, int clipW, int clipH, - const Path& path, const AffineTransform& transform, - int& x, int& y, int& w, int& h) const -{ - float tx, ty, tw, th; - path.getBoundsTransformed (transform, tx, ty, tw, th); - - x = roundDoubleToInt (tx) - 1; - y = roundDoubleToInt (ty) - 1; - w = roundDoubleToInt (tw) + 2; - h = roundDoubleToInt (th) + 2; - - // seems like this operation is using some crazy out-of-range numbers.. - ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, w, h); - - return Rectangle::intersectRectangles (x, y, w, h, clipX, clipY, clipW, clipH); -} - -void LowLevelGraphicsSoftwareRenderer::fillPathWithColour (const Path& path, const AffineTransform& t, - const Colour& colour, EdgeTable::OversamplingLevel quality) -{ - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedFillPathWithColour (r.getX(), r.getY(), r.getWidth(), r.getHeight(), path, t, colour, quality); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedFillPathWithColour (int clipX, int clipY, int clipW, int clipH, const Path& path, const AffineTransform& t, - const Colour& colour, EdgeTable::OversamplingLevel quality) -{ - const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset)); - int cx, cy, cw, ch; - - if (getPathBounds (clipX, clipY, clipW, clipH, path, transform, cx, cy, cw, ch)) - { - EdgeTable edgeTable (0, ch, quality); - - edgeTable.addPath (path, transform.translated ((float) -cx, (float) -cy)); - - int stride, pixelStride; - uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (cx, cy, cw, ch, stride, pixelStride); - - if (image.getFormat() == Image::RGB) - { - jassert (pixelStride == 3); - SolidColourEdgeTableRenderer renderer (pixels, stride, colour); - edgeTable.iterate (renderer, 0, 0, cw, ch, 0); - } - else if (image.getFormat() == Image::ARGB) - { - jassert (pixelStride == 4); - SolidColourEdgeTableRenderer renderer (pixels, stride, colour); - edgeTable.iterate (renderer, 0, 0, cw, ch, 0); - } - else if (image.getFormat() == Image::SingleChannel) - { - jassert (pixelStride == 1); - AlphaBitmapRenderer renderer (pixels, stride); - edgeTable.iterate (renderer, 0, 0, cw, ch, 0); - } - - image.releasePixelDataReadWrite (pixels); - } -} - -void LowLevelGraphicsSoftwareRenderer::fillPathWithGradient (const Path& path, const AffineTransform& t, const ColourGradient& gradient, EdgeTable::OversamplingLevel quality) -{ - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedFillPathWithGradient (r.getX(), r.getY(), r.getWidth(), r.getHeight(), - path, t, gradient, quality); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedFillPathWithGradient (int clipX, int clipY, int clipW, int clipH, const Path& path, const AffineTransform& t, - const ColourGradient& gradient, EdgeTable::OversamplingLevel quality) -{ - const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset)); - int cx, cy, cw, ch; - - if (getPathBounds (clipX, clipY, clipW, clipH, path, transform, cx, cy, cw, ch)) - { - int stride, pixelStride; - uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (cx, cy, cw, ch, stride, pixelStride); - - ColourGradient g2 (gradient); - - const bool isIdentity = g2.transform.isIdentity(); - if (isIdentity) - { - g2.x1 += xOffset - cx; - g2.x2 += xOffset - cx; - g2.y1 += yOffset - cy; - g2.y2 += yOffset - cy; - } - else - { - g2.transform = g2.transform.translated ((float) (xOffset - cx), - (float) (yOffset - cy)); - } - - int numLookupEntries; - PixelARGB* const lookupTable = g2.createLookupTable (numLookupEntries); - jassert (numLookupEntries > 0); - - EdgeTable edgeTable (0, ch, quality); - - edgeTable.addPath (path, transform.translated ((float) -cx, (float) -cy)); - - if (image.getFormat() == Image::RGB) - { - jassert (pixelStride == 3); - - if (g2.isRadial) - { - if (isIdentity) - { - GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); - edgeTable.iterate (renderer, 0, 0, cw, ch, 0); - } - else - { - GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); - edgeTable.iterate (renderer, 0, 0, cw, ch, 0); - } - } - else - { - GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); - edgeTable.iterate (renderer, 0, 0, cw, ch, 0); - } - } - else if (image.getFormat() == Image::ARGB) - { - jassert (pixelStride == 4); - - if (g2.isRadial) - { - if (isIdentity) - { - GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); - edgeTable.iterate (renderer, 0, 0, cw, ch, 0); - } - else - { - GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); - edgeTable.iterate (renderer, 0, 0, cw, ch, 0); - } - } - else - { - GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); - edgeTable.iterate (renderer, 0, 0, cw, ch, 0); - } - } - else if (image.getFormat() == Image::SingleChannel) - { - jassertfalse // not done! - } - - juce_free (lookupTable); - image.releasePixelDataReadWrite (pixels); - } -} - -void LowLevelGraphicsSoftwareRenderer::fillPathWithImage (const Path& path, const AffineTransform& transform, - const Image& sourceImage, int imageX, int imageY, float opacity, EdgeTable::OversamplingLevel quality) -{ - imageX += xOffset; - imageY += yOffset; - - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedFillPathWithImage (r.getX(), r.getY(), r.getWidth(), r.getHeight(), - path, transform, sourceImage, imageX, imageY, opacity, quality); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedFillPathWithImage (int x, int y, int w, int h, const Path& path, const AffineTransform& transform, - const Image& sourceImage, int imageX, int imageY, float opacity, EdgeTable::OversamplingLevel quality) -{ - if (Rectangle::intersectRectangles (x, y, w, h, imageX, imageY, sourceImage.getWidth(), sourceImage.getHeight())) - { - EdgeTable edgeTable (0, h, quality); - edgeTable.addPath (path, transform.translated ((float) (xOffset - x), (float) (yOffset - y))); - - int stride, pixelStride; - uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (x, y, w, h, stride, pixelStride); - - int srcStride, srcPixelStride; - const uint8* const srcPix = (const uint8*) sourceImage.lockPixelDataReadOnly (x - imageX, y - imageY, w, h, srcStride, srcPixelStride); - - const int alpha = jlimit (0, 255, roundDoubleToInt (opacity * 255.0f)); - - if (image.getFormat() == Image::RGB) - { - if (sourceImage.getFormat() == Image::RGB) - { - ImageFillEdgeTableRenderer renderer (pixels, stride, - srcPix, srcStride, - alpha, (PixelRGB*) 0); - edgeTable.iterate (renderer, 0, 0, w, h, 0); - } - else if (sourceImage.getFormat() == Image::ARGB) - { - ImageFillEdgeTableRenderer renderer (pixels, stride, - srcPix, srcStride, - alpha, (PixelARGB*) 0); - edgeTable.iterate (renderer, 0, 0, w, h, 0); - } - else - { - jassertfalse // not done! - } - } - else if (image.getFormat() == Image::ARGB) - { - if (sourceImage.getFormat() == Image::RGB) - { - ImageFillEdgeTableRenderer renderer (pixels, stride, - srcPix, srcStride, - alpha, (PixelRGB*) 0); - edgeTable.iterate (renderer, 0, 0, w, h, 0); - } - else if (sourceImage.getFormat() == Image::ARGB) - { - ImageFillEdgeTableRenderer renderer (pixels, stride, - srcPix, srcStride, - alpha, (PixelARGB*) 0); - edgeTable.iterate (renderer, 0, 0, w, h, 0); - } - else - { - jassertfalse // not done! - } - } - else - { - jassertfalse // not done! - } - - sourceImage.releasePixelDataReadOnly (srcPix); - image.releasePixelDataReadWrite (pixels); - } -} - -//============================================================================== -void LowLevelGraphicsSoftwareRenderer::fillAlphaChannelWithColour (const Image& clipImage, int x, int y, const Colour& colour) -{ - x += xOffset; - y += yOffset; - - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedFillAlphaChannelWithColour (r.getX(), r.getY(), r.getWidth(), r.getHeight(), - clipImage, x, y, colour); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedFillAlphaChannelWithColour (int clipX, int clipY, int clipW, int clipH, const Image& clipImage, int x, int y, const Colour& colour) -{ - int w = clipImage.getWidth(); - int h = clipImage.getHeight(); - int sx = 0; - int sy = 0; - - if (x < clipX) - { - sx = clipX - x; - w -= clipX - x; - x = clipX; - } - - if (y < clipY) - { - sy = clipY - y; - h -= clipY - y; - y = clipY; - } - - if (x + w > clipX + clipW) - w = clipX + clipW - x; - - if (y + h > clipY + clipH) - h = clipY + clipH - y; - - if (w > 0 && h > 0) - { - int stride, alphaStride, pixelStride; - uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (x, y, w, h, stride, pixelStride); - - const uint8* const alphaValues - = clipImage.lockPixelDataReadOnly (sx, sy, w, h, alphaStride, pixelStride); - -#if JUCE_BIG_ENDIAN - const uint8* const alphas = alphaValues; -#else - const uint8* const alphas = alphaValues + (clipImage.getFormat() == Image::ARGB ? 3 : 0); -#endif - - if (image.getFormat() == Image::RGB) - { - blendAlphaMapRGB (pixels, stride, - alphas, w, h, - pixelStride, alphaStride, - colour); - } - else if (image.getFormat() == Image::ARGB) - { - blendAlphaMapARGB (pixels, stride, - alphas, w, h, - pixelStride, alphaStride, - colour); - } - else - { - jassertfalse // not done! - } - - clipImage.releasePixelDataReadOnly (alphaValues); - image.releasePixelDataReadWrite (pixels); - } -} - -void LowLevelGraphicsSoftwareRenderer::fillAlphaChannelWithGradient (const Image& alphaChannelImage, int imageX, int imageY, const ColourGradient& gradient) -{ - imageX += xOffset; - imageY += yOffset; - - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedFillAlphaChannelWithGradient (r.getX(), r.getY(), r.getWidth(), r.getHeight(), - alphaChannelImage, imageX, imageY, gradient); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedFillAlphaChannelWithGradient (int x, int y, int w, int h, - const Image& alphaChannelImage, - int imageX, int imageY, const ColourGradient& gradient) -{ - if (Rectangle::intersectRectangles (x, y, w, h, imageX, imageY, alphaChannelImage.getWidth(), alphaChannelImage.getHeight())) - { - ColourGradient g2 (gradient); - g2.x1 += xOffset - x; - g2.x2 += xOffset - x; - g2.y1 += yOffset - y; - g2.y2 += yOffset - y; - - Image temp (g2.isOpaque() ? Image::RGB : Image::ARGB, w, h, true); - LowLevelGraphicsSoftwareRenderer tempG (temp); - tempG.fillRectWithGradient (0, 0, w, h, g2); - - clippedFillAlphaChannelWithImage (x, y, w, h, - alphaChannelImage, imageX, imageY, - temp, x, y, 1.0f); - } -} - -void LowLevelGraphicsSoftwareRenderer::fillAlphaChannelWithImage (const Image& alphaImage, int alphaImageX, int alphaImageY, - const Image& fillerImage, int fillerImageX, int fillerImageY, float opacity) -{ - alphaImageX += xOffset; - alphaImageY += yOffset; - - fillerImageX += xOffset; - fillerImageY += yOffset; - - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedFillAlphaChannelWithImage (r.getX(), r.getY(), r.getWidth(), r.getHeight(), - alphaImage, alphaImageX, alphaImageY, - fillerImage, fillerImageX, fillerImageY, opacity); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedFillAlphaChannelWithImage (int x, int y, int w, int h, const Image& alphaImage, int alphaImageX, int alphaImageY, - const Image& fillerImage, int fillerImageX, int fillerImageY, float opacity) -{ - if (Rectangle::intersectRectangles (x, y, w, h, alphaImageX, alphaImageY, alphaImage.getWidth(), alphaImage.getHeight()) - && Rectangle::intersectRectangles (x, y, w, h, fillerImageX, fillerImageY, fillerImage.getWidth(), fillerImage.getHeight())) - { - int dstStride, dstPixStride; - uint8* const dstPix = image.lockPixelDataReadWrite (x, y, w, h, dstStride, dstPixStride); - - int srcStride, srcPixStride; - const uint8* const srcPix = fillerImage.lockPixelDataReadOnly (x - fillerImageX, y - fillerImageY, w, h, srcStride, srcPixStride); - - int maskStride, maskPixStride; - const uint8* const alpha - = alphaImage.lockPixelDataReadOnly (x - alphaImageX, y - alphaImageY, w, h, maskStride, maskPixStride); - -#if JUCE_BIG_ENDIAN - const uint8* const alphaValues = alpha; -#else - const uint8* const alphaValues = alpha + (alphaImage.getFormat() == Image::ARGB ? 3 : 0); -#endif - - const int extraAlpha = jlimit (0, 0x100, roundDoubleToInt (opacity * 256.0f)); - - if (image.getFormat() == Image::RGB) - { - if (fillerImage.getFormat() == Image::RGB) - { - renderAlphaMap ((PixelRGB*) dstPix, dstStride, (const PixelRGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha); - } - else if (fillerImage.getFormat() == Image::ARGB) - { - renderAlphaMap ((PixelRGB*) dstPix, dstStride, (const PixelARGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha); - } - else - { - jassertfalse // not done! - } - } - else if (image.getFormat() == Image::ARGB) - { - if (fillerImage.getFormat() == Image::RGB) - { - renderAlphaMap ((PixelARGB*) dstPix, dstStride, (const PixelRGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha); - } - else if (fillerImage.getFormat() == Image::ARGB) - { - renderAlphaMap ((PixelARGB*) dstPix, dstStride, (const PixelARGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha); - } - else - { - jassertfalse // not done! - } - } - else - { - jassertfalse // not done! - } - - alphaImage.releasePixelDataReadOnly (alphaValues); - fillerImage.releasePixelDataReadOnly (srcPix); - image.releasePixelDataReadWrite (dstPix); - } -} - -//============================================================================== -void LowLevelGraphicsSoftwareRenderer::blendImage (const Image& sourceImage, int dx, int dy, int dw, int dh, int sx, int sy, float opacity) -{ - dx += xOffset; - dy += yOffset; - - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedBlendImage (r.getX(), r.getY(), r.getWidth(), r.getHeight(), - sourceImage, dx, dy, dw, dh, sx, sy, opacity); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedBlendImage (int clipX, int clipY, int clipW, int clipH, - const Image& sourceImage, int dx, int dy, int dw, int dh, int sx, int sy, float opacity) -{ - if (dx < clipX) - { - sx += clipX - dx; - dw -= clipX - dx; - dx = clipX; - } - - if (dy < clipY) - { - sy += clipY - dy; - dh -= clipY - dy; - dy = clipY; - } - - if (dx + dw > clipX + clipW) - dw = clipX + clipW - dx; - - if (dy + dh > clipY + clipH) - dh = clipY + clipH - dy; - - if (dw <= 0 || dh <= 0) - return; - - const uint8 alpha = (uint8) jlimit (0, 0xff, roundDoubleToInt (opacity * 256.0f)); - - if (alpha == 0) - return; - - int dstStride, dstPixelStride; - uint8* const dstPixels = image.lockPixelDataReadWrite (dx, dy, dw, dh, dstStride, dstPixelStride); - - int srcStride, srcPixelStride; - const uint8* const srcPixels = sourceImage.lockPixelDataReadOnly (sx, sy, dw, dh, srcStride, srcPixelStride); - - if (image.getFormat() == Image::ARGB) - { - if (sourceImage.getFormat() == Image::ARGB) - { - overlayImage ((PixelARGB*) dstPixels, dstStride, - (PixelARGB*) srcPixels, srcStride, - dw, dh, alpha); - } - else if (sourceImage.getFormat() == Image::RGB) - { - overlayImage ((PixelARGB*) dstPixels, dstStride, - (PixelRGB*) srcPixels, srcStride, - dw, dh, alpha); - } - else - { - jassertfalse - } - } - else if (image.getFormat() == Image::RGB) - { - if (sourceImage.getFormat() == Image::ARGB) - { - overlayImage ((PixelRGB*) dstPixels, dstStride, - (PixelARGB*) srcPixels, srcStride, - dw, dh, alpha); - } - else if (sourceImage.getFormat() == Image::RGB) - { - overlayImage ((PixelRGB*) dstPixels, dstStride, - (PixelRGB*) srcPixels, srcStride, - dw, dh, alpha); - } - else - { - jassertfalse - } - } - else - { - jassertfalse - } - - image.releasePixelDataReadWrite (dstPixels); - sourceImage.releasePixelDataReadOnly (srcPixels); -} - -//============================================================================== -void LowLevelGraphicsSoftwareRenderer::blendImageRescaling (const Image& sourceImage, - int dx, int dy, int dw, int dh, - int sx, int sy, int sw, int sh, - float alpha, - const Graphics::ResamplingQuality quality) -{ - if (sw > 0 && sh > 0) - { - if (sw == dw && sh == dh) - { - blendImage (sourceImage, - dx, dy, dw, dh, - sx, sy, alpha); - } - else - { - blendImageWarping (sourceImage, - sx, sy, sw, sh, - AffineTransform::translation ((float) -sx, - (float) -sy) - .scaled (dw / (float) sw, - dh / (float) sh) - .translated ((float) dx, - (float) dy), - alpha, - quality); - } - } -} - -//============================================================================== -void LowLevelGraphicsSoftwareRenderer::blendImageWarping (const Image& sourceImage, - int srcClipX, int srcClipY, int srcClipW, int srcClipH, - const AffineTransform& t, - float opacity, - const Graphics::ResamplingQuality quality) -{ - const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset)); - - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedBlendImageWarping (r.getX(), r.getY(), r.getWidth(), r.getHeight(), - sourceImage, srcClipX, srcClipY, srcClipW, srcClipH, - transform, opacity, quality); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedBlendImageWarping (int destClipX, int destClipY, int destClipW, int destClipH, - const Image& sourceImage, - int srcClipX, int srcClipY, int srcClipW, int srcClipH, - const AffineTransform& transform, - float opacity, - const Graphics::ResamplingQuality quality) -{ - if (opacity > 0 && destClipW > 0 && destClipH > 0 && ! transform.isSingularity()) - { - Rectangle::intersectRectangles (srcClipX, srcClipY, srcClipW, srcClipH, - 0, 0, sourceImage.getWidth(), sourceImage.getHeight()); - - if (srcClipW <= 0 || srcClipH <= 0) - return; - - jassert (srcClipX >= 0 && srcClipY >= 0); - - Path imageBounds; - imageBounds.addRectangle ((float) srcClipX, (float) srcClipY, (float) srcClipW, (float) srcClipH); - imageBounds.applyTransform (transform); - float imX, imY, imW, imH; - imageBounds.getBounds (imX, imY, imW, imH); - - if (Rectangle::intersectRectangles (destClipX, destClipY, destClipW, destClipH, - (int) floorf (imX), - (int) floorf (imY), - 1 + roundDoubleToInt (imW), - 1 + roundDoubleToInt (imH))) - { - const uint8 alpha = (uint8) jlimit (0, 0xff, roundDoubleToInt (opacity * 256.0f)); - - float srcX1 = (float) destClipX; - float srcY1 = (float) destClipY; - float srcX2 = (float) (destClipX + destClipW); - float srcY2 = srcY1; - float srcX3 = srcX1; - float srcY3 = (float) (destClipY + destClipH); - - AffineTransform inverse (transform.inverted()); - inverse.transformPoint (srcX1, srcY1); - inverse.transformPoint (srcX2, srcY2); - inverse.transformPoint (srcX3, srcY3); - - const double lineDX = (double) (srcX3 - srcX1) / destClipH; - const double lineDY = (double) (srcY3 - srcY1) / destClipH; - const double pixelDX = (double) (srcX2 - srcX1) / destClipW; - const double pixelDY = (double) (srcY2 - srcY1) / destClipW; - - if (image.getFormat() == Image::ARGB) - { - if (sourceImage.getFormat() == Image::ARGB) - { - transformedImageRender (image, sourceImage, - destClipX, destClipY, destClipW, destClipH, - srcClipX, srcClipY, srcClipW, srcClipH, - srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY, - alpha, quality, (PixelARGB*)0, (PixelARGB*)0); - } - else if (sourceImage.getFormat() == Image::RGB) - { - transformedImageRender (image, sourceImage, - destClipX, destClipY, destClipW, destClipH, - srcClipX, srcClipY, srcClipW, srcClipH, - srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY, - alpha, quality, (PixelARGB*)0, (PixelRGB*)0); - } - else - { - jassertfalse - } - } - else if (image.getFormat() == Image::RGB) - { - if (sourceImage.getFormat() == Image::ARGB) - { - transformedImageRender (image, sourceImage, - destClipX, destClipY, destClipW, destClipH, - srcClipX, srcClipY, srcClipW, srcClipH, - srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY, - alpha, quality, (PixelRGB*)0, (PixelARGB*)0); - } - else if (sourceImage.getFormat() == Image::RGB) - { - transformedImageRender (image, sourceImage, - destClipX, destClipY, destClipW, destClipH, - srcClipX, srcClipY, srcClipW, srcClipH, - srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY, - alpha, quality, (PixelRGB*)0, (PixelRGB*)0); - } - else - { - jassertfalse - } - } - else - { - jassertfalse - } - } - } -} - -//============================================================================== -void LowLevelGraphicsSoftwareRenderer::drawLine (double x1, double y1, double x2, double y2, const Colour& colour) -{ - x1 += xOffset; - y1 += yOffset; - x2 += xOffset; - y2 += yOffset; - - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedDrawLine (r.getX(), r.getY(), r.getWidth(), r.getHeight(), - x1, y1, x2, y2, colour); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedDrawLine (int clipX, int clipY, int clipW, int clipH, double x1, double y1, double x2, double y2, const Colour& colour) -{ - if (clipW > 0 && clipH > 0) - { - if (x1 == x2) - { - if (y2 < y1) - swapVariables (y1, y2); - - clippedDrawVerticalLine (clipX, clipY, clipW, clipH, roundDoubleToInt (x1), y1, y2, colour); - } - else if (y1 == y2) - { - if (x2 < x1) - swapVariables (x1, x2); - - clippedDrawHorizontalLine (clipX, clipY, clipW, clipH, roundDoubleToInt (y1), x1, x2, colour); - } - else - { - double gradient = (y2 - y1) / (x2 - x1); - - if (fabs (gradient) > 1.0) - { - gradient = 1.0 / gradient; - - int y = roundDoubleToInt (y1); - const int startY = y; - int endY = roundDoubleToInt (y2); - - if (y > endY) - swapVariables (y, endY); - - while (y < endY) - { - const double x = x1 + gradient * (y - startY); - clippedDrawHorizontalLine (clipX, clipY, clipW, clipH, y, x, x + 1.0, colour); - ++y; - } - } - else - { - int x = roundDoubleToInt (x1); - const int startX = x; - int endX = roundDoubleToInt (x2); - - if (x > endX) - swapVariables (x, endX); - - while (x < endX) - { - const double y = y1 + gradient * (x - startX); - clippedDrawVerticalLine (clipX, clipY, clipW, clipH, x, y, y + 1.0, colour); - ++x; - } - } - } - } -} - -void LowLevelGraphicsSoftwareRenderer::drawVerticalLine (const int x, double top, double bottom, const Colour& col) -{ - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedDrawVerticalLine (r.getX(), r.getY(), r.getWidth(), r.getHeight(), - x + xOffset, top + yOffset, bottom + yOffset, col); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedDrawVerticalLine (int clipX, int clipY, int clipW, int clipH, - const int x, double top, double bottom, const Colour& col) -{ - jassert (top <= bottom); - - if (((unsigned int) (x - clipX)) < (unsigned int) clipW - && top < clipY + clipH - && bottom > clipY - && clipW > 0) - { - if (top < clipY) - top = clipY; - - if (bottom > clipY + clipH) - bottom = clipY + clipH; - - if (bottom > top) - drawVertical (x, top, bottom, col); - } -} - -void LowLevelGraphicsSoftwareRenderer::drawHorizontalLine (const int y, double left, double right, const Colour& col) -{ - for (RectangleList::Iterator i (*clip); i.next();) - { - const Rectangle& r = *i.getRectangle(); - - clippedDrawHorizontalLine (r.getX(), r.getY(), r.getWidth(), r.getHeight(), - y + yOffset, left + xOffset, right + xOffset, col); - } -} - -void LowLevelGraphicsSoftwareRenderer::clippedDrawHorizontalLine (int clipX, int clipY, int clipW, int clipH, - const int y, double left, double right, const Colour& col) -{ - jassert (left <= right); - - if (((unsigned int) (y - clipY)) < (unsigned int) clipH - && left < clipX + clipW - && right > clipX - && clipW > 0) - { - if (left < clipX) - left = clipX; - - if (right > clipX + clipW) - right = clipX + clipW; - - if (right > left) - drawHorizontal (y, left, right, col); - } -} - -void LowLevelGraphicsSoftwareRenderer::drawVertical (const int x, - const double top, - const double bottom, - const Colour& col) -{ - int wholeStart = (int) top; - const int wholeEnd = (int) bottom; - - const int lastAlpha = roundDoubleToInt (255.0 * (bottom - wholeEnd)); - const int totalPixels = (wholeEnd - wholeStart) + (lastAlpha > 0 ? 1 : 0); - - if (totalPixels <= 0) - return; - - int lineStride, dstPixelStride; - uint8* const dstPixels = image.lockPixelDataReadWrite (x, wholeStart, 1, totalPixels, lineStride, dstPixelStride); - uint8* dest = dstPixels; - - PixelARGB colour (col.getPixelARGB()); - - if (wholeEnd == wholeStart) - { - if (image.getFormat() == Image::ARGB) - ((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top))); - else if (image.getFormat() == Image::RGB) - ((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top))); - else - { - jassertfalse - } - } - else - { - if (image.getFormat() == Image::ARGB) - { - ((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart)))); - ++wholeStart; - dest += lineStride; - - if (colour.getAlpha() == 0xff) - { - while (wholeEnd > wholeStart) - { - ((PixelARGB*) dest)->set (colour); - ++wholeStart; - dest += lineStride; - } - } - else - { - while (wholeEnd > wholeStart) - { - ((PixelARGB*) dest)->blend (colour); - ++wholeStart; - dest += lineStride; - } - } - - if (lastAlpha > 0) - { - ((PixelARGB*) dest)->blend (colour, lastAlpha); - } - } - else if (image.getFormat() == Image::RGB) - { - ((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart)))); - ++wholeStart; - dest += lineStride; - - if (colour.getAlpha() == 0xff) - { - while (wholeEnd > wholeStart) - { - ((PixelRGB*) dest)->set (colour); - ++wholeStart; - dest += lineStride; - } - } - else - { - while (wholeEnd > wholeStart) - { - ((PixelRGB*) dest)->blend (colour); - ++wholeStart; - dest += lineStride; - } - } - - if (lastAlpha > 0) - { - ((PixelRGB*) dest)->blend (colour, lastAlpha); - } - } - else - { - jassertfalse - } - } - - image.releasePixelDataReadWrite (dstPixels); -} - -void LowLevelGraphicsSoftwareRenderer::drawHorizontal (const int y, - const double top, - const double bottom, - const Colour& col) -{ - int wholeStart = (int) top; - const int wholeEnd = (int) bottom; - - const int lastAlpha = roundDoubleToInt (255.0 * (bottom - wholeEnd)); - const int totalPixels = (wholeEnd - wholeStart) + (lastAlpha > 0 ? 1 : 0); - - if (totalPixels <= 0) - return; - - int lineStride, dstPixelStride; - uint8* const dstPixels = image.lockPixelDataReadWrite (wholeStart, y, totalPixels, 1, lineStride, dstPixelStride); - uint8* dest = dstPixels; - - PixelARGB colour (col.getPixelARGB()); - - if (wholeEnd == wholeStart) - { - if (image.getFormat() == Image::ARGB) - ((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top))); - else if (image.getFormat() == Image::RGB) - ((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top))); - else - { - jassertfalse - } - } - else - { - if (image.getFormat() == Image::ARGB) - { - ((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart)))); - dest += dstPixelStride; - ++wholeStart; - - if (colour.getAlpha() == 0xff) - { - while (wholeEnd > wholeStart) - { - ((PixelARGB*) dest)->set (colour); - dest += dstPixelStride; - ++wholeStart; - } - } - else - { - while (wholeEnd > wholeStart) - { - ((PixelARGB*) dest)->blend (colour); - dest += dstPixelStride; - ++wholeStart; - } - } - - if (lastAlpha > 0) - { - ((PixelARGB*) dest)->blend (colour, lastAlpha); - } - } - else if (image.getFormat() == Image::RGB) - { - ((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart)))); - dest += dstPixelStride; - ++wholeStart; - - if (colour.getAlpha() == 0xff) - { - while (wholeEnd > wholeStart) - { - ((PixelRGB*) dest)->set (colour); - dest += dstPixelStride; - ++wholeStart; - } - } - else - { - while (wholeEnd > wholeStart) - { - ((PixelRGB*) dest)->blend (colour); - dest += dstPixelStride; - ++wholeStart; - } - } - - if (lastAlpha > 0) - { - ((PixelRGB*) dest)->blend (colour, lastAlpha); - } - } - else - { - jassertfalse - } - } - - image.releasePixelDataReadWrite (dstPixels); -} - - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_LowLevelGraphicsSoftwareRenderer.h" +#include "juce_EdgeTable.h" +#include "../imaging/juce_Image.h" +#include "../colour/juce_PixelFormats.h" +#include "../geometry/juce_PathStrokeType.h" +#include "../geometry/juce_Rectangle.h" +#include "../../../../juce_core/basics/juce_SystemStats.h" + +#if ! (defined (JUCE_MAC) || (defined (JUCE_WIN32) && defined (JUCE_64BIT))) + #define JUCE_USE_SSE_INSTRUCTIONS 1 +#endif + +#if defined (JUCE_DEBUG) && JUCE_MSVC + #pragma warning (disable: 4714) +#endif + +#define MINIMUM_COORD -0x3fffffff +#define MAXIMUM_COORD 0x3fffffff + +#undef ASSERT_COORDS_ARE_SENSIBLE_NUMBERS +#define ASSERT_COORDS_ARE_SENSIBLE_NUMBERS(x, y, w, h) \ + jassert ((int) x >= MINIMUM_COORD \ + && (int) x <= MAXIMUM_COORD \ + && (int) y >= MINIMUM_COORD \ + && (int) y <= MAXIMUM_COORD \ + && (int) w >= 0 \ + && (int) w < MAXIMUM_COORD \ + && (int) h >= 0 \ + && (int) h < MAXIMUM_COORD); + +//============================================================================== +static void replaceRectRGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw() +{ + const PixelARGB blendColour (colour.getPixelARGB()); + + if (w < 32) + { + while (--h >= 0) + { + PixelRGB* dest = (PixelRGB*) pixels; + + for (int i = w; --i >= 0;) + (dest++)->set (blendColour); + + pixels += stride; + } + } + else + { + // for wider fills, it's worth using some optimisations.. + + const uint8 r = blendColour.getRed(); + const uint8 g = blendColour.getGreen(); + const uint8 b = blendColour.getBlue(); + + if (r == g && r == b) // if all the component values are the same, we can cheat.. + { + while (--h >= 0) + { + memset (pixels, r, w * 3); + pixels += stride; + } + } + else + { + PixelRGB filler [4]; + filler[0].set (blendColour); + filler[1].set (blendColour); + filler[2].set (blendColour); + filler[3].set (blendColour); + const int* const intFiller = (const int*) filler; + + while (--h >= 0) + { + uint8* dest = (uint8*) pixels; + + int i = w; + + while ((i > 8) && (((pointer_sized_int) dest & 7) != 0)) + { + ((PixelRGB*) dest)->set (blendColour); + dest += 3; + --i; + } + + while (i >= 4) + { + ((int*) dest) [0] = intFiller[0]; + ((int*) dest) [1] = intFiller[1]; + ((int*) dest) [2] = intFiller[2]; + + dest += 12; + i -= 4; + } + + while (--i >= 0) + { + ((PixelRGB*) dest)->set (blendColour); + dest += 3; + } + + pixels += stride; + } + } + } +} + +static void replaceRectARGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw() +{ + const PixelARGB blendColour (colour.getPixelARGB()); + + while (--h >= 0) + { + PixelARGB* const dest = (PixelARGB*) pixels; + + for (int i = 0; i < w; ++i) + dest[i] = blendColour; + + pixels += stride; + } +} + +static void blendRectRGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw() +{ + if (colour.isOpaque()) + { + replaceRectRGB (pixels, w, h, stride, colour); + } + else + { + const PixelARGB blendColour (colour.getPixelARGB()); + const int alpha = blendColour.getAlpha(); + + if (alpha <= 0) + return; + +#if defined (JUCE_USE_SSE_INSTRUCTIONS) && ! JUCE_64BIT + if (SystemStats::hasSSE()) + { + int64 rgb0 = (((int64) blendColour.getRed()) << 32) + | (int64) ((blendColour.getGreen() << 16) + | blendColour.getBlue()); + + const int invAlpha = 0xff - alpha; + int64 aaaa = (invAlpha << 16) | invAlpha; + aaaa = (aaaa << 16) | aaaa; + +#ifndef JUCE_GCC + __asm + { + movq mm1, aaaa + movq mm2, rgb0 + pxor mm7, mm7 + } + + while (--h >= 0) + { + __asm + { + mov edx, pixels + mov ebx, w + + pixloop: + prefetchnta [edx] + mov ax, [edx + 1] + shl eax, 8 + mov al, [edx] + movd mm0, eax + + punpcklbw mm0, mm7 + pmullw mm0, mm1 + psrlw mm0, 8 + paddw mm0, mm2 + packuswb mm0, mm7 + + movd eax, mm0 + mov [edx], al + inc edx + shr eax, 8 + mov [edx], ax + add edx, 2 + + dec ebx + jg pixloop + } + + pixels += stride; + } + + __asm emms +#else + __asm__ __volatile__ ( + "movq %[aaaa], %%mm1 \n" + "\tmovq %[rgb0], %%mm2 \n" + "\tpxor %%mm7, %%mm7 \n" + ".lineLoop2: \n" + "\tmovl %%esi,%%edx \n" + "\tmovl %[w], %%ebx \n" + ".pixLoop2: \n" + "\tprefetchnta (%%edx) \n" + "\tmov (%%edx), %%ax \n" + "\tshl $8, %%eax \n" + "\tmov 2(%%edx), %%al \n" + "\tmovd %%eax, %%mm0 \n" + "\tpunpcklbw %%mm7, %%mm0 \n" + "\tpmullw %%mm1, %%mm0 \n" + "\tpsrlw $8, %%mm0 \n" + "\tpaddw %%mm2, %%mm0 \n" + "\tpackuswb %%mm7, %%mm0 \n" + "\tmovd %%mm0, %%eax \n" + "\tmovb %%al, (%%edx) \n" + "\tinc %%edx \n" + "\tshr $8, %%eax \n" + "\tmovw %%ax, (%%edx) \n" + "\tadd $2, %%edx \n" + "\tdec %%ebx \n" + "\tjg .pixLoop2 \n" + "\tadd %%edi, %%esi \n" + "\tdec %%ecx \n" + "\tjg .lineLoop2 \n" + "\temms \n" + : /* No output registers */ + : [aaaa] "m" (aaaa), /* Input registers */ + [rgb0] "m" (rgb0), + [w] "m" (w), + "c" (h), + [stride] "D" (stride), + [pixels] "S" (pixels) + : "cc", "eax", "edx", "memory" /* Clobber list */ + ); +#endif + } + else +#endif + { + while (--h >= 0) + { + PixelRGB* dest = (PixelRGB*) pixels; + + for (int i = w; --i >= 0;) + (dest++)->blend (blendColour); + + pixels += stride; + } + } + } +} + +static void blendRectARGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw() +{ + if (colour.isOpaque()) + { + replaceRectARGB (pixels, w, h, stride, colour); + } + else + { + const PixelARGB blendColour (colour.getPixelARGB()); + const int alpha = blendColour.getAlpha(); + + if (alpha <= 0) + return; + + while (--h >= 0) + { + PixelARGB* dest = (PixelARGB*) pixels; + + for (int i = w; --i >= 0;) + (dest++)->blend (blendColour); + + pixels += stride; + } + } +} + +//============================================================================== +static void blendAlphaMapARGB (uint8* destPixel, const int imageStride, + const uint8* alphaValues, const int w, int h, + const int pixelStride, const int lineStride, + const Colour& colour) throw() +{ + const PixelARGB srcPix (colour.getPixelARGB()); + + while (--h >= 0) + { + PixelARGB* dest = (PixelARGB*) destPixel; + const uint8* src = alphaValues; + + int i = w; + while (--i >= 0) + { + unsigned int srcAlpha = *src; + src += pixelStride; + + if (srcAlpha > 0) + dest->blend (srcPix, srcAlpha); + + ++dest; + } + + alphaValues += lineStride; + destPixel += imageStride; + } +} + +static void blendAlphaMapRGB (uint8* destPixel, const int imageStride, + const uint8* alphaValues, int const width, int height, + const int pixelStride, const int lineStride, + const Colour& colour) throw() +{ + const PixelARGB srcPix (colour.getPixelARGB()); + + while (--height >= 0) + { + PixelRGB* dest = (PixelRGB*) destPixel; + const uint8* src = alphaValues; + + int i = width; + while (--i >= 0) + { + unsigned int srcAlpha = *src; + src += pixelStride; + + if (srcAlpha > 0) + dest->blend (srcPix, srcAlpha); + + ++dest; + } + + alphaValues += lineStride; + destPixel += imageStride; + } +} + +//============================================================================== +template +class SolidColourEdgeTableRenderer +{ + uint8* const data; + const int stride; + PixelType* linePixels; + PixelARGB sourceColour; + + SolidColourEdgeTableRenderer (const SolidColourEdgeTableRenderer&); + const SolidColourEdgeTableRenderer& operator= (const SolidColourEdgeTableRenderer&); + +public: + SolidColourEdgeTableRenderer (uint8* const data_, + const int stride_, + const Colour& colour) throw() + : data (data_), + stride (stride_), + sourceColour (colour.getPixelARGB()) + { + } + + forcedinline void setEdgeTableYPos (const int y) throw() + { + linePixels = (PixelType*) (data + stride * y); + } + + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw() + { + linePixels[x].blend (sourceColour, alphaLevel); + } + + forcedinline void handleEdgeTableLine (const int x, int width, const int alphaLevel) const throw() + { + PixelARGB p (sourceColour); + p.multiplyAlpha (alphaLevel); + + PixelType* dest = linePixels + x; + + if (p.getAlpha() < 0xff) + { + do + { + dest->blend (p); + ++dest; + + } while (--width > 0); + } + else + { + do + { + dest->set (p); + ++dest; + + } while (--width > 0); + } + } +}; + +class AlphaBitmapRenderer +{ + uint8* data; + int stride; + uint8* lineStart; + + AlphaBitmapRenderer (const AlphaBitmapRenderer&); + const AlphaBitmapRenderer& operator= (const AlphaBitmapRenderer&); + +public: + AlphaBitmapRenderer (uint8* const data_, + const int stride_) throw() + : data (data_), + stride (stride_) + { + } + + forcedinline void setEdgeTableYPos (const int y) throw() + { + lineStart = data + (stride * y); + } + + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw() + { + lineStart [x] = (uint8) alphaLevel; + } + + forcedinline void handleEdgeTableLine (const int x, int width, const int alphaLevel) const throw() + { + uint8* d = lineStart + x; + + while (--width >= 0) + *d++ = (uint8) alphaLevel; + } +}; + +//============================================================================== +static const int numScaleBits = 12; + +class LinearGradientPixelGenerator +{ + const PixelARGB* const lookupTable; + const int numEntries; + PixelARGB linePix; + int start, scale; + double grad, yTerm; + bool vertical, horizontal; + + LinearGradientPixelGenerator (const LinearGradientPixelGenerator&); + const LinearGradientPixelGenerator& operator= (const LinearGradientPixelGenerator&); + +public: + LinearGradientPixelGenerator (const ColourGradient& gradient, + const PixelARGB* const lookupTable_, const int numEntries_) + : lookupTable (lookupTable_), + numEntries (numEntries_) + { + jassert (numEntries_ >= 0); + float x1 = gradient.x1; + float y1 = gradient.y1; + float x2 = gradient.x2; + float y2 = gradient.y2; + + if (! gradient.transform.isIdentity()) + { + Line l (x2, y2, x1, y1); + const Point p3 = l.getPointAlongLine (0.0, 100.0f); + float x3 = p3.getX(); + float y3 = p3.getY(); + + gradient.transform.transformPoint (x1, y1); + gradient.transform.transformPoint (x2, y2); + gradient.transform.transformPoint (x3, y3); + + Line l2 (x2, y2, x3, y3); + float prop = l2.findNearestPointTo (x1, y1); + const Point newP2 (l2.getPointAlongLineProportionally (prop)); + + x2 = newP2.getX(); + y2 = newP2.getY(); + } + + vertical = fabs (x1 - x2) < 0.001f; + horizontal = fabs (y1 - y2) < 0.001f; + + if (vertical) + { + scale = roundDoubleToInt ((numEntries << numScaleBits) / (double) (y2 - y1)); + start = roundDoubleToInt (y1 * scale); + } + else if (horizontal) + { + scale = roundDoubleToInt ((numEntries << numScaleBits) / (double) (x2 - x1)); + start = roundDoubleToInt (x1 * scale); + } + else + { + grad = (y2 - y1) / (double) (x1 - x2); + yTerm = y1 - x1 / grad; + scale = roundDoubleToInt ((numEntries << numScaleBits) / (yTerm * grad - (y2 * grad - x2))); + grad *= scale; + } + } + + forcedinline void setY (const int y) throw() + { + if (vertical) + linePix = lookupTable [jlimit (0, numEntries, (y * scale - start) >> numScaleBits)]; + else if (! horizontal) + start = roundDoubleToInt ((y - yTerm) * grad); + } + + forcedinline const PixelARGB getPixel (const int x) const throw() + { + if (vertical) + return linePix; + + return lookupTable [jlimit (0, numEntries, (x * scale - start) >> numScaleBits)]; + } +}; + +class RadialGradientPixelGenerator +{ +protected: + const PixelARGB* const lookupTable; + const int numEntries; + const double gx1, gy1; + double maxDist, invScale; + double dy; + + RadialGradientPixelGenerator (const RadialGradientPixelGenerator&); + const RadialGradientPixelGenerator& operator= (const RadialGradientPixelGenerator&); + +public: + RadialGradientPixelGenerator (const ColourGradient& gradient, + const PixelARGB* const lookupTable_, const int numEntries_) throw() + : lookupTable (lookupTable_), + numEntries (numEntries_), + gx1 (gradient.x1), + gy1 (gradient.y1) + { + jassert (numEntries_ >= 0); + const float dx = gradient.x1 - gradient.x2; + const float dy = gradient.y1 - gradient.y2; + maxDist = dx * dx + dy * dy; + invScale = (numEntries + 1) / sqrt (maxDist); + } + + forcedinline void setY (const int y) throw() + { + dy = y - gy1; + dy *= dy; + } + + forcedinline const PixelARGB getPixel (const int px) const throw() + { + double x = px - gx1; + x *= x; + x += dy; + + if (x >= maxDist) + return lookupTable [numEntries]; + else + return lookupTable [jmin (numEntries, roundDoubleToInt (sqrt (x) * invScale))]; + } +}; + +class TransformedRadialGradientPixelGenerator : public RadialGradientPixelGenerator +{ + double tM10, tM00, lineYM01, lineYM11; + AffineTransform inverseTransform; + + TransformedRadialGradientPixelGenerator (const TransformedRadialGradientPixelGenerator&); + const TransformedRadialGradientPixelGenerator& operator= (const TransformedRadialGradientPixelGenerator&); + +public: + TransformedRadialGradientPixelGenerator (const ColourGradient& gradient, + const PixelARGB* const lookupTable_, const int numEntries_) throw() + : RadialGradientPixelGenerator (gradient, lookupTable_, numEntries_), + inverseTransform (gradient.transform.inverted()) + { + tM10 = inverseTransform.mat10; + tM00 = inverseTransform.mat00; + } + + forcedinline void setY (const int y) throw() + { + lineYM01 = inverseTransform.mat01 * y + inverseTransform.mat02 - gx1; + lineYM11 = inverseTransform.mat11 * y + inverseTransform.mat12 - gy1; + } + + forcedinline const PixelARGB getPixel (const int px) const throw() + { + double x = px; + const double y = tM10 * x + lineYM11; + x = tM00 * x + lineYM01; + x *= x; + x += y * y; + + if (x >= maxDist) + return lookupTable [numEntries]; + else + return lookupTable [jmin (numEntries, roundDoubleToInt (sqrt (x) * invScale))]; + } +}; + +template +class GradientEdgeTableRenderer : public GradientType +{ + uint8* const data; + const int stride; + PixelType* linePixels; + + GradientEdgeTableRenderer (const GradientEdgeTableRenderer&); + const GradientEdgeTableRenderer& operator= (const GradientEdgeTableRenderer&); + +public: + GradientEdgeTableRenderer (uint8* const data_, + const int stride_, + const ColourGradient& gradient, + const PixelARGB* const lookupTable, const int numEntries) throw() + : GradientType (gradient, lookupTable, numEntries - 1), + data (data_), + stride (stride_) + { + } + + forcedinline void setEdgeTableYPos (const int y) throw() + { + linePixels = (PixelType*) (data + stride * y); + GradientType::setY (y); + } + + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw() + { + linePixels[x].blend (GradientType::getPixel (x), alphaLevel); + } + + forcedinline void handleEdgeTableLine (int x, int width, const int alphaLevel) const throw() + { + PixelType* dest = linePixels + x; + + if (alphaLevel < 0xff) + { + do + { + (dest++)->blend (GradientType::getPixel (x++), alphaLevel); + + } while (--width > 0); + } + else + { + do + { + (dest++)->blend (GradientType::getPixel (x++)); + + } while (--width > 0); + } + } +}; + +//============================================================================== +template +class ImageFillEdgeTableRenderer +{ + uint8* const destImageData; + const uint8* srcImageData; + int stride, srcStride, extraAlpha; + + DestPixelType* linePixels; + SrcPixelType* sourceLineStart; + + ImageFillEdgeTableRenderer (const ImageFillEdgeTableRenderer&); + const ImageFillEdgeTableRenderer& operator= (const ImageFillEdgeTableRenderer&); + +public: + ImageFillEdgeTableRenderer (uint8* const destImageData_, + const int stride_, + const uint8* srcImageData_, + const int srcStride_, + int extraAlpha_, + SrcPixelType*) throw() // dummy param to avoid compiler error + : destImageData (destImageData_), + srcImageData (srcImageData_), + stride (stride_), + srcStride (srcStride_), + extraAlpha (extraAlpha_) + { + } + + forcedinline void setEdgeTableYPos (int y) throw() + { + linePixels = (DestPixelType*) (destImageData + stride * y); + sourceLineStart = (SrcPixelType*) (srcImageData + srcStride * y); + } + + forcedinline void handleEdgeTablePixel (const int x, int alphaLevel) const throw() + { + alphaLevel = (alphaLevel * extraAlpha) >> 8; + + linePixels[x].blend (sourceLineStart [x], alphaLevel); + } + + forcedinline void handleEdgeTableLine (int x, int width, int alphaLevel) const throw() + { + DestPixelType* dest = linePixels + x; + alphaLevel = (alphaLevel * extraAlpha) >> 8; + + if (alphaLevel < 0xfe) + { + do + { + dest++ ->blend (sourceLineStart [x++], alphaLevel); + + } while (--width > 0); + } + else + { + do + { + dest++ ->blend (sourceLineStart [x++]); + + } while (--width > 0); + } + } +}; + +//============================================================================== +static void blendRowOfPixels (PixelARGB* dst, + const PixelRGB* src, + int width) throw() +{ + while (--width >= 0) + (dst++)->set (*src++); +} + +static void blendRowOfPixels (PixelRGB* dst, + const PixelRGB* src, + int width) throw() +{ + memcpy (dst, src, 3 * width); +} + +static void blendRowOfPixels (PixelRGB* dst, + const PixelARGB* src, + int width) throw() +{ + while (--width >= 0) + (dst++)->blend (*src++); +} + +static void blendRowOfPixels (PixelARGB* dst, + const PixelARGB* src, + int width) throw() +{ + while (--width >= 0) + (dst++)->blend (*src++); +} + +static void blendRowOfPixels (PixelARGB* dst, + const PixelRGB* src, + int width, + const uint8 alpha) throw() +{ + while (--width >= 0) + (dst++)->blend (*src++, alpha); +} + +static void blendRowOfPixels (PixelRGB* dst, + const PixelRGB* src, + int width, + const uint8 alpha) throw() +{ + uint8* d = (uint8*) dst; + const uint8* s = (const uint8*) src; + const int inverseAlpha = 0xff - alpha; + + while (--width >= 0) + { + d[0] = (uint8) (s[0] + (((d[0] - s[0]) * inverseAlpha) >> 8)); + d[1] = (uint8) (s[1] + (((d[1] - s[1]) * inverseAlpha) >> 8)); + d[2] = (uint8) (s[2] + (((d[2] - s[2]) * inverseAlpha) >> 8)); + + d += 3; + s += 3; + } +} + +static void blendRowOfPixels (PixelRGB* dst, + const PixelARGB* src, + int width, + const uint8 alpha) throw() +{ + while (--width >= 0) + (dst++)->blend (*src++, alpha); +} + +static void blendRowOfPixels (PixelARGB* dst, + const PixelARGB* src, + int width, + const uint8 alpha) throw() +{ + while (--width >= 0) + (dst++)->blend (*src++, alpha); +} + +template +static void overlayImage (DestPixelType* dest, + const int destStride, + const SrcPixelType* src, + const int srcStride, + const int width, + int height, + const uint8 alpha) throw() +{ + if (alpha < 0xff) + { + while (--height >= 0) + { + blendRowOfPixels (dest, src, width, alpha); + + dest = (DestPixelType*) (((uint8*) dest) + destStride); + src = (const SrcPixelType*) (((const uint8*) src) + srcStride); + } + } + else + { + while (--height >= 0) + { + blendRowOfPixels (dest, src, width); + + dest = (DestPixelType*) (((uint8*) dest) + destStride); + src = (const SrcPixelType*) (((const uint8*) src) + srcStride); + } + } +} + +template +static void transformedImageRender (Image& destImage, + const Image& sourceImage, + const int destClipX, const int destClipY, + const int destClipW, const int destClipH, + const int srcClipX, const int srcClipY, + const int srcClipWidth, const int srcClipHeight, + double srcX, double srcY, + const double lineDX, const double lineDY, + const double pixelDX, const double pixelDY, + const uint8 alpha, + const Graphics::ResamplingQuality quality, + DestPixelType*, + SrcPixelType*) throw() // forced by a compiler bug to include dummy + // parameters of the templated classes to + // make it use the correct instance of this function.. +{ + int destStride, destPixelStride; + uint8* const destPixels = destImage.lockPixelDataReadWrite (destClipX, destClipY, destClipW, destClipH, destStride, destPixelStride); + + int srcStride, srcPixelStride; + const uint8* const srcPixels = sourceImage.lockPixelDataReadOnly (srcClipX, srcClipY, srcClipWidth, srcClipHeight, srcStride, srcPixelStride); + + if (quality == Graphics::lowResamplingQuality) // nearest-neighbour.. + { + for (int y = 0; y < destClipH; ++y) + { + double sx = srcX; + double sy = srcY; + + DestPixelType* dest = (DestPixelType*) (destPixels + destStride * y); + + for (int x = 0; x < destClipW; ++x) + { + const int ix = roundDoubleToInt (floor (sx)) - srcClipX; + + if (((unsigned int) ix) < (unsigned int) srcClipWidth) + { + const int iy = roundDoubleToInt (floor (sy)) - srcClipY; + + if (((unsigned int) iy) < (unsigned int) srcClipHeight) + { + const SrcPixelType* const src = (const SrcPixelType*) (srcPixels + srcStride * iy + srcPixelStride * ix); + + dest->blend (*src, alpha); + } + } + + ++dest; + sx += pixelDX; + sy += pixelDY; + } + + srcX += lineDX; + srcY += lineDY; + } + } + else + { + jassert (quality == Graphics::mediumResamplingQuality); // (only bilinear is implemented, so that's what you'll get here..) + + for (int y = 0; y < destClipH; ++y) + { + double sx = srcX - 0.5; + double sy = srcY - 0.5; + DestPixelType* dest = (DestPixelType*) (destPixels + destStride * y); + + for (int x = 0; x < destClipW; ++x) + { + const double fx = floor (sx); + const double fy = floor (sy); + const int ix = roundDoubleToInt (fx) - srcClipX; + const int iy = roundDoubleToInt (fy) - srcClipY; + + if (ix < srcClipWidth && iy < srcClipHeight) + { + PixelARGB p1 (0), p2 (0), p3 (0), p4 (0); + + const SrcPixelType* src = (const SrcPixelType*) (srcPixels + srcStride * iy + srcPixelStride * ix); + + if (iy >= 0) + { + if (ix >= 0) + p1.set (src[0]); + + if (((unsigned int) (ix + 1)) < (unsigned int) srcClipWidth) + p2.set (src[1]); + } + + if (((unsigned int) (iy + 1)) < (unsigned int) srcClipHeight) + { + src = (const SrcPixelType*) (((const uint8*) src) + srcStride); + + if (ix >= 0) + p3.set (src[0]); + + if (((unsigned int) (ix + 1)) < (unsigned int) srcClipWidth) + p4.set (src[1]); + } + + const int dx = roundDoubleToInt ((sx - fx) * 255.0); + p1.tween (p2, dx); + p3.tween (p4, dx); + p1.tween (p3, roundDoubleToInt ((sy - fy) * 255.0)); + + if (p1.getAlpha() > 0) + dest->blend (p1, alpha); + } + + ++dest; + sx += pixelDX; + sy += pixelDY; + } + + srcX += lineDX; + srcY += lineDY; + } + } + + destImage.releasePixelDataReadWrite (destPixels); + sourceImage.releasePixelDataReadOnly (srcPixels); +} + +template +static void renderAlphaMap (DestPixelType* destPixels, + int destStride, + SrcPixelType* srcPixels, + int srcStride, + const uint8* alphaValues, + const int lineStride, const int pixelStride, + int width, int height, + const int extraAlpha) throw() +{ + while (--height >= 0) + { + SrcPixelType* srcPix = srcPixels; + srcPixels = (SrcPixelType*) (((const uint8*) srcPixels) + srcStride); + + DestPixelType* destPix = destPixels; + destPixels = (DestPixelType*) (((uint8*) destPixels) + destStride); + + const uint8* alpha = alphaValues; + alphaValues += lineStride; + + if (extraAlpha < 0x100) + { + for (int i = width; --i >= 0;) + { + destPix++ ->blend (*srcPix++, (extraAlpha * *alpha) >> 8); + alpha += pixelStride; + } + } + else + { + for (int i = width; --i >= 0;) + { + destPix++ ->blend (*srcPix++, *alpha); + alpha += pixelStride; + } + } + } +} + +//============================================================================== +LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (Image& image_) + : image (image_), + xOffset (0), + yOffset (0), + stateStack (20) +{ + clip = new RectangleList (Rectangle (0, 0, image_.getWidth(), image_.getHeight())); +} + +LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() +{ + delete clip; +} + +bool LowLevelGraphicsSoftwareRenderer::isVectorDevice() const +{ + return false; +} + +//============================================================================== +void LowLevelGraphicsSoftwareRenderer::setOrigin (int x, int y) +{ + xOffset += x; + yOffset += y; +} + +bool LowLevelGraphicsSoftwareRenderer::reduceClipRegion (int x, int y, int w, int h) +{ + return clip->clipTo (Rectangle (x + xOffset, y + yOffset, w, h)); +} + +bool LowLevelGraphicsSoftwareRenderer::reduceClipRegion (const RectangleList& clipRegion) +{ + RectangleList temp (clipRegion); + temp.offsetAll (xOffset, yOffset); + + return clip->clipTo (temp); +} + +void LowLevelGraphicsSoftwareRenderer::excludeClipRegion (int x, int y, int w, int h) +{ + clip->subtract (Rectangle (x + xOffset, y + yOffset, w, h)); +} + +bool LowLevelGraphicsSoftwareRenderer::clipRegionIntersects (int x, int y, int w, int h) +{ + return clip->intersectsRectangle (Rectangle (x + xOffset, y + yOffset, w, h)); +} + +const Rectangle LowLevelGraphicsSoftwareRenderer::getClipBounds() const +{ + return clip->getBounds().translated (-xOffset, -yOffset); +} + +bool LowLevelGraphicsSoftwareRenderer::isClipEmpty() const +{ + return clip->isEmpty(); +} + +//============================================================================== +LowLevelGraphicsSoftwareRenderer::SavedState::SavedState (RectangleList* const clip_, + const int xOffset_, const int yOffset_) + : clip (clip_), + xOffset (xOffset_), + yOffset (yOffset_) +{ +} + +LowLevelGraphicsSoftwareRenderer::SavedState::~SavedState() +{ + delete clip; +} + +void LowLevelGraphicsSoftwareRenderer::saveState() +{ + stateStack.add (new SavedState (new RectangleList (*clip), xOffset, yOffset)); +} + +void LowLevelGraphicsSoftwareRenderer::restoreState() +{ + SavedState* const top = stateStack.getLast(); + + if (top != 0) + { + clip->swapWith (*top->clip); + + xOffset = top->xOffset; + yOffset = top->yOffset; + + stateStack.removeLast(); + } + else + { + jassertfalse // trying to pop with an empty stack! + } +} + +//============================================================================== +void LowLevelGraphicsSoftwareRenderer::fillRectWithColour (int x, int y, int w, int h, const Colour& colour, const bool replaceExistingContents) +{ + x += xOffset; + y += yOffset; + + for (RectangleList::Iterator i (*clip); i.next();) + { + clippedFillRectWithColour (*i.getRectangle(), x, y, w, h, colour, replaceExistingContents); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedFillRectWithColour (const Rectangle& clipRect, + int x, int y, int w, int h, const Colour& colour, const bool replaceExistingContents) +{ + if (clipRect.intersectRectangle (x, y, w, h)) + { + int stride, pixelStride; + uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (x, y, w, h, stride, pixelStride); + + if (image.getFormat() == Image::RGB) + { + if (replaceExistingContents) + replaceRectRGB (pixels, w, h, stride, colour); + else + blendRectRGB (pixels, w, h, stride, colour); + } + else if (image.getFormat() == Image::ARGB) + { + if (replaceExistingContents) + replaceRectARGB (pixels, w, h, stride, colour); + else + blendRectARGB (pixels, w, h, stride, colour); + } + else + { + jassertfalse // not done! + } + + image.releasePixelDataReadWrite (pixels); + } +} + +void LowLevelGraphicsSoftwareRenderer::fillRectWithGradient (int x, int y, int w, int h, const ColourGradient& gradient) +{ + Path p; + p.addRectangle ((float) x, (float) y, (float) w, (float) h); + fillPathWithGradient (p, AffineTransform::identity, gradient, EdgeTable::Oversampling_none); +} + +//============================================================================== +bool LowLevelGraphicsSoftwareRenderer::getPathBounds (int clipX, int clipY, int clipW, int clipH, + const Path& path, const AffineTransform& transform, + int& x, int& y, int& w, int& h) const +{ + float tx, ty, tw, th; + path.getBoundsTransformed (transform, tx, ty, tw, th); + + x = roundDoubleToInt (tx) - 1; + y = roundDoubleToInt (ty) - 1; + w = roundDoubleToInt (tw) + 2; + h = roundDoubleToInt (th) + 2; + + // seems like this operation is using some crazy out-of-range numbers.. + ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, w, h); + + return Rectangle::intersectRectangles (x, y, w, h, clipX, clipY, clipW, clipH); +} + +void LowLevelGraphicsSoftwareRenderer::fillPathWithColour (const Path& path, const AffineTransform& t, + const Colour& colour, EdgeTable::OversamplingLevel quality) +{ + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedFillPathWithColour (r.getX(), r.getY(), r.getWidth(), r.getHeight(), path, t, colour, quality); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedFillPathWithColour (int clipX, int clipY, int clipW, int clipH, const Path& path, const AffineTransform& t, + const Colour& colour, EdgeTable::OversamplingLevel quality) +{ + const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset)); + int cx, cy, cw, ch; + + if (getPathBounds (clipX, clipY, clipW, clipH, path, transform, cx, cy, cw, ch)) + { + EdgeTable edgeTable (0, ch, quality); + + edgeTable.addPath (path, transform.translated ((float) -cx, (float) -cy)); + + int stride, pixelStride; + uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (cx, cy, cw, ch, stride, pixelStride); + + if (image.getFormat() == Image::RGB) + { + jassert (pixelStride == 3); + SolidColourEdgeTableRenderer renderer (pixels, stride, colour); + edgeTable.iterate (renderer, 0, 0, cw, ch, 0); + } + else if (image.getFormat() == Image::ARGB) + { + jassert (pixelStride == 4); + SolidColourEdgeTableRenderer renderer (pixels, stride, colour); + edgeTable.iterate (renderer, 0, 0, cw, ch, 0); + } + else if (image.getFormat() == Image::SingleChannel) + { + jassert (pixelStride == 1); + AlphaBitmapRenderer renderer (pixels, stride); + edgeTable.iterate (renderer, 0, 0, cw, ch, 0); + } + + image.releasePixelDataReadWrite (pixels); + } +} + +void LowLevelGraphicsSoftwareRenderer::fillPathWithGradient (const Path& path, const AffineTransform& t, const ColourGradient& gradient, EdgeTable::OversamplingLevel quality) +{ + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedFillPathWithGradient (r.getX(), r.getY(), r.getWidth(), r.getHeight(), + path, t, gradient, quality); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedFillPathWithGradient (int clipX, int clipY, int clipW, int clipH, const Path& path, const AffineTransform& t, + const ColourGradient& gradient, EdgeTable::OversamplingLevel quality) +{ + const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset)); + int cx, cy, cw, ch; + + if (getPathBounds (clipX, clipY, clipW, clipH, path, transform, cx, cy, cw, ch)) + { + int stride, pixelStride; + uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (cx, cy, cw, ch, stride, pixelStride); + + ColourGradient g2 (gradient); + + const bool isIdentity = g2.transform.isIdentity(); + if (isIdentity) + { + g2.x1 += xOffset - cx; + g2.x2 += xOffset - cx; + g2.y1 += yOffset - cy; + g2.y2 += yOffset - cy; + } + else + { + g2.transform = g2.transform.translated ((float) (xOffset - cx), + (float) (yOffset - cy)); + } + + int numLookupEntries; + PixelARGB* const lookupTable = g2.createLookupTable (numLookupEntries); + jassert (numLookupEntries > 0); + + EdgeTable edgeTable (0, ch, quality); + + edgeTable.addPath (path, transform.translated ((float) -cx, (float) -cy)); + + if (image.getFormat() == Image::RGB) + { + jassert (pixelStride == 3); + + if (g2.isRadial) + { + if (isIdentity) + { + GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); + edgeTable.iterate (renderer, 0, 0, cw, ch, 0); + } + else + { + GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); + edgeTable.iterate (renderer, 0, 0, cw, ch, 0); + } + } + else + { + GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); + edgeTable.iterate (renderer, 0, 0, cw, ch, 0); + } + } + else if (image.getFormat() == Image::ARGB) + { + jassert (pixelStride == 4); + + if (g2.isRadial) + { + if (isIdentity) + { + GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); + edgeTable.iterate (renderer, 0, 0, cw, ch, 0); + } + else + { + GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); + edgeTable.iterate (renderer, 0, 0, cw, ch, 0); + } + } + else + { + GradientEdgeTableRenderer renderer (pixels, stride, g2, lookupTable, numLookupEntries); + edgeTable.iterate (renderer, 0, 0, cw, ch, 0); + } + } + else if (image.getFormat() == Image::SingleChannel) + { + jassertfalse // not done! + } + + juce_free (lookupTable); + image.releasePixelDataReadWrite (pixels); + } +} + +void LowLevelGraphicsSoftwareRenderer::fillPathWithImage (const Path& path, const AffineTransform& transform, + const Image& sourceImage, int imageX, int imageY, float opacity, EdgeTable::OversamplingLevel quality) +{ + imageX += xOffset; + imageY += yOffset; + + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedFillPathWithImage (r.getX(), r.getY(), r.getWidth(), r.getHeight(), + path, transform, sourceImage, imageX, imageY, opacity, quality); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedFillPathWithImage (int x, int y, int w, int h, const Path& path, const AffineTransform& transform, + const Image& sourceImage, int imageX, int imageY, float opacity, EdgeTable::OversamplingLevel quality) +{ + if (Rectangle::intersectRectangles (x, y, w, h, imageX, imageY, sourceImage.getWidth(), sourceImage.getHeight())) + { + EdgeTable edgeTable (0, h, quality); + edgeTable.addPath (path, transform.translated ((float) (xOffset - x), (float) (yOffset - y))); + + int stride, pixelStride; + uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (x, y, w, h, stride, pixelStride); + + int srcStride, srcPixelStride; + const uint8* const srcPix = (const uint8*) sourceImage.lockPixelDataReadOnly (x - imageX, y - imageY, w, h, srcStride, srcPixelStride); + + const int alpha = jlimit (0, 255, roundDoubleToInt (opacity * 255.0f)); + + if (image.getFormat() == Image::RGB) + { + if (sourceImage.getFormat() == Image::RGB) + { + ImageFillEdgeTableRenderer renderer (pixels, stride, + srcPix, srcStride, + alpha, (PixelRGB*) 0); + edgeTable.iterate (renderer, 0, 0, w, h, 0); + } + else if (sourceImage.getFormat() == Image::ARGB) + { + ImageFillEdgeTableRenderer renderer (pixels, stride, + srcPix, srcStride, + alpha, (PixelARGB*) 0); + edgeTable.iterate (renderer, 0, 0, w, h, 0); + } + else + { + jassertfalse // not done! + } + } + else if (image.getFormat() == Image::ARGB) + { + if (sourceImage.getFormat() == Image::RGB) + { + ImageFillEdgeTableRenderer renderer (pixels, stride, + srcPix, srcStride, + alpha, (PixelRGB*) 0); + edgeTable.iterate (renderer, 0, 0, w, h, 0); + } + else if (sourceImage.getFormat() == Image::ARGB) + { + ImageFillEdgeTableRenderer renderer (pixels, stride, + srcPix, srcStride, + alpha, (PixelARGB*) 0); + edgeTable.iterate (renderer, 0, 0, w, h, 0); + } + else + { + jassertfalse // not done! + } + } + else + { + jassertfalse // not done! + } + + sourceImage.releasePixelDataReadOnly (srcPix); + image.releasePixelDataReadWrite (pixels); + } +} + +//============================================================================== +void LowLevelGraphicsSoftwareRenderer::fillAlphaChannelWithColour (const Image& clipImage, int x, int y, const Colour& colour) +{ + x += xOffset; + y += yOffset; + + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedFillAlphaChannelWithColour (r.getX(), r.getY(), r.getWidth(), r.getHeight(), + clipImage, x, y, colour); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedFillAlphaChannelWithColour (int clipX, int clipY, int clipW, int clipH, const Image& clipImage, int x, int y, const Colour& colour) +{ + int w = clipImage.getWidth(); + int h = clipImage.getHeight(); + int sx = 0; + int sy = 0; + + if (x < clipX) + { + sx = clipX - x; + w -= clipX - x; + x = clipX; + } + + if (y < clipY) + { + sy = clipY - y; + h -= clipY - y; + y = clipY; + } + + if (x + w > clipX + clipW) + w = clipX + clipW - x; + + if (y + h > clipY + clipH) + h = clipY + clipH - y; + + if (w > 0 && h > 0) + { + int stride, alphaStride, pixelStride; + uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (x, y, w, h, stride, pixelStride); + + const uint8* const alphaValues + = clipImage.lockPixelDataReadOnly (sx, sy, w, h, alphaStride, pixelStride); + +#if JUCE_BIG_ENDIAN + const uint8* const alphas = alphaValues; +#else + const uint8* const alphas = alphaValues + (clipImage.getFormat() == Image::ARGB ? 3 : 0); +#endif + + if (image.getFormat() == Image::RGB) + { + blendAlphaMapRGB (pixels, stride, + alphas, w, h, + pixelStride, alphaStride, + colour); + } + else if (image.getFormat() == Image::ARGB) + { + blendAlphaMapARGB (pixels, stride, + alphas, w, h, + pixelStride, alphaStride, + colour); + } + else + { + jassertfalse // not done! + } + + clipImage.releasePixelDataReadOnly (alphaValues); + image.releasePixelDataReadWrite (pixels); + } +} + +void LowLevelGraphicsSoftwareRenderer::fillAlphaChannelWithGradient (const Image& alphaChannelImage, int imageX, int imageY, const ColourGradient& gradient) +{ + imageX += xOffset; + imageY += yOffset; + + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedFillAlphaChannelWithGradient (r.getX(), r.getY(), r.getWidth(), r.getHeight(), + alphaChannelImage, imageX, imageY, gradient); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedFillAlphaChannelWithGradient (int x, int y, int w, int h, + const Image& alphaChannelImage, + int imageX, int imageY, const ColourGradient& gradient) +{ + if (Rectangle::intersectRectangles (x, y, w, h, imageX, imageY, alphaChannelImage.getWidth(), alphaChannelImage.getHeight())) + { + ColourGradient g2 (gradient); + g2.x1 += xOffset - x; + g2.x2 += xOffset - x; + g2.y1 += yOffset - y; + g2.y2 += yOffset - y; + + Image temp (g2.isOpaque() ? Image::RGB : Image::ARGB, w, h, true); + LowLevelGraphicsSoftwareRenderer tempG (temp); + tempG.fillRectWithGradient (0, 0, w, h, g2); + + clippedFillAlphaChannelWithImage (x, y, w, h, + alphaChannelImage, imageX, imageY, + temp, x, y, 1.0f); + } +} + +void LowLevelGraphicsSoftwareRenderer::fillAlphaChannelWithImage (const Image& alphaImage, int alphaImageX, int alphaImageY, + const Image& fillerImage, int fillerImageX, int fillerImageY, float opacity) +{ + alphaImageX += xOffset; + alphaImageY += yOffset; + + fillerImageX += xOffset; + fillerImageY += yOffset; + + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedFillAlphaChannelWithImage (r.getX(), r.getY(), r.getWidth(), r.getHeight(), + alphaImage, alphaImageX, alphaImageY, + fillerImage, fillerImageX, fillerImageY, opacity); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedFillAlphaChannelWithImage (int x, int y, int w, int h, const Image& alphaImage, int alphaImageX, int alphaImageY, + const Image& fillerImage, int fillerImageX, int fillerImageY, float opacity) +{ + if (Rectangle::intersectRectangles (x, y, w, h, alphaImageX, alphaImageY, alphaImage.getWidth(), alphaImage.getHeight()) + && Rectangle::intersectRectangles (x, y, w, h, fillerImageX, fillerImageY, fillerImage.getWidth(), fillerImage.getHeight())) + { + int dstStride, dstPixStride; + uint8* const dstPix = image.lockPixelDataReadWrite (x, y, w, h, dstStride, dstPixStride); + + int srcStride, srcPixStride; + const uint8* const srcPix = fillerImage.lockPixelDataReadOnly (x - fillerImageX, y - fillerImageY, w, h, srcStride, srcPixStride); + + int maskStride, maskPixStride; + const uint8* const alpha + = alphaImage.lockPixelDataReadOnly (x - alphaImageX, y - alphaImageY, w, h, maskStride, maskPixStride); + +#if JUCE_BIG_ENDIAN + const uint8* const alphaValues = alpha; +#else + const uint8* const alphaValues = alpha + (alphaImage.getFormat() == Image::ARGB ? 3 : 0); +#endif + + const int extraAlpha = jlimit (0, 0x100, roundDoubleToInt (opacity * 256.0f)); + + if (image.getFormat() == Image::RGB) + { + if (fillerImage.getFormat() == Image::RGB) + { + renderAlphaMap ((PixelRGB*) dstPix, dstStride, (const PixelRGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha); + } + else if (fillerImage.getFormat() == Image::ARGB) + { + renderAlphaMap ((PixelRGB*) dstPix, dstStride, (const PixelARGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha); + } + else + { + jassertfalse // not done! + } + } + else if (image.getFormat() == Image::ARGB) + { + if (fillerImage.getFormat() == Image::RGB) + { + renderAlphaMap ((PixelARGB*) dstPix, dstStride, (const PixelRGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha); + } + else if (fillerImage.getFormat() == Image::ARGB) + { + renderAlphaMap ((PixelARGB*) dstPix, dstStride, (const PixelARGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha); + } + else + { + jassertfalse // not done! + } + } + else + { + jassertfalse // not done! + } + + alphaImage.releasePixelDataReadOnly (alphaValues); + fillerImage.releasePixelDataReadOnly (srcPix); + image.releasePixelDataReadWrite (dstPix); + } +} + +//============================================================================== +void LowLevelGraphicsSoftwareRenderer::blendImage (const Image& sourceImage, int dx, int dy, int dw, int dh, int sx, int sy, float opacity) +{ + dx += xOffset; + dy += yOffset; + + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedBlendImage (r.getX(), r.getY(), r.getWidth(), r.getHeight(), + sourceImage, dx, dy, dw, dh, sx, sy, opacity); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedBlendImage (int clipX, int clipY, int clipW, int clipH, + const Image& sourceImage, int dx, int dy, int dw, int dh, int sx, int sy, float opacity) +{ + if (dx < clipX) + { + sx += clipX - dx; + dw -= clipX - dx; + dx = clipX; + } + + if (dy < clipY) + { + sy += clipY - dy; + dh -= clipY - dy; + dy = clipY; + } + + if (dx + dw > clipX + clipW) + dw = clipX + clipW - dx; + + if (dy + dh > clipY + clipH) + dh = clipY + clipH - dy; + + if (dw <= 0 || dh <= 0) + return; + + const uint8 alpha = (uint8) jlimit (0, 0xff, roundDoubleToInt (opacity * 256.0f)); + + if (alpha == 0) + return; + + int dstStride, dstPixelStride; + uint8* const dstPixels = image.lockPixelDataReadWrite (dx, dy, dw, dh, dstStride, dstPixelStride); + + int srcStride, srcPixelStride; + const uint8* const srcPixels = sourceImage.lockPixelDataReadOnly (sx, sy, dw, dh, srcStride, srcPixelStride); + + if (image.getFormat() == Image::ARGB) + { + if (sourceImage.getFormat() == Image::ARGB) + { + overlayImage ((PixelARGB*) dstPixels, dstStride, + (PixelARGB*) srcPixels, srcStride, + dw, dh, alpha); + } + else if (sourceImage.getFormat() == Image::RGB) + { + overlayImage ((PixelARGB*) dstPixels, dstStride, + (PixelRGB*) srcPixels, srcStride, + dw, dh, alpha); + } + else + { + jassertfalse + } + } + else if (image.getFormat() == Image::RGB) + { + if (sourceImage.getFormat() == Image::ARGB) + { + overlayImage ((PixelRGB*) dstPixels, dstStride, + (PixelARGB*) srcPixels, srcStride, + dw, dh, alpha); + } + else if (sourceImage.getFormat() == Image::RGB) + { + overlayImage ((PixelRGB*) dstPixels, dstStride, + (PixelRGB*) srcPixels, srcStride, + dw, dh, alpha); + } + else + { + jassertfalse + } + } + else + { + jassertfalse + } + + image.releasePixelDataReadWrite (dstPixels); + sourceImage.releasePixelDataReadOnly (srcPixels); +} + +//============================================================================== +void LowLevelGraphicsSoftwareRenderer::blendImageRescaling (const Image& sourceImage, + int dx, int dy, int dw, int dh, + int sx, int sy, int sw, int sh, + float alpha, + const Graphics::ResamplingQuality quality) +{ + if (sw > 0 && sh > 0) + { + if (sw == dw && sh == dh) + { + blendImage (sourceImage, + dx, dy, dw, dh, + sx, sy, alpha); + } + else + { + blendImageWarping (sourceImage, + sx, sy, sw, sh, + AffineTransform::translation ((float) -sx, + (float) -sy) + .scaled (dw / (float) sw, + dh / (float) sh) + .translated ((float) dx, + (float) dy), + alpha, + quality); + } + } +} + +//============================================================================== +void LowLevelGraphicsSoftwareRenderer::blendImageWarping (const Image& sourceImage, + int srcClipX, int srcClipY, int srcClipW, int srcClipH, + const AffineTransform& t, + float opacity, + const Graphics::ResamplingQuality quality) +{ + const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset)); + + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedBlendImageWarping (r.getX(), r.getY(), r.getWidth(), r.getHeight(), + sourceImage, srcClipX, srcClipY, srcClipW, srcClipH, + transform, opacity, quality); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedBlendImageWarping (int destClipX, int destClipY, int destClipW, int destClipH, + const Image& sourceImage, + int srcClipX, int srcClipY, int srcClipW, int srcClipH, + const AffineTransform& transform, + float opacity, + const Graphics::ResamplingQuality quality) +{ + if (opacity > 0 && destClipW > 0 && destClipH > 0 && ! transform.isSingularity()) + { + Rectangle::intersectRectangles (srcClipX, srcClipY, srcClipW, srcClipH, + 0, 0, sourceImage.getWidth(), sourceImage.getHeight()); + + if (srcClipW <= 0 || srcClipH <= 0) + return; + + jassert (srcClipX >= 0 && srcClipY >= 0); + + Path imageBounds; + imageBounds.addRectangle ((float) srcClipX, (float) srcClipY, (float) srcClipW, (float) srcClipH); + imageBounds.applyTransform (transform); + float imX, imY, imW, imH; + imageBounds.getBounds (imX, imY, imW, imH); + + if (Rectangle::intersectRectangles (destClipX, destClipY, destClipW, destClipH, + (int) floorf (imX), + (int) floorf (imY), + 1 + roundDoubleToInt (imW), + 1 + roundDoubleToInt (imH))) + { + const uint8 alpha = (uint8) jlimit (0, 0xff, roundDoubleToInt (opacity * 256.0f)); + + float srcX1 = (float) destClipX; + float srcY1 = (float) destClipY; + float srcX2 = (float) (destClipX + destClipW); + float srcY2 = srcY1; + float srcX3 = srcX1; + float srcY3 = (float) (destClipY + destClipH); + + AffineTransform inverse (transform.inverted()); + inverse.transformPoint (srcX1, srcY1); + inverse.transformPoint (srcX2, srcY2); + inverse.transformPoint (srcX3, srcY3); + + const double lineDX = (double) (srcX3 - srcX1) / destClipH; + const double lineDY = (double) (srcY3 - srcY1) / destClipH; + const double pixelDX = (double) (srcX2 - srcX1) / destClipW; + const double pixelDY = (double) (srcY2 - srcY1) / destClipW; + + if (image.getFormat() == Image::ARGB) + { + if (sourceImage.getFormat() == Image::ARGB) + { + transformedImageRender (image, sourceImage, + destClipX, destClipY, destClipW, destClipH, + srcClipX, srcClipY, srcClipW, srcClipH, + srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY, + alpha, quality, (PixelARGB*)0, (PixelARGB*)0); + } + else if (sourceImage.getFormat() == Image::RGB) + { + transformedImageRender (image, sourceImage, + destClipX, destClipY, destClipW, destClipH, + srcClipX, srcClipY, srcClipW, srcClipH, + srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY, + alpha, quality, (PixelARGB*)0, (PixelRGB*)0); + } + else + { + jassertfalse + } + } + else if (image.getFormat() == Image::RGB) + { + if (sourceImage.getFormat() == Image::ARGB) + { + transformedImageRender (image, sourceImage, + destClipX, destClipY, destClipW, destClipH, + srcClipX, srcClipY, srcClipW, srcClipH, + srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY, + alpha, quality, (PixelRGB*)0, (PixelARGB*)0); + } + else if (sourceImage.getFormat() == Image::RGB) + { + transformedImageRender (image, sourceImage, + destClipX, destClipY, destClipW, destClipH, + srcClipX, srcClipY, srcClipW, srcClipH, + srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY, + alpha, quality, (PixelRGB*)0, (PixelRGB*)0); + } + else + { + jassertfalse + } + } + else + { + jassertfalse + } + } + } +} + +//============================================================================== +void LowLevelGraphicsSoftwareRenderer::drawLine (double x1, double y1, double x2, double y2, const Colour& colour) +{ + x1 += xOffset; + y1 += yOffset; + x2 += xOffset; + y2 += yOffset; + + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedDrawLine (r.getX(), r.getY(), r.getWidth(), r.getHeight(), + x1, y1, x2, y2, colour); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedDrawLine (int clipX, int clipY, int clipW, int clipH, double x1, double y1, double x2, double y2, const Colour& colour) +{ + if (clipW > 0 && clipH > 0) + { + if (x1 == x2) + { + if (y2 < y1) + swapVariables (y1, y2); + + clippedDrawVerticalLine (clipX, clipY, clipW, clipH, roundDoubleToInt (x1), y1, y2, colour); + } + else if (y1 == y2) + { + if (x2 < x1) + swapVariables (x1, x2); + + clippedDrawHorizontalLine (clipX, clipY, clipW, clipH, roundDoubleToInt (y1), x1, x2, colour); + } + else + { + double gradient = (y2 - y1) / (x2 - x1); + + if (fabs (gradient) > 1.0) + { + gradient = 1.0 / gradient; + + int y = roundDoubleToInt (y1); + const int startY = y; + int endY = roundDoubleToInt (y2); + + if (y > endY) + swapVariables (y, endY); + + while (y < endY) + { + const double x = x1 + gradient * (y - startY); + clippedDrawHorizontalLine (clipX, clipY, clipW, clipH, y, x, x + 1.0, colour); + ++y; + } + } + else + { + int x = roundDoubleToInt (x1); + const int startX = x; + int endX = roundDoubleToInt (x2); + + if (x > endX) + swapVariables (x, endX); + + while (x < endX) + { + const double y = y1 + gradient * (x - startX); + clippedDrawVerticalLine (clipX, clipY, clipW, clipH, x, y, y + 1.0, colour); + ++x; + } + } + } + } +} + +void LowLevelGraphicsSoftwareRenderer::drawVerticalLine (const int x, double top, double bottom, const Colour& col) +{ + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedDrawVerticalLine (r.getX(), r.getY(), r.getWidth(), r.getHeight(), + x + xOffset, top + yOffset, bottom + yOffset, col); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedDrawVerticalLine (int clipX, int clipY, int clipW, int clipH, + const int x, double top, double bottom, const Colour& col) +{ + jassert (top <= bottom); + + if (((unsigned int) (x - clipX)) < (unsigned int) clipW + && top < clipY + clipH + && bottom > clipY + && clipW > 0) + { + if (top < clipY) + top = clipY; + + if (bottom > clipY + clipH) + bottom = clipY + clipH; + + if (bottom > top) + drawVertical (x, top, bottom, col); + } +} + +void LowLevelGraphicsSoftwareRenderer::drawHorizontalLine (const int y, double left, double right, const Colour& col) +{ + for (RectangleList::Iterator i (*clip); i.next();) + { + const Rectangle& r = *i.getRectangle(); + + clippedDrawHorizontalLine (r.getX(), r.getY(), r.getWidth(), r.getHeight(), + y + yOffset, left + xOffset, right + xOffset, col); + } +} + +void LowLevelGraphicsSoftwareRenderer::clippedDrawHorizontalLine (int clipX, int clipY, int clipW, int clipH, + const int y, double left, double right, const Colour& col) +{ + jassert (left <= right); + + if (((unsigned int) (y - clipY)) < (unsigned int) clipH + && left < clipX + clipW + && right > clipX + && clipW > 0) + { + if (left < clipX) + left = clipX; + + if (right > clipX + clipW) + right = clipX + clipW; + + if (right > left) + drawHorizontal (y, left, right, col); + } +} + +void LowLevelGraphicsSoftwareRenderer::drawVertical (const int x, + const double top, + const double bottom, + const Colour& col) +{ + int wholeStart = (int) top; + const int wholeEnd = (int) bottom; + + const int lastAlpha = roundDoubleToInt (255.0 * (bottom - wholeEnd)); + const int totalPixels = (wholeEnd - wholeStart) + (lastAlpha > 0 ? 1 : 0); + + if (totalPixels <= 0) + return; + + int lineStride, dstPixelStride; + uint8* const dstPixels = image.lockPixelDataReadWrite (x, wholeStart, 1, totalPixels, lineStride, dstPixelStride); + uint8* dest = dstPixels; + + PixelARGB colour (col.getPixelARGB()); + + if (wholeEnd == wholeStart) + { + if (image.getFormat() == Image::ARGB) + ((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top))); + else if (image.getFormat() == Image::RGB) + ((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top))); + else + { + jassertfalse + } + } + else + { + if (image.getFormat() == Image::ARGB) + { + ((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart)))); + ++wholeStart; + dest += lineStride; + + if (colour.getAlpha() == 0xff) + { + while (wholeEnd > wholeStart) + { + ((PixelARGB*) dest)->set (colour); + ++wholeStart; + dest += lineStride; + } + } + else + { + while (wholeEnd > wholeStart) + { + ((PixelARGB*) dest)->blend (colour); + ++wholeStart; + dest += lineStride; + } + } + + if (lastAlpha > 0) + { + ((PixelARGB*) dest)->blend (colour, lastAlpha); + } + } + else if (image.getFormat() == Image::RGB) + { + ((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart)))); + ++wholeStart; + dest += lineStride; + + if (colour.getAlpha() == 0xff) + { + while (wholeEnd > wholeStart) + { + ((PixelRGB*) dest)->set (colour); + ++wholeStart; + dest += lineStride; + } + } + else + { + while (wholeEnd > wholeStart) + { + ((PixelRGB*) dest)->blend (colour); + ++wholeStart; + dest += lineStride; + } + } + + if (lastAlpha > 0) + { + ((PixelRGB*) dest)->blend (colour, lastAlpha); + } + } + else + { + jassertfalse + } + } + + image.releasePixelDataReadWrite (dstPixels); +} + +void LowLevelGraphicsSoftwareRenderer::drawHorizontal (const int y, + const double top, + const double bottom, + const Colour& col) +{ + int wholeStart = (int) top; + const int wholeEnd = (int) bottom; + + const int lastAlpha = roundDoubleToInt (255.0 * (bottom - wholeEnd)); + const int totalPixels = (wholeEnd - wholeStart) + (lastAlpha > 0 ? 1 : 0); + + if (totalPixels <= 0) + return; + + int lineStride, dstPixelStride; + uint8* const dstPixels = image.lockPixelDataReadWrite (wholeStart, y, totalPixels, 1, lineStride, dstPixelStride); + uint8* dest = dstPixels; + + PixelARGB colour (col.getPixelARGB()); + + if (wholeEnd == wholeStart) + { + if (image.getFormat() == Image::ARGB) + ((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top))); + else if (image.getFormat() == Image::RGB) + ((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top))); + else + { + jassertfalse + } + } + else + { + if (image.getFormat() == Image::ARGB) + { + ((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart)))); + dest += dstPixelStride; + ++wholeStart; + + if (colour.getAlpha() == 0xff) + { + while (wholeEnd > wholeStart) + { + ((PixelARGB*) dest)->set (colour); + dest += dstPixelStride; + ++wholeStart; + } + } + else + { + while (wholeEnd > wholeStart) + { + ((PixelARGB*) dest)->blend (colour); + dest += dstPixelStride; + ++wholeStart; + } + } + + if (lastAlpha > 0) + { + ((PixelARGB*) dest)->blend (colour, lastAlpha); + } + } + else if (image.getFormat() == Image::RGB) + { + ((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart)))); + dest += dstPixelStride; + ++wholeStart; + + if (colour.getAlpha() == 0xff) + { + while (wholeEnd > wholeStart) + { + ((PixelRGB*) dest)->set (colour); + dest += dstPixelStride; + ++wholeStart; + } + } + else + { + while (wholeEnd > wholeStart) + { + ((PixelRGB*) dest)->blend (colour); + dest += dstPixelStride; + ++wholeStart; + } + } + + if (lastAlpha > 0) + { + ((PixelRGB*) dest)->blend (colour, lastAlpha); + } + } + else + { + jassertfalse + } + } + + image.releasePixelDataReadWrite (dstPixels); +} + + + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/graphics/imaging/image_file_formats/juce_JPEGLoader.cpp b/src/juce_appframework/gui/graphics/imaging/image_file_formats/juce_JPEGLoader.cpp index 0a0f3766e6..e478bf5ba4 100644 --- a/src/juce_appframework/gui/graphics/imaging/image_file_formats/juce_JPEGLoader.cpp +++ b/src/juce_appframework/gui/graphics/imaging/image_file_formats/juce_JPEGLoader.cpp @@ -1,378 +1,378 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../../juce_core/basics/juce_StandardHeader.h" - -#if JUCE_MSVC - #pragma warning (push) -#endif - -namespace jpeglibNamespace -{ - extern "C" - { - #define JPEG_INTERNALS - #undef FAR - #include "jpglib/jpeglib.h" - - #include "jpglib/jcapimin.c" - #include "jpglib/jcapistd.c" - #include "jpglib/jccoefct.c" - #include "jpglib/jccolor.c" - #undef FIX - #include "jpglib/jcdctmgr.c" - #undef CONST_BITS - #include "jpglib/jchuff.c" - #undef emit_byte - #include "jpglib/jcinit.c" - #include "jpglib/jcmainct.c" - #include "jpglib/jcmarker.c" - #include "jpglib/jcmaster.c" - #include "jpglib/jcomapi.c" - #include "jpglib/jcparam.c" - #include "jpglib/jcphuff.c" - #include "jpglib/jcprepct.c" - #include "jpglib/jcsample.c" - #include "jpglib/jctrans.c" - #include "jpglib/jdapistd.c" - #include "jpglib/jdapimin.c" - #include "jpglib/jdatasrc.c" - #include "jpglib/jdcoefct.c" - #undef FIX - #include "jpglib/jdcolor.c" - #undef FIX - #include "jpglib/jddctmgr.c" - #undef CONST_BITS - #undef ASSIGN_STATE - #include "jpglib/jdhuff.c" - #include "jpglib/jdinput.c" - #include "jpglib/jdmainct.c" - #include "jpglib/jdmarker.c" - #include "jpglib/jdmaster.c" - #undef FIX - #include "jpglib/jdmerge.c" - #undef ASSIGN_STATE - #include "jpglib/jdphuff.c" - #include "jpglib/jdpostct.c" - #undef FIX - #include "jpglib/jdsample.c" - #include "jpglib/jdtrans.c" - #include "jpglib/jfdctflt.c" - #include "jpglib/jfdctint.c" - #undef CONST_BITS - #undef MULTIPLY - #undef FIX_0_541196100 - #include "jpglib/jfdctfst.c" - #undef FIX_0_541196100 - #include "jpglib/jidctflt.c" - #undef CONST_BITS - #undef FIX_1_847759065 - #undef MULTIPLY - #undef DEQUANTIZE - #undef DESCALE - #include "jpglib/jidctfst.c" - #undef CONST_BITS - #undef FIX_1_847759065 - #undef MULTIPLY - #undef DEQUANTIZE - #include "jpglib/jidctint.c" - #include "jpglib/jidctred.c" - #include "jpglib/jmemmgr.c" - #include "jpglib/jmemnobs.c" - #include "jpglib/jquant1.c" - #include "jpglib/jquant2.c" - #include "jpglib/jutils.c" - #include "jpglib/transupp.c" - } -} - -#if JUCE_MSVC - #pragma warning (pop) -#endif - -BEGIN_JUCE_NAMESPACE - -#include "../juce_Image.h" -#include "../../../../../juce_core/io/juce_InputStream.h" -#include "../../../../../juce_core/io/juce_OutputStream.h" -#include "../../colour/juce_PixelFormats.h" - -using namespace jpeglibNamespace; - -//============================================================================== -struct JPEGDecodingFailure {}; - -static void fatalErrorHandler (j_common_ptr) -{ - throw JPEGDecodingFailure(); -} - -static void silentErrorCallback1 (j_common_ptr) {} -static void silentErrorCallback2 (j_common_ptr, int) {} -static void silentErrorCallback3 (j_common_ptr, char*) {} - -static void setupSilentErrorHandler (struct jpeg_error_mgr& err) -{ - zerostruct (err); - - err.error_exit = fatalErrorHandler; - err.emit_message = silentErrorCallback2; - err.output_message = silentErrorCallback1; - err.format_message = silentErrorCallback3; - err.reset_error_mgr = silentErrorCallback1; -} - - -//============================================================================== -static void dummyCallback1 (j_decompress_ptr) throw() -{ -} - -static void jpegSkip (j_decompress_ptr decompStruct, long num) throw() -{ - decompStruct->src->next_input_byte += num; - - num = jmin (num, (int) decompStruct->src->bytes_in_buffer); - decompStruct->src->bytes_in_buffer -= num; -} - -static boolean jpegFill (j_decompress_ptr) throw() -{ - return 0; -} - -//============================================================================== -Image* juce_loadJPEGImageFromStream (InputStream& in) throw() -{ - MemoryBlock mb; - in.readIntoMemoryBlock (mb); - - Image* image = 0; - - if (mb.getSize() > 16) - { - struct jpeg_decompress_struct jpegDecompStruct; - - struct jpeg_error_mgr jerr; - setupSilentErrorHandler (jerr); - jpegDecompStruct.err = &jerr; - - jpeg_create_decompress (&jpegDecompStruct); - - jpegDecompStruct.src = (jpeg_source_mgr*)(jpegDecompStruct.mem->alloc_small) - ((j_common_ptr)(&jpegDecompStruct), JPOOL_PERMANENT, sizeof (jpeg_source_mgr)); - - jpegDecompStruct.src->init_source = dummyCallback1; - jpegDecompStruct.src->fill_input_buffer = jpegFill; - jpegDecompStruct.src->skip_input_data = jpegSkip; - jpegDecompStruct.src->resync_to_restart = jpeg_resync_to_restart; - jpegDecompStruct.src->term_source = dummyCallback1; - - jpegDecompStruct.src->next_input_byte = (const unsigned char*) mb.getData(); - jpegDecompStruct.src->bytes_in_buffer = mb.getSize(); - - try - { - jpeg_read_header (&jpegDecompStruct, TRUE); - - jpeg_calc_output_dimensions (&jpegDecompStruct); - - const int width = jpegDecompStruct.output_width; - const int height = jpegDecompStruct.output_height; - - jpegDecompStruct.out_color_space = JCS_RGB; - - JSAMPARRAY buffer - = (*jpegDecompStruct.mem->alloc_sarray) ((j_common_ptr) &jpegDecompStruct, - JPOOL_IMAGE, - width * 3, 1); - - if (jpeg_start_decompress (&jpegDecompStruct)) - { - image = new Image (Image::RGB, width, height, false); - - for (int y = 0; y < height; ++y) - { - jpeg_read_scanlines (&jpegDecompStruct, buffer, 1); - - int stride, pixelStride; - uint8* pixels = image->lockPixelDataReadWrite (0, y, width, 1, stride, pixelStride); - const uint8* src = *buffer; - uint8* dest = pixels; - - for (int i = width; --i >= 0;) - { - ((PixelRGB*) dest)->setARGB (0, src[0], src[1], src[2]); - dest += pixelStride; - src += 3; - } - - image->releasePixelDataReadWrite (pixels); - } - - jpeg_finish_decompress (&jpegDecompStruct); - } - - jpeg_destroy_decompress (&jpegDecompStruct); - } - catch (...) - {} - - in.setPosition (((char*) jpegDecompStruct.src->next_input_byte) - (char*) mb.getData()); - } - - return image; -} - - -//============================================================================== -static const int bufferSize = 512; - -struct JuceJpegDest : public jpeg_destination_mgr -{ - OutputStream* output; - char* buffer; -}; - -static void jpegWriteInit (j_compress_ptr) throw() -{ -} - -static void jpegWriteTerminate (j_compress_ptr cinfo) throw() -{ - JuceJpegDest* const dest = (JuceJpegDest*) cinfo->dest; - - const int numToWrite = bufferSize - dest->free_in_buffer; - dest->output->write (dest->buffer, numToWrite); -} - -static boolean jpegWriteFlush (j_compress_ptr cinfo) throw() -{ - JuceJpegDest* const dest = (JuceJpegDest*) cinfo->dest; - - const int numToWrite = bufferSize; - - dest->next_output_byte = (JOCTET*) dest->buffer; - dest->free_in_buffer = bufferSize; - - return dest->output->write (dest->buffer, numToWrite); -} - -//============================================================================== -bool juce_writeJPEGImageToStream (const Image& image, - OutputStream& out, - float quality) throw() -{ - if (image.hasAlphaChannel()) - { - // this method could fill the background in white and still save the image.. - jassertfalse - return true; - } - - struct jpeg_compress_struct jpegCompStruct; - - struct jpeg_error_mgr jerr; - setupSilentErrorHandler (jerr); - jpegCompStruct.err = &jerr; - - jpeg_create_compress (&jpegCompStruct); - - JuceJpegDest dest; - jpegCompStruct.dest = &dest; - - dest.output = &out; - dest.buffer = (char*) juce_malloc (bufferSize); - dest.next_output_byte = (JOCTET*) dest.buffer; - dest.free_in_buffer = bufferSize; - dest.init_destination = jpegWriteInit; - dest.empty_output_buffer = jpegWriteFlush; - dest.term_destination = jpegWriteTerminate; - - jpegCompStruct.image_width = image.getWidth(); - jpegCompStruct.image_height = image.getHeight(); - jpegCompStruct.input_components = 3; - jpegCompStruct.in_color_space = JCS_RGB; - jpegCompStruct.write_JFIF_header = 1; - - jpegCompStruct.X_density = 72; - jpegCompStruct.Y_density = 72; - - jpeg_set_defaults (&jpegCompStruct); - - jpegCompStruct.dct_method = JDCT_FLOAT; - jpegCompStruct.optimize_coding = 1; -// jpegCompStruct.smoothing_factor = 10; - - if (quality < 0.0f) - quality = 0.85f; - - jpeg_set_quality (&jpegCompStruct, jlimit (0, 100, roundFloatToInt (quality * 100.0f)), TRUE); - - jpeg_start_compress (&jpegCompStruct, TRUE); - - const int strideBytes = jpegCompStruct.image_width * jpegCompStruct.input_components; - - JSAMPARRAY buffer = (*jpegCompStruct.mem->alloc_sarray) ((j_common_ptr) &jpegCompStruct, - JPOOL_IMAGE, - strideBytes, 1); - - while (jpegCompStruct.next_scanline < jpegCompStruct.image_height) - { - int stride, pixelStride; - const uint8* pixels = image.lockPixelDataReadOnly (0, jpegCompStruct.next_scanline, jpegCompStruct.image_width, 1, stride, pixelStride); - const uint8* src = pixels; - uint8* dst = *buffer; - - for (int i = jpegCompStruct.image_width; --i >= 0;) - { - *dst++ = ((const PixelRGB*) src)->getRed(); - *dst++ = ((const PixelRGB*) src)->getGreen(); - *dst++ = ((const PixelRGB*) src)->getBlue(); - src += pixelStride; - } - - jpeg_write_scanlines (&jpegCompStruct, buffer, 1); - image.releasePixelDataReadOnly (pixels); - } - - jpeg_finish_compress (&jpegCompStruct); - jpeg_destroy_compress (&jpegCompStruct); - - juce_free (dest.buffer); - - out.flush(); - - return true; -} - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../../juce_core/basics/juce_StandardHeader.h" + +#if JUCE_MSVC + #pragma warning (push) +#endif + +namespace jpeglibNamespace +{ + extern "C" + { + #define JPEG_INTERNALS + #undef FAR + #include "jpglib/jpeglib.h" + + #include "jpglib/jcapimin.c" + #include "jpglib/jcapistd.c" + #include "jpglib/jccoefct.c" + #include "jpglib/jccolor.c" + #undef FIX + #include "jpglib/jcdctmgr.c" + #undef CONST_BITS + #include "jpglib/jchuff.c" + #undef emit_byte + #include "jpglib/jcinit.c" + #include "jpglib/jcmainct.c" + #include "jpglib/jcmarker.c" + #include "jpglib/jcmaster.c" + #include "jpglib/jcomapi.c" + #include "jpglib/jcparam.c" + #include "jpglib/jcphuff.c" + #include "jpglib/jcprepct.c" + #include "jpglib/jcsample.c" + #include "jpglib/jctrans.c" + #include "jpglib/jdapistd.c" + #include "jpglib/jdapimin.c" + #include "jpglib/jdatasrc.c" + #include "jpglib/jdcoefct.c" + #undef FIX + #include "jpglib/jdcolor.c" + #undef FIX + #include "jpglib/jddctmgr.c" + #undef CONST_BITS + #undef ASSIGN_STATE + #include "jpglib/jdhuff.c" + #include "jpglib/jdinput.c" + #include "jpglib/jdmainct.c" + #include "jpglib/jdmarker.c" + #include "jpglib/jdmaster.c" + #undef FIX + #include "jpglib/jdmerge.c" + #undef ASSIGN_STATE + #include "jpglib/jdphuff.c" + #include "jpglib/jdpostct.c" + #undef FIX + #include "jpglib/jdsample.c" + #include "jpglib/jdtrans.c" + #include "jpglib/jfdctflt.c" + #include "jpglib/jfdctint.c" + #undef CONST_BITS + #undef MULTIPLY + #undef FIX_0_541196100 + #include "jpglib/jfdctfst.c" + #undef FIX_0_541196100 + #include "jpglib/jidctflt.c" + #undef CONST_BITS + #undef FIX_1_847759065 + #undef MULTIPLY + #undef DEQUANTIZE + #undef DESCALE + #include "jpglib/jidctfst.c" + #undef CONST_BITS + #undef FIX_1_847759065 + #undef MULTIPLY + #undef DEQUANTIZE + #include "jpglib/jidctint.c" + #include "jpglib/jidctred.c" + #include "jpglib/jmemmgr.c" + #include "jpglib/jmemnobs.c" + #include "jpglib/jquant1.c" + #include "jpglib/jquant2.c" + #include "jpglib/jutils.c" + #include "jpglib/transupp.c" + } +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +BEGIN_JUCE_NAMESPACE + +#include "../juce_Image.h" +#include "../../../../../juce_core/io/juce_InputStream.h" +#include "../../../../../juce_core/io/juce_OutputStream.h" +#include "../../colour/juce_PixelFormats.h" + +using namespace jpeglibNamespace; + +//============================================================================== +struct JPEGDecodingFailure {}; + +static void fatalErrorHandler (j_common_ptr) +{ + throw JPEGDecodingFailure(); +} + +static void silentErrorCallback1 (j_common_ptr) {} +static void silentErrorCallback2 (j_common_ptr, int) {} +static void silentErrorCallback3 (j_common_ptr, char*) {} + +static void setupSilentErrorHandler (struct jpeg_error_mgr& err) +{ + zerostruct (err); + + err.error_exit = fatalErrorHandler; + err.emit_message = silentErrorCallback2; + err.output_message = silentErrorCallback1; + err.format_message = silentErrorCallback3; + err.reset_error_mgr = silentErrorCallback1; +} + + +//============================================================================== +static void dummyCallback1 (j_decompress_ptr) throw() +{ +} + +static void jpegSkip (j_decompress_ptr decompStruct, long num) throw() +{ + decompStruct->src->next_input_byte += num; + + num = jmin (num, (int) decompStruct->src->bytes_in_buffer); + decompStruct->src->bytes_in_buffer -= num; +} + +static boolean jpegFill (j_decompress_ptr) throw() +{ + return 0; +} + +//============================================================================== +Image* juce_loadJPEGImageFromStream (InputStream& in) throw() +{ + MemoryBlock mb; + in.readIntoMemoryBlock (mb); + + Image* image = 0; + + if (mb.getSize() > 16) + { + struct jpeg_decompress_struct jpegDecompStruct; + + struct jpeg_error_mgr jerr; + setupSilentErrorHandler (jerr); + jpegDecompStruct.err = &jerr; + + jpeg_create_decompress (&jpegDecompStruct); + + jpegDecompStruct.src = (jpeg_source_mgr*)(jpegDecompStruct.mem->alloc_small) + ((j_common_ptr)(&jpegDecompStruct), JPOOL_PERMANENT, sizeof (jpeg_source_mgr)); + + jpegDecompStruct.src->init_source = dummyCallback1; + jpegDecompStruct.src->fill_input_buffer = jpegFill; + jpegDecompStruct.src->skip_input_data = jpegSkip; + jpegDecompStruct.src->resync_to_restart = jpeg_resync_to_restart; + jpegDecompStruct.src->term_source = dummyCallback1; + + jpegDecompStruct.src->next_input_byte = (const unsigned char*) mb.getData(); + jpegDecompStruct.src->bytes_in_buffer = mb.getSize(); + + try + { + jpeg_read_header (&jpegDecompStruct, TRUE); + + jpeg_calc_output_dimensions (&jpegDecompStruct); + + const int width = jpegDecompStruct.output_width; + const int height = jpegDecompStruct.output_height; + + jpegDecompStruct.out_color_space = JCS_RGB; + + JSAMPARRAY buffer + = (*jpegDecompStruct.mem->alloc_sarray) ((j_common_ptr) &jpegDecompStruct, + JPOOL_IMAGE, + width * 3, 1); + + if (jpeg_start_decompress (&jpegDecompStruct)) + { + image = new Image (Image::RGB, width, height, false); + + for (int y = 0; y < height; ++y) + { + jpeg_read_scanlines (&jpegDecompStruct, buffer, 1); + + int stride, pixelStride; + uint8* pixels = image->lockPixelDataReadWrite (0, y, width, 1, stride, pixelStride); + const uint8* src = *buffer; + uint8* dest = pixels; + + for (int i = width; --i >= 0;) + { + ((PixelRGB*) dest)->setARGB (0, src[0], src[1], src[2]); + dest += pixelStride; + src += 3; + } + + image->releasePixelDataReadWrite (pixels); + } + + jpeg_finish_decompress (&jpegDecompStruct); + } + + jpeg_destroy_decompress (&jpegDecompStruct); + } + catch (...) + {} + + in.setPosition (((char*) jpegDecompStruct.src->next_input_byte) - (char*) mb.getData()); + } + + return image; +} + + +//============================================================================== +static const int bufferSize = 512; + +struct JuceJpegDest : public jpeg_destination_mgr +{ + OutputStream* output; + char* buffer; +}; + +static void jpegWriteInit (j_compress_ptr) throw() +{ +} + +static void jpegWriteTerminate (j_compress_ptr cinfo) throw() +{ + JuceJpegDest* const dest = (JuceJpegDest*) cinfo->dest; + + const int numToWrite = bufferSize - dest->free_in_buffer; + dest->output->write (dest->buffer, numToWrite); +} + +static boolean jpegWriteFlush (j_compress_ptr cinfo) throw() +{ + JuceJpegDest* const dest = (JuceJpegDest*) cinfo->dest; + + const int numToWrite = bufferSize; + + dest->next_output_byte = (JOCTET*) dest->buffer; + dest->free_in_buffer = bufferSize; + + return dest->output->write (dest->buffer, numToWrite); +} + +//============================================================================== +bool juce_writeJPEGImageToStream (const Image& image, + OutputStream& out, + float quality) throw() +{ + if (image.hasAlphaChannel()) + { + // this method could fill the background in white and still save the image.. + jassertfalse + return true; + } + + struct jpeg_compress_struct jpegCompStruct; + + struct jpeg_error_mgr jerr; + setupSilentErrorHandler (jerr); + jpegCompStruct.err = &jerr; + + jpeg_create_compress (&jpegCompStruct); + + JuceJpegDest dest; + jpegCompStruct.dest = &dest; + + dest.output = &out; + dest.buffer = (char*) juce_malloc (bufferSize); + dest.next_output_byte = (JOCTET*) dest.buffer; + dest.free_in_buffer = bufferSize; + dest.init_destination = jpegWriteInit; + dest.empty_output_buffer = jpegWriteFlush; + dest.term_destination = jpegWriteTerminate; + + jpegCompStruct.image_width = image.getWidth(); + jpegCompStruct.image_height = image.getHeight(); + jpegCompStruct.input_components = 3; + jpegCompStruct.in_color_space = JCS_RGB; + jpegCompStruct.write_JFIF_header = 1; + + jpegCompStruct.X_density = 72; + jpegCompStruct.Y_density = 72; + + jpeg_set_defaults (&jpegCompStruct); + + jpegCompStruct.dct_method = JDCT_FLOAT; + jpegCompStruct.optimize_coding = 1; +// jpegCompStruct.smoothing_factor = 10; + + if (quality < 0.0f) + quality = 0.85f; + + jpeg_set_quality (&jpegCompStruct, jlimit (0, 100, roundFloatToInt (quality * 100.0f)), TRUE); + + jpeg_start_compress (&jpegCompStruct, TRUE); + + const int strideBytes = jpegCompStruct.image_width * jpegCompStruct.input_components; + + JSAMPARRAY buffer = (*jpegCompStruct.mem->alloc_sarray) ((j_common_ptr) &jpegCompStruct, + JPOOL_IMAGE, + strideBytes, 1); + + while (jpegCompStruct.next_scanline < jpegCompStruct.image_height) + { + int stride, pixelStride; + const uint8* pixels = image.lockPixelDataReadOnly (0, jpegCompStruct.next_scanline, jpegCompStruct.image_width, 1, stride, pixelStride); + const uint8* src = pixels; + uint8* dst = *buffer; + + for (int i = jpegCompStruct.image_width; --i >= 0;) + { + *dst++ = ((const PixelRGB*) src)->getRed(); + *dst++ = ((const PixelRGB*) src)->getGreen(); + *dst++ = ((const PixelRGB*) src)->getBlue(); + src += pixelStride; + } + + jpeg_write_scanlines (&jpegCompStruct, buffer, 1); + image.releasePixelDataReadOnly (pixels); + } + + jpeg_finish_compress (&jpegCompStruct); + jpeg_destroy_compress (&jpegCompStruct); + + juce_free (dest.buffer); + + out.flush(); + + return true; +} + + +END_JUCE_NAMESPACE diff --git a/src/juce_core/containers/juce_ArrayAllocationBase.h b/src/juce_core/containers/juce_ArrayAllocationBase.h index da0ae4f997..3377d947f3 100644 --- a/src/juce_core/containers/juce_ArrayAllocationBase.h +++ b/src/juce_core/containers/juce_ArrayAllocationBase.h @@ -104,7 +104,7 @@ protected: elements = 0; } - numAllocated = numElements; + numAllocated = numElements; } }