From ddfb1a5089c2c8245bf1ccfb73cc62be0f7345be Mon Sep 17 00:00:00 2001 From: twanvl Date: Sun, 1 Oct 2006 14:08:07 +0000 Subject: [PATCH] initial checkin of C++ port (in progress) git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@2 0fc631ac-6414-0410-93d0-97cfa31319b6 --- COPYING | 350 +++++++++++ TODO | 0 src/data/action/symbol.cpp | 368 +++++++++++ src/data/action/symbol.hpp | 241 ++++++++ src/data/action/symbol_part.cpp | 379 ++++++++++++ src/data/action/symbol_part.hpp | 158 +++++ src/data/symbol.cpp | 183 ++++++ src/data/symbol.hpp | 192 ++++++ src/gfx/bezier.cpp | 252 ++++++++ src/gfx/bezier.hpp | 130 ++++ src/gfx/combine_image.cpp | 139 +++++ src/gfx/gfx.hpp | 109 ++++ src/gfx/polynomial.cpp | 87 +++ src/gfx/polynomial.hpp | 43 ++ src/gfx/rotate_image.cpp | 96 +++ src/gui/symbol/basic_shape_editor.cpp | 284 +++++++++ src/gui/symbol/basic_shape_editor.hpp | 73 +++ src/gui/symbol/control.cpp | 236 +++++++ src/gui/symbol/control.hpp | 103 ++++ src/gui/symbol/editor.cpp | 16 + src/gui/symbol/editor.hpp | 91 +++ src/gui/symbol/part_list.cpp | 155 +++++ src/gui/symbol/part_list.hpp | 72 +++ src/gui/symbol/point_editor.cpp | 529 ++++++++++++++++ src/gui/symbol/point_editor.hpp | 169 +++++ src/gui/symbol/select_editor.cpp | 424 +++++++++++++ src/gui/symbol/select_editor.hpp | 117 ++++ src/gui/symbol/viewer.cpp | 220 +++++++ src/gui/symbol/viewer.hpp | 74 +++ src/gui/symbol/window.cpp | 283 +++++++++ src/gui/symbol/window.hpp | 75 +++ src/gui/util.cpp | 43 ++ src/gui/util.hpp | 29 + src/mse.sln | 29 + src/mse.vcproj | 846 ++++++++++++++++++++++++++ src/util/action_stack.cpp | 108 ++++ src/util/action_stack.hpp | 136 +++++ src/util/error.cpp | 21 + src/util/error.hpp | 60 ++ src/util/for_each.hpp | 169 +++++ src/util/index_map.hpp | 73 +++ src/util/io/reader.cpp | 127 ++++ src/util/io/reader.hpp | 165 +++++ src/util/io/writer.cpp | 108 ++++ src/util/io/writer.hpp | 131 ++++ src/util/prec.hpp | 68 +++ src/util/real_point.hpp | 116 ++++ src/util/reflect.hpp | 111 ++++ src/util/rotation.cpp | 56 ++ src/util/rotation.hpp | 123 ++++ src/util/smart_ptr.hpp | 62 ++ src/util/string.cpp | 118 ++++ src/util/string.hpp | 108 ++++ src/util/vector2d.hpp | 141 +++++ src/util/window_id.hpp | 154 +++++ tools/gen.pl | 31 + 56 files changed, 8781 insertions(+) create mode 100644 COPYING create mode 100644 TODO create mode 100644 src/data/action/symbol.cpp create mode 100644 src/data/action/symbol.hpp create mode 100644 src/data/action/symbol_part.cpp create mode 100644 src/data/action/symbol_part.hpp create mode 100644 src/data/symbol.cpp create mode 100644 src/data/symbol.hpp create mode 100644 src/gfx/bezier.cpp create mode 100644 src/gfx/bezier.hpp create mode 100644 src/gfx/combine_image.cpp create mode 100644 src/gfx/gfx.hpp create mode 100644 src/gfx/polynomial.cpp create mode 100644 src/gfx/polynomial.hpp create mode 100644 src/gfx/rotate_image.cpp create mode 100644 src/gui/symbol/basic_shape_editor.cpp create mode 100644 src/gui/symbol/basic_shape_editor.hpp create mode 100644 src/gui/symbol/control.cpp create mode 100644 src/gui/symbol/control.hpp create mode 100644 src/gui/symbol/editor.cpp create mode 100644 src/gui/symbol/editor.hpp create mode 100644 src/gui/symbol/part_list.cpp create mode 100644 src/gui/symbol/part_list.hpp create mode 100644 src/gui/symbol/point_editor.cpp create mode 100644 src/gui/symbol/point_editor.hpp create mode 100644 src/gui/symbol/select_editor.cpp create mode 100644 src/gui/symbol/select_editor.hpp create mode 100644 src/gui/symbol/viewer.cpp create mode 100644 src/gui/symbol/viewer.hpp create mode 100644 src/gui/symbol/window.cpp create mode 100644 src/gui/symbol/window.hpp create mode 100644 src/gui/util.cpp create mode 100644 src/gui/util.hpp create mode 100644 src/mse.sln create mode 100644 src/mse.vcproj create mode 100644 src/util/action_stack.cpp create mode 100644 src/util/action_stack.hpp create mode 100644 src/util/error.cpp create mode 100644 src/util/error.hpp create mode 100644 src/util/for_each.hpp create mode 100644 src/util/index_map.hpp create mode 100644 src/util/io/reader.cpp create mode 100644 src/util/io/reader.hpp create mode 100644 src/util/io/writer.cpp create mode 100644 src/util/io/writer.hpp create mode 100644 src/util/prec.hpp create mode 100644 src/util/real_point.hpp create mode 100644 src/util/reflect.hpp create mode 100644 src/util/rotation.cpp create mode 100644 src/util/rotation.hpp create mode 100644 src/util/smart_ptr.hpp create mode 100644 src/util/string.cpp create mode 100644 src/util/string.hpp create mode 100644 src/util/vector2d.hpp create mode 100644 src/util/window_id.hpp create mode 100644 tools/gen.pl diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..c48d55b1 --- /dev/null +++ b/COPYING @@ -0,0 +1,350 @@ +All or most of the source files in this distribution refer to this +file for copyright and warranty information. This file should be +included whenever those files are redistributed. + +This software is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License, version 2, as +published by the Free Software Foundation. That license is reproduced +below. + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/TODO b/TODO new file mode 100644 index 00000000..e69de29b diff --git a/src/data/action/symbol.cpp b/src/data/action/symbol.cpp new file mode 100644 index 00000000..1577ea18 --- /dev/null +++ b/src/data/action/symbol.cpp @@ -0,0 +1,368 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +typedef pair pair_part_combine_t; +typedef pair pair_part_size_t; +DECLARE_TYPEOF_COLLECTION(pair_part_combine_t); +DECLARE_TYPEOF_COLLECTION(pair_part_size_t); + +// ----------------------------------------------------------------------------- : Moving symbol parts + +SymbolPartMoveAction::SymbolPartMoveAction(const set& parts) + : parts(parts) + , constrain(false) +{} + +String SymbolPartMoveAction::getName(bool toUndo) const { + return parts.size() == 1 ? _("Move shape") : _("Move shapes"); +} + +void SymbolPartMoveAction::perform(bool toUndo) { + // move the points back + FOR_EACH(p, parts) { + p->minPos -= moved; + p->maxPos -= moved; + FOR_EACH(pnt, p->points) { + pnt->pos -= moved; + } + } + moved = -moved; +} + +void SymbolPartMoveAction::move(const Vector2D& deltaDelta) { + delta += deltaDelta; + // Move each point by deltaDelta, possibly constrained + Vector2D d = constrainVector(delta, constrain); + Vector2D dd = d - moved; + FOR_EACH(p, parts) { + p->minPos += dd; + p->maxPos += dd; + FOR_EACH(pnt, p->points) { + pnt->pos += dd; + } + } + moved = d; +} + +// ----------------------------------------------------------------------------- : Rotating symbol parts + +SymbolPartMatrixAction::SymbolPartMatrixAction(const set& parts, const Vector2D& center) + : parts(parts) + , center(center) +{} + +void SymbolPartMatrixAction::transform(const Vector2D& mx, const Vector2D& my) { + // Transform each point + FOR_EACH(p, parts) { + FOR_EACH(pnt, p->points) { + pnt->pos = (pnt->pos - center).mul(mx,my) + center; + pnt->deltaBefore = pnt->deltaBefore.mul(mx,my); + pnt->deltaAfter = pnt->deltaAfter .mul(mx,my); + } + // bounds change after transforming + p->calculateBounds(); + } +} + + +SymbolPartRotateAction::SymbolPartRotateAction(const set& parts, const Vector2D& center) + : SymbolPartMatrixAction(parts, center) + , constrain(false) + , angle(0) +{} + +String SymbolPartRotateAction::getName(bool toUndo) const { + return parts.size() == 1 ? _("Rotate shape") : _("Rotate shapes"); +} + +void SymbolPartRotateAction::perform(bool toUndo) { + // move the points back + rotateBy(-angle); + angle = -angle; +} + +void SymbolPartRotateAction::rotateTo(double newAngle) { + double oldAngle = angle; + angle = newAngle; + // constrain? + if (constrain) { + // multiples of 2pi/24 i.e. 24 stops + double mult = (2 * M_PI) / 24; + angle = floor(angle / mult + 0.5) * mult; + } + if (oldAngle != angle) rotateBy(angle - oldAngle); +} + +void SymbolPartRotateAction::rotateBy(double deltaAngle) { + // Rotation 'matrix' + transform( + Vector2D(cos(deltaAngle), -sin(deltaAngle)), + Vector2D(sin(deltaAngle), cos(deltaAngle)) + ); +} + + +// ----------------------------------------------------------------------------- : Shearing symbol parts + +SymbolPartShearAction::SymbolPartShearAction(const set& parts, const Vector2D& center) + : SymbolPartMatrixAction(parts, center) + , constrain(false) +{} + +String SymbolPartShearAction::getName(bool toUndo) const { + return parts.size() == 1 ? _("Shear shape") : _("Shear shapes"); +} + +void SymbolPartShearAction::perform(bool toUndo) { + // move the points back + // the vector shear = (x,y) is used as: + // <1 x> + // + // inverse is: + // <1 -x> / + // <-y 1> / (1 - xy) + // we have: xy = 0 => (1 - xy) = 1 + shearBy(-shear); +} + +void SymbolPartShearAction::move(const Vector2D& deltaShear) { + shear += deltaShear; + shearBy(deltaShear); +} + +void SymbolPartShearAction::shearBy(const Vector2D& shear) { + // Shear 'matrix' + transform( + Vector2D(1, shear.x), + Vector2D(shear.y, 1) + ); +} + + +// ----------------------------------------------------------------------------- : Scaling symbol parts + + +SymbolPartScaleAction::SymbolPartScaleAction(const set& parts, int scaleX, int scaleY) + : parts(parts) + , constrain(false) + , scaleX(scaleX), scaleY(scaleY) +{ + // Find min and max coordinates + oldMin = Vector2D::infinity(); + Vector2D oldMax = -Vector2D::infinity(); + FOR_EACH(p, parts) { + oldMin = piecewise_min(oldMin, p->minPos); + oldMax = piecewise_max(oldMax, p->maxPos); + } + // new == old + newMin = newRealMin = oldMin; + newSize = newRealSize = oldSize = oldMax - oldMin; +} + +String SymbolPartScaleAction::getName(bool toUndo) const { + return parts.size() == 1 ? _("Scale shape") : _("Scale shapes"); +} + +void SymbolPartScaleAction::perform(bool toUndo) { + swap(oldMin, newMin); + swap(oldSize, newSize); + transformAll(); +} + +void SymbolPartScaleAction::move(const Vector2D& deltaMin, const Vector2D& deltaMax) { + newRealMin += deltaMin; + newRealSize += deltaMax - deltaMin; + update(); +} + +void SymbolPartScaleAction::update() { + // Move each point so the range maps to + // we have already moved to the current + Vector2D tmpMin = oldMin, tmpSize = oldSize; // the size before any scaling + oldMin = newMin; oldSize = newSize; // the size before this move + // the size after the move + newMin = newRealMin; newSize = newRealSize; + if (constrain && scaleX != 0 && scaleY != 0) { + Vector2D scale = newSize.div(tmpSize); + scale = constrainVector(scale, true, true); + newSize = tmpSize.mul(scale); + newMin += (newRealSize - newSize).mul(Vector2D(scaleX == -1 ? 1 : 0, scaleY == -1 ? 1 : 0)); + } + // now move all points + transformAll(); + // restore oldMin/Size + oldMin = tmpMin; oldSize = tmpSize; +} + +void SymbolPartScaleAction::transformAll() { + Vector2D scale = newSize.div(oldSize); + FOR_EACH(p, parts) { + p->minPos = transform(p->minPos); + p->maxPos = transform(p->maxPos); + // make sure that max >= min + if (p->minPos.x > p->maxPos.x) swap(p->minPos.x, p->maxPos.x); + if (p->minPos.y > p->maxPos.y) swap(p->minPos.y, p->maxPos.y); + // scale all points + FOR_EACH(pnt, p->points) { + pnt->pos = transform(pnt->pos); + // also scale handles + pnt->deltaBefore = pnt->deltaBefore.mul(scale); + pnt->deltaAfter = pnt->deltaAfter .mul(scale); + } + } +} + +// ----------------------------------------------------------------------------- : Change combine mode + +CombiningModeAction::CombiningModeAction(const set& parts, SymbolPartCombine mode) { + FOR_EACH(p, parts) { + this->parts.push_back(make_pair(p,mode)); + } +} + +String CombiningModeAction::getName(bool toUndo) const { + return _("Change combine mode"); +} + +void CombiningModeAction::perform(bool toUndo) { + FOR_EACH(pm, parts) { + swap(pm.first->combine, pm.second); + } +} + +// ----------------------------------------------------------------------------- : Change name + +SymbolPartNameAction::SymbolPartNameAction(const SymbolPartP& part, const String& name) + : part(part), partName(name) +{} + +String SymbolPartNameAction::getName(bool toUndo) const { + return _("Change shape name"); +} + +void SymbolPartNameAction::perform(bool toUndo) { + swap(part->name, partName); +} + +// ----------------------------------------------------------------------------- : Add symbol part + +AddSymbolPartAction::AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part) + : symbol(symbol), part(part) +{} + +String AddSymbolPartAction::getName(bool toUndo) const { + return _("Add ") + part->name; +} + +void AddSymbolPartAction::perform(bool toUndo) { + if (toUndo) { + assert(!symbol.parts.empty()); + symbol.parts.erase (symbol.parts.begin()); + } else { + symbol.parts.insert(symbol.parts.begin(), part); + } +} + +// ----------------------------------------------------------------------------- : Remove symbol part + +RemoveSymbolPartsAction::RemoveSymbolPartsAction(Symbol& symbol, const set& parts) + : symbol(symbol) +{ + size_t index = 0; + FOR_EACH(p, symbol.parts) { + if (parts.find(p) != parts.end()) { + removals.push_back(make_pair(p, index)); // remove this part + } + ++index; + } +} + +String RemoveSymbolPartsAction::getName(bool toUndo) const { + return removals.size() == 1 ? _("Remove shape") : _("Remove shapes"); +} + +void RemoveSymbolPartsAction::perform(bool toUndo) { + if (toUndo) { + // 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); + } + } 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); + } + } +} + +// ----------------------------------------------------------------------------- : Duplicate symbol parts + +DuplicateSymbolPartsAction::DuplicateSymbolPartsAction(Symbol& symbol, const set& parts) + : symbol(symbol) +{ + UInt index = 0; + FOR_EACH(p, symbol.parts) { + index += 1; + if (parts.find(p) != parts.end()) { + // duplicate this part + duplications.push_back(make_pair(p->clone(), index)); + index += 1; // the clone also takes up space on the vector + } + } +} + +String DuplicateSymbolPartsAction::getName(bool toUndo) const { + return duplications.size() == 1 ? _("Duplicate shape") : _("Duplicate shapes"); +} + +void DuplicateSymbolPartsAction::perform(bool toUndo) { + if (toUndo) { + // remove the clones + // walk in reverse order, otherwise we will shift the vector + FOR_EACH_REVERSE(d, duplications) { + assert(d.second < symbol.parts.size()); + symbol.parts.erase(symbol.parts.begin() + d.second); + } + } else { + // insert the clones + FOR_EACH(d, duplications) { + assert(d.second <= symbol.parts.size()); + symbol.parts.insert(symbol.parts.begin() + d.second, d.first); + } + } +} + +void DuplicateSymbolPartsAction::getParts(set& parts) { + parts.clear(); + FOR_EACH(d, duplications) { + parts.insert(d.first); + } +} + +// ----------------------------------------------------------------------------- : Reorder symbol parts + +ReorderSymbolPartsAction::ReorderSymbolPartsAction(Symbol& symbol, size_t partId1, size_t partId2) + : symbol(symbol), partId1(partId1), partId2(partId2) +{} + +String ReorderSymbolPartsAction::getName(bool toUndo) const { + return _("Reorder"); +} + +void ReorderSymbolPartsAction::perform(bool toUndo) { + assert(partId1 < symbol.parts.size()); + assert(partId2 < symbol.parts.size()); + swap(symbol.parts[partId1], symbol.parts[partId2]); +} diff --git a/src/data/action/symbol.hpp b/src/data/action/symbol.hpp new file mode 100644 index 00000000..3ab1977d --- /dev/null +++ b/src/data/action/symbol.hpp @@ -0,0 +1,241 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_DATA_ACTION_SYMBOL +#define HEADER_DATA_ACTION_SYMBOL + +/** @file data/action/symbol.hpp + * + * Actions operating on Symbols or whole SymbolParts. + */ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include + +// ----------------------------------------------------------------------------- : Transform symbol part + +/// Anything that changes a part +class SymbolPartAction : public Action {}; + +/// 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 { + public: + SymbolPartMoveAction(const set& parts); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + /// Update this action to move some more + void move(const Vector2D& delta); + + private: + set parts; //^ Parts to move + Vector2D delta; //^ How much to move + Vector2D moved; //^ How much has been moved + public: + bool constrain; //^ Constrain movement? +}; + +// ----------------------------------------------------------------------------- : Rotating symbol parts + +/// Transforming symbol parts using a matrix +class SymbolPartMatrixAction : public SymbolPartAction { + public: + SymbolPartMatrixAction(const set& parts, const Vector2D& center); + + /// Update this action to move some more + void move(const Vector2D& delta); + + protected: + /// Perform the transformation using the given matrix + void transform(const Vector2D& mx, const Vector2D& my); + + set parts; //^ Parts to transform + Vector2D center; //^ Center to transform around +}; + +/// Rotate some symbol parts +class SymbolPartRotateAction : public SymbolPartMatrixAction { + public: + SymbolPartRotateAction(const set& parts, const Vector2D& center); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + /// Update this action to rotate to a different angle + void rotateTo(double newAngle); + + /// Update this action to rotate by a deltaAngle + void rotateBy(double deltaAngle); + + private: + double angle; //^ How much to rotate? + public: + bool constrain; //^ Constrain movement? +}; + + +// ----------------------------------------------------------------------------- : Shearing symbol parts + +/// Shear some symbol parts +class SymbolPartShearAction : public SymbolPartMatrixAction { + public: + SymbolPartShearAction(const set& parts, const Vector2D& center); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + /// Change shear by a given amount + void move(const Vector2D& deltaShear); + + private: + Vector2D shear; //^ Shearing, shear.x == 0 || shear.y == 0 + void shearBy(const Vector2D& shear); + public: + bool constrain; //^ Constrain movement? +}; + + +// ----------------------------------------------------------------------------- : Scaling symbol parts + +/// Scale some symbol parts +class SymbolPartScaleAction : public SymbolPartAction { + public: + SymbolPartScaleAction(const set& parts, int scaleX, int scaleY); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + /// Change min and max coordinates + void move(const Vector2D& deltaMin, const Vector2D& deltaMax); + /// Update the action's effect + void update(); + + private: + set parts; //^ Parts to scale + Vector2D oldMin, oldSize; //^ the original pos/size + Vector2D newRealMin, newRealSize; //^ the target pos/sizevoid shearBy(const Vector2D& shear) + Vector2D newMin, newSize; //^ the target pos/size after applying constrains + int scaleX, scaleY; //^ to what corner are we attached? + /// Transform everything in the parts + void transformAll(); + /// Transform a single vector + inline Vector2D transform(const Vector2D& v) { + return (v - oldMin).div(oldSize).mul(newSize) + newMin; + } + public: + bool constrain; //^ Constrain movement? +}; + +// ----------------------------------------------------------------------------- : Change combine mode + +/// Change the name of a symbol part +class CombiningModeAction : public SymbolPartListAction { + public: + CombiningModeAction(const set& parts, SymbolPartCombine mode); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + private: + vector > parts; //^ Affected parts with new combining modes +}; + +// ----------------------------------------------------------------------------- : Change name + +/// Change the name of a symbol part +class SymbolPartNameAction : public SymbolPartListAction { + public: + SymbolPartNameAction(const SymbolPartP& part, const String& name); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + private: + SymbolPartP part; //^ Affected part + String partName; //^ New name +}; + +// ----------------------------------------------------------------------------- : Add symbol part + +/// Adding a part to a symbol, added at the front of the list +/// front = drawn on top +class AddSymbolPartAction : public SymbolPartListAction { + public: + AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + private: + Symbol& symbol; //^ Symbol to add the part to + SymbolPartP part; //^ Part to add +}; + +// ----------------------------------------------------------------------------- : Remove symbol part + +/// Removing parts from a symbol +class RemoveSymbolPartsAction : public SymbolPartListAction { + public: + RemoveSymbolPartsAction(Symbol& symbol, const set& parts); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + private: + Symbol& symbol; + /// Removed parts and their positions, sorted by ascending pos + vector > removals; +}; + +// ----------------------------------------------------------------------------- : Duplicate symbol parts + +/// Duplicating parts in a symbol +class DuplicateSymbolPartsAction : public SymbolPartListAction { + public: + DuplicateSymbolPartsAction(Symbol& symbol, const set& parts); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + /// Fill a set with all the new parts + void getParts(set& parts); + + private: + Symbol& symbol; + /// Duplicates of parts and their positions, sorted by ascending pos + vector > duplications; +}; + + +// ----------------------------------------------------------------------------- : Reorder symbol parts + +/// Change the position of a part in a symbol. +/// This is done by swapping two parts. +class ReorderSymbolPartsAction : public SymbolPartListAction { + public: + ReorderSymbolPartsAction(Symbol& symbol, size_t partId1, size_t partId2); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + private: + Symbol& symbol; //^ Symbol to swap the parts in + public: + size_t partId1, partId2; //^ Indeces of parts to swap +}; + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/data/action/symbol_part.cpp b/src/data/action/symbol_part.cpp new file mode 100644 index 00000000..5875e946 --- /dev/null +++ b/src/data/action/symbol_part.cpp @@ -0,0 +1,379 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +DECLARE_TYPEOF_COLLECTION(Vector2D); + +// ----------------------------------------------------------------------------- : Utility + +inline double sgn(double v) { return v > 0 ? 1 : -1; } + +Vector2D constrainVector(const Vector2D& v, bool constrain, bool onlyDiagonal) { + if (!constrain) return v; + double ax = abs(v.x), ay = abs(v.y); + if (ax * 2 < ay && !onlyDiagonal) { + return Vector2D(0, v.y); // vertical + } else if(ay * 2 < ax && !onlyDiagonal) { + return Vector2D(v.x, 0); // horizontal + } else { + return Vector2D( // diagonal + sgn(v.x) * (ax + ay) / 2, + sgn(v.y) * (ax + ay) / 2 + ); + } +} + +// ----------------------------------------------------------------------------- : Move control point + +ControlPointMoveAction::ControlPointMoveAction(const set& points) + : points(points) + , constrain(false) +{ + oldValues.reserve(points.size()); + FOR_EACH(p, points) { + oldValues.push_back(p->pos); + } +} + +String ControlPointMoveAction::getName(bool toUndo) const { + return points.size() == 1 ? _("Move point") : _("Move points"); +} + +void ControlPointMoveAction::perform(bool toUndo) { + /* + set::const_iterator it = points.begin(); + vector ::iterator it2 = oldValues.begin(); + for( ; it != points.end() && it2 != oldValues.end() ; ++it, ++it2) { + swap((*it)->pos, *it2); + } + */ + FOR_EACH_2(p,points, op,oldValues) { + swap(p->pos, op); + } +} + +void ControlPointMoveAction::move (const Vector2D& deltaDelta) { + delta += deltaDelta; + // Move each point by delta, possibly constrained + Vector2D d = constrainVector(delta, constrain); + set::const_iterator it = points.begin(); + vector ::iterator it2 = oldValues.begin(); + for( ; it != points.end() && it2 != oldValues.end() ; ++it, ++it2) { + (*it)->pos = (*it2) + d; + } +} + + +// ----------------------------------------------------------------------------- : Move handle + +HandleMoveAction::HandleMoveAction(const SelectedHandle& handle) + : handle(handle) + , constrain(false) + , oldHandle(handle.getHandle()) + , oldOther (handle.getOther()) +{} + +String HandleMoveAction::getName(bool toUndo) const { + return _("Move handle"); +} + +void HandleMoveAction::perform(bool toUndo) { + swap(oldHandle, handle.getHandle()); + swap(oldOther, handle.getOther()); +} + +void HandleMoveAction::move(const Vector2D& deltaDelta) { + delta += deltaDelta; + handle.getHandle() = constrainVector(oldHandle + delta, constrain); + handle.getOther() = oldOther; + handle.onUpdateHandle(); +} + + +// ----------------------------------------------------------------------------- : Segment mode + +ControlPointUpdate::ControlPointUpdate(const ControlPointP& pnt) + : other(*pnt) + , point(pnt) +{} + +void ControlPointUpdate::perform() { + swap(other, *point); +} + + +SegmentModeAction::SegmentModeAction(const ControlPointP& p1, const ControlPointP& p2, SegmentMode mode) + : point1(p1), point2(p2) +{ + if (p1->segmentAfter == mode) return; + point1.other.segmentAfter = point2.other.segmentBefore = mode; + if (mode == SEGMENT_LINE) { + point1.other.deltaAfter = Vector2D(0,0); + point2.other.deltaBefore = Vector2D(0,0); + point1.other.lock = LOCK_FREE; + point2.other.lock = LOCK_FREE; + } else if (mode == SEGMENT_CURVE) { + point1.other.deltaAfter = (p2->pos - p1->pos) / 3.0f; + point2.other.deltaBefore = (p1->pos - p2->pos) / 3.0f; + } +} +String SegmentModeAction::getName(bool toUndo) const { + SegmentMode mode = toUndo ? point1.point->segmentAfter : point1.other.segmentAfter; + if (mode == SEGMENT_LINE) return _("Convert to line"); + else return _("Convert to curve"); +} + +void SegmentModeAction::perform(bool toUndo) { + point1.perform(); + point2.perform(); +} + + +// ----------------------------------------------------------------------------- : Locking mode + +LockModeAction::LockModeAction(const ControlPointP& p, LockMode lock) + : point(p) +{ + point.other.lock = lock; + point.other.onUpdateLock(); +} + +String LockModeAction::getName(bool toUndo) const { + return _("Lock point"); +} + +void LockModeAction::perform(bool toUndo) { + point.perform(); +} + + +// ----------------------------------------------------------------------------- : Move curve + +CurveDragAction::CurveDragAction(const ControlPointP& point1, const ControlPointP& point2) + : SegmentModeAction(point1, point2, SEGMENT_CURVE) +{} + +String CurveDragAction::getName(bool toUndo) const { + return _("Move curve"); +} + +void CurveDragAction::perform(bool toUndo) { + SegmentModeAction::perform(toUndo); +} + +void CurveDragAction::move(const Vector2D& delta, double t) { + // Logic: + // Assuming old point is p, new point is p' + // Point on old bezier curve is: + // p = a t^3 + 3b (1-t) t^2 + 3c (1-t)^2 t + d (1-t)^2 + // Point on new bezier curve is: + // p_(' = a t^3 + 3b') (1-t) t^2 + 3c' (1-t)^2 t + d (1-t)^2 + // We now want to change control points b and c, the closer we are to b (t close to 0) + // the more effect we have on b, so we substitute: + // b' = b + x t + // c' = c + x (1-t) + // Solving for x we get: + // x = (p'-p) / ( t (1-t) ( t^2 + (1-t)^2) ) + // Naming: + // delta = p' - p + // pointDelta = x * t * (1-t) + Vector2D pointDelta = delta / (3 * (t * t + (1-t) * (1-t))); + point1.point->deltaAfter += pointDelta / t; + point2.point->deltaBefore += pointDelta / (1-t); + point1.point->onUpdateHandle(HANDLE_AFTER); + point2.point->onUpdateHandle(HANDLE_BEFORE); +} + + +// ----------------------------------------------------------------------------- : Add control point + +ControlPointAddAction::ControlPointAddAction(const SymbolPartP& part, UInt insertAfter, double t) + : point1(part->getPoint(insertAfter)) + , point2(part->getPoint(insertAfter + 1)) + , part(part) + , insertAfter(insertAfter) + , newPoint(new ControlPoint()) +{ + // calculate new point + if (point1.other.segmentAfter == SEGMENT_CURVE) { + // calculate new handles using de Casteljau's subdivision algorithm + deCasteljau(point1.other, point2.other, t, *newPoint); + // unlock if needed + if (point1.other.lock == LOCK_SIZE) point1.other.lock = LOCK_DIR; + if (point2.other.lock == LOCK_SIZE) point2.other.lock = LOCK_DIR; + newPoint->lock = LOCK_DIR; + newPoint->segmentBefore = SEGMENT_CURVE; + newPoint->segmentAfter = SEGMENT_CURVE; + } else { + newPoint->pos = point1.other.pos * (1 - t) + point2.other.pos * t; + newPoint->lock = LOCK_FREE; + newPoint->segmentBefore = SEGMENT_LINE; + newPoint->segmentAfter = SEGMENT_LINE; + } +} + +String ControlPointAddAction::getName(bool toUndo) const { + return _("Add control point"); +} + +void ControlPointAddAction::perform(bool toUndo) { + if (toUndo) { // remove the point + part->points.erase( part->points.begin() + insertAfter + 1); + } else { + part->points.insert(part->points.begin() + insertAfter + 1, newPoint); + } + // update points before/after + point1.perform(); + point2.perform(); +} + +// ----------------------------------------------------------------------------- : Remove control point + +/// Sqaure root that caries the sign over the root +/// or formally: ssqrt(x) = Re - Im = x / sqrt(|x|) +double ssqrt(double x) { + if (x > 0) return sqrt(x); + else return -sqrt(-x); +} + +// Remove a single control point +class SinglePointRemoveAction : public Action { + public: + SinglePointRemoveAction(const SymbolPartP& part, UInt position); + + virtual String getName(bool toUndo) const { return _("Delete point"); } + virtual void perform(bool toUndo); + + private: + SymbolPartP part; + UInt position; + ControlPointP point; //^ Removed point + ControlPointUpdate point1, point2; //^ Points before/after +}; + +SinglePointRemoveAction::SinglePointRemoveAction(const SymbolPartP& part, UInt position) + : part(part) + , position(position) + , point (part->getPoint(position - 1)) + , point1(part->getPoint(position - 1)) + , point2(part->getPoint(position + 1)) +{ + if (point1.other.segmentAfter == SEGMENT_CURVE || point2.other.segmentBefore == SEGMENT_CURVE) { + // try to preserve curve + Vector2D before = point->deltaBefore; + Vector2D after = point->deltaAfter; + + // convert both segments to curves first + if (point1.other.segmentAfter != SEGMENT_CURVE) { + point1.other.deltaAfter = - + before = (point1.other.pos - point->pos) / 3.0; + point1.other.segmentAfter = SEGMENT_CURVE; + } + if (point2.other.segmentBefore != SEGMENT_CURVE) { + point2.other.deltaBefore = - + after = (point2.other.pos - point->pos) / 3.0; + point2.other.segmentBefore = SEGMENT_CURVE; + } + + // The inverse of adding a point, reconstruct the original handles + // before being subdivided using de Casteljau's algorithm + // length of handles + double bl = before.length() + 0.00000001; // prevent division by 0 + double al = after .length() + 0.00000001; + double totl = bl + al; + // set new handle sizes + point1.other.deltaAfter *= totl / bl; + point2.other.deltaBefore *= totl / al; + + // Also take in acount cases where the point does not correspond to a freshly added point. + // distance from the point to the curve as it would be in the above case can be used, + // in the case of a point just added this distance = 0 + BezierCurve c(point1.other, point2.other); + double t = bl / totl; + Vector2D p = c.pointAt(t); + Vector2D distP = point->pos - p; + // adjust handle sizes + point1.other.deltaAfter *= ssqrt(distP.dot(point1.other.deltaAfter) /point1.other.deltaAfter.lengthSqr()) + 1; + point2.other.deltaBefore *= ssqrt(distP.dot(point2.other.deltaBefore)/point2.other.deltaBefore.lengthSqr()) + 1; + + // unlock if needed + if (point1.other.lock == LOCK_SIZE) point1.other.lock = LOCK_DIR; + if (point2.other.lock == LOCK_SIZE) point2.other.lock = LOCK_DIR; + } else { + // just lines, keep it that way + } +} + +void SinglePointRemoveAction::perform(bool toUndo) { + if (toUndo) { + // reinsert the point + part->points.insert(part->points.begin() + position, point); + } else { + // remove the point + part->points.erase( part->points.begin() + position); + } + // update points around removed point + point1.perform(); + point2.perform(); +} + +DECLARE_POINTER_TYPE(SinglePointRemoveAction); +DECLARE_TYPEOF_COLLECTION(SinglePointRemoveActionP); + + +/// Remove a set of points from a symbol part +/// Internally represented as a list of Single Point Remove Actions +/// Not all points mat be removed, at least two points must remain +class ControlPointRemoveAction : public Action { + public: + ControlPointRemoveAction(const SymbolPartP& part, const set& toDelete); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + private: + vector removals; +}; + +ControlPointRemoveAction::ControlPointRemoveAction(const SymbolPartP& part, const set& toDelete) { + int index = 0; + // find points to remove, in reverse order + FOR_EACH(point, part->points) { + if (toDelete.find(point) != toDelete.end()) { + // remove this point + removals.push_back(new_shared2(part, index)); + } + ++index; + } +} + +String ControlPointRemoveAction::getName(bool toUndo) const { + return removals.size() == 1 ? _("Delete point") : _("Delete points"); +} + +void ControlPointRemoveAction::perform(bool toUndo) { + if (toUndo) { + FOR_EACH(r, removals) r->perform(toUndo); + } else { + // in reverse order, because positions of later points will + // change after removal of earlier points. + FOR_EACH_REVERSE(r, removals) r->perform(toUndo); + } +} + + +Action* controlPointRemoveAction(const SymbolPartP& part, const set& toDelete) { + if (part->points.size() - toDelete.size() < 2) { + // TODO : remove part? + //new_shared(part); + return 0; // no action + } else { + return new ControlPointRemoveAction(part, toDelete); + } +} diff --git a/src/data/action/symbol_part.hpp b/src/data/action/symbol_part.hpp new file mode 100644 index 00000000..d0f1e637 --- /dev/null +++ b/src/data/action/symbol_part.hpp @@ -0,0 +1,158 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_DATA_ACTION_SYMBOL_PART +#define HEADER_DATA_ACTION_SYMBOL_PART + +/** @file data/action/symbol_part.hpp + * + * Actions operating on the insides of SymbolParts (ControlPoints and the like). + */ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include + +// ----------------------------------------------------------------------------- : Utility + +/// Constrain a vector to be horizontal, vertical or diagonal +/// If constraint==false does nothing +Vector2D constrainVector(const Vector2D& v, bool constrain = true, bool onlyDiagonal = false); + +// ----------------------------------------------------------------------------- : Move control point + +/// Moving a control point in a symbol +class ControlPointMoveAction : public Action { + public: + ControlPointMoveAction(const set& points); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + /// Update this action to move some more + void move(const Vector2D& delta); + + private: + set points; //^ Points to move + vector oldValues; //^ Their old positions + Vector2D delta; //^ Amount we moved + public: + bool constrain; //^ Constrain movement? +}; + +// ----------------------------------------------------------------------------- : Move handle + +/// Moving a handle(before/after) of a control point in a symbol +class HandleMoveAction : public Action { + public: + HandleMoveAction(const SelectedHandle& handle); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + /// Update this action to move some more + void move(const Vector2D& delta); + + private: + SelectedHandle handle; //^ The handle to move + Vector2D oldHandle; //^ Old value of this handle + Vector2D oldOther; //^ Old value of other handle, needed for contraints + Vector2D delta; //^ Amount we moved + public: + bool constrain; //^ Constrain movement? +}; + +// ----------------------------------------------------------------------------- : Segment mode + +/// Utility class to update a control point +class ControlPointUpdate { + public: + ControlPointUpdate(const ControlPointP& pnt); + + /// Perform or undo an update on this control point + void perform(); + + /// Other value that is swapped with the current one. + /// Should be changed to make perform have an effect + ControlPoint other; + /// The point that is to be changed, should not be updated before perform() + ControlPointP point; +}; + + +/// Changing a line to a curve and vice versa +class SegmentModeAction : public Action { + public: + SegmentModeAction(const ControlPointP& p1, const ControlPointP& p2, SegmentMode mode); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + protected: + ControlPointUpdate point1, point2; +}; + +// ----------------------------------------------------------------------------- : Locking mode + +/// Locking a control point +class LockModeAction : public Action { + public: + LockModeAction(const ControlPointP& p, LockMode mode); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + private: + ControlPointUpdate point; //^ The affected point +}; + +// ----------------------------------------------------------------------------- : Move curve + +/// Dragging a curve; also coverts lines to curves +/** Inherits from SegmentModeAction because it also has that effect + */ +class CurveDragAction : public SegmentModeAction { + public: + CurveDragAction(const ControlPointP& point1, const ControlPointP& point2); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + // Move the curve by this much, it is grabbed at time t + void move(const Vector2D& delta, double t); +}; + +// ----------------------------------------------------------------------------- : Add control point + +/// Insert a new point in a symbol part +class ControlPointAddAction : public Action { + public: + /// Insert a new point in part, after position insertAfter_, at the time t on the segment + ControlPointAddAction(const SymbolPartP& part, UInt insertAfter, double t); + + virtual String getName(bool toUndo) const; + virtual void perform(bool toUndo); + + inline ControlPointP getNewPoint() const { return newPoint; } + + private: + SymbolPartP part; //^ SymbolPart we are in + ControlPointP newPoint; //^ The point to insert + UInt insertAfter; //^ Insert after index .. in the array + ControlPointUpdate point1, point2; //^ Update the points around the new point +}; + +// ----------------------------------------------------------------------------- : Remove control point + +/// Action that removes any number of points from a symbol part +/// TODO: If less then 3 points are left removes the entire part! +Action* controlPointRemoveAction(const SymbolPartP& part, const set& toDelete); + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/data/symbol.cpp b/src/data/symbol.cpp new file mode 100644 index 00000000..15e5cb4d --- /dev/null +++ b/src/data/symbol.cpp @@ -0,0 +1,183 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +// ----------------------------------------------------------------------------- : ControlPoint + +IMPLEMENT_REFLECTION_ENUM(LockMode) { + VALUE_N("free", LOCK_FREE); + VALUE_N("direction", LOCK_DIR); + VALUE_N("size", LOCK_SIZE); +} +IMPLEMENT_REFLECTION_ENUM(SegmentMode) { + VALUE_N("line", SEGMENT_LINE); + VALUE_N("curve", SEGMENT_CURVE); +} +IMPLEMENT_REFLECTION(ControlPoint) { + REFLECT_N("position", pos); + REFLECT_N("lock", lock); + REFLECT_N("line after", segmentAfter); + if (tag.reading() || segmentBefore == SEGMENT_CURVE) { + REFLECT_N("handle before", deltaBefore); + } + if (tag.reading() || segmentAfter == SEGMENT_CURVE) { + REFLECT_N("handle after", deltaAfter); + } +} + +ControlPoint::ControlPoint() + : segmentBefore(SEGMENT_LINE), segmentAfter(SEGMENT_LINE) + , lock(LOCK_FREE) +{} +ControlPoint::ControlPoint(double x, double y) + : segmentBefore(SEGMENT_LINE), segmentAfter(SEGMENT_LINE) + , lock(LOCK_FREE) + , pos(x,y) +{} +ControlPoint::ControlPoint(double x, double y, double xb, double yb, double xa, double ya, LockMode lock) + : segmentBefore(SEGMENT_CURVE), segmentAfter(SEGMENT_CURVE) + , lock(lock) + , pos(x,y) + , deltaBefore(xb,yb) + , deltaAfter(xa,ya) +{} + +void ControlPoint::onUpdateHandle(WhichHandle wh) { + // One handle has changed, update only the other one + if (lock == LOCK_DIR) { + getOther(wh) = -getHandle(wh) * getOther(wh).length() / getHandle(wh).length(); + } else if (lock == LOCK_SIZE) { + getOther(wh) = -getHandle(wh); + } +} +void ControlPoint::onUpdateLock() { + // The lock has changed, avarage the handle values + if (lock == LOCK_DIR) { + // deltaBefore = x * deltaAfter + Vector2D dir = (deltaBefore - deltaAfter).normalized(); + deltaBefore = dir * deltaBefore.length(); + deltaAfter = dir * -deltaAfter.length(); + } else if (lock == LOCK_SIZE) { + // deltaBefore = -deltaAfter + deltaBefore = (deltaBefore - deltaAfter) * 0.5; + deltaAfter = -deltaBefore; + } +} + +Vector2D& ControlPoint::getHandle(WhichHandle wh) { + if (wh == HANDLE_BEFORE) { + return deltaBefore; + } else { + assert(wh == HANDLE_AFTER); + return deltaAfter; + } +} +Vector2D& ControlPoint::getOther(WhichHandle wh) { + if (wh == HANDLE_BEFORE) { + return deltaAfter; + } else { + assert(wh == HANDLE_AFTER); + return deltaBefore; + } +} + +// ----------------------------------------------------------------------------- : SymbolPart + +IMPLEMENT_REFLECTION_ENUM(SymbolPartCombine) { + VALUE_N("overlap", PART_OVERLAP); + VALUE_N("merge", PART_MERGE); + VALUE_N("subtract", PART_SUBTRACT); + VALUE_N("intersection", PART_INTERSECTION); + VALUE_N("difference", PART_DIFFERENCE); + VALUE_N("border", PART_BORDER); +} + +IMPLEMENT_REFLECTION(SymbolPart) { + REFLECT(name); + REFLECT(combine); + REFLECT_N("point", points); + // Fixes after reading + if (tag.reading()) { + // enforce constraints + enforceConstraints(); + calculateBounds(); + if (maxPos.x > 100 && maxPos.y > 100) { + // this is a <= 0.1.2 symbol, points range [0...500] instead of [0...1] + // adjust it + FOR_EACH(p, points) { + p->pos /= 500.0; + p->deltaBefore /= 500.0; + p->deltaAfter /= 500.0; + } + if (name.empty()) name = _("Shape"); + calculateBounds(); + } + } +} + +SymbolPart::SymbolPart() + : combine(PART_OVERLAP), rotationCenter(.5, .5) +{} + +SymbolPartP SymbolPart::clone() const { + SymbolPartP part = new_shared1(*this); + // also clone the control points + FOR_EACH(p, part->points) { + p = new_shared1(*p); + } + return part; +} + +void SymbolPart::enforceConstraints() { + for (int i = 0 ; i < (int)points.size() ; ++i) { + ControlPointP p1 = getPoint(i); + ControlPointP p2 = getPoint(i + 1); + p2->segmentBefore = p1->segmentAfter; + p1->onUpdateLock(); + } +} + +void SymbolPart::calculateBounds() { + minPos = Vector2D::infinity(); + maxPos = -Vector2D::infinity(); + for (int i = 0 ; i < (int)points.size() ; ++i) { + segmentBounds(*getPoint(i), *getPoint(i + 1), minPos, maxPos); + } +} + +// ----------------------------------------------------------------------------- : Symbol + +IMPLEMENT_REFLECTION(Symbol) { +//%% version? + REFLECT_N("part", parts); +} + +// ----------------------------------------------------------------------------- : SymbolView + +SymbolView::SymbolView() {} + +SymbolView::SymbolView(SymbolP symbol) + : symbol(symbol) +{ + if (symbol) symbol->actions.addListener(this); +} + +SymbolView::~SymbolView() { + if (symbol) symbol->actions.removeListener(this); +} + +void SymbolView::setSymbol(SymbolP newSymbol) { + // no longer listening to old symbol + if (symbol) symbol->actions.removeListener(this); + symbol = newSymbol; + // start listening to new symbol + if (symbol) symbol->actions.addListener(this); + onSymbolChange(); +} diff --git a/src/data/symbol.hpp b/src/data/symbol.hpp new file mode 100644 index 00000000..231d516b --- /dev/null +++ b/src/data/symbol.hpp @@ -0,0 +1,192 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_DATA_SYMBOL +#define HEADER_DATA_SYMBOL + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include + +DECLARE_POINTER_TYPE(ControlPoint); +DECLARE_POINTER_TYPE(SymbolPart); +DECLARE_POINTER_TYPE(Symbol); +DECLARE_TYPEOF_COLLECTION(ControlPointP); +DECLARE_TYPEOF_COLLECTION(SymbolPartP); + +// ----------------------------------------------------------------------------- : ControlPoint + +/// Mode of locking for control points in a bezier curve +/** Specificly: the relation between deltaBefore and deltaAfter + */ +enum LockMode +{ LOCK_FREE //^ no locking +, LOCK_DIR //^ deltaBefore = x * deltaAfter +, LOCK_SIZE //^ deltaBefore = -deltaAfter +}; + +/// Is the segment between two ControlPoints a line or a curve? +enum SegmentMode +{ SEGMENT_LINE +, SEGMENT_CURVE +}; + +/// To refer to a specific handle of a control point +enum WhichHandle +{ HANDLE_NONE = 0 //^ point is not selected +, HANDLE_MAIN +, HANDLE_BEFORE +, HANDLE_AFTER +}; + +/// A control point (corner) of a SymbolPart (polygon/bezier-gon) +class ControlPoint { + public: + Vector2D pos; //^ position of the control point itself + Vector2D deltaBefore; //^ delta to bezier control point, for curve before point + Vector2D deltaAfter; //^ delta to bezier control point, for curve after point + SegmentMode segmentBefore, segmentAfter; + LockMode lock; + + /// Default constructor + ControlPoint(); + /// Constructor for straight lines, takes only the position + ControlPoint(double x, double y); + /// Constructor for curves lines, takes postions, deltaBefore, deltaAfter and lock mode + ControlPoint(double x, double y, double xb, double yb, double xa, double ya, LockMode lock = LOCK_FREE); + + /// Must be called after deltaBefore/deltaAfter has changed, enforces lock constraints + void onUpdateHandle(WhichHandle wh); + /// Must be called after lock has changed, enforces lock constraints + void onUpdateLock(); + + /// Get a handle of this control point + Vector2D& getHandle(WhichHandle wh); + /// Get a handle of this control point that is oposite wh + Vector2D& getOther(WhichHandle wh); + + DECLARE_REFLECTION(); +}; + + +// ----------------------------------------------------------------------------- : Selected handles + +/// A specific handle of a ControlPoint +class SelectedHandle { + public: + ControlPointP point; //^ the selected point + WhichHandle handle; //^ the selected handle of the point + + // SelectedHandle + SelectedHandle() : handle(HANDLE_NONE) {} + SelectedHandle(const WhichHandle& handle) : handle(handle) { assert (handle == HANDLE_NONE); } + SelectedHandle(const ControlPointP& point, const WhichHandle& handle) : point(point), handle(handle) {} + + inline Vector2D& getHandle() const { return point->getHandle(handle); } + inline Vector2D& getOther() const { return point->getOther (handle); } + inline void onUpdateHandle() const { return point->onUpdateHandle(handle); } + + /* + bool operator == (const ControlPointP pnt) const; + + bool SelectedHandle::operator == (const ControlPointP pnt) const { return point == pnt; } + bool operator == (const WhichHandle& wh) const; + bool SelectedHandle::operator == (const WhichHandle& wh) const { return handle == wh; } + bool operator ! () const; + bool SelectedHandle::operator ! () const { return handle == handleNone; } + */ +}; + + +// ----------------------------------------------------------------------------- : SymbolPart + +/// How are symbol parts combined with parts below it? +enum SymbolPartCombine +{ PART_MERGE +, PART_SUBTRACT +, PART_INTERSECTION +, PART_DIFFERENCE +, PART_OVERLAP +, PART_BORDER +}; + +/// A single part (polygon/bezier-gon) in a Symbol +class SymbolPart { + public: + /// The points of this polygon + vector points; + /// Name/label for this part + String name; + /// How is this part combined with parts below it? + SymbolPartCombine combine; + // Center of rotation, relative to the part, when the part is scaled to [0..1] + Vector2D rotationCenter; + /// Position and size of the part + /// this is the smallest axis aligned bounding box that fits around the part + Vector2D minPos, maxPos; + + SymbolPart(); + + /// Create a clone of this symbol part + SymbolPartP clone() const; + + /// Get a control point, wraps around + inline ControlPointP getPoint(int id) const { + return points[id >= 0 ? id % points.size() : id + points.size()]; + } + + /// Enforce lock constraints + void enforceConstraints(); + + /// Calculate the position and size of the part + void calculateBounds(); + + DECLARE_REFLECTION(); +}; + + +// ----------------------------------------------------------------------------- : Symbol + +/// An editable symbol, consists of any number of SymbolParts +class Symbol { + public: + /// The parts of this symbol + vector parts; + /// Actions performed on this symbol and the parts in it + ActionStack actions; + + DECLARE_REFLECTION(); +}; + + +// ----------------------------------------------------------------------------- : SymbolView + +/// A 'view' of a symbol, is notified when the symbol is updated +class SymbolView : public ActionListener { + public: + SymbolView(); + SymbolView(SymbolP symbol); + ~SymbolView(); + + /// Get the symbol that is currently being viewed + inline SymbolP getSymbol() { return symbol; } + /// Change the symbol that is being viewed + void setSymbol(SymbolP symbol); + + protected: + /// The symbol that is currently being viewed, should not be modified directly! + SymbolP symbol; + + /// Called when the associated symbol is changed, but not when it is initially set! + virtual void onSymbolChange() {} +}; + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gfx/bezier.cpp b/src/gfx/bezier.cpp new file mode 100644 index 00000000..26353edb --- /dev/null +++ b/src/gfx/bezier.cpp @@ -0,0 +1,252 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +// ----------------------------------------------------------------------------- : Evaluation + +BezierCurve::BezierCurve(const Vector2D& p0, const Vector2D& p1, const Vector2D& p2, const Vector2D& p3) { + // calculate coefficients + c = (p1 - p0) * 3.0; + b = (p2 - p1) * 3.0 - c; + a = (p3 - p0) - c - b; + d = p0; +} + +BezierCurve::BezierCurve(const ControlPoint& p0, const ControlPoint& p3) { + // calculate coefficients + c = p0.deltaAfter * 3.0; + b = (p3.pos + p3.deltaBefore - p0.pos - p0.deltaAfter) * 3.0 - c; + a = (p3.pos - p0.pos) - c - b; + d = p0.pos; +} + + +void deCasteljau(Vector2D a1, Vector2D a2, Vector2D a3, Vector2D a4, + Vector2D& b2, Vector2D& b3, Vector2D& b4c1, + Vector2D& c2, Vector2D& c3, double t) +{ + b2 = a1 + (a2 - a1) * t; + Vector2D mid23 = a2 + (a3 - a2) * t; + c3 = a3 + (a4 - a3) * t; + b3 = b2 + (mid23 - b2) * t; + c2 = mid23 + (c3 - mid23) * t; + b4c1 = b3 + (c2 - b3) * t; +} + +void deCasteljau(ControlPoint& a, ControlPoint& b, double t, ControlPoint& mid) { + deCasteljau(a.pos, a.deltaAfter, b.deltaBefore, b.pos, t, mid); +} + +void deCasteljau(const Vector2D& a1, Vector2D& a21, Vector2D& a34, const Vector2D& a4, double t, ControlPoint& out) { + Vector2D half21 = a21 * t; + Vector2D half34 = a34 * (1-t); + Vector2D mid23 = (a1 + a21) * (1-t) + (a34 + a4) * t; + Vector2D mid23h21 = (a1 + half21) * (1-t) + mid23 * t; + Vector2D mid23h34 = (a4 + half34) * t + mid23 * (1-t); + out.pos = mid23h21 * (1-t) + mid23h34 * t; + out.deltaBefore = mid23h21 - out.pos; + out.deltaAfter = mid23h34 - out.pos; + a21 = half21; + a34 = half34; +} + +// ----------------------------------------------------------------------------- : Drawing + +void curveSubdivide(const BezierCurve& c, const Vector2D& p0, const Vector2D& p1, double t0, double t1, const Rotation& rot, vector& out, UInt level) { + if (level <= 0) return; + double midtime = (t0+t1) * 0.5f; + Vector2D midpoint = c.pointAt(midtime); + Vector2D d0 = p0 - midpoint; + Vector2D d1 = midpoint - p1; + // Determine treshold for subdivision, greater angle -> subdivide + // greater size -> subdivide + double treshold = fabs( atan2(d0.x,d0.y) - atan2(d1.x,d1.y)) * (p0-p1).lengthSqr(); + bool subdivide = treshold >= .0001; + // subdivide left + curveSubdivide(c, p0, midpoint, t0, midtime, rot, out, level - 1); + // add midpoint + if (subdivide) { + out.push_back(rot.tr(midpoint)); + } + // subdivide right + curveSubdivide(c, midpoint, p1, midtime, t1, rot, out, level - 1); +} + +void segmentSubdivide(const ControlPoint& p0, const ControlPoint& p1, const Rotation& rot, vector& out) { + assert(p0.segmentAfter == p1.segmentBefore); + // always the start + out.push_back(rot.tr(p0.pos)); + if (p0.segmentAfter == SEGMENT_CURVE) { + // need more points? + BezierCurve curve(p0,p1); + curveSubdivide(curve, p0.pos, p1.pos, 0, 1, rot, out, 5); + } +} + +// ----------------------------------------------------------------------------- : Bounds + +void segmentBounds(const ControlPoint& p1, const ControlPoint& p2, Vector2D& min, Vector2D& max) { + assert(p1.segmentAfter == p2.segmentBefore); + if (p1.segmentAfter == SEGMENT_LINE) { + lineBounds (p1.pos, p2.pos, min, max); + } else { + bezierBounds(p1, p2, min, max); + } +} + +void bezierBounds(const ControlPoint& p1, const ControlPoint& p2, Vector2D& min, Vector2D& max) { + assert(p1.segmentAfter == SEGMENT_CURVE); + // First of all, the corners should be in the bounding box + pointBounds(p1.pos, min, max); + pointBounds(p2.pos, min, max); + // Solve the derivative of the bezier curve to find its extremes + // It's only a quadtratic equation :) + BezierCurve curve(p1,p2); + double roots[4]; + UInt count; + count = solveQuadratic(3*curve.a.x, 2*curve.b.x, curve.c.x, roots); + count += solveQuadratic(3*curve.a.y, 2*curve.b.y, curve.c.y, roots + count); + // now check them for min/max + for (UInt i = 0 ; i < count ; ++i) { + double t = roots[i]; + if (t >=0 && t <= 1) { + pointBounds(curve.pointAt(t), min, max); + } + } +} + +void lineBounds(const Vector2D& p1, const Vector2D& p2, Vector2D& min, Vector2D& max) { + pointBounds(p1, min, max); + pointBounds(p2, min, max); +} + +void pointBounds(const Vector2D& p, Vector2D& min, Vector2D& max) { + min = piecewise_min(min, p); + max = piecewise_max(max, p); +} + +// Is a point inside the bounds ? +bool pointInBounds(const Vector2D& p, const Vector2D& min, const Vector2D& max) { + return p.x >= min.x && p.y >= min.y && + p.x <= max.x && p.y <= max.y; +} + + +// ----------------------------------------------------------------------------- : Point tests + +// As a point inside a symbol part? +bool pointInPart(const Vector2D& pos, const SymbolPart& part) { + // Step 1. compare bounding box of the part + if (!pointInBounds(pos, part.minPos, part.maxPos)) return false; + + // Step 2. trace ray outward, count intersections + int count = 0; + size_t size = part.points.size(); + for(size_t i = 0 ; i < size ; ++i) { + ControlPointP p1 = part.getPoint((int) i); + ControlPointP p2 = part.getPoint((int) i + 1); + if (p1->segmentAfter == SEGMENT_LINE) { + count += intersectLineRay (p1->pos, p2->pos, pos); + } else { + count += intersectBezierRay(*p1, *p2, pos); + } + } + + return count & 1; // odd number of intersections +} + + +// ----------------------------------------------------------------------------- : Finding points + +bool posOnSegment(const Vector2D& pos, double range, const ControlPoint& p1, const ControlPoint& p2, Vector2D& pOut, double& tOut) { + if (p1.segmentAfter == SEGMENT_CURVE) { + return posOnBezier(pos, range, p1, p2, pOut, tOut); + } else { + return posOnLine (pos, range, p1.pos, p2.pos, pOut, tOut); + } +} + +bool posOnBezier(const Vector2D& pos, double range, const ControlPoint& p1, const ControlPoint& p2, Vector2D& pOut, double& tOut) { + assert(p1.segmentAfter == SEGMENT_CURVE); + // Find intersections with the horizontal and vertical lines through p0 + // theoretically we would need to check in all directions, but this covers enough + BezierCurve curve(p1, p2); + double roots[6]; + UInt count; + count = solveCubic(curve.a.y, curve.b.y, curve.c.y, curve.d.y - pos.y, roots); + count += solveCubic(curve.a.x, curve.b.x, curve.c.x, curve.d.x - pos.x, roots + count); // append intersections + // take the best intersection point + double bestDistSqr = std::numeric_limits::max(); //infinity + for(UInt i = 0 ; i < count ; ++i) { + double t = roots[i]; + if (t >= 0 && t < 1) { + Vector2D pnt = curve.pointAt(t); + double distSqr = (pnt - pos).lengthSqr(); + if (distSqr < bestDistSqr) { + bestDistSqr = distSqr; + pOut = pnt; + tOut = t; + } + } + } + return bestDistSqr <= range * range; +} + +bool posOnLine(const Vector2D& pos, double range, const Vector2D& p1, const Vector2D& p2, Vector2D& pOut, double& t) { + 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 + if (t < 0 || t > 1) return false; // outside segment + pOut = p1 + p21 * t; // point on line + Vector2D dist = pOut - pos; // distance to line + return dist.lengthSqr() <= range * range; // in range? +} + +// ----------------------------------------------------------------------------- : Intersection + +UInt intersectBezierRay(const ControlPoint& p1, const ControlPoint& p2, const Vector2D& pos) { + // Looking only at the y coordinate + // we can use the cubic formula to find roots, points where the horizontal line + // through pos intersects the (extended) curve + BezierCurve curve(p1,p2); + double roots[3]; + UInt count = solveCubic(curve.a.y, curve.b.y, curve.c.y, curve.d.y - pos.y, roots); + // now check if the solutions are left of pos.x + UInt solsInRange = 0; + for(UInt i = 0 ; i < count ; ++i) { + double t = roots[i]; + if (t >= 0 && t < 1 && curve.pointAt(t).x < pos.x) { + solsInRange += 1; + } + } + return solsInRange; +} + +bool intersectLineRay(const Vector2D& p1, const Vector2D& p2, const Vector2D& pos) { + // Vector2D intersection = p1 + t * (p2 - p1) + // intersection.y == pos.y + // == p1.y + t * (p2.y - p1.y) + // => t == (pos.y - p1.y) / (p2.y - p1.y) + // intersection.x == p1.x + t * (p2.x - p1.x) + // == p1.x + (pos.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + double dy = p2.y - p1.y; + if (fabs(dy) < 0.0000000001) { + // horizontal line + return (p1.x > pos.x || p2.x > pos.x) && // starts to the left of pos + fabs(p1.y - pos.y) < 0.0000000001; // same y as pos + } else { + double dx = p2.x - p1.x; + double t = (pos.y - p1.y) / dy; + if (t < 0.0 || t >= 1.0) return false; + double intersectX = p1.x + t * dx; + return intersectX <= pos.x; // intersection is left of pos + } +} \ No newline at end of file diff --git a/src/gfx/bezier.hpp b/src/gfx/bezier.hpp new file mode 100644 index 00000000..68a88a9e --- /dev/null +++ b/src/gfx/bezier.hpp @@ -0,0 +1,130 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GFX_BEZIER +#define HEADER_GFX_BEZIER + +/** @file gfx/bezier.hpp + * + * Bezier curve and line related functions + */ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include + +// ----------------------------------------------------------------------------- : Evaluation + +/// A bezier curve for evaluation +class BezierCurve { + public: + /// coefficients of the equation (x,y) = at^3 + bt^2 + ct + d + Vector2D a, b, c, d; + + /// Construct a bezier curve evaluator given the 4 handles + BezierCurve(const Vector2D& p0, const Vector2D& p1, const Vector2D& p2, const Vector2D& p3); + + /// Construct a bezier curve evaluator given two ControlPoints at the ends + BezierCurve(const ControlPoint& p0, const ControlPoint& p3); + + /// Return the point on this curve at time t in [0...1) + inline Vector2D pointAt(double t) const { + return d + (c + (b + a * t) * t) * t; + } + + /// Return the tangent on this curve at time t in [0...1) + inline Vector2D tangentAt(double t) const { + return c + ((b * 2) + (a * 3) * t) * t; + } +}; + +/// Subdivide a curve from a to b, store the result in a control point +/** Also modifies the handles of the points to accomodate the inserted point + * Direct version, using input curve a1,a2,a3,a4 and output curves a1,b2,b3,b4 and c1,c2,c3,a4 + */ +void deCasteljau(Vector2D a1, Vector2D a2, Vector2D a3, Vector2D a4, + Vector2D& b2, Vector2D& b3, Vector2D& b4c1, + Vector2D& c2, Vector2D& c3, double t); + +/// Subdivide a curve from a to b at time t +/** Stores the point at time t in mid, updates the handles of a and b! + */ +void deCasteljau(ControlPoint& a, ControlPoint& b, double t, ControlPoint& mid); + +/// Subdivide a curve from a to b, store the result in a control point +/** Also modifies the handles of the points to accomodate the inserted point! + */ +void deCasteljau(const Vector2D& a1, Vector2D& a21, Vector2D& a34, const Vector2D& a4, double t, ControlPoint& out); + +// ----------------------------------------------------------------------------- : Drawing + +/// Devide a segment into a number of straight lines for display purposes +/** Adds the resulting corner points of those lines to out, the last point is not added. + * All points are converted to display coordinates using rot.tr + */ +void segmentSubdivide(const ControlPoint& p0, const ControlPoint& p1, const Rotation& rot, vector& out); + +// ----------------------------------------------------------------------------- : Bounds + +/// Find a bounding box that fits a segment (either a line or a bezier curve) between p1 and p2. +/** stores the results in min and max. + * min is only changed if the minimum is smaller then the current value in min, + * max only if the maximum is larger. + */ +void segmentBounds(const ControlPoint& p1, const ControlPoint& p2, Vector2D& min, Vector2D& max); + +/// Find a bounding box that fits a curve between p1 and p2, stores the results in min and max. +/** min is only changed if the minimum is smaller then the current value in min, + * max only if the maximum is larger + */ +void bezierBounds(const ControlPoint& p1, const ControlPoint& p2, Vector2D& min, Vector2D& max); + +/// Find a bounding box that fits around p1 and p2, stores the result in min and max +/** min is only changed if the minimum is smaller then the current value in min, + * max only if the maximum is larger + */ +void lineBounds(const Vector2D& p1, const Vector2D& p2, Vector2D& min, Vector2D& max); + +/// Find a bounding 'box' that fits around a single point +/** min is only changed if the minimum is smaller then the current value in min, + * max only if the maximum is larger + */ +void pointBounds(const Vector2D& p, Vector2D& min, Vector2D& max); + +// ----------------------------------------------------------------------------- : Point tests + +/// Is a point inside the given symbol part? +bool pointInPart(const Vector2D& p, const SymbolPart& part); + +// ----------------------------------------------------------------------------- : Finding points + +/// Finds the position of p0 on the line between p1 and p2, returns true if the point is on (or near) the line +/// the line between p1 and p2 can also be a bezier curve +/** Returns the time on the segment in tOut, and the point on the segment in pOut + */ +bool posOnSegment(const Vector2D& pos, double range, const ControlPoint& p1, const ControlPoint& p2, Vector2D& pOut, double& tOut); + +/// Finds the position of p0 on the line between p1 and p2, returns true if the point is on (or near) +/// the bezier curve between p1 and p2 +bool posOnBezier (const Vector2D& pos, double range, const ControlPoint& p1, const ControlPoint& p2, Vector2D& pOut, double& tOut); + +/// Finds the position of p0 on the line p1-p2, returns true if the point is withing range of the line +/// if that is the case then (x,y) = p1 + (p2-p1) * out +bool posOnLine (const Vector2D& pos, double range, const Vector2D& p1, const Vector2D& p2, Vector2D& pOut, double& tOut); + +// ----------------------------------------------------------------------------- : Intersection + +/// Counts the number of intersections between the ray/halfline from (-inf, pos.y) to pos +/// and the bezier curve between p1 and p2. +UInt intersectBezierRay(const ControlPoint& p1, const ControlPoint& p2, const Vector2D& pos); + +// Does the line between p1 and p2 intersect the ray (half line) from (-inf, pos.y) to pos? +bool intersectLineRay(const Vector2D& p1, const Vector2D& p2, const Vector2D& pos); + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gfx/combine_image.cpp b/src/gfx/combine_image.cpp new file mode 100644 index 00000000..59083e80 --- /dev/null +++ b/src/gfx/combine_image.cpp @@ -0,0 +1,139 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include "../util/prec.hpp" +#include "../util/reflect.hpp" +#include "gfx.hpp" +#include + +using namespace std; + +// ----------------------------------------------------------------------------- : Reflection for combining modes + +IMPLEMENT_REFLECTION_ENUM(ImageCombine) { + VALUE_N("normal", COMBINE_NORMAL); + VALUE_N("add", COMBINE_ADD); + VALUE_N("subtract", COMBINE_SUBTRACT); + VALUE_N("stamp", COMBINE_STAMP); + VALUE_N("difference", COMBINE_DIFFERENCE); + VALUE_N("negation", COMBINE_NEGATION); + VALUE_N("multiply", COMBINE_MULTIPLY); + VALUE_N("darken", COMBINE_DARKEN); + VALUE_N("lighten", COMBINE_LIGHTEN); + VALUE_N("color dodge", COMBINE_COLOR_DODGE); + VALUE_N("color burn", COMBINE_COLOR_BURN); + VALUE_N("screen", COMBINE_SCREEN); + VALUE_N("overlay", COMBINE_OVERLAY); + VALUE_N("hard light", COMBINE_HARD_LIGHT); + VALUE_N("soft light", COMBINE_SOFT_LIGHT); + VALUE_N("reflect", COMBINE_REFLECT); + VALUE_N("glow", COMBINE_GLOW); + VALUE_N("freeze", COMBINE_FREEZE); + VALUE_N("heat", COMBINE_HEAT); + VALUE_N("and", COMBINE_AND); + VALUE_N("or", COMBINE_OR); + VALUE_N("xor", COMBINE_XOR); + VALUE_N("shadow", COMBINE_SHADOW); +} + +// ----------------------------------------------------------------------------- : Combining functions + +// Functor for combining functions for a given combining type +template struct Combine { + static inline int f(int a, int b); +}; + +// Give a combining function for enum value 'combine' +#define COMBINE_FUN(combine,fun) \ + template <> int Combine::f(int a, int b) { return fun; } + +// Based on +// http://www.pegtop.net/delphi/articles/blendmodes/ + +COMBINE_FUN(COMBINE_NORMAL, b ) +COMBINE_FUN(COMBINE_ADD, top(a + b) ) +COMBINE_FUN(COMBINE_SUBTRACT, bot(a - b) ) +COMBINE_FUN(COMBINE_STAMP, col(a - 2 * b + 256) ) +COMBINE_FUN(COMBINE_DIFFERENCE, abs(a - b) ) +COMBINE_FUN(COMBINE_NEGATION, 255 - abs(255 - a - b) ) +COMBINE_FUN(COMBINE_MULTIPLY, (a * b) / 255 ) +COMBINE_FUN(COMBINE_DARKEN, min(a, b) ) +COMBINE_FUN(COMBINE_LIGHTEN, max(a, b) ) +COMBINE_FUN(COMBINE_COLOR_DODGE,b == 255 ? 255 : top(a * 255 / (255 - b)) ) +COMBINE_FUN(COMBINE_COLOR_BURN, b == 0 ? 0 : bot(255 - (255-a) * 255 / b) ) +COMBINE_FUN(COMBINE_SCREEN, 255 - (((255 - a) * (255 - b)) / 255) ) +COMBINE_FUN(COMBINE_OVERLAY, a < 128 + ? (a * b) >> 7 + : 255 - (((255 - a) * (255 - b)) >> 7) ) +COMBINE_FUN(COMBINE_HARD_LIGHT, b < 128 + ? (a * b) >> 7 + : 255 - (((255 - a) * (255 - b)) >> 7) ) +COMBINE_FUN(COMBINE_SOFT_LIGHT, b) +COMBINE_FUN(COMBINE_REFLECT, b == 255 ? 255 : top(a * a / (255 - b)) ) +COMBINE_FUN(COMBINE_GLOW, a == 255 ? 255 : top(b * b / (255 - a)) ) +COMBINE_FUN(COMBINE_FREEZE, b == 0 ? 0 : bot(255 - (255 - a) * (255 - a) / b) ) +COMBINE_FUN(COMBINE_HEAT, a == 0 ? 0 : bot(255 - (255 - b) * (255 - b) / a) ) +COMBINE_FUN(COMBINE_AND, a & b ) +COMBINE_FUN(COMBINE_OR, a | b ) +COMBINE_FUN(COMBINE_XOR, a ^ b ) +COMBINE_FUN(COMBINE_SHADOW, (b * a * a) / (255 * 255) ) + +// ----------------------------------------------------------------------------- : Combining + +/// Combine image b onto image a using some combining mode. +/// The results are stored in the image A. +template +void combineImageDo(Image& a, Image b) { + UInt size = a.GetWidth() * a.GetHeight() * 3; + Byte *dataA = a.GetData(), *dataB = b.GetData(); + // for each pixel: apply function + for (UInt i = 0 ; i < size ; ++i) { + dataA[i] = Combine::f(dataA[i], dataB[i]); + } +} + +void combineImage(Image& a, const Image& b, ImageCombine combine) { + // Images must have same size + assert(a.GetWidth() == b.GetWidth()); + assert(a.GetHeight() == b.GetHeight()); + // Copy alpha channel? + if (b.HasAlpha()) { + if (!a.HasAlpha()) a.InitAlpha(); + memcpy(a.GetAlpha(), b.GetAlpha(), a.GetWidth() * a.GetHeight()); + } + // Combine image data, by dispatching to combineImageDo + switch(combine) { + #define DISPATCH(comb) case comb: combineImageDo(a,b); return + DISPATCH(COMBINE_NORMAL); + DISPATCH(COMBINE_ADD); + DISPATCH(COMBINE_SUBTRACT); + DISPATCH(COMBINE_STAMP); + DISPATCH(COMBINE_DIFFERENCE); + DISPATCH(COMBINE_NEGATION); + DISPATCH(COMBINE_MULTIPLY); + DISPATCH(COMBINE_DARKEN); + DISPATCH(COMBINE_LIGHTEN); + DISPATCH(COMBINE_COLOR_DODGE); + DISPATCH(COMBINE_COLOR_BURN); + DISPATCH(COMBINE_SCREEN); + DISPATCH(COMBINE_OVERLAY); + DISPATCH(COMBINE_HARD_LIGHT); + DISPATCH(COMBINE_SOFT_LIGHT); + DISPATCH(COMBINE_REFLECT); + DISPATCH(COMBINE_GLOW); + DISPATCH(COMBINE_FREEZE); + DISPATCH(COMBINE_HEAT); + DISPATCH(COMBINE_AND); + DISPATCH(COMBINE_OR); + DISPATCH(COMBINE_XOR); + DISPATCH(COMBINE_SHADOW); + } +} + +void drawCombineImage(DC& dc, UInt x, UInt y, const Image& img, ImageCombine combine) { +} \ No newline at end of file diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp new file mode 100644 index 00000000..bd66863c --- /dev/null +++ b/src/gfx/gfx.hpp @@ -0,0 +1,109 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GFX_GFX +#define HEADER_GFX_GFX + +/** @file gfx/gfx.hpp + * + * Graphics/image processing functions. + */ + +// ----------------------------------------------------------------------------- : Includes + +#include "../util/prec.hpp" + +// ----------------------------------------------------------------------------- : Resampling + +/// Resample (resize) an image, uses bilenear filtering +/** The algorithm first resizes in horizontally, then vertically, + * the two passes are essentially the same: + * - for each row: + * - each input pixel becomes a fixed amount of output (in 1< +#include + +// ----------------------------------------------------------------------------- : Solving + +UInt solveLinear(double a, double b, double* root) { + if (a == 0) { + if (b == 0) { + root[0] = 0; + return 1; + } else { + return 0; + } + } else { + root[0] = -b / a; + return 1; + } +} + +UInt solveQuadratic(double a, double b, double c, double* roots) { + if (a == 0) { + return solveLinear(b, c, roots); + } else { + double d = b*b - 4*a*c; + if (d < 0) return 0; + roots[0] = (-b - sqrt(d)) / (2*a); + roots[1] = (-b + sqrt(d)) / (2*a); + return 2; + } +} + +UInt solveCubic(double a, double b, double c, double d, double* roots) { + if (a == 0) { + return solveQuadratic(b, c, d, roots); + } else { + return solveCubic(b/a, c/a, d/a, roots); + } +} + +// cubic root +template +inline T curt(T x) { return pow(x, 1.0 / 3); } + +UInt solveCubic(double a, double b, double c, double* roots) { + double p = b - a*a / 3; + double q = c + (2 * a*a*a - 9 * a * b) / 27; + if (p == 0 && q == 0) { + roots[0] = -a / 3; + return 1; + } + complex u; + if (q > 0) { + u = curt(q/2 + sqrt(complex(q*q / 4 + p*p*p / 27))); + } else { + u = curt(q/2 - sqrt(complex(q*q / 4 + p*p*p / 27))); + } + // now for the complex part + // rot1(1, 0) + complex rot2(-0.5, sqrt(3.0) / 2); + complex rot3(-0.5, -sqrt(3.0) / 2); + complex x1 = p / (3.0 * u) - u - a / 3.0; + complex x2 = p / (3.0 * u * rot2) - u * rot2 - a / 3.0; + complex x3 = p / (3.0 * u * rot3) - u * rot3 - a / 3.0; + // check if the solutions are real + UInt count = 0; + if (abs(x1.imag()) < 0.00001) { + roots[count] = x1.real(); + count += 1; + } + if (abs(x2.imag()) < 0.00001) { + roots[count] = x2.real(); + count += 1; + } + if (abs(x3.imag()) < 0.00001) { + roots[count] = x3.real(); + count += 1; + } + return count; +} diff --git a/src/gfx/polynomial.hpp b/src/gfx/polynomial.hpp new file mode 100644 index 00000000..ad60c0fb --- /dev/null +++ b/src/gfx/polynomial.hpp @@ -0,0 +1,43 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GFX_POLYNOMIAL +#define HEADER_GFX_POLYNOMIAL + +/** @file gfx/polynomial.hpp + * + * Solutions to polynomials, used by bezier curve algorithms + */ + +// ----------------------------------------------------------------------------- : Includes + +#include + +// ----------------------------------------------------------------------------- : Solving + +/// Solve a linear equation a x + b = 0 +/** Returns the number of real roots, and the roots themselfs in the output parameter. + */ +UInt solveLinear(double a, double b, double* root); + +/// Solve a quadratic equation a x^2 + b x + c == 0 +/** Returns the number of real roots, and the roots themselfs in the output parameter. + */ +UInt solveQuadratic(double a, double b, double c, double* roots); + +// Solve a cubic equation a x^3 + b x^2 + c x + d == 0 +/** Returns the number of real roots, and the roots themselfs in the output parameter. + */ +UInt solveCubic(double a, double b, double c, double d, double* roots); + +// Solve a cubic equation x^3 + a x^2 + b x + c == 0 +/** Returns the number of real roots, and the roots themselfs in the output parameter. + * Based on http://en.wikipedia.org/wiki/Cubic_equation + */ +UInt solveCubic(double a, double b, double c, double* roots); + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gfx/rotate_image.cpp b/src/gfx/rotate_image.cpp new file mode 100644 index 00000000..d64c8aca --- /dev/null +++ b/src/gfx/rotate_image.cpp @@ -0,0 +1,96 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include "../util/prec.hpp" +#include "gfx.hpp" + +// ----------------------------------------------------------------------------- : Implementation + +// Rotates an image +// 'Rotater' is a function object that knows how to 'rotate' a pixel coordinate +template +Image rotateImageImpl(Image img) { + UInt width = img.GetWidth(), height = img.GetHeight(); + // initialize the return image + Image ret; + Rotater::init(ret, width, height); + Byte* in = img.GetData(), *out = ret.GetData(); + // rotate each pixel + for (UInt y = 0 ; y < height ; ++y) { + for (UInt x = 0 ; x < width ; ++x) { + memcpy(out + 3 * Rotater::offset(x, y, width, height), in, 3); + in += 3; + } + } + // don't forget alpha + if (img.HasAlpha()) { + ret.InitAlpha(); + in = img.GetAlpha(); + out = ret.GetAlpha(); + for (UInt y = 0 ; y < height ; ++y) { + for (UInt x = 0 ; x < width ; ++x) { + out[Rotater::offset(x, y, width, height)] = *in; + in += 1; + } + } + } + // ret is rotated image + return ret; +} + +// ----------------------------------------------------------------------------- : Rotations + +// Function object to handle rotation +struct Rotate90 { + /// Init a rotated image, where the source is w * h pixels + inline static void init(Image& img, UInt w, UInt h) { + img.Create(h, w, false); + } + /// Offset in the target data, x, y, w, h are SOURCE coordintes + inline static int offset(UInt x, UInt y, UInt w, UInt h) { + int mx = y; + int my = w - x - 1; + return h * my + mx; // note: h, since that is the width of the target image + } +}; + +struct Rotate180 { + inline static void init(Image& img, UInt w, UInt h) { + img.Create(w, h, false); + } + inline static int offset(UInt x, UInt y, UInt w, UInt h) { + UInt mx = w - x - 1; + UInt my = h - y - 1; + return w * my + mx; + } +}; + +struct Rotate270 { + inline static void init(Image& img, UInt w, UInt h) { + img.Create(h, w, false); + } + inline static int offset(UInt x, UInt y, UInt w, UInt h) { + UInt mx = h - y - 1; + UInt my = x; + return h * my + mx; + } +}; + +// ----------------------------------------------------------------------------- : Interface + +Image rotateImageBy(const Image& image, int angle) { + if (angle == 90) { + return rotateImageImpl(image); + } else if (angle == 180){ + return rotateImageImpl(image); + } else if (angle == 270){ + return rotateImageImpl(image); + } else{ + return image; + } +} diff --git a/src/gui/symbol/basic_shape_editor.cpp b/src/gui/symbol/basic_shape_editor.cpp new file mode 100644 index 00000000..9c8710d8 --- /dev/null +++ b/src/gui/symbol/basic_shape_editor.cpp @@ -0,0 +1,284 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- : SymbolBasicShapeEditor + +SymbolBasicShapeEditor::SymbolBasicShapeEditor(SymbolControl* control) + : SymbolEditorBase(control) + , drawing(false) + , mode(ID_SHAPE_CIRCLE) +{ + control->SetCursor(*wxCROSS_CURSOR); +} + +// ----------------------------------------------------------------------------- : Drawing + +void SymbolBasicShapeEditor::draw(DC& dc) { + // highlight the part we are drawing + if (drawing) { + control.highlightPart(dc, *shape, HIGHLIGHT_BORDER); + } +} + +// ----------------------------------------------------------------------------- : UI + +void SymbolBasicShapeEditor::initUI(wxToolBar* tb, wxMenuBar* mb) { + sides = new wxSpinCtrl( tb, ID_SIDES, _("3"), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 3, 50, 3); + sidesL = new wxStaticText(tb, ID_SIDES, _(" sides: ")); + sides->SetSize(50, -1); + tb->AddSeparator(); + tb->AddTool(ID_SHAPE_CIRCLE, _("Ellipse"), Bitmap(_("TOOL_CIRCLE")), wxNullBitmap, wxITEM_CHECK, _("Circle / Ellipse"), _("Draw circles and ellipses")); + tb->AddTool(ID_SHAPE_RECTANGLE, _("Rectangle"), Bitmap(_("TOOL_RECTANGLE")), wxNullBitmap, wxITEM_CHECK, _("Square / Rectangle"), _("Draw squares and rectangles")); + tb->AddTool(ID_SHAPE_POLYGON, _("Polygon"), Bitmap(_("TOOL_TRIANGLE")), wxNullBitmap, wxITEM_CHECK, _("Polygon"), _("Draw triangles, pentagons and other regular polygons")); + tb->AddTool(ID_SHAPE_STAR, _("Star"), Bitmap(_("TOOL_STAR")), wxNullBitmap, wxITEM_CHECK, _("Star"), _("Draw stars")); + tb->AddControl(sidesL); + tb->AddControl(sides); + tb->Realize(); + control.SetCursor(*wxCROSS_CURSOR); +} + +void SymbolBasicShapeEditor::destroyUI(wxToolBar* tb, wxMenuBar* mb) { + tb->DeleteTool(ID_SHAPE_CIRCLE); + tb->DeleteTool(ID_SHAPE_RECTANGLE); + tb->DeleteTool(ID_SHAPE_POLYGON); + tb->DeleteTool(ID_SHAPE_STAR); + tb->RemoveChild(sidesL); + tb->RemoveChild(sides); + // HACK: hardcoded size of rest of toolbar + tb->DeleteToolByPos(4); // delete separator + tb->DeleteToolByPos(4); // delete sidesL + tb->DeleteToolByPos(4); // delete sides + #if wxVERSION_NUMBER < 2600 + delete sides; + delete sidesL; + #endif +} + +void SymbolBasicShapeEditor::onUpdateUI(wxUpdateUIEvent& ev) { + if (ev.GetId() >= ID_SHAPE && ev.GetId() < ID_SHAPE_MAX) { + ev.Check(ev.GetId() == mode); + } else if (ev.GetId() == ID_SIDES) { + ev.Enable(mode == ID_SHAPE_POLYGON || mode == ID_SHAPE_STAR); + } else { + ev.Enable(false); // we don't know about this item + } +} + +void SymbolBasicShapeEditor::onCommand(int id) { + if (id >= ID_SHAPE && id < ID_SHAPE_MAX) { + // change shape mode + mode = id; + } +} + +int SymbolBasicShapeEditor::modeToolId() { return ID_MODE_SHAPES; } + +// ----------------------------------------------------------------------------- : Mouse events + +void SymbolBasicShapeEditor::onLeftDown (const Vector2D& pos, wxMouseEvent& ev) { + // Start drawing + drawing = true; + start = end = pos; + SetStatusText(_("Drag to resize shape, Ctrl constrains shape, Shift centers shape")); +} + +void SymbolBasicShapeEditor::onLeftUp (const Vector2D& pos, wxMouseEvent& ev) { + if (drawing && shape) { + // Finalize the shape + getSymbol()->actions.add(new AddSymbolPartAction(*getSymbol(), shape)); + // Select the part + control.selectPart(shape); + // no need to clean up, this editor is replaced + // // Clean up + // stopActions() + } +} + +void SymbolBasicShapeEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) { + // Resize the object + if (drawing) { + end = to; + makeShape(start, end, ev.ControlDown(), ev.ShiftDown()); + control.Refresh(false); + } +} + +// ----------------------------------------------------------------------------- : Other events + +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()); + control.Refresh(false); + } else if (ev.GetKeyCode() == WXK_ESCAPE) { + // cancel drawing + stopActions(); + } + } +} + +bool SymbolBasicShapeEditor::isEditing() { return drawing; } + +// ----------------------------------------------------------------------------- : Generating shapes + +void SymbolBasicShapeEditor::stopActions() { + shape = SymbolPartP(); + drawing = false; + switch (mode) { + case ID_SHAPE_CIRCLE: + SetStatusText(_("Click and drag to draw a ellipse, hold Ctrl for a circle")); + break; + case ID_SHAPE_RECTANGLE: + SetStatusText(_("Click and drag to draw a rectangle, hold Ctrl for a square")); + break; + case ID_SHAPE_POLYGON: + SetStatusText(_("Click and drag to draw a polygon")); + break; + case ID_SHAPE_STAR: + SetStatusText(_("Click and drag to draw a star")); + break; + } + control.Refresh(false); +} + +inline double sgn(double d) { + return d < 0 ? - 1 : 1; +} + +void SymbolBasicShapeEditor::makeShape(const Vector2D& a, const Vector2D& b, bool constrained, bool centered) { + // constrain + Vector2D size = b - a; + if (constrained) { + if (abs(size.x) > abs(size.y)) { + size.y = sgn(size.y) * abs(size.x); + } else { + size.x = sgn(size.x) * abs(size.y); + } + } + // make shape + if (centered) { + makeCenteredShape(a, size, constrained); + } else { + makeCenteredShape(a + size / 2, size / 2, constrained); + } +} + +// TODO : Move out of this class +void SymbolBasicShapeEditor::makeCenteredShape(const Vector2D& c, Vector2D r, bool constrained) { + shape = new_shared(); + // What shape to make? + switch (mode) { + case ID_SHAPE_CIRCLE: { + // A circle / ellipse + if (constrained) { + shape->name = _("Circle"); + } else { + shape->name = _("Ellipse"); + } + // a circle has 4 control points, the first is: (x+r, y) db(0, kr) da(0, -kr) + // kr is a magic constant + const double kr = 0.5522847498f; // = 4/3 * (sqrt(2) - 1) + shape->points.push_back(new_shared7(c.x + r.x, c.y, 0, kr * r.y, 0, -kr * r.y, LOCK_SIZE)); + shape->points.push_back(new_shared7(c.x, c.y - r.y, kr * r.x, 0, -kr * r.x, 0, LOCK_SIZE)); + shape->points.push_back(new_shared7(c.x - r.x, c.y, 0, -kr * r.y, 0, kr * r.y, LOCK_SIZE)); + shape->points.push_back(new_shared7(c.x, c.y + r.y, -kr * r.x, 0, kr * r.x, 0, LOCK_SIZE)); + break; + } case ID_SHAPE_RECTANGLE: { + // A rectangle / square + if (constrained) { + shape->name = _("Square"); + } else { + shape->name = _("Rectangle"); + } + // a rectangle just has four corners + shape->points.push_back(new_shared2(c.x - r.x, c.y - r.y)); + shape->points.push_back(new_shared2(c.x + r.x, c.y - r.y)); + shape->points.push_back(new_shared2(c.x + r.x, c.y + r.y)); + shape->points.push_back(new_shared2(c.x - r.x, c.y + r.y)); + break; + } default: { + // A polygon or star + int n = sides->GetValue(); // number of sides + switch (n) { + case 3: shape->name = _("Triangle"); + case 4: shape->name = _("Rhombus"); + case 5: shape->name = _("Pentagon"); + case 6: shape->name = _("Hexagon"); + default: shape->name = _("Polygon"); + } + // Example: n == 7 + // a a..g = corners + // g b O = center + // f O c ra = radius, |Oa| + // e d + double alpha = 2 * M_PI / n; // internal angle /_aOb + // angle between point touching side and point on top + // floor((n+1)/4) == number of sides between these two points + // beta = /_aOc + double beta = alpha * ((n+1)/4); + // define: + // width = 2 = |fc| + // lb = |ac| + // gamma = (pi - beta) / 2 + // equations: + // lb * sin(gamma) == 1 (right angled tri /_\ aXc where X is halfway fc) + // lb / sin(beta) == ra / sin(gamma) (law of sines in /_\ abc) + // solving leads to: + // sin(gamma) == cos(beta/2) + double lb = 1 / cos(beta/2); + double ra = lb / sin(beta) * cos(beta/2); + // now we know the center of the polygon: + double y = c.y + (ra - 1) * r.y; + if (mode == ID_SHAPE_POLYGON) { + // we can generate points + for(int i = 0 ; i < n ; ++i) { + double theta = alpha * i; + shape->points.push_back(new_shared2( + c.x + ra * r.x * sin(theta), + y - ra * r.y * cos(theta) + )); + } + } else { + // a star is made using a smaller, inverted polygon at the inside + // points are interleaved + // rb = radius of smaller polygon + // lc = length of a side + double lc = ra * sin(alpha) / cos(alpha/2); + // ld = length of side skipping one corner + double delta = alpha * 2; + double ld = ra * sin(delta) / cos(delta/2); + // Using symmetry: /_\gab ~ /_\axb where x is intersection + // gives ratio lc/ld + // converting back to radius using ra/lb = cos(beta/2) / sin(beta) + // NOTE: This is only correct for n<=6, but gives acceptable results for higher n + double rb = (ld - 2 * lc * (lc/ld)) * ra / lb; + for(int i = 0 ; i < n ; ++i) { + double theta = alpha * i; + // from a + shape->points.push_back(new_shared2( + c.x + ra * r.x * sin(theta), + y - ra * r.y * cos(theta) + )); + // from b + theta = alpha * (i + 0.5); + shape->points.push_back(new_shared2( + c.x + rb * r.x * sin(theta), + y - rb * r.y * cos(theta) + )); + } + } + break; + } + } +} diff --git a/src/gui/symbol/basic_shape_editor.hpp b/src/gui/symbol/basic_shape_editor.hpp new file mode 100644 index 00000000..e812997b --- /dev/null +++ b/src/gui/symbol/basic_shape_editor.hpp @@ -0,0 +1,73 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_SYMBOL_BASIC_SHAPE_EDITOR +#define HEADER_GUI_SYMBOL_BASIC_SHAPE_EDITOR + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +class wxSpinCtrl; + +// ----------------------------------------------------------------------------- : SymbolBasicShapeEditor + +/// Editor for drawing basic shapes such as rectangles and polygons +class SymbolBasicShapeEditor : public SymbolEditorBase { + public: + SymbolBasicShapeEditor(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& e); + 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; + SymbolPartP shape; + Vector2D start; + Vector2D end; + bool drawing; + // controls + wxSpinCtrl* sides; + wxStaticText* sidesL; + + /// Cancel the drawing + void stopActions(); + + /// Make the shape + /** 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); + + /// Make the shape, centered in c, with radius r + void makeCenteredShape(const Vector2D& c, Vector2D r, bool constrained); +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/symbol/control.cpp b/src/gui/symbol/control.cpp new file mode 100644 index 00000000..eddf9884 --- /dev/null +++ b/src/gui/symbol/control.cpp @@ -0,0 +1,236 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- : SymbolControl + +SymbolControl::SymbolControl(SymbolWindow* parent, int id, const SymbolP& symbol) + : wxControl(parent, id) + , SymbolViewer(symbol) + , parent(parent) +{ + switchEditor(new_shared2(this, false)); +} + +void SymbolControl::switchEditor(const SymbolEditorBaseP& e) { + if (editor) editor->destroyUI(parent->GetToolBar(), parent->GetMenuBar()); + editor = e; + if (editor) editor->initUI (parent->GetToolBar(), parent->GetMenuBar()); + Refresh(false); +} + +void SymbolControl::onSymbolChange() { + selectedParts.clear(); + switchEditor(new_shared2(this, false)); + Refresh(false); +} + +void SymbolControl::onModeChange(wxCommandEvent& ev) { + switch (ev.GetId()) { + case ID_MODE_SELECT: + switchEditor(new_shared2(this, false)); + break; + case ID_MODE_ROTATE: + switchEditor(new_shared2(this, true)); + break; + case ID_MODE_POINTS: + if (selectedParts.size() == 1) { + singleSelection = *selectedParts.begin(); + switchEditor(new_shared2(this, singleSelection)); + } + break; + case ID_MODE_SHAPES: + if (!selectedParts.empty()) { + selectedParts.clear(); + signalSelectionChange(); + } + switchEditor(new_shared1(this)); + break; + } +} + +void SymbolControl::onExtraTool(wxCommandEvent& ev) { + if (editor) editor->onCommand(ev.GetId()); +} + +void SymbolControl::onAction(const Action& action) { + TYPE_CASE_(action, SymbolPartAction) { + Refresh(false); + } +} + +void SymbolControl::onUpdateSelection() { + switch(editor->modeToolId()) { + case ID_MODE_POINTS: + // can only select a single part! + if (selectedParts.size() > 1) { + SymbolPartP part = *selectedParts.begin(); + selectedParts.clear(); + selectedParts.insert(part); + signalSelectionChange(); + } else if (selectedParts.empty()) { + selectedParts.insert(singleSelection); + signalSelectionChange(); + } + if (singleSelection != *selectedParts.begin()) { + // begin editing another part + singleSelection = *selectedParts.begin(); + editor = new_shared2(this, singleSelection); + Refresh(false); + } + break; + case ID_MODE_SHAPES: + if (!selectedParts.empty()) { + // there can't be a selection + selectedParts.clear(); + signalSelectionChange(); + } + break; + default: + Refresh(false); + break; + } +} + +void SymbolControl::selectPart(const SymbolPartP& part) { + selectedParts.clear(); + selectedParts.insert(part); + switchEditor(new_shared2(this, false)); + signalSelectionChange(); +} + +void SymbolControl::activatePart(const SymbolPartP& part) { + selectedParts.clear(); + selectedParts.insert(part); + switchEditor(new_shared2(this, part)); +} + +void SymbolControl::signalSelectionChange() { + parent->onSelectFromControl(); +} + +bool SymbolControl::isEditing() { + return editor && editor->isEditing(); +} + +// ----------------------------------------------------------------------------- : Drawing + +void SymbolControl::draw(DC& dc) { + // clear the background + clearDC(dc, Color(0, 128, 0)); + // draw symbol iself + SymbolViewer::draw(dc); + // draw editing overlay + if (editor) { + editor->draw(dc); + } +} +void SymbolControl::onPaint(wxPaintEvent& e) { + wxBufferedPaintDC dc(this); + dc.BeginDrawing(); + draw(dc); + dc.EndDrawing(); +} + +// ----------------------------------------------------------------------------- : Events + +// Mouse events, convert position, forward event + +void SymbolControl::onLeftDown(wxMouseEvent& ev) { + Vector2D pos = rotation.trInv(RealPoint(ev.GetX(), ev.GetY())); + if (editor) editor->onLeftDown(pos, ev); + lastPos = pos; + ev.Skip(); // for focus +} +void SymbolControl::onLeftUp(wxMouseEvent& ev) { + Vector2D pos = rotation.trInv(RealPoint(ev.GetX(), ev.GetY())); + if (editor) editor->onLeftUp(pos, ev); + lastPos = pos; +} +void SymbolControl::onLeftDClick(wxMouseEvent& ev) { + Vector2D pos = rotation.trInv(RealPoint(ev.GetX(), ev.GetY())); + if (editor) editor->onLeftDClick(pos, ev); + lastPos = pos; +} +void SymbolControl::onRightDown(wxMouseEvent& ev) { + Vector2D pos = rotation.trInv(RealPoint(ev.GetX(), ev.GetY())); + if (editor) editor->onRightDown(pos, ev); + lastPos = pos; +} + +void SymbolControl::onMotion(wxMouseEvent& ev) { + Vector2D pos = rotation.trInv(RealPoint(ev.GetX(), ev.GetY())); + // Dragging something? + if (ev.LeftIsDown()) { + if (editor) editor->onMouseDrag(lastPos, pos, ev); + } else { + if (editor) editor->onMouseMove(lastPos, pos, ev); + } + lastPos = pos; +} + +// Key events, just forward + +void SymbolControl::onKeyChange(wxKeyEvent& ev) { + if (editor) editor->onKeyChange(ev); + ev.Skip(); // so we get char events +} +void SymbolControl::onChar(wxKeyEvent& ev) { + if (editor) editor->onChar(ev); + else ev.Skip(); +} + +void SymbolControl::onSize(wxSizeEvent& ev) { + wxSize s = ev.GetSize(); + rotation.setZoom(min(s.GetWidth(), s.GetHeight())); + Refresh(false); +} +void SymbolControl::onUpdateUI(wxUpdateUIEvent& ev) { + if (!editor) return; + switch (ev.GetId()) { + case ID_MODE_SELECT: case ID_MODE_ROTATE: case ID_MODE_POINTS: case ID_MODE_SHAPES: //case ID_MODE_PAINT: + ev.Check(editor->modeToolId() == ev.GetId()); + if (ev.GetId() == ID_MODE_POINTS) { + // can only edit points when a single part is selected + ev.Enable(selectedParts.size() == 1); + } + break; + case ID_MODE_PAINT: + ev.Enable(false); // TODO + break; + default: + if (ev.GetId() >= ID_CHILD_MIN && ev.GetId() < ID_CHILD_MAX) { + editor->onUpdateUI(ev); // foward to editor + } + } +} + +// ----------------------------------------------------------------------------- : Event table + +BEGIN_EVENT_TABLE(SymbolControl, wxControl) + EVT_PAINT (SymbolControl::onPaint) + EVT_SIZE (SymbolControl::onSize) + EVT_LEFT_UP (SymbolControl::onLeftUp) + EVT_LEFT_DOWN (SymbolControl::onLeftDown) + EVT_RIGHT_DOWN (SymbolControl::onRightDown) + EVT_LEFT_DCLICK (SymbolControl::onLeftDClick) + EVT_MOTION (SymbolControl::onMotion) + EVT_KEY_UP (SymbolControl::onKeyChange) + EVT_KEY_DOWN (SymbolControl::onKeyChange) + EVT_CHAR (SymbolControl::onChar) +END_EVENT_TABLE () \ No newline at end of file diff --git a/src/gui/symbol/control.hpp b/src/gui/symbol/control.hpp new file mode 100644 index 00000000..23f4b12c --- /dev/null +++ b/src/gui/symbol/control.hpp @@ -0,0 +1,103 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_SYMBOL_CONTROL +#define HEADER_GUI_SYMBOL_CONTROL + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include + +class SymbolWindow; +DECLARE_POINTER_TYPE(SymbolEditorBase); + +// ----------------------------------------------------------------------------- : SymbolControl + +/// Control for editing symbols +/** What kind of editing is done is determined by the contained SymbolEditorBase object + * That object handles all events and the drawing. This class is mostly just a proxy. + */ +class SymbolControl : public wxControl, public SymbolViewer { + public: + SymbolControl(SymbolWindow* parent, int id, const SymbolP& symbol); + + virtual void onSymbolChange(); + + virtual void onAction(const Action&); + + // Forward command to editor + void onExtraTool(wxCommandEvent& ev); + + // Switch to some editing mode + void onModeChange(wxCommandEvent& ev); + + /// Handle UpdateUIEvents propagated from the SymbolWindow + /** Handles events for editing mode related stuff + */ + void onUpdateUI(wxUpdateUIEvent& ev); + + /// The selection has changed, tell the part list + void signalSelectionChange(); + + /// Activate a part, open it in the point editor + void activatePart(const SymbolPartP& part); + + /// Select a specific part from the symbol + /// The editor is switched to the select editor + void selectPart(const SymbolPartP& part); + + /// Update the selection + void onUpdateSelection(); + + /// Are we editing? + bool isEditing(); + + private: + /// Switch the a different editor object + void switchEditor(const SymbolEditorBaseP& e); + + /// Draw the editor + void draw(DC& dc); + + private: + DECLARE_EVENT_TABLE(); + + // --------------------------------------------------- : Data + + public: + /// What parts are selected + set selectedParts; + SymbolPartP singleSelection; + + /// Parent window + SymbolWindow* parent; + + private: + /// The current editor + SymbolEditorBaseP editor; + + /// Last mouse position + Vector2D lastPos; + + // --------------------------------------------------- : Events + + void onLeftDown (wxMouseEvent& ev); + void onLeftUp (wxMouseEvent& ev); + void onLeftDClick(wxMouseEvent& ev); + void onRightDown (wxMouseEvent& ev); + void onMotion (wxMouseEvent& ev); + + void onPaint (wxPaintEvent& e); + void onKeyChange(wxKeyEvent& ev); + void onChar (wxKeyEvent& ev); + void onSize (wxSizeEvent& ev); +}; + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/symbol/editor.cpp b/src/gui/symbol/editor.cpp new file mode 100644 index 00000000..59922696 --- /dev/null +++ b/src/gui/symbol/editor.cpp @@ -0,0 +1,16 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +// ----------------------------------------------------------------------------- : SymbolEditorBase + +void SymbolEditorBase::SetStatusText(const String& text) { + control.parent->SetStatusText(text); +} diff --git a/src/gui/symbol/editor.hpp b/src/gui/symbol/editor.hpp new file mode 100644 index 00000000..04998e92 --- /dev/null +++ b/src/gui/symbol/editor.hpp @@ -0,0 +1,91 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_SYMBOL_EDITOR +#define HEADER_GUI_SYMBOL_EDITOR + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include + +class SymbolControl; + +// ----------------------------------------------------------------------------- : SymbolEditorBase + +/// Base class for editors of symbols. +/** A symbol editor is like a FieldEditor, events are forwarded to it. + * Differrent SymbolEditors represent different tools. + * NOTE : Do not confuse with SymbolEditor (a FieldEditor) + */ +class SymbolEditorBase { + protected: + /// The control for which we are editing + SymbolControl& control; + + inline SymbolP getSymbol() { return control.getSymbol(); } + void SetStatusText(const String& text); + + public: + SymbolEditorBase(SymbolControl* control) + : control(*control) + {} + virtual ~SymbolEditorBase() {}; + + // --------------------------------------------------- : Drawing + + /// Drawing for this control, + virtual void draw(DC& dc) = 0; + + // --------------------------------------------------- : UI + + /// Init extra toolbar items and menus needed for this panel + virtual void initUI(wxToolBar* tb, wxMenuBar* mb) {} + /// Destroy the extra items added by initUI + virtual void destroyUI(wxToolBar* tb, wxMenuBar* mb) {} + /// Update the UI by enabling/disabling items + virtual void onUpdateUI(wxUpdateUIEvent& ev) {} + /// Respond to one of the extra menu/tool items + virtual void onCommand(int id) {} + /// Tool id used in the symbol window + virtual int modeToolId() = 0; + + // --------------------------------------------------- : Mouse events + + /// The left mouse button has been pressed, at the given position (internal coordinates) + virtual void onLeftDown (const Vector2D& pos, wxMouseEvent& ev) {} + /// The left mouse button has been released, at the given position (internal coordinates) + virtual void onLeftUp (const Vector2D& pos, wxMouseEvent& ev) {} + /// The left mouse button has been double clicked, at the given position (internal coordinates) + virtual void onLeftDClick (const Vector2D& pos, wxMouseEvent& ev) {} + /// The right mouse button has been pressed, at the given position (internal coordinates) + virtual void onRightDown (const Vector2D& pos, wxMouseEvent& ev) {} + /// The mouse is being moved, no mouse buttons are pressed + virtual void onMouseMove (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) {} + /// The mouse is being moved while mouse buttons are being pressed + virtual void onMouseDrag (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) {} + + // --------------------------------------------------- : Keyboard events + + /// A key is pressed or released, should be used for modifier keys (Shift/Ctrl/Alt) + virtual void onKeyChange (wxKeyEvent& ev) {} + /// A key is pressed/clicked + virtual void onChar (wxKeyEvent& ev) {} + + // --------------------------------------------------- : Other events + /// A context menu is requested + virtual void onContextMenu(wxContextMenuEvent& ev) {} + + /// Is the user currently editing, i.e. dragging the mouse? + /** This disables undo/redo, so the current action is not + * undone while it is in progress. + */ + virtual bool isEditing() { return false; } +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/symbol/part_list.cpp b/src/gui/symbol/part_list.cpp new file mode 100644 index 00000000..8693de52 --- /dev/null +++ b/src/gui/symbol/part_list.cpp @@ -0,0 +1,155 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- : Constructor + +SymbolPartList::SymbolPartList(Window* parent, int id, SymbolP symbol) + : wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, + wxLC_REPORT | wxLC_NO_HEADER | wxLC_VIRTUAL | wxLC_EDIT_LABELS) + , SymbolView(symbol) +{ + // Create image list + wxImageList* images = new wxImageList(16,16); + // NOTE: this is based on the order of the SymbolPartCombine enum! + images->Add(loadResourceImage(_("COMBINE_OR"))); + images->Add(loadResourceImage(_("COMBINE_SUB"))); + images->Add(loadResourceImage(_("COMBINE_AND"))); + images->Add(loadResourceImage(_("COMBINE_XOR"))); + images->Add(loadResourceImage(_("COMBINE_OVER"))); + images->Add(loadResourceImage(_("COMBINE_BORDER"))); + AssignImageList(images, wxIMAGE_LIST_SMALL); + // create columns + InsertColumn(0, _("Name")); + update(); +} + +// ----------------------------------------------------------------------------- : View events + +void SymbolPartList::onSymbolChange() { + update(); +} + +void SymbolPartList::onAction(const Action& action) { + TYPE_CASE(action, ReorderSymbolPartsAction) { + if (selected == (long) action.partId1) { + selectItem((long) action.partId2); + } else if (selected == (long) action.partId2) { + selectItem((long) action.partId1); + } + } + TYPE_CASE_(action, SymbolPartListAction) { + update(); + } +} + +// ----------------------------------------------------------------------------- : 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)->combine; +} + +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); + } +} + +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); + } +} + +// ----------------------------------------------------------------------------- : Event handling + +void SymbolPartList::onSelect(wxListEvent& ev) { + selected = ev.GetIndex(); + ev.Skip(); +} +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 + } + } +} + +// ----------------------------------------------------------------------------- : 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) +END_EVENT_TABLE () diff --git a/src/gui/symbol/part_list.hpp b/src/gui/symbol/part_list.hpp new file mode 100644 index 00000000..3a504f44 --- /dev/null +++ b/src/gui/symbol/part_list.hpp @@ -0,0 +1,72 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_SYMBOL_PART_LIST +#define HEADER_GUI_SYMBOL_PART_LIST + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include + +// ----------------------------------------------------------------------------- : SymbolPartList + +// A list view of parts of a symbol +class SymbolPartList : public wxListCtrl, 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); + + /// Another symbol is being viewed + void onSymbolChange(); + + /// Event handler for changes to the symbol + virtual void onAction(const Action& a); + + 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; + + private: + /// The selected item, or -1 if there is no selection + long selected; + + /// Get a part from the symbol + SymbolPartP getPart(long item) const; + + /// Select an item, also in the list control + /// Deselects all other items + void selectItem(long item); + + // --------------------------------------------------- : 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); +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/symbol/point_editor.cpp b/src/gui/symbol/point_editor.cpp new file mode 100644 index 00000000..b388410b --- /dev/null +++ b/src/gui/symbol/point_editor.cpp @@ -0,0 +1,529 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- : SymbolPointEditor + +SymbolPointEditor::SymbolPointEditor(SymbolControl* control, const SymbolPartP& part) + : SymbolEditorBase(control) + , part(part) + , selection(SELECTED_NONE) + , hovering(SELECTED_NONE) + // Load gui stock + , pointSelect(_("CUR_POINT"), wxBITMAP_TYPE_CUR_RESOURCE) + , pointAdd (_("CUR_POINT_ADD"), wxBITMAP_TYPE_CUR_RESOURCE) + , pointCurve (_("CUR_POINT_CURVE"),wxBITMAP_TYPE_CUR_RESOURCE) + , pointMove (_("CUR_POINT_MOVE"), wxBITMAP_TYPE_CUR_RESOURCE) +{ + resetActions(); +// // fix pen joins +// penHandleHover.join = wxJOIN_MITER; +// penMainHover.join = wxJOIN_MITER; +} + +// ----------------------------------------------------------------------------- : Drawing + +void SymbolPointEditor::draw(DC& dc) { + // highlight the part + control.highlightPart(dc, *part, HIGHLIGHT_BORDER); + // handles etc. + if (hovering == SELECTED_LINE) { + drawHoveredLine(dc); + } + drawHandles(dc); + if (hovering == SELECTED_NEW_POINT) { + drawNewPoint(dc); + } +} + +void SymbolPointEditor::drawHoveredLine(DC& dc) { + BezierCurve c(*hoverLine1, *hoverLine2); + wxPoint prevPoint = control.rotation.tr(hoverLine1->pos); + for(int i = 1 ; i <= 100 ; ++i) { + // Draw 100 segments of the curve + double t = double(i)/100.0f; + wxPoint curPoint = control.rotation.tr(c.pointAt(t)); + double selectPercent = 1.0 - 1.2 * sqrt(fabs(hoverLineT-t)); // amount to color + if (selectPercent > 0) { + // gradient color + Color color( + col(300 - 300 * selectPercent), + col(300 * selectPercent), + col(0) + ); + dc.SetPen(wxPen(color, 3)); + dc.DrawLine(prevPoint, curPoint); + } + prevPoint = curPoint; + } +} + +void SymbolPointEditor::drawHandles(DC& dc) { + dc.SetPen(Color(0,0,128)); + dc.SetBrush(Color(128,128,255)); + for (int i = 0 ; (size_t)i < part->points.size() ; ++i) { + // determine which handles to draw + bool selected = pointSelected(*part->getPoint(i)); + bool selBefore = selected || pointSelected(*part->getPoint(i-1)); + bool selAfter = selected || pointSelected(*part->getPoint(i+1)); + // and draw them + drawControlPoint(dc, *part->getPoint(i), selBefore, selAfter); + } +} + +void SymbolPointEditor::drawNewPoint(DC& dc) { + dc.SetPen(*wxGREEN_PEN); + dc.SetBrush(*wxTRANSPARENT_BRUSH); + wxPoint p = control.rotation.tr(newPoint); + drawHandleBox(dc, p.x, p.y, true); +} + +void SymbolPointEditor::drawControlPoint(DC& dc, const ControlPoint& p, bool drawHandleBefore, bool drawHandleAfter) { + // Position + wxPoint p0 = control.rotation.tr(p.pos); + // Sub handles + if (drawHandleBefore || drawHandleAfter) { + dc.SetBrush(*wxTRANSPARENT_BRUSH); + // Before handle + if (drawHandleBefore && p.segmentBefore == SEGMENT_CURVE) { + wxPoint p1 = control.rotation.tr(p.pos + p.deltaBefore); + dc.SetPen(handlePen(PEN_LINE, p.lock)); + dc.DrawLine(p0.x, p0.y, p1.x, p1.y); + dc.SetPen(handlePen(handleHovered(p, HANDLE_BEFORE) ? PEN_HOVER : PEN_NORMAL, p.lock)); + drawHandleCircle(dc, p1.x, p1.y); + } + // After handle + if (drawHandleAfter && p.segmentAfter == SEGMENT_CURVE) { + wxPoint p1 = control.rotation.tr(p.pos + p.deltaAfter); + dc.SetPen(handlePen(PEN_LINE, p.lock)); + dc.DrawLine(p0.x, p0.y, p1.x, p1.y); + dc.SetPen(handlePen(handleHovered(p, HANDLE_AFTER) ? PEN_HOVER : PEN_NORMAL, p.lock)); + drawHandleCircle(dc, p1.x, p1.y); + } + } + // Main handle + // last, so it draws over lines to handles + bool selected = pointSelected(p); + wxPen hp(*wxBLACK, pointHovered(p) ? 2 : 1); + hp.SetJoin(wxJOIN_MITER); + dc.SetPen(hp); + dc.SetBrush(selected ? *wxGREEN_BRUSH : *wxTRANSPARENT_BRUSH); + drawHandleBox(dc, p0.x, p0.y, selected); +} + +void SymbolPointEditor::drawHandleBox(DC& dc, UInt px, UInt py, bool active) { + dc.DrawRectangle(px - 3, py - 3, 7, 7); + if (!active) { + dc.SetPen(*wxWHITE_PEN); + dc.DrawRectangle(px - 2, py - 2, 5, 5); + } +} +void SymbolPointEditor::drawHandleCircle(DC& dc, UInt px, UInt py) { + dc.DrawCircle(px, py, 4); +} + +wxPen SymbolPointEditor::handlePen(WhichPen p, LockMode lock) { + Color col; + if (lock == LOCK_FREE) col = Color(100, 100, 255); + if (lock == LOCK_DIR) col = Color(153, 0, 204); + if (lock == LOCK_SIZE) col = Color(204, 50, 50); + switch(p) { + case PEN_NORMAL: return wxPen(col); + case PEN_HOVER: return wxPen(col, 2); + case PEN_LINE: return wxPen(col, 1, wxDOT); + default: throw InternalError(_("SymbolPointEditor::handlePen")); + } +} + +// ----------------------------------------------------------------------------- : UI + +void SymbolPointEditor::initUI(wxToolBar* tb, wxMenuBar* mb) { + // Initialize toolbar + tb->AddSeparator(); + tb->AddTool(ID_SEGMENT_LINE, _("Line"), Bitmap(_("TOOL_LINE")), wxNullBitmap, wxITEM_CHECK, _("To straigt line"), _("Makes the selected line straight")); + tb->AddTool(ID_SEGMENT_CURVE, _("Curve"), Bitmap(_("TOOL_CURVE")), wxNullBitmap, wxITEM_CHECK, _("To curve"), _("Makes the selected line curved")); + tb->AddSeparator(); + tb->AddTool(ID_LOCK_FREE, _("Free"), Bitmap(_("TOOL_LOCK_FREE")), wxNullBitmap, wxITEM_CHECK, _("Unlock node"), _("Allows the two control points on the node to be moved freely")); + tb->AddTool(ID_LOCK_DIR, _("Smooth"), Bitmap(_("TOOL_LOCK_DIR")), wxNullBitmap, wxITEM_CHECK, _("Make node smooth"), _("Makes the selected node smooth by placing the two control points opposite each other")); + tb->AddTool(ID_LOCK_SIZE, _("Symmetric"), Bitmap(_("TOOL_LOCK_SIZE")), wxNullBitmap, wxITEM_CHECK, _("Make node symmetric"), _("Makes the selected node symetric")); + tb->Realize(); + // TODO : menu bar + //mb->Insert(2, curveMenu, _("&Curve")) +} + +void SymbolPointEditor::destroyUI(wxToolBar* tb, wxMenuBar* mb) { + tb->DeleteTool(ID_SEGMENT_LINE); + tb->DeleteTool(ID_SEGMENT_CURVE); + tb->DeleteTool(ID_LOCK_FREE); + tb->DeleteTool(ID_LOCK_DIR); + tb->DeleteTool(ID_LOCK_SIZE); + // HACK: hardcoded size of rest of toolbar + tb->DeleteToolByPos(4); // delete separator + tb->DeleteToolByPos(4); // delete separator + // TODO : menu bar + //mb->Remove(2) +} + +void SymbolPointEditor::onUpdateUI(wxUpdateUIEvent& ev) { + // enable + bool enabled = false, checked = false; + switch (ev.GetId()) { + case ID_SEGMENT_LINE: case ID_SEGMENT_CURVE: + enabled = selection == SELECTED_LINE; + break; + case ID_LOCK_FREE: case ID_LOCK_DIR: case ID_LOCK_SIZE: + enabled = selection == SELECTED_POINTS && + selectedPoints.size() == 1 && + (*selectedPoints.begin())->segmentBefore == SEGMENT_CURVE && + (*selectedPoints.begin())->segmentAfter == SEGMENT_CURVE; + break; + default: + ev.Enable(false); // we don't know this item + return; + } + // check + if (enabled) { + switch (ev.GetId()) { + case ID_SEGMENT_LINE: case ID_SEGMENT_CURVE: + checked = selectedLine1->segmentAfter == ev.GetId() - ID_SEGMENT; + break; + case ID_LOCK_FREE: case ID_LOCK_DIR: case ID_LOCK_SIZE: + checked = (*selectedPoints.begin())->lock == ev.GetId() - ID_LOCK; + break; + } + } + + ev.Enable(enabled); + ev.Check(checked); +} + +void SymbolPointEditor::onCommand(int id) { + switch (id) { + case ID_SEGMENT_LINE: case ID_SEGMENT_CURVE: + onChangeSegment( static_cast(id - ID_SEGMENT) ); + case ID_LOCK_FREE: case ID_LOCK_DIR: case ID_LOCK_SIZE: + onChangeLock( static_cast(id - ID_LOCK) ); + } +} + + +int SymbolPointEditor::modeToolId() { return ID_MODE_POINTS; } + +// ----------------------------------------------------------------------------- : Mouse events + +void SymbolPointEditor::onLeftDown(const Vector2D& pos, wxMouseEvent& ev) { + SelectedHandle handle = findHandle(pos); + if (handle.handle) { + selectHandle(handle, ev); + } else if (hovering == SELECTED_LINE) { + selectLine(ev); + } else if (hovering == SELECTED_NEW_POINT) { + selectLine(ev); + } else { + selectNothing(); + } + // update window + control.Refresh(false); +} + +void SymbolPointEditor::onLeftUp(const Vector2D& pos, wxMouseEvent& ev) { + // Left up => finalize all actions, new events start new actions + resetActions(); +} + +void SymbolPointEditor::onLeftDClick(const Vector2D& pos, wxMouseEvent& ev) { + findHoveredItem(pos, false); + if (hovering == SELECTED_NEW_POINT) { + // Add point + ControlPointAddAction* act = new ControlPointAddAction(part, hoverLine1Idx, hoverLineT); + getSymbol()->actions.add(act); + // select the new point + selectPoint(act->getNewPoint(), false); + selection = SELECTED_POINTS; + } else if (hovering == SELECTED_HANDLE && hoverHandle.handle == HANDLE_MAIN) { //%%%%%%% ||/&& + // Delete point + selectedPoints.clear(); + selectPoint(hoverHandle.point, false); + getSymbol()->actions.add(controlPointRemoveAction(part, selectedPoints)); + selectedPoints.clear(); + selection = SELECTED_NONE; + } + // refresh + findHoveredItem(pos, false); + control.Refresh(false); +} + +void SymbolPointEditor::onMouseMove(const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) { + // Moving the mouse without dragging => select a point/handle + findHoveredItem(to, ev.AltDown()); + control.Refresh(false); +} + +void SymbolPointEditor::onMouseDrag(const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) { + Vector2D delta = to - from; + if (selection == SELECTED_LINE && ev.AltDown()) { + // Drag the curve + if (controlPointMoveAction) controlPointMoveAction = 0; + if (!curveDragAction) { + curveDragAction = new CurveDragAction(selectedLine1, selectedLine2); + getSymbol()->actions.add(curveDragAction); + } + curveDragAction->move(delta, selectedLineT); + control.Refresh(false); + } else if (selection == SELECTED_POINTS || selection == SELECTED_LINE) { + // Move all selected points + if (curveDragAction) curveDragAction = 0; + if (!controlPointMoveAction) { + // create action we can add this movement to + controlPointMoveAction = new ControlPointMoveAction(selectedPoints); + getSymbol()->actions.add(controlPointMoveAction); + } + controlPointMoveAction->constrain = ev.ControlDown(); // ctrl constrains + controlPointMoveAction->move(delta); + newPoint += delta; + control.Refresh(false); + } else if (selection == SELECTED_HANDLE) { + // Move the selected handle + if (!handleMoveAction) { + handleMoveAction = new HandleMoveAction(selectedHandle); + getSymbol()->actions.add(handleMoveAction); + } + handleMoveAction->constrain = ev.ControlDown(); // ctrl constrains + handleMoveAction->move(delta); + control.Refresh(false); + } +} + +// ----------------------------------------------------------------------------- : Other events + + +void SymbolPointEditor::onKeyChange(wxKeyEvent& ev) { + if (ev.GetKeyCode() == WXK_ALT && (hovering == SELECTED_LINE || hovering == SELECTED_NEW_POINT)) { + if (ev.AltDown()) { + hovering = SELECTED_LINE; + control.SetCursor(pointCurve); + SetStatusText(_("Drag to move curve")); + } else { + hovering = SELECTED_NEW_POINT; + control.SetCursor(pointAdd); + SetStatusText(_("Alt + drag to move curve; double click to add control point on this line")); + } + control.Refresh(false); + } else if (ev.GetKeyCode() == WXK_CONTROL) { + // constrain changed + if (controlPointMoveAction) { + controlPointMoveAction->constrain = ev.ControlDown(); + controlPointMoveAction->move(Vector2D()); //refresh action + control.Refresh(false); + } else if (handleMoveAction) { + handleMoveAction->constrain = ev.ControlDown(); + handleMoveAction->move(Vector2D()); //refresh action + control.Refresh(false); + } + } +} + +void SymbolPointEditor::onChar(wxKeyEvent& ev) { + if (ev.GetKeyCode() == WXK_DELETE) { + deleteSelection(); + } else { + ev.Skip(); + } +} + +bool SymbolPointEditor::isEditing() { + return handleMoveAction || controlPointMoveAction || curveDragAction; +} + +// ----------------------------------------------------------------------------- : Selection + +void SymbolPointEditor::selectNothing() { + selection = SELECTED_NONE; + selectedPoints.clear(); +} + +void SymbolPointEditor::selectPoint(const ControlPointP& point, bool toggle) { + set::iterator inSet = selectedPoints.find(point); + if (toggle) { + if (inSet == selectedPoints.end()) { + selectedPoints.insert(point); + } else { + selectedPoints.erase(inSet); + } + } else { + if (inSet == selectedPoints.end()) { + selectedPoints.clear(); + selectedPoints.insert(point); + } + } +} + +void SymbolPointEditor::selectHandle(const SelectedHandle& h, const wxMouseEvent& keystate) { + if (h.handle == HANDLE_MAIN) { + selection = SELECTED_POINTS; + selectPoint(h.point, keystate.ShiftDown()); + } else { + selection = SELECTED_HANDLE; + selectedHandle = h; + } +} + +void SymbolPointEditor::selectLine(const wxMouseEvent& keystate) { + selection = SELECTED_LINE; + selectedLine1 = hoverLine1; + selectedLine2 = hoverLine2; + selectedLineT = hoverLineT; + if (!keystate.ShiftDown()) selectedPoints.clear(); + selectPoint(selectedLine1, true); + selectPoint(selectedLine2, true); +} + + +bool SymbolPointEditor::pointSelected(const ControlPointP& pnt) { + return selectedPoints.find(pnt) != selectedPoints.end(); +} +bool SymbolPointEditor::pointSelected(const ControlPoint& pnt) { + FOR_EACH(s, selectedPoints) { + if (s.get() == &pnt) return true; + } + return false; +} + +bool SymbolPointEditor::pointHovered(const ControlPointP& pnt) { + return handleHovered(pnt, HANDLE_MAIN); +} +bool SymbolPointEditor::pointHovered(const ControlPoint& pnt) { + return handleHovered(pnt, HANDLE_MAIN); +} + +bool SymbolPointEditor::handleHovered(const ControlPointP& pnt, WhichHandle wh) { + return hovering == SELECTED_HANDLE && hoverHandle.point == pnt && hoverHandle.handle == wh; +} +bool SymbolPointEditor::handleHovered(const ControlPoint& pnt, WhichHandle wh) { + return hovering == SELECTED_HANDLE && hoverHandle.point && hoverHandle.point.get() == &pnt && hoverHandle.handle == wh; +} + + +// ----------------------------------------------------------------------------- : Actions + +void SymbolPointEditor::resetActions() { + handleMoveAction = nullptr; + controlPointMoveAction = nullptr; + curveDragAction = nullptr; +} + +void SymbolPointEditor::deleteSelection() { + if (!selectedPoints.empty()) { + getSymbol()->actions.add(controlPointRemoveAction(part, selectedPoints)); + selectedPoints.clear(); + resetActions(); + control.Refresh(false); + } +} + +void SymbolPointEditor::onChangeSegment(SegmentMode mode) { + assert(selectedLine1); + assert(selectedLine2); + if (selectedLine1->segmentAfter == mode) return; + getSymbol()->actions.add(new SegmentModeAction(selectedLine1, selectedLine2, mode)); + control.Refresh(false); +} + +void SymbolPointEditor::onChangeLock(LockMode mode) { + getSymbol()->actions.add(new LockModeAction(*selectedPoints.begin(), mode)); + control.Refresh(false); +} + + +// ----------------------------------------------------------------------------- : Finding items + +void SymbolPointEditor::findHoveredItem(const Vector2D& pos, bool altDown) { + // is there a point currently under the cursor? + hoverHandle = findHandle(pos); + // change cursor and statusbar if point is under it + if (hoverHandle.handle) { + hovering = SELECTED_HANDLE; + control.SetCursor(pointMove); + SetStatusText(_("Click and drag to move control point")); + } else { + // Not on a point or handle, maybe the cursor is on a curve + if (checkPosOnCurve(pos)) { + if (altDown) { + hovering = SELECTED_LINE; + control.SetCursor(pointCurve); + SetStatusText(_("Drag to move curve")); + } else { + hovering = SELECTED_NEW_POINT; + control.SetCursor(pointAdd); + SetStatusText(_("Alt + drag to move curve; double click to add control point on this line")); + } + } else { + hovering = SELECTED_NONE; + control.SetCursor(*wxSTANDARD_CURSOR); + SetStatusText(_("")); + } + } +} + +bool SymbolPointEditor::checkPosOnCurve(const Vector2D& pos) { + double range = control.rotation.trInvS(3); // less then 3 pixels away is still a hit + size_t size = part->points.size(); + for(int i = 0 ; (size_t)i < size ; ++i) { + // Curve between these lines + hoverLine1 = part->getPoint(i); + hoverLine2 = part->getPoint(i + 1); + if (posOnSegment(pos, range, *hoverLine1, *hoverLine2, newPoint, hoverLineT)) { + // mouse is on this line + hoverLine1Idx = i; + return true; + } + } + return false; +} + +SelectedHandle SymbolPointEditor::findHandle(const Vector2D& pos) { + double range = control.rotation.trInvS(3); // less then 3 pixels away is still a hit + // Is there a main handle there? + FOR_EACH(p, part->points) { + if (inRange(p->pos, pos, range)) { + // point is at pos + return SelectedHandle(p, HANDLE_MAIN); + } + } + // Is there a sub handle there? + // only check visible handles + for (int i = 0 ; (size_t)i < part->points.size() ; ++i) { + ControlPointP p = part->getPoint(i); + bool sel = pointSelected(p); + bool before = sel || pointSelected(part->getPoint(i-1)); // are the handles visible? + bool after = sel || pointSelected(part->getPoint(i+1)); + if (before && p->segmentBefore == SEGMENT_CURVE) { + if (inRange(p->pos + p->deltaBefore, pos, range)) { + return SelectedHandle(p, HANDLE_BEFORE); + } + } + if (after && p->segmentAfter == SEGMENT_CURVE) { + if (inRange(p->pos + p->deltaAfter, pos, range)) { + return SelectedHandle(p, HANDLE_AFTER); + } + } + } + // Nothing found + return HANDLE_NONE; +} + +bool SymbolPointEditor::inRange(const Vector2D& a, const Vector2D& b, double range) { + return abs(a.x - b.x) <= range && + abs(a.y - b.y) <= range; +} diff --git a/src/gui/symbol/point_editor.hpp b/src/gui/symbol/point_editor.hpp new file mode 100644 index 00000000..fdca6037 --- /dev/null +++ b/src/gui/symbol/point_editor.hpp @@ -0,0 +1,169 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_SYMBOL_POINT_EDITOR +#define HEADER_GUI_SYMBOL_POINT_EDITOR + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +class HandleMoveAction; +class ControlPointMoveAction; +class CurveDragAction; + +// ----------------------------------------------------------------------------- : SymbolPointEditor + + +// Symbol editor for editing control points and handles +class SymbolPointEditor : public SymbolEditorBase { + public: + SymbolPointEditor(SymbolControl* control, const SymbolPartP& part); + + // --------------------------------------------------- : Drawing + + virtual void draw(DC& dc); + + private: + /// Draws a gradient on the selected line to indicate curve dragging + void drawHoveredLine(DC& dc); + /// Draw all handles belonging to selected points + void drawHandles(DC& dc); + /// Draws the point to be inserted + void drawNewPoint(DC& dc); + /// Draw a single control point + void drawControlPoint(DC& dc, const ControlPoint& p, bool drawHandleBefore, bool drawHandleAfter); + /// Draws a handle as a box + void drawHandleBox(DC& dc, UInt px, UInt py, bool active); + /// Draws a handle as a circle + void drawHandleCircle(DC& dc, UInt px, UInt py); + + enum WhichPen { + PEN_NORMAL, //^ Pen for normal handles + PEN_HOVER, //^ Pen for hovered handles + PEN_LINE, //^ Pen for the line to handles + PEN_MAIN, //^ Pen for the main handle + PEN_NEW_POINT //^ Pen for the new point + }; + /// Retrieve a pen for the drawing of parts of handles + wxPen handlePen(WhichPen p, LockMode lock); + /// Retrieve a pen for the drawing of other things + wxPen otherPen(WhichPen p); + + public: + // --------------------------------------------------- : UI + + virtual void initUI(wxToolBar* tb, wxMenuBar* mb); + virtual void destroyUI(wxToolBar* tb, wxMenuBar* mb); + virtual void onUpdateUI(wxUpdateUIEvent& e); + 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 onLeftDClick(const Vector2D& pos, wxMouseEvent& ev); + virtual void onMouseMove(const Vector2D& from, const Vector2D& to, wxMouseEvent& ev); + virtual void onMouseDrag(const Vector2D& from, const Vector2D& to, wxMouseEvent& ev); + + // --------------------------------------------------- : Other events + + virtual void onKeyChange(wxKeyEvent& ev); + virtual void onChar(wxKeyEvent& ev); + virtual bool isEditing(); + + private: + // --------------------------------------------------- : Data + + // The symbol part we are editing + SymbolPartP part; + + // Actions in progress + // All are owned by the action stack, or they are 0 + HandleMoveAction* handleMoveAction; + ControlPointMoveAction* controlPointMoveAction; + CurveDragAction* curveDragAction; + + // Selection + enum Selection { + SELECTED_NONE, //^ no selection + SELECTED_POINTS, //^ some points are selected + SELECTED_HANDLE, //^ a handle is selected + SELECTED_LINE, //^ a line is selected + SELECTED_NEW_POINT //^ a new point on a line (used for hovering) + }; + Selection selection; + // points + set selectedPoints; + // handle + SelectedHandle selectedHandle; + // line + ControlPointP selectedLine1, selectedLine2; // selected the line between these points + double selectedLineT; // time on the line of the selection + + // Mouse feedback + Selection hovering; + // handle + SelectedHandle hoverHandle; // the handle currently under the cursor + // new point + Vector2D newPoint; + // line + ControlPointP hoverLine1, hoverLine2; // hovering on the line between these points + double hoverLineT; + int hoverLine1Idx; // index of hoverLine1 in the list of points + + // Gui stock + wxBitmap background; + wxCursor pointSelect, pointAdd, pointCurve, pointMove; + + // --------------------------------------------------- : Selection + + /// Clears the selection + void selectNothing(); + /// Select a point, if toggle then toggles the selection of the point + void selectPoint(const ControlPointP& point, bool toggle); + void selectHandle(const SelectedHandle& h, const wxMouseEvent& keystate); + void selectLine(const wxMouseEvent& keystate); + + /// Is a point selected? + bool pointSelected(const ControlPointP& pnt); + bool pointSelected(const ControlPoint& pnt); + /// Is the mouse pointer above a point? + bool pointHovered(const ControlPointP& pnt); + bool pointHovered(const ControlPoint& pnt); + /// Is the mouse pointer above a handle of a point? + bool handleHovered(const ControlPointP& pnt, WhichHandle wh); + bool handleHovered(const ControlPoint& pnt, WhichHandle wh); + + // --------------------------------------------------- : Actions + + // Finalize actions; new events start new actions + void resetActions(); + void deleteSelection(); + void onChangeSegment(SegmentMode mode); + void onChangeLock (LockMode mode); + + // --------------------------------------------------- : Finding items + + /// Finds the item that is currently being hovered, stores the results in hover* + void findHoveredItem(const Vector2D& pos, bool altDown); + + /// Is the specified position on a curve? + /// If so, sets hoverLine*, and set hovering=hoveringLine + bool checkPosOnCurve(const Vector2D& pos); + + /// Finds a handle at or near pos + SelectedHandle findHandle(const Vector2D& pos); + + /// Is the manhatan distance between two points <= range? + bool inRange(const Vector2D& a, const Vector2D& b, double range); +}; + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/symbol/select_editor.cpp b/src/gui/symbol/select_editor.cpp new file mode 100644 index 00000000..7ee35e79 --- /dev/null +++ b/src/gui/symbol/select_editor.cpp @@ -0,0 +1,424 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- : SymbolSelectEditor + +SymbolSelectEditor::SymbolSelectEditor(SymbolControl* control, bool rotate) + : SymbolEditorBase(control) + , rotate(rotate) + , cursorRotate(_("CUR_ROTATE"), wxBITMAP_TYPE_CUR_RESOURCE) + , cursorShearX(_("CUR_SHEAR_X"), wxBITMAP_TYPE_CUR_RESOURCE) + , cursorShearY(_("CUR_SHEAR_Y"), wxBITMAP_TYPE_CUR_RESOURCE) +{ + // Load resource images + Image rot = loadResourceImage(_("HANDLE_ROTATE")); + handleRotateTL = wxBitmap(rot); + handleRotateTR = wxBitmap(rotateImageBy(rot,90)); + handleRotateBR = wxBitmap(rotateImageBy(rot,180)); + handleRotateBL = wxBitmap(rotateImageBy(rot,270)); + Image shear = loadResourceImage(_("HANDLE_SHEAR_X")); + handleShearX = wxBitmap(shear); + handleShearY = wxBitmap(rotateImageBy(shear,90)); + handleCenter = wxBitmap(loadResourceImage(_("HANDLE_CENTER"))); + // Make sure all parts have updated bounds + FOR_EACH(p, getSymbol()->parts) { + p->calculateBounds(); + } + resetActions(); +} + +// ----------------------------------------------------------------------------- : Drawing + +void SymbolSelectEditor::draw(DC& dc) { + // highlight selected parts + FOR_EACH(p, control.selectedParts) { + control.highlightPart(dc, *p, HIGHLIGHT_INTERIOR); + } + // highlight the part under the cursor + if (highlightPart) { + control.highlightPart(dc, *highlightPart, HIGHLIGHT_BORDER); + } + // draw handles + drawHandles(dc); +} + +void SymbolSelectEditor::drawHandles(DC& dc) { + if (control.selectedParts.empty()) return; + if (rotateAction) return; // not when rotating + updateBoundingBox(); + // Draw handles on all sides + for (int dx = -1 ; dx <= 1 ; ++dx) { + for (int dy = -1 ; dy <= 1 ; ++dy) { + if (dx != 0 || dy != 0) { + // no handle in the center + drawHandle(dc, dx, dy); + } + } + } + // Draw rotation center? + if (rotate) { + drawRotationCenter(dc, center); + } +} + +void SymbolSelectEditor::drawHandle(DC& dc, int dx, int dy) { + wxPoint p = control.rotation.tr(handlePos(dx, dy)); + p.x += 4 * dx; + p.y += 4 * dy; + if (rotate) { + // rotate or shear handle + if (dx == 0) dc.DrawBitmap(handleShearX, p.x - 10, p.y - 3 - (dy < 0 ? 1 : 0)); + if (dy == 0) dc.DrawBitmap(handleShearY, p.x - 3 - (dx < 0 ? 1 : 0), p.y - 10); + else { + // rotate + if (dx == -1 && dy == -1) dc.DrawBitmap(handleRotateTL, p.x - 5, p.y - 5); + if (dx == -1 && dy == 1) dc.DrawBitmap(handleRotateTR, p.x - 5, p.y - 11); + if (dx == 1 && dy == -1) dc.DrawBitmap(handleRotateBL, p.x - 11, p.y - 5); + if (dx == 1 && dy == 1) dc.DrawBitmap(handleRotateBR, p.x - 11, p.y - 11); + } + } else { + // resize handle + dc.SetBrush(*wxBLUE_BRUSH); + dc.SetPen( *wxWHITE_PEN); + dc.DrawRectangle(p.x - 3, p.y - 3, 6, 6); + } +} + +void SymbolSelectEditor::drawRotationCenter(DC& dc, const Vector2D& pos) { + wxPoint p = control.rotation.tr(pos); + dc.DrawBitmap(handleCenter, p.x - 9, p.y - 9); +} + +// ----------------------------------------------------------------------------- : UI + +void SymbolSelectEditor::initUI(wxToolBar* tb, wxMenuBar* mb) { + tb->AddSeparator(); + tb->AddTool(ID_PART_MERGE, _("Merge"), loadResourceImage(_("COMBINE_OR")), wxNullBitmap, wxITEM_CHECK, _("Merge with shapes below"), _("Merges this shape with those below it")); + tb->AddTool(ID_PART_SUBTRACT, _("Subtract"), loadResourceImage(_("COMBINE_SUB_DARK")), wxNullBitmap, wxITEM_CHECK, _("Subtract from shapes below"), _("Subtracts this shape from shapes below it, leaves only the area in that shape that is not in this shape")); + tb->AddTool(ID_PART_INTERSECTION, _("Intersect"), loadResourceImage(_("COMBINE_AND_DARK")), wxNullBitmap, wxITEM_CHECK, _("Intersect with shapes below"), _("Intersects this shape with shapes below it, leaves only the area in both shapes")); + // note: difference doesn't work (yet) + tb->AddTool(ID_PART_OVERLAP, _("Overlap"), loadResourceImage(_("COMBINE_OVER")), wxNullBitmap, wxITEM_CHECK, _("Place above other shapes"), _("Place this shape, and its border above shapes below it")); + tb->AddTool(ID_PART_BORDER, _("Border"), loadResourceImage(_("COMBINE_BORDER")), wxNullBitmap, wxITEM_CHECK, _("Draw as a border"), _("Draws this shape as a border")); + tb->Realize(); +} +void SymbolSelectEditor::destroyUI(wxToolBar* tb, wxMenuBar* mb) { + tb->DeleteTool(ID_PART_MERGE); + tb->DeleteTool(ID_PART_SUBTRACT); + tb->DeleteTool(ID_PART_INTERSECTION); + tb->DeleteTool(ID_PART_OVERLAP); + tb->DeleteTool(ID_PART_BORDER); + // HACK: hardcoded size of rest of toolbar + tb->DeleteToolByPos(4); // delete separator +} + +void SymbolSelectEditor::onUpdateUI(wxUpdateUIEvent& ev) { + if (ev.GetId() >= ID_PART && ev.GetId() < ID_PART_MAX) { + if (control.selectedParts.empty()) { + ev.Check(false); + ev.Enable(false); + } else { + ev.Enable(true); + bool check = true; + FOR_EACH(p, control.selectedParts) { + if (p->combine != ev.GetId() - ID_PART) { + check = false; + break; + } + } + ev.Check(check); + } + } else if (ev.GetId() == ID_EDIT_DUPLICATE) { + ev.Enable(!control.selectedParts.empty()); + } else { + ev.Enable(false); // we don't know about this item + } +} + +void SymbolSelectEditor::onCommand(int id) { + if (id >= ID_PART && id < ID_PART_MAX) { + // change combine mode + getSymbol()->actions.add(new CombiningModeAction( + control.selectedParts, + static_cast(id - ID_PART) + )); + control.Refresh(false); + } else if (id == ID_EDIT_DUPLICATE && !isEditing()) { + // duplicate selection, not when dragging + DuplicateSymbolPartsAction* action = new DuplicateSymbolPartsAction( + *getSymbol(), control.selectedParts + ); + getSymbol()->actions.add(action); + control.Refresh(false); + } +} + +int SymbolSelectEditor::modeToolId() { + return rotate ? ID_MODE_ROTATE : ID_MODE_SELECT; +} + +// ----------------------------------------------------------------------------- : Mouse Events + +void SymbolSelectEditor::onLeftDown (const Vector2D& pos, wxMouseEvent& ev) { +} + +void SymbolSelectEditor::onLeftUp (const Vector2D& pos, wxMouseEvent& ev) { + if (isEditing()) { + // stop editing + resetActions(); + } else { + // mouse not moved, change selection + // Are we on a handle? + int dx, dy; + if (onAnyHandle(pos, &dx, &dy)) return; // don't change the selection + // Select the part under the cursor + SymbolPartP part = findPart(pos); + if (part) { + if (ev.ShiftDown()) { + // toggle selection + set::iterator it = control.selectedParts.find(part); + if (it != control.selectedParts.end()) { + control.selectedParts.erase(it); + } else { + control.selectedParts.insert(part); + } + } else { + if (control.selectedParts.find(part) != control.selectedParts.end()) { + // already selected, don't change selection + // instead switch between rotate and resize mode + rotate = !rotate; + } else { + // select the part under the cursor + control.selectedParts.clear(); + control.selectedParts.insert(part); + } + } + } else if (!ev.ShiftDown()) { + // select nothing + control.selectedParts.clear(); + } + // selection has changed + updateBoundingBox(); + control.signalSelectionChange(); + } + control.Refresh(false); +} + +void SymbolSelectEditor::onLeftDClick(const Vector2D& pos, wxMouseEvent& ev) { + // start editing the points of the clicked part + highlightPart = findPart(pos); + if (highlightPart) { + control.activatePart(highlightPart); + } +} + +void SymbolSelectEditor::onMouseMove (const Vector2D& from, const Vector2D& to, wxMouseEvent& e) { + // can we highlight a part? + highlightPart = findPart(to); + // are we on a handle? + int dx, dy; + if (!control.selectedParts.empty() && onAnyHandle(to, &dx, &dy)) { + // we are on a handle, don't highlight + highlightPart = SymbolPartP(); + if (rotate) { + // shear or rotating? + if (dx == 0 || dy == 0) { + SetStatusText(String(_("Drag to shear selected shape")) + (control.selectedParts.size() > 1 ? _("s") : _(""))); + control.SetCursor(dx == 0 ? cursorShearX : cursorShearY); + } else { + SetStatusText(String(_("Drag to rotate selected shape")) + (control.selectedParts.size() > 1 ? _("s") : _("")) + _(", Ctrl constrains angle to multiples of 15 degrees")); + control.SetCursor(cursorRotate); + } + } else { + SetStatusText(String(_("Drag to resize selected shape")) + (control.selectedParts.size() > 1 ? _("s") : _("")) + _(", Ctrl constrains size")); + // what cursor to use? + if (dx == dy) control.SetCursor(wxCURSOR_SIZENWSE); + else if (dx == -dy) control.SetCursor(wxCURSOR_SIZENESW); + else if (dx == 0) control.SetCursor(wxCURSOR_SIZENS); + else if (dy == 0) control.SetCursor(wxCURSOR_SIZEWE); + } + } else { + if (highlightPart) { + SetStatusText(_("Click to select shape, drag to move shape, double click to edit shape")); + } else { + SetStatusText(_("")); + } + control.SetCursor(*wxSTANDARD_CURSOR); + } + control.Refresh(false); +} + +void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) { + if (control.selectedParts.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 + if (onAnyHandle(from, &scaleX, &scaleY)) { + if (rotate) { + if (scaleX == 0 || scaleY == 0) { + // shear, center/fixed point on the opposite side + shearAction = new SymbolPartShearAction(control.selectedParts, handlePos(-scaleX, -scaleY)); + getSymbol()->actions.add(shearAction); + } else { + // rotate + rotateAction = new SymbolPartRotateAction(control.selectedParts, center); + getSymbol()->actions.add(rotateAction); + startAngle = angleTo(to); + } + } else { + // we are on a handle; start scaling + scaleAction = new SymbolPartScaleAction(control.selectedParts, scaleX, scaleY); + getSymbol()->actions.add(scaleAction); + } + } else { + // move + moveAction = new SymbolPartMoveAction(control.selectedParts); + getSymbol()->actions.add(moveAction); + } + } + + // now update the action + if (moveAction) { + // move the selected parts + moveAction->constrain = ev.ControlDown(); + moveAction->move(to - from); + } else if (scaleAction) { + // scale the selected parts + Vector2D delta = to-from; + Vector2D dMin, dMax; + if (scaleX == -1) dMin.x = delta.x; + if (scaleX == 1) dMax.x = delta.x; + if (scaleY == -1) dMin.y = delta.y; + if (scaleY == 1) dMax.y = delta.y; + scaleAction->constrain = ev.ControlDown(); + scaleAction->move(dMin, dMax); + } else if (rotateAction) { + // rotate the selected parts + double angle = angleTo(to); + rotateAction->constrain = ev.ControlDown(); + rotateAction->rotateTo(startAngle - angle); + } else if (shearAction) { + // shear the selected parts + Vector2D delta = to-from; + delta = delta.mul(Vector2D(scaleY, scaleX)); + delta = delta.div(maxV - minV); + shearAction->constrain = ev.ControlDown(); + shearAction->move(delta); + } + control.Refresh(false); +} + +// ----------------------------------------------------------------------------- : Key Events + +void SymbolSelectEditor::onKeyChange (wxKeyEvent& ev) { + if (ev.GetKeyCode() == WXK_CONTROL) { + // changed constrains + if (moveAction) { + moveAction->constrain = ev.ControlDown(); + moveAction->move(Vector2D()); // apply constrains + control.Refresh(false); + } else if (scaleAction) { + // only allow constrained scaling in diagonal direction + scaleAction->constrain = ev.ControlDown(); + scaleAction->update(); // apply constrains + control.Refresh(false); + } else if (rotateAction) { + rotateAction->constrain = ev.ControlDown(); + rotateAction->rotateBy(0); // apply constrains + control.Refresh(false); + } + } +} +void SymbolSelectEditor::onChar(wxKeyEvent& ev) { + if (ev.GetKeyCode() == WXK_DELETE) { + // delete selected parts + getSymbol()->actions.add(new RemoveSymbolPartsAction(*getSymbol(), control.selectedParts)); + control.selectedParts.clear(); + resetActions(); + control.Refresh(false); + } else { + ev.Skip(); + } +} + +bool SymbolSelectEditor::isEditing() { + return moveAction || scaleAction || rotateAction || shearAction; +} + +// ----------------------------------------------------------------------------- : Other + +Vector2D SymbolSelectEditor::handlePos(int dx, int dy) { + return Vector2D( + 0.5 * (maxV.x + minV.x + dx * (maxV.x - minV.x)), + 0.5 * (maxV.y + minV.y + dy * (maxV.y - minV.y)) + ); +} + +bool SymbolSelectEditor::onHandle(const Vector2D& mpos, int dx, int dy) { + wxPoint p = control.rotation.tr(handlePos(dx, dy)); + wxPoint mp = control.rotation.tr(mpos); + p.x = p.x + 4 * dx; + p.y = p.y + 4 * dy; + return mp.x >= p.x - 4 && mp.x < p.x + 4 && + mp.y >= p.y - 4 && mp.y < p.y + 4; +} +bool SymbolSelectEditor::onAnyHandle(const Vector2D& mpos, int* dxOut, int* dyOut) { + for (int dx = -1 ; dx < 1 ; ++dx) { + for (int dy = -1 ; dy < 1 ; ++dy) { + if ((dx != 0 || dy != 0) && onHandle(mpos, dx, dy)) { // (0,0) == center, not a handle + *dxOut = dx; + *dyOut = dy; + return true; + } + } + } + return false; +} + +double SymbolSelectEditor::angleTo(const Vector2D& pos) { + return atan2(center.x - pos.x, center.y - pos.y); +} + + +SymbolPartP SymbolSelectEditor::findPart(const Vector2D& pos) { + FOR_EACH(p, getSymbol()->parts) { + if (pointInPart(pos, *p)) return p; + } + return SymbolPartP(); +} + +void SymbolSelectEditor::updateBoundingBox() { + // Find min and max coordinates + minV = Vector2D::infinity(); + maxV = -Vector2D::infinity(); + FOR_EACH(p, control.selectedParts) { + minV = piecewise_min(minV, p->minPos); + maxV = piecewise_max(maxV, p->maxPos); + } + // Find rotation center + center = Vector2D(0,0); + FOR_EACH(p, control.selectedParts) { + Vector2D size = p->maxPos - p->minPos; + size = size.mul(p->rotationCenter); + center += p->minPos + size; + } + center /= control.selectedParts.size(); +} + +void SymbolSelectEditor::resetActions() { + moveAction = nullptr; + scaleAction = nullptr; + rotateAction = nullptr; + shearAction = nullptr; +} diff --git a/src/gui/symbol/select_editor.hpp b/src/gui/symbol/select_editor.hpp new file mode 100644 index 00000000..e96c4d41 --- /dev/null +++ b/src/gui/symbol/select_editor.hpp @@ -0,0 +1,117 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_SYMBOL_SELECT_EDITOR +#define HEADER_GUI_SYMBOL_SELECT_EDITOR + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +DECLARE_POINTER_TYPE(SymbolPartMoveAction); +DECLARE_POINTER_TYPE(SymbolPartScaleAction); +DECLARE_POINTER_TYPE(SymbolPartRotateAction); +DECLARE_POINTER_TYPE(SymbolPartShearAction); + +// ----------------------------------------------------------------------------- : SymbolSelectEditor + +/// Editor that allows the user to select symbol parts +class SymbolSelectEditor : public SymbolEditorBase { + public: + SymbolSelectEditor(SymbolControl* control, bool rotate); + + // --------------------------------------------------- : Drawing + + virtual void draw(DC& dc); + + private: + /// Draw handles on all sides + void drawHandles(DC& dc); + /// Draw a handle, dx and dy indicate the side, can be {-1,0,1} + void drawHandle(DC& dc, int dx, int dy); + + /// Draw the rotation center + void drawRotationCenter(DC& dc, const Vector2D& pos); + + public: + // --------------------------------------------------- : UI + + virtual void initUI (wxToolBar* tb, wxMenuBar* mb); + virtual void destroyUI(wxToolBar* tb, wxMenuBar* mb); + virtual void onUpdateUI(wxUpdateUIEvent& e); + virtual void onCommand(int id); + virtual int modeToolId(); + + // --------------------------------------------------- : Mouse events + + virtual void onLeftDown (const Vector2D& pos, wxMouseEvent& ev); + virtual void onLeftDClick (const Vector2D& pos, wxMouseEvent& ev); + virtual void onLeftUp (const Vector2D& pos, wxMouseEvent& ev); + virtual void onMouseMove (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev); + virtual void onMouseDrag (const Vector2D& from, const Vector2D& to, wxMouseEvent& ev); + + // --------------------------------------------------- : Other events + + virtual void onKeyChange (wxKeyEvent& ev); + virtual void onChar (wxKeyEvent& ev); + + virtual bool isEditing(); + + private: + // The part under the mouse cursor + SymbolPartP highlightPart; + // Actions + // All are either owned by the symbol's action stack or equal 0 + SymbolPartMoveAction* moveAction; + SymbolPartScaleAction* scaleAction; + SymbolPartRotateAction* rotateAction; + SymbolPartShearAction* shearAction; + // Bounding box of selection + Vector2D minV, maxV; + // Where is the rotation center? + Vector2D center; + // At what angle is the handle we started draging for rotation + double startAngle; + // what side are we dragging/rotating on? + int scaleX, scaleY; + // Do we want to rotate? + bool rotate; + // Graphics assets + wxCursor cursorRotate; + wxCursor cursorShearX; + wxCursor cursorShearY; + Bitmap handleRotateTL, handleRotateTR, handleRotateBL, handleRotateBR; + Bitmap handleShearX, handleShearY; + Bitmap handleCenter; + + /// Is the mouse on a scale/rotate handle? + bool onHandle(const Vector2D& mpos, int dx, int dy); + + /// Is the mouse on any handle? + /** Returns the handle coordinates [-1..1] in d?Out + */ + bool onAnyHandle(const Vector2D& mpos, int* dxOut, int* dyOut); + + /// Angle between center and pos + double angleTo(const Vector2D& pos); + + /// Return the position of a handle, dx,dy in <-1, 0, 1> + Vector2D handlePos(int dx, int dy); + + /// Find the first part at the given position + SymbolPartP findPart(const Vector2D& pos); + + /// Update minV and maxV to be the bounding box of the selectedParts + /// Updates center to be the rotation center of the parts + void updateBoundingBox(); + + /// Reset all the actions to 0 + void resetActions(); +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/symbol/viewer.cpp b/src/gui/symbol/viewer.cpp new file mode 100644 index 00000000..55fd7133 --- /dev/null +++ b/src/gui/symbol/viewer.cpp @@ -0,0 +1,220 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include + +// ----------------------------------------------------------------------------- : Constructor + +SymbolViewer::SymbolViewer(const SymbolP& symbol, double borderRadius) + : borderRadius(borderRadius) + , SymbolView(symbol) + , rotation(0, RealRect(0,0,500,500)) +{} + +// ----------------------------------------------------------------------------- : Drawing + +typedef shared_ptr MemoryDCP; + +// Return a temporary DC with the same size as the parameter +MemoryDCP getTempDC(DC& dc) { + wxSize s = dc.GetSize(); + Bitmap buffer(s.GetWidth(), s.GetHeight(), 1); + MemoryDCP newDC(new wxMemoryDC); + newDC->SelectObject(buffer); + // On windows 9x it seems that bitmaps are not black by default + #if !BITMAPS_DEFAULT_BLACK + newDC->SetPen(*wxTRANSPARENT_PEN); + newDC->SetBrush(*wxBLACK_BRUSH); + newDC->DrawRectangle(0, 0, s.GetWidth(), s.GetHeight()); + #endif + return newDC; +} + +// Combine the temporary DCs used in the drawing with the main dc +void combineBuffers(DC& dc, DC* borders, DC* interior) { + wxSize s = dc.GetSize(); + if (borders) dc.Blit(0, 0, s.GetWidth(), s.GetHeight(), borders, 0, 0, wxOR); + if (interior) dc.Blit(0, 0, s.GetWidth(), s.GetHeight(), interior, 0, 0, wxAND_INVERT); +} + +void SymbolViewer::draw(DC& dc) { + bool paintedSomething = false; + bool buffersFilled = false; + // Temporary dcs + MemoryDCP borderDC; + MemoryDCP interiorDC; + // Check if we can paint directly to the dc + // This will fail if there are parts with combine == intersection + FOR_EACH(p, symbol->parts) { + if (p->combine == PART_INTERSECTION) { + paintedSomething = true; + break; + } + } + // Draw all parts, in reverse order (bottom to top) + FOR_EACH_REVERSE(p, symbol->parts) { + const SymbolPart& part = *p; + if (part.combine == PART_OVERLAP && buffersFilled) { + // We will be overlapping some previous parts, write them to the screen + combineBuffers(dc, borderDC.get(), interiorDC.get()); + // Clear the buffers + buffersFilled = false; + paintedSomething = true; + wxSize s = dc.GetSize(); + if (borderDC) { + borderDC->SetBrush(*wxBLACK_BRUSH); + borderDC->SetPen( *wxTRANSPARENT_PEN); + borderDC->DrawRectangle(0, 0, s.GetWidth(), s.GetHeight()); + } + interiorDC->SetBrush(*wxBLACK_BRUSH); + interiorDC->DrawRectangle(0, 0, s.GetWidth(), s.GetHeight()); + } + + if (!paintedSomething) { + // No need to buffer + if (!interiorDC) interiorDC = getTempDC(dc); + combineSymbolPart(part, dc, *interiorDC, true, false); + buffersFilled = true; + } else { + if (!borderDC) borderDC = getTempDC(dc); + if (!interiorDC) interiorDC = getTempDC(dc); + // Draw this part to the buffer + combineSymbolPart(part, *borderDC, *interiorDC, false, false); + buffersFilled = true; + } + } + + // Output the final parts from the buffer + if (buffersFilled) { + combineBuffers(dc, borderDC.get(), interiorDC.get()); + } +} + +void SymbolViewer::highlightPart(DC& dc, const SymbolPart& part, HighlightStyle style) { + // create point list + vector points; + size_t size = part.points.size(); + for(size_t i = 0 ; i < size ; ++i) { + segmentSubdivide(*part.getPoint((int)i), *part.getPoint((int)i+1), rotation, points); + } + // draw + if (style == HIGHLIGHT_BORDER) { + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.SetPen (wxPen(Color(255,0,0), 2)); + dc.DrawPolygon((int)points.size(), &points[0]); + } else { + dc.SetLogicalFunction(wxOR); + dc.SetBrush(Color(0,0,64)); + dc.SetPen (*wxTRANSPARENT_PEN); + dc.DrawPolygon((int)points.size(), &points[0]); + if (part.combine == PART_SUBTRACT || part.combine == PART_BORDER) { + dc.SetLogicalFunction(wxAND); + dc.SetBrush(Color(192,192,255)); + dc.DrawPolygon((int)points.size(), &points[0]); + } + dc.SetLogicalFunction(wxCOPY); + } +} + + +void SymbolViewer::combineSymbolPart(const SymbolPart& part, DC& border, DC& interior, bool directB, bool directI) { + // what color should the interior be? + // use black when drawing to the screen + int interiorCol = directI ? 0 : 255; + // how to draw depends on combining mode + switch(part.combine) { + case PART_OVERLAP: + case PART_MERGE: { + drawSymbolPart(part, &border, &interior, 255, interiorCol, directB); + break; + } case PART_SUBTRACT: { + border.SetLogicalFunction(wxAND); + drawSymbolPart(part, &border, &interior, 0, interiorCol ^ 255, directB); + border.SetLogicalFunction(wxCOPY); + break; + } case PART_INTERSECTION: { + MemoryDCP keepBorder = getTempDC(border); + MemoryDCP keepInterior = getTempDC(interior); + drawSymbolPart(part, keepBorder.get(), keepInterior.get(), 255, 255, false); + // combine the temporary dcs with the result using the AND operator + wxSize s = border.GetSize(); + border .Blit(0, 0, s.GetWidth(), s.GetHeight(), &*keepBorder, 0, 0, wxAND); + interior.Blit(0, 0, s.GetWidth(), s.GetHeight(), &*keepInterior, 0, 0, wxAND); + break; + } case PART_DIFFERENCE: { + // TODO + break; + } case PART_BORDER: { + // draw border as interior + drawSymbolPart(part, nullptr, &border, 0, 255, false); + break; + } + } +} + +void SymbolViewer::drawSymbolPart(const SymbolPart& part, DC* border, DC* interior, int borderCol, int interiorCol, bool directB) { + // create point list + vector points; + size_t size = part.points.size(); + for(size_t i = 0 ; i < size ; ++i) { + segmentSubdivide(*part.getPoint((int)i), *part.getPoint((int)i+1), rotation, points); + } + // draw border + if (border) { + if (directB) { + // white/green + border->SetBrush(Color(borderCol, min(255,borderCol + 128), borderCol)); + } else { + // white/black + border->SetBrush(Color(borderCol, borderCol, borderCol)); + } + border->SetPen(wxPen(*wxWHITE, rotation.trS(borderRadius))); + border->DrawPolygon((int)points.size(), &points[0]); + } + // draw interior + if (interior) { + interior->SetBrush(Color(interiorCol,interiorCol,interiorCol)); + interior->SetPen(*wxTRANSPARENT_PEN); + interior->DrawPolygon((int)points.size(), &points[0]); + } +} + + +/* +void SymbolViewer::calcBezierPoint(const ControlPointP& p0, const ControlPointP& p1, Point*& p_out, UInt count) { + BezierCurve c(*p0, *p1); + // add start point + *p_out = toDisplay(*p0); + ++p_out; + // recursively calculate rest of curve + calcBezierOpt(c, *p0, *p1, 0.0f, 1.0f, p_out, count-1); +} + + +void SymbolViewer::calcBezierOpt(const BezierCurve& c, const Vector2D& p0, const Vector2D& p1, double t0, double t1, Point*& p_out, mutable UInt count) { + if (count <= 0) return; + double midtime = (t0+t1) * 0.5f; + Vector2D midpoint = c.pointAt(midtime); + Vector2D d0 = p0 - midpoint; + Vector2D d1 = midpoint - p1; + // Determine treshold for subdivision, greater angle -> subdivide + // greater size -> subdivide + double treshold = fabs( atan2(d0.x,d0.y) - atan2(d1.x,d1.y)) * (p0-p1).lengthSqr(); + bool subdivide = treshold >= .0001; + // subdivide left + calcBezierOpt(c, p0, midpoint, t0, midtime, p_out, count/2); + // add midpoint + if (subdivide) { + *p_out = toDisplay(midpoint); + ++p_out; + count -= 1; + } + // subdivide right + calcBezierOpt(c, midpoint, p1, midtime, t1, p_out, count/2); +} +*/ \ No newline at end of file diff --git a/src/gui/symbol/viewer.hpp b/src/gui/symbol/viewer.hpp new file mode 100644 index 00000000..9f4dd11b --- /dev/null +++ b/src/gui/symbol/viewer.hpp @@ -0,0 +1,74 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_SYMBOL_VIEWER +#define HEADER_GUI_SYMBOL_VIEWER + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- : Symbol Viewer + +enum HighlightStyle { + HIGHLIGHT_BORDER, + HIGHLIGHT_INTERIOR +}; + +/// Class that knows how to draw a symbol +class SymbolViewer : public SymbolView { + public: + // --------------------------------------------------- : Data + SymbolViewer(const SymbolP& symbol, double borderRadius = 0.05); + + // drawing + double borderRadius; + + // --------------------------------------------------- : Point translation + + Rotation rotation; //^ Object that handles rotation, scaling and translation + + // --------------------------------------------------- : Drawing + + public: + /// Draw the symbol to a dc + void draw(DC& dc); + + void highlightPart(DC& dc, const SymbolPart& part, HighlightStyle style); + + private: + /// Combines a symbol part with what is currently drawn, the border and interior are drawn separatly + /** directB/directI are true if the border/interior is the screen dc, false if it + * is a temporary 1 bit one + */ + void combineSymbolPart(const SymbolPart& part, DC& border, DC& interior, bool directB, bool directI); + + /// Draw a symbol part, draws the border and the interior to separate DCs + /** The DCs may be null. directB should be true when drawing the border directly to the screen. + * The **Col parameters give the color to use for the (interior of) the border and the interior + * default should be white (255) border and black (0) interior. + */ + void drawSymbolPart(const SymbolPart& part, DC* border, DC* interior, int borderCol, int interiorCol, bool directB); +/* + // ------------------- Bezier curve calculation + + // Calculate the points on a bezier curve between p0 and p1 + // Stores the Points in p_out, at most count points are stored + // after this call p_out points to just beyond the last point + void calcBezierPoint(const ControlPointP& p0, const ControlPointP& p1, wxPoint*& p_out, UInt count); + + // Subdivide a bezier curve by adding at most count points + // p0 = c(t0), p1 = c(p1) + // subdivides linearly between t0 and t1, and only when necessary + // adds points to p_out and increments the pointer when a point is added + void calcBezierOpt(const BezierCurve& c, const Vector2D& p0, const Vector2D& p1, double t0, double t1, wxPoint*& p_out, UInt count); +*/}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/symbol/window.cpp b/src/gui/symbol/window.cpp new file mode 100644 index 00000000..e38bac9a --- /dev/null +++ b/src/gui/symbol/window.cpp @@ -0,0 +1,283 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- : Window ids + +enum SymIDs +{ idFileNew = wxID_NEW +, idFileOpen = wxID_OPEN +, idFileSave = wxID_SAVE +, idFileSaveAs = wxID_SAVEAS +, idFileStore = 0 +, idFileExit = wxID_EXIT + +, idExtraTools = 1000 +, idExtraToolsMax = idExtraTools + 500 + +, idEditUndo = wxID_UNDO +, idEditRedo = wxID_REDO +, idEditDuplicate = 1100 // idExtraTools + 100 + +, idModeSelect = idFileStore + 1 +, idModeRotate +, idModePoints +, idModeShapes +, idModePaint +, idModeMax + +, idPartList +, idControl +}; + +// ------------------------------------------------------------------------------------------------ : Default symbol + +// A default symbol part, a square, moved by d +SymbolPartP defaultSymbolPart(double d) { + SymbolPartP part = new_shared(); + part->points.push_back(new_shared2(d + .2, d + .2)); + part->points.push_back(new_shared2(d + .2, d + .8)); + part->points.push_back(new_shared2(d + .8, d + .8)); + part->points.push_back(new_shared2(d + .8, d + .2)); + part->name = _("Square"); + return part; +} + +// A default symbol, a square +SymbolP defaultSymbol() { + SymbolP symbol = new_shared(); + symbol->parts.push_back(defaultSymbolPart(0)); + return symbol; +} + +// ----------------------------------------------------------------------------- : Constructor + +SymbolWindow::SymbolWindow(Window* parent) { + init(parent, defaultSymbol()); +} + +SymbolWindow::SymbolWindow(Window* parent, String filename) { + // TODO + init(parent, defaultSymbol()); +} + +void SymbolWindow::init(Window* parent, SymbolP symbol) { + Create(parent, wxID_ANY, _("Symbol Editor"), wxDefaultPosition, wxSize(600,600), wxDEFAULT_FRAME_STYLE | wxNO_FULL_REPAINT_ON_RESIZE); + inSelectionEvent = false; + + // Menu bar + wxMenuBar* menuBar = new wxMenuBar(); + IconMenu* menuFile = new IconMenu(); + menuFile->Append(ID_FILE_NEW, _("TOOL_NEW"), _("&New...\tCtrl+N"), _("Create a new symbol")); + menuFile->Append(ID_FILE_OPEN, _("TOOL_OPEN"), _("&Open...\tCtrl+O"), _("Open a symbol")); + menuFile->Append(ID_FILE_SAVE, _("TOOL_SAVE"), _("&Save\tCtrl+S"), _("Save the symbol")); + menuFile->Append(ID_FILE_SAVE_AS, _("Save &As...\tF12"), _("Save the symbol under a diferent filename")); + menuFile->AppendSeparator(); + menuFile->Append(ID_FILE_STORE, _("TOOL_APPLY"), _("S&tore\tCtrl+Enter"), _("Stores the symbol in the set")); + menuFile->AppendSeparator(); + menuFile->Append(ID_FILE_EXIT, _("&Close\tAlt+F4"), _("Closes the symbol editor")); + menuBar->Append(menuFile, _("&File")); + + IconMenu* menuEdit = new IconMenu(); + menuEdit->Append(ID_EDIT_UNDO, _("TOOL_UNDO"), _("&Undo\tCtrl+Z"), _("Undoes the last action")); + menuEdit->Append(ID_EDIT_REDO, _("TOOL_REDO"), _("&Redo\tF4"), _("")); + menuEdit->AppendSeparator(); + menuEdit->Append(ID_EDIT_DUPLICATE, _("TOOL_DUPLICATE"), _("&Duplicate\tCtrl+D"),_("Duplicates the selected shapes")); + menuBar->Append(menuEdit, _("&Edit")); + + IconMenu* menuTool = new IconMenu(); + menuTool->Append(ID_MODE_SELECT, _("TOOL_MODE_SELECT"), _("&Select\tF5"), _("Select and move shapes"), wxITEM_CHECK); + menuTool->Append(ID_MODE_ROTATE, _("TOOL_MODE_ROTATE"), _("&Rotate\tF6"), _("Rotate and shear shapes"), wxITEM_CHECK); + menuTool->Append(ID_MODE_POINTS, _("TOOL_MODE_CURVE"), _("&Points\tF7"), _("Edit control points for a shape in the symbol"), wxITEM_CHECK); + menuTool->Append(ID_MODE_SHAPES, _("TOOL_CIRCLE"), _("&Basic Shapes\tF8"), _("Draw basic shapes, such as rectangles and circles"), wxITEM_CHECK); + menuTool->Append(ID_MODE_PAINT, _("TOOL_MODE_PAINT"), _("P&aint\tF9"), _("Paint on the shape using a paintbrush"), wxITEM_CHECK); + menuBar->Append(menuTool, _("&Tool")); + + SetMenuBar(menuBar); + + // Statusbar + CreateStatusBar(); + SetStatusText(_("")); + + // Toolbar + wxToolBar* tb = CreateToolBar(wxTB_FLAT | wxNO_BORDER | wxTB_HORIZONTAL | wxTB_TEXT); + tb->AddTool(ID_FILE_STORE, _("Store"), Bitmap(_("TOOL_APPLY")), wxNullBitmap, wxITEM_NORMAL, _("Store symbol in set"), _("Stores the symbol in the set")); + tb->AddSeparator(); + tb->AddTool(ID_EDIT_UNDO, _("Undo"), Bitmap(_("TOOL_UNDO")), wxNullBitmap, wxITEM_NORMAL, _("Undo"), _("Undoes the last action")); + tb->AddTool(ID_EDIT_REDO, _("Redo"), Bitmap(_("TOOL_REDO")), wxNullBitmap, wxITEM_NORMAL, _("Redo"), _("Redoes the last action undone")); + tb->Realize(); + + // Edit mode toolbar + wxPanel* emp = new wxPanel(this, wxID_ANY); + wxToolBar* em = new wxToolBar(emp, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTB_FLAT | wxTB_VERTICAL | wxTB_TEXT | wxTB_HORZ_LAYOUT); + em->AddTool(ID_MODE_SELECT,_("Select"), Bitmap(_("TOOL_MODE_SELECT")), wxNullBitmap, wxITEM_CHECK, _("Select (F5)"), _("Select and move parts of the symbol")); + em->AddTool(ID_MODE_ROTATE,_("Rotate"), Bitmap(_("TOOL_MODE_ROTATE")), wxNullBitmap, wxITEM_CHECK, _("Rotate (F6)"), _("Rotate and shear parts of the symbol")); + em->AddSeparator(); + em->AddTool(ID_MODE_POINTS,_("Points"), Bitmap(_("TOOL_MODE_CURVE")), wxNullBitmap, wxITEM_CHECK, _("Points (F7)"), _("Edit control points for a shape in the symbol")); + em->AddSeparator(); + em->AddTool(ID_MODE_SHAPES,_("Basic Shapes"), Bitmap(_("TOOL_CIRCLE")), wxNullBitmap, wxITEM_CHECK, _("Basic Shapes (F8)"), _("Draw basic shapes, such as rectangles and circles")); + em->AddSeparator(); + em->AddTool(ID_MODE_PAINT, _("Paint"), Bitmap(_("TOOL_MODE_PAINT")), wxNullBitmap, wxITEM_CHECK, _("Paint on shape (F9)"), _("Paint on the shape using a paintbrush")); + em->AddSeparator(); + em->Realize(); + + // Controls + control = new SymbolControl (this, ID_CONTROL, symbol); + parts = new SymbolPartList(this, ID_PART_LIST, symbol); + + // Lay out + wxSizer* es = new wxBoxSizer(wxHORIZONTAL); + es->Add(em, 0, wxEXPAND | wxTOP | wxBOTTOM | wxALIGN_CENTER, 1); + emp->SetSizer(es); + + wxSizer* s = new wxBoxSizer(wxHORIZONTAL); + wxSizer* v = new wxBoxSizer(wxVERTICAL); + v->Add(emp, 0, wxEXPAND); + v->Add(parts, 1, wxEXPAND); + s->Add(v, 0, wxEXPAND); + s->Add(control, 1, wxEXPAND); + SetSizer(s); +} + +// ----------------------------------------------------------------------------- : Event handling + +void SymbolWindow::onFileNew(wxCommandEvent& ev) { + SymbolP symbol = defaultSymbol(); + parts->setSymbol(symbol); + control->setSymbol(symbol); +} + +void SymbolWindow::onFileOpen(wxCommandEvent& ev) { + String name = wxFileSelector(_("Open symbol"),_(""),_(""),_(""),_("Symbol files|*.mse-symbol;*.bmp|MSE2 symbol files (*.mse-symbol)|*.mse-symbol|MSE1 symbol files (*.bmp)|*.bmp"),wxOPEN|wxFILE_MUST_EXIST, this); + if (!name.empty()) { + wxFileName n(name); + String ext = n.GetExt(); + SymbolP symbol; + if (ext.Lower() == _("bmp")) { +//% symbol = importSymbol(wxImage(name)); + } else { + Reader reader(new_shared1(name), name); + reader.handle(symbol); + } + // show... + parts->setSymbol(symbol); + control->setSymbol(symbol); + } +} + +void SymbolWindow::onFileSave(wxCommandEvent& ev) { +} + +void SymbolWindow::onFileSaveAs(wxCommandEvent& ev) { +} + +void SymbolWindow::onFileStore(wxCommandEvent& ev) { +} + +void SymbolWindow::onFileExit(wxCommandEvent& ev) { + Close(); +} + + +void SymbolWindow::onEditUndo(wxCommandEvent& ev) { + if (!control->isEditing()) { + control->getSymbol()->actions.undo(); + control->Refresh(false); + } +} + +void SymbolWindow::onEditRedo(wxCommandEvent& ev) { + if (!control->isEditing()) { + control->getSymbol()->actions.redo(); + control->Refresh(false); + } +} + +void SymbolWindow::onModeChange(wxCommandEvent& ev) { + control->onModeChange(ev); +} + +void SymbolWindow::onExtraTool(wxCommandEvent& ev) { + control->onExtraTool(ev); +} + + +void SymbolWindow::onUpdateUI(wxUpdateUIEvent& ev) { + switch(ev.GetId()) { + // file menu + case idFileStore: { + // ev.Enable(value); + break; + // undo/redo + } case idEditUndo: { + ev.Enable(control->getSymbol()->actions.canUndo()); + String label = control->getSymbol()->actions.undoName(); + ev.SetText(label + _("\tCtrl+Z")); + GetToolBar()->SetToolShortHelp(ID_EDIT_UNDO, label); + break; + } case idEditRedo: { + ev.Enable(control->getSymbol()->actions.canRedo()); + String label = control->getSymbol()->actions.redoName(); + ev.SetText(label + _("\tF4")); + GetToolBar()->SetToolShortHelp(ID_EDIT_REDO, label); + break; + } default: { + // items created by the editor control + control->onUpdateUI(ev); + break; + } + } +} + + +void SymbolWindow::onSelectFromList(wxListEvent& ev) { + if (inSelectionEvent) return ; + inSelectionEvent = true; + parts->getSelectedParts(control->selectedParts); + control->onUpdateSelection(); + inSelectionEvent = false; +} +void SymbolWindow::onActivateFromList(wxListEvent& ev) { + control->activatePart(control->getSymbol()->parts.at(ev.GetIndex())); +} + +void SymbolWindow::onSelectFromControl() { + if (inSelectionEvent) return ; + inSelectionEvent = true; + parts->selectParts(control->selectedParts); + inSelectionEvent = false; +} + +// ----------------------------------------------------------------------------- : Event table + +BEGIN_EVENT_TABLE(SymbolWindow, wxFrame) + EVT_MENU (ID_FILE_NEW, SymbolWindow::onFileNew) + EVT_MENU (ID_FILE_OPEN, SymbolWindow::onFileOpen) + EVT_MENU (ID_FILE_SAVE, SymbolWindow::onFileSave) + EVT_MENU (ID_FILE_SAVE_AS, SymbolWindow::onFileSaveAs) + EVT_MENU (ID_FILE_STORE, SymbolWindow::onFileStore) + EVT_MENU (ID_FILE_EXIT, SymbolWindow::onFileExit) + EVT_MENU (ID_EDIT_UNDO, SymbolWindow::onEditUndo) + EVT_MENU (ID_EDIT_REDO, SymbolWindow::onEditRedo) + + EVT_TOOL_RANGE (ID_MODE_MIN, ID_MODE_MAX, SymbolWindow::onModeChange) + 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) +END_EVENT_TABLE () diff --git a/src/gui/symbol/window.hpp b/src/gui/symbol/window.hpp new file mode 100644 index 00000000..d4fa2244 --- /dev/null +++ b/src/gui/symbol/window.hpp @@ -0,0 +1,75 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_SYMBOL_WINDOW +#define HEADER_GUI_SYMBOL_WINDOW + +// ----------------------------------------------------------------------------- : Includes + +#include "../../util/prec.hpp" +#include +#include +//#include "control.hpp" + +class SymbolControl; +class SymbolPartList; + +// ----------------------------------------------------------------------------- : SymbolWindow + +/// The window for editing symbols +class SymbolWindow : public Frame { + public: + /// Construct a SymbolWindow + SymbolWindow(Window* parent); + /// Construct a SymbolWindow showing a symbol from a file + SymbolWindow(Window* parent, String filename); +// /// Construct a SymbolWindow showing a symbol from a set +// SymbolWindow(Window* parent); + + private: + // --------------------------------------------------- : Children + + /// Actual initialisation + void init(Window* parent, SymbolP symbol); + + SymbolControl* control; //^ The control for editing/displaying the symbol + SymbolPartList* parts; //^ A list of parts in the symbol + + // when editing a symbol field +// SymbolValueP value +// SetP set + + // --------------------------------------------------- : Event handling + DECLARE_EVENT_TABLE(); + + void onFileNew (wxCommandEvent&); + void onFileOpen (wxCommandEvent&); + void onFileSave (wxCommandEvent&); + void onFileSaveAs(wxCommandEvent&); + void onFileStore (wxCommandEvent&); + void onFileExit (wxCommandEvent&); + + void onEditUndo (wxCommandEvent&); + void onEditRedo (wxCommandEvent&); + + void onModeChange(wxCommandEvent&); + void onExtraTool (wxCommandEvent&); + + void onUpdateUI(wxUpdateUIEvent& e); + + /// Changing selected parts in the list + void onSelectFromList(wxListEvent& ev); + /// Activating a part: open the point editor + void onActivateFromList(wxListEvent& ev); + + bool inSelectionEvent; //^ Prevent recursion in onSelect... + + public: + void onSelectFromControl(); +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/util.cpp b/src/gui/util.cpp new file mode 100644 index 00000000..609c2730 --- /dev/null +++ b/src/gui/util.cpp @@ -0,0 +1,43 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include + +// ----------------------------------------------------------------------------- : DC related + +/// Fill a DC with a single color +void clearDC(DC& dc, const wxBrush& brush) { + wxSize size = dc.GetSize(); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(brush); + dc.DrawRectangle(0, 0, size.GetWidth(), size.GetHeight()); +} + +// ----------------------------------------------------------------------------- : Image related + +Image loadResourceImage(String name) { + #ifdef __WXMSW__ + // Load resource + // based on wxLoadUserResource + // The image can be in an IMAGE resource, in any file format + HRSRC hResource = ::FindResource(wxGetInstance(), name, _("IMAGE")); + if ( hResource == 0 ) throw InternalError(_("Resource not found: ") + name); + + HGLOBAL hData = ::LoadResource(wxGetInstance(), hResource); + if ( hData == 0 ) throw InternalError(_("Resource not an image: ") + name); + + char* data = (char *)::LockResource(hData); + if ( !data ) throw InternalError(_("Resource cannot be locked: ") + name); + + int len = ::SizeofResource(wxGetInstance(), hResource); + wxMemoryInputStream stream(data, len); + return wxImage(stream); + #endif +} \ No newline at end of file diff --git a/src/gui/util.hpp b/src/gui/util.hpp new file mode 100644 index 00000000..3519b99a --- /dev/null +++ b/src/gui/util.hpp @@ -0,0 +1,29 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_UTIL +#define HEADER_GUI_UTIL + +/** @file gui/util.hpp + * Utility functions for use in the gui. Most are related to drawing. + */ + +// ----------------------------------------------------------------------------- : Includes + +#include + +// ----------------------------------------------------------------------------- : DC related + +/// Fill a DC with a single color +void clearDC(DC& dc, const wxBrush& brush = *wxBLACK_BRUSH); + +// ----------------------------------------------------------------------------- : Resource related + +/// Load an image from a resource +Image loadResourceImage(String name); + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/mse.sln b/src/mse.sln new file mode 100644 index 00000000..7c0df81a --- /dev/null +++ b/src/mse.sln @@ -0,0 +1,29 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mse", "mse.vcproj", "{A78D6E04-7637-4C7A-AC63-DAAC3C4DB81B}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Debug Unicode = Debug Unicode + Release = Release + Release Profile Unicode = Release Profile Unicode + Release Unicode = Release Unicode + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {A78D6E04-7637-4C7A-AC63-DAAC3C4DB81B}.Debug.ActiveCfg = Debug|Win32 + {A78D6E04-7637-4C7A-AC63-DAAC3C4DB81B}.Debug.Build.0 = Debug|Win32 + {A78D6E04-7637-4C7A-AC63-DAAC3C4DB81B}.Debug Unicode.ActiveCfg = Debug Unicode|Win32 + {A78D6E04-7637-4C7A-AC63-DAAC3C4DB81B}.Debug Unicode.Build.0 = Debug Unicode|Win32 + {A78D6E04-7637-4C7A-AC63-DAAC3C4DB81B}.Release.ActiveCfg = Release|Win32 + {A78D6E04-7637-4C7A-AC63-DAAC3C4DB81B}.Release.Build.0 = Release|Win32 + {A78D6E04-7637-4C7A-AC63-DAAC3C4DB81B}.Release Profile Unicode.ActiveCfg = Release Unicode|Win32 + {A78D6E04-7637-4C7A-AC63-DAAC3C4DB81B}.Release Profile Unicode.Build.0 = Release Unicode|Win32 + {A78D6E04-7637-4C7A-AC63-DAAC3C4DB81B}.Release Unicode.ActiveCfg = Release Unicode|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/src/mse.vcproj b/src/mse.vcproj new file mode 100644 index 00000000..a483a613 --- /dev/null +++ b/src/mse.vcproj @@ -0,0 +1,846 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/util/action_stack.cpp b/src/util/action_stack.cpp new file mode 100644 index 00000000..74062073 --- /dev/null +++ b/src/util/action_stack.cpp @@ -0,0 +1,108 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include "action_stack.hpp" +#include "for_each.hpp" + +// ----------------------------------------------------------------------------- : Action stack + +DECLARE_TYPEOF_COLLECTION(Action*); +DECLARE_TYPEOF_COLLECTION(ActionListener*); + +ActionStack::ActionStack() + : savePoint(nullptr) +{} + +ActionStack::~ActionStack() { + // we own the actions, delete them + FOR_EACH(a, undoActions) delete a; + FOR_EACH(a, redoActions) delete a; +} + +void ActionStack::add(Action* action, bool allowMerge) { + if (!action) return; // no action + action->perform(false); // TODO: delete action if perform throws + redoActions.clear(); + tellListeners(*action); + // try to merge? + if (allowMerge && !undoActions.empty() && undoActions.back()->merge(action)) { + // merged with top undo action + delete action; + } else { + undoActions.push_back(action); + } +} + +void ActionStack::undo() { + assert(canUndo()); + Action* action = undoActions.back(); + action->perform(true); + // move to redo stack + undoActions.pop_back(); + redoActions.push_back(action); +} +void ActionStack::redo() { + assert(canRedo()); + Action* action = redoActions.back(); + action->perform(false); + // move to undo stack + redoActions.pop_back(); + undoActions.push_back(action); +} + +bool ActionStack::canUndo() const { + return !undoActions.empty(); +} +bool ActionStack::canRedo() const { + return !redoActions.empty(); +} + +String ActionStack::undoName() const { + if (canUndo()) { + return _("Undo ") + capitalize(undoActions.back()->getName(true)); + } else { + return _("Undo"); + } +} +String ActionStack::redoName() const { + if (canRedo()) { + return _("Redo ") + capitalize(redoActions.back()->getName(false)); + } else { + return _("Redo"); + } +} + +bool ActionStack::atSavePoint() const { + return (undoActions.empty() && savePoint == nullptr) + || (undoActions.back() == savePoint); +} +void ActionStack::setSavePoint() { + if (undoActions.empty()) { + savePoint = nullptr; + } else { + savePoint = undoActions.back(); + } +} + +void ActionStack::addListener(ActionListener* listener) { + listeners.push_back(listener); +} +void ActionStack::removeListener(ActionListener* listener) { + listeners.erase( + std::remove( + listeners.begin(), + listeners.end(), + listener + ), + listeners.end() + ); +} +void ActionStack::tellListeners(const Action& action) { + FOR_EACH(l, listeners) l->onAction(action); +} diff --git a/src/util/action_stack.hpp b/src/util/action_stack.hpp new file mode 100644 index 00000000..d0b92548 --- /dev/null +++ b/src/util/action_stack.hpp @@ -0,0 +1,136 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_ACTION_STACK +#define HEADER_UTIL_ACTION_STACK + +// ----------------------------------------------------------------------------- : Includes + +#include +#include "string.hpp" + +// ----------------------------------------------------------------------------- : Action + +/// Base class for actions that can be stored in an ActionStack. +/** An action is something that can be done to modify an object. + * It must store the necessary information to also undo the action. + */ +class Action { + public: + virtual ~Action() {}; + + /// Name of the action, for use in strings like "Undo " + virtual String getName(bool toUndo) const = 0; + + /// Perform the action + /// Must be implemented in derived class + /** Perform will only ever be called alternatingly with toUndo = true/false, + * the first time with toUndo = false + */ + /// @param toUndo if true, undo the action instead of doing it + virtual void perform(bool toUndo) = 0; + + /// Try to merge another action to the end of this action. + /// Either: return false and do nothing + /// Or: return true and change this action to incorporate both actions + virtual bool merge(const Action* action) { return false; } +}; + +// ----------------------------------------------------------------------------- : Action listeners + +/// Base class/interface for objects that listen to actions +class ActionListener { + public: + virtual void onAction(const Action& a) = 0; +}; + +// ----------------------------------------------------------------------------- : Action stack + +/// A stack of actions that can be done and undone. +/** This class handles the undo and redo functionality of a particular object. + * + * This class also takes on the role of Observable, ActionListeners can register themselfs. + * They will be notified when an action is added. + */ +class ActionStack { + public: + ActionStack(); + ~ActionStack(); + + /// Add an action to the stack, and perform that action. + /// Tells all listeners about the action. + /// The ActionStack takes ownership of the action + void add(Action* action, bool allowMerge = true); + + /// Undoes the last action that was (re)done + /// @pre canUndo() + void undo(); + /// Redoes the last action that was undone + /// @pre canRedo() + void redo(); + + /// Is undoing possible? + bool canUndo() const; + /// Is redoing possible? + bool canRedo() const; + + /// Name of the action that will be undone next, in the form "Undo " + /// If there is no action to undo returns "Undo" + String undoName() const; + /// Name of the action that will be redone next "Redo " + /// If there is no action to undo returns "Redo" + String redoName() const; + + /// Is the file currently at a 'savepoint'? + /// This is the last point at which the file was saved + bool atSavePoint() const; + /// Indicate that the file is at a savepoint. + void setSavePoint(); + + /// Add an action listener + void addListener(ActionListener* listener); + /// Remove an action listener + void removeListener(ActionListener* listener); + /// Tell all listeners about an action + void tellListeners(const Action&); + + private: + /// Actions to be undone + /// Owns the action objects! + vector undoActions; + /// Actions to be redone + /// Owns the action objects! + vector redoActions; + /// Point at which the file was saved, corresponds to the top of the undo stack at that point + Action* savePoint; + /// Objects that are listening to actions + vector listeners; +}; + + +// ----------------------------------------------------------------------------- : Utilities + +/// Tests if variable has the type Type +/** Uses dynamic cast, so Type must have a virtual function. + */ +#define TYPE_CASE_(variable, Type) \ + if (dynamic_cast(&variable)) + +/// Tests if variable has the type Type. If this is the case, makes +/// variable have type Type inside the statement +/** Uses dynamic cast, so Type must have a virtual function. + */ +#define TYPE_CASE(variable, Type) \ + pair Type##variable \ + (dynamic_cast(&variable), true); \ + if (Type##variable.first) \ + for (const Type& variable = *Type##variable.first ; \ + Type##variable.second ; \ + Type##variable.second = false) + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/error.cpp b/src/util/error.cpp new file mode 100644 index 00000000..6d34239d --- /dev/null +++ b/src/util/error.cpp @@ -0,0 +1,21 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include + +// ----------------------------------------------------------------------------- : Error types + +Error::Error(const String& message) + : message(message) +{} + +Error::~Error() {} + +String Error::what() const { + return message; +} diff --git a/src/util/error.hpp b/src/util/error.hpp new file mode 100644 index 00000000..674ce87f --- /dev/null +++ b/src/util/error.hpp @@ -0,0 +1,60 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_ERROR +#define HEADER_UTIL_ERROR + +// ----------------------------------------------------------------------------- : Includes + +#include + +/** @file util/error.hpp + * + * Classes and functions for handling errors/exceptions + */ + +// ----------------------------------------------------------------------------- : Error types + +/// Our own exception class +class Error { + public: + Error(const String& message); + virtual ~Error(); + + /// Return the error message + virtual String what() const; + + private: + String message; //^ The error message +}; + + +/// Internal errors +class InternalError : public Error { + public: + inline InternalError(const String& str) + : Error(_("An internal error occured, please contact the author:\n") + str) + {} +}; + +// ----------------------------------------------------------------------------- : File errors + +// Errors related to packages +class PackageError : public Error { + public: + inline PackageError(const String& str) : Error(str) {} +}; + +// ----------------------------------------------------------------------------- : Parse errors + +// Parse errors +class ParseError : public Error { + public: + inline ParseError(const String& str) : Error(str) {} +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/for_each.hpp b/src/util/for_each.hpp new file mode 100644 index 00000000..f5fb7cef --- /dev/null +++ b/src/util/for_each.hpp @@ -0,0 +1,169 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_FOR_EACH +#define HEADER_UTIL_FOR_EACH + +/** @file util/for_each.hpp + * + * Macros to simplify looping over collections. + * This header contains some evil template and macro hackery. + */ + +// ----------------------------------------------------------------------------- : Includes + +// ----------------------------------------------------------------------------- : Typeof magic + +#ifdef __GNUC__ + // GCC has a buildin typeof function, so it doesn't need (as much) hacks + #define DECLARE_TYPEOF(T) + #define DECLARE_TYPEOF_COLLECTION(T) + + #define TYPEOF(Value) __typeof(Value) + #define TYPEOF_IT(Value) __typeof(Value.begin()) + #define TYPEOF_CIT(Value) __typeof(Value.begin()) + #define TYPEOF_RIT(Value) __typeof(Value.rbegin()) + #define TYPEOF_REF(Value) __typeof(*Value.begin())& + +#else + /// Helper for typeof tricks + template struct TypeOf {}; + + /// The type of a value + #define TYPEOF(Value) TypeOf::type + /// The type of an iterator + #define TYPEOF_IT(Value) TypeOf::iterator + /// The type of a const iterator + #define TYPEOF_CIT(Value) TypeOf::const_iterator + /// The type of a reverse iterator + #define TYPEOF_RIT(Value) TypeOf::reverse_iterator + /// The type of a value + #define TYPEOF_REF(Value) TypeOf::reference + + /// Declare typeof magic for a specific type + #define DECLARE_TYPEOF(T) \ + template<> struct TypeOf { \ + typedef T type; \ + typedef T::iterator iterator; \ + typedef T::const_iterator const_iterator; \ + typedef T::reverse_iterator reverse_iterator; \ + typedef T::reference reference; \ + } + /// Declare typeof magic for a specific type that doesn't support reverse iterators + #define DECLARE_TYPEOF_NO_REV(T) \ + template<> struct TypeOf { \ + typedef T type; \ + typedef T::iterator iterator; \ + typedef T::const_iterator const_iterator; \ + typedef T::reference reference; \ + } + /// Declare typeof magic for a specific type, using const iterators + #define DECLARE_TYPEOF_CONST(T) \ + template<> struct TypeOf { \ + typedef T type; \ + typedef T::const_iterator iterator; \ + typedef T::const_iterator const_iterator; \ + typedef T::const_reverse_iterator reverse_iterator; \ + typedef T::const_reference reference; \ + } + + + /// Declare typeof magic for a specific std::vector type + #define DECLARE_TYPEOF_COLLECTION(T) DECLARE_TYPEOF(vector); \ + DECLARE_TYPEOF_CONST(set) + +#endif + +// ----------------------------------------------------------------------------- : Looping macros with iterators + +/// Iterate over a collection, using an iterator it of type Type +/// Usage: FOR_EACH_IT_T(Type,it,collect) { body-of-loop } +#define FOR_EACH_IT_T(Type,Iterator,Collection) \ + for(Type Iterator = Collection.begin() ; \ + Iterator != Collection.end() ; \ + ++Iterator) + +/// Iterate over a collection whos type must be declared with DECLARE_TYPEOF +/// Usage: FOR_EACH_IT(it,collect) { body-of-loop } +#define FOR_EACH_IT(Iterator,Collection) \ + FOR_EACH_IT_T(TYPEOF_IT(Collection), Iterator, Collection) + +/// Iterate over a collection whos type must be declared with DECLARE_TYPEOF +/// Uses a const_iterator +/// Usage: FOR_EACH_IT(it,collect) { body-of-loop } +#define FOR_EACH_CONST_IT(Iterator,Collection) \ + FOR_EACH_IT_T(TYPEOF_CIT(Collection), Iterator, Collection) + +/// Iterate over a collection in whos type must be declared with DECLARE_TYPEOF +/// Iterates using a reverse_iterator +/// Usage: FOR_EACH_REVERSE_IT(it,collect) { body-of-loop } +#define FOR_EACH_REVERSE_IT(Iterator,Collection) \ + for(TYPEOF_RIT(Collection) \ + Iterator = Collection.rbegin() ; \ + Iterator != Collection.rend() ; \ + ++Iterator) + +// ----------------------------------------------------------------------------- : Looping macros + +/// Iterate over a collection, with an iterator of type TypeIt, and elements of type TypeElem +/// Usage: FOR_EACH_T(TypeIt,TypeElem,e,collect) { body-of-loop } +/** We need a hack to be able to declare a local variable without needing braces. + * To do this we use a nested for loop that is only executed once, and which is optimized away. + * To terminate this loop we need an extra bool, which we set to false after the first iteration. + */ +#define FOR_EACH_T(TypeIt,TypeElem,Elem,Collection) \ + for(std::pair Elem##_IT(Collection.begin(), true) ; \ + Elem##_IT.first != Collection.end() ; \ + ++Elem##_IT.first, Elem##_IT.second = true) \ + for(TypeElem Elem = *Elem##_IT.first ; \ + Elem##_IT.second ; \ + Elem##_IT.second = false) + +/// Iterate over a collection whos type must be declared with DECLARE_TYPEOF +/// Usage: FOR_EACH(e,collect) { body-of-loop } +#define FOR_EACH(Elem,Collection) \ + FOR_EACH_T(TYPEOF_IT(Collection), TYPEOF_REF(Collection), Elem, Collection) + +/// Iterate over a collection whos type must be declared with DECLARE_TYPEOF +/// Iterates using a reverse_iterator +/// Usage: FOR_EACH_REVERSE(e,collect) { body-of-loop } +#define FOR_EACH_REVERSE(Elem,Collection) \ + for(std::pair Elem##_IT(Collection.rbegin(), true) ; \ + Elem##_IT.first != Collection.rend() ; \ + ++Elem##_IT.first, Elem##_IT.second = true) \ + for(TYPEOF_REF(Collection) Elem = *Elem##_IT.first ; \ + Elem##_IT.second ; \ + Elem##_IT.second = false) + +/// Iterate over two collection in parallel +/// Usage: FOR_EACH_2_T(TypeIt1,TypeElem1,e1,collect1,TypeIt2,TypeElem2,e2,collect2) { body-of-loop } +/** Note: This has got to be one of the craziest pieces of code I have ever written :) + * It is just an extension of the idea of FOR_EACH_T. + */ +#define FOR_EACH_2_T(TypeIt1,TypeElem1,Elem1,Coll1,TypeIt2,TypeElem2,Elem2,Coll2) \ + for(std::pair, bool> \ + Elem1##_IT(make_pair(Coll1.begin(), Coll2.begin()), true) ; \ + Elem1##_IT.first.first != Coll1.end() && \ + Elem1##_IT.first.second != Coll2.end() ; \ + ++Elem1##_IT.first.first, ++Elem1##_IT.first.second, \ + Elem1##_IT.second = true) \ + for(TypeElem1 Elem1 = *Elem1##_IT.first.first ; \ + Elem1##_IT.second ; \ + Elem1##_IT.second = false) \ + for(TypeElem2 Elem2 = *Elem1##_IT.first.second ; \ + Elem1##_IT.second ; \ + Elem1##_IT.second = false) + +/// Iterate over two collections in parallel, +/// their type must be declared with DECLARE_TYPEOF. +/// Usage: FOR_EACH_2(e1,collect1, e2,collect2) { body-of-loop } +#define FOR_EACH_2(Elem1,Collection1, Elem2,Collection2) \ + FOR_EACH_2_T(TYPEOF_IT(Collection1), TYPEOF_REF(Collection1), Elem1, Collection1, \ + TYPEOF_IT(Collection2), TYPEOF_REF(Collection2), Elem2, Collection2) + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/index_map.hpp b/src/util/index_map.hpp new file mode 100644 index 00000000..dd422953 --- /dev/null +++ b/src/util/index_map.hpp @@ -0,0 +1,73 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_INDEX_MAP +#define HEADER_UTIL_INDEX_MAP + +// ----------------------------------------------------------------------------- : Includes + +#include + +// ----------------------------------------------------------------------------- : IndexMap + +/// A kind of map of K->V, with the following properties: +/** - K must have a unique member ->index of type UInt + * - There must exist a function initObject(K, V&) + * that stores a new V object for a given key in v + * - O(1) inserts and lookups + */ +template +class IndexMap : private vector { + public: + using vector::empty; + using vector::size; + using vector::iterator; + using vector::begin; + using vector::end; + + /// Initialize this map with default values given a list of keys, has no effect if !empty() + /** Requires a function + * void initObject(Key, Value&) + */ + void init(const vector& keys) { + if (!this->empty()) return; + this->reserve(keys.size()); + FOR_EACH(it, keys) { + Key& k = *it; + if (k->index >= this->size()) this->resize(k->index + 1); + initObject(k, (*this)[k->index]); + } + } + + /// Retrieve a value given its key + inline Value operator [] (const Key& key) { + assert(key); + assert(this->size() > key->index); + return at(key->index); + } + + /// Is a value contained in this index map? + /// requires a function Key Value::getKey() + inline bool contains(const Value& value) const { + assert(value); + size_t index = value->getKey()->index; + return index < this.size() && (*this)[index] == value + } + + /// Is a key in the domain of this index map? + /// requires a function Key Value::getKey() + inline bool containsKey(const Key& key) const { + assert(key); + return key->index < this.size() && (*this)[key->index]->getKey() == key + } + + private: + using vector::operator []; +}; + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/io/reader.cpp b/src/util/io/reader.cpp new file mode 100644 index 00000000..530b2687 --- /dev/null +++ b/src/util/io/reader.cpp @@ -0,0 +1,127 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include "reader.hpp" +#include +#include + +// ----------------------------------------------------------------------------- : Reader + +Reader::Reader(const InputStreamP& input, String filename) + : input(input), filename(filename), lineNumber(0) + , indent(0), expectedIndent(0), justOpened(false) + , stream(*input) +{ + moveNext(); +} + +bool Reader::enterBlock(const Char* name) { + if (justOpened) moveNext(); // on the key of the parent block, first move inside it + if (indent != expectedIndent) return false; // not enough indentation + if (name == key) { + justOpened = true; + expectedIndent += 1; // the indent inside the block must be at least this much + return true; + } else { + return false; + } +} + +void Reader::exitBlock() { + assert(expectedIndent > 0); + expectedIndent -= 1; + multiLineStr.clear(); + if (justOpened) moveNext(); // leave this key + // Dump the remainder of the block + // TODO: issue warnings? + while (indent > expectedIndent) { + moveNext(); + } +} + +void Reader::moveNext() { + justOpened = false; + key.clear(); + multiLineStr.clear(); + indent = -1; // if no line is read it never has the expected indentation + // repeat until we have a good line + while (key.empty() && !input->Eof()) { + readLine(); + } + // did we reach the end of the file? + if (key.empty() && input->Eof()) { + lineNumber += 1; + indent = -1; + } +} + +void Reader::readLine() { + // fix UTF8 in ascii builds; skip BOM + line = decodeUTF8BOM(stream.ReadLine()); + // read indentation + indent = 0; + while ((UInt)indent < line.size() && line.GetChar(indent) == _('\t')) { + indent += 1; + } + // read key / value + size_t pos = line.find_first_of(_(':'), indent); + key = trim(line.substr(indent, pos - indent)); + value = pos == String::npos ? _("") : trimLeft(line.substr(pos+1)); + // we read a line + lineNumber += 1; + // was it a comment? + if (!key.empty() && key.GetChar(0) == _('#')) { + key.clear(); + } +} + +// ----------------------------------------------------------------------------- : Handling basic types + +template <> void Reader::handle(String& s) { + if (!multiLineStr.empty()) { + s = multiLineStr; + } else if (value.empty()) { + // a multiline string + bool first = true; + // read all lines that are indented enough + readLine(); + while (indent >= expectedIndent) { + if (!first) value += '\n'; + first = false; + multiLineStr += line.substr(expectedIndent); // strip expected indent + readLine(); + } + // moveNext(), but without emptying multiLineStr + justOpened = false; + while (key.empty() && !input->Eof()) { + readLine(); + } + s = multiLineStr; + } else { + s = value; + } +} +template <> void Reader::handle(int& i) { + long l = 0; + value.ToLong(&l); + i = l; +} +template <> void Reader::handle(double& d) { + value.ToDouble(&d); +} +template <> void Reader::handle(bool& b) { + b = (value==_("true") || value==_("1") || value==_("yes")); +} + +// ----------------------------------------------------------------------------- : Handling less basic util types + +template <> void Reader::handle(Vector2D& vec) { + if (!wxSscanf(value.c_str(), _("(%lf,%lf)"), &vec.x, &vec.y)) { + throw ParseError(_("Expected (x,y)")); + } +} diff --git a/src/util/io/reader.hpp b/src/util/io/reader.hpp new file mode 100644 index 00000000..25043289 --- /dev/null +++ b/src/util/io/reader.hpp @@ -0,0 +1,165 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_IO_READER +#define HEADER_UTIL_IO_READER + +// ----------------------------------------------------------------------------- : Includes + +#include "../prec.hpp" +#include + +// ----------------------------------------------------------------------------- : Reader + +typedef wxInputStream InputStream; +typedef shared_ptr InputStreamP; + +/// The Reader can be used for reading (deserializing) objects +/** This class makes use of the reflection functionality, in effect + * an object tells the Reader what fields it would like to read. + * The reader then sees if the requested field is currently available. + * + * The handle functions ensure that afterwards the reader is at the line after the + * object that was just read. + */ +class Reader { + public: + /// Construct a reader that reads from the given input stream + /** filename is used only for error messages + */ + Reader(const InputStreamP& input, String filename = _("")); + + /// Construct a reader that reads a file in a package + Reader(String filename); + + /// Tell the reflection code we are reading + inline bool reading() const { return true; } + + // --------------------------------------------------- : Handling objects + /// Handle an object: read it if it's name matches + template + void handle(const Char* name, T& object) { + if (enterBlock(name)) { + handle(object); + exitBlock(); + } + } + + /// Reads an object of type T from the input stream + template void handle(T& object); + /// Reads a vector from the input stream + template void handle(vector& vector); + /// Reads a shared_ptr from the input stream + template void handle(shared_ptr& pointer); + /// Reads a map from the input stream + //template void handle(map& map); + + private: + // --------------------------------------------------- : Data + /// The line we read + String line; + /// The key and value of the last line we read + String key, value; + /// A string spanning multiple lines + String multiLineStr; + /// Indentation of the last line we read + int indent; + /// Indentation of the block we are in + int expectedIndent; + /// Did we just open a block (i.e. not read any more lines of it)? + bool justOpened; + + /// Filename for error messages + String filename; + /// Line number for error messages + UInt lineNumber; + /// Input stream we are reading from + InputStreamP input; + /// Text stream wrapping the input stream + wxTextInputStream stream; + + // --------------------------------------------------- : Reading the stream + + /// Is there a block with the given key under the current cursor? + bool enterBlock(const Char* name); + /// Leave the block we are in + void exitBlock(); + + /// Move to the next non empty line + void moveNext(); + /// Reads the next line from the input, and stores it in line/key/value/indent + void readLine(); + + /// Issue a warning: "Unexpected key: $key" + void unexpected(); +}; + +// ----------------------------------------------------------------------------- : Container types + +template +void Reader::handle(vector& vector) { + String vectorKey = key; + while (key == vectorKey) { // TODO : check indent + moveNext(); // skip key + vector.resize(vector.size() + 1); + handle(vector.back()); + } +} + +template +void Reader::handle(shared_ptr& pointer) { + if (!pointer) pointer.reset(new T); + handle(*pointer); +} + +// ----------------------------------------------------------------------------- : Reflection + +/// Implement reflection as used by Reader +#define REFLECT_OBJECT_READER(Cls) \ + template<> void Reader::handle(Cls& object) { \ + object.reflect(*this); \ + } + +// ----------------------------------------------------------------------------- : Reflection for enumerations + +/// Implement enum reflection as used by Reader +#define REFLECT_ENUM_READER(Enum) \ + template<> void Reader::handle(Enum& enum_) { \ + EnumReader reader(value); \ + reflect_ ## Enum(enum_, reader); \ + if (!reader.isDone()) { \ + /* warning message */ \ + } \ + } + +/// 'Tag' to be used when reflecting enumerations for Reader +class EnumReader { + public: + inline EnumReader(String read) + : read(read), first(true), done(false) {} + + /// Handle a possible value for the enum, if the name matches the name in the input + template + inline void handle(const Char* name, Enum value, Enum& enum_) { + if (!done && read == name) { + first = true; + enum_ = value; + } else if (first) { + first = false; + enum_ = value; + } + } + + inline bool isDone() const { return done; } + + private: + String read; //^ The string to match to a value name + bool first; //^ Has the first (default) value been matched? + bool done; //^ Was anything matched? +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/io/writer.cpp b/src/util/io/writer.cpp new file mode 100644 index 00000000..13a5d7a7 --- /dev/null +++ b/src/util/io/writer.cpp @@ -0,0 +1,108 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include "writer.hpp" +#include +#include + +// ----------------------------------------------------------------------------- : Writer + +Writer::Writer(const OutputStreamP& output) + : output(output) + , stream(*output) + , indentation(0) + , justOpened(false) +{ + stream.WriteString(BYTE_ORDER_MARK); +} + + +void Writer::enterBlock(const Char* name) { + // indenting into a sub-block? + if (justOpened) { + writeKey(); + stream.WriteString(_(":\n")); + } + // don't write the key yet + indentation += 1; + openedKey = name; + justOpened = true; +} + +void Writer::exitBlock() { + assert(indentation > 0); + indentation -= 1; + justOpened = false; +} + +void Writer::writeKey() { + writeIndentation(); + writeUTF8(stream, openedKey); +} +void Writer::writeIndentation() { + for(int i = 1 ; i < indentation ; ++i) { + stream.PutChar(_('\t')); + } +} + +// ----------------------------------------------------------------------------- : Handling basic types + +void Writer::handle(const String& value) { + if (!justOpened) { + throw InternalError(_("Can only write a value in a key that was just opened")); + } + // write indentation and key + writeKey(); + writeUTF8(stream, _(": ")); + if (value.find_first_of(_('\n')) != String::npos) { + // multiline string + stream.PutChar(_('\n')); + indentation += 1; + // split lines, and write each line + size_t start = 0, end, size = value.size(); + while (start < size) { + end = value.find_first_of(_("\n\r"), start); // until end of line + // write the line + writeIndentation(); + writeUTF8(stream, value.substr(start, end - start)); + stream.PutChar(_('\n')); + // Skip \r and \n + if (end == String::npos) break; + start = end + 1; + if (start < size) { + Char c1 = value.GetChar(start - 1); + Char c2 = value.GetChar(start); + // skip second character of \r\n or \n\r + if (c1 != c2 && (c2 == _('\r') || c2 == _('\n'))) start += 1; + } + } + indentation -= 1; + } else { + writeUTF8(stream, value); + } + stream.PutChar(_('\n')); + justOpened = false; +} + +template <> void Writer::handle(const int& value) { + handle(String() << value); +} +template <> void Writer::handle(const double& value) { + handle(String() << value); +} +template <> void Writer::handle(const bool& value) { + handle(value ? _("true") : _("false")); +} + +// ----------------------------------------------------------------------------- : Handling less basic util types + +template <> void Writer::handle(const Vector2D& vec) { + String formated; + formated.Printf(_("(%.10lf,%.10lf)"), vec.x, vec.y); + handle(formated); +} diff --git a/src/util/io/writer.hpp b/src/util/io/writer.hpp new file mode 100644 index 00000000..86a8439c --- /dev/null +++ b/src/util/io/writer.hpp @@ -0,0 +1,131 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_IO_WRITER +#define HEADER_UTIL_IO_WRITER + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +// ----------------------------------------------------------------------------- : Writer + +typedef wxOutputStream OutputStream; +typedef shared_ptr OutputStreamP; + +/// The Writer can be used for writing (serializing) objects +class Writer { + public: + /// Construct a writer that writes to the given output stream + Writer(const OutputStreamP& output); + + /// Tell the reflection code we are not reading + inline bool reading() const { return false; } + + // --------------------------------------------------- : Handling objects + /// Handle an object: write it under the given name + template + void handle(const Char* name, const T& object) { + enterBlock(name); + handle(object); + exitBlock(); + } + + /// Write a string to the output stream + void handle(const String& str); + void handle(const Char* str) { handle(String(str)); } + + /// Write an object of type T to the output stream + template void handle(const T& object); + /// Write a vector to the output stream + template void handle(const vector& vector); + /// Write a shared_ptr to the output stream + template void handle(const shared_ptr& pointer); + /// Write a map to the output stream + //template void handle(map& map); + + private: + // --------------------------------------------------- : Data + /// Indentation of the current block + int indentation; + /// Did we just open a block (i.e. not written any lines of it)? + bool justOpened; + /// Last key opened + String openedKey; + + /// Output stream we are writing to + OutputStreamP output; + /// Text stream wrapping the output stream + wxTextOutputStream stream; + + // --------------------------------------------------- : Writing to the stream + + /// Start a new block with the given name + void enterBlock(const Char* name); + /// Leave the block we are in + void exitBlock(); + + /// Write the openedKey and the required indentation + void writeKey(); + /// Output some taps to represent the indentation level + void writeIndentation(); +}; + +// ----------------------------------------------------------------------------- : Container types + +template +void Writer::handle(const vector& vector) { + /*String vectorKey = key; + while (key == vectorKey) { // TODO : check indent + moveNext(); // skip key + vector.resize(vector.size() + 1); + handle(vector.back()); + }*/ +} + +template +void Writer::handle(const shared_ptr& pointer) { + if (pointer) handle(*pointer); +} + +// ----------------------------------------------------------------------------- : Reflection + +/// Implement reflection as used by Writer +#define REFLECT_OBJECT_WRITER(Cls) \ + template<> void Writer::handle(const Cls& object) { \ + const_cast(object).reflect(*this); \ + } + +// ----------------------------------------------------------------------------- : Reflection for enumerations + +/// Implement enum reflection as used by Writer +#define REFLECT_ENUM_WRITER(Enum) \ + template<> void Writer::handle(const Enum& enum_) { \ + EnumWriter writer(*this); \ + reflect_ ## Enum(const_cast(enum_), writer); \ + } + +/// 'Tag' to be used when reflecting enumerations for Writer +class EnumWriter { + public: + inline EnumWriter(Writer& writer) + : writer(writer) {} + + /// Handle a possible value for the enum, if the name matches the name in the input + template + inline void handle(const Char* name, Enum value, Enum enum_) { + if (enum_ == value) { + writer.handle(name); + } + } + + private: + Writer& writer; //^ The writer to write output to +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/prec.hpp b/src/util/prec.hpp new file mode 100644 index 00000000..cd3d901d --- /dev/null +++ b/src/util/prec.hpp @@ -0,0 +1,68 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_PREC +#define HEADER_UTIL_PREC + +/** @file util/prec.hpp + * + * Precompiled header, and aliasses for common types + */ + +// ----------------------------------------------------------------------------- : Compiler specific + +#ifdef _MSC_VER +# pragma warning (disable: 4100) // unreferenced formal parameter +# pragma warning (disable: 4800) // 'int' : forcing value to bool 'true' or 'false' (performance warning) +#endif + +// ----------------------------------------------------------------------------- : Includes + +// Wx headers +#include +#include +#include +#include + +// Std headers +#include +#include +#include +using namespace std; + +// MSE utility headers (ones unlikely to change and used everywhere) +#include "for_each.hpp" +#include "string.hpp" +#include "smart_ptr.hpp" +#include "index_map.hpp" + +// ----------------------------------------------------------------------------- : Wx Aliasses + +// Remove some of the wxUglyness + +typedef wxPanel Panel; +typedef wxWindow Window; +typedef wxFrame Frame; + +typedef wxBitmap Bitmap; +typedef wxImage Image; +typedef wxColour Color; +typedef wxDC DC; + +typedef wxDateTime DateTime; + +typedef wxOutputStream OutputStream; + +// ----------------------------------------------------------------------------- : Other aliasses + +typedef unsigned char Byte; +typedef unsigned int UInt; + +/// Null pointer +#define nullptr 0 + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/real_point.hpp b/src/util/real_point.hpp new file mode 100644 index 00000000..87845643 --- /dev/null +++ b/src/util/real_point.hpp @@ -0,0 +1,116 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_REAL_POINT +#define HEADER_UTIL_REAL_POINT + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +// ----------------------------------------------------------------------------- : Point using doubles + +/// A point using real (double) coordinates +typedef Vector2D RealPoint; + +// ----------------------------------------------------------------------------- : Size using doubles + +/// A size (width,height) using real (double) coordinates +class RealSize { + public: + double width; + double height; + + inline RealSize() + : width(0), height(0) + {} + inline RealSize(double w, double h) + : width(w), height(h) + {} + inline RealSize(wxSize s) + : width(s.GetWidth()), height(s.GetHeight()) + {} + + /// Addition of two sizes + inline void operator += (const RealSize& s2) { + width += s2.width; + height += s2.height; + } + /// Addition of two sizes + inline RealSize operator + (const RealSize& s2) const { + return RealSize(width + s2.width, height + s2.height); + } + /// Difference of two sizes + inline void operator -= (const RealSize& s2){ + width -= s2.width; + height -= s2.height; + } + /// Difference of two sizes + inline RealSize operator - (const RealSize& s2) const { + return RealSize(width - s2.width, height - s2.height); + } + /// Inversion of a size, inverts both components + inline RealSize operator - () const { + return RealSize(-width, -height); + } + + /// Multiplying a size by a scalar r, multiplies both components + inline void operator *= (double r) { + width *= r; + height *= r; + } + /// Multiplying a size by a scalar r, multiplies both components + inline RealSize operator * (double r) const { + return RealSize(width * r, height * r); + } + /// Dividing a size by a scalar r, divides both components + inline RealSize operator / (double r) const { + return RealSize(width / r, height / r); + } + + /// Can be converted to a wxSize, with integer components + inline operator wxSize() { + return wxSize(realRound(width), realRound(height)); + } +}; + +// ----------------------------------------------------------------------------- : Rectangle using doubles + +/// A rectangle (postion and size) using real (double) coordinats +class RealRect { + public: + /// Position of the top left corner + RealPoint position; + /// Size of the rectangle + RealSize size; + + inline RealRect(const RealPoint& position, const RealSize& size) + : position(position), size(size) + {} + inline RealRect(double x, double y, double w, double h) + : position(x,y), size(w,h) + {} +}; + +// ----------------------------------------------------------------------------- : Operators + +inline RealPoint operator + (const RealSize& s, const RealPoint& p) { + return RealPoint(p.x + s.width, p.y + s.height); +} +inline RealPoint operator + (const RealPoint& p, const RealSize& s) { + return RealPoint(p.x + s.width, p.y + s.height); +} +inline RealPoint operator - (const RealPoint& p, const RealSize& s) { + return RealPoint(p.x - s.width, p.y - s.height); +} +inline void operator += (RealPoint& p, const RealSize& s) { + p.x += s.width; + p.y += s.height; +} + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/reflect.hpp b/src/util/reflect.hpp new file mode 100644 index 00000000..0be19806 --- /dev/null +++ b/src/util/reflect.hpp @@ -0,0 +1,111 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_REFLECT +#define HEADER_UTIL_REFLECT + +/** @file util/reflect.hpp + * + * Reflection of classes, currently reflection is used for (de)serialization. + */ + +// ----------------------------------------------------------------------------- : Includes + +#include "io/reader.hpp" +#include "io/writer.hpp" + +// ----------------------------------------------------------------------------- : Declaring reflection + +/// Declare that a class supports reflection +/// Reflection allows the member variables of a class to be inspected at runtime. +#define DECLARE_REFLECTION() \ + protected: \ + template void reflect_impl(Tag& tag); \ + friend class Reader; \ + friend class Writer; \ + void reflect(Reader& reader); \ + void reflect(Writer& writer) + +/// Declare that a class supports reflection, which can be overridden in derived classes +#define DECLARE_REFLECTION_VIRTUAL() \ + protected: \ + template void reflect_impl(Tag& tag); \ + friend class Reader; \ + friend class Writer; \ + virtual void reflect(Reader& reader); \ + virtual void reflect(Writer& writer) + +// ----------------------------------------------------------------------------- : Implementing reflection + +/// Implement the refelection of a class type Cls +/// Reflection allows the member variables of a class to be inspected at runtime. +/// +/// Currently creates the methods: +/// - Reader::handle(Cls&) +/// - Writer::handle(Cls&) +/** Usage: + * \begincode + * IMPLEMENT_REFLECTION(MyClass) { + * REFLECT(a_variable_in_my_class); + * REFLECT(another_variable_in_my_class); + * } + * \endcode + */ +#define IMPLEMENT_REFLECTION(Cls) \ + REFLECT_OBJECT_READER(Cls) \ + REFLECT_OBJECT_WRITER(Cls) \ + /* Extra level, so it can be declared virtual */ \ + void Cls::reflect(Reader& reader) { \ + reflect_impl(reader); \ + } \ + void Cls::reflect(Writer& writer) { \ + reflect_impl(writer); \ + } \ + template \ + void Cls::reflect_impl(Tag& tag) + +/// Reflect a variable +#define REFLECT(var) tag.handle(_(#var), var) +/// Reflect a variable under the given name +#define REFLECT_N(name, var) tag.handle(_(name), var) + +/// Declare that the variables of a base class should also be reflected +#define REFLECT_BASE(Base) Base::reflect_impl(tag) + + +// ----------------------------------------------------------------------------- : Reflecting enums + +/// Implement the refelection of a enumeration type Enum +/** Usage: + * \begincode + * IMPLEMENT_REFLECTION_ENUM(MyEnum) { + * VALUE(value_of_enum_1); + * VALUE(value_of_enum_2); + * } + * \endcode + * + * When reading the first value declared is the default value + * + * Currently creates the methods: + * - Reader::handle(Enum& + * - Writer::handle(const Enum&) + */ +#define IMPLEMENT_REFLECTION_ENUM(Enum) \ + template \ + void reflect_ ## Enum (Enum& enum_, Tag& tag); \ + REFLECT_ENUM_READER(Enum) \ + REFLECT_ENUM_WRITER(Enum) \ + template \ + void reflect_ ## Enum (Enum& enum_, Tag& tag) + +/// Declare a possible value of an enum +#define VALUE(val) tag.handle(_(#val), val, enum_) + +/// Declare a possible value of an enum under the given name +#define VALUE_N(name, val) tag.handle(_(name), val, enum_) + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/rotation.cpp b/src/util/rotation.cpp new file mode 100644 index 00000000..7547bbf5 --- /dev/null +++ b/src/util/rotation.cpp @@ -0,0 +1,56 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include + +// ----------------------------------------------------------------------------- : Rotation + +Rotation::Rotation(int angle, const RealRect& rect, double zoom) + : angle(angle) + , size(rect.size) + , origin(rect.position) + , zoom(zoom) +{ + // set origin + if (revX()) origin.x += size.width; + if (revY()) origin.x += size.height; +} + +RealPoint Rotation::tr(const RealPoint& p) const { + return tr(RealSize(p.x, p.y)) + origin; // TODO : optimize? +} +RealSize Rotation::tr(const RealSize& s) const { + if (sideways()) { + return RealSize(negX(s.height), negY(s.width)) * zoom; + } else { + return RealSize(negX(s.width), negY(s.height)) * zoom; + } +} +RealRect Rotation::tr(const RealRect& r) const { + return RealRect(tr(r.position), tr(r.size)); +} + +RealSize Rotation::trNoNeg(const RealSize& s) const { + if (sideways()) { + return RealSize(s.height, s.width) * zoom; + } else { + return RealSize(s.width, s.height) * zoom; + } +} +RealRect Rotation::trNoNeg(const RealRect& r) const { + throw "TODO"; +} + +RealPoint Rotation::trInv(const RealPoint& p) const { + RealPoint p2 = (p - origin) / zoom; + if (sideways()) { + return RealPoint(negY(p2.y), negX(p2.x)); + } else { + return RealPoint(negX(p2.x), negY(p2.y)); + } +} \ No newline at end of file diff --git a/src/util/rotation.hpp b/src/util/rotation.hpp new file mode 100644 index 00000000..5f935009 --- /dev/null +++ b/src/util/rotation.hpp @@ -0,0 +1,123 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_ROTATION +#define HEADER_UTIL_ROTATION + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include + +// ----------------------------------------------------------------------------- : Rotation + +/// An object that can rotate coordinates inside a specified rectangle +/** This class has lots of tr*** functions, they convert + * internal coordinates to external/screen coordinates. + * tr***inv do the opposite. + */ +class Rotation { + public: + /// Construct a rotation object with the given rectangle of external coordinates + /// and a given rotation angle and zoom factor + Rotation(int angle, const RealRect& rect, double zoom = 1.0); + + /// Change the zoom factor + inline void setZoom(double z) { zoom = z; } + /// Change the angle + void setAngle(int a); + + /// Translate a size or length + inline double trS(double s) const { return s * zoom; } + + /// Translate a single point + RealPoint tr(const RealPoint& p) const; + /// Translate a single size, the result may be negative + RealSize tr(const RealSize& s) const; + /// Translate a rectangle, the size of the result may be negative + RealRect tr(const RealRect& r) const; + + /// Translate a size, the result will never be negative + RealSize trNoNeg(const RealSize& s) const; + /// Translate a rectangle, the result will never have a negative size + RealRect trNoNeg(const RealRect& r) const; + + /// Translate a size or length back to internal 'coordinates' + inline double trInvS(double s) const { return s / zoom; } + + /// Translate a point back to internal coordinates + RealPoint trInv(const RealPoint& p) const; + + private: + int angle; //^ The angle of rotation in degrees (counterclockwise) + RealSize size; //^ Size of the rectangle, in external coordinates + RealPoint origin; //^ tr(0,0) + double zoom; //^ Zoom factor, zoom = 2.0 means that 1 internal = 2 external + + /// Is the rotation sideways (90 or 270 degrees)? + // Note: angle & 2 == 0 for angle in {0, 180} and != 0 for angle in {90, 270) + inline bool sideways() const { return (angle & 2) != 0; } + /// Is the x axis 'reversed' (after turning sideways)? + inline bool revX() const { return angle >= 180; } + /// Is the y axis 'reversed' (after turning sideways)? + inline bool revY() const { return angle == 90 || angle == 180; } + /// Negate if revX + inline double negX(double d) const { return revX() ? -d : d; } + /// Negate if revY + inline double negY(double d) const { return revY() ? -d : d; } +}; + +// ----------------------------------------------------------------------------- : Rotater + +/// An object that changes a rotation RIIA style +/** Usage: + * \begincode + * Rotation a, b; + * Rotater(a,b); + * a.tr(x) // now acts as a.tr(b.tr(x)) + * \endcode + */ +class Rotater { + /// Compose a rotation by onto the rotation rot + /** rot is restored when this object is destructed + */ + Rotater(Rotation& rot, const Rotation& by); + ~Rotater(); +}; + +// ----------------------------------------------------------------------------- : RotatedDC + +/// A DC with rotation applied +/** All draw** functions take internal coordinates. + */ +class RotatedDC : public Rotation { + public: + RotatedDC(int angle, const RealRect& rect, double zoom = 1.0); + RotatedDC(const Rotation& rotation); + + // ----------------------------- : Drawing + + void DrawText (const String& text, const RealPoint& pos); + void DrawBitmap(const Bitmap& bitmap, const RealPoint& pos); + void DrawImage (const Image& image, const RealPoint& pos, ImageCombine combine = COMBINE_NORMAL); + void DrawLine (const RealPoint& p1, const RealPoint& p2); + void DrawRectangle(const RealRect& r); + void DrawRoundedRectangle(const RealRect& r, double radius); + + // ----------------------------- : Forwarded properties + + void SetPen(const wxPen&); + void SetBrush(const wxBrush&); + void SetTextForeground(const Color&); + void SetLogicalFunction(int function); + + RealSize getTextExtent(const String& string); + +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/smart_ptr.hpp b/src/util/smart_ptr.hpp new file mode 100644 index 00000000..4287c0e5 --- /dev/null +++ b/src/util/smart_ptr.hpp @@ -0,0 +1,62 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_SMART_PTR +#define HEADER_UTIL_SMART_PTR + +/** @file util/shared_ptr.hpp + * + * Utilities related to boost smart pointers + */ + +// ----------------------------------------------------------------------------- : Includes + +#include +using namespace boost; + +// ----------------------------------------------------------------------------- : Declaring + +/// Declares the type TypeP as a shared_ptr +#define DECLARE_POINTER_TYPE(Type) \ + class Type; \ + typedef shared_ptr Type##P + +// ----------------------------------------------------------------------------- : Creating + +/// Allocate a new shared-pointed object +template +inline shared_ptr new_shared() { + return shared_ptr(new T()); +} + +/// Allocate a new shared-pointed object, given one argument to pass to the ctor of T +template +inline shared_ptr new_shared1(const A0& a0) { + return shared_ptr(new T(a0)); +} +/// Allocate a new shared-pointed object, given two arguments to pass to the ctor of T +template +inline shared_ptr new_shared2(const A0& a0, const A1& a1) { + return shared_ptr(new T(a0, a1)); +} +/// Allocate a new shared-pointed object, given three arguments to pass to the ctor of T +template +inline shared_ptr new_shared3(const A0& a0, const A1& a1, const A2& a2) { + return shared_ptr(new T(a0, a1, a2)); +} +/// Allocate a new shared-pointed object, given four arguments to pass to the ctor of T +template +inline shared_ptr new_shared4(const A0& a0, const A1& a1, const A2& a2, const A3& a3) { + return shared_ptr(new T(a0, a1, a2, a3)); +} +/// Allocate a new shared-pointed object, given seven arguments to pass to the ctor of T +template +inline shared_ptr new_shared7(const A0& a0, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6) { + return shared_ptr(new T(a0, a1, a2, a3, a4, a5, a6)); +} + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/string.cpp b/src/util/string.cpp new file mode 100644 index 00000000..f5101dd6 --- /dev/null +++ b/src/util/string.cpp @@ -0,0 +1,118 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include "string.hpp" +#include "for_each.hpp" +#include + +// ----------------------------------------------------------------------------- : Unicode + +String decodeUTF8BOM(const String& s) { + #ifdef UNICODE + if (!s.empty() && s.GetChar(0) == L'\xFEFF') { + // skip byte-order-mark + return s.substr(1); + } else { + return s; + } + #else + wxWCharBuffer buf = s.wc_str(wxConvUTF8); + if (buf && buf[size_t(0)] == L'\xFEFF') { + // skip byte-order-mark + return String(buf + 1, *wxConvCurrent); + } else { + return String(buf, *wxConvCurrent); + } + #endif +} + +void writeUTF8(wxTextOutputStream& stream, const String& str) { + #ifdef UNICODE + stream.WriteString(str); + #else + wxWCharBuffer buf = str.wc_str(*wxConvCurrent); + stream.WriteString(wxString(buf, wxConvUTF8)); + #endif +} + +// ----------------------------------------------------------------------------- : String utilities + +String trim(const String& s){ + size_t start = s.find_first_not_of(_(" \t")); + size_t end = s.find_last_not_of( _(" \t")); + if (start == String::npos) { + return String(); + } else { + return s.substr(start, end - start + 1); + } +} + +String trimLeft(const String& s) { + size_t start = s.find_first_not_of(_(' ')); + if (start == String::npos) { + return String(); + } else { + return s.substr(start); + } +} + +// ----------------------------------------------------------------------------- : Words + +String lastWord(const String& s) { + size_t endLastWord = s.find_last_not_of(_(' ')); + size_t startLastWord = s.find_last_of( _(' '), endLastWord); + if (endLastWord == String::npos) { + return String(); // empty string + } else if (startLastWord == String::npos) { + return s.substr(0, endLastWord + 1);// first word + } else { + return s.substr(startLastWord + 1, endLastWord - startLastWord); + } +} + +String stripLastWord(const String& s) { + size_t endLastWord = s.find_last_not_of(_(' ')); + size_t startLastWord = s.find_last_of(_(' '), endLastWord); + if (endLastWord == String::npos || startLastWord == String::npos) { + return String(); // single word or empty string + } else { + return s.substr(0, startLastWord + 1); + } +} + +// ----------------------------------------------------------------------------- : Caseing + +/// Quick check to see if the substring starting at the given iterator is equal +/// to some given string +bool is_substr(const String& s, String::iterator it, const Char* cmp) { + while (it != s.end() && *cmp != 0) { + if (*it++ != *cmp++) return false; + } + return *cmp == 0; +} +String capitalize(const String& s) { + String result = s; + bool afterSpace = true; + FOR_EACH_IT(it, result) { + if (*it == ' ') { + afterSpace = true; + } else if (afterSpace) { + afterSpace = false; + if (it != s.begin() && + (is_substr(result,it,_("is ")) || is_substr(result,it,_("the ")) || + is_substr(result,it,_("in ")) || is_substr(result,it,_("of ")) || + is_substr(result,it,_("to ")) || is_substr(result,it,_("at ")) || + is_substr(result,it,_("a " )))) { + // Short words are not capitalized, keep lower case + } else { + *it = toUpper(*it); + } + } + } + return result; +} diff --git a/src/util/string.hpp b/src/util/string.hpp new file mode 100644 index 00000000..6572b0f9 --- /dev/null +++ b/src/util/string.hpp @@ -0,0 +1,108 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_STRING +#define HEADER_UTIL_STRING + +/** @file util/string.hpp + * + * String and character utility functions and macros + */ + +// ----------------------------------------------------------------------------- : Includes + +#include "prec.hpp" +#include "for_each.hpp" +#include +#include + +class wxTextOutputStream; + +// ----------------------------------------------------------------------------- : String type + +/// The string type used throughout MSE +typedef wxString String; + +DECLARE_TYPEOF_NO_REV(String); // iterating over characters in a string + +// ----------------------------------------------------------------------------- : Unicode + +/// u if UNICODE is defined, a otherwise +#ifdef UNICODE +# define IF_UNICODE(u,a) u +#else +# define IF_UNICODE(u,a) a +#endif + +#undef _ +/// A string/character constant, correctly handled in unicode builds +#define _(S) IF_UNICODE(BOOST_PP_CAT(L,S), S) + +/// The character type used +typedef IF_UNICODE(wchar_t, char) Char; + +/// Decode a UTF8 string +/** In non-unicode builds the input is considered to be an incorrectly encoded utf8 string. + * In unicode builds it is a normal string, utf8 already decoded. + * Also removes a byte-order-mark from the start of the string if it is pressent + */ +String decodeUTF8BOM(const String& s); + +/// UTF8 Byte order mark for writing at the start of files +/** In non-unicode builds it is UTF8 encoded \xFEFF +* In unicode builds it is a normal \xFEFF +*/ +const Char BYTE_ORDER_MARK[] = IF_UNICODE(L"\xFEFF", "\xEF\xBB\xBF"); + +/// Writes a string to an output stream, encoded as UTF8 +void writeUTF8(wxTextOutputStream& stream, const String& str); + +// ----------------------------------------------------------------------------- : Char functions + +// Character set tests +inline bool isSpace(Char c) { return IF_UNICODE( iswspace(c) , isspace(c) ); } +inline bool isAlpha(Char c) { return IF_UNICODE( iswalpha(c) , isalpha(c) ); } +inline bool isDigit(Char c) { return IF_UNICODE( iswdigit(c) , isdigit(c) ); } +inline bool isAlnum(Char c) { return IF_UNICODE( iswalnum(c) , isalnum(c) ); } +inline bool isUpper(Char c) { return IF_UNICODE( iswupper(c) , isupper(c) ); } +inline bool isLower(Char c) { return IF_UNICODE( iswlower(c) , islower(c) ); } +// Character conversions +inline Char toUpper(Char c) { return IF_UNICODE( towupper(c) , toupper(c) ); } +inline Char toLower(Char c) { return IF_UNICODE( towlower(c) , tolower(c) ); } + +// ----------------------------------------------------------------------------- : String utilities + +/// Remove whitespace from both ends of a string +String trim(const String&); + +/// Remove whitespace from the start of a string +String trimLeft(const String&); + +// ----------------------------------------------------------------------------- : Words + +/// Returns the last word in a string +String lastWord(const String&); + +/// Remove the last word from a string, leaves whitespace before that word +String stripLastWord(const String&); + +// ----------------------------------------------------------------------------- : Caseing + +/// Make each word in a string start with an upper case character. +/// for use in menus +String capitalize(const String&); + +/// Make the first word in a string start with an upper case character. +/// for use in dialogs +String capitalizeSentence(const String&); + +/// Convert a field name to cannocial form: lower case and ' ' instead of '_' +/// non alphanumeric characters are ignored +/// "camalCase" is converted to words "camel case" +String cannocialNameForm(const String&); + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/vector2d.hpp b/src/util/vector2d.hpp new file mode 100644 index 00000000..85a38d00 --- /dev/null +++ b/src/util/vector2d.hpp @@ -0,0 +1,141 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_VECTOR2D +#define HEADER_UTIL_VECTOR2D + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +// ----------------------------------------------------------------------------- : Rounding + +// Rounding function for converting doubles to integers +inline int realRound(double d) { + return d > 0 ? d + 0.5 : d - 0.5; +} + +// ----------------------------------------------------------------------------- : Vector2D + +/// A simple 2d vector class +class Vector2D { + public: + /// Coordinates of this vector + double x, y; + + /// Default contructor + inline Vector2D() + : x(0), y(0) {} + /// Contructor with given x and y values + inline Vector2D(double x, double y) + : x(x), y(y) {} + + /// Addition of two vectors + inline Vector2D operator + (Vector2D p2) const { + return Vector2D(x + p2.x, y + p2.y); + } + /// Addition of two vectors + inline void operator += (Vector2D p2) { + x += p2.x; y += p2.y; + } + /// Subtract two vectors + inline Vector2D operator - (Vector2D p2) const { + return Vector2D(x - p2.x, y - p2.y); + } + /// Subtract two vectors + inline void operator -= (Vector2D p2) { + x -= p2.x; y -= p2.y; + } + /// Invert a vector + inline Vector2D operator - () const { + return Vector2D(-x, -y); + } + + /// Multiply with a scalar + inline Vector2D operator * (double r) const { + return Vector2D(x * r, y * r); + } + /// Multiply with a scalar + inline void operator *= (double r) { + x *= r; y *= r; + } + /// Divide by a scalar + inline Vector2D operator / (double r) const { + return Vector2D(x / r, y / r); + } + /// Divide by a scalar + inline void operator /= (double r) { + x /= r; y /= r; + } + + /// Inner product with another vector + inline double dot(Vector2D p2) const { + return (x * p2.x) + (y * p2.y); + } + /// Outer product with another vector + inline double cross(Vector2D p2) const { + return (x * p2.y) - (y * p2.x); + } + + /// Piecewise multiplication + inline Vector2D mul(Vector2D p2) { + return Vector2D(x * p2.x, y * p2.y); + } + /// Piecewise division + inline Vector2D div(Vector2D p2) { + return Vector2D(x / p2.x, y / p2.y); + } + + /// Apply a 'matrix' to this vector + inline Vector2D mul(Vector2D mx, Vector2D my) { + return Vector2D(dot(mx), dot(my)); + } + + /// Returns the square of the length of this vector + inline double lengthSqr() const { + return x*x + y*y; + } + /// Returns the length of this vector + inline double length() const { + return sqrt(lengthSqr()); + } + /// Returns a normalized version of this vector + /// i.e. length() == 1 + inline Vector2D normalized() const { + return *this / length(); + } + + inline operator wxPoint() const { + return wxPoint(realRound(x), realRound(y)); + } + + // Vector at infinity + static inline Vector2D infinity() { + double inf = numeric_limits::infinity(); + return Vector2D(inf, inf); + } +}; + +/// Piecewise minimum +inline Vector2D piecewise_min(Vector2D a, Vector2D b) { + return Vector2D( + a.x < b.x ? a.x : b.x, + a.y < b.y ? a.y : b.y + ); +} + +/// Piecewise maximum +inline Vector2D piecewise_max(Vector2D a, Vector2D b) { + return Vector2D( + a.x < b.x ? b.x : a.x, + a.y < b.y ? b.y : a.y + ); +} + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/window_id.hpp b/src/util/window_id.hpp new file mode 100644 index 00000000..62a23525 --- /dev/null +++ b/src/util/window_id.hpp @@ -0,0 +1,154 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_WINDOW_ID +#define HEADER_UTIL_WINDOW_ID + +// ----------------------------------------------------------------------------- : Includes + +// ----------------------------------------------------------------------------- : Menu ids + +/// Window ids for menus and toolbars +enum MenuID { + + ID_MENU_MIN = 0 +, ID_MENU_MAX = 999 + + // File menu +, ID_FILE_NEW = wxID_NEW +, ID_FILE_OPEN = wxID_OPEN +, ID_FILE_SAVE = wxID_SAVE +, ID_FILE_SAVE_AS = wxID_SAVEAS +, ID_FILE_STORE = 1 +, ID_FILE_EXIT = wxID_EXIT +, ID_FILE_EXPORT = 2 +, ID_FILE_EXPORT_HTML = 3 +, ID_FILE_EXPORT_IMAGE = 4 +, ID_FILE_EXPORT_IMAGES = 5 +, ID_FILE_EXPORT_APPR = 6 +, ID_FILE_EXPORT_MWS = 7 +, ID_FILE_PRINT = wxID_PRINT +, ID_FILE_PRINT_PREVIEW = wxID_PREVIEW +, ID_FILE_INSPECT = 8 +, ID_FILE_RECENT = wxID_FILE1 +, ID_FILE_RECENT_MAX = wxID_FILE9 + + // Edit menu +, ID_EDIT_UNDO = wxID_UNDO +, ID_EDIT_REDO = wxID_REDO +, ID_EDIT_CUT = wxID_CUT +, ID_EDIT_COPY = wxID_COPY +, ID_EDIT_PASTE = wxID_PASTE +, ID_EDIT_DELETE = 101 +, ID_EDIT_DUPLICATE = 102 +, ID_EDIT_FIND = wxID_FIND +, ID_EDIT_FIND_NEXT = 103 +, ID_EDIT_REPLACE = wxID_REPLACE +, ID_EDIT_PREFERENCES = 104 + + // Window menu (MainWindow) +, ID_WINDOW_NEW = 201 +, ID_WINDOW_MIN = 202 +, ID_WINDOW_MAX = 220 + + // Help menu (MainWindow) +, ID_HELP_INDEX = 301 +, ID_HELP_ABOUT + + // Mode menu (SymbolWindow) +, ID_MODE_MIN = 401 +, ID_MODE_SELECT = ID_MODE_MIN +, ID_MODE_ROTATE +, ID_MODE_POINTS +, ID_MODE_SHAPES +, ID_MODE_PAINT +, ID_MODE_MAX +}; + + +// ----------------------------------------------------------------------------- : Child ids + +/// Ids for menus on child panels (MainWindowPanel / SymbolEditorBase) +enum ChildMenuID { + + ID_CHILD_MIN = 1000 +, ID_CHILD_MAX = 2999 + + // Cards menu +, ID_CARD_ADD = 1001 +, ID_CARD_ADD_MULT +, ID_CARD_REMOVE +, ID_CARD_PREV +, ID_CARD_NEXT +, ID_CARD_ROTATE +, ID_CARD_ROTATE_0 +, ID_CARD_ROTATE_90 +, ID_CARD_ROTATE_180 +, ID_CARD_ROTATE_270 + + // Keyword menu +, ID_KEYWORD_ADD = 1101 +, ID_KEYWORD_REMOVE +, ID_KEYWORD_PREV +, ID_KEYWORD_NEXT + + + // SymbolSelectEditor toolbar/menu +, ID_PART = 2001 +, ID_PART_MERGE = ID_PART + PART_MERGE +, ID_PART_SUBTRACT = ID_PART + PART_SUBTRACT +, ID_PART_INTERSECTION = ID_PART + PART_INTERSECTION +, ID_PART_DIFFERENCE = ID_PART + PART_DIFFERENCE +, ID_PART_OVERLAP = ID_PART + PART_OVERLAP +, ID_PART_BORDER = ID_PART + PART_BORDER +, ID_PART_MAX + + // SymbolPointEditor toolbar/menu +, ID_SEGMENT = 2101 +, ID_SEGMENT_LINE = ID_SEGMENT + SEGMENT_LINE +, ID_SEGMENT_CURVE = ID_SEGMENT + SEGMENT_CURVE +, ID_SEGMENT_MAX +, ID_LOCK = 2151 +, ID_LOCK_FREE = ID_LOCK + LOCK_FREE +, ID_LOCK_DIR = ID_LOCK + LOCK_DIR +, ID_LOCK_SIZE = ID_LOCK + LOCK_SIZE +, ID_LOCK_MAX + + // SymbolBasicShapeEditor toolbar/menu +, ID_SHAPE = 2201 +, ID_SHAPE_CIRCLE = ID_SHAPE +, ID_SHAPE_RECTANGLE +, ID_SHAPE_POLYGON +, ID_SHAPE_STAR +, ID_SHAPE_MAX +, ID_SIDES +}; + + +// ----------------------------------------------------------------------------- : Control/window ids + +/// Window ids for controls +enum ControlID { + + ID_CONTROL_MIN = 6000 +, ID_CONTROL_MAX = 6999 + + // Controls +, ID_VIEWER = 6001 +, ID_EDITOR +, ID_CONTROL +, ID_CARD_LIST +, ID_PART_LIST +, ID_NOTES +, ID_KEYWORD +, ID_PARAMETER +, ID_SEPARATOR +, ID_REMINDER +, ID_RULES +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/tools/gen.pl b/tools/gen.pl new file mode 100644 index 00000000..c4be239f --- /dev/null +++ b/tools/gen.pl @@ -0,0 +1,31 @@ +# Generate a header and source file + +$file = $ARGV[0]; +$macro = uc $file; +$macro =~ s@/@_@g; + +# read templates + +open F, "../src/code_template.hpp"; +$hpp = join('',); +close F; + +open F, "../src/code_template.cpp"; +$cpp = join('',); +close F; + +# insert stuff + +$hpp =~ s/_\n/_$macro\n/g; + +$cpp =~ s@@<$file.hpp>@g; + +# write files + +open F, "> ../src/$file.hpp"; +print F $hpp; +close F; + +open F, "> ../src/$file.cpp"; +print F $cpp; +close F; \ No newline at end of file