Commit b90e2dda authored by twanvl's avatar twanvl

Implemented selection using rectangles in symbol editor

parent 67c0e51f
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
DECLARE_TYPEOF_COLLECTION(pair<SymbolShapeP COMMA SymbolShapeCombine>); DECLARE_TYPEOF_COLLECTION(pair<SymbolShapeP COMMA SymbolShapeCombine>);
DECLARE_TYPEOF_COLLECTION(pair<SymbolPartP COMMA size_t >); DECLARE_TYPEOF_COLLECTION(pair<SymbolPartP COMMA size_t >);
DECLARE_TYPEOF_COLLECTION(RemoveSymbolPartsAction::Removal);
DECLARE_TYPEOF_COLLECTION(SymbolPartP); DECLARE_TYPEOF_COLLECTION(SymbolPartP);
DECLARE_TYPEOF_COLLECTION(ControlPointP); DECLARE_TYPEOF_COLLECTION(ControlPointP);
...@@ -375,13 +376,25 @@ void AddSymbolPartAction::perform(bool to_undo) { ...@@ -375,13 +376,25 @@ void AddSymbolPartAction::perform(bool to_undo) {
RemoveSymbolPartsAction::RemoveSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts) RemoveSymbolPartsAction::RemoveSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts)
: symbol(symbol) : symbol(symbol)
{ {
check(symbol, parts);
}
void RemoveSymbolPartsAction::check(SymbolGroup& group, const set<SymbolPartP>& parts) {
size_t index = 0; size_t index = 0;
FOR_EACH(p, symbol.parts) { size_t removed = 0;
FOR_EACH(p, group.parts) {
if (parts.find(p) != parts.end()) { 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; ++index;
} }
if (!group.isSymbolSymmetry() && &group != &symbol) {
// remove empty groups
// TODO
}
} }
String RemoveSymbolPartsAction::getName(bool to_undo) const { String RemoveSymbolPartsAction::getName(bool to_undo) const {
...@@ -393,15 +406,15 @@ void RemoveSymbolPartsAction::perform(bool to_undo) { ...@@ -393,15 +406,15 @@ void RemoveSymbolPartsAction::perform(bool to_undo) {
// reinsert the parts // reinsert the parts
// ascending order, this is the reverse of removal // ascending order, this is the reverse of removal
FOR_EACH(r, removals) { FOR_EACH(r, removals) {
assert(r.second <= symbol.parts.size()); assert(r.pos <= r.parent->parts.size());
symbol.parts.insert(symbol.parts.begin() + r.second, r.first); r.parent->parts.insert(r.parent->parts.begin() + r.pos, r.removed);
} }
} else { } else {
// remove the parts // remove the parts
// descending order, because earlier removals shift the rest of the vector // descending order, because earlier removals shift the rest of the vector
FOR_EACH_REVERSE(r, removals) { FOR_EACH_REVERSE(r, removals) {
assert(r.second < symbol.parts.size()); assert(r.pos < r.parent->parts.size());
symbol.parts.erase(symbol.parts.begin() + r.second); r.parent->parts.erase(r.parent->parts.begin() + r.pos);
} }
} }
} }
......
...@@ -211,8 +211,21 @@ class RemoveSymbolPartsAction : public SymbolPartListAction { ...@@ -211,8 +211,21 @@ class RemoveSymbolPartsAction : public SymbolPartListAction {
private: private:
Symbol& symbol; Symbol& symbol;
/// Removed parts and their positions, sorted by ascending pos /// Check for removals in a group
vector<pair<SymbolPartP, size_t> > removals; void check(SymbolGroup& group, const set<SymbolPartP>& 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<Removal> removals;
}; };
// ----------------------------------------------------------------------------- : Duplicate symbol parts // ----------------------------------------------------------------------------- : Duplicate symbol parts
......
...@@ -56,7 +56,7 @@ void SymbolSelectEditor::draw(DC& dc) { ...@@ -56,7 +56,7 @@ void SymbolSelectEditor::draw(DC& dc) {
if (click_mode == CLICK_RECT) { if (click_mode == CLICK_RECT) {
// draw selection rectangle // draw selection rectangle
dc.SetBrush(*wxTRANSPARENT_BRUSH); 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))); RealRect rect = control.rotation.tr(RealRect(selection_rect_a, RealSize(selection_rect_b - selection_rect_a)));
dc.DrawRectangle(rect); dc.DrawRectangle(rect);
} else { } else {
...@@ -292,13 +292,16 @@ template <typename Event> int snap(Event& ev) { ...@@ -292,13 +292,16 @@ template <typename Event> int snap(Event& ev) {
void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) { void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) {
if (click_mode == CLICK_NONE) return; if (click_mode == CLICK_NONE) return;
if (control.selected_parts.empty()) return;
if (click_mode == CLICK_RECT) { if (click_mode == CLICK_RECT) {
// rectangle // 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; selection_rect_b = to;
control.Refresh(false); control.Refresh(false);
return;
} }
if (control.selected_parts.empty()) return;
if (!isEditing()) { if (!isEditing()) {
// we don't have an action yet, determine what to do // 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 // note: base it on the from position, which is the position where dragging started
......
//+----------------------------------------------------------------------------+
//| 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 <gui/symbol/selection.hpp>
#include <data/symbol.hpp>
#include <gfx/bezier.hpp>
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<SymbolShape>(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;
}
//+----------------------------------------------------------------------------+
//| 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 <util/prec.hpp>
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<SymbolPartP>& 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<SymbolPartP> 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
//+----------------------------------------------------------------------------+
//| 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 <gui/symbol/symmetry_editor.hpp>
#include <gui/util.hpp>
#include <util/window_id.hpp>
#include <data/settings.hpp>
#include <data/action/symbol.hpp>
#include <data/action/symbol_part.hpp>
#include <wx/spinctrl.h>
// ----------------------------------------------------------------------------- : 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<SymbolSymmetry>();
}
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);
}
//+----------------------------------------------------------------------------+
//| 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 <util/prec.hpp>
#include <gui/symbol/editor.hpp>
// ----------------------------------------------------------------------------- : 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
...@@ -565,6 +565,8 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc ...@@ -565,6 +565,8 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc
} else if (minPrec <= PREC_SET && token==_(":=")) { } else if (minPrec <= PREC_SET && token==_(":=")) {
// We made a mistake, the part before the := should be a variable name, // We made a mistake, the part before the := should be a variable name,
// not an expression. Remove that instruction. // 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(); Instruction instr = script.getInstructions().back();
if (instr.instr != I_GET_VAR) { if (instr.instr != I_GET_VAR) {
input.add_error(_("Can only assign to variables")); input.add_error(_("Can only assign to variables"));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment