Browse Source

Additions to Expression class. Tweak for mac app shutdown behaviour.

tags/2021-05-28
Julian Storer 14 years ago
parent
commit
88b1d2a2de
7 changed files with 267 additions and 100 deletions
  1. +87
    -49
      juce_amalgamated.cpp
  2. +46
    -1
      juce_amalgamated.h
  3. +71
    -20
      src/containers/juce_Expression.cpp
  4. +46
    -0
      src/containers/juce_Expression.h
  5. +6
    -29
      src/core/juce_RelativeTime.cpp
  6. +1
    -1
      src/core/juce_StandardHeader.h
  7. +10
    -0
      src/native/mac/juce_mac_MessageManager.mm

+ 87
- 49
juce_amalgamated.cpp View File

@@ -1600,35 +1600,12 @@ RelativeTime& RelativeTime::operator= (const RelativeTime& other) throw()
return *this;
}

bool RelativeTime::operator== (const RelativeTime& other) const throw()
{
return seconds == other.seconds;
}

bool RelativeTime::operator!= (const RelativeTime& other) const throw()
{
return seconds != other.seconds;
}

bool RelativeTime::operator> (const RelativeTime& other) const throw()
{
return seconds > other.seconds;
}

bool RelativeTime::operator< (const RelativeTime& other) const throw()
{
return seconds < other.seconds;
}

bool RelativeTime::operator>= (const RelativeTime& other) const throw()
{
return seconds >= other.seconds;
}

bool RelativeTime::operator<= (const RelativeTime& other) const throw()
{
return seconds <= other.seconds;
}
bool RelativeTime::operator== (const RelativeTime& other) const throw() { return seconds == other.seconds; }
bool RelativeTime::operator!= (const RelativeTime& other) const throw() { return seconds != other.seconds; }
bool RelativeTime::operator> (const RelativeTime& other) const throw() { return seconds > other.seconds; }
bool RelativeTime::operator< (const RelativeTime& other) const throw() { return seconds < other.seconds; }
bool RelativeTime::operator>= (const RelativeTime& other) const throw() { return seconds >= other.seconds; }
bool RelativeTime::operator<= (const RelativeTime& other) const throw() { return seconds <= other.seconds; }

const RelativeTime RelativeTime::operator+ (const RelativeTime& timeToAdd) const throw()
{
@@ -4617,6 +4594,7 @@ public:
Constant (const double value_, bool isResolutionTarget_)
: value (value_), isResolutionTarget (isResolutionTarget_) {}

Type getType() const throw() { return constantType; }
Term* clone() const { return new Constant (value, isResolutionTarget); }
double evaluate (const EvaluationContext&, int) const { return value; }
int getNumInputs() const { return 0; }
@@ -4667,9 +4645,11 @@ public:
return 0;
}

Type getType() const throw() { return symbolType; }
Term* clone() const { return new Symbol (mainSymbol, member); }
int getNumInputs() const { return 0; }
Term* getInput (int) const { return 0; }
const String getFunctionName() const { return toString(); }

const String toString() const
{
@@ -4716,9 +4696,11 @@ public:
return c.evaluateFunction (functionName, params, parameters.size());
}

int getInputIndexFor (const Term* possibleInput) const { return parameters.indexOf (possibleInput); }
int getNumInputs() const { return parameters.size(); }
Term* getInput (int i) const { return parameters [i]; }
Type getType() const throw() { return functionType; }
int getInputIndexFor (const Term* possibleInput) const { return parameters.indexOf (possibleInput); }
int getNumInputs() const { return parameters.size(); }
Term* getInput (int i) const { return parameters [i]; }
const String getFunctionName() const { return functionName; }

bool referencesSymbol (const String& s, const EvaluationContext& c, int recursionDepth) const
{
@@ -4760,11 +4742,13 @@ public:
jassert (input_ != 0);
}

Type getType() const throw() { return operatorType; }
int getInputIndexFor (const Term* possibleInput) const { return possibleInput == input ? 0 : -1; }
int getNumInputs() const { return 1; }
Term* getInput (int index) const { return index == 0 ? static_cast<Term*> (input) : 0; }
Term* clone() const { return new Negate (input->clone()); }
double evaluate (const EvaluationContext& c, int recursionDepth) const { return -input->evaluate (c, recursionDepth); }
const String getFunctionName() const { return "-"; }

const TermPtr negated()
{
@@ -4811,6 +4795,8 @@ public:
return possibleInput == left ? 0 : (possibleInput == right ? 1 : -1);
}

Type getType() const throw() { return operatorType; }

int getNumInputs() const { return 2; }
Term* getInput (int index) const { return index == 0 ? static_cast<Term*> (left) : (index == 1 ? static_cast<Term*> (right) : 0); }

@@ -4820,10 +4806,7 @@ public:
|| right->referencesSymbol (s, c, recursionDepth);
}

protected:
const TermPtr left, right;

const String createString (const String& op) const
const String toString() const
{
String s;

@@ -4833,7 +4816,7 @@ public:
else
s = left->toString();

s << ' ' << op << ' ';
s << ' ' << getFunctionName() << ' ';

if (right->getOperatorPrecedence() >= ourPrecendence)
s << '(' << right->toString() << ')';
@@ -4843,6 +4826,9 @@ public:
return s;
}

protected:
const TermPtr left, right;

const TermPtr createDestinationTerm (const EvaluationContext& context, const Term* input, double overallTarget, Term* topLevelTerm) const
{
jassert (input == left || input == right);
@@ -4863,10 +4849,10 @@ public:
public:
Add (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {}

Term* clone() const { return new Add (left->clone(), right->clone()); }
Term* clone() const { return new Add (left->clone(), right->clone()); }
double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) + right->evaluate (c, recursionDepth); }
const String toString() const { return createString ("+"); }
int getOperatorPrecedence() const { return 2; }
int getOperatorPrecedence() const { return 2; }
const String getFunctionName() const { return "+"; }

const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const
{
@@ -4883,10 +4869,10 @@ public:
public:
Subtract (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {}

Term* clone() const { return new Subtract (left->clone(), right->clone()); }
Term* clone() const { return new Subtract (left->clone(), right->clone()); }
double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) - right->evaluate (c, recursionDepth); }
const String toString() const { return createString ("-"); }
int getOperatorPrecedence() const { return 2; }
int getOperatorPrecedence() const { return 2; }
const String getFunctionName() const { return "-"; }

const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const
{
@@ -4906,10 +4892,10 @@ public:
public:
Multiply (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {}

Term* clone() const { return new Multiply (left->clone(), right->clone()); }
Term* clone() const { return new Multiply (left->clone(), right->clone()); }
double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) * right->evaluate (c, recursionDepth); }
const String toString() const { return createString ("*"); }
int getOperatorPrecedence() const { return 1; }
const String getFunctionName() const { return "*"; }
int getOperatorPrecedence() const { return 1; }

const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const
{
@@ -4926,10 +4912,10 @@ public:
public:
Divide (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {}

Term* clone() const { return new Divide (left->clone(), right->clone()); }
Term* clone() const { return new Divide (left->clone(), right->clone()); }
double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) / right->evaluate (c, recursionDepth); }
const String toString() const { return createString ("/"); }
int getOperatorPrecedence() const { return 1; }
const String getFunctionName() const { return "/"; }
int getOperatorPrecedence() const { return 1; }

const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const
{
@@ -5444,6 +5430,36 @@ bool Expression::usesAnySymbols() const
return Helpers::containsAnySymbols (term);
}

Expression::Type Expression::getType() const throw()
{
return term->getType();
}

const String Expression::getSymbol() const
{
return term->getSymbolName();
}

const String Expression::getFunction() const
{
return term->getFunctionName();
}

const String Expression::getOperator() const
{
return term->getFunctionName();
}

int Expression::getNumInputs() const
{
return term->getNumInputs();
}

const Expression Expression::getInput (int index) const
{
return Expression (term->getInput (index));
}

int Expression::Term::getOperatorPrecedence() const
{
return 0;
@@ -5470,6 +5486,18 @@ const ReferenceCountedObjectPtr<Expression::Term> Expression::Term::negated()
return new Helpers::Negate (this);
}

const String Expression::Term::getSymbolName() const
{
jassertfalse; // You should only call getSymbol() on an expression that's actually a symbol!
return String::empty;
}

const String Expression::Term::getFunctionName() const
{
jassertfalse; // You shouldn't call this for an expression that's not actually a function!
return String::empty;
}

Expression::ParseError::ParseError (const String& message)
: description (message)
{
@@ -276206,6 +276234,16 @@ public:
if (JUCEApplication::getInstance() != 0)
{
JUCEApplication::getInstance()->systemRequestedQuit();

if (MessageManager::getInstance()->hasStopMessageBeenSent())
{
[NSApp performSelectorOnMainThread: @selector (replyToApplicationShouldTerminate:)
withObject: [NSNumber numberWithBool: YES]
waitUntilDone: NO];

return NSTerminateLater;
}

return NSTerminateCancel;
}



+ 46
- 1
juce_amalgamated.h View File

@@ -64,7 +64,7 @@
*/
#define JUCE_MAJOR_VERSION 1
#define JUCE_MINOR_VERSION 52
#define JUCE_BUILDNUMBER 58
#define JUCE_BUILDNUMBER 59

/** Current Juce version number.

@@ -6651,11 +6651,13 @@ public:
/** Evaluates this expression, without using an EvaluationContext.
Without an EvaluationContext, no symbols can be used, and only basic functions such as sin, cos, tan,
min, max are available.
@throws Expression::EvaluationError
*/
double evaluate() const;

/** Evaluates this expression, providing a context that should be able to evaluate any symbols
or functions that it uses.
@throws Expression::EvaluationError
*/
double evaluate (const EvaluationContext& context) const;

@@ -6665,6 +6667,8 @@ public:
E.g. if the expression is "x + 10" and x is 5, then asking for a target value of 8 will return
the expression "x + 3". Obviously some expressions can't be reversed in this way, in which
case they might just be adjusted by adding a constant to them.

@throws Expression::EvaluationError
*/
const Expression adjustedToGiveNewResult (double targetValue, const EvaluationContext& context) const;

@@ -6675,6 +6679,8 @@ public:
If a suitable context is supplied, the search will dereference and recursively check
all symbols, so that it can be determined whether this expression relies on the given
symbol at any level in its evaluation.

@throws Expression::EvaluationError
*/
bool referencesSymbol (const String& symbol, const EvaluationContext& context) const;

@@ -6699,6 +6705,41 @@ public:
String description;
};

/** Expression type.
@see Expression::getType()
*/
enum Type
{
constantType,
functionType,
operatorType,
symbolType
};

/** Returns the type of this expression. */
Type getType() const throw();

/** If this expression is a symbol, this returns its name. */
const String getSymbol() const;

/** If this expression is a function, this returns its name. */
const String getFunction() const;

/** If this expression is an operator, this returns its name.
E.g. "+", "-", "*", "/", etc.
*/
const String getOperator() const;

/** Returns the number of inputs to this expression.
@see getInput
*/
int getNumInputs() const;

/** Retrieves one of the inputs to this expression.
@see getNumInputs
*/
const Expression getInput (int index) const;

juce_UseDebuggingNewOperator

private:
@@ -6722,6 +6763,10 @@ private:
virtual const ReferenceCountedObjectPtr<Term> createTermToEvaluateInput (const EvaluationContext&, const Term* inputTerm,
double overallTarget, Term* topLevelTerm) const;
virtual const ReferenceCountedObjectPtr<Term> negated();
virtual Type getType() const throw() = 0;
virtual const String getSymbolName() const;
virtual const String getFunctionName() const;

juce_UseDebuggingNewOperator

private:


+ 71
- 20
src/containers/juce_Expression.cpp View File

@@ -44,6 +44,7 @@ public:
Constant (const double value_, bool isResolutionTarget_)
: value (value_), isResolutionTarget (isResolutionTarget_) {}
Type getType() const throw() { return constantType; }
Term* clone() const { return new Constant (value, isResolutionTarget); }
double evaluate (const EvaluationContext&, int) const { return value; }
int getNumInputs() const { return 0; }
@@ -95,9 +96,11 @@ public:
return 0;
}
Type getType() const throw() { return symbolType; }
Term* clone() const { return new Symbol (mainSymbol, member); }
int getNumInputs() const { return 0; }
Term* getInput (int) const { return 0; }
const String getFunctionName() const { return toString(); }
const String toString() const
{
@@ -145,9 +148,11 @@ public:
return c.evaluateFunction (functionName, params, parameters.size());
}
int getInputIndexFor (const Term* possibleInput) const { return parameters.indexOf (possibleInput); }
int getNumInputs() const { return parameters.size(); }
Term* getInput (int i) const { return parameters [i]; }
Type getType() const throw() { return functionType; }
int getInputIndexFor (const Term* possibleInput) const { return parameters.indexOf (possibleInput); }
int getNumInputs() const { return parameters.size(); }
Term* getInput (int i) const { return parameters [i]; }
const String getFunctionName() const { return functionName; }
bool referencesSymbol (const String& s, const EvaluationContext& c, int recursionDepth) const
{
@@ -190,11 +195,13 @@ public:
jassert (input_ != 0);
}
Type getType() const throw() { return operatorType; }
int getInputIndexFor (const Term* possibleInput) const { return possibleInput == input ? 0 : -1; }
int getNumInputs() const { return 1; }
Term* getInput (int index) const { return index == 0 ? static_cast<Term*> (input) : 0; }
Term* clone() const { return new Negate (input->clone()); }
double evaluate (const EvaluationContext& c, int recursionDepth) const { return -input->evaluate (c, recursionDepth); }
const String getFunctionName() const { return "-"; }
const TermPtr negated()
{
@@ -242,6 +249,8 @@ public:
return possibleInput == left ? 0 : (possibleInput == right ? 1 : -1);
}
Type getType() const throw() { return operatorType; }
int getNumInputs() const { return 2; }
Term* getInput (int index) const { return index == 0 ? static_cast<Term*> (left) : (index == 1 ? static_cast<Term*> (right) : 0); }
@@ -251,10 +260,7 @@ public:
|| right->referencesSymbol (s, c, recursionDepth);
}
protected:
const TermPtr left, right;
const String createString (const String& op) const
const String toString() const
{
String s;
@@ -264,7 +270,7 @@ public:
else
s = left->toString();
s << ' ' << op << ' ';
s << ' ' << getFunctionName() << ' ';
if (right->getOperatorPrecedence() >= ourPrecendence)
s << '(' << right->toString() << ')';
@@ -274,6 +280,9 @@ public:
return s;
}
protected:
const TermPtr left, right;
const TermPtr createDestinationTerm (const EvaluationContext& context, const Term* input, double overallTarget, Term* topLevelTerm) const
{
jassert (input == left || input == right);
@@ -295,10 +304,10 @@ public:
public:
Add (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {}
Term* clone() const { return new Add (left->clone(), right->clone()); }
Term* clone() const { return new Add (left->clone(), right->clone()); }
double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) + right->evaluate (c, recursionDepth); }
const String toString() const { return createString ("+"); }
int getOperatorPrecedence() const { return 2; }
int getOperatorPrecedence() const { return 2; }
const String getFunctionName() const { return "+"; }
const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const
{
@@ -316,10 +325,10 @@ public:
public:
Subtract (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {}
Term* clone() const { return new Subtract (left->clone(), right->clone()); }
Term* clone() const { return new Subtract (left->clone(), right->clone()); }
double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) - right->evaluate (c, recursionDepth); }
const String toString() const { return createString ("-"); }
int getOperatorPrecedence() const { return 2; }
int getOperatorPrecedence() const { return 2; }
const String getFunctionName() const { return "-"; }
const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const
{
@@ -340,10 +349,10 @@ public:
public:
Multiply (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {}
Term* clone() const { return new Multiply (left->clone(), right->clone()); }
Term* clone() const { return new Multiply (left->clone(), right->clone()); }
double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) * right->evaluate (c, recursionDepth); }
const String toString() const { return createString ("*"); }
int getOperatorPrecedence() const { return 1; }
const String getFunctionName() const { return "*"; }
int getOperatorPrecedence() const { return 1; }
const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const
{
@@ -361,10 +370,10 @@ public:
public:
Divide (Term* const left_, Term* const right_) : BinaryTerm (left_, right_) {}
Term* clone() const { return new Divide (left->clone(), right->clone()); }
Term* clone() const { return new Divide (left->clone(), right->clone()); }
double evaluate (const EvaluationContext& c, int recursionDepth) const { return left->evaluate (c, recursionDepth) / right->evaluate (c, recursionDepth); }
const String toString() const { return createString ("/"); }
int getOperatorPrecedence() const { return 1; }
const String getFunctionName() const { return "/"; }
int getOperatorPrecedence() const { return 1; }
const TermPtr createTermToEvaluateInput (const EvaluationContext& c, const Term* input, double overallTarget, Term* topLevelTerm) const
{
@@ -883,6 +892,36 @@ bool Expression::usesAnySymbols() const
return Helpers::containsAnySymbols (term);
}
Expression::Type Expression::getType() const throw()
{
return term->getType();
}
const String Expression::getSymbol() const
{
return term->getSymbolName();
}
const String Expression::getFunction() const
{
return term->getFunctionName();
}
const String Expression::getOperator() const
{
return term->getFunctionName();
}
int Expression::getNumInputs() const
{
return term->getNumInputs();
}
const Expression Expression::getInput (int index) const
{
return Expression (term->getInput (index));
}
//==============================================================================
int Expression::Term::getOperatorPrecedence() const
{
@@ -910,6 +949,18 @@ const ReferenceCountedObjectPtr<Expression::Term> Expression::Term::negated()
return new Helpers::Negate (this);
}
const String Expression::Term::getSymbolName() const
{
jassertfalse; // You should only call getSymbol() on an expression that's actually a symbol!
return String::empty;
}
const String Expression::Term::getFunctionName() const
{
jassertfalse; // You shouldn't call this for an expression that's not actually a function!
return String::empty;
}
//==============================================================================
Expression::ParseError::ParseError (const String& message)
: description (message)


+ 46
- 0
src/containers/juce_Expression.h View File

@@ -129,11 +129,13 @@ public:
/** Evaluates this expression, without using an EvaluationContext.
Without an EvaluationContext, no symbols can be used, and only basic functions such as sin, cos, tan,
min, max are available.
@throws Expression::EvaluationError
*/
double evaluate() const;
/** Evaluates this expression, providing a context that should be able to evaluate any symbols
or functions that it uses.
@throws Expression::EvaluationError
*/
double evaluate (const EvaluationContext& context) const;
@@ -143,6 +145,8 @@ public:
E.g. if the expression is "x + 10" and x is 5, then asking for a target value of 8 will return
the expression "x + 3". Obviously some expressions can't be reversed in this way, in which
case they might just be adjusted by adding a constant to them.
@throws Expression::EvaluationError
*/
const Expression adjustedToGiveNewResult (double targetValue, const EvaluationContext& context) const;
@@ -153,6 +157,8 @@ public:
If a suitable context is supplied, the search will dereference and recursively check
all symbols, so that it can be determined whether this expression relies on the given
symbol at any level in its evaluation.
@throws Expression::EvaluationError
*/
bool referencesSymbol (const String& symbol, const EvaluationContext& context) const;
@@ -179,6 +185,42 @@ public:
String description;
};
//==============================================================================
/** Expression type.
@see Expression::getType()
*/
enum Type
{
constantType,
functionType,
operatorType,
symbolType
};
/** Returns the type of this expression. */
Type getType() const throw();
/** If this expression is a symbol, this returns its name. */
const String getSymbol() const;
/** If this expression is a function, this returns its name. */
const String getFunction() const;
/** If this expression is an operator, this returns its name.
E.g. "+", "-", "*", "/", etc.
*/
const String getOperator() const;
/** Returns the number of inputs to this expression.
@see getInput
*/
int getNumInputs() const;
/** Retrieves one of the inputs to this expression.
@see getNumInputs
*/
const Expression getInput (int index) const;
//==============================================================================
juce_UseDebuggingNewOperator
@@ -203,6 +245,10 @@ private:
virtual const ReferenceCountedObjectPtr<Term> createTermToEvaluateInput (const EvaluationContext&, const Term* inputTerm,
double overallTarget, Term* topLevelTerm) const;
virtual const ReferenceCountedObjectPtr<Term> negated();
virtual Type getType() const throw() = 0;
virtual const String getSymbolName() const;
virtual const String getFunctionName() const;
juce_UseDebuggingNewOperator
private:


+ 6
- 29
src/core/juce_RelativeTime.cpp View File

@@ -183,35 +183,12 @@ RelativeTime& RelativeTime::operator= (const RelativeTime& other) throw()
return *this;
}
bool RelativeTime::operator== (const RelativeTime& other) const throw()
{
return seconds == other.seconds;
}
bool RelativeTime::operator!= (const RelativeTime& other) const throw()
{
return seconds != other.seconds;
}
bool RelativeTime::operator> (const RelativeTime& other) const throw()
{
return seconds > other.seconds;
}
bool RelativeTime::operator< (const RelativeTime& other) const throw()
{
return seconds < other.seconds;
}
bool RelativeTime::operator>= (const RelativeTime& other) const throw()
{
return seconds >= other.seconds;
}
bool RelativeTime::operator<= (const RelativeTime& other) const throw()
{
return seconds <= other.seconds;
}
bool RelativeTime::operator== (const RelativeTime& other) const throw() { return seconds == other.seconds; }
bool RelativeTime::operator!= (const RelativeTime& other) const throw() { return seconds != other.seconds; }
bool RelativeTime::operator> (const RelativeTime& other) const throw() { return seconds > other.seconds; }
bool RelativeTime::operator< (const RelativeTime& other) const throw() { return seconds < other.seconds; }
bool RelativeTime::operator>= (const RelativeTime& other) const throw() { return seconds >= other.seconds; }
bool RelativeTime::operator<= (const RelativeTime& other) const throw() { return seconds <= other.seconds; }
//==============================================================================
const RelativeTime RelativeTime::operator+ (const RelativeTime& timeToAdd) const throw()


+ 1
- 1
src/core/juce_StandardHeader.h View File

@@ -33,7 +33,7 @@
*/
#define JUCE_MAJOR_VERSION 1
#define JUCE_MINOR_VERSION 52
#define JUCE_BUILDNUMBER 58
#define JUCE_BUILDNUMBER 59
/** Current Juce version number.


+ 10
- 0
src/native/mac/juce_mac_MessageManager.mm View File

@@ -69,6 +69,16 @@ public:
if (JUCEApplication::getInstance() != 0)
{
JUCEApplication::getInstance()->systemRequestedQuit();
if (MessageManager::getInstance()->hasStopMessageBeenSent())
{
[NSApp performSelectorOnMainThread: @selector (replyToApplicationShouldTerminate:)
withObject: [NSNumber numberWithBool: YES]
waitUntilDone: NO];
return NSTerminateLater;
}
return NSTerminateCancel;
}


Loading…
Cancel
Save