diff --git a/src/data/action/symbol.cpp b/src/data/action/symbol.cpp index b2111023..4508d87e 100644 --- a/src/data/action/symbol.cpp +++ b/src/data/action/symbol.cpp @@ -12,6 +12,7 @@ DECLARE_TYPEOF_COLLECTION(pair); DECLARE_TYPEOF_COLLECTION(pair); +DECLARE_TYPEOF_COLLECTION(RemoveSymbolPartsAction::Removal); DECLARE_TYPEOF_COLLECTION(SymbolPartP); DECLARE_TYPEOF_COLLECTION(ControlPointP); @@ -375,13 +376,25 @@ void AddSymbolPartAction::perform(bool to_undo) { RemoveSymbolPartsAction::RemoveSymbolPartsAction(Symbol& symbol, const set& parts) : symbol(symbol) { + check(symbol, parts); +} + +void RemoveSymbolPartsAction::check(SymbolGroup& group, const set& parts) { size_t index = 0; - FOR_EACH(p, symbol.parts) { + size_t removed = 0; + FOR_EACH(p, group.parts) { if (parts.find(p) != parts.end()) { - removals.push_back(make_pair(p, index)); // remove this part + removals.push_back(Removal(group, index, p)); // remove this part + ++ removed; + } else if (SymbolGroup* g = p->isSymbolGroup()) { + check(*g, parts); } ++index; } + if (!group.isSymbolSymmetry() && &group != &symbol) { + // remove empty groups + // TODO + } } String RemoveSymbolPartsAction::getName(bool to_undo) const { @@ -393,15 +406,15 @@ void RemoveSymbolPartsAction::perform(bool to_undo) { // reinsert the parts // ascending order, this is the reverse of removal FOR_EACH(r, removals) { - assert(r.second <= symbol.parts.size()); - symbol.parts.insert(symbol.parts.begin() + r.second, r.first); + assert(r.pos <= r.parent->parts.size()); + r.parent->parts.insert(r.parent->parts.begin() + r.pos, r.removed); } } else { // remove the parts // descending order, because earlier removals shift the rest of the vector FOR_EACH_REVERSE(r, removals) { - assert(r.second < symbol.parts.size()); - symbol.parts.erase(symbol.parts.begin() + r.second); + assert(r.pos < r.parent->parts.size()); + r.parent->parts.erase(r.parent->parts.begin() + r.pos); } } } diff --git a/src/data/action/symbol.hpp b/src/data/action/symbol.hpp index 647c3555..18d959c1 100644 --- a/src/data/action/symbol.hpp +++ b/src/data/action/symbol.hpp @@ -211,8 +211,21 @@ class RemoveSymbolPartsAction : public SymbolPartListAction { private: Symbol& symbol; - /// Removed parts and their positions, sorted by ascending pos - vector > removals; + /// Check for removals in a group + void check(SymbolGroup& group, const set& parts); + public: + /// A removal step + struct Removal { + inline Removal(SymbolGroup& parent, size_t pos, const SymbolPartP& removed) + : parent(&parent), pos(pos), removed(removed) + {} + SymbolGroup* parent; + size_t pos; + SymbolPartP removed; + }; + private: + /// Removed parts, sorted by ascending pos + vector removals; }; // ----------------------------------------------------------------------------- : Duplicate symbol parts diff --git a/src/gui/symbol/select_editor.cpp b/src/gui/symbol/select_editor.cpp index 73f4c31c..a29b4029 100644 --- a/src/gui/symbol/select_editor.cpp +++ b/src/gui/symbol/select_editor.cpp @@ -56,7 +56,7 @@ void SymbolSelectEditor::draw(DC& dc) { if (click_mode == CLICK_RECT) { // draw selection rectangle dc.SetBrush(*wxTRANSPARENT_BRUSH); - dc.SetPen(wxPen(*wxBLUE,1,wxDOT)); + dc.SetPen(wxPen(*wxCYAN,1,wxDOT)); RealRect rect = control.rotation.tr(RealRect(selection_rect_a, RealSize(selection_rect_b - selection_rect_a))); dc.DrawRectangle(rect); } else { @@ -292,13 +292,16 @@ template int snap(Event& ev) { void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) { if (click_mode == CLICK_NONE) return; - if (control.selected_parts.empty()) return; if (click_mode == CLICK_RECT) { // rectangle - control.selected_parts.selectRect(selection_rect_a, selection_rect_b, to, SELECT_TOGGLE); + if (control.selected_parts.selectRect(selection_rect_a, selection_rect_b, to)) { + control.signalSelectionChange(); + } selection_rect_b = to; control.Refresh(false); + return; } + if (control.selected_parts.empty()) return; if (!isEditing()) { // we don't have an action yet, determine what to do // note: base it on the from position, which is the position where dragging started diff --git a/src/gui/symbol/selection.cpp b/src/gui/symbol/selection.cpp new file mode 100644 index 00000000..36e0b549 --- /dev/null +++ b/src/gui/symbol/selection.cpp @@ -0,0 +1,132 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2007 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include + +DECLARE_TYPEOF_COLLECTION(SymbolPartP); + +// ----------------------------------------------------------------------------- : Selection + +void SymbolPartsSelection::setSymbol(const SymbolP& symbol) { + root = symbol.get(); + clear(); +} + +void SymbolPartsSelection::clear() { + selection.clear(); +} + +bool SymbolPartsSelection::select(const SymbolPartP& part, SelectMode mode) { + // make sure part is not the decendent of a part that is already selected + if (mode != SELECT_OVERRIDE) { + FOR_EACH(s, selection) { + if (isAncestor(s.get(), part.get())) return false; + } + } + // select + if (mode == SELECT_OVERRIDE) { + if (selection.size() == 1 && *selection.begin() == part) return false; // already selected + selection.clear(); + selection.insert(part); + } else if (mode == SELECT_IF_OUTSIDE) { + if (selected(part)) { + return false; + } else { + selection.clear(); + selection.insert(part); + } + } else { + if (selected(part)) { + selection.erase(part); + } else { + selection.insert(part); + // make part is not the ancestor of a part that is already selected + clearChildren(part.get()); + } + } + return true; +} + +SymbolShapeP SymbolPartsSelection::getAShape() const { + FOR_EACH(s, selection) { + if (s->isSymbolShape()) return static_pointer_cast(s); + } + return SymbolShapeP(); +} + +void SymbolPartsSelection::clearChildren(SymbolPart* part) { + if (SymbolGroup* g = part->isSymbolGroup()) { + FOR_EACH(p, g->parts) { + if (selected(p)) selection.erase(p); + clearChildren(p.get()); + } + } +} + +bool SymbolPartsSelection::isAncestor(SymbolPart* ancestor, SymbolPart* part) { + if (SymbolGroup* g = ancestor->isSymbolGroup()) { + FOR_EACH(p, g->parts) { + if (part == p.get()) return true; + if (isAncestor(p.get(), part)) return true; + } + } + return false; +} + +// ----------------------------------------------------------------------------- : Position based + +SymbolPartP SymbolPartsSelection::find(const SymbolPartP& part, const Vector2D& pos) const { + if (SymbolShape* s = part->isSymbolShape()) { + if (point_in_shape(pos, *s)) return part; + } + if (SymbolGroup* g = part->isSymbolGroup()) { + FOR_EACH(p, g->parts) { + SymbolPartP found = find(p, pos); + if (found) { + if (part->isSymbolSymmetry() || selected(found)) { + return found; + } else { + return part; // don't select inside groups + } + } + } + } + return SymbolPartP(); +} + +SymbolPartP SymbolPartsSelection::find(const Vector2D& position) const { + FOR_EACH(p, root->parts) { + SymbolPartP found = find(p, position); + if (found) return found; + } + return SymbolPartP(); +} + +// ----------------------------------------------------------------------------- : Rectangle based + +bool SymbolPartsSelection::selectRect(const Vector2D& a, const Vector2D& b, const Vector2D& c) { + return selectRect(*root, a, b, c); +} +bool SymbolPartsSelection::selectRect(const SymbolGroup& parent, const Vector2D& a, const Vector2D& b, const Vector2D& c) { + bool changes = false; + FOR_EACH_CONST(p, root->parts) { + bool in_ab = (p->min_pos.x >= min(a.x, b.x) && p->min_pos.y >= min(a.y, b.y) && p->max_pos.x <= max(a.x, b.x) && p->max_pos.y <= max(a.y, b.y)); + bool in_bc = (p->min_pos.x >= min(a.x, c.x) && p->min_pos.y >= min(a.y, c.y) && p->max_pos.x <= max(a.x, c.x) && p->max_pos.y <= max(a.y, c.y)); + if (in_ab != in_bc) { + select(p, SELECT_TOGGLE); + changes = true; + } else if (SymbolGroup* g = p->isSymbolGroup()) { + if (p->isSymbolSymmetry() || selected(p)) { + selectRect(*g, a, b, c); + } + } + } + return changes; +} diff --git a/src/gui/symbol/selection.hpp b/src/gui/symbol/selection.hpp new file mode 100644 index 00000000..95df0d8e --- /dev/null +++ b/src/gui/symbol/selection.hpp @@ -0,0 +1,81 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2007 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_SYMBOL_SELECTION +#define HEADER_GUI_SYMBOL_SELECTION + +// ----------------------------------------------------------------------------- : Includes + +#include + +class Vector2D; +DECLARE_POINTER_TYPE(Symbol); +DECLARE_POINTER_TYPE(SymbolPart); +DECLARE_POINTER_TYPE(SymbolShape); +class SymbolGroup; + +// ----------------------------------------------------------------------------- : Selection + +enum SelectMode +{ SELECT_OVERRIDE // give a completely new selection +, SELECT_IF_OUTSIDE // define a new selection if the affected part is not already selected +, SELECT_TOGGLE // toggle selection of affected part +}; + +/// The selected parts of a symbol, enforcing constraints +class SymbolPartsSelection { + public: + inline SymbolPartsSelection() : root(nullptr) {} + + void setSymbol(const SymbolP& symbol); + + /// Clear selection + void clear(); + /// Select a part or toggle its selection + /** Return true if the selection changed */ + bool select(const SymbolPartP& part, SelectMode mode = SELECT_OVERRIDE); + /// Toggle the selection of the parts in a rectangle (a,b) or (a,c) but not in both + /** Return true if the selection changed */ + bool selectRect(const Vector2D& a, const Vector2D& b, const Vector2D& c); + + /// Find a part by position (not just in the selection!) + /** Returns SymbolPartP() if nothing is found. + * Does not select inside groups unless the part in question is already selected. + */ + SymbolPartP find(const Vector2D& position) const; + + /// Get the selection + inline const set& get() const { return selection; } + + /// Is the selection empty? + inline bool empty() const { return selection.empty(); } + /// Number of items selected + inline size_t size() const { return selection.size(); } + /// Is a part selected? + inline bool selected(const SymbolPartP& part) const { + return selection.find(part) != selection.end(); + } + + /// Get any SymbolShape if there is one selected + SymbolShapeP getAShape() const; + + private: + Symbol* root; + set selection; + + /// Find a part, in some root + SymbolPartP find(const SymbolPartP& part, const Vector2D& pos) const; + /// Select rect for some parent + bool selectRect(const SymbolGroup& parent, const Vector2D& a, const Vector2D& b, const Vector2D& c); + + /// Make sure not both a parent and its child/decendant are selected + void clearChildren (SymbolPart* part); + /// Is a part another's ancestor? + static bool isAncestor(SymbolPart* ancestor, SymbolPart* part); +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/symbol/symmetry_editor.cpp b/src/gui/symbol/symmetry_editor.cpp new file mode 100644 index 00000000..247d8eec --- /dev/null +++ b/src/gui/symbol/symmetry_editor.cpp @@ -0,0 +1,160 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2007 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- : SymbolSymmetryEditor + +SymbolSymmetryEditor::SymbolSymmetryEditor(SymbolControl* control) + : SymbolEditorBase(control) + , mode(ID_SYMMETRY_ROTATION) + , drawing(false) +{ + control->SetCursor(*wxCROSS_CURSOR); +} + +// ----------------------------------------------------------------------------- : Drawing + +void SymbolSymmetryEditor::draw(DC& dc) { + if (symmetry) { + control.highlightPart(dc, *symmetry); + } +} + +// ----------------------------------------------------------------------------- : UI + +void SymbolSymmetryEditor::initUI(wxToolBar* tb, wxMenuBar* mb) { + copies = new wxSpinCtrl( tb, ID_COPIES, _("2"), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 2, 10, 2); + copies->SetHelpText(_HELP_("copies")); + copies->SetSize(50, -1); + tb->AddSeparator(); + tb->AddTool(ID_SYMMETRY_ROTATION, _TOOL_("rotation"), load_resource_image(_("symmetry_rotation")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("rotation"), _HELP_("rotation")); + tb->AddTool(ID_SYMMETRY_REFLECTION, _TOOL_("reflection"), load_resource_image(_("symmetry_reflection")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("reflection"), _HELP_("reflection")); + tb->AddControl(copies); + tb->Realize(); + control.SetCursor(*wxCROSS_CURSOR); + stopActions(); // set status text +} + +void SymbolSymmetryEditor::destroyUI(wxToolBar* tb, wxMenuBar* mb) { + tb->DeleteTool(ID_SYMMETRY_REFLECTION); + tb->DeleteTool(ID_SYMMETRY_ROTATION); + // HACK: hardcoded size of rest of toolbar + tb->DeleteToolByPos(7); // delete separator + tb->DeleteTool(ID_COPIES); // delete sides + #if wxVERSION_NUMBER < 2600 + delete sides; + #endif +} + +void SymbolSymmetryEditor::onUpdateUI(wxUpdateUIEvent& ev) { + if (ev.GetId() >= ID_SYMMETRY && ev.GetId() < ID_SYMMETRY_MAX) { + ev.Check(ev.GetId() == mode); + } else if (ev.GetId() == ID_COPIES) { + ev.Enable(true); + } else { + ev.Enable(false); // we don't know about this item + } +} + +void SymbolSymmetryEditor::onCommand(int id) { + if (id >= ID_SYMMETRY && id < ID_SYMMETRY_MAX) { + mode = id; + stopActions(); + } +} + +int SymbolSymmetryEditor::modeToolId() { return ID_MODE_SYMMETRY; } + +// ----------------------------------------------------------------------------- : Mouse events + +void SymbolSymmetryEditor::onLeftDown (const Vector2D& pos, wxMouseEvent& ev) { + // Start drawing + drawing = true; + center = handle = pos; + SetStatusText(_HELP_("drag to draw symmetry")); +} + +void SymbolSymmetryEditor::onLeftUp (const Vector2D& pos, wxMouseEvent& ev) { + if (drawing && symmetry) { + // Finalize the symmetry + getSymbol()->actions.add(new AddSymbolPartAction(*getSymbol(), symmetry)); + // Select the part + control.selectPart(symmetry); + // No need to clean up, this editor gets destroyed + //stopActions(); + } +} + +void SymbolSymmetryEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) { + // Resize the object + if (drawing) { + handle = to; + makePart(center, handle, ev.ControlDown(), settings.symbol_grid_snap != ev.ShiftDown()); + control.Refresh(false); + } +} + +// ----------------------------------------------------------------------------- : Other events + +void SymbolSymmetryEditor::onKeyChange(wxKeyEvent& ev) { + if (drawing) { + if (ev.GetKeyCode() == WXK_CONTROL || ev.GetKeyCode() == WXK_SHIFT) { + // changed constrains + makePart(center, handle, ev.ControlDown(), settings.symbol_grid_snap != ev.ShiftDown()); + control.Refresh(false); + } else if (ev.GetKeyCode() == WXK_ESCAPE) { + // cancel drawing + stopActions(); + } + } +} + +bool SymbolSymmetryEditor::isEditing() { return drawing; } + +// ----------------------------------------------------------------------------- : Generating shapes + +void SymbolSymmetryEditor::stopActions() { + symmetry = SymbolSymmetryP(); + drawing = false; + SetStatusText(_HELP_("draw symmetry")); + control.Refresh(false); +} + +void SymbolSymmetryEditor::makePart(Vector2D a, Vector2D b, bool constrained, bool snap) { + // snap + if (snap) { + a = snap_vector(a, settings.symbol_grid_size); + b = snap_vector(b, settings.symbol_grid_size); + } + // constrain + Vector2D dir = b - a; + if (constrained) { + double angle = atan2(dir.y, dir.x); + // multiples of 2pi/24 i.e. 24 stops + double mult = (2 * M_PI) / 24; + angle = floor(angle / mult + 0.5) * mult; + dir = Vector2D(cos(angle), sin(angle)) * dir.length(); + } + // make part + if (!symmetry) { + symmetry = new_intrusive(); + } + symmetry->kind = mode == ID_SYMMETRY_ROTATION ? SYMMETRY_ROTATION : SYMMETRY_REFLECTION; + symmetry->copies = copies->GetValue(); + symmetry->center = a; + symmetry->handle = dir; + symmetry->name = capitalize(mode == ID_SYMMETRY_ROTATION ? _TYPE_("rotation") : _TYPE_("reflection")) + + String::Format(_(" (%d)"), symmetry->copies); +} diff --git a/src/gui/symbol/symmetry_editor.hpp b/src/gui/symbol/symmetry_editor.hpp new file mode 100644 index 00000000..918afc89 --- /dev/null +++ b/src/gui/symbol/symmetry_editor.hpp @@ -0,0 +1,64 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2007 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_SYMBOL_SYMMETRY_EDITOR +#define HEADER_GUI_SYMBOL_SYMMETRY_EDITOR + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +// ----------------------------------------------------------------------------- : SymbolSymmetryEditor + +/// Editor for adding symmetries +class SymbolSymmetryEditor : public SymbolEditorBase { + public: + SymbolSymmetryEditor(SymbolControl* control); + + // --------------------------------------------------- : Drawing + + virtual void draw(DC& dc); + + // --------------------------------------------------- : UI + + virtual void initUI (wxToolBar* tb, wxMenuBar* mb); + virtual void destroyUI(wxToolBar* tb, wxMenuBar* mb); + virtual void onUpdateUI(wxUpdateUIEvent&); + virtual void onCommand(int id); + virtual int modeToolId(); + + // --------------------------------------------------- : Mouse events + + virtual void onLeftDown (const Vector2D& pos, wxMouseEvent& ev); + virtual void onLeftUp (const Vector2D& pos, wxMouseEvent& ev); + virtual void onMouseDrag (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev); + + // --------------------------------------------------- : Other events + + virtual void onKeyChange(wxKeyEvent& ev); + + virtual bool isEditing(); + + // --------------------------------------------------- : Data + private: + int mode; + SymbolSymmetryP symmetry; + bool drawing; + Vector2D center, handle; + // controls + wxSpinCtrl* copies; + + /// Cancel the drawing + void stopActions(); + + /// Make the symmetry object + void makePart(Vector2D a, Vector2D b, bool constrained, bool snap); + +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/script/parser.cpp b/src/script/parser.cpp index f9b090a0..6c70ae81 100644 --- a/src/script/parser.cpp +++ b/src/script/parser.cpp @@ -565,6 +565,8 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc } else if (minPrec <= PREC_SET && token==_(":=")) { // We made a mistake, the part before the := should be a variable name, // not an expression. Remove that instruction. + // TODO: There is a bug here: + // (if x then a else b) := c will use the 'b' as variable name Instruction instr = script.getInstructions().back(); if (instr.instr != I_GET_VAR) { input.add_error(_("Can only assign to variables"));