diff --git a/src/data/action/symbol.cpp b/src/data/action/symbol.cpp index ca5b4ab1..ed4dfd55 100644 --- a/src/data/action/symbol.cpp +++ b/src/data/action/symbol.cpp @@ -18,13 +18,17 @@ DECLARE_TYPEOF_COLLECTION(ControlPointP); // ----------------------------------------------------------------------------- : Utility String action_name_for(const set& parts, const String& action) { - return format_string(action, parts.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes")); + return format_string(action, parts.size() == 1 ? (*parts.begin())->name : _TYPE_("shapes")); } +SymbolPartsAction::SymbolPartsAction(const set& parts) + : parts(parts) +{} + // ----------------------------------------------------------------------------- : Moving symbol parts SymbolPartMoveAction::SymbolPartMoveAction(const set& parts, const Vector2D& delta) - : parts(parts) + : SymbolPartsAction(parts) , delta(delta), moved(-delta) , min_pos(Vector2D::infinity()), max_pos(-Vector2D::infinity()) , constrain(false) @@ -32,10 +36,8 @@ SymbolPartMoveAction::SymbolPartMoveAction(const set& parts, const { // Determine min/max_pos FOR_EACH(p, parts) { - if (SymbolShape* s = p->isSymbolShape()) { - min_pos = piecewise_min(min_pos, s->min_pos); - max_pos = piecewise_max(max_pos, s->max_pos); - } + min_pos = piecewise_min(min_pos, p->min_pos); + max_pos = piecewise_max(max_pos, p->max_pos); } } @@ -46,20 +48,28 @@ String SymbolPartMoveAction::getName(bool to_undo) const { void SymbolPartMoveAction::perform(bool to_undo) { // move the points back FOR_EACH(p, parts) { - if (SymbolShape* s = p->isSymbolShape()) { - s->min_pos -= moved; - s->max_pos -= moved; - FOR_EACH(pnt, s->points) { - pnt->pos -= moved; - } - } else if (SymbolSymmetry* s = p->isSymbolSymmetry()) { - s->center -= moved; - } else { - throw InternalError(_("Invalid symbol part type")); - } + movePart(*p); } moved = -moved; } +void SymbolPartMoveAction::movePart(SymbolPart& part) { + if (SymbolShape* s = part.isSymbolShape()) { + s->min_pos -= moved; + s->max_pos -= moved; + FOR_EACH(pnt, s->points) { + pnt->pos -= moved; + } + } else if (SymbolSymmetry* s = part.isSymbolSymmetry()) { + s->center -= moved; + } else if (SymbolGroup* g = part.isSymbolGroup()) { + FOR_EACH(p, g->parts) { + movePart(*p); + } + g->calculateBoundsNonRec(); + } else { + throw InternalError(_("Invalid symbol part type")); + } +} void SymbolPartMoveAction::move(const Vector2D& deltaDelta) { delta += deltaDelta; @@ -75,26 +85,34 @@ void SymbolPartMoveAction::move(const Vector2D& deltaDelta) { // ----------------------------------------------------------------------------- : Rotating symbol parts SymbolPartMatrixAction::SymbolPartMatrixAction(const set& parts, const Vector2D& center) - : parts(parts) + : SymbolPartsAction(parts) , center(center) {} -void SymbolPartMatrixAction::transform(const Vector2D& mx, const Vector2D& my) { - // Transform each point +void SymbolPartMatrixAction::transform(const Matrix2D& m) { + // Transform each part FOR_EACH(p, parts) { - if (SymbolShape* s = p->isSymbolShape()) { - FOR_EACH(pnt, s->points) { - pnt->pos = (pnt->pos - center).mul(mx,my) + center; - pnt->delta_before = pnt->delta_before.mul(mx,my); - pnt->delta_after = pnt->delta_after .mul(mx,my); - } - // bounds change after transforming - s->calculateBounds(); - } else if (SymbolSymmetry* s = p->isSymbolSymmetry()) { - s->handle = s->handle.mul(mx,my); - } else { - throw InternalError(_("Invalid symbol part type")); + transform(*p, m); + } +} +void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) { + if (SymbolShape* s = part.isSymbolShape()) { + FOR_EACH(pnt, s->points) { + pnt->pos = ((pnt->pos - center) * m) + center; + pnt->delta_before = pnt->delta_before * m; + pnt->delta_after = pnt->delta_after * m; } + // bounds change after transforming + s->calculateBounds(); + } else if (SymbolSymmetry* s = part.isSymbolSymmetry()) { + s->handle = s->handle * m; + } else if (SymbolGroup* g = part.isSymbolGroup()) { + FOR_EACH(p, g->parts) { + transform(*p, m); + } + g->calculateBoundsNonRec(); + } else { + throw InternalError(_("Invalid symbol part type")); } } @@ -130,8 +148,8 @@ void SymbolPartRotateAction::rotateTo(double newAngle) { void SymbolPartRotateAction::rotateBy(double deltaAngle) { // Rotation 'matrix' transform( - Vector2D(cos(deltaAngle), -sin(deltaAngle)), - Vector2D(sin(deltaAngle), cos(deltaAngle)) + Matrix2D(cos(deltaAngle), -sin(deltaAngle) + ,sin(deltaAngle), cos(deltaAngle)) ); } @@ -170,8 +188,8 @@ void SymbolPartShearAction::move(const Vector2D& deltaShear) { void SymbolPartShearAction::shearBy(const Vector2D& shear) { // Shear 'matrix' transform( - Vector2D(1, shear.x), - Vector2D(shear.y, 1) + Matrix2D(1, shear.x + ,shear.y, 1) ); } @@ -180,7 +198,7 @@ void SymbolPartShearAction::shearBy(const Vector2D& shear) { SymbolPartScaleAction::SymbolPartScaleAction(const set& parts, int scaleX, int scaleY) - : parts(parts) + : SymbolPartsAction(parts) , scaleX(scaleX), scaleY(scaleY) , constrain(false) , snap(0) @@ -189,10 +207,8 @@ SymbolPartScaleAction::SymbolPartScaleAction(const set& parts, int old_min = Vector2D( 1e6, 1e6); Vector2D old_max (-1e6,-1e6); FOR_EACH(p, parts) { - if (SymbolShape* s = p->isSymbolShape()) { - old_min = piecewise_min(old_min, s->min_pos); - old_max = piecewise_max(old_max, s->max_pos); - } + old_min = piecewise_min(old_min, p->min_pos); + old_max = piecewise_max(old_max, p->max_pos); } // new == old new_min = new_real_min = old_min; @@ -244,26 +260,34 @@ void SymbolPartScaleAction::update() { } void SymbolPartScaleAction::transformAll() { - Vector2D scale = new_size.div(old_size); FOR_EACH(p, parts) { - if (SymbolShape* s = p->isSymbolShape()) { - s->min_pos = transform(s->min_pos); - s->max_pos = transform(s->max_pos); - // make sure that max >= min - if (s->min_pos.x > s->max_pos.x) swap(s->min_pos.x, s->max_pos.x); - if (s->min_pos.y > s->max_pos.y) swap(s->min_pos.y, s->max_pos.y); - // scale all points - FOR_EACH(pnt, s->points) { - pnt->pos = transform(pnt->pos); - // also scale handles - pnt->delta_before = pnt->delta_before.mul(scale); - pnt->delta_after = pnt->delta_after .mul(scale); - } - } else if (SymbolSymmetry* s = p->isSymbolSymmetry()) { - throw "TODO"; - } else { - throw InternalError(_("Invalid symbol part type")); + transformPart(*p); + } +} +void SymbolPartScaleAction::transformPart(SymbolPart& part) { + if (SymbolShape* s = part.isSymbolShape()) { + Vector2D scale = new_size.div(old_size); + s->min_pos = transform(s->min_pos); + s->max_pos = transform(s->max_pos); + // make sure that max >= min + if (s->min_pos.x > s->max_pos.x) swap(s->min_pos.x, s->max_pos.x); + if (s->min_pos.y > s->max_pos.y) swap(s->min_pos.y, s->max_pos.y); + // scale all points + FOR_EACH(pnt, s->points) { + pnt->pos = transform(pnt->pos); + // also scale handles + pnt->delta_before = pnt->delta_before.mul(scale); + pnt->delta_after = pnt->delta_after .mul(scale); } + } else if (SymbolSymmetry* s = part.isSymbolSymmetry()) { + throw "TODO"; + } else if (SymbolGroup* g = part.isSymbolGroup()) { + FOR_EACH(p, g->parts) { + transformPart(*p); + } + g->calculateBoundsNonRec(); + } else { + throw InternalError(_("Invalid symbol part type")); } } @@ -274,11 +298,18 @@ Vector2D SymbolPartScaleAction::transform(const Vector2D& v) { // ----------------------------------------------------------------------------- : Change combine mode -CombiningModeAction::CombiningModeAction(const set& parts, SymbolShapeCombine mode) { +CombiningModeAction::CombiningModeAction(const set& parts, SymbolShapeCombine mode) + : SymbolPartsAction(parts) +{ FOR_EACH(p, parts) { - if (p->isSymbolShape()) { - this->parts.push_back(make_pair(static_pointer_cast(p),mode)); - } + add(p,mode); + } +} +void CombiningModeAction::add(const SymbolPartP& part, SymbolShapeCombine mode) { + if (part->isSymbolShape()) { + this->parts.push_back(make_pair(static_pointer_cast(part),mode)); + } else if (SymbolGroup* g = part->isSymbolGroup()) { + FOR_EACH(p, g->parts) add(p, mode); } } @@ -294,16 +325,28 @@ void CombiningModeAction::perform(bool to_undo) { // ----------------------------------------------------------------------------- : Change name -SymbolPartNameAction::SymbolPartNameAction(const SymbolPartP& part, const String& name) +SymbolPartNameAction::SymbolPartNameAction(const SymbolPartP& part, const String& name, size_t old_cursor, size_t new_cursor) : part(part), part_name(name) + , new_cursor(old_cursor), old_cursor(new_cursor) // will be swapped {} String SymbolPartNameAction::getName(bool to_undo) const { return _ACTION_("change shape name"); } +bool SymbolPartNameAction::merge(const Action& action) { + TYPE_CASE(action, SymbolPartNameAction) { + if (action.part == part) { + // adjacent actions on the same part, discard the other one, + // because it only keeps an intermediate value + return true; + } + } + return false; +} void SymbolPartNameAction::perform(bool to_undo) { swap(part->name, part_name); + swap(old_cursor, new_cursor); } // ----------------------------------------------------------------------------- : Add symbol part @@ -313,7 +356,7 @@ AddSymbolPartAction::AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part {} String AddSymbolPartAction::getName(bool to_undo) const { - return format_string(_ACTION_("add"), part->name); + return format_string(_ACTION_("add part"), part->name); } void AddSymbolPartAction::perform(bool to_undo) { @@ -407,8 +450,8 @@ void DuplicateSymbolPartsAction::getParts(set& parts) { // ----------------------------------------------------------------------------- : Reorder symbol parts -ReorderSymbolPartsAction::ReorderSymbolPartsAction(Symbol& symbol, size_t part_id1, size_t part_id2) - : symbol(symbol), part_id1(part_id1), part_id2(part_id2) +ReorderSymbolPartsAction::ReorderSymbolPartsAction(Symbol& symbol, size_t old_position, size_t new_position) + : symbol(symbol), old_position(old_position), new_position(new_position) {} String ReorderSymbolPartsAction::getName(bool to_undo) const { @@ -416,9 +459,12 @@ String ReorderSymbolPartsAction::getName(bool to_undo) const { } void ReorderSymbolPartsAction::perform(bool to_undo) { - assert(part_id1 < symbol.parts.size()); - assert(part_id2 < symbol.parts.size()); - swap(symbol.parts[part_id1], symbol.parts[part_id2]); + assert(old_position < symbol.parts.size()); + assert(new_position < symbol.parts.size()); + SymbolPartP part = symbol.parts.at(old_position); + symbol.parts.erase( symbol.parts.begin() + old_position); + symbol.parts.insert(symbol.parts.begin() + new_position, part); + swap(old_position, new_position); } // ----------------------------------------------------------------------------- : Group symbol parts @@ -439,6 +485,7 @@ GroupSymbolPartsAction::GroupSymbolPartsAction(Symbol& symbol, const setname = _("Group"); FOR_EACH(p, symbol.parts) { if (parts.find(p) != parts.end()) { + // add to group instead group->parts.push_back(p); if (!done) { done = true; @@ -449,6 +496,7 @@ GroupSymbolPartsAction::GroupSymbolPartsAction(Symbol& symbol, const setcalculateBounds(); } String GroupSymbolPartsAction::getName(bool to_undo) const { return _ACTION_("group parts"); diff --git a/src/data/action/symbol.hpp b/src/data/action/symbol.hpp index 89a21df8..71ad4fc9 100644 --- a/src/data/action/symbol.hpp +++ b/src/data/action/symbol.hpp @@ -20,16 +20,24 @@ // ----------------------------------------------------------------------------- : Transform symbol part -/// Anything that changes a part +/// Anything that changes one or more parts class SymbolPartAction : public Action {}; +/// Anything that changes a set of parts +class SymbolPartsAction : public SymbolPartAction { + public: + SymbolPartsAction(const set& parts); + + const set parts; ///< Affected parts +}; + /// Anything that changes a part as displayed in the part list class SymbolPartListAction : public SymbolPartAction {}; // ----------------------------------------------------------------------------- : Moving symbol parts /// Move some symbol parts -class SymbolPartMoveAction : public SymbolPartAction { +class SymbolPartMoveAction : public SymbolPartsAction { public: SymbolPartMoveAction(const set& parts, const Vector2D& delta = Vector2D()); @@ -40,10 +48,11 @@ class SymbolPartMoveAction : public SymbolPartAction { void move(const Vector2D& delta); private: - set parts; ///< Parts to move Vector2D delta; ///< How much to move Vector2D moved; ///< How much has been moved Vector2D min_pos, max_pos; ///< Bounding box of the thing we are moving + + void movePart(SymbolPart& part); ///< Move a single part public: bool constrain; ///< Constrain movement? int snap; ///< Snap to grid? @@ -52,7 +61,7 @@ class SymbolPartMoveAction : public SymbolPartAction { // ----------------------------------------------------------------------------- : Rotating symbol parts /// Transforming symbol parts using a matrix -class SymbolPartMatrixAction : public SymbolPartAction { +class SymbolPartMatrixAction : public SymbolPartsAction { public: SymbolPartMatrixAction(const set& parts, const Vector2D& center); @@ -61,10 +70,10 @@ class SymbolPartMatrixAction : public SymbolPartAction { protected: /// Perform the transformation using the given matrix - void transform(const Vector2D& mx, const Vector2D& my); + void transform(const Matrix2D& m); + void transform(SymbolPart& part, const Matrix2D& m); - set parts; ///< Parts to transform - Vector2D center; ///< Center to transform around + Vector2D center; ///< Center to transform around }; /// Rotate some symbol parts @@ -114,7 +123,7 @@ class SymbolPartShearAction : public SymbolPartMatrixAction { // ----------------------------------------------------------------------------- : Scaling symbol parts /// Scale some symbol parts -class SymbolPartScaleAction : public SymbolPartAction { +class SymbolPartScaleAction : public SymbolPartsAction { public: SymbolPartScaleAction(const set& parts, int scaleX, int scaleY); @@ -127,13 +136,13 @@ class SymbolPartScaleAction : public SymbolPartAction { void update(); private: - set parts; ///< Parts to scale Vector2D old_min, old_size; ///< the original pos/size Vector2D new_real_min, new_real_size; ///< the target pos/sizevoid shearBy(const Vector2D& shear) Vector2D new_min, new_size; ///< the target pos/size after applying constrains int scaleX, scaleY; ///< to what corner are we attached? /// Transform everything in the parts void transformAll(); + void transformPart(SymbolPart&); /// Transform a single vector inline Vector2D transform(const Vector2D& v); public: @@ -144,7 +153,7 @@ class SymbolPartScaleAction : public SymbolPartAction { // ----------------------------------------------------------------------------- : Change combine mode /// Change the name of a symbol part -class CombiningModeAction : public SymbolPartListAction { +class CombiningModeAction : public SymbolPartsAction { public: // All parts must be SymbolParts CombiningModeAction(const set& parts, SymbolShapeCombine mode); @@ -153,22 +162,26 @@ class CombiningModeAction : public SymbolPartListAction { virtual void perform(bool to_undo); private: + void add(const SymbolPartP&, SymbolShapeCombine mode); vector > parts; ///< Affected parts with new combining modes }; // ----------------------------------------------------------------------------- : Change name /// Change the name of a symbol part -class SymbolPartNameAction : public SymbolPartListAction { +class SymbolPartNameAction : public SymbolPartAction { public: - SymbolPartNameAction(const SymbolPartP& part, const String& name); + SymbolPartNameAction(const SymbolPartP& part, const String& name, size_t old_cursor, size_t new_cursor); virtual String getName(bool to_undo) const; virtual void perform(bool to_undo); + virtual bool merge(const Action& action); - private: + public: SymbolPartP part; ///< Affected part String part_name; ///< New name + size_t old_cursor; ///< Cursor position + size_t new_cursor; ///< Cursor position }; // ----------------------------------------------------------------------------- : Add symbol part @@ -224,18 +237,18 @@ class DuplicateSymbolPartsAction : public SymbolPartListAction { // ----------------------------------------------------------------------------- : Reorder symbol parts -/// Change the position of a part in a symbol, by swapping two parts. +/// Change the position of a part in a symbol, by moving a part. class ReorderSymbolPartsAction : public SymbolPartListAction { public: - ReorderSymbolPartsAction(Symbol& symbol, size_t part_id1, size_t part_id2); + ReorderSymbolPartsAction(Symbol& symbol, size_t old_position, size_t new_position); virtual String getName(bool to_undo) const; virtual void perform(bool to_undo); private: - Symbol& symbol; ///< Symbol to swap the parts in + Symbol& symbol; ///< Symbol to swap the parts in public: - size_t part_id1, part_id2; ///< Indices of parts to swap + size_t old_position, new_position; ///< Positions to move from and to }; diff --git a/src/data/action/symbol_part.cpp b/src/data/action/symbol_part.cpp index c10d4b45..01254065 100644 --- a/src/data/action/symbol_part.cpp +++ b/src/data/action/symbol_part.cpp @@ -349,8 +349,8 @@ SinglePointRemoveAction::SinglePointRemoveAction(const SymbolShapeP& shape, UInt Vector2D p = c.pointAt(t); Vector2D distP = point->pos - p; // adjust handle sizes - point1.other.delta_after *= ssqrt(distP.dot(point1.other.delta_after) /point1.other.delta_after.lengthSqr()) + 1; - point2.other.delta_before *= ssqrt(distP.dot(point2.other.delta_before)/point2.other.delta_before.lengthSqr()) + 1; + point1.other.delta_after *= ssqrt(dot(distP, point1.other.delta_after) /point1.other.delta_after.lengthSqr()) + 1; + point2.other.delta_before *= ssqrt(dot(distP, point2.other.delta_before)/point2.other.delta_before.lengthSqr()) + 1; // unlock if needed if (point1.other.lock == LOCK_SIZE) point1.other.lock = LOCK_DIR; diff --git a/src/data/format/image_to_symbol.cpp b/src/data/format/image_to_symbol.cpp index ceb4b773..cbf2ba2d 100644 --- a/src/data/format/image_to_symbol.cpp +++ b/src/data/format/image_to_symbol.cpp @@ -245,7 +245,7 @@ void mark_corners(SymbolShape& shape) { Vector2D after = .6 * shape.getPoint(i+1)->pos + .2 * shape.getPoint(i+2)->pos + .1 * shape.getPoint(i+3)->pos + .1 * shape.getPoint(i+4)->pos; before = (before - current.pos).normalized(); after = (after - current.pos).normalized(); - if (before.dot(after) >= -0.25f) { + if (dot(before,after) >= -0.25f) { // corner current.lock = LOCK_FREE; } else { @@ -285,8 +285,8 @@ void merge_corners(SymbolShape& shape) { for (int j = 0 ; j < 4 ; ++j) { Vector2D a_ = (shape.getPoint(i-j-1)->pos - prev.pos).normalized(); Vector2D b_ = (shape.getPoint(i+j)->pos - cur.pos).normalized(); - double a_dot = a_.dot(ab); - double b_dot = -b_.dot(ab); + double a_dot = dot(a_, ab); + double b_dot = -dot(b_, ab); if (a_dot < min_a_dot) { min_a_dot = a_dot; a = a_; @@ -300,8 +300,8 @@ void merge_corners(SymbolShape& shape) { // t a + ab = u b, solve for t,u // Gives us: // t = ab cross b / b cross a - double tden = max(0.00000001, b.cross(a)); - double t = ab.cross(b) / tden; + double tden = max(0.00000001, cross(b,a)); + double t = cross(ab,b) / tden; // do these tangent lines intersect, and not too far away? // if so, then the intersection point is the merged point if (t >= 0 && t < 20.0) { @@ -358,7 +358,7 @@ void straighten(SymbolShape& shape) { Vector2D bb = next.delta_before.normalized(); // if the area beneath the polygon formed by the handles is small // then it is a straight line - double cpDot = abs(aa.cross(ab)) + abs(bb.cross(ab)); + double cpDot = abs(cross(aa,ab)) + abs(cross(bb,ab)); if (cpDot < treshold) { cur.segment_after = next.segment_before = SEGMENT_LINE; cur.delta_after = next.delta_before = Vector2D(); diff --git a/src/data/symbol.cpp b/src/data/symbol.cpp index bea73f6e..2ef1cbfe 100644 --- a/src/data/symbol.cpp +++ b/src/data/symbol.cpp @@ -94,6 +94,11 @@ Vector2D& ControlPoint::getOther(WhichHandle wh) { // ----------------------------------------------------------------------------- : SymbolPart +void SymbolPart::calculateBounds() { + min_pos = Vector2D::infinity(); + max_pos = -Vector2D::infinity(); +} + IMPLEMENT_REFLECTION(SymbolPart) { REFLECT_IF_NOT_READING { String type = typeName(); @@ -235,6 +240,19 @@ SymbolPartP SymbolGroup::clone() const { return part; } +void SymbolGroup::calculateBounds() { + FOR_EACH(p, parts) p->calculateBounds(); + calculateBoundsNonRec(); +} +void SymbolGroup::calculateBoundsNonRec() { + min_pos = Vector2D::infinity(); + max_pos = -Vector2D::infinity(); + FOR_EACH(p, parts) { + min_pos = piecewise_min(min_pos, p->min_pos); + max_pos = piecewise_max(max_pos, p->max_pos); + } +} + IMPLEMENT_REFLECTION(SymbolGroup) { REFLECT_BASE(SymbolPart); REFLECT(parts); diff --git a/src/data/symbol.hpp b/src/data/symbol.hpp index 3ccde3d1..0375c3a8 100644 --- a/src/data/symbol.hpp +++ b/src/data/symbol.hpp @@ -112,6 +112,9 @@ class SymbolPart : public IntrusivePtrVirtualBase { public: /// Name/label for this part String name; + /// Position and size of the part. + /** this is the smallest axis aligned bounding box that fits around the part */ + Vector2D min_pos, max_pos; /// Type of this part virtual String typeName() const = 0; @@ -130,6 +133,9 @@ class SymbolPart : public IntrusivePtrVirtualBase { virtual SymbolGroup* isSymbolGroup() { return nullptr; } virtual const SymbolGroup* isSymbolGroup() const { return nullptr; } + /// Calculate the position and size of the part (min_pos and max_pos) + virtual void calculateBounds(); + DECLARE_REFLECTION_VIRTUAL(); }; @@ -162,9 +168,6 @@ class SymbolShape : public SymbolPart { SymbolShapeCombine combine; // Center of rotation, relative to the part, when the part is scaled to [0..1] Vector2D rotation_center; - /// Position and size of the part - /// this is the smallest axis aligned bounding box that fits around the part - Vector2D min_pos, max_pos; SymbolShape(); @@ -183,7 +186,7 @@ class SymbolShape : public SymbolPart { void enforceConstraints(); /// Calculate the position and size of the part - void calculateBounds(); + virtual void calculateBounds(); DECLARE_REFLECTION(); }; @@ -221,7 +224,7 @@ class SymbolSymmetry : public SymbolPart { /// A group of symbol parts class SymbolGroup : public SymbolPart { public: - vector parts; ///< The parts in this group + vector parts; ///< The parts in this group, first item is on top virtual String typeName() const; virtual SymbolPartP clone() const; @@ -229,16 +232,18 @@ class SymbolGroup : public SymbolPart { virtual SymbolGroup* isSymbolGroup() { return this; } virtual const SymbolGroup* isSymbolGroup() const { return this; } + virtual void calculateBounds(); + /// re-calculate the bounds, but not of the contained parts + void calculateBoundsNonRec(); + DECLARE_REFLECTION(); }; // ----------------------------------------------------------------------------- : Symbol /// An editable symbol, consists of any number of SymbolParts -class Symbol : public IntrusivePtrBase { +class Symbol : public SymbolGroup { public: - /// The parts of this symbol, first item is on top - vector parts; /// Actions performed on this symbol and the parts in it ActionStack actions; diff --git a/src/gfx/bezier.cpp b/src/gfx/bezier.cpp index 9b979691..75800d85 100644 --- a/src/gfx/bezier.cpp +++ b/src/gfx/bezier.cpp @@ -203,7 +203,7 @@ bool pos_on_line(const Vector2D& pos, double range, const Vector2D& p1, const Ve Vector2D p21 = p2 - p1; double p21len = p21.lengthSqr(); if (p21len < 0.00001) return false; // line is too short - t = p21.dot(pos - p1) / p21len; // 'time' on line p1->p2 + t = dot(p21, pos - p1) / p21len; // 'time' on line p1->p2 if (t < 0 || t > 1) return false; // outside segment pOut = p1 + p21 * t; // point on line Vector2D dist = pOut - pos; // distance to line diff --git a/src/gui/symbol/basic_shape_editor.cpp b/src/gui/symbol/basic_shape_editor.cpp index 02185a8b..602d24da 100644 --- a/src/gui/symbol/basic_shape_editor.cpp +++ b/src/gui/symbol/basic_shape_editor.cpp @@ -9,7 +9,9 @@ #include #include #include +#include #include +#include #include // ----------------------------------------------------------------------------- : SymbolBasicShapeEditor @@ -108,7 +110,7 @@ void SymbolBasicShapeEditor::onMouseDrag (const Vector2D& from, const Vector2D& // Resize the object if (drawing) { end = to; - makeShape(start, end, ev.ControlDown(), ev.ShiftDown()); + makeShape(start, end, ev.ControlDown(), settings.symbol_grid_snap, ev.ShiftDown()); control.Refresh(false); } } @@ -119,7 +121,7 @@ void SymbolBasicShapeEditor::onKeyChange(wxKeyEvent& ev) { if (drawing) { if (ev.GetKeyCode() == WXK_CONTROL || ev.GetKeyCode() == WXK_SHIFT) { // changed constrains - makeShape(start, end, ev.ControlDown(), ev.ShiftDown()); + makeShape(start, end, ev.ControlDown(), settings.symbol_grid_snap, ev.ShiftDown()); control.Refresh(false); } else if (ev.GetKeyCode() == WXK_ESCAPE) { // cancel drawing @@ -156,7 +158,12 @@ inline double sgn(double d) { return d < 0 ? - 1 : 1; } -void SymbolBasicShapeEditor::makeShape(const Vector2D& a, const Vector2D& b, bool constrained, bool centered) { +void SymbolBasicShapeEditor::makeShape(Vector2D a, Vector2D b, bool constrained, bool snap, bool centered) { + // snap + if (snap) { + a = snap_vector(a, settings.symbol_grid_size); + b = snap_vector(b, settings.symbol_grid_size); + } // constrain Vector2D size = b - a; if (constrained) { diff --git a/src/gui/symbol/basic_shape_editor.hpp b/src/gui/symbol/basic_shape_editor.hpp index ad9febcc..875a7fe1 100644 --- a/src/gui/symbol/basic_shape_editor.hpp +++ b/src/gui/symbol/basic_shape_editor.hpp @@ -63,7 +63,7 @@ class SymbolBasicShapeEditor : public SymbolEditorBase { /** when centered: a = center, b-a = radius * otherwise: a = top left, b = bottom right */ - void makeShape(const Vector2D& a, const Vector2D& b, bool constrained, bool centered); + void makeShape(Vector2D a, Vector2D b, bool constrained, bool snap, bool centered); /// Make the shape, centered in c, with radius r void makeCenteredShape(const Vector2D& c, Vector2D r, bool constrained); diff --git a/src/gui/symbol/control.cpp b/src/gui/symbol/control.cpp index 8e848ea7..39a57c68 100644 --- a/src/gui/symbol/control.cpp +++ b/src/gui/symbol/control.cpp @@ -162,20 +162,20 @@ void SymbolControl::draw(DC& dc) { SymbolViewer::draw(dc); // draw grid if (settings.symbol_grid) { - wxSize s = dc.GetSize(); double lines = settings.symbol_grid_size; + double end = rotation.trS(1); for (int i = 0 ; i <= lines ; ++i) { int x = (int) floor(rotation.trS(i/lines-0.0001)); //dc.SetPen(Color(0, i%5 == 0 ? 64 : 31, 0)); //dc.SetPen(Color(i%5 == 0 ? 64 : 31, 0, 0)); dc.SetLogicalFunction(wxAND); dc.SetPen(i%5 == 0 ? Color(191,255,191) : Color(191, 255, 191)); - dc.DrawLine(x, 0, x, s.y); - dc.DrawLine(0, x, s.x, x); + dc.DrawLine(x, 0, x, end); + dc.DrawLine(0, x, end, x); dc.SetLogicalFunction(wxOR); dc.SetPen(i%5 == 0 ? Color(0,63,0) : Color(0, 31, 0)); - dc.DrawLine(x, 0, x, s.y); - dc.DrawLine(0, x, s.x, x); + dc.DrawLine(x, 0, x, end); + dc.DrawLine(0, x, end, x); } dc.SetLogicalFunction(wxCOPY); } @@ -251,7 +251,7 @@ void SymbolControl::onUpdateUI(wxUpdateUIEvent& ev) { ev.Check(editor->modeToolId() == ev.GetId()); if (ev.GetId() == ID_MODE_POINTS) { // can only edit points when a single part is selected - ev.Enable(selected_parts.size() == 1); + ev.Enable(selected_parts.size() == 1 && (*selected_parts.begin())->isSymbolShape()); } break; case ID_MODE_PAINT: diff --git a/src/gui/symbol/control.hpp b/src/gui/symbol/control.hpp index 50054313..bd43ffdb 100644 --- a/src/gui/symbol/control.hpp +++ b/src/gui/symbol/control.hpp @@ -72,7 +72,8 @@ class SymbolControl : public wxControl, public SymbolViewer { public: /// What parts are selected? set selected_parts; - SymbolShapeP selected_shape; // if there is a single selection + SymbolPartP highlight_part; ///< part the mouse cursor is over + SymbolShapeP selected_shape; ///< if there is a single selection /// Parent window SymbolWindow* parent; diff --git a/src/gui/symbol/part_list.cpp b/src/gui/symbol/part_list.cpp index 38d7ff6d..89115fbf 100644 --- a/src/gui/symbol/part_list.cpp +++ b/src/gui/symbol/part_list.cpp @@ -9,147 +9,455 @@ #include #include #include -#include +#include +#include +#include +#include -// ----------------------------------------------------------------------------- : Constructor +DECLARE_TYPEOF_COLLECTION(SymbolPartP); -SymbolPartList::SymbolPartList(Window* parent, int id, SymbolP symbol) - : wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, - wxLC_REPORT | wxLC_NO_HEADER | wxLC_VIRTUAL | wxLC_EDIT_LABELS) +// ----------------------------------------------------------------------------- : Events + +DEFINE_EVENT_TYPE(EVENT_PART_SELECT); +DEFINE_EVENT_TYPE(EVENT_PART_ACTIVATE); + +// ----------------------------------------------------------------------------- : SymbolPartList + + +SymbolPartList::SymbolPartList(Window* parent, int id, set& selection, SymbolP symbol) + : wxScrolledWindow(parent, id, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER | wxVSCROLL) + , selection(selection) + , state_icons(9,8) { - // Create image list - wxImageList* images = new wxImageList(16,16); // NOTE: this is based on the order of the SymbolShapeCombine and SymbolSymmetryType enums! - images->Add(load_resource_image(_("combine_or"))); - images->Add(load_resource_image(_("combine_sub"))); - images->Add(load_resource_image(_("combine_and"))); - images->Add(load_resource_image(_("combine_xor"))); - images->Add(load_resource_image(_("combine_over"))); - images->Add(load_resource_image(_("combine_border"))); - images->Add(load_resource_image(_("symmetry_rotation"))); - images->Add(load_resource_image(_("symmetry_reflection"))); - AssignImageList(images, wxIMAGE_LIST_SMALL); - // create columns - InsertColumn(0, _("Name")); + state_icons.Add(load_resource_image(_("icon_combine_merge"))); + state_icons.Add(load_resource_image(_("icon_combine_subtract"))); + state_icons.Add(load_resource_image(_("icon_combine_intersection"))); + state_icons.Add(load_resource_image(_("icon_combine_difference"))); + state_icons.Add(load_resource_image(_("icon_combine_overlap"))); + state_icons.Add(load_resource_image(_("icon_combine_border"))); + state_icons.Add(load_resource_image(_("icon_symmetry_rotation"))); + state_icons.Add(load_resource_image(_("icon_symmetry_reflection"))); + state_icons.Add(load_resource_image(_("icon_symbol_group"))); // view symbol setSymbol(symbol); } -// ----------------------------------------------------------------------------- : View events - void SymbolPartList::onChangeSymbol() { + typing_in = SymbolPartP(); + cursor = 0; update(); } void SymbolPartList::onAction(const Action& action, bool undone) { - TYPE_CASE(action, ReorderSymbolPartsAction) { - if (selected == (long) action.part_id1) { - selectItem((long) action.part_id2); - } else if (selected == (long) action.part_id2) { - selectItem((long) action.part_id1); + TYPE_CASE(action, SymbolPartNameAction) { + if (typing_in == action.part) { + cursor = action.new_cursor; } + Refresh(false); + if (true) return; } TYPE_CASE_(action, SymbolPartListAction) { update(); + return; + } + TYPE_CASE(action, SymbolPartsAction) { + symbol_preview.up_to_date = false; + updateParts(action.parts); + if (true) return; + } + TYPE_CASE_(action, SymbolPartAction) { + symbol_preview.up_to_date = false; + // some part changed, but we don't know which one, assume it is the selection + updateParts(selection); + return; } } -// ----------------------------------------------------------------------------- : Other - -String SymbolPartList::OnGetItemText(long item, long col) const { - assert(col == 0); - return getPart(item)->name; -} -int SymbolPartList::OnGetItemImage(long item) const { - return getPart(item)->icon(); -} - -SymbolPartP SymbolPartList::getPart(long item) const { - return symbol->parts.at(item); -} -void SymbolPartList::selectItem(long item) { - selected = (long)item; - long count = GetItemCount(); - for (long i = 0 ; i < count ; ++i) { - SetItemState(i, i == selected ? wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED : 0, - wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); - } -} - -void SymbolPartList::getSelectedParts(set& sel) { - sel.clear(); - long count = GetItemCount(); - for (long i = 0 ; i < count ; ++ i) { - bool selected = GetItemState(i, wxLIST_STATE_SELECTED); - if (selected) sel.insert(symbol->parts.at(i)); - } -} - -void SymbolPartList::selectParts(const set& sel) { - long count = GetItemCount(); - for (long i = 0 ; i < count ; ++ i) { - // is that part selected? - bool selected = sel.find(symbol->parts.at(i)) != sel.end(); - SetItemState(i, selected ? wxLIST_STATE_SELECTED : 0, - wxLIST_STATE_SELECTED); - } +wxSize SymbolPartList::DoGetBestSize() const { + wxSize ws = GetSize(), cs = GetClientSize(); + const int w = 110; + const int h = ITEM_HEIGHT; + return wxSize(w, h) + ws - cs; } void SymbolPartList::update() { - if (symbol->parts.empty()) { - // deleting all items requires a full refresh on win32 - SetItemCount(0); - Refresh(true); - } else { - SetItemCount((long) symbol->parts.size() ); - Refresh(false); + // count items + number_of_items = childCount(symbol); + SetVirtualSize(110, number_of_items * (ITEM_HEIGHT+1)); + // invalidate previews + symbol_preview.up_to_date = false; + for (size_t i = 0 ; i < part_previews.size() ; ++i) { + part_previews[i].up_to_date = false; } + // refresh + Refresh(false); } - -// ----------------------------------------------------------------------------- : Event handling - -void SymbolPartList::onSelect(wxListEvent& ev) { - selected = ev.GetIndex(); - ev.Skip(); +void SymbolPartList::updateParts(const set& parts) { + symbol_preview.up_to_date = false; + int i = 0; + FOR_EACH(p, symbol->parts) { + updatePart(parts, i, false, p); + } + // refresh + Refresh(false); } -void SymbolPartList::onDeselect(wxListEvent& ev) { - selected = -1; - ev.Skip(); -} - -void SymbolPartList::onLabelEdit(wxListEvent& ev){ - symbol->actions.add( - new SymbolPartNameAction(getPart(ev.GetIndex()), ev.GetLabel()) - ); -} - -void SymbolPartList::onSize(wxSizeEvent& ev) { - wxSize s = GetClientSize(); - SetColumnWidth(0, s.GetWidth() - 2); -} - -void SymbolPartList::onDrag(wxMouseEvent& ev) { - if (!ev.Dragging() || selected == -1) return; - // reorder the list of parts - int flags; - long item = HitTest(ev.GetPosition(), flags); - if (flags & wxLIST_HITTEST_ONITEM) { - if (item > 0) EnsureVisible(item - 1); - if (item < GetItemCount() - 1) EnsureVisible(item + 1); - if (item != selected) { - // swap the two items - symbol->actions.add(new ReorderSymbolPartsAction(*symbol, item, selected)); - selectItem(item); // deselect all other items, to prevent 'lassoing' them +void SymbolPartList::updatePart(const set& parts, int& i, bool parent_updated, const SymbolPartP& part) { + bool update = parent_updated || parts.find(part) != parts.end(); + if (update) { + part_previews[i].up_to_date = false; + } + ++i; + if (SymbolGroup* g = part->isSymbolGroup()) { + FOR_EACH(p, g->parts) { + updatePart(parts, i, update, p); } } } +// ----------------------------------------------------------------------------- : Events + +void SymbolPartList::onLeftDown(wxMouseEvent& ev) { + // find item under cursor + if (ev.GetX() < 0 || ev.GetX() >= GetClientSize().x) return; + SymbolPartP part = findItem(ev.GetY() / (ITEM_HEIGHT + 1)); + if (part) { + // toggle/select + if (!ev.ShiftDown()) { + selection.clear(); + } + if (selection.find(part) != selection.end()) { + selection.erase(part); + } else { + selection.insert(part); + } + if (!ev.ShiftDown()) { + // drag item + mouse_down_on = part; + drop_position = -1; + CaptureMouse(); + } + sendEvent(EVENT_PART_SELECT); + Refresh(false); + } + ev.Skip(); // for focus +} +void SymbolPartList::onLeftUp(wxMouseEvent& ev) { + if (HasCapture()) ReleaseMouse(); + if (mouse_down_on && drop_position != -1) { + // move part + // find old position + vector::const_iterator it = find(symbol->parts.begin(), symbol->parts.end(), mouse_down_on); + mouse_down_on = SymbolPartP(); + if (it == symbol->parts.end()) { + Refresh(false); + return; + } + size_t old_position = it - symbol->parts.begin(); + // find new position + size_t new_position; + SymbolPartP drop_before = findItem(drop_position); + it = find(symbol->parts.begin(), symbol->parts.end(), drop_before); + if (it == symbol->parts.end()) { + new_position = number_of_items - 1; + } else { + new_position = it - symbol->parts.begin(); + if (old_position < new_position) new_position -= 1; + } + // move part + if (old_position != new_position) { + symbol->actions.add(new ReorderSymbolPartsAction(*symbol, old_position, new_position)); + } else { + Refresh(false); + } + } else { + mouse_down_on = SymbolPartP(); + } +} +void SymbolPartList::onMotion(wxMouseEvent& ev) { + if (mouse_down_on) { + int new_drop_position = (ev.GetY() + ITEM_HEIGHT/2) / (ITEM_HEIGHT + 1); + if (new_drop_position < 0 || new_drop_position > number_of_items) new_drop_position = -1; + // TODO: make sure it is not in a group + if (drop_position != new_drop_position) { + drop_position = new_drop_position; + Refresh(false); + } + } +} + +void SymbolPartList::onLeftDClick(wxMouseEvent& ev) { + // double click = activate + sendEvent(EVENT_PART_ACTIVATE); +} + +void SymbolPartList::onChar(wxKeyEvent& ev) { + if (typing_in) { + // Typing in name + String new_name = typing_in->name; + switch (ev.GetKeyCode()) { + case WXK_HOME: + if (cursor != 0) { + cursor = 0; Refresh(false); // TODO: without refresh + } + break; + case WXK_END: + if (cursor != typing_in->name.size()) { + cursor = typing_in->name.size(); Refresh(false); + } + break; + case WXK_LEFT: + if (cursor > 0) { + --cursor; Refresh(false); + } + break; + case WXK_RIGHT: + if (cursor < typing_in->name.size()) { + ++cursor; Refresh(false); + } + break; + case WXK_BACK: + if (cursor > 0 && cursor <= typing_in->name.size()) { + String new_name = typing_in->name; + new_name.erase(cursor - 1, 1); + symbol->actions.add(new SymbolPartNameAction(typing_in, new_name, cursor, cursor - 1)); + } + break; + case WXK_DELETE: + if (cursor < typing_in->name.size()) { + String new_name = typing_in->name; + new_name.erase(cursor, 1); + symbol->actions.add(new SymbolPartNameAction(typing_in, new_name, cursor, cursor)); + } + break; + default: + // See gui/value/text.cpp + #ifdef __WXMSW__ + if (ev.GetKeyCode() >= _(' ') && ev.GetKeyCode() == (int)ev.GetRawKeyCode()) { + #else + if (ev.GetKeyCode() >= _(' ') /*&& ev.GetKeyCode() == (int)ev.GetRawKeyCode()*/) { + #endif + #ifdef UNICODE + Char key = ev.GetUnicodeKey(); + #else + Char key = (Char)ev.GetKeyCode(); + #endif + String new_name = typing_in->name; + new_name.insert(cursor, 1, key); + symbol->actions.add(new SymbolPartNameAction(typing_in, new_name, cursor, cursor + 1)); + } + } + } + // Move up/down? +} + +void SymbolPartList::onSize(wxSizeEvent&) { + Refresh(false); +} + +void SymbolPartList::sendEvent(int type) { + wxCommandEvent ev(type, GetId()); + ProcessEvent(ev); +} + +// ----------------------------------------------------------------------------- : Items + +SymbolPartP SymbolPartList::findItem(int i) const { + FOR_EACH(p, symbol->parts) { + SymbolPartP f = findItem(i, p); + if (f) return f; + } + return SymbolPartP(); +} +SymbolPartP SymbolPartList::findItem(int& i, const SymbolPartP& part) { + if (i < 0 ) return SymbolPartP(); + if (i == 0) return part; + i -= 1; + // sub item? + if (SymbolGroup* g = part->isSymbolGroup()) { + FOR_EACH(p, g->parts) { + if (findItem(i, p)) return part; + } + } + return SymbolPartP(); +} + +int SymbolPartList::childCount(const SymbolPartP& part) { + if (SymbolGroup* g = part->isSymbolGroup()) { + int count = 0; + FOR_EACH(p, g->parts) count += 1 + childCount(p); + return count; + } else { + return 0; + } +} + +// ----------------------------------------------------------------------------- : Text editor + + +// ----------------------------------------------------------------------------- : Drawing + + +void SymbolPartList::onPaint(wxPaintEvent&) { + wxBufferedPaintDC dc(this); + DoPrepareDC(dc); + OnDraw(dc); +} +void SymbolPartList::OnDraw(DC& dc) { + // init + dc.SetFont(*wxNORMAL_FONT); + // clear background + wxSize size = GetClientSize(); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + dc.DrawRectangle(0,0,size.x,size.y); + // items + int i = 0; + FOR_EACH(p, symbol->parts) { + drawItem(dc, 0, i, false, p); + } + // drag/drop indicator + if (mouse_down_on && drop_position != -1) { + dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), 3)); + int y = drop_position * (ITEM_HEIGHT + 1) - 1; + dc.DrawLine(0,y,size.x,y); + dc.DrawLine(0,y-3,0,y+3); + dc.DrawLine(size.x-1,y-3,size.x-1,y+3); + } + // hide caret + if (selection.size() != 1) { + typing_in = SymbolPartP(); + wxCaret* caret = GetCaret(); + if (caret) caret->Hide(); + } +} + +void SymbolPartList::drawItem(DC& dc, int x, int& i, bool parent_active, const SymbolPartP& part) { + wxSize size = GetClientSize(); + int w = size.x - x; + int y = i * (ITEM_HEIGHT + 1); + // draw item : highlight + Color background; + dc.SetPen(*wxTRANSPARENT_PEN); + bool active = selection.find(part) != selection.end(); + if (active) { + background = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); + dc.SetBrush(background); + dc.DrawRectangle(x,y,w,ITEM_HEIGHT); + dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT)); + } else if (parent_active) { + background = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); + dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + } else { + background = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + } + // draw item : name + int h = dc.GetCharHeight(); + dc.DrawText(part->name, ITEM_HEIGHT + x + 3, y + (ITEM_HEIGHT - h) / 2); + // draw item : icon + dc.SetBrush(lerp(background,wxColour(0,128,0),0.7)); + dc.DrawRectangle(x,y,ITEM_HEIGHT,ITEM_HEIGHT); + dc.DrawBitmap(symbolPreview(), x, y); + dc.DrawBitmap(itemPreview(i,part), x, y); + // draw item : border + wxPen line_pen = lerp(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), + wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),0.5); + dc.SetPen(line_pen); + dc.DrawLine(x+ITEM_HEIGHT, y, x+ITEM_HEIGHT, y + ITEM_HEIGHT + 1); // line after image + dc.DrawLine(x, y + ITEM_HEIGHT, size.x, y + ITEM_HEIGHT); // line below + // update caret + if (selection.size() == 1 && active) { + updateCaret(dc, x + ITEM_HEIGHT + 3, y + (ITEM_HEIGHT - h) / 2, h, part); + } + // move down + i += 1; + // draw more? + if (SymbolShape* s = part->isSymbolShape()) { + // combine state + state_icons.Draw(s->combine, dc, size.x - 10, y + 1); + } else if (SymbolSymmetry* s = part->isSymbolSymmetry()) { + // kind of symmetry + state_icons.Draw(s->kind, dc, size.x - 10, y + 1); + // TODO: show clip mode? + } else if (SymbolGroup* g = part->isSymbolGroup()) { + state_icons.Draw(SYMMETRY_REFLECTION + 1, dc, size.x - 10, y + 1); + FOR_EACH(p, g->parts) drawItem(dc, x + 5, i, active || parent_active, p); + // draw bar on the left + int new_y = i * (ITEM_HEIGHT + 1); + y += ITEM_HEIGHT+1; + if (y != new_y) { + dc.SetPen(line_pen); + dc.SetBrush(background); + dc.DrawRectangle(x-1,y-1,5+1,new_y-y+1); + } + } else { + throw "Unknown symbol part type"; + } +} + +const Image& SymbolPartList::itemPreview(int i, const SymbolPartP& part) { + if ((size_t)i >= part_previews.size()) part_previews.resize(i + 1); + Preview& p = part_previews[i]; + if (!p.up_to_date) { + SolidFillSymbolFilter filter(*wxBLACK, Color(255,128,128)); + // temporary symbol + SymbolP sym(new Symbol); sym->parts.push_back(part); + Image img; + if (SymbolShape* s = part->isSymbolShape()) { + if (s->combine == SYMBOL_COMBINE_SUBTRACT) { + // temporarily render using subtract instead, otherwise we don't see anything + s->combine = SYMBOL_COMBINE_BORDER; + img = render_symbol(sym, filter, 0.08, ITEM_HEIGHT * 4); + s->combine = SYMBOL_COMBINE_SUBTRACT; + } else { + img = render_symbol(sym, filter, 0.08, ITEM_HEIGHT * 4); + } + } else { + img = render_symbol(sym, filter, 0.08, ITEM_HEIGHT * 4); + } + resample(img, p.image); + p.up_to_date = true; + } + return p.image; +} +const Image& SymbolPartList::symbolPreview() { + if (!symbol_preview.up_to_date) { + SolidFillSymbolFilter filter(AColor(0,0,0,40), AColor(255,255,255,40)); + Image img = render_symbol(symbol, filter, 0.06, ITEM_HEIGHT * 4); + resample(img, symbol_preview.image); + symbol_preview.up_to_date = true; + } + return symbol_preview.image; +} + +void SymbolPartList::updateCaret(DC& dc, int x, int y, int h, const SymbolPartP& part) { + // make caret + wxCaret* caret = GetCaret(); + if (!caret) { + caret = new wxCaret(this, 1, h); + SetCaret(caret); + } + // move caret + if (typing_in != part) { + typing_in = part; + cursor = typing_in->name.size(); + } + cursor = min(cursor, typing_in->name.size()); + int w; + dc.GetTextExtent(typing_in->name.substr(0,cursor), &w, nullptr); + caret->Move(x+w,y); + if (!caret->IsVisible()) caret->Show(); +} + // ----------------------------------------------------------------------------- : Event table -BEGIN_EVENT_TABLE(SymbolPartList, wxListCtrl) - EVT_LIST_ITEM_SELECTED (wxID_ANY, SymbolPartList::onSelect) - EVT_LIST_ITEM_DESELECTED (wxID_ANY, SymbolPartList::onDeselect) - EVT_LIST_END_LABEL_EDIT (wxID_ANY, SymbolPartList::onLabelEdit) - EVT_SIZE ( SymbolPartList::onSize) - EVT_MOTION ( SymbolPartList::onDrag) +BEGIN_EVENT_TABLE(SymbolPartList, wxScrolledWindow) + EVT_LEFT_DOWN (SymbolPartList::onLeftDown) + EVT_LEFT_DCLICK (SymbolPartList::onLeftDClick) + EVT_LEFT_UP (SymbolPartList::onLeftUp) + EVT_MOTION (SymbolPartList::onMotion) + EVT_CHAR (SymbolPartList::onChar) + EVT_PAINT (SymbolPartList::onPaint) + EVT_SIZE (SymbolPartList::onSize) END_EVENT_TABLE () diff --git a/src/gui/symbol/part_list.hpp b/src/gui/symbol/part_list.hpp index b3ceff27..15dd8144 100644 --- a/src/gui/symbol/part_list.hpp +++ b/src/gui/symbol/part_list.hpp @@ -11,61 +11,80 @@ #include #include -#include + +// ----------------------------------------------------------------------------- : Events + +DECLARE_EVENT_TYPE(EVENT_PART_SELECT, ) +DECLARE_EVENT_TYPE(EVENT_PART_ACTIVATE, ) + +/// Handle EVENT_PART_SELECT events +#define EVT_PART_SELECT( id, handler) EVT_COMMAND(id, EVENT_PART_SELECT, handler) +/// Handle EVENT_PART_ACTIVATE events +#define EVT_PART_ACTIVATE(id, handler) EVT_COMMAND(id, EVENT_PART_ACTIVATE, handler) // ----------------------------------------------------------------------------- : SymbolPartList -// A list view of parts of a symbol -class SymbolPartList : public wxListCtrl, public SymbolView { +class SymbolPartList : public wxScrolledWindow, public SymbolView { public: - SymbolPartList(Window* parent, int id, SymbolP symbol = SymbolP()); - - /// Update the list - void update(); - - /// Is there a selection? - inline bool hasSelection() const { return selected != -1; } - /// Return the last part that was selected - /** @pre hasSelection() - */ - inline SymbolPartP getSelection() const { return getPart(selected); } - - /// Get a set of selected parts - void getSelectedParts(set& sel); - /// Select the specified parts, and nothing else - void selectParts(const set& sel); + SymbolPartList(Window* parent, int id, set& selection, SymbolP symbol = SymbolP()); /// Another symbol is being viewed - void onChangeSymbol(); - + virtual void onChangeSymbol(); /// Event handler for changes to the symbol - virtual void onAction(const Action& a, bool undone); + virtual void onAction(const Action&, bool); + + /// Update the control + void update(); + /// Update only a subset of the parts + void updateParts(const set& parts); protected: - /// Get the text of an item - virtual String OnGetItemText(long item, long col) const; - /// Get the icon of an item - virtual int OnGetItemImage(long item) const; - + virtual wxSize DoGetBestSize() const; private: - /// The selected item, or -1 if there is no selection - long selected; + set& selection; ///< Store selection here - /// Get a part from the symbol - SymbolPartP getPart(long item) const; + SymbolPartP mouse_down_on; + int drop_position; + int number_of_items; - /// Select an item, also in the list control - /// Deselects all other items - void selectItem(long item); + SymbolPartP typing_in; + size_t cursor; + wxImageList state_icons; + struct Preview { + Preview() : up_to_date(false), image(25,25) {} + bool up_to_date; + Image image; + }; + Preview symbol_preview; ///< Preview of the whole symbol + vector part_previews; + + static const int ITEM_HEIGHT = 25; // --------------------------------------------------- : Event handling DECLARE_EVENT_TABLE(); - void onSelect (wxListEvent& ev); - void onDeselect (wxListEvent& ev); - void onLabelEdit(wxListEvent& ev); - void onSize (wxSizeEvent& ev); - void onDrag (wxMouseEvent& ev); + void onLeftDown (wxMouseEvent& ev); + void onLeftDClick(wxMouseEvent& ev); + void onLeftUp (wxMouseEvent& ev); + void onMotion (wxMouseEvent& ev); + void onChar(wxKeyEvent& ev); + void onPaint(wxPaintEvent&); + void onSize(wxSizeEvent&); + void OnDraw(DC& dc); + + void sendEvent(int type); + + void drawItem(DC& dc, int x, int& i, bool parent_active, const SymbolPartP& part); + const Image& itemPreview(int i, const SymbolPartP& part); + const Image& symbolPreview(); + void updatePart(const set& parts, int& i, bool parent_updated, const SymbolPartP& part); + + SymbolPartP findItem(int i) const; + static SymbolPartP findItem(int& i, const SymbolPartP& part); + + static int childCount(const SymbolPartP& part); + + void updateCaret(DC& dc, int x, int y, int h, const SymbolPartP& part); }; // ----------------------------------------------------------------------------- : EOF diff --git a/src/gui/symbol/select_editor.cpp b/src/gui/symbol/select_editor.cpp index fff39aea..2477f192 100644 --- a/src/gui/symbol/select_editor.cpp +++ b/src/gui/symbol/select_editor.cpp @@ -37,11 +37,7 @@ SymbolSelectEditor::SymbolSelectEditor(SymbolControl* control, bool rotate) handleShearY = wxBitmap(rotate_image(shear,90)); handleCenter = wxBitmap(load_resource_image(_("handle_center"))); // Make sure all parts have updated bounds - FOR_EACH(p, getSymbol()->parts) { - if (SymbolShape* s = p->isSymbolShape()) { - s->calculateBounds(); - } - } + getSymbol()->calculateBounds(); resetActions(); } @@ -132,24 +128,32 @@ void SymbolSelectEditor::destroyUI(wxToolBar* tb, wxMenuBar* mb) { void SymbolSelectEditor::onUpdateUI(wxUpdateUIEvent& ev) { if (ev.GetId() >= ID_SYMBOL_COMBINE && ev.GetId() < ID_SYMBOL_COMBINE_MAX) { - if (control.selected_parts.empty()) { - ev.Check(false); - ev.Enable(false); - } else { - ev.Enable(true); - bool check = true; - FOR_EACH(p, control.selected_parts) { - if (SymbolShape* s = p->isSymbolShape()) { - if (s->combine != ev.GetId() - ID_SYMBOL_COMBINE) { - check = false; - break; - } - } // disable when symmetries are selected? - } - ev.Check(check); + bool enable = false; + bool check = true; + FOR_EACH(p, control.selected_parts) { + if (SymbolShape* s = p->isSymbolShape()) { + enable = true; + if (s->combine != ev.GetId() - ID_SYMBOL_COMBINE) { + check = false; + break; + } + } // disable when symmetries are selected? } + ev.Enable(enable); + ev.Check(enable && check); } else if (ev.GetId() == ID_EDIT_DUPLICATE) { ev.Enable(!control.selected_parts.empty()); + } else if (ev.GetId() == ID_EDIT_GROUP) { + ev.Enable(control.selected_parts.size() >= 2); + } else if (ev.GetId() == ID_EDIT_UNGROUP) { + // is a group selected + FOR_EACH(p, control.selected_parts) { + if (p->isSymbolGroup()) { + ev.Enable(true); + return; + } + } + ev.Enable(false); } else { ev.Enable(false); // we don't know about this item } @@ -165,10 +169,15 @@ void SymbolSelectEditor::onCommand(int id) { control.Refresh(false); } else if (id == ID_EDIT_DUPLICATE && !isEditing()) { // duplicate selection, not when dragging - DuplicateSymbolPartsAction* action = new DuplicateSymbolPartsAction( - *getSymbol(), control.selected_parts - ); - getSymbol()->actions.add(action); + getSymbol()->actions.add(new DuplicateSymbolPartsAction(*getSymbol(), control.selected_parts)); + control.Refresh(false); + } else if (id == ID_EDIT_GROUP && !isEditing()) { + // group selection, not when dragging + getSymbol()->actions.add(new GroupSymbolPartsAction(*getSymbol(), control.selected_parts)); + control.Refresh(false); + } else if (id == ID_EDIT_UNGROUP && !isEditing()) { + // ungroup selection, not when dragging + getSymbol()->actions.add(new UngroupSymbolPartsAction(*getSymbol(), control.selected_parts)); control.Refresh(false); } } @@ -383,6 +392,7 @@ void SymbolSelectEditor::onChar(wxKeyEvent& ev) { if (ev.GetKeyCode() == WXK_DELETE) { // delete selected parts getSymbol()->actions.add(new RemoveSymbolPartsAction(*getSymbol(), control.selected_parts)); + if (control.selected_parts.find(highlightPart) != control.selected_parts.end()) highlightPart = SymbolPartP(); // deleted it control.selected_parts.clear(); resetActions(); control.Refresh(false); @@ -441,15 +451,25 @@ double SymbolSelectEditor::angleTo(const Vector2D& pos) { } +SymbolPartP find_part(const SymbolPartP& part, const Vector2D& pos) { + if (SymbolShape* s = part->isSymbolShape()) { + if (point_in_shape(pos, *s)) return part; + } else if (SymbolSymmetry* s = part->isSymbolSymmetry()) { + // TODO + } else if (SymbolGroup* g = part->isSymbolGroup()) { + FOR_EACH(p, g->parts) { + if (find_part(p,pos)) return part; + } + } else { + throw InternalError(_("Invalid symbol part type")); + } + return SymbolPartP(); +} + SymbolPartP SymbolSelectEditor::findPart(const Vector2D& pos) { FOR_EACH(p, getSymbol()->parts) { - if (SymbolShape* s = p->isSymbolShape()) { - if (point_in_shape(pos, *s)) return p; - } else if (SymbolSymmetry* s = p->isSymbolSymmetry()) { - // TODO - } else { - throw InternalError(_("Invalid symbol part type")); - } + SymbolPartP found = find_part(p, pos); + if (found) return found; } return SymbolPartP(); } @@ -459,10 +479,8 @@ void SymbolSelectEditor::updateBoundingBox() { minV = Vector2D::infinity(); maxV = -Vector2D::infinity(); FOR_EACH(p, control.selected_parts) { - if (SymbolShape* s = p->isSymbolShape()) { - minV = piecewise_min(minV, s->min_pos); - maxV = piecewise_max(maxV, s->max_pos); - } + minV = piecewise_min(minV, p->min_pos); + maxV = piecewise_max(maxV, p->max_pos); } /* // Find rotation center center = Vector2D(0,0); diff --git a/src/gui/symbol/window.cpp b/src/gui/symbol/window.cpp index 61519fbc..98d76265 100644 --- a/src/gui/symbol/window.cpp +++ b/src/gui/symbol/window.cpp @@ -74,6 +74,9 @@ void SymbolWindow::init(Window* parent, SymbolP symbol) { menuEdit->Append(ID_EDIT_UNDO, _("undo"), _MENU_1_("undo",wxEmptyString), _HELP_("undo")); menuEdit->Append(ID_EDIT_REDO, _("redo"), _MENU_1_("redo",wxEmptyString), _HELP_("redo")); menuEdit->AppendSeparator(); + menuEdit->Append(ID_EDIT_GROUP, _("group"), _MENU_("group"), _HELP_("group")); + menuEdit->Append(ID_EDIT_UNGROUP, _("ungroup"), _MENU_("ungroup"), _HELP_("ungroup")); + menuEdit->AppendSeparator(); menuEdit->Append(ID_EDIT_DUPLICATE, _("duplicate"), _MENU_("duplicate"), _HELP_("duplicate")); menuBar->Append(menuEdit, _MENU_("edit")); @@ -82,6 +85,7 @@ void SymbolWindow::init(Window* parent, SymbolP symbol) { menuTool->Append(ID_MODE_ROTATE, _("mode_rotate"), _MENU_("rotate"), _HELP_("rotate"), wxITEM_CHECK); menuTool->Append(ID_MODE_POINTS, _("mode_curve"), _MENU_("points"), _HELP_("points"), wxITEM_CHECK); menuTool->Append(ID_MODE_SHAPES, _("circle"), _MENU_("basic shapes"), _HELP_("basic shapes"), wxITEM_CHECK); + menuTool->Append(ID_MODE_SYMMETRY, _("mode_symmetry"), _MENU_("symmetry"), _HELP_("symmetry"), wxITEM_CHECK); menuTool->Append(ID_MODE_PAINT, _("mode_paint"), _MENU_("paint"), _HELP_("paint"), wxITEM_CHECK); menuBar->Append(menuTool, _MENU_("tool")); @@ -118,11 +122,11 @@ void SymbolWindow::init(Window* parent, SymbolP symbol) { // Controls control = new SymbolControl (this, ID_CONTROL, symbol); - parts = new SymbolPartList(this, ID_PART_LIST, symbol); + parts = new SymbolPartList(this, ID_PART_LIST, control->selected_parts, symbol); // Lay out wxSizer* es = new wxBoxSizer(wxHORIZONTAL); - es->Add(em, 0, wxEXPAND | wxTOP | wxBOTTOM | wxALIGN_CENTER, 1); + es->Add(em, 1, wxEXPAND | wxTOP | wxBOTTOM | wxALIGN_CENTER, 1); emp->SetSizer(es); wxSizer* s = new wxBoxSizer(wxHORIZONTAL); @@ -246,21 +250,21 @@ void SymbolWindow::onUpdateUI(wxUpdateUIEvent& ev) { } -void SymbolWindow::onSelectFromList(wxListEvent& ev) { +void SymbolWindow::onSelectFromList(wxCommandEvent& ev) { if (inSelectionEvent) return ; inSelectionEvent = true; - parts->getSelectedParts(control->selected_parts); control->onUpdateSelection(); inSelectionEvent = false; } -void SymbolWindow::onActivateFromList(wxListEvent& ev) { - control->activatePart(control->getSymbol()->parts.at(ev.GetIndex())); +void SymbolWindow::onActivateFromList(wxCommandEvent& ev) { +//% control->activatePart(control->getSymbol()->parts.at(ev.GetIndex())); + // TODO } void SymbolWindow::onSelectFromControl() { if (inSelectionEvent) return ; inSelectionEvent = true; - parts->selectParts(control->selected_parts); + parts->Refresh(false); inSelectionEvent = false; } @@ -280,7 +284,6 @@ BEGIN_EVENT_TABLE(SymbolWindow, wxFrame) EVT_TOOL_RANGE (ID_CHILD_MIN, ID_CHILD_MAX, SymbolWindow::onExtraTool) EVT_UPDATE_UI (wxID_ANY, SymbolWindow::onUpdateUI) - EVT_LIST_ITEM_SELECTED (ID_PART_LIST, SymbolWindow::onSelectFromList) - EVT_LIST_ITEM_DESELECTED (ID_PART_LIST, SymbolWindow::onSelectFromList) - EVT_LIST_ITEM_ACTIVATED (ID_PART_LIST, SymbolWindow::onActivateFromList) + EVT_PART_SELECT (ID_PART_LIST, SymbolWindow::onSelectFromList) + EVT_PART_ACTIVATE (ID_PART_LIST, SymbolWindow::onActivateFromList) END_EVENT_TABLE () diff --git a/src/gui/symbol/window.hpp b/src/gui/symbol/window.hpp index 38fcd2ba..b18951af 100644 --- a/src/gui/symbol/window.hpp +++ b/src/gui/symbol/window.hpp @@ -14,6 +14,7 @@ #include class SymbolControl; +//%%class SymbolPartList; class SymbolPartList; DECLARE_POINTER_TYPE(SymbolValue); DECLARE_POINTER_TYPE(Set); @@ -62,9 +63,9 @@ class SymbolWindow : public Frame { void onUpdateUI(wxUpdateUIEvent&); /// Changing selected parts in the list - void onSelectFromList(wxListEvent& ev); + void onSelectFromList(wxCommandEvent& ev); /// Activating a part: open the point editor - void onActivateFromList(wxListEvent& ev); + void onActivateFromList(wxCommandEvent& ev); bool inSelectionEvent; ///< Prevent recursion in onSelect... diff --git a/src/main.cpp b/src/main.cpp index b8333306..d711d332 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,6 +96,10 @@ bool MSE::OnInit() { // Installer; install it Installer::installFrom(argv[1], true); return false; + } else if (arg == _("--symbol-editor")) { + Window* wnd = new SymbolWindow(nullptr); + wnd->Show(); + return true; } else if (arg == _("--create-installer")) { // create an installer Installer inst; diff --git a/src/render/symbol/filter.cpp b/src/render/symbol/filter.cpp index 1c065f4e..e7fd7dd9 100644 --- a/src/render/symbol/filter.cpp +++ b/src/render/symbol/filter.cpp @@ -12,6 +12,29 @@ #include #include