@@ -833,7 +833,7 @@ public: | |||||
AudioIODeviceCallback* const oldCallback = currentCallback; | AudioIODeviceCallback* const oldCallback = currentCallback; | ||||
close(); | close(); | ||||
open (currentChansIn, currentChansOut, | |||||
open (BitArray (currentChansIn), BitArray (currentChansOut), | |||||
currentSampleRate, currentBlockSizeSamples); | currentSampleRate, currentBlockSizeSamples); | ||||
if (oldCallback != 0) | if (oldCallback != 0) | ||||
@@ -63,7 +63,6 @@ int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) throw() | |||||
{ | { | ||||
// this method only works for valid starting bytes of a short midi message | // this method only works for valid starting bytes of a short midi message | ||||
jassert (firstByte >= 0x80 | jassert (firstByte >= 0x80 | ||||
&& firstByte != 0xff | |||||
&& firstByte != 0xf0 | && firstByte != 0xf0 | ||||
&& firstByte != 0xf7); | && firstByte != 0xf7); | ||||
@@ -110,7 +110,7 @@ void MidiMessageCollector::removeNextBlockOfMessages (MidiBuffer& destBuffer, | |||||
{ | { | ||||
// if our list of events is longer than the buffer we're being | // if our list of events is longer than the buffer we're being | ||||
// asked for, scale them down to squeeze them all in.. | // asked for, scale them down to squeeze them all in.. | ||||
const int maxBlockLengthToUse = numSamples << 3; | |||||
const int maxBlockLengthToUse = numSamples << 5; | |||||
if (numSourceSamples > maxBlockLengthToUse) | if (numSourceSamples > maxBlockLengthToUse) | ||||
{ | { | ||||
@@ -150,7 +150,7 @@ void MidiMessageCollector::removeNextBlockOfMessages (MidiBuffer& destBuffer, | |||||
void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) | void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) | ||||
{ | { | ||||
MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity)); | MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity)); | ||||
m.setTimeStamp (Time::getMillisecondCounter() * 0.001); | |||||
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); | |||||
addMessageToQueue (m); | addMessageToQueue (m); | ||||
} | } | ||||
@@ -158,7 +158,7 @@ void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, in | |||||
void MidiMessageCollector::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) | void MidiMessageCollector::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) | ||||
{ | { | ||||
MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber)); | MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber)); | ||||
m.setTimeStamp (Time::getMillisecondCounter() * 0.001); | |||||
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); | |||||
addMessageToQueue (m); | addMessageToQueue (m); | ||||
} | } | ||||
@@ -1,444 +1,456 @@ | |||||
/* | |||||
============================================================================== | |||||
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" | |||||
//============================================================================== | |||||
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), | |||||
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::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) | |||||
{ | |||||
g.fillAll (findColour (backgroundColourId)); | |||||
if (editor == 0) | |||||
{ | |||||
const float alpha = isEnabled() ? 1.0f : 0.5f; | |||||
g.setColour (findColour (textColourId).withMultipliedAlpha (alpha)); | |||||
g.setFont (font); | |||||
g.drawFittedText (text, | |||||
3, 1, getWidth() - 6, getHeight() - 2, | |||||
justification, | |||||
jmax (1, (int) (getHeight() / font.getHeight()))); | |||||
g.setColour (findColour (outlineColourId).withMultipliedAlpha (alpha)); | |||||
g.drawRect (0, 0, getWidth(), getHeight()); | |||||
} | |||||
else if (isEnabled()) | |||||
{ | |||||
g.setColour (editor->findColour (TextEditor::backgroundColourId) | |||||
.overlaidWith (findColour (outlineColourId))); | |||||
g.drawRect (0, 0, getWidth(), getHeight()); | |||||
} | |||||
} | |||||
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 <TextEditor*> (current) != 0 | |||||
? current->getParentComponent() : current); | |||||
} | |||||
Component* getPreviousComponent (Component* current) | |||||
{ | |||||
return KeyboardFocusTraverser::getPreviousComponent (dynamic_cast <TextEditor*> (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" | |||||
//============================================================================== | |||||
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), | |||||
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) | |||||
{ | |||||
g.fillAll (findColour (backgroundColourId)); | |||||
if (editor == 0) | |||||
{ | |||||
const float alpha = isEnabled() ? 1.0f : 0.5f; | |||||
g.setColour (findColour (textColourId).withMultipliedAlpha (alpha)); | |||||
g.setFont (font); | |||||
g.drawFittedText (text, | |||||
horizontalBorderSize, | |||||
verticalBorderSize, | |||||
getWidth() - 2 * horizontalBorderSize, | |||||
getHeight() - 2 * verticalBorderSize, | |||||
justification, | |||||
jmax (1, (int) (getHeight() / font.getHeight()))); | |||||
g.setColour (findColour (outlineColourId).withMultipliedAlpha (alpha)); | |||||
g.drawRect (0, 0, getWidth(), getHeight()); | |||||
} | |||||
else if (isEnabled()) | |||||
{ | |||||
g.setColour (editor->findColour (TextEditor::backgroundColourId) | |||||
.overlaidWith (findColour (outlineColourId))); | |||||
g.drawRect (0, 0, getWidth(), getHeight()); | |||||
} | |||||
} | |||||
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 <TextEditor*> (current) != 0 | |||||
? current->getParentComponent() : current); | |||||
} | |||||
Component* getPreviousComponent (Component* current) | |||||
{ | |||||
return KeyboardFocusTraverser::getPreviousComponent (dynamic_cast <TextEditor*> (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 |
@@ -148,6 +148,12 @@ public: | |||||
/** Returns the type of justification, as set in setJustificationType(). */ | /** Returns the type of justification, as set in setJustificationType(). */ | ||||
const Justification getJustificationType() const throw() { return justification; } | const Justification getJustificationType() const throw() { return justification; } | ||||
/** Changes the gap that is left between the edge of the component and the text. | |||||
By default there's a small gap left at the sides of the component to allow for | |||||
the drawing of the border, but you can change this if necessary. | |||||
*/ | |||||
void setBorderSize (int horizontalBorder, int verticalBorder); | |||||
/** Makes this label "stick to" another component. | /** Makes this label "stick to" another component. | ||||
This will cause the label to follow another component around, staying | This will cause the label to follow another component around, staying | ||||
@@ -294,7 +300,7 @@ private: | |||||
SortedSet <void*> listeners; | SortedSet <void*> listeners; | ||||
Component* ownerComponent; | Component* ownerComponent; | ||||
ComponentDeletionWatcher* deletionWatcher; | ComponentDeletionWatcher* deletionWatcher; | ||||
int horizontalBorderSize, verticalBorderSize; | |||||
bool editSingleClick : 1; | bool editSingleClick : 1; | ||||
bool editDoubleClick : 1; | bool editDoubleClick : 1; | ||||
bool lossOfFocusDiscardsChanges : 1; | bool lossOfFocusDiscardsChanges : 1; | ||||
@@ -1,421 +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 <PropertyComponent*>& 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 <PropertyComponent*> (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 <PropertyComponent*> (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 <PropertyComponent*> (getChildComponent (i)); | |||||
if (pec != 0) | |||||
pec->setVisible (open); | |||||
} | |||||
// (unable to use the syntax findParentComponentOfClass <DragAndDropContainer> () 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 <PropertyComponent*> (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 <PropertySectionComponent*> (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 <PropertySectionComponent*> (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 <PropertyComponent*>& newProperties) | |||||
{ | |||||
if (propertyHolderComponent->getNumChildComponents() == 0) | |||||
repaint(); | |||||
propertyHolderComponent->addAndMakeVisible (new PropertySectionComponent (String::empty, | |||||
newProperties, | |||||
true), 0); | |||||
updatePropHolderLayout(); | |||||
} | |||||
void PropertyPanel::addSection (const String& sectionTitle, | |||||
const Array <PropertyComponent*>& 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 <PropertySectionComponent*> (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 <PropertySectionComponent*> (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 <PropertySectionComponent*> (propertyHolderComponent->getChildComponent (i)); | |||||
if (section != 0 && section->getName().isNotEmpty()) | |||||
{ | |||||
if (index == sectionIndex) | |||||
{ | |||||
section->setOpen (shouldBeOpen); | |||||
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 <PropertyComponent*>& 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 <PropertyComponent*> (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 <PropertyComponent*> (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 <PropertyComponent*> (getChildComponent (i)); | |||||
if (pec != 0) | |||||
pec->setVisible (open); | |||||
} | |||||
// (unable to use the syntax findParentComponentOfClass <DragAndDropContainer> () 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 <PropertyComponent*> (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 <PropertySectionComponent*> (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 <PropertySectionComponent*> (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 <PropertyComponent*>& newProperties) | |||||
{ | |||||
if (propertyHolderComponent->getNumChildComponents() == 0) | |||||
repaint(); | |||||
propertyHolderComponent->addAndMakeVisible (new PropertySectionComponent (String::empty, | |||||
newProperties, | |||||
true), 0); | |||||
updatePropHolderLayout(); | |||||
} | |||||
void PropertyPanel::addSection (const String& sectionTitle, | |||||
const Array <PropertyComponent*>& 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 <PropertySectionComponent*> (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 <PropertySectionComponent*> (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 <PropertySectionComponent*> (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 <PropertySectionComponent*> (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 |
@@ -109,6 +109,12 @@ public: | |||||
*/ | */ | ||||
void setSectionOpen (const int sectionIndex, const bool shouldBeOpen); | void setSectionOpen (const int sectionIndex, const bool shouldBeOpen); | ||||
/** Enables or disables one of the sections. | |||||
The index is from 0 up to the number of items returned by getSectionNames(). | |||||
*/ | |||||
void setSectionEnabled (const int sectionIndex, const bool shouldBeEnabled); | |||||
//============================================================================== | //============================================================================== | ||||
/** Saves the current state of open/closed sections so it can be restored later. | /** Saves the current state of open/closed sections so it can be restored later. | ||||
@@ -1,376 +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 (...) | |||||
{} | |||||
} | |||||
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 |
@@ -81,6 +81,7 @@ | |||||
#include <stdexcept> | #include <stdexcept> | ||||
#include <typeinfo> | #include <typeinfo> | ||||
#include <cstring> | #include <cstring> | ||||
#include <cstdio> | |||||
#if JUCE_MAC || JUCE_LINUX | #if JUCE_MAC || JUCE_LINUX | ||||
#include <pthread.h> | #include <pthread.h> | ||||
@@ -958,7 +958,7 @@ void BitArray::parseString (const String& text, | |||||
const MemoryBlock BitArray::toMemoryBlock() const throw() | const MemoryBlock BitArray::toMemoryBlock() const throw() | ||||
{ | { | ||||
const int numBytes = (getHighestBit() + 7) >> 3; | |||||
const int numBytes = (getHighestBit() + 8) >> 3; | |||||
MemoryBlock mb (numBytes); | MemoryBlock mb (numBytes); | ||||
for (int i = 0; i < numBytes; ++i) | for (int i = 0; i < numBytes; ++i) | ||||
@@ -1068,14 +1068,8 @@ const String File::getRelativePathFrom (const File& dir) const throw() | |||||
--commonBitLength; | --commonBitLength; | ||||
// if the only common bit is the root, then just return the full path.. | // if the only common bit is the root, then just return the full path.. | ||||
#if JUCE_WIN32 | |||||
if (commonBitLength <= 0 | |||||
|| (commonBitLength == 1 && thisPath [1] == File::separator) | |||||
|| (commonBitLength <= 3 && thisPath [1] == T(':'))) | |||||
#else | |||||
if (commonBitLength <= 0 | if (commonBitLength <= 0 | ||||
|| (commonBitLength == 1 && thisPath [1] == File::separator)) | || (commonBitLength == 1 && thisPath [1] == File::separator)) | ||||
#endif | |||||
return fullPath; | return fullPath; | ||||
thisPath = thisPath.substring (commonBitLength); | thisPath = thisPath.substring (commonBitLength); | ||||
@@ -1946,15 +1946,11 @@ const String String::toHexString (const unsigned char* data, | |||||
*d++ = hexDigits [(*data) & 0xf]; | *d++ = hexDigits [(*data) & 0xf]; | ||||
++data; | ++data; | ||||
if (groupSize > 0 && (i % groupSize) == 0) | |||||
if (groupSize > 0 && (i % groupSize) == (groupSize - 1) && i < (size - 1)) | |||||
*d++ = T(' '); | *d++ = T(' '); | ||||
} | } | ||||
if (groupSize > 0) | |||||
--d; | |||||
*d = 0; | *d = 0; | ||||
return s; | return s; | ||||
} | } | ||||