/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-9 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "jucer_Coordinate.h" //============================================================================== const char* Coordinate::parentLeftMarkerName = "parent.left"; const char* Coordinate::parentRightMarkerName = "parent.right"; const char* Coordinate::parentTopMarkerName = "parent.top"; const char* Coordinate::parentBottomMarkerName = "parent.bottom"; Coordinate::Coordinate (bool isHorizontal_) : value (0), isProportion (false), isHorizontal (isHorizontal_) { } Coordinate::Coordinate (double absoluteDistanceFromOrigin, bool isHorizontal_) : value (absoluteDistanceFromOrigin), isProportion (false), isHorizontal (isHorizontal_) { } Coordinate::Coordinate (double absoluteDistance, const String& source, bool isHorizontal_) : anchor1 (source), value (absoluteDistance), isProportion (false), isHorizontal (isHorizontal_) { } Coordinate::Coordinate (double relativeProportion, const String& pos1, const String& pos2, bool isHorizontal_) : anchor1 (pos1), anchor2 (pos2), value (relativeProportion), isProportion (true), isHorizontal (isHorizontal_) { } Coordinate::~Coordinate() { } const Coordinate Coordinate::getAnchorPoint1() const { return Coordinate (0.0, anchor1, isHorizontal); } const Coordinate Coordinate::getAnchorPoint2() const { return Coordinate (0.0, anchor2, isHorizontal); } bool Coordinate::isOrigin (const String& name) { return name.isEmpty() || name == parentLeftMarkerName || name == parentTopMarkerName; } const String Coordinate::getOriginMarkerName() const { return isHorizontal ? parentLeftMarkerName : parentTopMarkerName; } const String Coordinate::getExtentMarkerName() const { return isHorizontal ? parentRightMarkerName : parentBottomMarkerName; } const String Coordinate::checkName (const String& name) const { return name.isEmpty() ? getOriginMarkerName() : name; } double Coordinate::getPosition (const String& name, MarkerResolver& markerResolver, int recursionCounter) const { if (isOrigin (name)) return 0.0; return markerResolver.findMarker (name, isHorizontal) .resolve (markerResolver, recursionCounter + 1); } struct RecursivePositionException { }; double Coordinate::resolve (MarkerResolver& markerResolver, int recursionCounter) const { if (recursionCounter > 100) { jassertfalse throw RecursivePositionException(); } const double pos1 = getPosition (anchor1, markerResolver, recursionCounter); return isProportion ? pos1 + (getPosition (anchor2, markerResolver, recursionCounter) - pos1) * value : pos1 + value; } double Coordinate::resolve (MarkerResolver& markerResolver) const { try { return resolve (markerResolver, 0); } catch (RecursivePositionException&) {} return 0.0; } void Coordinate::moveToAbsolute (double newPos, MarkerResolver& markerResolver) { try { const double pos1 = getPosition (anchor1, markerResolver, 0); if (isProportion) { const double size = getPosition (anchor2, markerResolver, 0) - pos1; if (size != 0) value = (newPos - pos1) / size; } else { value = newPos - pos1; } } catch (RecursivePositionException&) {} } bool Coordinate::isRecursive (MarkerResolver& markerResolver) const { try { resolve (markerResolver, 0); } catch (RecursivePositionException&) { return true; } return false; } void Coordinate::skipWhitespace (const String& s, int& i) { while (CharacterFunctions::isWhitespace (s[i])) ++i; } const String Coordinate::readMarkerName (const String& s, int& i) { skipWhitespace (s, i); if (CharacterFunctions::isLetter (s[i]) || s[i] == '_') { int start = i; while (CharacterFunctions::isLetterOrDigit (s[i]) || s[i] == '_' || s[i] == '.') ++i; return s.substring (start, i); } return String::empty; } double Coordinate::readNumber (const String& s, int& i) { skipWhitespace (s, i); int start = i; if (CharacterFunctions::isDigit (s[i]) || s[i] == '.' || s[i] == '-') ++i; while (CharacterFunctions::isDigit (s[i]) || s[i] == '.') ++i; if ((s[i] == 'e' || s[i] == 'E') && (CharacterFunctions::isDigit (s[i + 1]) || s[i + 1] == '-' || s[i + 1] == '+')) { i += 2; while (CharacterFunctions::isDigit (s[i])) ++i; } const double value = s.substring (start, i).getDoubleValue(); while (CharacterFunctions::isWhitespace (s[i]) || s[i] == ',') ++i; return value; } Coordinate::Coordinate (const String& s, bool isHorizontal_) : value (0), isProportion (false), isHorizontal (isHorizontal_) { int i = 0; anchor1 = readMarkerName (s, i); if (anchor1.isNotEmpty()) { skipWhitespace (s, i); if (s[i] == '+') value = readNumber (s, ++i); else if (s[i] == '-') value = -readNumber (s, ++i); } else { value = readNumber (s, i); skipWhitespace (s, i); if (s[i] == '%') { isProportion = true; value /= 100.0; skipWhitespace (s, ++i); if (s[i] == '*') { anchor1 = readMarkerName (s, ++i); skipWhitespace (s, i); if (s[i] == '-' && s[i + 1] == '>') { i += 2; anchor2 = readMarkerName (s, i); } else { anchor2 = anchor1; anchor1 = getOriginMarkerName(); } } else { anchor1 = getOriginMarkerName(); anchor2 = getExtentMarkerName(); } } } } static const String limitedAccuracyString (const double n) { return String (n, 3).trimCharactersAtEnd ("0").trimCharactersAtEnd ("."); } const String Coordinate::toString() const { if (isProportion) { const String percent (limitedAccuracyString (value * 100.0)); if (isOrigin (anchor1)) { if (anchor2 == parentRightMarkerName || anchor2 == parentBottomMarkerName) return percent + "%"; else return percent + "% * " + checkName (anchor2); } else return percent + "% * " + checkName (anchor1) + " -> " + checkName (anchor2); } else { if (isOrigin (anchor1)) return limitedAccuracyString (value); else if (value > 0) return checkName (anchor1) + " + " + limitedAccuracyString (value); else if (value < 0) return checkName (anchor1) + " - " + limitedAccuracyString (-value); else return checkName (anchor1); } } const double Coordinate::getEditableValue() const { return isProportion ? value * 100.0 : value; } void Coordinate::setEditableValue (const double newValue) { value = isProportion ? newValue / 100.0 : newValue; } void Coordinate::toggleProportionality (MarkerResolver& markerResolver) { const double oldValue = resolve (markerResolver); isProportion = ! isProportion; anchor1 = getOriginMarkerName(); anchor2 = getExtentMarkerName(); moveToAbsolute (oldValue, markerResolver); } void Coordinate::changeAnchor1 (const String& newMarkerName, MarkerResolver& markerResolver) { const double oldValue = resolve (markerResolver); anchor1 = newMarkerName; moveToAbsolute (oldValue, markerResolver); } void Coordinate::changeAnchor2 (const String& newMarkerName, MarkerResolver& markerResolver) { const double oldValue = resolve (markerResolver); anchor2 = newMarkerName; moveToAbsolute (oldValue, markerResolver); } //============================================================================== RectangleCoordinates::RectangleCoordinates() : left (true), right (true), top (false), bottom (false) { } RectangleCoordinates::RectangleCoordinates (const Rectangle& rect) : left (rect.getX(), true), right (rect.getWidth(), "left", true), top (rect.getY(), false), bottom (rect.getHeight(), "top", false) { } RectangleCoordinates::RectangleCoordinates (const String& stringVersion) : left (true), right (true), top (false), bottom (false) { StringArray tokens; tokens.addTokens (stringVersion, ",", String::empty); left = Coordinate (tokens [0], true); top = Coordinate (tokens [1], false); right = Coordinate (tokens [2], true); bottom = Coordinate (tokens [3], false); } bool RectangleCoordinates::isRecursive (Coordinate::MarkerResolver& markerResolver) const { return left.isRecursive (markerResolver) || right.isRecursive (markerResolver) || top.isRecursive (markerResolver) || bottom.isRecursive (markerResolver); } const Rectangle RectangleCoordinates::resolve (Coordinate::MarkerResolver& markerResolver) const { const int l = roundToInt (left.resolve (markerResolver)); const int r = roundToInt (right.resolve (markerResolver)); const int t = roundToInt (top.resolve (markerResolver)); const int b = roundToInt (bottom.resolve (markerResolver)); return Rectangle (l, t, r - l, b - t); } void RectangleCoordinates::moveToAbsolute (const Rectangle& newPos, Coordinate::MarkerResolver& markerResolver) { left.moveToAbsolute (newPos.getX(), markerResolver); right.moveToAbsolute (newPos.getRight(), markerResolver); top.moveToAbsolute (newPos.getY(), markerResolver); bottom.moveToAbsolute (newPos.getBottom(), markerResolver); } const String RectangleCoordinates::toString() const { return left.toString() + ", " + top.toString() + ", " + right.toString() + ", " + bottom.toString(); }