diff options
author | Xiangfu Liu <xiangfu@openmobilefree.net> | 2012-10-08 05:38:57 +0200 |
---|---|---|
committer | Xiangfu Liu <xiangfu@openmobilefree.net> | 2012-10-08 05:38:57 +0200 |
commit | 4edcd19776f36123e6bcb7581b6863af719da84b (patch) | |
tree | 7d11bc6c0272a4a9c3499d544f36e00ab6474a28 |
Import fped_0.1+201210.orig.tar.gz
[dgit import orig fped_0.1+201210.orig.tar.gz]
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | COPYING.GPLv2 | 339 | ||||
-rw-r--r-- | Makefile | 195 | ||||
-rw-r--r-- | README | 749 | ||||
-rw-r--r-- | TODO | 80 | ||||
-rw-r--r-- | bitset.c | 133 | ||||
-rw-r--r-- | bitset.h | 34 | ||||
-rw-r--r-- | coord.c | 250 | ||||
-rw-r--r-- | coord.h | 98 | ||||
-rw-r--r-- | cpp.c | 217 | ||||
-rw-r--r-- | cpp.h | 26 | ||||
-rw-r--r-- | delete.c | 705 | ||||
-rw-r--r-- | delete.h | 32 | ||||
-rw-r--r-- | dump.c | 625 | ||||
-rw-r--r-- | dump.h | 49 | ||||
-rw-r--r-- | error.c | 83 | ||||
-rw-r--r-- | error.h | 34 | ||||
-rw-r--r-- | examples/fbga.fpd | 56 | ||||
-rw-r--r-- | examples/meas.fpd | 24 | ||||
-rw-r--r-- | examples/qfn.fpd | 73 | ||||
-rw-r--r-- | examples/quad.fpd | 15 | ||||
-rw-r--r-- | examples/sc89.fpd | 80 | ||||
-rw-r--r-- | examples/tab.fpd | 16 | ||||
-rw-r--r-- | expr.c | 677 | ||||
-rw-r--r-- | expr.h | 154 | ||||
-rw-r--r-- | file.c | 215 | ||||
-rw-r--r-- | file.h | 37 | ||||
-rw-r--r-- | fpd.h | 36 | ||||
-rw-r--r-- | fpd.l | 204 | ||||
-rw-r--r-- | fpd.y | 1288 | ||||
-rw-r--r-- | fped.1 | 51 | ||||
-rw-r--r-- | fped.c | 274 | ||||
-rw-r--r-- | fped.h | 23 | ||||
-rw-r--r-- | gnuplot.c | 195 | ||||
-rw-r--r-- | gnuplot.h | 22 | ||||
-rw-r--r-- | gui.c | 430 | ||||
-rw-r--r-- | gui.h | 38 | ||||
-rw-r--r-- | gui.html | 282 | ||||
-rw-r--r-- | gui_canvas.c | 589 | ||||
-rw-r--r-- | gui_canvas.h | 45 | ||||
-rw-r--r-- | gui_frame.c | 1875 | ||||
-rw-r--r-- | gui_frame.h | 36 | ||||
-rw-r--r-- | gui_frame_drag.c | 589 | ||||
-rw-r--r-- | gui_frame_drag.h | 30 | ||||
-rw-r--r-- | gui_inst.c | 668 | ||||
-rw-r--r-- | gui_inst.h | 55 | ||||
-rw-r--r-- | gui_meas.c | 470 | ||||
-rw-r--r-- | gui_meas.h | 30 | ||||
-rw-r--r-- | gui_over.c | 215 | ||||
-rw-r--r-- | gui_over.h | 79 | ||||
-rw-r--r-- | gui_status.c | 1117 | ||||
-rw-r--r-- | gui_status.h | 91 | ||||
-rw-r--r-- | gui_style.c | 93 | ||||
-rw-r--r-- | gui_style.h | 130 | ||||
-rw-r--r-- | gui_tool.c | 1241 | ||||
-rw-r--r-- | gui_tool.h | 84 | ||||
-rw-r--r-- | gui_util.c | 399 | ||||
-rw-r--r-- | gui_util.h | 84 | ||||
-rw-r--r-- | hole.c | 86 | ||||
-rw-r--r-- | hole.h | 18 | ||||
-rw-r--r-- | icons/all.fig | 22 | ||||
-rw-r--r-- | icons/all_off.fig | 22 | ||||
-rw-r--r-- | icons/arc.fig | 13 | ||||
-rw-r--r-- | icons/bright.fig | 24 | ||||
-rw-r--r-- | icons/bright_off.fig | 18 | ||||
-rw-r--r-- | icons/circ.fig | 12 | ||||
-rw-r--r-- | icons/delete.fig | 15 | ||||
-rw-r--r-- | icons/delete_off.fig | 15 | ||||
-rw-r--r-- | icons/frame.fig | 16 | ||||
-rw-r--r-- | icons/hole.fig | 22 | ||||
-rw-r--r-- | icons/line.fig | 13 | ||||
-rw-r--r-- | icons/meas.fig | 19 | ||||
-rw-r--r-- | icons/meas_off.fig | 19 | ||||
-rw-r--r-- | icons/meas_x.fig | 23 | ||||
-rw-r--r-- | icons/meas_y.fig | 23 | ||||
-rw-r--r-- | icons/pad.fig | 15 | ||||
-rw-r--r-- | icons/point.fig | 14 | ||||
-rw-r--r-- | icons/rect.fig | 13 | ||||
-rw-r--r-- | icons/rpad.fig | 17 | ||||
-rw-r--r-- | icons/stuff.fig | 17 | ||||
-rw-r--r-- | icons/stuff_off.fig | 17 | ||||
-rw-r--r-- | icons/template.fig | 11 | ||||
-rw-r--r-- | icons/vec.fig | 17 | ||||
-rw-r--r-- | inst.c | 1423 | ||||
-rw-r--r-- | inst.h | 221 | ||||
-rw-r--r-- | kicad.c | 327 | ||||
-rw-r--r-- | kicad.h | 22 | ||||
-rw-r--r-- | layer.c | 196 | ||||
-rw-r--r-- | layer.h | 84 | ||||
-rw-r--r-- | leak.supp | 31 | ||||
-rwxr-xr-x | leakcheck | 4 | ||||
-rw-r--r-- | manual/concept-inst.fig | 35 | ||||
-rw-r--r-- | manual/intro-1.png | bin | 0 -> 4561 bytes | |||
-rw-r--r-- | manual/intro-2.png | bin | 0 -> 4544 bytes | |||
-rw-r--r-- | manual/intro-3.png | bin | 0 -> 3700 bytes | |||
-rw-r--r-- | manual/intro-4.png | bin | 0 -> 461 bytes | |||
-rw-r--r-- | manual/intro-5.png | bin | 0 -> 1217 bytes | |||
-rw-r--r-- | manual/intro-6.png | bin | 0 -> 19348 bytes | |||
-rw-r--r-- | meas.c | 326 | ||||
-rw-r--r-- | meas.h | 82 | ||||
-rw-r--r-- | obj.c | 604 | ||||
-rw-r--r-- | obj.h | 283 | ||||
-rw-r--r-- | overlap.c | 226 | ||||
-rw-r--r-- | overlap.h | 45 | ||||
-rw-r--r-- | postscript.c | 1234 | ||||
-rw-r--r-- | postscript.h | 35 | ||||
-rwxr-xr-x | test/Common | 85 | ||||
-rwxr-xr-x | test/dbg_meas | 54 | ||||
-rwxr-xr-x | test/del_frame | 97 | ||||
-rwxr-xr-x | test/del_vec | 70 | ||||
-rwxr-xr-x | test/floor | 40 | ||||
-rwxr-xr-x | test/frame_ref | 149 | ||||
-rwxr-xr-x | test/iprint | 101 | ||||
-rwxr-xr-x | test/keys | 134 | ||||
-rwxr-xr-x | test/meas_qual | 232 | ||||
-rwxr-xr-x | test/structure | 111 | ||||
-rwxr-xr-x | test/tsort | 137 | ||||
-rw-r--r-- | tsort.c | 162 | ||||
-rw-r--r-- | tsort.h | 25 | ||||
-rw-r--r-- | unparse.c | 136 | ||||
-rw-r--r-- | unparse.h | 22 | ||||
-rw-r--r-- | util.c | 114 | ||||
-rw-r--r-- | util.h | 70 |
123 files changed, 23180 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1abcc21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +*.d +icons/*.xpm diff --git a/COPYING.GPLv2 b/COPYING.GPLv2 new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/COPYING.GPLv2 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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. + + <signature of Ty Coon>, 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 Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..131e58d --- /dev/null +++ b/Makefile @@ -0,0 +1,195 @@ +# +# Makefile - Makefile of fped, the footprint editor +# +# Written 2009-2012 by Werner Almesberger +# Copyright 2009-2012 by Werner Almesberger +# +# 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. +# + +PREFIX ?= /usr/local + +UPLOAD = www-data@downloads.qi-hardware.com:werner/fped/ + +OBJS = fped.o expr.o coord.o obj.o delete.o inst.o util.o error.o \ + unparse.o file.o dump.o kicad.o postscript.o gnuplot.o meas.o \ + layer.o overlap.o hole.o tsort.o bitset.o \ + cpp.o lex.yy.o y.tab.o \ + gui.o gui_util.o gui_style.o gui_inst.o gui_status.o gui_canvas.o \ + gui_tool.o gui_over.o gui_meas.o gui_frame.o gui_frame_drag.o + +XPMS = point.xpm delete.xpm delete_off.xpm \ + vec.xpm frame.xpm \ + line.xpm rect.xpm pad.xpm rpad.xpm hole.xpm arc.xpm circ.xpm \ + meas.xpm meas_x.xpm meas_y.xpm \ + stuff.xpm stuff_off.xpm meas_off.xpm \ + bright.xpm bright_off.xpm all.xpm all_off.xpm + +PNGS = intro-1.png intro-2.png intro-3.png intro-4.png intro-5.png \ + intro-6.png concept-inst.png + +SHELL = /bin/bash + +CPPFLAGS += +CFLAGS_GTK = `pkg-config --cflags gtk+-2.0` +LIBS_GTK = `pkg-config --libs gtk+-2.0` + +CFLAGS_WARN = -Wall -Wshadow -Wmissing-prototypes \ + -Wmissing-declarations -Wno-format-zero-length +CFLAGS += -g -std=gnu99 $(CFLAGS_GTK) -DCPP='"cpp"' \ + -DVERSION='"$(GIT_VERSION)$(GIT_STATUS)"' $(CFLAGS_WARN) +SLOPPY = -Wno-unused -Wno-implicit-function-declaration \ + -Wno-missing-prototypes -Wno-missing-declarations +LDFLAGS += +LDLIBS = -lm -lfl $(LIBS_GTK) +YACC = bison -y +YYFLAGS = -v + +GIT_VERSION:=$(shell git rev-parse HEAD | cut -c 1-7) +GIT_STATUS:=$(shell [ -z "`git status -s -uno`" ] || echo +) + +MKDEP = $(DEPEND) $(1).c | \ + sed -e \ + '/^\(.*:\)\? */{p;s///;s/ *\\\?$$/ /;s/ */:\n/g;H;}' \ + -e '$${g;p;}' -e d >$(1).d; \ + [ "$${PIPESTATUS[*]}" = "0 0" ] || { rm -f $(1).d; exit 1; } + + +# ----- Verbosity control ----------------------------------------------------- + +CPP := $(CPP) # make sure changing CC won't affect CPP + +CC_normal := $(CC) +YACC_normal := $(YACC) +LEX_normal := $(LEX) +DEPEND_normal := $(CPP) $(CFLAGS) -MM -MG + +CC_quiet = @echo " CC " $@ && $(CC_normal) +YACC_quiet = @echo " YACC " $@ && $(YACC_normal) +LEX_quiet = @echo " LEX " $@ && $(LEX_normal) +GEN_quiet = @echo " GENERATE " $@ && +DEPEND_quiet = @$(DEPEND_normal) + +ifeq ($(V),1) + CC = $(CC_normal) + LEX = $(LEX_normal) + YACC = $(YACC_normal) + GEN = + DEPEND = $(DEPEND_normal) +else + CC = $(CC_quiet) + LEX = $(LEX_quiet) + YACC = $(YACC_quiet) + GEN = $(GEN_quiet) + DEPEND = $(DEPEND_quiet) +endif + +# ----- Rules ----------------------------------------------------------------- + +.PHONY: all dep depend clean spotless +.PHONY: install uninstall manual upload-manual +.PHONY: montage test tests valgrind + +.SUFFIXES: .fig .xpm .ppm + +# compile and generate dependencies, based on +# http://scottmcpeak.com/autodepend/autodepend.html + +%.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c $*.c -o $*.o + $(call MKDEP, $*) + +# generate 26x26 pixels icons, then drop the 1-pixel frame + +.fig.ppm: + $(GEN) fig2dev -L ppm -Z 0.32 -S 4 $< | \ + convert -crop 24x24+1+1 - - >$@; \ + [ "$${PIPESTATUS[*]}" = "0 0" ] || { rm -f $@; exit 1; } + +# ppmtoxpm is very chatty, so we suppress its stderr + +.ppm.xpm: + $(GEN) export TMP=_tmp$$$$; ppmcolormask white $< >$$TMP && \ + ppmtoxpm -name xpm_`basename $@ .xpm` -alphamask $$TMP \ + $< >$@ 2>/dev/null && rm -f $$TMP || \ + { rm -f $@ $$TMP; exit 1; } + +all: fped + +fped: $(OBJS) + $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS) + +lex.yy.c: fpd.l y.tab.h + $(LEX) fpd.l + +lex.yy.o: lex.yy.c y.tab.h + $(CC) -c $(CFLAGS) $(SLOPPY) lex.yy.c + $(call MKDEP, lex.yy) + +y.tab.c y.tab.h: fpd.y + $(YACC) $(YYFLAGS) -d fpd.y + +y.tab.o: y.tab.c + $(CC) -c $(CFLAGS) $(SLOPPY) y.tab.c + $(call MKDEP, y.tab) + +gui_tool.o gui.o: $(XPMS:%=icons/%) + +# ----- Upload the GUI manual ------------------------------------------------- + +manual: $(XPMS:%=icons/%) + for n in $(XPMS:%.xpm=%); do \ + convert icons/$$n.xpm manual/$$n.png || exit 1; done + fig2dev -L png -S 4 manual/concept-inst.fig \ + >manual/concept-inst.png + +upload-manual: manual + scp gui.html README $(UPLOAD)/ + scp $(XPMS:%.xpm=manual/%.png) $(PNGS:%=manual/%) \ + $(UPLOAD)/manual/ + +# ----- Debugging help -------------------------------------------------------- + +montage: + montage -label %f -frame 3 __dbg????.png png:- | display - + +# ----- Dependencies ---------------------------------------------------------- + +dep depend .depend: + @echo 'no need to run "make depend" anymore' 1>&2 + +-include $(OBJS:.o=.d) + +# ----- Tests ----------------------------------------------------------------- + +test tests: all + LANG= sh -c \ + 'passed=0 && cd test && \ + for n in [a-z]*; do \ + [ $$n != core ] && SCRIPT=$$n CWD_PREFIX=.. . ./$$n; done; \ + echo "Passed all $$passed tests"' + +valgrind: + VALGRIND="valgrind -q" $(MAKE) tests + +# ----- Cleanup --------------------------------------------------------------- + +clean: + rm -f $(OBJS) $(XPMS:%=icons/%) $(XPMS:%.xpm=icons/%.ppm) + rm -f lex.yy.c y.tab.c y.tab.h y.output .depend $(OBJS:.o=.d) + rm -f __dbg????.png _tmp* test/core + +spotless: clean + rm -f fped + +# ----- Install / uninstall --------------------------------------------------- + +install: all + mkdir -p $(DESTDIR)/$(PREFIX)/bin/ + install -m 755 fped $(DESTDIR)/$(PREFIX)/bin/ + +uninstall: + rm -f $(DESTDIR)/$(PREFIX)/bin/fped @@ -0,0 +1,749 @@ +fped - Footprint editor +======================= + +fped is an editor that allows the interactive creation of footprints of +electronic components. Footprint definitions are stored in a text format +that resembles a programming language. + +The language is constrained such that anything that can be expressed in +the textual definition also has a straightforward equivalent operation +that can be performed through the GUI. + +This README describes only the footprint definition language. A +description of the GUI can be found here: + +http://downloads.qi-hardware.com/people/werner/fped/gui.html + +This work is distributed under the terms of the GNU GENERAL PUBLIC +LICENSE, Version 2: + + 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. + +For your convenience, a copy of the complete license has been included +in the file COPYING.GPLv2. + + +Building +-------- + +Prerequisites: + +- bash +- flex +- bison +- fig2dev (transfig) +- ImageMagick +- Netpbm +- Gtk+ 2.x development package (libgtk2.0-dev or similar) +- Liberation Fonts (ttf-liberation or similar) + +Check out the repository: + + git clone git://projects.qi-hardware.com/fped.git + cd fped + +Get updates: + + git pull + +Compile: + + make + +Run an example: + + ./fped examples/qfn.fpd + + +Motivation +---------- + +KiCad already includes a footprint ("module") editor, so why do we need +a new one ? The issue with footprint generation for KiCad is that the +built-in module editor is basically a drawing program that only captures +the result of the module author's interpretation of a footprint drawing, +but does not include the steps that led to this construction. + +Furthermore, accurate measuring of dimensions in the drawing can only be +done manually in the module editor, which makes review difficult and +time-consuming. + +In fped, the construction process is made explicit and each step can be +expressed in terms of the parameters that appear in the vendor's +drawing. Dimensions can be explicitly measured and the results can be +included in the graphical output generated by fped. + +Directly using parameters and construction steps from the reference +drawing reduces the risk of mistakes. Visualizing the construction +process and verificative measurements helps efficient and accurate +review. + + +Footprint definition file format +-------------------------------- + +Footprint definitions are stored in text files. The program "fped" reads +and (soon) writes such files, visualizes their content, and provides a +graphical editor for them. + +The syntax is unique and draws from elements of a variety of languages +commonly found on unix systems. One specialty is that there are no +reserved words - the language keywords appear only at the beginning of +a line and can thus be recognized as such without restricting their use +for identifiers. This reduces the risk of creating incompatibilities +with existing designs when introduction future language features. + +fped uses the C preprocessor for comments, conditional compilation, +and - to a limited extent - also macros. Long lines can be split by +ending them with a backslash. If multiple items need to be placed in a +single line, e.g., in a macro, they can be separated with semicolons. + +The file has the following structure: + +frame definitions +... +package name +setup +objects +... + + +Geometry model +-------------- + +The geometry model consists of frames, vectors, and objects. The shape of +objects is defined by a number of points. These points are produced by +concatenating vectors. + +E.g., to draw a line from (1mm, 1mm) to (2mm, 2mm), one would make a +vector from the origin to (1mm, 1mm) and one either from the origin or +from the previous vector to (2mm, 2mm), and then make a line connecting +the two points. + + +Setup +- - - + +The setup section defines settings that affect the entire footprint. +It is optional and can contain a "unit" directive and an "allow" +directive. + + +Units +- - - + +fped can calculate in mm and mil. Units are specified by following a +number with "mm" or "mil", separated by zero or more spaces or tabs. + +Examples: + +1mm +2 mil + +Units can be mixed in calculations, e.g., + +set a = 1mm+20mil +set b = 10*1mm + +All values used as dimensions must be either mm or mil. + +The default unit can be set with one of the following directives: + +unit mm +unit mil +unit auto + +If the "unit" directive is omitted, fped defaults to millimeters. + +When saving a footprint definition, the default unit is set to the +unit set in the GUI. + + +Allow +- - - + +fped normally disallows overlapping pads. This restriction can be +relaxed with the "allow" directive. + +allow touch + +Allows pads touching but not having more than their border in common. + +allow overlap + +Do not check for overlaps at all. + +If the "allow" directive is omitted, fped defaults to allowing +neigher overlap nor touch. + + +Vectors +- - - - + +Vectors can be anonymous or they can be named for future reference: + +vec <base> ( <x-expr>, <y-expr> ) +<identifier>: vec <base> ( <x-expr>, <y-expr> ) + +The base can be one of the following items: + +- @ is the origin of the frame containing the vector +- . is the end of the previous vector in this frame +- <identifier> is the name of a previous vector in the same frame + +The following example would draw the line described in the previous +section: + +a: vec @(1mm, 1mm) +b: vec .(1mm, 1mm) +line a b + + +Silk screen objects +- - - - - - - - - - + +The output of fped is a footprint definition that contains pads and silk +screen drawings (we may add more layers in the future). These items are +called "objects". Their geometry is defined through points obtained with +vectors. + +A line connects two points: + +line <point-a> <point-b> [<width>] + +The points can be specified with @, ., and an identifier, just like +a vector base. The option width specifies the thickness of the silk +screen line. If omitted, a hard-coded default of 15 mil is used. + +A rectangle has sides parallel to the x and y axis and is defined +by two diagonally opposite corners: + +rect <point-a> <point-b> [<width>] + +A circle is defined by its center and a point on the circle: + +circ <center> <point> [<width>] + +This example draws a unit circle: + +vec @(1mm, 0mm) +circ @ . + +An arc is like a circle, but the part of the circle drawn is determined +by two points. The first point determines the radius and the starting +angle. The second point only determines the end angle but its distance +from the center is ignored. + +arc <center> <radius> <end> [<width>] + +The arc is drawn in a counter-clockwise direction. The following example +draws an arc of the unit circle in the x > 0, y > 0 quadrant: + +from: vec @(1mm, 0mm) +to: vec @(0mm, 1mm) +arc @ from to + + +Pads +- - + +Pads are similar to rectangles, but they also have a name. + +pad "<name>" <point-a> <point-b> [<type>] + +Variables can be expanded in a pad's name by prefixing their name with +a dollar sign. The ${name} syntax is also available. + +Example: + +vec @(1mm, 1mm) +pad "1" @ . + +Pads normally affect the surface copper layer, the solder mask layer, +and the solder paste layer. This can be modified with the optional +type argument: + +Type Layers +--------- ------------------------------------- +(default) copper, solder mask, and solder paste +bare copper and solder mask +trace copper without solder mask opening +paste solder paste +mask solder mask + +Typical uses: +- "bare": connectors printed directly on the PCB +- "trace": connections or antennas +- "paste": sparse solder paste, e.g., for QFN center pads +- "mask": non-standard mask openings, e.g., for solder mask defined + pads + + +Rounded pads +- - - - - - + +Rounded pads are like rectangular pads except that they end with a +semi-circle at each of the smaller sides of the enclosing rectangle. +If enclosed in a square, rounded pads form a circle. + +rpad "<name>" <point-a> <point-b> [<type>] + + +Holes +- - - + +Holes can be used for through-hole pins or for mechanical support. +In the former case, the hole must be placed inside a pad. Only one +hole per pad is allowed. Mechanical holes must be outside any pads. + +Through-hole pads are always present on both sides of the board, i.e., +when fped generates a KiCad module, the surface layers of a pad +containing a hole are propagated to the opposite side of the board. + +Holes have the same shape as a rounded pad and their geometry is +defined in the same way: + +hole <point-a> <point-b> + + +Measurements +- - - - - - + +*** This is obsolete - see the section on new-style mesurements at the end. *** + +Measurements show the distance between two points: + +meas <point-a> <point-b> <offset> + +The offset is the distance from the imaginary line connecting points A +and B the measurement line is draw: + +- if the offset is 0mm, the line will connect A and B +- if the offset is positive, the line would be on the left-hand side when + traveling from A to B +- if the offset is negative , the line would be on the right-hand side when + traveling from A to B + +Example: + +a: vec @(-1mm, 1mm) +b: vec @(1mm, 1mm) +meas a b 0.2 mm + + +Package name +- - - - - - + +The package name is a non-empty string of printable ASCII characters, +including spaces. If the "package" directive is omitted, fped defaults +to using the name "_". + +package "<name>" + +Examples: + +package "48-SSOP" +package "0603" + +Like in pad names, variables are expanded in package names. This allows +the generation of multiple packages from a single definition. + + +Frames +- - - + +Frames are used to group things and to reuse them multiple times. Frames +must be defined before they can be used: + +frame <name> { + ... items ... +} + +Once defined, a frame is placed at a given location with + +frame <name> <point> + +The frame definitions must precede all other items in a footprint +description. Frames cannot be defined inside other frames, but frames +can invoke each other recursively. + +For example, this puts two unity squares, one centered at (0 mm, 0 mm), +the other at (2 mm, 0 mm): + +frame unit_square { + a: vec @(-0.5mm, -0.5mm) + b: vec .(1mm, 1mm) + rect a b +} + +frame unit_square @ +vec @(2mm, 0mm) +frame unit_square . + + +Names and variables +------------------- + +fped uses several name spaces: + +- frame names occupy one global name space + +- vector names occupy name spaces delimited by the frame they're + contained in. A vector name is only visible inside the frame in which + it is defined. + +- variable names occupy name spaces delimited by the frame they're + contained in. A variable lookup starts in the frame in which the + corresponding expression appears and propagates to outer frames + until the variable is found. + +- pads occupy one global name space (this is currently not enforced) + +Note that names cannot be redefined. E.g., this does not work: + +set a = 1 +set a = a+1 + +The names spaces of frames, vectors, variables, and pads are separate +from each other. + + +Simple variables +- - - - - - - - + +A variable with a single value is defined with the following +assignment syntax: + +set <identifier> = <expression> + +Example: + +set a = b+2 + + +Loops +- - - + +A loop is a variable with a range of values: + +loop <identifier> = <from>, <to> + +The variable assumes all the values i for <from> <= i <= <to>, in +increments of one. E.g., + +loop n = 1, 3 + +and + +loop n = 1, 3.5 + +both assign the values 1, 2, and 3 to the variable "n". The +following loop would not execute at all: + +loop n = 1, 0 + +This can be used to implement conditional execution. For example, +the items in the following frame would be instantiated if the +variable "enable" is set to 1 but not it is set to 0: + +frame ... { + loop dummy = 1, enable + ... +} + +When a loop is executed, the objects contained in the body of the +enclosing frame are generated for each value of the variable. If +a frame contains multiple loops, all possible combinations of the +values are generated. + +The following example draws three concentric circles around the +origin, with radii 1, 2, and 3: + +loop x = 1, 3 +vec @(x*1mm, 0mm) +circ @ . + + +Tables +- - - + +Tables combine values for multiple variables. Like loops, they are +used to iteratively generate objects. A table begins with a row of +variable names, followed by one or more rows with values. Rows are +enclosed in curly braces and their elements are separated by commas. + +table + { <identifier>, ... } + { <expression>, ... } + ... + +Like loops, tables are iterated to generate objects. The following +example is equivalent to the one in the previous section: + +table + { x } + { 1mm } + { 2mm } + { 3mm } +vec @(x, 0mm) +circ @ . + +Note that we can set the unit of the values directly in this case. + +Iteration is performed over rows. All variables of the table are set +to the value in the respective row at the same time. For example, in + +table + { x, y } + { 1, 2 } + { 3, 4 } + +(x, y) assume the values (1, 2) and (3, 4). + +Tables can also be used to provide information that depends on +other variables. The value of such a variable acts as a key, and a +row is only selected if all the keys in that row match the +respective variables. To mark a variable as being used as key, its +name it prefixed with a question mark. + +Example: + +loop n = 1, 2, 3 +table + { ?n, name } + { 1, "one" } + { 2, "two" } + { 3, "three" } + + +Expressions +----------- + +Expressions can contain numeric constants (in non-exponential notation), +variable names, the arithmetic operations +, -, *, /, unary -, and the +functions sin(), cos(), sqrt(), and floor(). + +Parentheses can be used to change precedence. + +The argument of sin and cos is a dimensionless number that specifies the +angle in degrees. E.g., sin(90) yields 1. + +The argument of sqrt() can be dimensionless or have a dimension with an +exponent that's a multiple of two. E.g., sqrt(2) and sqrt(2mm*3mm) are +valid expressions, sqrt(2mm) isn't. + +The function floor() returns the next integer that is below or equal to +the argument. If the argument has a dimension, that dimension is +preserved. E.g., floor(-1.2) returns -2, floor(4.7mm) returns 4mm. + + +GUI +--- + +Part of the GUI is described in +http://downloads.qi-hardware.com/people/werner/fped/gui.html + + +Keyboard shortcuts +- - - - - - - - - + +Space reset user coordinates ++, = zoom in (like mouse wheel forward) +- zoom out (like mouse wheel backward) +. cursor position to screen center (like middle click) +* zoom and center to extents +# zoom and center to currently active frame instance +U undelete the previously deleted object +/ Switch between variable and item display. + + +Canvas +- - - + +To create a new object, click on the corresponding tool icon, move the +mouse to the base point of the new object, then drag to the object's +second point. + +Frame references are created as follows: + +- select the frame you want to add +- click on the frame icon. A black dot should appear on the icon. +- select the frame on which you want to add the new reference. + The black dot should change to a green dot. If the current frame + is a child of the selected frame, the dot remains black. +- click on the desired base location + +To change a point of an object, select the object, then drag the point +to its new location. To edit the object's parameters, select it and +make the changes in the input area at the bottom. + +To delete an object, select the delete tool and click on the object. +Deleted objects can be undeleted by pressing "u". If any other changes +have been made since deletion, fped may misbehave. If deleting a vector, +all items that reference it are deleted as well. + + +Experimental: new-style measurements +------------------------------------ + +New-style measurements can measure the distance between various pairs +of points, not only between points in the same instance and the same +frame. They operate on the set of points produced during instantiation. + +New-style measurements are placed in the root frame after all other +items. + +Known issues: +- they currently can't be edited through the GUI +- tie-breaking heuristics don't always do what one expects + +Syntax: + +<type> [<label>] <from> <op> <to> [<offset>] + +Types: +- meas: measure diagonally +- measx: measure along the X axis +- measy: measure along the y axis + +Note that the type also affects the selection of the points. E.g., +measx will select maximum x values. + +Operators: +- A -> B: smallest value of A and smallest B greater than A +- A <- B: like A -> B, but normal (for offset and text) is inverted +- A >> B: smallest value of A and greatest value of B +- A << B: like A -> B, but normal (for offset and text) is inverted + +Operands are qualified vector names. Vectors in the root frame are +referenced by their name. Vectors in other frames are prefixed with +the name of the frame followed by a dot. + +Example: + +measx pad.sw -> pad.se 1mm + +The optional label is printed directly before the distance. Example: + +a: vec @(0mm, 0mm) +b: vec @(1mm, 0mm) +measx "width = " a >> b 0mm + +would print "width = 1mm" + + +Additional qualifiers +- - - - - - - - - - - + +When using frames as reusable building blocks, similar to functions or +macros in many programming languages, one may need finer control over +the points that are selected for measurements. + +For example, let us consider a frame "square" that draws a square +centered at the frame's origin and with a side length given by the +variable "size". This variable be set in the frame referencing +"square". + + frame square { + a: vec @(-size/2, -size/2) + b: vec @(size/2, size/2) + rect a b + } + + frame small { + set size = 2mm + frame square @ + } + + frame big { + set size = 5mm + frame square @ + } + + frame small @ + vec @(5mm, 0mm) + frame big . + +If we want to measure the size of each square, we could use + +measx square.a -> square.b + +Unfortunately, this only measures the small square. To reach the +big frame, we need to tell fped to use only those points in "square" +that have been placed when "square" was invoked from the big frame. + +This is accomplished by prefixing the points in question with the +name(s) of the frames that need to be visited. The frame names are +separated by slashes (/). + +measx big/square.a -> square.b + +For clarity, it's better to qualify both points, e.g., + +measx big/square.a -> big/square.b + +If multiple frame names are given, they must be in the order in +which they are invoked. + + +Experimental: debugging directives +---------------------------------- + +For debugging and regression tests, fped supports the following commands, +most of which mimick the effect of GUI operations: + +%del <qualified-identifier> +%move <identifier> [<number>] <identifier> +%frame <identifier> <qualified-base> +%print <expression> +%iprint <expression> +%meas <identifier> +%dump +%exit +%tsort { -<id> | +<id> | <id-before> <id-after> [<number>] ... } + +%del removes the specified item. This can be a vector, an object, or +a frame. If the vector or object is in a different frame than the +current, its name is qualified with the frame name, e.g., "foo.obj". + +For this purpose, also objects can be labeled. Object labels behave like +vector labels and share the same name space. They are not normally +accessible in the GUI. (You can see them in the code view.) + +%move take as its first argument the name of the vector or object to +manipulate. %move sets an anchor point to the vector named as its last +argument. The anchor point is identified by index as follows: + +anchor index vec/frame line/rect/pad arc measurement +-------------- --------- ------------- ------------ ----------- +0 (or omitted) base first point center low point +1 - second point end of arc high point +2 - - start of arc - + +%frame creates a frame reference. Unlike "frame", the destination frame +can be different from the current frame. E.g., "%frame foo bar.a" would +add a reference to frame "foo" in frame "bar", rooted at vector "a". The +parent frame's origin can be references as "@". + +%dump writes the footprint definition in the fped language to standard +output. %exit immediately exits fped, without invoking the GUI. + +%print and %iprint evaluate the expression and print the result to +standard output. The difference between them is that %print runs only +once and without explicit instantiation, while %iprint is treated as +a regular object and is executed as many times as instantiation +demands. + +For example, after loop x = 1, 3 we would obtain just 1 with %print +while %iprint would display, 1, 2, and 3. + +%meas performs an instantiation and prints the value of the labeled +measurement. + +%tsort is used to test-drive the topological sort algorithm. The items +in the curly braces are declarations of nodes with (-<id>) or without +(+<id>) decay or edges in the partial order. The optional number is +the edge's priority. See tsort.c for details, test/tsort for examples. @@ -0,0 +1,80 @@ +Major missing features: +- add postscript output (partially done) +- add option to include/omit helper vecs and frames (done for display, still + need postscript). Better idea: in PS, print the component 10x, 1x, and then + each individual frame 10x. + +Minor missing features: +- reorder variables in a frame (can use text editor) +- move items/vectors up and down the hierarchy + +Error detection: +- eliminate duplicate instances + +Style and usability: +- make column of entry field greedily consume all unallocated space +- make menu bar consume all unallocated space instead of sharing it evenly with + upper toolbar +- status area looks awful +- add button with GTK_STOCK_UNDELETE for "undelete" to menu bar +- maximizing pad name size creates uneven text sizes. Particularly "1" gets + excessively large. +- pango_layout_get_size doesn't seem to consider rotation, so we currently + don't properly size rotated text. +- when changing the part, we should automatically switch to a configuration + that generates any of its (non-global) elements +- add zoom controls to top toolbar + +Bugs: +- default silk width has no business being hard-coded in obj.c +- undelete only works if not much has changed since the deletion +- focus should return to canvas if nobody else wants it +- whenever we call parse_* for input parsing, we may leak lots of expressions +- can't edit measurement labels through the GUI + +Code cleanup: +- merge edit_unique with edit_name +- merge find_var_in_frame with similar mechanisms in expr.c and fpd.y +- add regression tests +- the drag logic is too complex. Better: let tool/instance just generate the + list of points at each stage, then handle the highlighting and hovering + inside a dragging module. +- code organization is very poor. E.g., functions belonging to the different + items (pads, silk objects, vectors, etc.) should be grouped by item, not by + type of function, similar to how some things are now with gui_meas.c +- eval_string_var should be merged into eval_var and the result should be a + struct num (?) that can contain both types. This also means changing all the + ops to handle/reject strings. + +Open decisions: +- Q: should loop be (start, last) or (start, iterations) ? or start ... last ? +- change vector circle color ? (also, highlight on hover ?) +- Q: allow reassignment of vector names ? + A1: no: would cause confusion in GUI (vectors could become orphaned) + A2: yes. but we don't change the linkage. +- Q: how do we handle stacks of objects ? + A1: we don't but we make it easy to avoid them, by giving a good zoom, + flexible selection, and by disallowing stacks of identical objects in the + first place. + A2: the current mechanism of selecting the next eligible object when clicking + on the same position repeatedly lets one cycle through stacks. +- Q: add frame arguments ? (e.g., .frame pad(pin_num_offset) ...) + A: we can already approximate this by introducing an intermediate table that + sets up the arguments (provided that we don't consider vectors as well) +- Q: should we make it a requirement to generate objects only once ? + A: yes. + +Future directions: +- future: consider using cairo instead of gdk +- live update of value when entering strings and expressions ? +- advanced: non-standard solder mask +- advanced: solder paste exceptions (subtractive, additive) +- advanced: silk line width +- future: consider editing non-canvas items (e.g., variable names/values) in + place +- add a means to cut&paste and copy&paste objects, and perhaps also variables + (to move objects, we need to make sure all references are included. besides + that, copy&paste should be a slight variation of delete/undelete) +- instead of blue screening, we could perhaps just skip the offending items and + replace them with a "here's a problem" marker that would still point to the + underlying object and allow repairs to be made diff --git a/bitset.c b/bitset.c new file mode 100644 index 0000000..6d3eb2f --- /dev/null +++ b/bitset.c @@ -0,0 +1,133 @@ +/* + * bitset.c - Arbitrary-length bit sets + * + * Written 2010 by Werner Almesberger + * Copyright 2010 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <sys/types.h> + +#include "util.h" +#include "bitset.h" + + +struct bitset { + int v_n; + unsigned *v; +}; + +#define BITS (sizeof(unsigned)*8) + + +struct bitset *bitset_new(int n) +{ + struct bitset *new; + + new = alloc_type(struct bitset); + new->v_n = (n+BITS-1) & ~(BITS-1); + new->v = zalloc_size(sizeof(unsigned)*new->v_n); + return new; +} + + +struct bitset *bitset_clone(const struct bitset *old) +{ + struct bitset *new; + size_t bytes; + + new = alloc_type(struct bitset); + bytes = sizeof(unsigned)*old->v_n; + new->v_n = old->v_n; + new->v = alloc_size(bytes); + memcpy(new->v, old->v, bytes); + return new; +} + + +void bitset_free(struct bitset *set) +{ + free(set->v); + free(set); +} + + +void bitset_set(struct bitset *set, int n) +{ + assert(n < set->v_n*BITS); + set->v[n/BITS] |= 1U << (n % BITS); +} + + +void bitset_clear(struct bitset *set, int n) +{ + assert(n < set->v_n*BITS); + set->v[n/BITS] &= ~(1U << (n % BITS)); +} + + +int bitset_pick(const struct bitset *set, int n) +{ + assert(n < set->v_n*BITS); + return !!(set->v[n/BITS] & (1U << (n % BITS))); +} + + +int bitset_is_empty(const struct bitset *set) +{ + int i; + + for (i = 0; i != set->v_n; i++) + if (set->v[i]) + return 0; + return 1; +} + + +void bitset_zero(struct bitset *a) +{ + int i; + + for (i = 0; i != a->v_n; i++) + a->v[i] = 0; +} + + +void bitset_and(struct bitset *a, const struct bitset *b) +{ + int i; + + assert(a->v_n == b->v_n); + for (i = 0; i != a->v_n; i++) + a->v[i] &= b->v[i]; +} + + +void bitset_or(struct bitset *a, const struct bitset *b) +{ + int i; + + assert(a->v_n == b->v_n); + for (i = 0; i != a->v_n; i++) + a->v[i] |= b->v[i]; +} + + +int bitset_ge(const struct bitset *a, const struct bitset *b) +{ + int i; + + assert(a->v_n == b->v_n); + for (i = 0; i != a->v_n; i++) + if (~a->v[i] & b->v[i]) + return 0; + return 1; +} diff --git a/bitset.h b/bitset.h new file mode 100644 index 0000000..65a123f --- /dev/null +++ b/bitset.h @@ -0,0 +1,34 @@ +/* + * bitset.h - Arbitrary-length bit sets + * + * Written 2010 by Werner Almesberger + * Copyright 2010 by Werner Almesberger + * + * 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. + */ + +#ifndef BITSET_H +#define BITSET_H + +struct bitset; + +struct bitset *bitset_new(int n); +struct bitset *bitset_clone(const struct bitset *old); +void bitset_free(struct bitset *set); + +void bitset_set(struct bitset *set, int n); +void bitset_clear(struct bitset *set, int n); +int bitset_pick(const struct bitset *set, int n); + +int bitset_is_empty(const struct bitset *set); +void bitset_zero(struct bitset *a); + +void bitset_and(struct bitset *a, const struct bitset *b); +void bitset_or(struct bitset *a, const struct bitset *b); + +int bitset_ge(const struct bitset *a, const struct bitset *b); + +#endif /* !BITSET_H */ @@ -0,0 +1,250 @@ +/* + * coord.c - Coordinate representation and basic operations + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#include <math.h> + +#include "util.h" +#include "coord.h" + + +/* ----- unit conversion --------------------------------------------------- */ + + +double mm_to_mil(double mm, int exponent) +{ + return mm*pow(MIL_IN_MM, -exponent); +} + + +double mil_to_mm(double mil, int exponent) +{ + return mil*pow(MIL_IN_MM, exponent); +} + + +/* ----- convert internal units to best external unit ---------------------- */ + + +double units_to_best(unit_type u, int *mm) +{ + /* + * For finding the best choice, we work with deci-micrometers and + * micro-inches. The conversion to "dum" is actually a no-op, but that + * may change if we ever pick a different internal unit than 0.1 um. + */ + + long dum = round(units_to_mm(u)*10000.0); + long uin = round(units_to_mil(u)*1000.0); + + /* remove trailing zeroes */ + + while (dum && !(dum % 10)) + dum /= 10; + while (uin && !(uin % 10)) + uin /= 10; + + /* ceil(log10(dum)) <= ceil(log10(uin)) ? */ + + while (dum && uin) { + dum /= 10; + uin /= 10; + } + if (!dum) { + *mm = 1; + return units_to_mm(u); + } else { + *mm = 0; + return units_to_mil(u); + } +} + + +/* ----- vector operations ------------------------------------------------- */ + + +struct coord normalize(struct coord v, unit_type len) +{ + double f; + + f = len/hypot(v.x, v.y); + v.x *= f; + v.y *= f; + return v; +} + + +struct coord rotate(struct coord v, double angle) +{ + double rad = M_PI*angle/180.0; + struct coord res; + + res.x = v.x*cos(rad)-v.y*sin(rad); + res.y = v.y*cos(rad)+v.x*sin(rad); + return res; +} + + +struct coord add_vec(struct coord a, struct coord b) +{ + a.x += b.x; + a.y += b.y; + return a; +} + + +struct coord sub_vec(struct coord a, struct coord b) +{ + a.x -= b.x; + a.y -= b.y; + return a; +} + + +struct coord neg_vec(struct coord v) +{ + v.x = -v.x; + v.y = -v.y; + return v; +} + + +/* ----- point on circle --------------------------------------------------- */ + + +struct coord rotate_r(struct coord c, unit_type r, double angle) +{ + struct coord p; + + angle = angle/180.0*M_PI; + p.x = c.x+r*cos(angle); + p.y = c.y+r*sin(angle); + return p; +} + + +double theta_vec(struct coord v) +{ + double a; + + a = atan2(v.y, v.x)/M_PI*180.0; + if (a < 0) + a += 360.0; + return a; +} + + +double theta(struct coord c, struct coord p) +{ + p.x -= c.x; + p.y -= c.y; + return theta_vec(p); +} + + +/* ----- sorting coordinates ----------------------------------------------- */ + + +void sort_coord(struct coord *min, struct coord *max) +{ + if (min->x > max->x) + SWAP(min->x, max->x); + if (min->y > max->y) + SWAP(min->y, max->y); + +} + + +/* ----- distance calculations --------------------------------------------- */ + + +unit_type dist_point(struct coord a, struct coord b) +{ + return hypot(a.x-b.x, a.y-b.y); +} + + +static unit_type dist_line_xy(unit_type px, unit_type py, + unit_type ax, unit_type ay, unit_type bx, unit_type by) +{ + unit_type d_min, d; + double a, f; + + d_min = hypot(ax-px, ay-py); + d = hypot(bx-px, by-py); + if (d < d_min) + d_min = d; + if (ax != bx || ay != by) { + /* + * We make a the line vector from point B and b the vector from + * B to point P. Then we calculate the projection of b on a. + */ + ax -= bx; + ay -= by; + bx = px-bx; + by = py-by; + a = hypot(ax, ay); + f = ((double) ax*bx+(double) ay*by)/a/a; + if (f >= 0 && f <= 1) { + bx -= f*ax; + by -= f*ay; + d = hypot(bx, by); + if (d < d_min) + d_min = d; + } + } + return d_min; +} + + +unit_type dist_line(struct coord p, struct coord a, struct coord b) +{ + return dist_line_xy(p.x, p.y, a.x, a.y, b.x, b.y); +} + + +unit_type dist_rect(struct coord p, struct coord a, struct coord b) +{ + unit_type d_min, d; + + d_min = dist_line_xy(p.x, p.y, a.x, a.y, b.x, a.y); + d = dist_line_xy(p.x, p.y, a.x, a.y, a.x, b.y); + if (d < d_min) + d_min = d; + d = dist_line_xy(p.x, p.y, a.x, b.y, b.x, b.y); + if (d < d_min) + d_min = d; + d = dist_line_xy(p.x, p.y, b.x, a.y, b.x, b.y); + if (d < d_min) + d_min = d; + return d_min; +} + + +int inside_rect(struct coord p, struct coord a, struct coord b) +{ + sort_coord(&a, &b); + if (p.x < a.x || p.x > b.x) + return 0; + if (p.y < a.y || p.y > b.y) + return 0; + return 1; +} + + +unit_type dist_circle(struct coord p, struct coord c, unit_type r) +{ + unit_type d; + + d = hypot(p.x-c.x, p.y-c.y); + return fabs(d-r); +} @@ -0,0 +1,98 @@ +/* + * coord.h - Coordinate representation and basic operations + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#ifndef COORD_H +#define COORD_H + +#include <stdint.h> + + +#define MICRON_UNITS 10 +#define MIL_UNITS (25.4*MICRON_UNITS) +#define MM_UNITS (1000.0*MICRON_UNITS) +#define KICAD_UNIT (MIL_UNITS/10.0) + +#define MIL_IN_MM 0.0254 + + +typedef int32_t unit_type; + + +#define UNIT_ERROR ((unit_type) 1 << (sizeof(unit_type)*8-1)) + + +struct coord { + unit_type x, y; +}; + + +static inline unit_type mil_to_units(double mil) +{ + return mil*MIL_UNITS; +} + + +static inline unit_type mm_to_units(double mm) +{ + return mm*MM_UNITS; +} + + +static inline double units_to_mm(unit_type u) +{ + return (double) u/MM_UNITS; +} + + +static inline double units_to_mil(unit_type u) +{ + return (double) u/MIL_UNITS; +} + + +static inline int units_to_kicad(unit_type u) +{ + return (double) u/KICAD_UNIT; +} + + +static inline int coord_eq(struct coord a, struct coord b) +{ + return a.x == b.x && a.y == b.y; +} + + +double mm_to_mil(double mm, int exponent); +double mil_to_mm(double mil, int exponent); + +double units_to_best(unit_type u, int *mm); + +struct coord normalize(struct coord v, unit_type len); +struct coord rotate(struct coord v, double angle); +struct coord add_vec(struct coord a, struct coord b); +struct coord sub_vec(struct coord a, struct coord b); +struct coord neg_vec(struct coord v); + +struct coord rotate_r(struct coord c, unit_type r, double angle); +double theta_vec(struct coord v); +double theta(struct coord c, struct coord p); + +void sort_coord(struct coord *min, struct coord *max); + +unit_type dist_point(struct coord a, struct coord b); +unit_type dist_line(struct coord p, struct coord a, struct coord b); +unit_type dist_rect(struct coord p, struct coord a, struct coord b); +int inside_rect(struct coord p, struct coord a, struct coord b); +unit_type dist_circle(struct coord p, struct coord c, unit_type r); + +#endif /* !COORD_H */ @@ -0,0 +1,217 @@ +/* + * cpp.c - CPP subprocess + * + * Written 2002-2004, 2006, 2008 by Werner Almesberger + * Copyright 2002, 2003 California Institute of Technology + * Copyright 2004, 2006 Werner Almesberger + * Copyright 2008 by OpenMoko, Inc. + * + * 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. + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "cpp.h" + + +const char *cpp_command = CPP; + +static pid_t cpp_pid; +static int cpp_argc = 0; +static const char **cpp_argv = NULL; +static int real_stdin = -1; + + +void add_cpp_arg(const char *arg) +{ + if (!cpp_argc) + cpp_argc = 1; + cpp_argv = realloc(cpp_argv,sizeof(const char *)*(cpp_argc+1)); + if (!cpp_argv) { + perror("realloc"); + exit(1); + } + if (cpp_argc == 1) + cpp_argv[0] = cpp_command; + if (arg) { + arg = strdup(arg); + if (!arg) { + perror("strdup"); + exit(1); + } + } + cpp_argv[cpp_argc++] = arg; +} + + +void add_cpp_Wp(const char *arg) +{ + char *tmp = strdup(arg); + char *curr,*end; + + if (!tmp) { + perror("strdup"); + exit(1); + } + curr = tmp; + do { + end = strchr(curr,','); + if (end) + *end++ = 0; + add_cpp_arg(curr); + curr = end; + } + while (end); + free(tmp); +} + + +static void kill_cpp(void) +{ + if (cpp_pid) + (void) kill(cpp_pid,SIGTERM); +} + + +static void run_cpp(const char *name,int fd,int close_fd) +{ + char **arg; + int fds[2]; + + if (pipe(fds) < 0) { + perror("pipe"); + exit(1); + } + if (name) + add_cpp_arg(name); + add_cpp_arg(NULL); + cpp_pid = fork(); + if (cpp_pid < 0) { + perror("fork"); + exit(1); + } + if (!cpp_pid) { + if (close(fds[0]) < 0) { + perror("close"); + exit(1); + } + if (close_fd != -1 && close(close_fd) < 0) { + perror("close"); + exit(1); + } + if (fd != -1 && dup2(fd,0) < 0) { + perror("dup2"); + exit(1); + } + if (dup2(fds[1],1) < 0) { + perror("dup2"); + exit(1); + } + if (execvp(cpp_command,(char **) cpp_argv) < 0) { + /* prototype is weird */ + perror(cpp_command); + exit(1); + } + /* not reached */ + } + if (close(fds[1]) < 0) { + perror("close"); + exit(1); + } + real_stdin = dup(0); + if (real_stdin < 0) { + perror("dup"); + exit(1); + } + if (fd != -1 && close(fd) < 0) { + perror("close"); + exit(1); + } + if (dup2(fds[0],0) < 0) { + perror("dup2"); + exit(1); + } + for (arg = (char **) cpp_argv+1; *arg; arg++) + free(*arg); + free(cpp_argv); + cpp_argv = NULL; + cpp_argc = 0; +} + + +void run_cpp_on_file(const char *name) +{ + run_cpp(name,name ? -1 : 0,-1); + atexit(kill_cpp); +} + + +void run_cpp_on_string(const char *str) +{ + int fds[2]; + pid_t pid; + int left,wrote; + + if (pipe(fds) < 0) { + perror("pipe"); + exit(1); + } + run_cpp(NULL,fds[0],fds[1]); + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + if (!pid) { + for (left = strlen(str); left; left -= wrote) { + wrote = write(fds[1],str,left); + if (wrote < 0) + break; /* die silently */ + str += wrote; + } + exit(0); + } + if (close(fds[1]) < 0) { + perror("close"); + exit(1); + } + atexit(kill_cpp); +} + + +void reap_cpp(void) +{ + int status; + + cpp_pid = 0; + if (waitpid(cpp_pid,&status,0) < 0) { + perror("waitpid"); + exit(1); + } + if (!status) { + if (dup2(real_stdin,0) < 0) { + perror("dup2"); + exit(1); + } + return; + } + if (WIFEXITED(status)) + exit(WEXITSTATUS(status)); + if (WIFSIGNALED(status)) + fprintf(stderr,"cpp terminated with signal %d\n",WTERMSIG(status)); + else + fprintf(stderr,"cpp terminated with incomprehensible status %d\n", + status); + exit(1); +} @@ -0,0 +1,26 @@ +/* + * cpp.h - CPP subprocess + * + * Written 2002, 2003, 2008 by Werner Almesberger + * Copyright 2002, 2003 Caltech Netlab FAST project + * Copyright 2008 by OpenMoko, Inc. + * + * 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. + */ + +#ifndef CPP_H +#define CPP_H + + +extern const char *cpp_command; + +void add_cpp_arg(const char *arg); +void add_cpp_Wp(const char *arg); +void run_cpp_on_file(const char *name); /* NULL for stdin */ +void run_cpp_on_string(const char *str); +void reap_cpp(void); + +#endif /* CPP_H */ diff --git a/delete.c b/delete.c new file mode 100644 index 0000000..7128f9e --- /dev/null +++ b/delete.c @@ -0,0 +1,705 @@ +/* + * delete.c - Object deletion + * + * Written 2009, 2010, 2012 by Werner Almesberger + * Copyright 2009, 2010, 2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <assert.h> + +#include "util.h" +#include "error.h" +#include "expr.h" +#include "obj.h" +#include "delete.h" + + +static struct deletion { + enum del_type { + dt_vec, + dt_obj, + dt_frame, + dt_table, + dt_row, + dt_column, + dt_loop, + } type; + union { + struct { + struct frame *ref; + struct frame *prev; + } frame; + struct { + struct vec *ref; + struct vec *prev; + } vec; + struct { + struct obj *ref; + struct obj *prev; + } obj; + struct { + struct table *ref; + struct table *prev; + } table; + struct { + struct row *ref; + struct row *prev; + } row; + struct column { + struct var *var; + struct value *values; + struct table *table; + int n; + } col; + struct { + struct loop *ref; + struct loop *prev; + } loop; + } u; + int group; + struct deletion *next; +} *deletions = NULL; + +static int groups = 0; + + +static void do_delete_vec(struct vec *vec); +static void do_delete_obj(struct obj *obj); + + +/* ----- helper functions -------------------------------------------------- */ + + +static struct deletion *new_deletion(enum del_type type) +{ + struct deletion *del; + + del = alloc_type(struct deletion); + del->type = type; + del->group = groups; + del->next = deletions; + deletions = del; + return del; +} + + +static void reset_active_ref(struct frame *ref) +{ + const struct frame *frame; + struct obj *obj = NULL; + + for (frame = frames; frame; frame = frame->next) + for (obj = frame->objs; obj; obj = obj->next) + if (obj->type == ot_frame && obj->u.frame.ref == ref) + break; + ref->active_ref = obj; +} + + +/* ----- vectors ----------------------------------------------------------- */ + + +static void destroy_vec(struct vec *vec) +{ + free_expr(vec->x); + free_expr(vec->y); + free(vec); +} + + +static void delete_vecs_by_ref(struct vec *vecs, const struct vec *ref) +{ + while (vecs) { + if (vecs->base == ref) + do_delete_vec(vecs); + vecs = vecs->next; + } +} + + +static int obj_has_ref(const struct obj *obj, const struct vec *ref) +{ + if (obj->base == ref) + return 1; + switch (obj->type) { + case ot_frame: + return 0; + case ot_line: + return obj->u.line.other == ref; + case ot_rect: + return obj->u.rect.other == ref; + case ot_pad: + return obj->u.pad.other == ref; + case ot_hole: + return obj->u.hole.other == ref; + case ot_arc: + return obj->u.arc.start == ref || obj->u.arc.end == ref; + case ot_meas: + return obj->u.meas.high == ref; + case ot_iprint: + return 0; + default: + abort(); + } +} + + +static void delete_objs_by_ref(struct obj **objs, const struct vec *ref) +{ + struct obj *obj; + + for (obj = *objs; obj; obj = obj->next) + if (obj_has_ref(obj, ref)) + do_delete_obj(obj); +} + + +static void do_delete_vec(struct vec *vec) +{ + struct vec *walk, *prev; + struct deletion *del; + + prev = NULL; + for (walk = vec->frame->vecs; walk != vec; walk = walk->next) + prev = walk; + if (prev) + prev->next = vec->next; + else + vec->frame->vecs = vec->next; + del = new_deletion(dt_vec); + del->u.vec.ref = vec; + del->u.vec.prev = prev; + + delete_vecs_by_ref(vec->frame->vecs, vec); + delete_objs_by_ref(&vec->frame->objs, vec); + /* + * Catch measurements. During final cleanup, we may operate on an empty + * list of frames, hence the test. + */ + if (frames) + delete_objs_by_ref(&frames->objs, vec); +} + + +void delete_vec(struct vec *vec) +{ + groups++; + do_delete_vec(vec); +} + + +static void undelete_vec(struct vec *vec, struct vec *prev) +{ + if (prev) { + assert(vec->next == prev->next); + prev->next = vec; + } else { + assert(vec->next == vec->frame->vecs); + vec->frame->vecs = vec; + } +} + + +/* ----- objects ----------------------------------------------------------- */ + + +static void destroy_obj(struct obj *obj) +{ + switch (obj->type) { + case ot_frame: + if (obj->u.frame.ref->active_ref == obj) + reset_active_ref(obj->u.frame.ref); + break; + case ot_pad: + free(obj->u.pad.name); + break; + case ot_hole: + break; + case ot_line: + if (obj->u.line.width) + free_expr(obj->u.line.width); + break; + case ot_rect: + if (obj->u.rect.width) + free_expr(obj->u.rect.width); + break; + case ot_arc: + if (obj->u.arc.width) + free_expr(obj->u.arc.width); + break; + case ot_meas: + if (obj->u.meas.label) + free(obj->u.meas.label); + if (obj->u.meas.offset) + free_expr(obj->u.meas.offset); + break; + case ot_iprint: + free_expr(obj->u.iprint.expr); + break; + default: + abort(); + } + free(obj); +} + + +static void do_delete_obj(struct obj *obj) +{ + struct obj *walk, *prev; + struct deletion *del; + + prev = NULL; + for (walk = obj->frame->objs; walk != obj; walk = walk->next) + prev = walk; + if (prev) + prev->next = obj->next; + else + obj->frame->objs = obj->next; + del = new_deletion(dt_obj); + del->u.obj.ref = obj; + del->u.obj.prev = prev; + if (obj->type == ot_frame && obj->u.frame.ref->active_ref == obj) + reset_active_ref(obj->u.frame.ref); +} + + +void delete_obj(struct obj *obj) +{ + groups++; + do_delete_obj(obj); +} + + +static void undelete_obj(struct obj *obj, struct obj *prev) +{ + if (prev) { + assert(obj->next == prev->next); + prev->next = obj; + } else { + assert(obj->next == obj->frame->objs); + obj->frame->objs = obj; + } +} + + + +/* ----- rows -------------------------------------------------------------- */ + + +static void destroy_row(struct row *row) +{ + struct value *next_value; + + while (row->values) { + next_value = row->values->next; + free_expr(row->values->expr); + free(row->values); + row->values = next_value; + } + free(row); +} + + +void delete_row(struct row *row) +{ + struct deletion *del; + struct row *walk, *prev; + + groups++; + prev = NULL; + for (walk = row->table->rows; walk != row; walk = walk->next) + prev = walk; + if (prev) + prev->next = row->next; + else + row->table->rows = row->next; + del = new_deletion(dt_row); + del->u.row.ref = row; + del->u.row.prev = prev; +} + + +static void undelete_row(struct row *row, struct row *prev) +{ + if (prev) { + assert(row->next == prev->next); + prev->next = row; + } else { + assert(row->next == row->table->rows); + row->table->rows = row; + } +} + + +/* ----- columns ----------------------------------------------------------- */ + + +void delete_column(struct table *table, int n) +{ + struct deletion *del; + struct column *col; + struct var **var; + struct row *row; + struct value **next, **value; + int i; + + groups++; + + del = new_deletion(dt_column); + col = &del->u.col; + col->table = table; + col->n = n; + + var = &table->vars; + for (i = 0; i != n; i++) + var = &(*var)->next; + col->var = *var; + *var = (*var)->next; + + next = &col->values; + for (row = table->rows; row; row = row->next) { + value = &row->values; + for (i = 0; i != n; i++) + value = &(*value)->next; + *next = *value; + *value = (*value)->next; + next = &(*next)->next; + } + *next = NULL; +} + + +static void undelete_column(const struct column *col) +{ + struct var **var; + struct row *row; + struct value **anchor, *value, *next; + int i; + + var = &col->table->vars; + for (i = 0; i != col->n; i++) + var = &(*var)->next; + col->var->next = *var; + *var = col->var; + + value = col->values; + for (row = col->table->rows; row; row = row->next) { + anchor = &row->values; + for (i = 0; i != col->n; i++) + anchor = &(*anchor)->next; + next = value->next; + value->next = *anchor; + *anchor = value; + value = next; + } +} + + +/* ----- tables ------------------------------------------------------------ */ + + +static void destroy_table(struct table *table) +{ + struct var *next_var; + + while (table->vars) { + next_var = table->vars->next; + free(table->vars); + table->vars = next_var; + } + while (table->rows) { + delete_row(table->rows); + destroy(); + } + free(table); +} + + +void delete_table(struct table *table) +{ + struct frame *frame = table->vars->frame; + struct deletion *del; + struct table *walk, *prev; + + groups++; + prev = NULL; + for (walk = frame->tables; walk != table; walk = walk->next) + prev = walk; + if (prev) + prev->next = table->next; + else + frame->tables = table->next; + del = new_deletion(dt_table); + del->u.table.ref = table; + del->u.table.prev = prev; +} + + +static void undelete_table(struct table *table, struct table *prev) +{ + struct frame *frame = table->vars->frame; + + if (prev) { + assert(table->next == prev->next); + prev->next = table; + } else { + assert(table->next == frame->tables); + frame->tables = table; + } +} + + +/* ----- loops ------------------------------------------------------------- */ + + +static void destroy_loop(struct loop *loop) +{ + free_expr(loop->from.expr); + free_expr(loop->to.expr); + free(loop); +} + + +void delete_loop(struct loop *loop) +{ + struct frame *frame = loop->var.frame; + struct deletion *del; + struct loop *walk, *prev; + + groups++; + prev = NULL; + for (walk = frame->loops; walk != loop; walk = walk->next) + prev = walk; + if (prev) + prev->next = loop->next; + else + frame->loops = loop->next; + del = new_deletion(dt_loop); + del->u.loop.ref = loop; + del->u.loop.prev = prev; +} + + +static void undelete_loop(struct loop *loop, struct loop *prev) +{ + struct frame *frame = loop->var.frame; + + if (prev) { + assert(loop->next == prev->next); + prev->next = loop; + } else { + assert(loop->next == frame->loops); + frame->loops = loop; + } +} + + +/* ----- frames ------------------------------------------------------------ */ + + +static void destroy_frame(struct frame *frame) +{ + while (frame->tables) { + delete_table(frame->tables); + destroy(); + } + while (frame->loops) { + delete_loop(frame->loops); + destroy(); + } + while (frame->vecs) { + delete_vec(frame->vecs); + destroy(); + } + while (frame->objs) { + delete_obj(frame->objs); + destroy(); + } + free(frame); +} + + +static int qual_ref(const struct frame_qual *qual, const struct frame *ref) +{ + while (qual) { + if (qual->frame == ref) + return 1; + qual = qual->next; + } + return 0; +} + + +static void delete_references(const struct frame *ref) +{ + struct frame *frame; + struct obj *obj; + + for (frame = frames; frame; frame = frame->next) + for (obj = frame->objs; obj; obj = obj->next) + switch (obj->type) { + case ot_frame: + if (obj->u.frame.ref == ref) + do_delete_obj(obj); + break; + case ot_meas: + if (obj->base->frame == ref || + obj->u.meas.high->frame == ref || + qual_ref(obj->u.meas.low_qual, ref) || + qual_ref(obj->u.meas.high_qual, ref)) + do_delete_obj(obj); + break; + default: + break; + } + for (obj = ref->objs; obj; obj = obj->next) + if (obj->type == ot_frame) + if (obj->u.frame.ref->active_ref == obj) + reset_active_ref(obj->u.frame.ref); +} + + +void delete_frame(struct frame *frame) +{ + struct deletion *del; + struct frame *walk; + groups++; + + del = new_deletion(dt_frame); + del->u.frame.ref = frame; + del->u.frame.prev = NULL; + for (walk = frames; walk != frame; walk = walk->next) + del->u.frame.prev = walk; + if (del->u.frame.prev) + del->u.frame.prev->next = frame->next; + else + frames = frame->next; /* hmm, deleting the root frame ? */ + + delete_references(frame); +} + + +static void undelete_frame(struct frame *frame, struct frame *prev) +{ + if (prev) { + assert(frame->next == prev->next); + prev->next = frame; + } else { + assert(frame->next == frames); + frames = frame; + } +} + + +/* ----- destroy/undelete interface ---------------------------------------- */ + + +static void destroy_one(void) +{ + struct deletion *del; + + del = deletions; + switch (del->type) { + case dt_vec: + destroy_vec(del->u.vec.ref); + break; + case dt_obj: + destroy_obj(del->u.obj.ref); + break; + case dt_frame: + destroy_frame(del->u.frame.ref); + break; + case dt_loop: + destroy_loop(del->u.loop.ref); + break; + case dt_table: + destroy_table(del->u.table.ref); + break; + case dt_row: + destroy_row(del->u.row.ref); + break; + case dt_column: + abort(); /* @@@ later */ + break; + default: + abort(); + } + deletions = del->next; + free(del); +} + + +void destroy(void) +{ + int group; + + assert(deletions); + group = deletions->group; + while (deletions && deletions->group == group) + destroy_one(); +} + + +static int undelete_one(void) +{ + struct deletion *del; + + if (!deletions) + return 0; + del = deletions; + switch (del->type) { + case dt_vec: + undelete_vec(del->u.vec.ref, del->u.vec.prev); + break; + case dt_obj: + undelete_obj(del->u.obj.ref, del->u.obj.prev); + break; + case dt_frame: + undelete_frame(del->u.frame.ref, del->u.frame.prev); + break; + case dt_loop: + undelete_loop(del->u.loop.ref, del->u.loop.prev); + break; + case dt_table: + undelete_table(del->u.table.ref, del->u.table.prev); + break; + case dt_row: + undelete_row(del->u.row.ref, del->u.row.prev); + break; + case dt_column: + undelete_column(&del->u.col); + break; + default: + abort(); + } + deletions = del->next; + free(del); + return 1; +} + + +int undelete(void) +{ + int group; + + if (!deletions) + return 0; + group = deletions->group; + while (deletions && deletions->group == group) + undelete_one(); + return 1; +} + + +void purge(void) +{ + while (deletions) + destroy(); +} diff --git a/delete.h b/delete.h new file mode 100644 index 0000000..0815880 --- /dev/null +++ b/delete.h @@ -0,0 +1,32 @@ +/* + * delete.h - Object deletion + * + * Written 2009 by Werner Almesberger + * Copyright 2009 by Werner Almesberger + * + * 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. + */ + + +#ifndef DELETE_H +#define DELETE_H + + +#include "obj.h" + + +void delete_vec(struct vec *vec); +void delete_obj(struct obj *obj); +void delete_row(struct row *row); +void delete_column(struct table *table, int n); +void delete_table(struct table *table); +void delete_loop(struct loop *loop); +void delete_frame(struct frame *frame); +void destroy(void); +int undelete(void); +void purge(void); + +#endif /* !DELETE_H */ @@ -0,0 +1,625 @@ +/* + * dump.c - Dump objects in the native FPD format + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <sys/types.h> + +#include "util.h" +#include "unparse.h" +#include "obj.h" +#include "gui_status.h" +#include "dump.h" + + +/* ----- order items ------------------------------------------------------- */ + + +static void add_item(struct order **curr, struct vec *vec, struct obj *obj) +{ + (*curr)->vec = vec; + (*curr)->obj = obj; + (*curr)++; +} + + +static int n_vec_refs(const struct vec *vec) +{ + const struct vec *walk; + int n; + + n = 0; + for (walk = vec->frame->vecs; walk; walk = walk->next) + if (walk->base == vec) + n++; + return n; +} + + +/* + * If "prev" is non-NULL, we're looking for objects that need to be put after + * the current vector (in "prev"). Only those objects need to be put there + * that have at least one base that isn't the frame's origin. + * + * We could also make an exception for manually named vectors, but we get + * better clustering without. + */ + +static int need(const struct vec *base, const struct vec *prev) +{ + if (!base) + return 0; +#if 0 + if (base->name && *base->name != '_') + return 0; +#endif + if (prev) + return base == prev; + return 1; +} + + +/* + * If we need a vector that's defined later, we have to defer dumping the + * object. + */ + +static int later(const struct vec *base, const struct vec *prev) +{ + return base && !base->dumped; +#if 0 + while (1) { + prev = prev->next; + if (!prev) + break; + if (base == prev) + return 1; + } + return 0; +#endif +} + + +static int may_put_obj_now(const struct obj *obj, const struct vec *prev) +{ + int n, l; + + n = need(obj->base, prev); + l = later(obj->base, prev); + + switch (obj->type) { + case ot_frame: + break; + case ot_line: + n |= need(obj->u.line.other, prev); + l |= later(obj->u.line.other, prev); + break; + case ot_rect: + n |= need(obj->u.rect.other, prev); + l |= later(obj->u.rect.other, prev); + break; + case ot_pad: + n |= need(obj->u.pad.other, prev); + l |= later(obj->u.pad.other, prev); + break; + case ot_hole: + n |= need(obj->u.hole.other, prev); + l |= later(obj->u.hole.other, prev); + break; + case ot_arc: + n |= need(obj->u.arc.start, prev); + n |= need(obj->u.arc.end, prev); + l |= later(obj->u.arc.start, prev); + l |= later(obj->u.arc.end, prev); + break; + case ot_meas: + return 0; + default: + abort(); + } + + return n && !l; +} + + +static void put_obj(struct order **curr, struct obj *obj, + struct vec *prev) +{ + if (obj->dumped) + return; + obj->dumped = 1; + add_item(curr, prev, obj); +} + +/* + * Tricky logic ahead: when dumping a vector, we search for a vector that + * depends on that vector for ".". If we find one, we dump it immediately after + * this vector. + */ + +static void recurse_vec(struct order **curr, struct vec *vec) +{ + struct vec *next; + struct obj *obj; + + vec->dumped = 1; + add_item(curr, vec, NULL); + for (obj = vec->frame->objs; obj; obj = obj->next) + if (may_put_obj_now(obj, vec)) + put_obj(curr, obj, vec); + if (n_vec_refs(vec) == 1) { + for (next = vec->next; next->base != vec; next = next->next); + recurse_vec(curr, next); + } +} + + +static void order_vecs(struct order **curr, struct vec *vecs) +{ + struct vec *vec; + + for (vec = vecs; vec; vec = vec->next) + if (!vec->base || n_vec_refs(vec->base) != 1) + recurse_vec(curr, vec); +} + + +struct order *order_frame(const struct frame *frame) +{ + struct order *order, *curr; + struct vec *vec; + struct obj *obj; + int n = 0; + + for (vec = frame->vecs; vec; vec = vec->next) + n++; + for (obj = frame->objs; obj; obj = obj->next) + if (obj->type != ot_meas) + n++; + + for (vec = frame->vecs; vec; vec = vec->next) + vec->dumped = 0; + for (obj = frame->objs; obj; obj = obj->next) + obj->dumped = 0; + + order = alloc_size(sizeof(*order)*(n+1)); + curr = order; + + order_vecs(&curr, frame->vecs); + + /* frames based on @ (anything else ?) */ + for (obj = frame->objs; obj; obj = obj->next) + if (obj->type != ot_meas) + put_obj(&curr, obj, NULL); + + assert(curr == order+n); + add_item(&curr, NULL, NULL); + + return order; +} + + +/* ----- variables --------------------------------------------------------- */ + + +static void dump_var(FILE *file, const struct table *table, + const char *indent) +{ + char *s; + + s = unparse(table->rows->values->expr); + fprintf(file, "%sset %s%s = %s\n\n", indent, + table->vars->key ? "?" : "", table->vars->name, s); + free(s); +} + + +static void dump_table(FILE *file, const struct table *table, + const char *indent) +{ + const struct var *var; + const struct row *row; + const struct value *value; + char *s; + + if (table->vars && !table->vars->next && + table->rows && !table->rows->next) { + dump_var(file, table, indent); + return; + } + fprintf(file, "%stable\n%s {", indent, indent); + for (var = table->vars; var; var = var->next) + fprintf(file, "%s %s%s", var == table->vars ? "" : ",", + var->key ? "?" : "", var->name); + fprintf(file, " }\n"); + for (row = table->rows; row; row = row->next) { + fprintf(file, "%s {", indent); + for (value = row->values; value; value = value->next) { + s = unparse(value->expr); + fprintf(file, "%s %s", + value == row->values? "" : ",", s); + free(s); + } + fprintf(file, " }\n"); + } + fprintf(file, "\n"); +} + + +static void dump_loop(FILE *file, const struct loop *loop, const char *indent) +{ + char *from, *to; + + from = unparse(loop->from.expr); + to = unparse(loop->to.expr); + fprintf(file, "%sloop %s = %s, %s\n\n", + indent, loop->var.name, from, to); + free(from); + free(to); +} + + +/* ----- vectors and objects ----------------------------------------------- */ + + +static void generate_name(struct vec *base) +{ + char tmp[10]; /* plenty */ + const char *s; + struct vec *walk; + int n = 0; + + while (1) { + sprintf(tmp, "__%d", n); + s = unique(tmp); + for (walk = base->frame->vecs; walk; walk = walk->next) + if (walk->name == s) + break; + if (!walk) + break; + n++; + } + base->name = s; +} + + +static const char *base_name(struct vec *base, const struct vec *next) +{ + const char *name = (const char *) base; + + if (!base) + return "@"; + if (*name) + return name; + if (next && base->next == next) + return "."; + if (!base->name) + generate_name(base); + return base->name; +} + + +static const char *obj_base_name(struct vec *base, const struct vec *prev) +{ + if (base && base == prev) + return "."; + return base_name(base, NULL); +} + + +char *print_obj(const struct obj *obj, const struct vec *prev) +{ + const char *base, *s1, *s3; + char *s, *s2; + + base = obj_base_name(obj->base, prev); + switch (obj->type) { + case ot_frame: + s = stralloc_printf("frame %s %s", + obj->u.frame.ref->name, base); + break; + case ot_line: + s1 = obj_base_name(obj->u.line.other, prev); + s2 = unparse(obj->u.line.width); + s = stralloc_printf("line %s %s %s", base, s1, s2); + free(s2); + break; + case ot_rect: + s1 = obj_base_name(obj->u.rect.other, prev); + s2 = unparse(obj->u.rect.width); + s = stralloc_printf("rect %s %s %s", base, s1, s2); + free(s2); + break; + case ot_pad: + s1 = obj_base_name(obj->u.pad.other, prev); + switch (obj->u.pad.type) { + case pt_normal: + s2 = ""; + break; + case pt_bare: + s2 = " bare"; + break; + case pt_trace: + s2 = " trace"; + break; + case pt_paste: + s2 = " paste"; + break; + case pt_mask: + s2 = " mask"; + break; + default: + abort(); + } + s = stralloc_printf("%spad \"%s\" %s %s%s", + obj->u.pad.rounded ? "r" : "", + obj->u.pad.name, base, s1, s2); + break; + case ot_hole: + s1 = obj_base_name(obj->u.hole.other, prev); + s = stralloc_printf("hole %s %s", base, s1); + break; + case ot_arc: + s1 = obj_base_name(obj->u.arc.start, prev); + s2 = unparse(obj->u.arc.width); + if (obj->u.arc.start == obj->u.arc.end) { + s = stralloc_printf("circ %s %s %s", base, s1, s2); + } else { + s3 = obj_base_name(obj->u.arc.end, prev); + s = stralloc_printf("arc %s %s %s %s", + base, s1, s3, s2); + } + free(s2); + break; + default: + abort(); + } + return s; +} + + +/* ----- print measurement ------------------------------------------------- */ + + +static const char *meas_type_name[mt_n] = { + "meas", "measx", "measy", + "meas", "measx", "measy", +}; + + + +static char *print_meas_base(struct vec *base, const struct frame_qual *qual) +{ + const char *name; + size_t n; + const struct frame_qual *walk; + char *s, *p; + + name = base_name(base, NULL); + n = strlen(name)+1; /* vec\0 */ + for (walk = qual; walk; walk = walk->next) + n += strlen(walk->frame->name)+1; /* frame/ */ + if (base->frame != frames) + n += strlen(base->frame->name)+1; /* frame. */ + + s = p = alloc_size(n); + for (walk = qual; walk; walk = walk->next) { + n = strlen(walk->frame->name); + memcpy(p, walk->frame->name, n); + p[n] = '/'; + p += n+1; + } + if (base->frame != frames) { + n = strlen(base->frame->name); + memcpy(p, base->frame->name, n); + p[n] = '.'; + p += n+1; + } + strcpy(p, name); + return s; +} + + +char *print_meas(const struct obj *obj) +{ + char *s, *t; + char *s1, *s2, *s3; + + assert(obj->type == ot_meas); + + s = stralloc_printf("%s ", meas_type_name[obj->u.meas.type]); + if (obj->u.meas.label) { + t = stralloc_printf("%s\"%s\" ", s, obj->u.meas.label); + free(s); + s = t; + } + s1 = print_meas_base(obj->base, obj->u.meas.low_qual); + s2 = stralloc_printf(" %s ", + obj->u.meas.type < 3 ? obj->u.meas.inverted ? "<-" : "->" : + obj->u.meas.inverted ? "<<" : ">>"); + s3 = print_meas_base(obj->u.meas.high, obj->u.meas.high_qual); + t = stralloc_printf("%s%s%s%s", s, s1, s2, s3); + free(s); + free(s1); + free(s2); + free(s3); + s = t; + + if (!obj->u.meas.offset) + return s; + + s1 = unparse(obj->u.meas.offset); + t = stralloc_printf("%s %s", s, s1); + free(s); + free(s1); + return t; +} + + +/* ----- print vector ------------------------------------------------------ */ + + +const char *print_label(struct vec *vec) +{ + if (!vec->name) + generate_name(vec); + return vec->name; +} + + +char *print_vec(const struct vec *vec) +{ + const char *base; + char *x, *y, *s; + + base = base_name(vec->base, vec); + x = unparse(vec->x); + y = unparse(vec->y); + if (vec->name) + s = stralloc_printf("vec %s(%s, %s)", base, x, y); + else + s = stralloc_printf("vec %s(%s, %s)", base, x, y); + free(x); + free(y); + return s; +} + + +/* ----- frames ------------------------------------------------------------ */ + + +static void dump_frame(FILE *file, struct frame *frame, const char *indent) +{ + const struct table *table; + const struct loop *loop; + struct obj *obj; + struct order *order; + const struct order *item; + char *s; + const char *s1; + + if (frame->dumped) + return; + frame->dumped = 1; + + for (obj = frame->objs; obj; obj = obj->next) + if (obj->type == ot_frame) + dump_frame(file, obj->u.frame.ref, "\t"); + + if (frame->name) + fprintf(file, "frame %s {\n", frame->name); + + for (table = frame->tables; table; table = table->next) + dump_table(file, table, indent); + for (loop = frame->loops; loop; loop = loop->next) + dump_loop(file, loop, indent); + + order = order_frame(frame); + for (item = order; item->vec || item->obj; item++) { + if (item->obj) { + fprintf(file, "%s", indent); + if (item->obj->name) + fprintf(file, "%s: ", item->obj->name); + s = print_obj(item->obj, item->vec); + fprintf(file, "%s\n", s); + } else { + s1 = print_label(item->vec); + s = print_vec(item->vec); + fprintf(file, "%s%s: %s\n", indent, s1, s); + } + free(s); + } + free(order); + + for (obj = frame->objs; obj; obj = obj->next) { + if (obj->dumped) + continue; + s = print_meas(obj); + fprintf(file, "%s%s\n", indent, s); + free(s); + } + + if (frame->name) + fprintf(file, "}\n\n"); +} + + +/* ----- file -------------------------------------------------------------- */ + + +static void dump_unit(FILE *file) +{ + switch (curr_unit) { + case curr_unit_mm: + fprintf(file, "unit mm\n"); + break; + case curr_unit_mil: + fprintf(file, "unit mil\n"); + break; + case curr_unit_auto: + fprintf(file, "unit auto\n"); + break; + default: + abort(); + } +} + + +static void dump_allow(FILE *file) +{ + switch (allow_overlap) { + case ao_none: + break; + case ao_touch: + fprintf(file, "allow touch\n"); + break; + case ao_any: + fprintf(file, "allow overlap\n"); + break; + default: + abort(); + } +} + + +static void reverse_frames(FILE *file, struct frame *last) +{ + if (last) { + reverse_frames(file, last->next); + dump_frame(file, last, "\t"); + } +} + + +int dump(FILE *file, const char *one) +{ + struct frame *frame; + + assert(!one); + + fprintf(file, "%s\n", MACHINE_GENERATED); + for (frame = frames; frame; frame = frame->next) + frame->dumped = 0; + + reverse_frames(file, frames->next); + fprintf(file, "package \"%s\"\n", pkg_name); + dump_unit(file); + dump_allow(file); + fprintf(file, "\n"); + dump_frame(file, frames, ""); + + fflush(file); + return !ferror(file); +} @@ -0,0 +1,49 @@ +/* + * dump.h - Dump objects in the native FPD format + * + * Written 2009-2011 by Werner Almesberger + * Copyright 2009-2011 by Werner Almesberger + * + * 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. + */ + + +#ifndef DUMP_H +#define DUMP_H + +#include <stdio.h> + +#include "obj.h" + + +#define MACHINE_GENERATED "/* MACHINE-GENERATED ! */\n" + + +/* + * vec obj + * -------------------------------------------------------------- + * NULL NULL end of list + * non-NULL NULL vector + * NULL non-NULL object, no previous vector + * non-NULL non-NULL object, with previous vector + */ + +struct order { + struct vec *vec; + struct obj *obj; +}; + + +const char *print_label(struct vec *vec); +char *print_vec(const struct vec *vec); +char *print_obj(const struct obj *obj, const struct vec *prev); +char *print_meas(const struct obj *obj); + +struct order *order_frame(const struct frame *frame); + +int dump(FILE *file, const char *one); + +#endif /* !DUMP_H */ @@ -0,0 +1,83 @@ +/* + * error.c - Error reporting + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> + +#include "util.h" +#include "error.h" + + +extern char *yytext; + +int lineno = 1; +void (*reporter)(const char *s) = report_to_stderr; + + +void yywarn(const char *s) +{ + /* we use yywarn only when starting */ + fprintf(stderr, "%d: warning: %s near \"%s\"\n", lineno, s, yytext); +} + + +void yyerrorf(const char *fmt, ...) +{ + va_list ap; + char *buf; + int n; + + va_start(ap, fmt); + n = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + buf = alloc_size(n+1); + va_start(ap, fmt); + vsnprintf(buf, n+1, fmt, ap); + va_end(ap); + fail("%s", buf); + free(buf); +} + + +void yyerror(const char *s) +{ + yyerrorf("%s", s); +} + + +void report_parse_error(const char *s) +{ + fprintf(stderr, "%d: %s near \"%s\"\n", lineno, s, yytext); + exit(1); +} + + +void report_to_stderr(const char *s) +{ + fprintf(stderr, "%s\n", s); + exit(1); +} + + +void fail(const char *fmt, ...) +{ + va_list ap; + char *s; + + va_start(ap, fmt); + s = stralloc_vprintf(fmt, ap); + va_end(ap); + reporter(s); + free(s); +} @@ -0,0 +1,34 @@ +/* + * error.h - Error reporting + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#ifndef ERROR_H +#define ERROR_H + + +extern int lineno; + +extern void (*reporter)(const char *s); + + +void yywarn(const char *s); + +void yyerrorf(const char *fmt, ...) + __attribute__((format(printf, 1, 2))); +void yyerror(const char *s); + +void report_to_stderr(const char *s); +void report_parse_error(const char *s); +void fail(const char *fmt, ...) + __attribute__((format(printf, 1, 2))); + +#endif /* !ERROR_H */ diff --git a/examples/fbga.fpd b/examples/fbga.fpd new file mode 100644 index 0000000..80c956f --- /dev/null +++ b/examples/fbga.fpd @@ -0,0 +1,56 @@ +/* MACHINE-GENERATED ! */ + +frame pad { + set Px = 0.5mm + + set Py = 0.5mm + + set cname = col+1 + + set e = 1mm + + __0: vec @(col*e-Px/2, row*-e-Py/2) + __1: vec .(0mm, Py) + __2: vec __0(Px, 0mm) + rpad "$rname$cname" __1 . +} + +frame inner { + loop col = 2, 3 + + loop enable = 1, inner + + frame pad @ +} + +frame last { + loop col = 4, 5 + + frame pad @ +} + +frame first { + loop col = 0, 1 + + frame pad @ +} + +package "Fake_BGA" +table + { row, rname, inner } + { 0, "A", 1 } + { 1, "B", 1 } + { 2, "C", 0 } + { 3, "D", 0 } + { 4, "E", 1 } + { 5, "F", 1 } + +frame last @ +frame first @ +frame inner @ +measy pad.__0 -> pad.__1 0.2mm +measy pad.__0 -> pad.__0 0.5mm +measx pad.__0 -> pad.__2 -0.3mm +measx pad.__0 -> pad.__0 -0.6mm +measy pad.__0 >> pad.__1 0.8mm +measx pad.__0 >> pad.__2 -0.9mm diff --git a/examples/meas.fpd b/examples/meas.fpd new file mode 100644 index 0000000..a650cc4 --- /dev/null +++ b/examples/meas.fpd @@ -0,0 +1,24 @@ +/* + * new-style measurements demo + */ + +part "measurements" +loop x = -2, 2 +A: vec @(0mm, 0mm) +B: vec @(x*2mm, 2mm) +C: vec @(0mm, 4mm) + +/* + * If we measure (x, y), y trumps x + */ +meas "A -> B = " A -> B 0.2mm +meas "A <- B = " A <- B 0.5mm + +meas "A >> B = " A >> B 1.5mm + +measx "x(A -> B) = " A -> B -0.5mm +measx "x(A >> B) = " A >> B -1mm +measy "y(A -> B) = " A -> B -2mm +measy "y(A >> B) = " A >> B -4.5mm + +meas "B -> C = " B -> C 0.5mm diff --git a/examples/qfn.fpd b/examples/qfn.fpd new file mode 100644 index 0000000..472aaee --- /dev/null +++ b/examples/qfn.fpd @@ -0,0 +1,73 @@ +/* + * Example of a QFN package (and general construction site to experiment with + * fped features during development) + * + * Everything you see here is likely to change sooner or later. + * + * http://www.nxp.com/acrobat/packages/footprint/SOT616-1_fp_reflow.pdf + */ + +frame pad_up { + c: vec @(-D/2, 0mm) + o: vec c(D, C) + set pad = n+1 + pad "$pad" c . +} + +frame pads { + loop n = 0, N/4-1 + + vec @(P*(n-(N/4-1)/2), -Ay/2) + frame pad_up . + +} + + +part "qfn" + + +set N = 24 + +/* + * Note that this table is not a great example because it contains lots of + * fields we don't really need for iterations. But it's useful for driving + * the GUI to extremes. + */ + +table + { P, Ax, Ay, Bx, By, C, D, SLx, SLy, SPx_tot, SPy_tot, SPx, SPy, Gx, Gy, Hx, Hy } + { 0.5mm, 5mm, 5mm, 3.2mm, 3.2mm, 0.9mm, 0.24mm, 2.1mm, 2.1mm, 1.2mm, + 1.2mm, 0.45mm, 0.45mm, 4.3mm, 4.3mm, 5.25mm, 5.25mm } + +h_x0y0: vec @(-Hx/2, -Hy/2) +h_x1y1: vec .(Hx, Hy) +rect h_x0y0 h_x1y1 8mil + +/* + * we can't draw the package outline on the silk screen for it would print + * over the pads. + */ +#if 0 +g_x0y0: vec @(-Gx/2, -Gy/2) +g_x1y1: vec .(Gx, Gy) +#endif + +frame pads @ + +// ARC, just for testing + +c: vec @(-1mm, 1mm) +r: vec c(0mm, 0.5mm) +e: vec c(-0.5mm, 0mm) +arc c r e 2mil + +r2: vec c(0mm, 0.8mm) +circ c r2 5mil + +/* +x1 = 1+2*3 +x2 = (1+2)*3 +x3 = 1-(2+3) +*/ + +measy pad_up.c -> pad_up.o 0.2mm diff --git a/examples/quad.fpd b/examples/quad.fpd new file mode 100644 index 0000000..958d224 --- /dev/null +++ b/examples/quad.fpd @@ -0,0 +1,15 @@ +frame c { + vec @(1mm, 0mm) + circ . @ +} + +part "quad" + +vec @(-1mm, 1mm) +frame c . +vec @(1mm, -1mm) +frame c . +vec @(-1mm, -1mm) +frame c . +vec @(1mm, 1mm) +frame c . diff --git a/examples/sc89.fpd b/examples/sc89.fpd new file mode 100644 index 0000000..7a3af96 --- /dev/null +++ b/examples/sc89.fpd @@ -0,0 +1,80 @@ +/* MACHINE-GENERATED ! */ + +frame pad { + corner: vec @(-Px/2, -Py/2) + x: vec .(Px, 0mm) + y: vec corner(0mm, Py) + pad "$pad" . x +} + +frame pad_ne { + set pad = 2 + + _pad_ne_0: vec @(-Px/2, -Py/2) + frame pad . +} + +frame pad_nw { + set pad = 1 + + _pad_nw_0: vec @(Px/2, -Py/2) + frame pad . +} + +frame pad_sc { + set pad = 3 + + _pad_sc_0: vec @(0mm, Py/2) + frame pad . +} + +frame outline { + top: vec @(0mm, Oy/2) + bottom: vec @(0mm, -Oy/2) + bot_right: vec .(Ox/2, 0mm) + bot_left: vec bottom(-Ox/2, 0mm) + line . bot_right 5mil + top_right: vec top(Ow/2, 0mm) + top_left: vec top(-Ow/2, 0mm) + line . top_right 5mil + middle: vec @(0mm, Oh) + mid_rightmost: vec .(Ox/2, 0mm) + line . bot_right 5mil + mid_leftmost: vec middle(-Ox/2, 0mm) + line . bot_left 5mil + mid_right: vec middle(Ow/2, 0mm) + line top_right . 5mil + line . mid_rightmost 5mil + mid_left: vec middle(-Ow/2, 0mm) + line mid_leftmost . 5mil + line top_left . 5mil +} + +package "SC89" +table + { Px, Py, Gy, Wx } + { 0.5mm, 0.6mm, 0.7mm, 1.5mm } + +table + { Ox, Oy, Oh, Ow } + { 2mm, 2.2mm, 0.6mm, 0.85mm } + +ref_up: vec @(0mm, Gy/2) +frame pad_sc . +ref_down_c: vec @(0mm, -Gy/2) +ref_down_r: vec .(Wx/2, 0mm) +frame pad_ne . +ref_down_l: vec ref_down_c(-Wx/2, 0mm) +frame pad_nw . +dummy: vec @(0.2mm, 0mm) +frame outline @ +measx pad.corner >> pad.x -0.6mm +measy pad.corner >> pad.y 0.8mm +measy ref_down_l >> pad.corner 0.5mm +measx pad.corner -> pad.x -0.3mm +measy pad.corner >> ref_down_l 0.5mm +measy dummy >> outline.bot_right 1.1mm +measy outline.mid_rightmost >> dummy 0.3mm +measy outline.top_right >> outline.bot_right 1.2mm +measx outline.top_left >> outline.top_right 0.3mm +measx outline.bot_left >> outline.bot_right -0.8mm diff --git a/examples/tab.fpd b/examples/tab.fpd new file mode 100644 index 0000000..aefcbd5 --- /dev/null +++ b/examples/tab.fpd @@ -0,0 +1,16 @@ +/* + * row selection example + */ + +part "tab" + +table + { x, x2 } + { 1mm, 1 } + { 2mm, 4 } + { 3mm, 9 } +vec @(x, 0mm) +circ @ . +c: vec @(x-1mm, -4mm) +vec c(0.5mm, 0.5mm) +pad "$x2" c . @@ -0,0 +1,677 @@ +/* + * expr.c - Expressions and values + * + * Written 2009, 2010, 2012 by Werner Almesberger + * Copyright 2009, 2010, 2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "util.h" +#include "error.h" +#include "obj.h" +#include "unparse.h" +#include "fpd.h" +#include "expr.h" + + +struct num undef = { .type = nt_none }; + + +/* ----- error reporting --------------------------------------------------- */ + + +void fail_expr(const struct expr *expr) +{ + char *s; + + s = unparse(expr); + fail("in \"%s\" at line %d", s, expr->lineno); + free(s); +} + + +/* ----- unit conversion --------------------------------------------------- */ + + +/* + * If an expression contains a typo, we may get large exponents. Thus, we just + * "sprintf" in order to be able to handle any integer. Since the number of + * different exponents in a session will still be small, we use "unique" to + * give us a constant string, so that we don't have to worry about memory + * allocation. + */ + +const char *str_unit(struct num n) +{ + const char *unit; + char buf[20]; /* @@@ plenty */ + + if (n.exponent == 0) + return ""; + switch (n.type) { + case nt_mm: + unit = "mm"; + break; + case nt_mil: + unit = "mil"; + break; + default: + abort(); + } + if (n.exponent == 1) + return unit; + sprintf(buf, "%s^%d", unit, n.exponent); + return unique(buf); +} + + +int to_unit(struct num *n) +{ + if (!is_distance(*n)) { + fail("%s^%d is not a distance", + n->type == nt_mm ? "mm" : n->type == nt_mil ? "mil" : "?", + n->exponent); + return 0; + } + switch (n->type) { + case nt_mil: + n->n = mil_to_units(n->n); + break; + case nt_mm: + n->n = mm_to_units(n->n); + break; + default: + abort(); + } + return 1; +} + + +/* ----- number to string conversion (hackish) ----------------------------- */ + + +static char *num_to_string(struct num n) +{ + static char buf[100]; /* enough :-) */ + + snprintf(buf, sizeof(buf), "%lg%s", n.n, str_unit(n)); + return buf; +} + + +/* ----- primary expressions ----------------------------------------------- */ + + +struct num op_string(const struct expr *self, const struct frame *frame) +{ + fail("cannot evaluate string"); + return undef; +} + + +struct num op_num(const struct expr *self, const struct frame *frame) +{ + return self->u.num; +} + + +/* + * We have two modes of operation: during instantiation and editing, after + * instantiation. During instantiation, we follow curr_row and curr_parent. + * These pointers are NULL when instantiation finishes, and we use this as a + * signal that we're now in editing mode. In editing mode, the "active" values + * are used instead of the "current" ones. + */ + +struct num eval_var(const struct frame *frame, const char *name) +{ + const struct table *table; + const struct loop *loop; + const struct value *value; + struct var *var; + struct num res; + + for (table = frame->tables; table; table = table->next) { + value = table->curr_row ? table->curr_row->values : + table->active_row->values; + for (var = table->vars; var; var = var->next) { + if (!var->key && var->name == name) { + if (var->visited) { + fail("recursive evaluation through " + "\"%s\"", name); + return undef; + } + var->visited = 1; + res = eval_num(value->expr, frame); + var->visited = 0; + return res; + } + value = value->next; + } + } + for (loop = frame->loops; loop; loop = loop->next) + if (loop->var.name == name) { + if (loop->curr_value == UNDEF) + return make_num(loop->n+loop->active); + if (!loop->initialized) { + fail("uninitialized loop \"%s\"", name); + return undef; + } + return make_num(loop->curr_value); + } + if (frame->curr_parent) + return eval_var(frame->curr_parent, name); + if (frame->active_ref) + return eval_var(frame->active_ref->frame, name); + return undef; +} + + +static const char *eval_string_var(const struct frame *frame, const char *name) +{ + const struct table *table; + const struct loop *loop; + const struct value *value; + struct var *var; + const char *res; + + for (table = frame->tables; table; table = table->next) { + value = table->curr_row ? table->curr_row->values : + table->active_row->values; + for (var = table->vars; var; var = var->next) { + if (!var->key && var->name == name) { + if (var->visited) + return NULL; + var->visited = 1; + res = eval_str(value->expr, frame); + var->visited = 0; + return res; + } + value = value->next; + } + } + for (loop = frame->loops; loop; loop = loop->next) + if (loop->var.name == name) + return NULL; + if (frame->curr_parent) + return eval_string_var(frame->curr_parent, name); + if (frame->active_ref) + return eval_string_var(frame->active_ref->frame, name); + return NULL; +} + + +struct num op_var(const struct expr *self, const struct frame *frame) +{ + struct num res; + + res = eval_var(frame, self->u.var); + if (is_undef(res)) + fail("undefined variable \"%s\"", self->u.var); + return res; +} + + +/* ----- Variable equivalence ---------------------------------------------- */ + + +static int num_eq(struct num a, struct num b) +{ + if (a.exponent != b.exponent) + return 0; + if (a.exponent && a.type != b.type) { + if (a.type == nt_mil) + return mil_to_mm(a.n, a.exponent) == b.n; + else + return a.n == mil_to_mm(b.n, b.exponent); + } + return a.n == b.n; +} + + +int var_eq(const struct frame *frame, const char *name, + const struct expr *expr) +{ + const char *vs, *es; + struct num vn, en; + + vs = eval_string_var(frame, name); + if (!vs) { + vn = eval_var(frame, name); + if (is_undef(vn)) { + fail("undefined variable \"%s\"", name); + return -1; + } + } + es = eval_str(expr, frame); + if (!es) { + en = eval_num(expr, frame); + if (is_undef(en)) + return -1; + } + if (vs || es) { + if (!vs) + vs = num_to_string(vn); + if (!es) + es = num_to_string(en); + return !strcmp(vs, es); + } else { + return num_eq(vn, en); + } +} + + +/* ----- arithmetic -------------------------------------------------------- */ + + +static struct num compatible_sum(struct num *a, struct num *b) +{ + struct num res; + + if (a->type != b->type) { + if (a->type == nt_mil) { + a->type = nt_mm; + a->n = mil_to_mm(a->n, a->exponent); + } + if (b->type == nt_mil) { + b->type = nt_mm; + b->n = mil_to_mm(b->n, a->exponent); + } + } + if (a->exponent != b->exponent) { + fail("incompatible exponents (%d, %d)", + a->exponent, b->exponent); + return undef; + } + res.type = a->type; + res.exponent = a->exponent; + res.n = 0; /* keep gcc happy */ + return res; +} + + +static struct num compatible_mult(struct num *a, struct num *b, + int exponent) +{ + struct num res; + + if (a->type != b->type) { + if (a->type == nt_mil) { + a->type = nt_mm; + a->n = mil_to_mm(a->n, a->exponent); + } + if (b->type == nt_mil) { + b->type = nt_mm; + b->n = mil_to_mm(b->n, b->exponent); + } + } + res.type = a->type; + res.exponent = exponent; + res.n = 0; /* keep gcc happy */ + return res; +} + + +static struct num sin_cos(const struct expr *self, + const struct frame *frame, double (*fn)(double arg)) +{ + struct num res; + + res = eval_num(self->u.op.a, frame); + if (is_undef(res)) + return undef; + if (!is_dimensionless(res)) { + fail("angle must be dimensionless"); + return undef; + } + res.n = fn(res.n/180.0*M_PI); + return res; +} + + +struct num op_sin(const struct expr *self, const struct frame *frame) +{ + return sin_cos(self, frame, sin); +} + + +struct num op_cos(const struct expr *self, const struct frame *frame) +{ + return sin_cos(self, frame, cos); +} + + +struct num op_sqrt(const struct expr *self, const struct frame *frame) +{ + struct num res; + + res = eval_num(self->u.op.a, frame); + if (is_undef(res)) + return undef; + if (res.exponent & 1) { + fail("exponent of sqrt argument must be a multiple of two"); + return undef; + } + if (res.n < 0) { + fail("argument of sqrt must be positive"); + return undef; + } + res.n = sqrt(res.n); + res.exponent >>= 1; + return res; +} + + +struct num op_minus(const struct expr *self, const struct frame *frame) +{ + struct num res; + + res = eval_num(self->u.op.a, frame); + if (!is_undef(res)) + res.n = -res.n; + return res; +} + + +struct num op_floor(const struct expr *self, const struct frame *frame) +{ + struct num res; + + res = eval_num(self->u.op.a, frame); + if (!is_undef(res)) + res.n = floor(res.n); + return res; +} + + +#define BINARY \ + struct num a, b, res; \ + \ + a = eval_num(self->u.op.a, frame); \ + if (is_undef(a)) \ + return undef; \ + b = eval_num(self->u.op.b, frame); \ + if (is_undef(b)) \ + return undef; + + +struct num op_add(const struct expr *self, const struct frame *frame) +{ + BINARY; + res = compatible_sum(&a, &b); + if (is_undef(res)) + return undef; + res.n = a.n+b.n; + return res; +} + + +struct num op_sub(const struct expr *self, const struct frame *frame) +{ + BINARY; + res = compatible_sum(&a, &b); + if (is_undef(res)) + return undef; + res.n = a.n-b.n; + return res; +} + + +struct num op_mult(const struct expr *self, const struct frame *frame) +{ + BINARY; + res = compatible_mult(&a, &b, a.exponent+b.exponent); + res.n = a.n*b.n; + return res; +} + + +struct num op_div(const struct expr *self, const struct frame *frame) +{ + BINARY; + if (!b.n) { + fail("division by zero"); + return undef; + } + res = compatible_mult(&a, &b, a.exponent-b.exponent); + res.n = a.n/b.n; + return res; +} + + +/* ----- expression construction ------------------------------------------- */ + + +struct expr *new_op(op_type op) +{ + struct expr *expr; + + expr = alloc_type(struct expr); + expr->op = op; + expr->lineno = lineno; + return expr; +} + + +struct expr *binary_op(op_type op, struct expr *a, struct expr *b) +{ + struct expr *expr; + + expr = new_op(op); + expr->u.op.a = a; + expr->u.op.b = b; + return expr; +} + + +const char *eval_str(const struct expr *expr, const struct frame *frame) +{ + if (expr->op == op_string) + return expr->u.str; + if (expr->op == op_var) + return eval_string_var(frame, expr->u.var); + return NULL; +} + + +struct num eval_num(const struct expr *expr, const struct frame *frame) +{ + return expr->op(expr, frame); +} + + +/* ----- string expansion -------------------------------------------------- */ + + +char *expand(const char *name, const struct frame *frame) +{ + int len = strlen(name); + char *buf = alloc_size(len+1); + const char *s, *s0; + char *var; + const char *var_unique, *value_string; + struct num value; + int i, value_len; + + i = 0; + for (s = name; *s; s++) { + if (*s != '$') { + buf[i++] = *s; + continue; + } + s0 = ++s; + if (*s != '{') { + while (is_id_char(*s, s == s0)) + s++; + if (s == s0) { + if (*s) { + goto invalid; + } else { + fail("incomplete variable name"); + goto fail; + } + } + var = strnalloc(s0, s-s0); + len -= s-s0+1; + s--; + } else { + s++; + while (*s != '}') { + if (!*s) { + fail("unfinished \"${...}\""); + goto fail; + } + if (!is_id_char(*s, s == s0+1)) + goto invalid; + s++; + } + var = strnalloc(s0+1, s-s0-1); + len -= s-s0+2; + } + if (!frame) + continue; + var_unique = unique(var); + free(var); + value_string = eval_string_var(frame, var_unique); + if (!value_string) { + value = eval_var(frame, var_unique); + if (is_undef(value)) { + fail("undefined variable \"%s\"", var_unique); + goto fail; + } + value_string = num_to_string(value); + } + value_len = strlen(value_string); + len += value_len; + buf = realloc(buf, len+1); + if (!buf) + abort(); + strcpy(buf+i, value_string); + i += value_len; + } + buf[i] = 0; + return buf; + +invalid: + fail("invalid character in variable name"); +fail: + free(buf); + return NULL; +} + + +/* ----- make a number -----------------------------------------------------*/ + + +struct expr *new_num(struct num num) +{ + struct expr *expr; + + expr = new_op(op_num); + expr->u.num = num; + return expr; +} + + +/* ----- expression-only parser -------------------------------------------- */ + + +struct expr *parse_expr(const char *s) +{ + scan_expr(s); + return yyparse() ? NULL : expr_result; +} + + +static void vacate_op(struct expr *expr) +{ + if (expr->op == op_num || expr->op == op_var) + return; + if (expr->op == op_string) { + free(expr->u.str); + return; + } + if (expr->op == op_minus || expr->op == op_floor || + expr->op == op_sin || expr->op == op_cos || expr->op == op_sqrt) { + free_expr(expr->u.op.a); + return; + } + if (expr->op == op_add || expr->op == op_sub || + expr->op == op_mult || expr->op == op_div) { + free_expr(expr->u.op.a); + free_expr(expr->u.op.b); + return; + } + abort(); +} + + +void free_expr(struct expr *expr) +{ + vacate_op(expr); + free(expr); +} + + +/* ----- [var =] value, ... shortcuts -------------------------------------- */ + + +int parse_var(const char *s, const char **id, struct value **values, + int max_values) +{ + const struct value *value; + int n; + + scan_var(s); + if (yyparse()) + return -1; + if (id) + *id = var_id; + *values = var_value_list; + n = 0; + for (value = var_value_list; value; value = value->next) + n++; + if (max_values == -1 || n <= max_values) + return n; + free_values(var_value_list, 0); + return -1; +} + + +int parse_values(const char *s, struct value **values) +{ + const struct value *value; + int n; + + scan_values(s); + if (yyparse()) + return -1; + *values = var_value_list; + n = 0; + for (value = var_value_list; value; value = value->next) + n++; + return n; +} + + +void free_values(struct value *values, int keep_expr) +{ + struct value *next; + + while (values) { + next = values->next; + if (!keep_expr) + free_expr(values->expr); + free(values); + values = next; + } +} @@ -0,0 +1,154 @@ +/* + * expr.h - Expressions and values + * + * Written 2009, 2012 by Werner Almesberger + * Copyright 2009, 2012 by Werner Almesberger + * + * 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. + */ + + +#ifndef EXPR_H +#define EXPR_H + +#include <math.h> + + +#define UNDEF HUGE_VAL + + +struct frame; +struct expr; +struct value; + +enum num_type { + nt_none, + nt_mm, + nt_mil, +}; + +struct num { + enum num_type type; + int exponent; + double n; +}; + +typedef struct num (*op_type)(const struct expr *self, + const struct frame *frame); + +struct expr { + op_type op; + union { + struct num num; + const char *var; + char *str; + struct { + struct expr *a; + struct expr *b; + } op; + } u; + int lineno; +}; + + +extern struct num undef; + + +#define is_undef(num) ((num).type == nt_none) +#define is_dimensionless(num) (!(num).exponent) + + +static inline int is_distance(struct num num) +{ + return (num.type == nt_mm || num.type == nt_mil) && num.exponent == 1; +} + + +void fail_expr(const struct expr *expr); + +const char *str_unit(struct num n); + + +static inline struct num make_num(double n) +{ + struct num res; + + res.type = nt_mm; + res.exponent = 0; + res.n = n; + return res; +} + + +static inline struct num make_mm(double mm) +{ + struct num res; + + res.type = nt_mm; + res.exponent = 1; + res.n = mm; + return res; +} + + +static inline struct num make_mil(double mil) +{ + struct num res; + + res.type = nt_mil; + res.exponent = 1; + res.n = mil; + return res; +} + + +int to_unit(struct num *n); + +struct num op_num(const struct expr *self, const struct frame *frame); +struct num op_var(const struct expr *self, const struct frame *frame); +struct num op_string(const struct expr *self, const struct frame *frame); + +struct num op_sin(const struct expr *self, const struct frame *frame); +struct num op_cos(const struct expr *self, const struct frame *frame); +struct num op_sqrt(const struct expr *self, const struct frame *frame); + +struct num op_minus(const struct expr *self, const struct frame *frame); +struct num op_floor(const struct expr *self, const struct frame *frame); + +struct num op_add(const struct expr *self, const struct frame *frame); +struct num op_sub(const struct expr *self, const struct frame *frame); +struct num op_mult(const struct expr *self, const struct frame *frame); +struct num op_div(const struct expr *self, const struct frame *frame); + +struct expr *new_op(op_type op); +struct expr *binary_op(op_type op, struct expr *a, struct expr *b); + +int var_eq(const struct frame *frame, const char *name, + const struct expr *expr); + +struct num eval_var(const struct frame *frame, const char *name); + +/* + * eval_str returns NULL if the result isn't a string. Evaluation may then + * be attempted with eval_num, and the result can be converted accordingly. + */ +const char *eval_str(const struct expr *expr, const struct frame *frame); + +struct num eval_num(const struct expr *expr, const struct frame *frame); + +/* if frame == NULL, we only check the syntax without expanding */ +char *expand(const char *name, const struct frame *frame); + +struct expr *new_num(struct num num); +struct expr *parse_expr(const char *s); +void free_expr(struct expr *expr); + +int parse_var(const char *s, const char **id, struct value **values, + int max_values); +int parse_values(const char *s, struct value **values); +void free_values(struct value *values, int keep_expr); + +#endif /* !EXPR_H */ @@ -0,0 +1,215 @@ +/* + * file.c - File handling + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <sys/stat.h> + +#include "dump.h" +#include "kicad.h" +#include "postscript.h" +#include "gnuplot.h" +#include "util.h" +#include "file.h" +#include "fped.h" + + +/* ----- general helper functions ------------------------------------------ */ + + +char *set_extension(const char *name, const char *ext) +{ + char *s = stralloc(name); + char *slash, *dot; + char *res; + + slash = strrchr(s, '/'); + dot = strrchr(slash ? slash : s, '.'); + if (dot) + *dot = 0; + res = stralloc_printf("%s.%s", s, ext); + free(s); + return res; +} + + +int file_exists(const char *name) +{ + struct stat st; + + if (stat(name, &st) >= 0) + return 1; + if (errno == ENOENT) + return 0; + perror(name); + return -1; +} + + +int save_to(const char *name, int (*fn)(FILE *file, const char *one), + const char *one) +{ + FILE *file; + + file = fopen(name, "w"); + if (!file) { + perror(name); + return 0; + } + if (!fn(file, one)) { + perror(name); + return 0; + } + if (fclose(file) == EOF) { + perror(name); + return 0; + } + return 1; +} + + +void save_with_backup(const char *name, int (*fn)(FILE *file, const char *one), + const char *one) +{ + char *s = stralloc(name); + char *back, *tmp; + char *slash, *dot; + int n, res; + + /* save to temporary file */ + + slash = strrchr(s, '/'); + if (!slash) { + tmp = stralloc_printf("~%s", s); + } else { + *slash = 0; + tmp = stralloc_printf("%s/~%s", s, slash+1); + *slash = '/'; + } + + if (!save_to(tmp, fn, one)) + return; + + /* move existing file out of harm's way */ + + dot = strrchr(slash ? slash : s, '.'); + if (dot) + *dot = 0; + n = 0; + while (1) { + back = stralloc_printf("%s~%d%s%s", + s, n, dot ? "." : "", dot ? dot+1 : ""); + res = file_exists(back); + if (!res) + break; + free(back); + if (res < 0) + return; + n++; + } + if (rename(name, back) < 0) { + if (errno != ENOENT) { + perror(name); + free(back); + return; + } + } else { + fprintf(stderr, "renamed %s to %s\n", name, back); + } + free(back); + + /* rename to final name */ + + if (rename(tmp, name) < 0) { + perror(name); + free(tmp); + return; + } + free(tmp); + + fprintf(stderr, "saved to %s\n", name); +} + + +/* ----- application-specific save handlers -------------------------------- */ + + +void save_fpd(void) +{ + if (save_file_name) { + save_with_backup(save_file_name, dump, NULL); + } else { + if (!dump(stdout, NULL)) + perror("stdout"); + } +} + + +void write_kicad(void) +{ + char *name; + + if (save_file_name) { + name = set_extension(save_file_name, "mod"); + save_to(name, kicad, NULL); + free(name); + } else { + if (!kicad(stdout, NULL)) + perror("stdout"); + } +} + + +static void do_write_ps(int (*fn)(FILE *file, const char *one), + const char *one) +{ + char *name; + + if (save_file_name) { + name = set_extension(save_file_name, "ps"); + save_to(name, fn, one); + free(name); + } else { + if (!fn(stdout, one)) + perror("stdout"); + } +} + + +void write_ps(const char *one) +{ + do_write_ps(postscript, one); +} + + +void write_ps_fullpage(const char *one) +{ + do_write_ps(postscript_fullpage, one); +} + + +void write_gnuplot(const char *one) +{ + char *name; + + if (save_file_name) { + name = set_extension(save_file_name, "gp"); + save_to(name, gnuplot, one); + free(name); + } else { + if (!gnuplot(stdout, one)) + perror("stdout"); + } +} @@ -0,0 +1,37 @@ +/* + * file.h - File handling + * + * Written 2009-2011 by Werner Almesberger + * Copyright 2009-2011 by Werner Almesberger + * + * 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. + */ + + +#ifndef FILE_H +#define FILE_H + +#include <stdio.h> + + +/* + * Returns -1 on error. + */ +int file_exists(const char *name); + +char *set_extension(const char *name, const char *ext); +void save_with_backup(const char *name, int (*fn)(FILE *file, const char *one), + const char *one); +int save_to(const char *name, int (*fn)(FILE *file, const char *one), + const char *one); + +void save_fpd(void); +void write_kicad(void); +void write_ps(const char *one); +void write_ps_fullpage(const char *one); +void write_gnuplot(const char *one); + +#endif /* !FILE_H */ @@ -0,0 +1,36 @@ +/* + * fpd.c - Things fpd.l and fpd.y export + * + * Written 2009, 2012 by Werner Almesberger + * Copyright 2009, 2012 by Werner Almesberger + * + * 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. + */ + + +#ifndef FPD_H +#define FPD_H + +#include "expr.h" +#include "obj.h" + + +extern struct expr *expr_result; +extern const char *var_id; +extern struct value *var_value_list; + + +int dbg_print(const struct expr *expr, const struct frame *frame); + +void scan_empty(void); +void scan_file(void); +void scan_expr(const char *s); +void scan_var(const char *s); +void scan_values(const char *s); + +int yyparse(void); + +#endif /* !FPD_H */ @@ -0,0 +1,204 @@ +%{ +/* + * fpd.l - FootPrint Definition language + * + * Written 2009, 2010, 2012 by Werner Almesberger + * Copyright 2009, 2010, 2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> + +#include "util.h" +#include "coord.h" +#include "expr.h" +#include "error.h" +#include "meas.h" +#include "fpd.h" + +#include "y.tab.h" + + +static int start_token = START_FPD; +static int disable_keywords = 0; +static int is_table = 0; + + +void scan_empty(void) +{ + yy_scan_string(""); +} + + +void scan_file(void) +{ + start_token = START_FPD; +} + + +void scan_expr(const char *s) +{ + start_token = START_EXPR; + yy_scan_string(s); +} + + +void scan_var(const char *s) +{ + start_token = START_VAR; + yy_scan_string(s); +} + + +void scan_values(const char *s) +{ + start_token = START_VALUES; + yy_scan_string(s); +} + +%} + + +/* keywords are only accepted at the beginning of a line */ +%s NOKEYWORD +/* ALLOW is followed by special keywords (we could use NOKEYWORD as well) */ +%s ALLOW + +NUM [0-9]+\.?[0-9]* +SP [\t ]* + + +%% + +%{ + /* + * Nice hack: + * + * http://www.gnu.org/software/bison/manual/bison.html# + * Multiple-start_002dsymbols + */ + + if (start_token) { + int tmp = start_token; + start_token = 0; + return tmp; + } +%} + + +<INITIAL>"set" { BEGIN(NOKEYWORD); + return TOK_SET; } +<INITIAL>"loop" { BEGIN(NOKEYWORD); + return TOK_LOOP; } +<INITIAL>"part" { BEGIN(NOKEYWORD); + return TOK_PACKAGE; } +<INITIAL>"package" { BEGIN(NOKEYWORD); + return TOK_PACKAGE; } +<INITIAL>"frame" { BEGIN(NOKEYWORD); + is_table = 0; + return TOK_FRAME; } +<INITIAL>"table" { BEGIN(NOKEYWORD); + is_table = 1; + return TOK_TABLE; } +<INITIAL>"vec" { BEGIN(NOKEYWORD); + return TOK_VEC; } +<INITIAL>"pad" { BEGIN(NOKEYWORD); + return TOK_PAD; } +<INITIAL>"rpad" { BEGIN(NOKEYWORD); + return TOK_RPAD; } +<INITIAL>"hole" { BEGIN(NOKEYWORD); + return TOK_HOLE; } +<INITIAL>"rect" { BEGIN(NOKEYWORD); + return TOK_RECT; } +<INITIAL>"line" { BEGIN(NOKEYWORD); + return TOK_LINE; } +<INITIAL>"circ" { BEGIN(NOKEYWORD); + return TOK_CIRC; } +<INITIAL>"arc" { BEGIN(NOKEYWORD); + return TOK_ARC; } +<INITIAL>"meas" { BEGIN(NOKEYWORD); + return TOK_MEAS; } +<INITIAL>"measx" { BEGIN(NOKEYWORD); + return TOK_MEASX; } +<INITIAL>"measy" { BEGIN(NOKEYWORD); + return TOK_MEASY; } +<INITIAL>"unit" { BEGIN(NOKEYWORD); + return TOK_UNIT; } + +<INITIAL>"allow" BEGIN(ALLOW); +<ALLOW>"overlap" return TOK_ALLOW_OVERLAP; +<ALLOW>"touch" return TOK_ALLOW_TOUCH; + +<INITIAL>"%del" { BEGIN(NOKEYWORD); + return TOK_DBG_DEL; } +<INITIAL>"%move" { BEGIN(NOKEYWORD); + return TOK_DBG_MOVE; } +<INITIAL>"%frame" { BEGIN(NOKEYWORD); + return TOK_DBG_FRAME; } +<INITIAL>"%print" { BEGIN(NOKEYWORD); + return TOK_DBG_PRINT; } +<INITIAL>"%iprint" { BEGIN(NOKEYWORD); + return TOK_DBG_IPRINT; } +<INITIAL>"%meas" { BEGIN(NOKEYWORD); + return TOK_DBG_MEAS; } +<INITIAL>"%dump" { BEGIN(NOKEYWORD); + return TOK_DBG_DUMP; } +<INITIAL>"%exit" { BEGIN(NOKEYWORD); + return TOK_DBG_EXIT; } +<INITIAL>"%tsort" { BEGIN(NOKEYWORD); + return TOK_DBG_TSORT; } + +<INITIAL>[a-zA-Z_][a-zA-Z_0-9]*: { *strchr(yytext, ':') = 0; + yylval.id = unique(yytext); + return LABEL; } +[a-zA-Z_][a-zA-Z_0-9]* { yylval.id = unique(yytext); + return ID; } + +{NUM}{SP}mm { yylval.num.type = nt_mm; + yylval.num.exponent = 1; + sscanf(yytext, "%lf", &yylval.num.n); + return NUMBER; } +{NUM}{SP}mil { yylval.num.type = nt_mil; + yylval.num.exponent = 1; + sscanf(yytext, "%lf", &yylval.num.n); + return NUMBER; } +{NUM} { yylval.num.type = nt_mm; /* mm or mil */ + yylval.num.exponent = 0; + sscanf(yytext, "%lf", &yylval.num.n); + return NUMBER; } + +\"(\\[^\n\t]|[^\\"\n\t])*\" { *strrchr(yytext, '"') = 0; + yylval.str = stralloc(yytext+1); + return STRING; } + +"->" return TOK_NEXT; +"<-" return TOK_NEXT_INVERTED; +">>" return TOK_MAX; +"<<" return TOK_MAX_INVERTED; + +{SP} ; +\n { if (!disable_keywords) + BEGIN(INITIAL); + lineno++; } + +; BEGIN(INITIAL); + +"{" { disable_keywords = is_table; + BEGIN(disable_keywords ? + NOKEYWORD : INITIAL); + return '{'; } +"}" { disable_keywords = 0; + BEGIN(INITIAL); + return '}'; } + +^#\ [0-9]+\ \"[^"]*\"(\ [0-9]+)*\n { + if (!disable_keywords) + BEGIN(INITIAL); + lineno = strtol(yytext+2, NULL, 0); } + +. return *yytext; @@ -0,0 +1,1288 @@ +%{ +/* + * fpd.y - FootPrint Definition language + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <string.h> + +#include "util.h" +#include "error.h" +#include "coord.h" +#include "expr.h" +#include "obj.h" +#include "meas.h" +#include "gui_status.h" +#include "gui_inst.h" /* for %meas */ +#include "dump.h" +#include "tsort.h" +#include "fpd.h" + +#include "y.tab.h" + + +struct expr *expr_result; +const char *var_id; +struct value *var_value_list; + + +static struct frame *curr_frame; +static struct table *curr_table; +static struct row *curr_row; + +static struct vec *last_vec = NULL; + +static struct table **next_table; +static struct loop **next_loop; +static struct vec **next_vec; +static struct obj **next_obj; + +static int n_vars, n_values; + +static const char *id_sin, *id_cos, *id_sqrt, *id_floor; + +static struct tsort *tsort; + + +/* ----- lookup functions -------------------------------------------------- */ + + +static struct frame *find_frame(const char *name) +{ + struct frame *f; + + for (f = frames->next; f; f = f->next) + if (f->name == name) + return f; + return NULL; +} + + +static struct vec *find_vec(const struct frame *frame, const char *name) +{ + struct vec *v; + + for (v = frame->vecs; v; v = v->next) + if (v->name == name) + return v; + return NULL; +} + + +static struct obj *find_obj(const struct frame *frame, const char *name) +{ + struct obj *obj; + + for (obj = frame->objs; obj; obj = obj->next) + if (obj->name == name) + return obj; + return NULL; +} + + +static int find_label(const struct frame *frame, const char *name) +{ + if (find_vec(frame, name)) + return 1; + if (find_obj(frame, name)) + return 1; + return 0; +} + + +static struct var *find_var(const struct frame *frame, const char *name) +{ + const struct table *table; + struct var *var; + struct loop *loop; + + for (table = frame->tables; table; table = table->next) + for (var = table->vars; var; var = var->next) + if (!var->key && var->name == name) + return var; + for (loop = frame->loops; loop; loop = loop->next) + if (loop->var.name == name) + return &loop->var; + return NULL; +} + + +/* ----- item creation ----------------------------------------------------- */ + + +static void set_frame(struct frame *frame) +{ + curr_frame = frame; + next_table = &frame->tables; + next_loop = &frame->loops; + next_vec = &frame->vecs; + next_obj = &frame->objs; + last_vec = NULL; +} + + +static void make_var(const char *id, int key, struct expr *expr) +{ + struct table *table; + + table = zalloc_type(struct table); + table->vars = zalloc_type(struct var); + table->vars->name = id; + table->vars->frame = curr_frame; + table->vars->table = table; + table->vars->key = key; + table->rows = zalloc_type(struct row); + table->rows->table = table; + table->rows->values = zalloc_type(struct value); + table->rows->values->expr = expr; + table->rows->values->row = table->rows; + table->active_row = table->rows; + *next_table = table; + next_table = &table->next; +} + + +static void make_loop(const char *id, struct expr *from, struct expr *to) +{ + struct loop *loop; + + loop = alloc_type(struct loop); + loop->var.name = id; + loop->var.next = NULL; + loop->var.frame = curr_frame; + loop->var.table = NULL; + loop->from.expr = from; + loop->from.row = NULL; + loop->from.next = NULL; + loop->to.expr = to; + loop->to.row = NULL; + loop->to.next = NULL; + loop->next = NULL; + loop->active = 0; + loop->initialized = 0; + *next_loop = loop; + next_loop = &loop->next; +} + + +static struct obj *new_obj(enum obj_type type) +{ + struct obj *obj; + + obj = alloc_type(struct obj); + obj->type = type; + obj->name = NULL; + obj->frame = curr_frame; + obj->next = NULL; + obj->lineno = lineno; + return obj; +} + + +/* ---- frame qualifiers --------------------------------------------------- */ + + +static int duplicate_qualifier(const struct frame_qual *a, + const struct frame_qual *b) +{ + if (!b) + return 0; + if (a != b && a->frame == b->frame) { + yyerrorf("duplicate qualifier \"%s\"", a->frame->name); + return 1; + } + if (b && duplicate_qualifier(a, b->next)) + return 1; + return duplicate_qualifier(a->next, a->next); +} + + +static int can_reach(const struct frame *curr, const struct frame_qual *qual, + const struct frame *end) +{ + const struct obj *obj; + + if (curr == end) + return !qual; + + /* + * Don't recurse for removing the qualifier alone. We require a frame + * reference step as well, so that things like foo.foo.foo.bar.vec + * aren't allowed. + * + * Since a duplicate qualifier can never work, we test for this error + * explicitly in "duplicate_qualifier". + */ + if (qual && curr == qual->frame) + qual = qual->next; + + for (obj = curr->objs; obj; obj = obj->next) + if (obj->type == ot_frame) + if (can_reach(obj->u.frame.ref, qual, end)) + return 1; + return 0; +} + + +static int check_qbase(struct qbase *qbase) +{ + if (duplicate_qualifier(qbase->qualifiers, qbase->qualifiers)) + return 0; + + if (!can_reach(frames, qbase->qualifiers, qbase->vec->frame)) + yywarn("not all qualifiers can be reached"); + return 1; +} + + +/* ----- debugging directives ---------------------------------------------- */ + + +static int dbg_delete(const char *frame_name, const char *name) +{ + struct vec *vec; + struct obj *obj; + struct frame *frame; + + if (!frame_name) { + frame = curr_frame; + } else { + frame = find_frame(frame_name); + if (!frame) { + yyerrorf("unknown frame \"%s\"", frame_name); + return 0; + } + } + vec = find_vec(frame, name); + if (vec) { + delete_vec(vec); + return 1; + } + obj = find_obj(frame, name); + if (obj) { + delete_obj(obj); + return 1; + } + if (!frame_name) { + frame = find_frame(name); + if (frame) { + if (curr_frame == frame) { + yyerrorf("a frame can't delete itself"); + return 0; + } + delete_frame(frame); + return 1; + } + } + if (frame_name) + yyerrorf("unknown item \"%s.%s\"", frame_name, name); + else + yyerrorf("unknown item \"%s\"", name); + return 0; +} + + +static int dbg_move(const char *name, int anchor, const char *dest) +{ + struct vec *to, *vec; + struct obj *obj; + struct vec **anchors[3]; + int n_anchors; + + to = find_vec(curr_frame, dest); + if (!to) { + yyerrorf("unknown vector \"%s\"", dest); + return 0; + } + vec = find_vec(curr_frame, name); + if (vec) { + if (anchor) { + yyerrorf("invalid anchor (%d > 0)", anchor); + return 0; + } + vec->base = to; + return 1; + } + obj = find_obj(curr_frame, name); + if (!obj) { + yyerrorf("unknown item \"%s\"", name); + return 0; + } + n_anchors = obj_anchors(obj, anchors); + if (anchor >= n_anchors) { + yyerrorf("invalid anchor (%d > %d)", anchor, n_anchors-1); + return 0; + } + *anchors[anchor] = to; + return 1; +} + + +/* + * @@@ This is very similar to what we do in rule "obj". Consider merging. + */ + +/* + * We need to pass base_frame and base_vec, not just the vector (with the + * frame implied) since we can also reference the frame's origin, whose + * "vector" is NULL. + */ + +static int dbg_link_frame(const char *frame_name, + struct frame *base_frame, struct vec *base_vec) +{ + struct frame *frame; + struct obj *obj; + + assert(!base_vec || base_vec->frame == base_frame); + frame = find_frame(frame_name); + if (!frame) { + yyerrorf("unknown frame \"%s\"", frame_name); + return 0; + } + /* this can only fail in %frame */ + if (is_parent_of(frame, base_frame)) { + yyerrorf("frame \"%s\" is a parent of \"%s\"", + frame->name, base_frame->name); + return 0; + } + obj = new_obj(ot_frame); + obj->base = base_vec; + obj->frame = base_frame; + obj->u.frame.ref = frame; + connect_obj(base_frame, obj); + if (!frame->active_ref) + frame->active_ref = obj; + return 1; +} + + +int dbg_print(const struct expr *expr, const struct frame *frame) +{ + const char *s; + struct num num; + + s = eval_str(expr, frame); + if (s) { + printf("%s\n", s); + return 1; + } + num = eval_num(expr, frame); + if (is_undef(num)) + return 0; + printf("%lg%s\n", num.n, str_unit(num)); + return 1; +} + + +static int dbg_meas(const char *name) +{ + const struct obj *obj; + const struct inst *inst; + struct coord a1, b1; + char *s; + + obj = find_obj(frames, name); + if (!obj) { + yyerrorf("unknown object \"%s\"", name); + return 0; + } + + /* from fped.c:main */ + + if (!pkg_name) + pkg_name = stralloc("_"); + reporter = report_to_stderr; + if (!instantiate()) + return 0; + + inst = find_meas_hint(obj); + if (!inst) { + yyerrorf("measurement \"%s\" was not instantiated", name); + return 0; + } + + project_meas(inst, &a1, &b1); + s = format_len(obj->u.meas.label ? obj->u.meas.label : "", + dist_point(a1, b1), curr_unit); + printf("%s\n", s); + free(s); + + return 1; +} + + +%} + + +%union { + struct num num; + int flag; + char *str; + const char *id; + struct expr *expr; + struct frame *frame; + struct table *table; + struct var *var; + struct row *row; + struct value *value; + struct vec *vec; + struct obj *obj; + enum pad_type pt; + enum meas_type mt; + struct { + int inverted; + int max; + } mo; + struct { + struct frame *frame; + struct vec *vec; + } qvec; + struct qbase { + struct vec *vec; + struct frame_qual *qualifiers; + } qbase; +}; + + +%token START_FPD START_EXPR START_VAR START_VALUES +%token TOK_SET TOK_LOOP TOK_PACKAGE TOK_FRAME TOK_TABLE TOK_VEC +%token TOK_PAD TOK_RPAD TOK_HOLE TOK_RECT TOK_LINE TOK_CIRC TOK_ARC +%token TOK_MEAS TOK_MEASX TOK_MEASY TOK_UNIT +%token TOK_NEXT TOK_NEXT_INVERTED TOK_MAX TOK_MAX_INVERTED +%token TOK_DBG_DEL TOK_DBG_MOVE TOK_DBG_FRAME +%token TOK_DBG_PRINT TOK_DBG_IPRINT +%token TOK_DBG_DUMP TOK_DBG_EXIT TOK_DBG_TSORT TOK_DBG_MEAS +%token TOK_ALLOW_OVERLAP TOK_ALLOW_TOUCH + +%token <num> NUMBER +%token <str> STRING +%token <id> ID LABEL + +%type <table> table +%type <var> vars var +%type <row> rows +%type <value> row value opt_value_list +%type <vec> vec base +%type <obj> object obj meas unlabeled_meas +%type <expr> expr opt_expr add_expr mult_expr unary_expr primary_expr +%type <num> opt_num +%type <flag> opt_key +%type <frame> frame_qualifier +%type <str> opt_string +%type <pt> pad_type +%type <mt> meas_type +%type <mo> meas_op +%type <qvec> qualified_base +%type <qbase> qbase qbase_unchecked + +%% + +all: + START_FPD + { + frames = zalloc_type(struct frame); + set_frame(frames); + id_sin = unique("sin"); + id_cos = unique("cos"); + id_sqrt = unique("sqrt"); + id_floor = unique("floor"); + } + fpd + | START_EXPR expr + { + expr_result = $2; + } + | START_VAR ID opt_value_list + { + var_id = $2; + var_value_list = $3; + } + | START_VALUES row + { + var_value_list = $2; + } + ; + +fpd: + frame_defs part_name opt_setup opt_frame_items opt_measurements + | frame_defs setup opt_frame_items opt_measurements + | frame_defs frame_items opt_measurements + | frame_defs opt_measurements + ; + +part_name: + TOK_PACKAGE STRING + { + const char *p; + + if (!*$2) { + yyerrorf("invalid package name"); + YYABORT; + } + for (p = $2; *p; *p++) + if (*p < 32 || *p > 126) { + yyerrorf("invalid package name"); + YYABORT; + } + pkg_name = $2; + } + ; + +opt_setup: + | setup + ; + +setup: + unit + | allow + | unit allow + | allow unit + ; + +unit: + TOK_UNIT ID + { + if (!strcmp($2, "mm")) + curr_unit = curr_unit_mm; + else if (!strcmp($2, "mil")) + curr_unit = curr_unit_mil; + else if (!strcmp($2, "auto")) + curr_unit = curr_unit_auto; + else { + yyerrorf("unrecognized unit \"%s\"", $2); + YYABORT; + } + } + ; + +allow: + TOK_ALLOW_TOUCH + { + allow_overlap = ao_touch; + } + | TOK_ALLOW_OVERLAP + { + allow_overlap = ao_any; + } + ; + +frame_defs: + | frame_defs frame_def + ; + +frame_def: + TOK_FRAME ID '{' + { + if (find_frame($2)) { + yyerrorf("duplicate frame \"%s\"", $2); + YYABORT; + } + curr_frame = zalloc_type(struct frame); + curr_frame->name = $2; + set_frame(curr_frame); + curr_frame->next = frames->next; + frames->next = curr_frame; + } + opt_frame_items '}' + { + set_frame(frames); + } + ; + +opt_frame_items: + | frame_items + ; + +frame_items: + frame_item + | frame_item frame_items + ; + +frame_item: + table + | TOK_SET opt_key ID '=' expr + { + if (!$2 && find_var(curr_frame, $3)) { + yyerrorf("duplicate variable \"%s\"", $3); + YYABORT; + } + make_var($3, $2, $5); + } + | TOK_LOOP ID '=' expr ',' expr + { + if (find_var(curr_frame, $2)) { + yyerrorf("duplicate variable \"%s\"", $2); + YYABORT; + } + make_loop($2, $4, $6); + } + | vec + | LABEL vec + { + if (find_label(curr_frame, $1)) { + yyerrorf("duplicate label \"%s\"", $1); + YYABORT; + } + $2->name = $1; + } + | object + | LABEL object + { + if (find_label(curr_frame, $1)) { + yyerrorf("duplicate label \"%s\"", $1); + YYABORT; + } + $2->name = $1; + } + | debug_item + ; + +debug_item: + TOK_DBG_DEL ID + { + if (!dbg_delete(NULL, $2)) + YYABORT; + } + | TOK_DBG_DEL ID '.' ID + { + if (!dbg_delete($2, $4)) + YYABORT; + } + | TOK_DBG_MOVE ID opt_num ID + { + if (!dbg_move($2, $3.n, $4)) + YYABORT; + } + | TOK_DBG_FRAME ID qualified_base + { + if (!dbg_link_frame($2, $3.frame, $3.vec)) + YYABORT; + } + | TOK_DBG_PRINT expr + { + if (!dbg_print($2, curr_frame)) + YYABORT; + } + | TOK_DBG_MEAS ID + { + if (!dbg_meas($2)) + YYABORT; + } + | TOK_DBG_DUMP + { + if (!dump(stdout, NULL)) { + perror("stdout"); + exit(1); + } + } + | TOK_DBG_EXIT + { + exit(0); + } + | TOK_DBG_TSORT '{' + { + tsort = begin_tsort(); + } + sort_items '}' + { + void **sort, **walk; + + sort = end_tsort(tsort); + for (walk = sort; *walk; walk++) + printf("%s\n", (char *) *walk); + free(sort); + } + ; + +sort_items: + | sort_items '+' ID + { + add_node(tsort, (void *) $3, 0); + } + | sort_items '-' ID + { + add_node(tsort, (void *) $3, 1); + } + | sort_items ID ID opt_num + { + struct node *a, *b; + + /* order is important here ! */ + a = add_node(tsort, (void *) $2, 0); + b = add_node(tsort, (void *) $3, 0); + add_edge(a, b, $4.n); + } + ; + +table: + TOK_TABLE + { + $<table>$ = zalloc_type(struct table); + *next_table = $<table>$; + curr_table = $<table>$; + n_vars = 0; + } + '{' vars '}' rows + { + $$ = $<table>2; + $$->vars = $4; + $$->rows = $6; + $$->active_row = $6; + next_table = &$$->next; + } + ; + +vars: + var + { + $$ = $1; + } + | vars ',' var + { + struct var **walk; + + $$ = $1; + for (walk = &$$; *walk; walk = &(*walk)->next); + *walk = $3; + } + ; + +var: + opt_key ID + { + if (!$1 && find_var(curr_frame, $2)) { + yyerrorf("duplicate variable \"%s\"", $2); + YYABORT; + } + $$ = zalloc_type(struct var); + $$->name = $2; + $$->frame = curr_frame; + $$->table = curr_table; + $$->key = $1; + $$->next = NULL; + n_vars++; + } + ; + +opt_key: + { + $$ = 0; + } + | '?' + { + $$ = 1; + } + ; + +rows: + { + $$ = NULL; + } + | '{' + { + $<row>$ = alloc_type(struct row); + $<row>$->table = curr_table; + curr_row = $<row>$;; + n_values = 0; + } + row '}' + { + if (n_vars != n_values) { + yyerrorf("table has %d variables but row has " + "%d values", n_vars, n_values); + YYABORT; + } + $<row>2->values = $3; + } + rows + { + $$ = $<row>2; + $$->next = $6; + } + ; + +row: + value + { + $$ = $1; + } + | row ',' value + { + struct value **walk; + + $$ = $1; + for (walk = &$$; *walk; walk = &(*walk)->next); + *walk = $3; + } + ; + +value: + expr + { + $$ = alloc_type(struct value); + $$->expr = $1; + $$->row = curr_row; + $$->next = NULL; + n_values++; + } + ; + +vec: + TOK_VEC base '(' expr ',' expr ')' + { + $$ = alloc_type(struct vec); + $$->nul_tag = 0; + $$->name = NULL; + $$->base = $2; + $$->x = $4; + $$->y = $6; + $$->frame = curr_frame; + $$->next = NULL; + last_vec = $$; + *next_vec = $$; + next_vec = &$$->next; + } + ; + +base: + '@' + { + $$ = NULL; + } + | '.' + { + $$ = last_vec; + if (!$$) { + yyerrorf(". without predecessor"); + YYABORT; + } + } + | ID + { + $$ = find_vec(curr_frame, $1); + if (!$$) + $$ = (struct vec *) $1; + } + ; + +qualified_base: + base + { + $$.frame = curr_frame; + $$.vec = $1; + } + | frame_qualifier '@' + { + $$.frame = $1; + $$.vec = NULL; + } + | frame_qualifier ID + { + $$.frame = $1; + $$.vec = find_vec($1, $2); + if (!$$.vec) { + yyerrorf("unknown vector \"%s.%s\"", + $1->name, $2); + YYABORT; + } + } + ; + +frame_qualifier: + ID '.' + { + $$ = find_frame($1); + if (!$$) { + yyerrorf("unknown frame \"%s\"", $1); + YYABORT; + } + } + ; + +object: + obj + { + $$ = $1; + *next_obj = $1; + next_obj = &$1->next; + } + ; + +obj: + TOK_PAD STRING base base pad_type + { + $$ = new_obj(ot_pad); + $$->base = $3; + $$->u.pad.name = $2; + $$->u.pad.other = $4; + $$->u.pad.rounded = 0; + $$->u.pad.type = $5; + } + | TOK_RPAD STRING base base pad_type + { + $$ = new_obj(ot_pad); + $$->base = $3; + $$->u.pad.name = $2; + $$->u.pad.other = $4; + $$->u.pad.rounded = 1; + $$->u.pad.type = $5; + } + | TOK_HOLE base base + { + $$ = new_obj(ot_hole); + $$->base = $2; + $$->u.hole.other = $3; + } + | TOK_RECT base base opt_expr + { + $$ = new_obj(ot_rect); + $$->base = $2; + $$->u.rect.other = $3; + $$->u.rect.width = $4; + } + | TOK_LINE base base opt_expr + { + $$ = new_obj(ot_line); + $$->base = $2; + $$->u.line.other = $3; + $$->u.line.width = $4; + } + | TOK_CIRC base base opt_expr + { + $$ = new_obj(ot_arc); + $$->base = $2; + $$->u.arc.start = $3; + $$->u.arc.end = $3; + $$->u.arc.width = $4; + } + | TOK_ARC base base base opt_expr + { + $$ = new_obj(ot_arc); + $$->base = $2; + $$->u.arc.start = $3; + $$->u.arc.end = $4; + $$->u.arc.width = $5; + } + | TOK_FRAME ID + { + $<num>$.n = lineno; + } + base + { + $$ = new_obj(ot_frame); + $$->base = $4; + $$->u.frame.ref = find_frame($2); + if (!$$->u.frame.ref) { + yyerrorf("unknown frame \"%s\"", $2); + YYABORT; + } + if (!$$->u.frame.ref->active_ref) + $$->u.frame.ref->active_ref = $$; + $$->u.frame.lineno = $<num>3.n; + } + | TOK_DBG_IPRINT expr + { + $$ = new_obj(ot_iprint); + $$->base = NULL; + $$->u.iprint.expr = $2; + } + ; + +pad_type: + { + $$ = pt_normal; + } + | ID + { + if (!strcmp($1, "bare")) + $$ = pt_bare; + else if (!strcmp($1, "trace")) + $$ = pt_trace; + else if (!strcmp($1, "paste")) + $$ = pt_paste; + else if (!strcmp($1, "mask")) + $$ = pt_mask; + else { + yyerrorf("unknown pad type \"%s\"", $1); + YYABORT; + } + } + ; + +opt_measurements: + | measurements + ; + +measurements: + unlabeled_meas /* @@@ hack */ + { + *next_obj = $1; + next_obj = &$1->next; + } + | measurements meas + { + *next_obj = $2; + next_obj = &$2->next; + } + | measurements debug_item + ; + +meas: + unlabeled_meas + { + $$ = $1; + } + | LABEL unlabeled_meas + { + $$ = $2; + if (find_label(curr_frame, $1)) { + yyerrorf("duplicate label \"%s\"", $1); + YYABORT; + } + $$->name = $1; + } + ; + +unlabeled_meas: + meas_type opt_string qbase meas_op qbase opt_expr + { + struct meas *meas; + + $$ = new_obj(ot_meas); + meas = &$$->u.meas; + meas->type = $4.max ? $1+3 : $1; + meas->label = $2; + $$->base = $3.vec; + meas->low_qual = $3.qualifiers; + meas->inverted = $4.inverted; + meas->high = $5.vec; + meas->high_qual = $5.qualifiers; + meas->offset = $6; + $$->next = NULL; + } + ; + +qbase: + qbase_unchecked + { + $$ = $1; + if (!check_qbase(&$$)) + YYABORT; + } + ; + +qbase_unchecked: + ID + { + $$.vec = find_vec(frames, $1); + if (!$$.vec) { + yyerrorf("unknown vector \"%s\"", $1); + YYABORT; + } + $$.qualifiers = NULL; + } + | ID '.' ID + { + const struct frame *frame; + + frame = find_frame($1); + $$.vec = frame ? find_vec(frame, $3) : NULL; + if (!$$.vec) { + yyerrorf("unknown vector \"%s.%s\"", $1, $3); + YYABORT; + } + $$.qualifiers = NULL; + } + | ID '/' qbase + { + const struct frame *frame; + struct frame_qual *qual; + + $$ = $3; + frame = find_frame($1); + if (!frame) { + yyerrorf("unknown frame \"%s\"", $1); + YYABORT; + } else { + qual = alloc_type(struct frame_qual); + qual->frame = frame; + qual->next = $$.qualifiers; + $$.qualifiers = qual; + } + } + ; + +meas_type: + TOK_MEAS + { + $$ = mt_xy_next; + } + | TOK_MEASX + { + $$ = mt_x_next; + } + | TOK_MEASY + { + $$ = mt_y_next; + } + ; + +meas_op: + TOK_NEXT + { + $$.max = 0; + $$.inverted = 0; + } + | TOK_NEXT_INVERTED + { + $$.max = 0; + $$.inverted = 1; + } + | TOK_MAX + { + $$.max = 1; + $$.inverted = 0; + } + | TOK_MAX_INVERTED + { + $$.max = 1; + $$.inverted = 1; + } + ; + +opt_num: + { + $$.n = 0; + } + | NUMBER + { + $$ = $1; + } + ; + +opt_string: + { + $$ = NULL; + } + | STRING + { + $$ = $1; + } + ; + +opt_expr: + { + $$ = NULL; + } + | expr + { + $$ = $1; + } + ; + +expr: + add_expr + { + $$ = $1; + } + ; + +add_expr: + mult_expr + { + $$ = $1; + } + | add_expr '+' mult_expr + { + $$ = binary_op(op_add, $1, $3); + } + | add_expr '-' mult_expr + { + $$ = binary_op(op_sub, $1, $3); + } + ; + +mult_expr: + unary_expr + { + $$ = $1; + } + | mult_expr '*' unary_expr + { + $$ = binary_op(op_mult, $1, $3); + } + | mult_expr '/' unary_expr + { + $$ = binary_op(op_div, $1, $3); + } + ; + +unary_expr: + primary_expr + { + $$ = $1; + } + | '-' primary_expr + { + $$ = binary_op(op_minus, $2, NULL); + } + ; + +primary_expr: + NUMBER + { + $$ = new_op(op_num); + $$->u.num = $1; + } + | ID + { + $$ = new_op(op_var); + $$->u.var = $1; + } + | STRING + { + $$ = new_op(op_string); + $$->u.str = $1; + } + | '(' expr ')' + { + $$ = $2; + } + | ID '(' expr ')' + { + if ($1 == id_sin) + $$ = binary_op(op_sin, $3, NULL); + else if ($1 == id_cos) + $$ = binary_op(op_cos, $3, NULL); + else if ($1 == id_sqrt) + $$ = binary_op(op_sqrt, $3, NULL); + else if ($1 == id_floor) + $$ = binary_op(op_floor, $3, NULL); + else { + yyerrorf("unknown function \"%s\"", $1); + YYABORT; + } + } + ; + +/* special sub-grammar */ + +opt_value_list: + { + $$ = NULL; + } + | '=' row + { + $$ = $2; + } + ; @@ -0,0 +1,51 @@ +.TH FPED: "1" "October 2010" +.SH NAME +fped \- Footprint editor +.SH SYNOPSIS +.TP +.B fped +[\-k] [\-p|\-P [\-s scale]] [\-T [\-T]] [cpp_option ...] [in_file [out_file]] + +.SH DESCRIPTION +.B fped +is an editor that allows the interactive creation of footprints of +electronic components. Footprint definitions are stored in a text format +that resembles a programming language. +The language is constrained such that anything that can be expressed in +the textual definition also has a straightforward equivalent operation +that can be performed through the GUI. +A description of the GUI can be found here: +http://downloads.qi-hardware.com/people/werner/fped/gui.html +.SH OPTIONS +.TP +\fB\-k\fR +write KiCad output, then exit +.TP +\fB\-p\fR +write Postscript output, then exit +.TP +\fB\-P\fR +write Postscript output (full page), then exit +.TP +\fB\-s\fR scale +scale factor for \fB\-P\fR (default: auto\-scale) +.TP +\fB\-T\fR +test mode. Load file, then exit +.TP +\fB\-T\fR \fB\-T\fR +test mode. Load file, dump to stdout, then exit +.TP +cpp_option +\fB\-Idir\fR, \fB\-Dname\fR[=\fIvalue\fR], or \fB\-Uname\fR +.PP +Please report any further bugs at +.B werner@almesberger.net +.SH LICENCE +.B fped +is covered by the GNU General Public License (GPL), version 2 or later. +.SH AUTHORS +Werner Almesberger <werner@almesberger.net> +.PP +This manual page was written by Xiangfu Liu <xiangfu.z@gmail.com> +It is licensed under the terms of the GNU GPL (version 2 or later). @@ -0,0 +1,274 @@ +/* + * fped.c - Footprint editor, main function + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "cpp.h" +#include "util.h" +#include "error.h" +#include "obj.h" +#include "inst.h" +#include "file.h" +#include "postscript.h" +#include "dump.h" +#include "gui.h" +#include "delete.h" +#include "fpd.h" +#include "fped.h" + + +char *save_file_name = NULL; +int no_save = 0; + + +static void load_file(const char *name) +{ + FILE *file; + char line[sizeof(MACHINE_GENERATED)]; + + file = fopen(name, "r"); + if (file) { + if (!fgets(line, sizeof(line), file)) { + if (ferror(file)) { + perror(name); + exit(1); + } + *line = 0; + } + no_save = strcmp(line, MACHINE_GENERATED); + fclose(file); + reporter = report_parse_error; + run_cpp_on_file(name); + } else { + if (errno != ENOENT) { + perror(name); + exit(1); + } + scan_empty(); + } + (void) yyparse(); +} + + +void reload(void) +{ + struct frame *old_frames; + + /* @@@ this needs more work */ + purge(); + old_frames = frames; + scan_file(); + load_file(save_file_name); + if (!instantiate()) + frames = old_frames; + change_world(); +} + + +static void usage(const char *name) +{ + fprintf(stderr, +"usage: %s [batch_mode] [cpp_option ...] [in_file [out_file]]\n\n" +"Batch mode options:\n" +" -g [-1 package]\n" +" write gnuplot output, then exit\n" +" -k write KiCad output, then exit\n" +" -p write Postscript output, then exit\n" +" -P [-K] [-s scale] [-1 package]\n" +" write Postscript output (full page), then exit\n" +" -T test mode. Load file, then exit\n" +" -T -T test mode. Load file, dump to stdout, then exit\n\n" +"Common options:\n" +" -1 name output only the specified package\n" +" -K show the pad type key\n" +" -s scale scale factor for -P (default: auto-scale)\n" +" -s [width]x[heigth]\n" +" auto-scale to fit within specified box. Dimensions in mm.\n" +" cpp_option -Idir, -Dname[=value], or -Uname\n" + , name); + exit(1); +} + + +static int parse_scaling(const char *arg) +{ + const char *x; + char *end; + + x = strchr(arg, 'x'); + if (!x) { + postscript_params.zoom = strtod(arg, &end); + return !*end; + } + if (x != arg) { + postscript_params.max_width = mm_to_units(strtod(arg, &end)); + if (*end != 'x') + return 0; + } + if (x[1]) { + postscript_params.max_height = mm_to_units(strtod(x+1, &end)); + if (*end) + return 0; + } + return 1; +} + + +int main(int argc, char **argv) +{ + enum { + batch_none = 0, + batch_kicad, + batch_ps, + batch_ps_fullpage, + batch_gnuplot, + batch_test + } batch = batch_none; + char *name = *argv; + char **fake_argv; + char *args[2]; + int fake_argc; + char opt[] = "-?"; + int error; + int test_mode = 0; + const char *one = NULL; + int c; + + while ((c = getopt(argc, argv, "1:gkps:D:I:KPTU:")) != EOF) + switch (c) { + case '1': + one = optarg; + break; + case 'g': + if (batch) + usage(*argv); + batch = batch_gnuplot; + break; + case 'k': + if (batch) + usage(*argv); + batch = batch_kicad; + break; + case 'p': + if (batch) + usage(*argv); + batch = batch_ps; + break; + case 'P': + if (batch) + usage(*argv); + batch = batch_ps_fullpage; + break; + case 'K': + postscript_params.show_key = 1; + break; + case 's': + if (batch != batch_ps_fullpage) + usage(*argv); + if (!parse_scaling(optarg)) + usage(*argv); + break; + case 'T': + batch = batch_test; + test_mode++; + break; + case 'D': + case 'U': + case 'I': + opt[1] = c; + add_cpp_arg(opt); + add_cpp_arg(optarg); + break; + default: + usage(name); + } + + if (one && batch != batch_ps && batch != batch_ps_fullpage && + batch != batch_gnuplot) + usage(name); + if (postscript_params.show_key && batch != batch_ps_fullpage) + usage(name); + + if (!batch) { + args[0] = name; + args[1] = NULL; + fake_argc = 1; + fake_argv = args; + error = gui_init(&fake_argc, &fake_argv); + if (error) + return error; + } + + switch (argc-optind) { + case 0: + scan_empty(); + (void) yyparse(); + break; + case 1: + load_file(argv[optind]); + save_file_name = argv[optind]; + break; + case 2: + load_file(argv[optind]); + save_file_name = argv[optind+1]; + if (!strcmp(save_file_name, "-")) + save_file_name = NULL; + break; + default: + usage(name); + } + + if (!pkg_name) + pkg_name = stralloc("_"); + + reporter = report_to_stderr; + if (!instantiate()) + return 1; + + switch (batch) { + case batch_none: + error = gui_main(); + if (error) + return error; + break; + case batch_kicad: + write_kicad(); + break; + case batch_ps: + write_ps(one); + break; + case batch_ps_fullpage: + write_ps_fullpage(one); + break; + case batch_gnuplot: + write_gnuplot(one); + break; + case batch_test: + if (test_mode > 1) + dump(stdout, NULL); + break; + default: + abort(); + } + + purge(); + inst_revert(); + obj_cleanup(); + unique_cleanup(); + + return 0; +} @@ -0,0 +1,23 @@ +/* + * fped.h - Things fped.c exports + * + * Written 2010, 2012 by Werner Almesberger + * Copyright 2010, 2012 by Werner Almesberger + * + * 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. + */ + + +#ifndef FPED_H +#define FPED_H + +extern char *save_file_name; +extern int no_save; + + +void reload(void); + +#endif /* !FPED_H */ diff --git a/gnuplot.c b/gnuplot.c new file mode 100644 index 0000000..3b9397c --- /dev/null +++ b/gnuplot.c @@ -0,0 +1,195 @@ +/* + * gnuplot.c - Dump objects in gnuplot 2D format + * + * Written 2011 by Werner Almesberger + * Copyright 2011 by Werner Almesberger + * + * 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. + */ + + +#include <stdio.h> +#include <string.h> + +#include "coord.h" +#include "inst.h" +#include "gnuplot.h" + + +#define ARC_STEP 0.1 /* @@@ make configurable */ + + +static void recurse_id(FILE *file, const struct inst *inst) +{ + if (inst->obj->frame->name) { + recurse_id(file, inst->outer); + fprintf(file, "/%s", inst->obj->frame->name); + } +} + + +static void identify(FILE *file, const struct inst *inst) +{ + fprintf(file, "#%%id="); + recurse_id(file, inst); + fprintf(file, "\n"); +} + + +static void gnuplot_line(FILE *file, const struct inst *inst) +{ + double xa, ya, xb, yb; + + xa = units_to_mm(inst->base.x); + ya = units_to_mm(inst->base.y); + xb = units_to_mm(inst->u.rect.end.x); + yb = units_to_mm(inst->u.rect.end.y); + + identify(file, inst); + fprintf(file, "#%%r=%f\n%f %f\n%f %f\n\n", + units_to_mm(inst->u.rect.width), xa, ya, xb, yb); +} + + +static void gnuplot_rect(FILE *file, const struct inst *inst) +{ + double xa, ya, xb, yb; + + xa = units_to_mm(inst->base.x); + ya = units_to_mm(inst->base.y); + xb = units_to_mm(inst->u.rect.end.x); + yb = units_to_mm(inst->u.rect.end.y); + + identify(file, inst); + fprintf(file, "#%%r=%f\n", units_to_mm(inst->u.rect.width)); + fprintf(file, "%f %f\n", xa, ya); + fprintf(file, "%f %f\n", xa, yb); + fprintf(file, "%f %f\n", xb, yb); + fprintf(file, "%f %f\n", xb, ya); + fprintf(file, "%f %f\n\n", xa, ya); +} + + +static void gnuplot_circ(FILE *file, const struct inst *inst) +{ + double cx, cy, r; + double a; + int n, i; + + cx = units_to_mm(inst->base.x); + cy = units_to_mm(inst->base.y); + r = units_to_mm(inst->u.arc.r); + + identify(file, inst); + fprintf(file, "#%%r=%f\n", units_to_mm(inst->u.arc.width)); + + n = ceil(2*r*M_PI/ARC_STEP); + if (n < 2) + n = 2; + + for (i = 0; i <= n; i++) { + a = 2*M_PI/n*i; + fprintf(file, "%f %f\n", cx+r*sin(a), cy+r*cos(a)); + } + fprintf(file, "\n"); +} + + +static void gnuplot_arc(FILE *file, const struct inst *inst) +{ + double cx, cy, r; + double a, tmp; + int n, i; + + cx = units_to_mm(inst->base.x); + cy = units_to_mm(inst->base.y); + r = units_to_mm(inst->u.arc.r); + + a = inst->u.arc.a2-inst->u.arc.a1; + while (a <= 0) + a += 360; + while (a > 360) + a =- 360; + + n = ceil(2*r*M_PI/360*a/ARC_STEP); + if (n < 2) + n = 2; + + for (i = 0; i <= n; i++) { + tmp = (inst->u.arc.a1+a/n*i)*M_PI/180; + fprintf(file, "%f %f\n", cx+r*cos(tmp), cy+r*sin(tmp)); + } + + fprintf(file, "\n"); +} + + +static void gnuplot_inst(FILE *file, enum inst_prio prio, + const struct inst *inst) +{ + switch (prio) { + case ip_pad_copper: + case ip_pad_special: + /* complain ? */ + break; + case ip_hole: + /* complain ? */ + break; + case ip_line: + gnuplot_line(file, inst); + break; + case ip_rect: + gnuplot_rect(file, inst); + break; + case ip_circ: + gnuplot_circ(file, inst); + break; + case ip_arc: + gnuplot_arc(file, inst); + break; + default: + /* + * Don't try to export vectors, frame references, or + * measurements. + */ + break; + } +} + + +static void gnuplot_package(FILE *file, const struct pkg *pkg) +{ + enum inst_prio prio; + const struct inst *inst; + + /* + * Package name + */ + fprintf(file, "# %s\n", pkg->name); + + FOR_INST_PRIOS_UP(prio) { + for (inst = pkgs->insts[prio]; inst; inst = inst->next) + gnuplot_inst(file, prio, inst); + for (inst = pkg->insts[prio]; inst; inst = inst->next) + gnuplot_inst(file, prio, inst); + } + + fprintf(file, "\n"); +} + + +int gnuplot(FILE *file, const char *one) +{ + const struct pkg *pkg; + + for (pkg = pkgs; pkg; pkg = pkg->next) + if (pkg->name) + if (!one || !strcmp(pkg->name, one)) + gnuplot_package(file, pkg); + + fflush(file); + return !ferror(file); +} diff --git a/gnuplot.h b/gnuplot.h new file mode 100644 index 0000000..f56f2b8 --- /dev/null +++ b/gnuplot.h @@ -0,0 +1,22 @@ +/* + * gnuplot.h - Dump objects in gnuplot 2D format + * + * Written 2011 by Werner Almesberger + * Copyright 2011 by Werner Almesberger + * + * 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. + */ + + +#ifndef GNUPLOT_H +#define GNUPLOT_H + +#include <stdio.h> + + +int gnuplot(FILE *file, const char *one); + +#endif /* !GNUPLOT_H */ @@ -0,0 +1,430 @@ +/* + * gui.c - Editor GUI core + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <locale.h> +#include <gtk/gtk.h> + +#include "inst.h" +#include "file.h" +#include "gui_util.h" +#include "gui_style.h" +#include "gui_status.h" +#include "gui_canvas.h" +#include "gui_tool.h" +#include "gui_frame.h" +#include "gui.h" +#include "fped.h" + +#include "icons/stuff.xpm" +#include "icons/stuff_off.xpm" +#include "icons/meas.xpm" +#include "icons/meas_off.xpm" +#include "icons/all.xpm" +#include "icons/all_off.xpm" +#include "icons/bright.xpm" +#include "icons/bright_off.xpm" + + +GtkWidget *root; +int show_all = 1; +int show_stuff = 1; +int show_meas = 1; +int show_bright = 0; + + +static GtkWidget *paned; +static GtkWidget *frames_box; +static GtkWidget *ev_stuff, *ev_meas, *ev_all, *ev_bright; +static GtkWidget *stuff_image[2], *meas_image[2], *all_image[2]; +static GtkWidget *bright_image[2]; + +static void do_build_frames(void); + + +/* ----- save callbacks ---------------------------------------------------- */ + + +static void save_as_fpd(void) +{ + GtkWidget *dialog; + + dialog = gtk_file_chooser_dialog_new("Save File", + NULL, GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); + gtk_file_chooser_set_do_overwrite_confirmation( + GTK_FILE_CHOOSER(dialog), TRUE); + if (save_file_name) + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), + save_file_name); + if (gtk_dialog_run(GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { + save_file_name = + gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + save_fpd(); + /* @@@ we may leak save_file_name */ + no_save = 0; + } + gtk_widget_destroy(dialog); +} + + +/* ----- view callbacks ---------------------------------------------------- */ + + +static void swap_var_code(void) +{ + extern int show_vars; + + show_vars = !show_vars; + change_world(); +} + + +/* ----- menu bar ---------------------------------------------------------- */ + + +static GtkItemFactoryEntry menu_entries[] = { + { "/File", NULL, NULL, 0, "<Branch>" }, + { "/File/Save", NULL, save_fpd, 0, "<Item>" }, + { "/File/Save as", NULL, save_as_fpd, 0, "<Item>" }, + { "/File/sep1", NULL, NULL, 0, "<Separator>" }, + { "/File/Write KiCad", NULL, write_kicad, 0, "<Item>" }, + { "/File/Write Postscript", + NULL, write_ps, 0, "<Item>" }, + { "/File/sep2", NULL, NULL, 0, "<Separator>" }, + { "/File/Reload", NULL, reload, 0, "<Item>" }, + { "/File/sep3", NULL, NULL, 0, "<Separator>" }, + { "/File/Quit", NULL, gtk_main_quit, 0, "<Item>" }, + { "/View", NULL, NULL, 0, "<Branch>" }, + { "/View/Zoom in", NULL, zoom_in_center, 0, "<Item>" }, + { "/View/Zoom out", NULL, zoom_out_center,0, "<Item>" }, + { "/View/Zoom all", NULL, zoom_to_extents,0, "<Item>" }, + { "/View/Zoom frame", NULL, zoom_to_frame, 0, "<Item>" }, + { "/View/sep1", NULL, NULL, 0, "<Separator>" }, + { "/View/Swap var&code",NULL, swap_var_code, 0, "<Item>" }, +}; + + +static void make_menu_bar(GtkWidget *hbox) +{ + GtkItemFactory *factory; + GtkWidget *bar; + + factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<FpedMenu>", NULL); + gtk_item_factory_create_items(factory, + sizeof(menu_entries)/sizeof(*menu_entries), menu_entries, NULL); + + bar = gtk_item_factory_get_widget(factory, "<FpedMenu>"); + gtk_box_pack_start(GTK_BOX(hbox), bar, TRUE, TRUE, 0); + + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory, "/File/Save"), !no_save); + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory, "/File/Reload"), + no_save && !!save_file_name); +} + + +static gboolean toggle_all(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + switch (event->button) { + case 1: + show_all = !show_all; + set_image(ev_all, all_image[show_all]); + inst_deselect(); + redraw(); + break; + } + return TRUE; +} + + +static gboolean toggle_stuff(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + switch (event->button) { + case 1: + show_stuff = !show_stuff; + set_image(ev_stuff, stuff_image[show_stuff]); + inst_deselect(); + redraw(); + break; + } + return TRUE; +} + + +static gboolean toggle_meas(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + switch (event->button) { + case 1: + show_meas = !show_meas; + set_image(ev_meas, meas_image[show_meas]); + inst_deselect(); + redraw(); + break; + } + return TRUE; +} + + +static gboolean toggle_bright(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + switch (event->button) { + case 1: + show_bright = !show_bright; + set_image(ev_bright, bright_image[show_bright]); + inst_deselect(); + redraw(); + break; + } + return TRUE; +} + + +static void make_tool_bar(GtkWidget *hbox, GdkDrawable *drawable) +{ + GtkWidget *bar; + + bar = gtk_toolbar_new(); + gtk_box_pack_end(GTK_BOX(hbox), bar, TRUE, TRUE, 0); + //gtk_box_pack_end(GTK_BOX(hbox), bar, FALSE, FALSE, 0); + gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_ICONS); + + ev_all = tool_button(bar, drawable, NULL, NULL, toggle_all, NULL); + ev_stuff = tool_button(bar, drawable, NULL, NULL, toggle_stuff, NULL); + ev_meas = tool_button(bar, drawable, NULL, NULL, toggle_meas, NULL); + ev_bright = tool_button(bar, drawable, NULL, NULL, toggle_bright, NULL); + + stuff_image[0] = gtk_widget_ref(make_image(drawable, xpm_stuff_off, + "Show vectors and frame references (disabled)")); + stuff_image[1] = gtk_widget_ref(make_image(drawable, xpm_stuff, + "Show vectors and frame references (enabled)")); + meas_image[0] = gtk_widget_ref(make_image(drawable, xpm_meas_off, + "Show measurements (disabled)")); + meas_image[1] = gtk_widget_ref(make_image(drawable, xpm_meas, + "Show measurements (enabled)")); + all_image[0] = gtk_widget_ref(make_image(drawable, xpm_all_off, + "Show all frames (currently showing only the active frame)")); + all_image[1] = gtk_widget_ref(make_image(drawable, xpm_all, + "Show all frames (enabled)")); + bright_image[0] = gtk_widget_ref(make_image(drawable, xpm_bright_off, + "Highlight elements (disabled)")); + bright_image[1] = gtk_widget_ref(make_image(drawable, xpm_bright, + "Highlight elements (enabled)")); + + set_image(ev_stuff, stuff_image[show_stuff]); + set_image(ev_meas, meas_image[show_meas]); + set_image(ev_all, all_image[show_all]); + set_image(ev_bright, bright_image[show_bright]); +} + + +static void cleanup_tool_bar(void) +{ + g_object_unref(stuff_image[0]); + g_object_unref(stuff_image[1]); + g_object_unref(meas_image[0]); + g_object_unref(meas_image[1]); + g_object_unref(all_image[0]); + g_object_unref(all_image[1]); + g_object_unref(bright_image[0]); + g_object_unref(bright_image[1]); +} + + +static void make_top_bar(GtkWidget *vbox) +{ + GtkWidget *hbox; + + hbox = gtk_hbox_new(FALSE, 0); + make_menu_bar(hbox); + make_tool_bar(hbox, root->window); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); +} + + +/* ----- central screen area ----------------------------------------------- */ + + +static void resize_frames_area(GtkWidget *widget, GtkAllocation *allocation, + gpointer user_data) +{ + static int width = 0; + + if (allocation->width == width) + return; + width = allocation->width; + do_build_frames(); +} + + +static void make_center_area(GtkWidget *vbox) +{ + GtkWidget *hbox, *frames_area;//, *paned; + GtkWidget *tools; + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); + + paned = gtk_hpaned_new(); + gtk_box_pack_start(GTK_BOX(hbox), paned, TRUE, TRUE, 0); + + /* Frames */ + + frames_area = gtk_scrolled_window_new(NULL, NULL); + gtk_paned_add1(GTK_PANED(paned), frames_area); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(frames_area), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_set_size_request(frames_area, + DEFAULT_FRAME_AREA_WIDTH, DEFAULT_FRAME_AREA_HEIGHT); + + frames_box = gtk_vbox_new(FALSE, 0); + build_frames(frames_box, DEFAULT_FRAME_AREA_WIDTH); + + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(frames_area), + frames_box); + + g_signal_connect(G_OBJECT(frames_area), "size-allocate", + G_CALLBACK(resize_frames_area), NULL); + + /* Canvas */ + + gtk_paned_add2(GTK_PANED(paned), make_canvas()); + + /* Icon bar */ + + tools = gui_setup_tools(root->window); + gtk_box_pack_end(GTK_BOX(hbox), tools, FALSE, FALSE, 0); +} + + +/* ----- GUI construction -------------------------------------------------- */ + + +static void do_build_frames(void) +{ + int width; + + width = gtk_paned_get_position(GTK_PANED(paned)); + build_frames(frames_box, width > 0 ? width : DEFAULT_FRAME_AREA_WIDTH); +} + + +void change_world(void) +{ + struct bbox before, after; + int reachable_is_active; + + inst_deselect(); + status_begin_reporting(); + before = inst_get_bbox(NULL); + reachable_is_active = reachable_pkg && reachable_pkg == active_pkg; + instantiate(); + if (reachable_is_active && reachable_pkg && + reachable_pkg != active_pkg) { + active_pkg = reachable_pkg; + instantiate(); + } + after = inst_get_bbox(NULL); + label_in_box_bg(active_frame->label, COLOR_FRAME_SELECTED); + do_build_frames(); + if (after.min.x < before.min.x || after.min.y < before.min.y || + after.max.x > before.max.x || after.max.y > before.max.y) + zoom_to_extents(); + else + redraw(); +} + + +void change_world_reselect(void) +{ + struct obj *selected_obj; + + /* + * We can edit an object even if it's not selected if it was picked + * via the item view. inst_select_obj tries to find an instance, but + * if there's never been a successful instantiation since creation of + * the object or if the object is unreachable for some other reason, + * then selected_inst will be NULL. + */ + if (!selected_inst) { + change_world(); + return; + } + selected_obj = selected_inst->obj; + change_world(); + inst_select_obj(selected_obj); +} + + +static void make_screen(GtkWidget *window) +{ + GtkWidget *vbox; + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + + make_top_bar(vbox); + make_center_area(vbox); + make_status_area(vbox); +} + + +int gui_init(int *argc, char ***argv) +{ + gtk_init(argc, argv); + setlocale(LC_ALL, "C"); /* damage control */ + return 0; +} + + +int gui_main(void) +{ + root = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_position(GTK_WINDOW(root), GTK_WIN_POS_CENTER); + gtk_window_set_default_size(GTK_WINDOW(root), 620, 460); + if (*VERSION) + gtk_window_set_title(GTK_WINDOW(root), + "fped (rev " VERSION ")"); + else + gtk_window_set_title(GTK_WINDOW(root), "fped"); + + /* get root->window */ + gtk_widget_show_all(root); + + g_signal_connect(G_OBJECT(root), "destroy", + G_CALLBACK(gtk_main_quit), NULL); + + make_screen(root); + + gtk_widget_show_all(root); + + gui_setup_style(root->window); + init_canvas(); + edit_nothing(); + select_frame(frames); + make_popups(); + + gtk_main(); + + gui_cleanup_style(); + gui_cleanup_tools(); + cleanup_tool_bar(); + cleanup_status_area(); + + return 0; +} @@ -0,0 +1,38 @@ +/* + * gui.h - Editor GUI core + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_H +#define GUI_H + +#include <gtk/gtk.h> + + +extern GtkWidget *root; +extern int show_all; +extern int show_stuff; +extern int show_meas; +extern int show_bright; + +extern int no_save; + + +/* update everything after a model change */ +void change_world(void); + +/* like change_world, but select the object again */ +void change_world_reselect(void); + +int gui_init(int *argc, char ***argv); +int gui_main(void); + +#endif /* !GUI_H */ diff --git a/gui.html b/gui.html new file mode 100644 index 0000000..8f5207a --- /dev/null +++ b/gui.html @@ -0,0 +1,282 @@ +<HTML> +<HEAD> +<TITLE>Fped GUI Manual</TITLE> +</HEAD> +<BODY> +<H1>Fped GUI Manual</H1> + +This manual introduces the basic concepts of Fped and explains the elements +of the graphical user interface. Please refer to the file +<A href="README">README</A> for more +technical details and a discussion of the scripting language used by fped. + + +<H1>Objects and instances</H1> + +Footprints tend to be highly repetitive, with many pads placed in a +simple geometrical pattern. With fped, the user specifies the elements +to repeat and the way they are repeated. Fped then generates the +repetitions automatically. + +<H2>Hands-on example</H2> +<P> +Here is a simple example that illustrated the basic steps of constructing +things with fled: +<UL> + <LI> Start fped without a command-line argument. + <LI> Right-click on the yellow field that says "(root)" and select + "Add loop". An entry saying "_ = 0 ... 0 (0)" appears next to the + yellow field. + <P><IMG src="manual/intro-1.png"> + <LI> Click on the underscore, type <B>n=1,5</B> and press Enter. + The entry should now show "n = 1 ... 5 (1 2 3 4 5)" + <P><IMG src="manual/intro-2.png"> + <LI> Click on the dark-yellow vector icon on the right-hand side. + A red frame shows that it is selected. + <LI> Move the mouse pointer to the green dot in the middle of the + black canvas. A red circle appears when the pointer is over the + dot. + <LI> Press the left mouse button, drag a little to the right, and + release the mouse button. A white line appears and changes + to dark yellow after the button is released. + <LI> Click on the yellow line. It is now shown in bright yellow and + a number of text entry fields appear below the canvas. + <P><IMG src="manual/intro-3.png"> + <LI> Click into the field on the top that probably says "0.1mm", + change it to <B>n*1mm</B> and press Enter. + <LI> Select "Zoom all" from the "View" drop-down menu. The canvas + should now show the green dot on the left, with a yellow arrow + pointing to the right, and four more even darker arrows following + that arrow. + <P><IMG src="manual/intro-4.png"> + <LI> Click on the icon depicting a light-blue circle. + <LI> Move the mouse pointer over the green dot, then drag to the + circle at the end of the vector, and release the mouse button. + A series of partial circles should appear. + <P><IMG src="manual/intro-5.png"> + <LI> Select "Zoom all" again to show the full circles. + <P><IMG src="manual/intro-6.png"> +</UL> +The graphical items you have entered are a vector and a circle with the +radius determined by the vector. We call these items "objects". Furthermore, +you have defined a variable that gets set to the values from 1 to 5, in +increments of one. Fped had repeatedly drawn the objects for each such +value. We call the item that have been drawn "instances". +<P> +The innermost vector and circle are highlighted. You can highlight other +instances of the same objects by clicking on the numbers (1 2 3 4 5) shown +next to the loop. + +<H2>Conceptual view</H2> +The following picture illustrates the concept: the model is defined in +terms of objects, in this case a vector from the origin to the circle's +center, a vector for the radius, and the circle itself. +<P> +<IMG src="manual/concept-inst.png"> +<P> +The vector to the center uses a variable that gets iterated through the +values 0, 1, and 2. For each iteration, an instance is generated. +<P> +Only the instances of silk screen objects and pads are exported to KiCad. +Elements used for construction, such as vectors, only appear in fped. + + +<H1>Frames</H1> + +Frames serve various purposes: +<UL> + <LI> To structure the footprint drawing by grouping like elements. + For example, one may want to place pads, outline, and the keep-out + area in different frames, and probably subdivide some of those + constructs even further. + <LI> To define an element that is used in several places. For example, + a pad. + <LI> To define a repetition through a loop or a table. + <LI> To set variables for child frames. +</UL> + +At the bottom of the hierarchy, we have the root frame. To add another +frame, right-click on the root frame's label "(root)" and select "Add +frame". +<P> +To be able to put items into the new frame, it has to be attached to +the root frame (or to any other frame that's attached). This is called a +<I>frame reference</I>. First, we need a place to attach it to. This +can be the origin of its parent frame or it can be the end of a vector +in the parent frame. To create the frame reference, do this: +<P> +<UL> + <LI> Click on the parent frame to select it. + <LI> Press the left mouse button on the frame you wish to reference + and drag it (move the mouse pointer while keeping the left button + pressed) into the canvas. When dragging, the mouse cursor changes + to show a hand. + <LI> When the mouse pointer is above a suitable point of attachment, + the point of attachment is highlighted with a red circle and the + mouse cursor changes to show a hand with a plus sign. + <LI> At the desired location, release the mouse button. +</UL> + +If you wish to cancel the operation, simply release the mouse button at +any place that isn't a point of attachment. + + +<H1>Variables</H1> + + +<H1>Iconography</H1> + +The right-hand side of the fped window shows the component being drawn on a +black background. We call this the canvas. It is surrounded by a toolbar on +the right side and a few buttons with visibility options at the top. + + +<H2>The canvas</H2> + + +<H2>Blue screen</H2> + +When an expression uses an unknown variable or evaluates to an incorrect +value (e.g., a bare number where a dimension is expected), the +instantiation fails. Fped indicates this by changing the background color +of the canvas from black to blue. The cause of the failure is explained +in the status bar at the bottom. +<P> +In this state, the canvas is no longer updated when making changes until +the problem has been resolved. The most common causes are a misspelt +variable name in an expression, the use of a number without unit where a +dimension is expected, or the removal of a variable that's still used +somewhere. +<P> +If the location of the error is not obvious, the list of objects can be +shown by selecting "Swap var&code" from the View menu. The object +in which the error occurred is shown in red. If the error occurred in a +loop variable, the variable name is shown in red. + + +<H2>Visibility options</H2> + +When working on a complex component, the number of elements shown can be +overwhelming. The visibility options help to quickly hide irrelevant +details and get one's bearings. They are located in the menu bar at the +top. +<DL> + <DT><IMG src="manual/all.png"> <IMG src="manual/all_off.png"> + <DD>Show all frames. If disabled, only show the currently active frame. + <DT><IMG src="manual/stuff.png"> <IMG src="manual/stuff_off.png"> + <DD>Show vectors and frames. + <DT><IMG src="manual/meas.png"> <IMG src="manual/meas_off.png"> + <DD>Show measurements. + <DT><IMG src="manual/bright.png"> <IMG src="manual/bright_off.png"> + <DD>Highlight the elements that will be exported to KiCad, i.e., + the pads and the silk screen drawings. To show the component + exactly as it will appear in KiCad, also turn off vectors, + frames, and measurements. +</DL> +The visibility options can be combined. + + +<H2>Tools</H2> + +Tools are used to add new elements and to manipulate existing ones. +<DL> + <DT><IMG src="manual/point.png"> + <DD> The pointer. This is the default tool. The pointer is used to + select items and do move points of the selected item. + <P> + Clicking on an item selects it. If items overlap, the one with the + highest priority is selected. The priority is based on how difficult + it usually is to select an item, with frame references having a low + priority, pads, circles, arcs, rectangles, measurements, and lines + having increasingly higher priorities. There are a few special cases: + <UL> + <LI> The circle at the end of a vector has the highest priority + while its line has the lowest priority. + <LI> To select a frame reference, click on the L-shaped upper left + corner. + <LI> To select a measurement, click on the line with the text, not + the (hypothetical) line connecting the points being measured. + </UL> + If multiple items are under the mouse pointer, repeatedly clicking + iterates through them. + <P> + To move points, select the item, then move the mouse pointer over + the point to move. A red circle will appear under the mouse pointer. + Then drag the point to its new location and release the mouse button. + <DT><IMG src="manual/delete.png"> <IMG src="manual/delete_off.png"> + <DD> Delete the currently selected item. Whenever an item is selected, + the delete icon lights up. Clicking the icon deletes the item. + To undelete the item, press <B>U</B>. + <DT><IMG src="manual/vec.png"> + <DD> Add a vector. To add a new vector, move the mouse pointer to the + new vector's starting point then drag towards the desired end point. + Vectors are normally specified via parameters. To enter the parameters, + click on the new vector. + <P> + Note that the starting point of the vector has to be in the same + frame as the vector being drawn. This limitation also applies to + points defining pads and silk-screen items. + <DT><IMG src="manual/pad.png"> <IMG src="manual/rpad.png"> + <DD> Add a pad. Pads are either rectangular or rounded. They are + defined by two points which are opposite corners of the rectangle + containing the pad. Move the mouse cursor to the first point, then + drag to the second point. The pad's name can be edited after selecting + the pad. + <DT><IMG src="manual/hole.png"> + <DD> Add a hole. There are two purposes for holes: + <UL> + <LI> Pins of through-hole components. In this case, the hole has to be + inside a pad. + <LI> Mechanical support. In this case, the hole has to be outside any + pads. + </UL> + The construction of holes is the same as for pads. + <DT><IMG src="manual/line.png"> <IMG src="manual/rect.png"> + <DD> Add a line or a rectangle. Similar to pads, lines and rectangles + are defined by two points. The width of the line can be edited after + selecting the line or rectangle. + <DT><IMG src="manual/circ.png"> + <DD> Add circle or arc. Circles are defined by their center end a + point at their radius. An arc has a third point, which defines the + angle at which the arc ends. If this third point is not located on + the radius, the arc ends where an imaginary line between the center + and the end point would intersect with the radius. + <P> + An arc is made by first drawing a circle with the radius point at + the location where the arc should start. Then click and hold the + radius point to drag the end point to the desired location. + <P> + To change the radius point of a circle, first drag the end point, + then drag the radius point itself to that same location. + <DT><IMG src="manual/meas.png"> <IMG src="manual/meas_x.png"> + <IMG src="manual/meas_y.png"> + <DD> Add a measurement. Measurements show the distance between points. + They can either measure diagonally or only horizontally or only vertically. + Unlike other items, measurements are not limited to points in the same + frame. Instead, they operate on the minimum, maximum, and next greater + coordinates of instances of objects. + <P> + A measurement is added as follows: + <UL> + <LI> Click on one of the three measurement icons to select the + measurement type. All possible endpoints are highlighted. + <LI> Drag from the desired starting point. Now all the endpoints + available for this starting point are highlighted. + <LI> Drag to the endpoint and release the mouse button. The measurement + will now appear as a double-headed arrow and text between the two + points (if this is a diagonal measurement) or extending vertically or + horizontally from one of the two points. + <LI> To move the measurement arrow away from the two points, select + the measurement and set an offset. + </UL> + Sometimes, the second point becomes unavailable after selecting the + first point. This means that the two points are not a minimum or maximum, + or a minimum and the next greater neighbour. In this case, just try + another pair of points measuring the same distance. +</DL> + + +<H1>Keyboard shortcuts</H1> + +</BODY> +</HTML> diff --git a/gui_canvas.c b/gui_canvas.c new file mode 100644 index 0000000..7b65e93 --- /dev/null +++ b/gui_canvas.c @@ -0,0 +1,589 @@ +/* + * gui_canvas.c - GUI, canvas + * + * Written 2009, 2010, 2012 by Werner Almesberger + * Copyright 2009, 2010, 2012 by Werner Almesberger + * + * 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. + */ + + +#include <math.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "obj.h" +#include "delete.h" +#include "inst.h" +#include "gui_util.h" +#include "gui_inst.h" +#include "gui_style.h" +#include "gui_status.h" +#include "gui_tool.h" +#include "gui.h" +#include "gui_frame_drag.h" +#include "gui_canvas.h" + + +#if 0 +#define DPRINTF(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__) +#else +#define DPRINTF(fmt, ...) +#endif + + +void (*highlight)(void) = NULL; + +static struct coord curr_pos; /* canvas coordinates ! */ +static struct coord user_origin = { 0, 0 }; + +static int dragging = 0; +static int drag_escaped = 0; /* 1 once we've made it out of the drag radius */ +static struct coord drag_start; +static struct inst *selected_before_drag; + /* instance selected before dragging. we use it to do the click-to-select + routine in case we later find out the drag was really just a click. */ + + +/* ----- status display ---------------------------------------------------- */ + + +static void update_zoom(void) +{ + status_set_zoom("Zoom factor", "x%d", draw_ctx.scale); +} + + +static void update_pos(struct coord pos) +{ + struct coord user; + unit_type diag; + + set_with_units(status_set_sys_x, "X ", pos.x, "Absolute X position"); + set_with_units(status_set_sys_y, "Y ", pos.y, "Absolute Y position"); + + user.x = pos.x-user_origin.x; + user.y = pos.y-user_origin.y; + set_with_units(status_set_user_x, "x ", user.x, + "User X position. Press SPACE to zero."); + set_with_units(status_set_user_y, "y ", user.y, + "User Y position. Press SPACE to zero."); + + if (!selected_inst) { + diag = hypot(user.x, user.y); + set_with_units(status_set_r, "r = ", diag, + "Distance from user origin"); + status_set_angle_xy("Angle from user origin", user); + } +} + + +void refresh_pos(void) +{ + update_pos(canvas_to_coord(curr_pos.x, curr_pos.y)); +} + + +/* ----- coordinate system ------------------------------------------------- */ + + +static void center(const struct bbox *this_bbox) +{ + struct bbox bbox; + + bbox = this_bbox ? *this_bbox : inst_get_bbox(NULL); + draw_ctx.center.x = (bbox.min.x+bbox.max.x)/2; + draw_ctx.center.y = (bbox.min.y+bbox.max.y)/2; +} + + +static void auto_scale(const struct bbox *this_bbox) +{ + struct bbox bbox; + unit_type h, w; + int sx, sy; + float aw, ah; + + bbox = this_bbox ? *this_bbox : inst_get_bbox(NULL); + aw = draw_ctx.widget->allocation.width; + ah = draw_ctx.widget->allocation.height; + h = bbox.max.x-bbox.min.x; + w = bbox.max.y-bbox.min.y; + aw -= 2*CANVAS_CLEARANCE; + ah -= 2*CANVAS_CLEARANCE; + if (aw < 1) + aw = 1; + if (ah < 1) + ah = 1; + sx = ceil(h/aw); + sy = ceil(w/ah); + draw_ctx.scale = sx > sy ? sx : sy > 0 ? sy : 1; + + update_zoom(); +} + + +/* ----- drawing ----------------------------------------------------------- */ + + +void redraw(void) +{ + float aw, ah; + + aw = draw_ctx.widget->allocation.width; + ah = draw_ctx.widget->allocation.height; + gdk_draw_rectangle(draw_ctx.widget->window, + instantiation_error ? gc_bg_error : gc_bg, TRUE, 0, 0, aw, ah); + + DPRINTF("--- redraw: inst_draw ---"); + inst_draw(); + if (highlight) + highlight(); + DPRINTF("--- redraw: tool_redraw ---"); + tool_redraw(); + DPRINTF("--- redraw: done ---"); +} + + +/* ----- drag -------------------------------------------------------------- */ + + +static void drag_left(struct coord pos) +{ + if (!dragging) + return; + if (!drag_escaped && + hypot(pos.x-drag_start.x, pos.y-drag_start.y)/draw_ctx.scale < + DRAG_MIN_R) + return; + drag_escaped = 1; + tool_drag(pos); +} + + +static void drag_middle(struct coord pos) +{ +} + + +static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event, + gpointer data) +{ + struct coord pos = canvas_to_coord(event->x, event->y); + + DPRINTF("--- motion ---"); + curr_pos.x = event->x; + curr_pos.y = event->y; + tool_hover(pos); + if (event->state & GDK_BUTTON1_MASK) + drag_left(pos); + if (event->state & GDK_BUTTON2_MASK) + drag_middle(pos); + update_pos(pos); + return FALSE; +} + + +/* ----- drag and drop (frame to canvas) ----------------------------------- */ + + +void canvas_frame_begin(struct frame *frame) +{ + inst_deselect(); /* don't drag away bits of the selected object */ + redraw(); + tool_push_frame(frame); +} + + +int canvas_frame_motion(struct frame *frame, int x, int y) +{ + struct coord pos = canvas_to_coord(x, y); + + return tool_hover(pos); +} + + +void canvas_frame_end(void) +{ + tool_dehover(); + tool_pop_frame(); +} + + +int canvas_frame_drop(struct frame *frame, int x, int y) +{ + struct coord pos = canvas_to_coord(x, y); + + if (!tool_place_frame(frame, pos)) + return FALSE; + change_world(); + return TRUE; +} + + +/* ----- button press and release ------------------------------------------ */ + + +static void click_to_select(struct coord pos) +{ + const struct inst *prev; + + tool_reset(); + prev = selected_inst; + inst_select(pos); + if (prev != selected_inst) + redraw(); +} + + +static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + struct coord pos = canvas_to_coord(event->x, event->y); + int res; + + DPRINTF("--- button press ---"); + gtk_widget_grab_focus(widget); + switch (event->button) { + case 1: + if (dragging) { + fprintf(stderr, "HUH ?!?\n"); + tool_cancel_drag(); + dragging = 0; + } + res = tool_consider_drag(pos); + /* tool doesn't do drag */ + if (res < 0) { + change_world(); + inst_deselect(); + break; + } + if (res) { + selected_before_drag = selected_inst; + inst_deselect(); + redraw(); + dragging = 1; + drag_escaped = 0; + drag_start = pos; + break; + } + click_to_select(pos); + break; + case 2: + tool_dehover(); + draw_ctx.center = pos; + redraw(); + tool_hover(canvas_to_coord(event->x, event->y)); + break; + } + return TRUE; +} + + +static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + struct coord pos = canvas_to_coord(event->x, event->y); + + DPRINTF("--- button release ---"); + switch (event->button) { + case 1: + if (is_dragging_anything()) + return FALSE; + if (!dragging) + break; + drag_left(pos); + dragging = 0; + if (!drag_escaped) { + tool_cancel_drag(); + selected_inst = selected_before_drag; + click_to_select(pos); + break; + } + if (tool_end_drag(pos)) + change_world(); + break; + } + return TRUE; +} + + +/* ----- zoom control ------------------------------------------------------ */ + + +static void zoom_in(struct coord pos) +{ + if (draw_ctx.scale < 2) + return; + tool_dehover(); + draw_ctx.scale /= 2; + draw_ctx.center.x = (draw_ctx.center.x+pos.x)/2; + draw_ctx.center.y = (draw_ctx.center.y+pos.y)/2; + update_zoom(); + redraw(); + tool_hover(pos); +} + + +static void zoom_out(struct coord pos) +{ + struct bbox bbox; + + bbox = inst_get_bbox(NULL); + bbox.min = translate(bbox.min); + bbox.max = translate(bbox.max); + if (bbox.min.x >= ZOOM_STOP_BORDER && + bbox.max.y >= ZOOM_STOP_BORDER && + bbox.max.x < draw_ctx.widget->allocation.width-ZOOM_STOP_BORDER && + bbox.min.y < draw_ctx.widget->allocation.height-ZOOM_STOP_BORDER) + return; + tool_dehover(); + draw_ctx.scale *= 2; + draw_ctx.center.x = 2*draw_ctx.center.x-pos.x; + draw_ctx.center.y = 2*draw_ctx.center.y-pos.y; + update_zoom(); + redraw(); + tool_hover(pos); +} + + +void zoom_in_center(void) +{ + zoom_in(draw_ctx.center); +} + + +void zoom_out_center(void) +{ + zoom_out(draw_ctx.center); +} + + +void zoom_to_frame(void) +{ + tool_dehover(); + center(&active_frame_bbox); + auto_scale(&active_frame_bbox); + redraw(); + tool_hover(canvas_to_coord(curr_pos.x, curr_pos.y)); +} + + +void zoom_to_extents(void) +{ + tool_dehover(); + center(NULL); + auto_scale(NULL); + redraw(); + tool_hover(canvas_to_coord(curr_pos.x, curr_pos.y)); +} + + +static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, + gpointer data) +{ + struct coord pos = canvas_to_coord(event->x, event->y); + + gtk_widget_grab_focus(widget); + switch (event->direction) { + case GDK_SCROLL_UP: + zoom_in(pos); + break; + case GDK_SCROLL_DOWN: + zoom_out(pos); + break; + default: + /* ignore */; + } + return TRUE; +} + + +/* ----- keys -------------------------------------------------------------- */ + + +static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + struct coord pos = canvas_to_coord(curr_pos.x, curr_pos.y); + + DPRINTF("--- key press ---"); + switch (event->keyval) { + case ' ': + user_origin = pos; + update_pos(pos); + break; + case '+': + case '=': + zoom_in(pos); + break; + case '-': + zoom_out(pos); + break; + case '*': + zoom_to_extents(); + break; + case '#': + zoom_to_frame(); + break; + case '.': + tool_dehover(); + draw_ctx.center = pos; + redraw(); + tool_hover(canvas_to_coord(curr_pos.x, curr_pos.y)); + break; + case GDK_BackSpace: + case GDK_Delete: +#if 0 + case GDK_KP_Delete: + if (selected_inst) { + inst_delete(selected_inst); + change_world(); + } + break; +#endif + case 'u': + if (undelete()) + change_world(); + break; + case '/': +{ +/* @@@ find a better place for this */ +extern int show_vars; + show_vars = !show_vars; +change_world(); +} + } + return TRUE; +} + + +/* ----- expose event ------------------------------------------------------ */ + + +static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, + gpointer data) +{ + static int first = 1; + + DPRINTF("--- expose ---"); + if (first) { + init_canvas(); + first = 0; + } + tool_dehover(); + redraw(); + return TRUE; +} + + +/* ----- enter/leave ------------------------------------------------------- */ + + +static gboolean enter_notify_event(GtkWidget *widget, GdkEventCrossing *event, + gpointer data) +{ + DPRINTF("--- enter ---"); + gtk_widget_grab_focus(widget); + return FALSE; +} + + +static gboolean leave_notify_event(GtkWidget *widget, GdkEventCrossing *event, + gpointer data) +{ + DPRINTF("--- leave ---"); + if (dragging) + tool_cancel_drag(); + tool_dehover(); + dragging = 0; + return FALSE; +} + + +/* ----- tooltip ----------------------------------------------------------- */ + + +static gboolean canvas_tooltip(GtkWidget *widget, gint x, gint y, + gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data) +{ + struct coord pos = canvas_to_coord(x, y); + const char *res; + + res = tool_tip(pos); + if (!res) + return FALSE; + gtk_tooltip_set_markup(tooltip, res); + return TRUE; +} + + +/* ----- canvas setup ------------------------------------------------------ */ + + +/* + * Note that we call init_canvas twice: first to make sure we'll make it safely + * through select_frame, and the second time to set the geometry for the actual + * screen. + */ + +void init_canvas(void) +{ + center(NULL); + auto_scale(NULL); +} + + +GtkWidget *make_canvas(void) +{ + GtkWidget *canvas; + GdkColor black = { 0, 0, 0, 0 }; + + /* Canvas */ + + canvas = gtk_drawing_area_new(); + gtk_widget_modify_bg(canvas, GTK_STATE_NORMAL, &black); + + g_signal_connect(G_OBJECT(canvas), "motion_notify_event", + G_CALLBACK(motion_notify_event), NULL); + g_signal_connect(G_OBJECT(canvas), "button_press_event", + G_CALLBACK(button_press_event), NULL); + g_signal_connect(G_OBJECT(canvas), "button_release_event", + G_CALLBACK(button_release_event), NULL); + g_signal_connect(G_OBJECT(canvas), "scroll_event", + G_CALLBACK(scroll_event), NULL); + + GTK_WIDGET_SET_FLAGS(canvas, GTK_CAN_FOCUS); + + g_signal_connect(G_OBJECT(canvas), "key_press_event", + G_CALLBACK(key_press_event), NULL); + + g_signal_connect(G_OBJECT(canvas), "expose_event", + G_CALLBACK(expose_event), NULL); + g_signal_connect(G_OBJECT(canvas), "enter_notify_event", + G_CALLBACK(enter_notify_event), NULL); + g_signal_connect(G_OBJECT(canvas), "leave_notify_event", + G_CALLBACK(leave_notify_event), NULL); + + gtk_widget_set(canvas, "has-tooltip", TRUE, NULL); + g_signal_connect(G_OBJECT(canvas), "query_tooltip", + G_CALLBACK(canvas_tooltip), NULL); + + gtk_widget_set_events(canvas, + GDK_EXPOSE | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | + GDK_KEY_PRESS_MASK | + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_SCROLL | + GDK_POINTER_MOTION_MASK); + + gtk_widget_set_double_buffered(canvas, FALSE); + + setup_canvas_drag(canvas); + + draw_ctx.widget = canvas; + + return canvas; +} diff --git a/gui_canvas.h b/gui_canvas.h new file mode 100644 index 0000000..93443c0 --- /dev/null +++ b/gui_canvas.h @@ -0,0 +1,45 @@ +/* + * gui_canvas.h - GUI, canvas + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_CANVAS_H +#define GUI_CANVAS_H + +#include <gtk/gtk.h> + + +/* + * "highlight" is invoked at the end of each redraw, for optional highlighting + * of objects. + */ + +extern void (*highlight)(void); + + +void refresh_pos(void); + +void redraw(void); + +void zoom_in_center(void); +void zoom_out_center(void); +void zoom_to_frame(void); +void zoom_to_extents(void); + +void canvas_frame_begin(struct frame *frame); +int canvas_frame_motion(struct frame *frame, int x, int y); +void canvas_frame_end(void); +int canvas_frame_drop(struct frame *frame, int x, int y); + +GtkWidget *make_canvas(void); +void init_canvas(void); + +#endif /* !GUI_CANVAS_H */ diff --git a/gui_frame.c b/gui_frame.c new file mode 100644 index 0000000..e52425d --- /dev/null +++ b/gui_frame.c @@ -0,0 +1,1875 @@ +/* + * gui_frame.c - GUI, frame window + * + * Written 2009, 2010, 2012 by Werner Almesberger + * Copyright 2009, 2010, 2012 by Werner Almesberger + * + * 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. + */ + + +#include <string.h> +#include <gtk/gtk.h> + +#include "util.h" +#include "error.h" +#include "dump.h" +#include "inst.h" +#include "obj.h" +#include "delete.h" +#include "unparse.h" +#include "gui_util.h" +#include "gui_style.h" +#include "gui_status.h" +#include "gui_tool.h" +#include "gui_canvas.h" +#include "gui.h" +#include "gui_frame_drag.h" +#include "gui_frame.h" + + +int show_vars = 1; + + +/* ----- add elements, shared ---------------------------------------------- */ + + +/* @@@ merge with fpd.y */ + +static void add_table(struct frame *frame, struct table **anchor) +{ + struct table *table, **walk; + + table = zalloc_type(struct table); + table->vars = zalloc_type(struct var); + table->vars->name = unique("_"); + table->vars->frame = frame; + table->vars->table = table; + table->rows = zalloc_type(struct row); + table->rows->table = table; + table->rows->values = zalloc_type(struct value); + table->rows->values->expr = parse_expr("0"); + table->rows->values->row = table->rows; + table->active_row = table->rows; + if (anchor) { + table->next = *anchor; + *anchor = table; + } else { + for (walk = &frame->tables; *walk; walk = &(*walk)->next); + *walk = table; + } + change_world(); +} + + +static void add_loop(struct frame *frame, struct loop **anchor) +{ + struct loop *loop, **walk; + + loop = zalloc_type(struct loop); + loop->var.name = unique("_"); + loop->var.frame = frame; + loop->from.expr = parse_expr("0"); + loop->to.expr = parse_expr("0"); + if (anchor) { + loop->next = *anchor; + *anchor = loop; + } else { + loop->next = NULL; + for (walk = &frame->loops; *walk; walk = &(*walk)->next); + *walk = loop; + } + change_world(); +} + + +/* ----- popup dispatcher -------------------------------------------------- */ + + +static void *popup_data; + + +static void pop_up(GtkWidget *menu, GdkEventButton *event, void *data) +{ + popup_data = data; + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, + event->button, event->time); +} + + +/* ----- popup: frame ------------------------------------------------------ */ + + +static GtkItemFactory *factory_frame; +static GtkWidget *popup_frame_widget; + + +static void popup_add_frame(void) +{ + struct frame *parent = popup_data; + struct frame *new; + + new = zalloc_type(struct frame); + new->name = unique("_"); + new->next = parent->next; + parent->next = new; + change_world(); +} + + +static void popup_del_frame(void) +{ + struct frame *frame = popup_data; + + assert(frame != frames); + delete_frame(frame); + if (active_frame == frame) + select_frame(frames); + change_world(); +} + + +static void popup_add_table(void) +{ + add_table(popup_data, NULL); +} + + +static void popup_add_loop(void) +{ + add_loop(popup_data, NULL); +} + + +static GtkItemFactoryEntry popup_frame_entries[] = { + { "/Add frame", NULL, popup_add_frame,0, "<Item>" }, + { "/sep0", NULL, NULL, 0, "<Separator>" }, + { "/Add variable", NULL, popup_add_table,0, "<Item>" }, + { "/Add loop", NULL, popup_add_loop, 0, "<Item>" }, + { "/sep1", NULL, NULL, 0, "<Separator>" }, + { "/Delete frame", NULL, popup_del_frame,0, "<Item>" }, + { "/sep2", NULL, NULL, 0, "<Separator>" }, + { "/Close", NULL, NULL, 0, "<Item>" }, + { NULL } +}; + + +static gboolean can_add_frame(void) +{ + const struct frame *frame; + + for (frame = frames->next; frame; frame = frame->next) + if (!strcmp(frame->name, "_")) + return FALSE; + return TRUE; +} + + +static gboolean can_add_var(const struct frame *frame) +{ + const struct table *table; + const struct var *var; + const struct loop *loop; + + for (table = frame->tables; table; table = table->next) + for (var = table->vars; var; var = var->next) + if (!strcmp(var->name, "_")) + return FALSE; + for (loop = frame->loops; loop; loop = loop->next) + if (!strcmp(loop->var.name, "_")) + return FALSE; + return TRUE; +} + + +static void enable_add_var(struct frame *frame, GtkItemFactory *factory) +{ + gboolean add_var; + + add_var = can_add_var(frame); + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory, "/Add variable"), add_var); + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory, "/Add loop"), add_var); +} + + +static void pop_up_frame(struct frame *frame, GdkEventButton *event) +{ + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory_frame, "/Delete frame"), + frame != frames); + + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory_frame, "/Add frame"), + can_add_frame()); + + enable_add_var(frame, factory_frame); + + pop_up(popup_frame_widget, event, frame); +} + + +/* ----- popup: single variable -------------------------------------------- */ + + +static GtkItemFactory *factory_single_var; +static GtkWidget *popup_single_var_widget; + + +static void add_row_here(struct table *table, struct row **anchor) +{ + struct row *row; + const struct value *walk; + struct value *value; + + row = zalloc_type(struct row); + row->table = table; + /* @@@ future: adjust type */ + for (walk = table->rows->values; walk; walk = walk->next) { + value = zalloc_type(struct value); + value->expr = parse_expr("0"); + value->row = row; + value->next = row->values; + row->values = value; + } + row->next = *anchor; + *anchor = row; + change_world(); +} + + +static void add_column_here(struct table *table, struct var **anchor) +{ + const struct var *walk; + struct var *var; + struct row *row; + struct value *value; + struct value **value_anchor; + int n = 0, i; + + for (walk = table->vars; walk != *anchor; walk = walk->next) + n++; + var = zalloc_type(struct var); + var->name = unique("_"); + var->frame = table->vars->frame; + var->table = table; + var->next = *anchor; + *anchor = var; + for (row = table->rows; row; row = row->next) { + value_anchor = &row->values; + for (i = 0; i != n; i++) + value_anchor = &(*value_anchor)->next; + value = zalloc_type(struct value); + value->expr = parse_expr("0"); + value->row = row; + value->next = *value_anchor; + *value_anchor = value; + } + change_world(); +} + + +static void popup_add_row(void) +{ + struct var *var = popup_data; + + add_row_here(var->table, &var->table->rows); +} + + +static void popup_add_column(void) +{ + struct var *var = popup_data; + + add_column_here(var->table, &var->next); +} + + +static void popup_del_table(void) +{ + struct var *var = popup_data; + + delete_table(var->table); + change_world(); +} + + +static void popup_add_table_from_var(void) +{ + struct var *var = popup_data; + + add_table(var->frame, &var->table->next); +} + + +static void popup_add_loop_from_var(void) +{ + struct var *var = popup_data; + + add_loop(var->frame, NULL); +} + + +static GtkItemFactoryEntry popup_single_var_entries[] = { + { "/Add row", NULL, popup_add_row, 0, "<Item>" }, + { "/Add column", NULL, popup_add_column, 0, "<Item>" }, + { "/sep1", NULL, NULL, 0, "<Separator>" }, + { "/Delete variable", NULL, popup_del_table,0, "<Item>" }, + { "/sep2", NULL, NULL, 0, "<Separator>" }, + { "/Add variable", NULL, popup_add_table_from_var, + 0, "<Item>" }, + { "/Add loop", NULL, popup_add_loop_from_var, + 0, "<Item>" }, + { "/sep3", NULL, NULL, 0, "<Separator>" }, + { "/Close", NULL, NULL, 0, "<Item>" }, + { NULL } +}; + + +static void pop_up_single_var(struct var *var, GdkEventButton *event) +{ + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory_single_var, "/Add column"), + can_add_var(var->frame)); + enable_add_var(var->frame, factory_single_var); + pop_up(popup_single_var_widget, event, var); +} + + +/* ----- popup: table variable --------------------------------------------- */ + + +static GtkItemFactory *factory_table_var; +static GtkWidget *popup_table_var_widget; + + +static void popup_del_column(void) +{ + struct var *var = popup_data; + const struct var *walk; + int n = 0; + + for (walk = var->table->vars; walk != var; walk = walk->next) + n++; + delete_column(var->table, n); + change_world(); +} + + +static GtkItemFactoryEntry popup_table_var_entries[] = { + { "/Add row", NULL, popup_add_row, 0, "<Item>" }, + { "/Add column", NULL, popup_add_column, 0, "<Item>" }, + { "/sep1", NULL, NULL, 0, "<Separator>" }, + { "/Delete table", NULL, popup_del_table,0, "<Item>" }, + { "/Delete column", NULL, popup_del_column, 0, "<Item>" }, + { "/sep2", NULL, NULL, 0, "<Separator>" }, + { "/Add variable", NULL, popup_add_table_from_var, + 0, "<Item>" }, + { "/Add loop", NULL, popup_add_loop_from_var, + 0, "<Item>" }, + { "/sep3", NULL, NULL, 0, "<Separator>" }, + { "/Close", NULL, NULL, 0, "<Item>" }, + { NULL } +}; + + +static void pop_up_table_var(struct var *var, GdkEventButton *event) +{ + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory_table_var, "/Delete column"), + var->table->vars->next != NULL); + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory_table_var, "/Add column"), + can_add_var(var->frame)); + enable_add_var(var->frame, factory_table_var); + pop_up(popup_table_var_widget, event, var); +} + + +/* ----- popup: table value ------------------------------------------------ */ + + +static GtkItemFactory *factory_table_value; +static GtkWidget *popup_table_value_widget; + + +static void popup_add_column_by_value(void) +{ + struct value *value = popup_data; + const struct value *walk; + struct table *table = value->row->table; + struct var *var = table->vars; + + for (walk = value->row->values; walk != value; walk = walk->next) + var = var->next; + add_column_here(table, &var->next); +} + + +static void popup_add_row_by_value(void) +{ + struct value *value = popup_data; + + add_row_here(value->row->table, &value->row->next); +} + + +static void popup_del_row(void) +{ + struct value *value = popup_data; + struct table *table = value->row->table; + + delete_row(value->row); + if (table->active_row == value->row) + table->active_row = table->rows; + change_world(); +} + + +static void popup_del_column_by_value(void) +{ + struct value *value = popup_data; + const struct value *walk; + int n = 0; + + for (walk = value->row->values; walk != value; walk = walk->next) + n++; + delete_column(value->row->table, n); + change_world(); +} + + +static GtkItemFactoryEntry popup_table_value_entries[] = { + { "/Add row", NULL, popup_add_row_by_value, 0, "<Item>" }, + { "/Add column", NULL, popup_add_column_by_value, + 0, "<Item>" }, + { "/sep1", NULL, NULL, 0, "<Separator>" }, + { "/Delete row", NULL, popup_del_row, 0, "<Item>" }, + { "/Delete column", NULL, popup_del_column_by_value, + 0, "<Item>" }, + { "/sep2", NULL, NULL, 0, "<Separator>" }, + { "/Close", NULL, NULL, 0, "<Item>" }, + { NULL } +}; + + +static void pop_up_table_value(struct value *value, GdkEventButton *event) +{ + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory_table_value, "/Delete row"), + value->row->table->rows->next != NULL); + gtk_widget_set_sensitive( + gtk_item_factory_get_item(factory_table_value, "/Delete column"), + value->row->table->vars->next != NULL); + pop_up(popup_table_value_widget, event, value); +} + + +/* ----- popup: loop ------------------------------------------------------- */ + + +static GtkItemFactory *factory_loop_var; +static GtkWidget *popup_loop_var_widget; + + +static void popup_del_loop(void) +{ + struct loop *loop = popup_data; + + delete_loop(loop); + change_world(); +} + + +static void popup_add_table_from_loop(void) +{ + struct loop *loop = popup_data; + + add_table(loop->var.frame, NULL); +} + + +static void popup_add_loop_from_loop(void) +{ + struct loop *loop = popup_data; + + add_loop(loop->var.frame, &loop->next); +} + + +static GtkItemFactoryEntry popup_loop_var_entries[] = { + { "/Delete loop", NULL, popup_del_loop, 0, "<Item>" }, + { "/sep1", NULL, NULL, 0, "<Separator>" }, + { "/Add variable", NULL, popup_add_table_from_loop, + 0, "<Item>" }, + { "/Add loop", NULL, popup_add_loop_from_loop, + 0, "<Item>" }, + { "/sep2", NULL, NULL, 0, "<Separator>" }, + { "/Close", NULL, NULL, 0, "<Item>" }, + { NULL } +}; + + +static void pop_up_loop_var(struct loop *loop, GdkEventButton *event) +{ + enable_add_var(loop->var.frame, factory_loop_var); + pop_up(popup_loop_var_widget, event, loop); +} + + +/* ----- make popups ------------------------------------------------------- */ + + +static GtkWidget *make_popup(const char *name, GtkItemFactory **factory, + GtkItemFactoryEntry *entries) +{ + GtkWidget *popup; + int n; + + n = 0; + for (n = 0; entries[n].path; n++); + + *factory = gtk_item_factory_new(GTK_TYPE_MENU, name, NULL); + gtk_item_factory_create_items(*factory, n, entries, NULL); + popup = gtk_item_factory_get_widget(*factory, name); + return popup; +} + + +void make_popups(void) +{ + popup_frame_widget = make_popup("<FpedFramePopUp>", + &factory_frame, popup_frame_entries); + popup_single_var_widget = make_popup("<FpedSingleVarPopUp>", + &factory_single_var, popup_single_var_entries); + popup_table_var_widget = make_popup("<FpedTableVarPopUp>", + &factory_table_var, popup_table_var_entries); + popup_table_value_widget = make_popup("<FpedTableValusPopUp>", + &factory_table_value, popup_table_value_entries); + popup_loop_var_widget = make_popup("<FpedLoopVarPopUp>", + &factory_loop_var, popup_loop_var_entries); +} + + +/* ----- variable list ----------------------------------------------------- */ + + +static void add_sep(GtkWidget *box, int size) +{ + GtkWidget *sep; + GdkColor black = { 0, 0, 0, 0 }; + + sep = gtk_drawing_area_new(); + gtk_box_pack_start(GTK_BOX(box), sep, FALSE, TRUE, size); + gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &black); +} + + +/* ----- variable name editor ---------------------------------------------- */ + + +static int find_var_in_frame(const struct frame *frame, const char *name, + const struct var *self) +{ + const struct table *table; + const struct loop *loop; + const struct var *var; + + for (table = frame->tables; table; table = table->next) + for (var = table->vars; var; var = var->next) + if (var != self && !var->key && + !strcmp(var->name, name)) + return 1; + for (loop = frame->loops; loop; loop = loop->next) + if (&loop->var != self && !strcmp(loop->var.name, name)) + return 1; + return 0; +} + + +static int validate_var_name(const char *s, void *ctx) +{ + struct var *var = ctx; + + if (!is_id(s)) + return 0; + if (var->key) + return 1; + return !find_var_in_frame(var->frame, s, var); +} + + +static void unselect_var(void *data) +{ + struct var *var = data; + + label_in_box_bg(var->widget, COLOR_VAR_PASSIVE); +} + + +static void show_value(const struct expr *expr, const struct frame *frame) +{ + const char *value_string; + struct num value; + + status_set_type_x(NULL, "value ="); + value_string = eval_str(expr, frame); + if (value_string) { + status_set_x(NULL, "\"%s\"", value_string); + } else { + value = eval_num(expr, frame); + if (is_undef(value)) + status_set_x(NULL, "undefined"); + else + status_set_x(NULL, "%lg%s", value.n, str_unit(value)); + } +} + + +static void show_var_value(const struct var *var, const struct frame *frame) +{ + const struct var *walk; + const struct value *value; + + if (!var->table) + return; + value = var->table->active_row->values; + for (walk = var->table->vars; walk != var; walk = walk->next) + value = value->next; + show_value(value->expr, frame); +} + + +static void edit_var(struct var *var, + void (*set_values)(void *user, const struct value *values, int n_values), + void *user, int max_values) +{ + inst_select_outside(var, unselect_var); + label_in_box_bg(var->widget, COLOR_VAR_EDITING); + status_set_type_entry(NULL, "name ="); + status_set_name("Variable name", "%s", var->name); + show_var_value(var, var->frame); + edit_nothing(); + edit_var_type(var); + edit_unique_with_values(&var->name, validate_var_name, var, + set_values, user, max_values, + "Variable name. " + "Shortcut:<b><i>name</i>=<i>value</i>,<i>...</i> </b>"); +} + + +static void set_col_values(void *user, const struct value *values, + int n_values); + + +void reselect_var(struct var *var) +{ + edit_var(var, set_col_values, var, -1); +} + + +/* ----- value editor ------------------------------------------------------ */ + + +static void unselect_value(void *data) +{ + struct value *value = data; + + /* + * This condition is a little cryptic. Here is what it does: + * + * IF table/assignment (not loop) + * AND the current row is the active (selected) row + * AND it's an assignment (not a table). + * + * We need the last condition because the expressions of assignments + * are drawn with COLOR_EXPR_PASSIVE. (See build_assignment.) + */ + label_in_box_bg(value->widget, + value->row && value->row->table->active_row == value->row && + (value->row->table->rows->next || value->row->table->vars->next) ? + COLOR_CHOICE_SELECTED : COLOR_EXPR_PASSIVE); +} + + +static void edit_value(struct value *value, const struct frame *frame) +{ + inst_select_outside(value, unselect_value); + label_in_box_bg(value->widget, COLOR_EXPR_EDITING); + show_value(value->expr, frame); + edit_nothing(); + edit_expr(&value->expr, "Value"); +} + + +static void edit_value_list(struct value *value, const struct frame *frame, + void (*set_values)(void *user, const struct value *values, int n_values), + void *user) +{ + inst_select_outside(value, unselect_value); + label_in_box_bg(value->widget, COLOR_EXPR_EDITING); + show_value(value->expr, frame); + edit_nothing(); + edit_expr_list(value->expr, set_values, user, "Value(s)"); +} + + +/* ----- activator --------------------------------------------------------- */ + + +static GtkWidget *add_activator(GtkWidget *hbox, int active, + gboolean (*cb)(GtkWidget *widget, GdkEventButton *event, gpointer data), + gpointer user, const char *tooltip, const char *fmt, ...) +{ + GtkWidget *label; + va_list ap; + char buf[100]; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + label = label_in_box_new(buf, tooltip); + gtk_misc_set_padding(GTK_MISC(label), 2, 2); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + label_in_box_bg(label, + active ? COLOR_CHOICE_SELECTED : COLOR_CHOICE_UNSELECTED); + gtk_box_pack_start(GTK_BOX(hbox), box_of_label(label), + FALSE, FALSE, 2); + g_signal_connect(G_OBJECT(box_of_label(label)), + "button_press_event", G_CALLBACK(cb), user); + return label; +} + + +/* ----- assignments ------------------------------------------------------- */ + + +static void set_col_values(void *user, const struct value *values, + int n_values) +{ + struct var *var = user; + struct table *table = var->table; + struct value *value; + const struct var *walk; + struct row **row; + + row = &table->rows; + while (values) { + if (!*row) + add_row_here(table, row); + value = (*row)->values; + for (walk = table->vars; walk != var; walk = walk->next) + value = value->next; + free_expr(value->expr); + value->expr = values->expr; + values = values->next; + row = &(*row)->next; + } +} + + +static gboolean assignment_var_select_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + struct var *var = data; + + switch (event->button) { + case 1: + edit_var(var, set_col_values, var, -1); + break; + case 3: + pop_up_single_var(var, event); + break; + } + return TRUE; +} + + +static gboolean assignment_value_select_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + struct value *value = data; + + switch (event->button) { + case 1: + edit_nothing(); + edit_value(value, value->row->table->vars->frame); + break; + } + return TRUE; +} + + +/* + * In tables, expressions in the active row have a COLOR_CHOICE_SELECTED + * background. While expressions in assignments are technically on the active + * (and only) row, we use COLOR_VAR_PASSIVE for better readability. + */ + +static void build_assignment(GtkWidget *vbox, struct frame *frame, + struct table *table) +{ + GtkWidget *hbox, *field; + char *name, *expr; + + if (!table->vars || table->vars->next) + return; + if (!table->rows || table->rows->next) + return; + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + name = stralloc_printf("%s%s", table->vars->key ? "?" : "", + table->vars->name); + field = label_in_box_new(name, "Variable name. Click to edit."); + free(name); + + gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0); + label_in_box_bg(field, COLOR_VAR_PASSIVE); + table->vars->widget = field; + g_signal_connect(G_OBJECT(box_of_label(field)), + "button_press_event", + G_CALLBACK(assignment_var_select_event), table->vars); + + gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" = "), + FALSE, FALSE, 0); + + expr = unparse(table->rows->values->expr); + field = label_in_box_new(expr, "Variable value. Click to edit."); + free(expr); + gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0); + label_in_box_bg(field, COLOR_EXPR_PASSIVE); + table->rows->values->widget = field; + g_signal_connect(G_OBJECT(box_of_label(field)), + "button_press_event", + G_CALLBACK(assignment_value_select_event), table->rows->values); +} + + +/* ----- tables ------------------------------------------------------------ */ + + +static void select_row(struct row *row) +{ + struct table *table = row->table; + struct value *value; + + for (value = table->active_row->values; value; value = value->next) + label_in_box_bg(value->widget, COLOR_ROW_UNSELECTED); + table->active_row = row; + for (value = table->active_row->values; value; value = value->next) + label_in_box_bg(value->widget, COLOR_ROW_SELECTED); +} + + +static void set_row_values(void *user, const struct value *values, + int n_values) +{ + struct value *value = user; + struct row *row = value->row; + struct table *table = row->table; + struct var **var; + const struct value *walk; + int first = 1; + + var = &table->vars; + for (walk = row->values; walk != value; walk = walk->next) + var = &(*var)->next; + + while (values) { + if (!*var) + add_column_here(table, var); + if (first) + first = 0; + else + value = value->next; + free_expr(value->expr); + value->expr = values->expr; + values = values->next; + var = &(*var)->next; + } +} + + +static gboolean table_var_press_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + struct var *var = data; + + switch (event->button) { + case 3: + pop_up_table_var(var, event); + return TRUE; + } + return FALSE; +} + + +static gboolean table_var_release_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + struct var *var = data; + + switch (event->button) { + case 1: + if (is_dragging(var)) + return FALSE; + edit_var(var, set_col_values, var, -1); + return TRUE; + } + return FALSE; +} + + +static gboolean table_value_press_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + struct value *value = data; + + switch (event->button) { + case 3: + pop_up_table_value(value, event); + return TRUE; + } + return FALSE; +} + + +static gboolean table_value_release_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + struct value *value = data; + + switch (event->button) { + case 1: + if (is_dragging(value)) + return FALSE; + if (!value->row || + value->row->table->active_row == value->row) { + edit_nothing(); + edit_value_list(value, value->row->table->vars->frame, + set_row_values, value); + } else { + select_row(value->row); + change_world(); + } + return TRUE; + } + return FALSE; +} + + +static gboolean table_scroll_event(GtkWidget *widget, GdkEventScroll *event, + gpointer data) +{ + struct table *table = data; + struct row *row, *last; + + switch (event->direction) { + case GDK_SCROLL_UP: + last = NULL; + for (row = table->rows; + row && (!last || row != table->active_row); row = row->next) + last = row; + table->active_row = last; + change_world(); + break; + case GDK_SCROLL_DOWN: + table->active_row = table->active_row->next; + if (!table->active_row) + table->active_row = table->rows; + change_world(); + break; + default: + /* ignore */; + } + return TRUE; +} + + +/* @@@ this function is too long */ + +static void build_table(GtkWidget *vbox, struct frame *frame, + struct table *table, int wrap_width) +{ + GtkWidget *tab, *field; + GtkWidget *evbox, *align, *sep; + struct var *var; + struct row *row; + struct value *value; + int n_vars = 0, n_rows = 0; + int n_var, n_row, pos; + char *name, *expr; + GdkColor color; + + for (var = table->vars; var; var = var->next) + n_vars++; + for (row = table->rows; row; row = row->next) + n_rows++; + + if (n_vars == 1 && n_rows == 1) + return; + + var = table->vars; + n_var = 0; + n_vars = 0; + while (var) { + if (n_vars) { + gtk_table_resize(GTK_TABLE(tab), n_rows, n_vars+1); + } else { + evbox = gtk_event_box_new(); + align = gtk_alignment_new(0, 0, 0, 0); + gtk_container_add(GTK_CONTAINER(align), evbox); + gtk_box_pack_start(GTK_BOX(vbox), align, + FALSE, FALSE, 0); + + tab = gtk_table_new(n_rows+1, n_vars, FALSE); + gtk_container_add(GTK_CONTAINER(evbox), tab); + color = get_color(COLOR_VAR_TABLE_SEP); + gtk_widget_modify_bg(GTK_WIDGET(evbox), + GTK_STATE_NORMAL, &color); + + gtk_table_set_row_spacings(GTK_TABLE(tab), 1); + gtk_table_set_col_spacings(GTK_TABLE(tab), 1); + } + + name = stralloc_printf("%s%s", var->key ? "?" : "", var->name); + field = label_in_box_new(name, + "Variable (column) name. Click to edit."); + free(name); + + gtk_table_attach_defaults(GTK_TABLE(tab), box_of_label(field), + n_vars, n_vars+1, 0, 1); + label_in_box_bg(field, COLOR_VAR_PASSIVE); + g_signal_connect(G_OBJECT(box_of_label(field)), + "button_press_event", + G_CALLBACK(table_var_press_event), var); + g_signal_connect(G_OBJECT(box_of_label(field)), + "button_release_event", + G_CALLBACK(table_var_release_event), var); + g_signal_connect(G_OBJECT(box_of_label(field)), + "scroll_event", + G_CALLBACK(table_scroll_event), table); + var->widget = field; + + setup_var_drag(var); + + n_row = 0; + for (row = table->rows; row; row = row->next) { + value = row->values; + for (pos = 0; pos != n_var; pos++) + value = value->next; + expr = unparse(value->expr); + field = label_in_box_new(expr, + "Variable value. Click to select row or to edit."); + free(expr); + gtk_table_attach_defaults(GTK_TABLE(tab), + box_of_label(field), + n_vars, n_vars+1, + n_row+1, n_row+2); + label_in_box_bg(field, table->active_row == row ? + COLOR_ROW_SELECTED : COLOR_ROW_UNSELECTED); + g_signal_connect(G_OBJECT(box_of_label(field)), + "button_press_event", + G_CALLBACK(table_value_press_event), value); + g_signal_connect(G_OBJECT(box_of_label(field)), + "button_release_event", + G_CALLBACK(table_value_release_event), value); + g_signal_connect(G_OBJECT(box_of_label(field)), + "scroll_event", + G_CALLBACK(table_scroll_event), table); + value->widget = field; + setup_value_drag(value); + n_row++; + } + + /* + * Wrap tables wider than the screen area available for + * variables and tables. Don't wrap before having output at + * least one column. + */ + if (n_vars && get_widget_width(tab) > wrap_width) { + /* + * Resizing alone doesn't hide extra columns. We have + * to explicitly remove their content as well. + */ + gtk_container_remove(GTK_CONTAINER(tab), + box_of_label(var->widget)); + for (row = table->rows; row; row = row->next) { + value = row->values; + for (pos = 0; pos != n_var; pos++) + value = value->next; + gtk_container_remove(GTK_CONTAINER(tab), + box_of_label(value->widget)); + } + gtk_table_resize(GTK_TABLE(tab), n_rows, n_vars); + + sep = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), sep, + FALSE, FALSE, 1); + + n_vars = 0; + continue; + } + + var = var->next; + n_var++; + n_vars++; + } +} + + +/* ----- loops ------------------------------------------------------------- */ + + +static void set_loop_values(void *user, const struct value *values, + int n_values) +{ + struct loop *loop = user; + + switch (n_values) { + case 2: + if (loop->to.expr) + free_expr(loop->to.expr); + loop->to.expr = values->next->expr; + /* fall through */ + case 1: + if (loop->from.expr) + free_expr(loop->from.expr); + loop->from.expr = values->expr; + break; + case 0: + break; + default: + abort(); + } +} + + +static gboolean loop_var_select_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + struct loop *loop = data; + + switch (event->button) { + case 1: + edit_var(&loop->var, set_loop_values, loop, 2); + break; + case 3: + pop_up_loop_var(loop, event); + break; + } + return TRUE; +} + + +static gboolean loop_from_select_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + struct loop *loop = data; + + switch (event->button) { + case 1: + edit_nothing(); + edit_value(&loop->from, loop->var.frame); + break; + } + return TRUE; +} + + +static gboolean loop_to_select_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + struct loop *loop = data; + + switch (event->button) { + case 1: + edit_nothing(); + edit_value(&loop->to, loop->var.frame); + break; + } + return TRUE; +} + + +static gboolean loop_select_event(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + struct loop *loop = data; + + switch (event->button) { + case 1: + loop->active = + (long) gtk_object_get_data(GTK_OBJECT(widget), "value"); + change_world(); + break; + } + return TRUE; +} + + +static gboolean loop_scroll_event(GtkWidget *widget, GdkEventScroll *event, + gpointer data) +{ + struct loop *loop = data; + + switch (event->direction) { + case GDK_SCROLL_UP: + if (loop->active < loop->iterations-1) { + loop->active++; + change_world(); + } + break; + case GDK_SCROLL_DOWN: + if (loop->active) { + loop->active--; + change_world(); + } + break; + default: + /* ignore */; + } + return TRUE; +} + + +static void build_loop(GtkWidget *vbox, struct frame *frame, + struct loop *loop) +{ + GtkWidget *hbox, *field, *label; + char *expr; + int i; + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + field = label_in_box_new(loop->var.name, + "Variable name. Click to edit."); + gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0); + label_in_box_bg(field, COLOR_VAR_PASSIVE); + if (instantiation_error == loop) + label_in_box_fg(field, COLOR_ITEM_ERROR); + g_signal_connect(G_OBJECT(box_of_label(field)), + "button_press_event", + G_CALLBACK(loop_var_select_event), loop); + loop->var.widget = field; + + gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" = "), + FALSE, FALSE, 0); + + expr = unparse(loop->from.expr); + field = label_in_box_new(expr, + "Start value of loop. Click to edit."); + free(expr); + gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0); + label_in_box_bg(field, COLOR_EXPR_PASSIVE); + g_signal_connect(G_OBJECT(box_of_label(field)), + "button_press_event", + G_CALLBACK(loop_from_select_event), loop); + loop->from.widget = field; + + gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" ... "), + FALSE, FALSE, 0); + + expr = unparse(loop->to.expr); + field = label_in_box_new(expr, "End value of loop. Click to edit."); + free(expr); + gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0); + label_in_box_bg(field, COLOR_EXPR_PASSIVE); + g_signal_connect(G_OBJECT(box_of_label(field)), + "button_press_event", + G_CALLBACK(loop_to_select_event), loop); + loop->to.widget = field; + + gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" ("), + FALSE, FALSE, 0); + + for (i = 0; i != loop->iterations; i++) { + label = add_activator(hbox, loop->active == i, + loop_select_event, loop, + "Loop value. Click to make active.", + "%g", loop->n+i); + gtk_object_set_data(GTK_OBJECT(box_of_label(label)), "value", + (gpointer) (long) i); + + g_signal_connect(G_OBJECT(box_of_label(label)), + "scroll_event", + G_CALLBACK(loop_scroll_event), loop); + } + + gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(")"), + FALSE, FALSE, 0); +} + + +/* ----- the list of variables, tables, and loops -------------------------- */ + + +static GtkWidget *build_vars(struct frame *frame, int wrap_width) +{ + GtkWidget *vbox; + struct table *table; + struct loop *loop; + + vbox = gtk_vbox_new(FALSE, 0); + for (table = frame->tables; table; table = table->next) { + add_sep(vbox, 3); + build_assignment(vbox, frame, table); + build_table(vbox, frame, table, wrap_width); + } + for (loop = frame->loops; loop; loop = loop->next) { + add_sep(vbox, 3); + build_loop(vbox, frame, loop); + } + return vbox; +} + + +/* ----- items ------------------------------------------------------------- */ + + +static void set_item_color(struct inst *inst, const char *color) +{ + GtkWidget *label; + + if (inst->vec) + label = inst->vec->list_widget; + else + label = inst->obj->list_widget; + if (label) + label_in_box_bg(box_of_label(label), color); +} + + +void gui_frame_select_inst(struct inst *inst) +{ + set_item_color(inst, COLOR_ITEM_SELECTED); +} + + +void gui_frame_deselect_inst(struct inst *inst) +{ + set_item_color(inst, COLOR_ITEM_NORMAL); +} + + +static gboolean item_select_vec(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + struct vec *vec = data; + + switch (event->button) { + case 1: + inst_select_vec(vec); + redraw(); + break; + } + return TRUE; +} + + +static gboolean item_select_obj(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + struct obj *obj = data; + + switch (event->button) { + case 1: + inst_select_obj(obj); + redraw(); + break; + } + return TRUE; +} + + +static GtkWidget *item_label(GtkWidget *tab, char *s, int col, int row, + gboolean (*cb)(GtkWidget *widget, GdkEventButton *event, gpointer data), + gpointer data) +{ + GtkWidget *label; + + label = label_in_box_new(s, "Click to select."); + gtk_misc_set_padding(GTK_MISC(label), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_widget_modify_font(label, item_list_font); + gtk_table_attach_defaults(GTK_TABLE(tab), box_of_label(label), + col, col+1, row, row+1); + label_in_box_bg(box_of_label(label), COLOR_ITEM_NORMAL); + + if (cb) + g_signal_connect(G_OBJECT(box_of_label(label)), + "button_press_event", G_CALLBACK(cb), data); + + free(s); + return label; +} + + +static GtkWidget *build_items(struct frame *frame) +{ + GtkWidget *vbox, *hbox, *tab; + struct order *order, *item; + struct vec *vec; + struct obj *obj; + int n; + char *s, *t; + + n = 0; + for (vec = frame->vecs; vec; vec = vec->next) + n++; + for (obj = frame->objs; obj; obj = obj->next) + if (obj->type != ot_meas) + n++; + + vbox = gtk_vbox_new(FALSE, 0); + add_sep(vbox, 3); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + tab = gtk_table_new(n, 2, FALSE); + gtk_box_pack_start(GTK_BOX(hbox), tab, FALSE, FALSE, 0); + + order = order_frame(frame); + n = 0; + for (item = order; item->vec || item->obj; item++) { + if (item->obj) { + s = print_obj(item->obj, item->vec); + item->obj->list_widget = item_label(tab, s, 1, n, + item_select_obj, item->obj); + if (item->obj == instantiation_error) + label_in_box_fg(item->obj->list_widget, + COLOR_ITEM_ERROR); + } else { + t = stralloc_printf("%s: ", print_label(item->vec)); + item_label(tab, t, 0, n, NULL, NULL); + + s = print_vec(item->vec); + item->vec->list_widget = item_label(tab, s, 1, n, + item_select_vec, item->vec); + if (item->vec == instantiation_error) + label_in_box_fg(item->vec->list_widget, + COLOR_ITEM_ERROR); + } + n++; + } + free(order); + + return vbox; +} + + +static GtkWidget *build_meas(struct frame *frame) +{ + GtkWidget *vbox, *hbox, *tab; + struct obj *obj; + int n; + char *s; + + n = 0; + for (obj = frame->objs; obj; obj = obj->next) + if (obj->type == ot_meas) + n++; + + vbox = gtk_vbox_new(FALSE, 0); + add_sep(vbox, 3); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + tab = gtk_table_new(n, 2, FALSE); + gtk_box_pack_start(GTK_BOX(hbox), tab, FALSE, FALSE, 0); + + n = 0; + for (obj = frame->objs; obj; obj = obj->next) { + if (obj->type != ot_meas) + continue; + s = print_meas(obj); + obj->list_widget = item_label(tab, s, 0, n, + item_select_obj, obj); + if (obj == instantiation_error) + label_in_box_fg(obj->list_widget, COLOR_ITEM_ERROR); + n++; + } + + return vbox; +} + + +static void dont_build_items(struct frame *frame) +{ + struct vec *vec; + struct obj *obj; + + for (vec = frame->vecs; vec; vec = vec->next) + vec->list_widget = NULL; + for (obj = frame->objs; obj; obj = obj->next) + obj->list_widget = NULL; +} + + +/* ----- package name ------------------------------------------------------ */ + + +static int validate_pkg_name(const char *s, void *ctx) +{ + if (!*s) + return 0; + while (*s) { + if (*s < 32 || *s > 126) + return 0; + s++; + } + return 1; +} + +static void unselect_pkg_name(void *data) +{ + GtkWidget *widget = data; + + label_in_box_bg(widget, COLOR_PART_NAME); +} + + +static gboolean pkg_scroll_event(GtkWidget *widget, GdkEventScroll *event, + gpointer data) +{ + struct pkg *pkg, *last; + + switch (event->direction) { + case GDK_SCROLL_UP: + if (active_pkg->next) + active_pkg = active_pkg->next; + else + active_pkg = pkgs->next; + change_world(); + break; + case GDK_SCROLL_DOWN: + last = NULL; + for (pkg = pkgs->next; pkg && (!last || pkg != active_pkg); + pkg = pkg->next) + last = pkg; + active_pkg = last; + change_world(); + break; + default: + /* ignore */; + } + return TRUE; +} + + +static gboolean pkg_name_edit_event(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + switch (event->button) { + case 1: + inst_select_outside(widget, unselect_pkg_name); + label_in_box_bg(widget, COLOR_PART_NAME_EDITING); + status_set_type_entry(NULL, "package ="); + status_set_name("Package name (actual)", "%s", pkg_name); + edit_nothing(); + edit_name(&pkg_name, validate_pkg_name, NULL, + "Package name (template)"); + break; + } + return TRUE; +} + + +static GtkWidget *build_pkg_name(void) +{ + GtkWidget *label; + + label = label_in_box_new(pkg_name, + "Package name. (Template) Click to edit."); + gtk_misc_set_padding(GTK_MISC(label), 2, 2); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + + label_in_box_bg(label, COLOR_PART_NAME); + + g_signal_connect(G_OBJECT(box_of_label(label)), + "button_press_event", G_CALLBACK(pkg_name_edit_event), NULL); + g_signal_connect(G_OBJECT(box_of_label(label)), + "scroll_event", G_CALLBACK(pkg_scroll_event), NULL); + + return box_of_label(label); +} + + +/* ----- packages ---------------------------------------------------------- */ + + +static gboolean pkg_select_event(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + struct pkg *pkg = data; + + switch (event->button) { + case 1: + active_pkg = pkg; + /* @@@ we could actually skip instantiation here */ + change_world(); + break; + } + return TRUE; +} + + +static GtkWidget *build_pkg_names(void) +{ + GtkWidget *hbox; + struct pkg *pkg; + GtkWidget *field; + + hbox = gtk_hbox_new(FALSE, 0); + for (pkg = pkgs; pkg; pkg = pkg->next) + if (pkg->name) { + field = add_activator(hbox, pkg == active_pkg, + pkg_select_event, pkg, + "Package name. Click to make active.", + "%s", pkg->name); + g_signal_connect(G_OBJECT(box_of_label(field)), + "scroll_event", + G_CALLBACK(pkg_scroll_event), NULL); + } + return hbox; +} + + +/* ----- frame labels ------------------------------------------------------ */ + + +static int validate_frame_name(const char *s, void *ctx) +{ + struct frame *f; + + if (!is_id(s)) + return 0; + for (f = frames->next; f; f = f->next) + if (!strcmp(f->name, s)) + return 0; + return 1; +} + + +static void unselect_frame(void *data) +{ + struct frame *frame= data; + + /* + * "unselect" means in this context that the selection has moved + * elsewhere. However, this does not necessarily change the frame. + * (And, in fact, since we rebuild the frame list anyway, the color + * change here doesn't matter if selecting a different frame.) + * So we revert from "editing" to "selected". + */ + label_in_box_bg(frame->label, COLOR_FRAME_SELECTED); +} + + +static void edit_frame(struct frame *frame) +{ + const char *tip; + + inst_select_outside(frame, unselect_frame); + label_in_box_bg(frame->label, COLOR_FRAME_EDITING); + tip = "Frame name"; + status_set_type_entry(NULL, "name ="); + status_set_name(tip, "%s", frame->name); + edit_nothing(); + edit_unique(&frame->name, validate_frame_name, frame, tip); +} + + +void select_frame(struct frame *frame) +{ + if (active_frame) + label_in_box_bg(active_frame->label, COLOR_FRAME_UNSELECTED); + active_frame = frame; + change_world(); +} + + +static gboolean frame_press_event(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + struct frame *frame = data; + + switch (event->button) { + case 3: + pop_up_frame(frame, event); + return TRUE; + } + return FALSE; +} + + +static gboolean frame_release_event(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + struct frame *frame = data; + + switch (event->button) { + case 1: + if (is_dragging(frame)) + return FALSE; + if (active_frame != frame) { + select_frame(frame); + } else { + if (active_frame->name) { + edit_nothing(); + edit_frame(frame); + } + } + return TRUE; + } + return FALSE; +} + + +static GtkWidget *build_frame_label(struct frame *frame) +{ + GtkWidget *label; + + label = label_in_box_new(frame->name ? frame->name : "(root)", + frame->name ? "Frame name. Click to select or edit." : + "Root frame. Click to select."); + gtk_misc_set_padding(GTK_MISC(label), 2, 2); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + + label_in_box_bg(label, active_frame == frame ? + COLOR_FRAME_SELECTED : COLOR_FRAME_UNSELECTED); + + g_signal_connect(G_OBJECT(box_of_label(label)), + "button_press_event", G_CALLBACK(frame_press_event), frame); + g_signal_connect(G_OBJECT(box_of_label(label)), + "button_release_event", G_CALLBACK(frame_release_event), frame); + frame->label = label; + + if (frame != frames) + setup_frame_drag(frame); + + return box_of_label(label); +} + + +/* ----- frame references -------------------------------------------------- */ + + +static gboolean frame_ref_select_event(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + struct obj *obj = data; + + switch (event->button) { + case 1: + obj->u.frame.ref->active_ref = data; + change_world(); + break; + } + return TRUE; +} + + +static GtkWidget *build_frame_refs(const struct frame *frame) +{ + GtkWidget *hbox; + struct obj *obj; + char *tooltip; + + hbox = gtk_hbox_new(FALSE, 0); + for (obj = frame->objs; obj; obj = obj->next) + if (obj->type == ot_frame && + obj->u.frame.ref == active_frame) { + tooltip = stralloc_printf( + "Frame <b>%s</b> is referenced here. " + "Click to make active.", active_frame->name); + add_activator(hbox, + obj == obj->u.frame.ref->active_ref, + frame_ref_select_event, obj, + tooltip, + "%d", obj->u.frame.lineno); + free(tooltip); + } + return hbox; +} + + +/* ----- frames ------------------------------------------------------------ */ + + +void build_frames(GtkWidget *vbox, int wrap_width) +{ + struct frame *frame; + GtkWidget *hbox, *tab, *label, *packages, *refs, *vars, *items, *meas; + int n = 0; + int max_name_width, name_width; + + destroy_all_children(GTK_CONTAINER(vbox)); + for (frame = frames; frame; frame = frame->next) + n++; + + hbox = gtk_hbox_new(FALSE, 0); + + tab = gtk_table_new(n*2+3, 2, FALSE); + gtk_table_set_row_spacings(GTK_TABLE(tab), 1); + gtk_table_set_col_spacings(GTK_TABLE(tab), 1); + + gtk_box_pack_start(GTK_BOX(hbox), tab, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + label = build_pkg_name(); + gtk_table_attach_defaults(GTK_TABLE(tab), label, 0, 1, 0, 1); + max_name_width = get_widget_width(label); + + packages = build_pkg_names(); + gtk_table_attach_defaults(GTK_TABLE(tab), packages, 1, 2, 0, 1); + + n = 0; + for (frame = frames; frame; frame = frame->next) { + label = build_frame_label(frame); + gtk_table_attach_defaults(GTK_TABLE(tab), label, + 0, 1, n*2+1, n*2+2); + n++; + name_width = get_widget_width(label); + if (name_width > max_name_width) + max_name_width = name_width; + } + + wrap_width -= max_name_width+FRAME_AREA_MISC_WIDTH; + n = 0; + for (frame = frames; frame; frame = frame->next) { + refs = build_frame_refs(frame); + gtk_table_attach_defaults(GTK_TABLE(tab), refs, + 1, 2, n*2+1, n*2+2); + + if (show_vars) { + vars = build_vars(frame, wrap_width); + gtk_table_attach_defaults(GTK_TABLE(tab), vars, + 1, 2, n*2+2, n*2+3); + dont_build_items(frame); + } else { + items = build_items(frame); + gtk_table_attach_defaults(GTK_TABLE(tab), items, + 1, 2, n*2+2, n*2+3); + } + + n++; + } + + if (!show_vars) { + meas = build_meas(frames); + gtk_table_attach_defaults(GTK_TABLE(tab), meas, + 1, 2, n*2+2, n*2+3); + } + + gtk_widget_show_all(hbox); +} diff --git a/gui_frame.h b/gui_frame.h new file mode 100644 index 0000000..0b54f43 --- /dev/null +++ b/gui_frame.h @@ -0,0 +1,36 @@ +/* + * gui_frame.h - GUI, frame window + * + * Written 2009, 2010, 2012 by Werner Almesberger + * Copyright 2009, 2010, 2012 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_FRAME_H +#define GUI_FRAME_H + +#include <gtk/gtk.h> + +#include "obj.h" + + +extern int show_vars; + + +void reselect_var(struct var *var); + +void make_popups(void); + +void select_frame(struct frame *frame); + +void gui_frame_select_inst(struct inst *inst); +void gui_frame_deselect_inst(struct inst *inst); + +void build_frames(GtkWidget *vbox, int warp_width); + +#endif /* !GUI_FRAME_H */ diff --git a/gui_frame_drag.c b/gui_frame_drag.c new file mode 100644 index 0000000..47fa169 --- /dev/null +++ b/gui_frame_drag.c @@ -0,0 +1,589 @@ +/* + * gui_frame_drag.c - GUI, dragging of frame items + * + * Written 2010 by Werner Almesberger + * Copyright 2010 by Werner Almesberger + * + * 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. + */ + + +#include <assert.h> +#include <gtk/gtk.h> + +#include "util.h" +#include "obj.h" +#include "gui_util.h" +#include "gui.h" +#include "gui_canvas.h" +#include "gui_frame_drag.h" + +#if 0 +#include "icons/frame.xpm" +#endif + + +enum { + target_id_var, + target_id_value, + target_id_frame, + target_id_canvas, +}; + + +static GtkTargetEntry target_var = { + .target = "var", + .flags = GTK_TARGET_SAME_APP, + .info = target_id_var, +}; + +static GtkTargetEntry target_value = { + .target = "value", + .flags = GTK_TARGET_SAME_APP, + .info = target_id_value, +}; + +static GtkTargetEntry target_frame = { + .target = "frame", + .flags = GTK_TARGET_SAME_APP, + .info = target_id_frame, +}; + + +/* ----- dragging status --------------------------------------------------- */ + + +/* + * Pointer to whatever it is we're dragging. NULL if not dragging. + */ + +static void *dragging; + + +int is_dragging(void *this) +{ + return this == dragging; +} + + +int is_dragging_anything(void) +{ + return !!dragging; +} + + +/* ----- helper functions for indexed list --------------------------------- */ + + +#define NDX(first, item) \ + ({ typeof(first) NDX_walk; \ + int NDX_n = 0; \ + for (NDX_walk = (first); NDX_walk != (item); \ + NDX_walk = NDX_walk->next) \ + NDX_n++; \ + NDX_n; }) + +#define NTH(first, n) \ + ({ typeof(first) *NTH_walk; \ + int NTH_n = (n); \ + for (NTH_walk = &(first); NTH_n; NTH_n--) \ + NTH_walk = &(*NTH_walk)->next; \ + NTH_walk; }) + +#define FOR_UNORDERED(var, a, b) \ + for (var = (a) < (b) ? (a) : (b); var != ((a) < (b) ? (b) : (a)); \ + var++) + + +/* ----- generic helper functions. maybe move to gui_util later ------------ */ + + +static void get_cell_coords(GtkWidget *widget, guint res[4]) +{ + GtkWidget *tab; + + tab = gtk_widget_get_ancestor(widget, GTK_TYPE_TABLE); + gtk_container_child_get(GTK_CONTAINER(tab), widget, + "left-attach", res, + "right-attach", res+1, + "top-attach", res+2, + "bottom-attach", res+3, NULL); +} + + +static void swap_table_cells(GtkWidget *a, GtkWidget *b) +{ + GtkWidget *tab_a, *tab_b; + guint pos_a[4], pos_b[4]; + + tab_a = gtk_widget_get_ancestor(a, GTK_TYPE_TABLE); + tab_b = gtk_widget_get_ancestor(b, GTK_TYPE_TABLE); + get_cell_coords(a, pos_a); + get_cell_coords(b, pos_b); + g_object_ref(a); + g_object_ref(b); + gtk_container_remove(GTK_CONTAINER(tab_a), a); + gtk_container_remove(GTK_CONTAINER(tab_b), b); + gtk_table_attach_defaults(GTK_TABLE(tab_a), b, + pos_a[0], pos_a[1], pos_a[2], pos_a[3]); + gtk_table_attach_defaults(GTK_TABLE(tab_b), a, + pos_b[0], pos_b[1], pos_b[2], pos_b[3]); + g_object_unref(a); + g_object_unref(b); +} + + +static GtkWidget *pick_table_cell(GtkWidget *table, int x, int y) +{ + GList *children, *walk; + GtkWidget *child; + guint pos[4]; + + children = gtk_container_get_children(GTK_CONTAINER(table)); + for (walk = children; walk; walk = g_list_next(walk)) { + child = g_list_nth_data(walk, 0); + assert(child); + get_cell_coords(child, pos); + if (pos[0] == x && pos[2] == y) + break; + } + g_list_free(children); + return walk ? child : NULL; +} + + +static void swap_table_cells_by_coord(GtkWidget *table_a, + int a_col, int a_row, GtkWidget *table_b, int b_col, int b_row) +{ + GtkWidget *a, *b; + + a = pick_table_cell(table_a, a_col, a_row); + b = pick_table_cell(table_b, b_col, b_row); + if (a) { + g_object_ref(a); + gtk_container_remove(GTK_CONTAINER(table_a), a); + } + if (b) { + g_object_ref(b); + gtk_container_remove(GTK_CONTAINER(table_b), b); + } + if (a) + gtk_table_attach_defaults(GTK_TABLE(table_b), a, + b_col, b_col+1, b_row, b_row+1); + if (b) + gtk_table_attach_defaults(GTK_TABLE(table_a), b, + a_col, a_col+1, a_row, a_row+1); + if (a) + g_object_unref(a); + if (b) + g_object_unref(b); +} + + +static void swap_table_rows(GtkWidget *table, int a, int b) +{ + guint cols; + int i; + + g_object_get(table, "n-columns", &cols, NULL); + for (i = 0; i != cols; i++) + swap_table_cells_by_coord(table, i, a, table, i, b); +} + + +/* ----- swap table items -------------------------------------------------- */ + + +static void swap_vars(struct table *table, int a, int b) +{ + struct var **var_a, **var_b; + + var_a = NTH(table->vars, a); + var_b = NTH(table->vars, b); + + swap_table_cells(box_of_label((*var_a)->widget), + box_of_label((*var_b)->widget)); + + SWAP(*var_a, *var_b); + SWAP((*var_a)->next, (*var_b)->next); +} + + +static void swap_values(struct row *row, int a, int b) +{ + struct value **value_a, **value_b; + + value_a = NTH(row->values, a); + value_b = NTH(row->values, b); + + swap_table_cells(box_of_label((*value_a)->widget), + box_of_label((*value_b)->widget)); + + SWAP(*value_a, *value_b); + SWAP((*value_a)->next, (*value_b)->next); +} + + +static void swap_cols(struct table *table, int a, int b) +{ + struct row *row; + + swap_vars(table, a, b); + for (row = table->rows; row; row = row->next) + swap_values(row, a, b); +} + + +static void swap_rows(struct row **a, struct row **b) +{ + struct value *value_a, *value_b; + + value_a = (*a)->values; + value_b = (*b)->values; + while (value_a) { + swap_table_cells(box_of_label(value_a->widget), + box_of_label(value_b->widget)); + value_a = value_a->next; + value_b = value_b->next; + } + SWAP(*a, *b); + SWAP((*a)->next, (*b)->next); +} + + +/* ----- swap frames ------------------------------------------------------- */ + + +static void swap_frames(GtkWidget *table, int a, int b) +{ + struct frame **frame_a = NTH(frames, a); + struct frame **frame_b = NTH(frames, b); + + swap_table_rows(table, 2*a+1, 2*b+1); + swap_table_rows(table, 2*a+2, 2*b+2); + + SWAP(*frame_a, *frame_b); + SWAP((*frame_a)->next, (*frame_b)->next); +} + + +/* ----- common functions -------------------------------------------------- */ + + +/* + * according to + * http://www.pubbs.net/201004/gtk/22819-re-drag-and-drop-drag-motion-cursor-lockup-fixed-.html + * http://www.cryingwolf.org/articles/gtk-dnd.html + */ + +static int has_target(GtkWidget *widget, GdkDragContext *drag_context, + const char *name) +{ + GdkAtom target; + + target = gtk_drag_dest_find_target(widget, drag_context, NULL); + + /* + * Force allocation so that we don't have to check for GDK_NONE. + */ + return target == gdk_atom_intern(name, FALSE); +} + + +static void drag_begin(GtkWidget *widget, + GtkTextDirection previous_direction, gpointer user_data) +{ + GdkPixbuf *pixbuf; + + /* + * Suppress the icon. PixBufs can't be zero-sized, but nobody will + * notice a lone pixel either :-) + */ + pixbuf = + gdk_pixbuf_get_from_drawable(NULL, DA, NULL, 0, 0, 0, 0, 1, 1); + gtk_drag_source_set_icon_pixbuf(widget, pixbuf); + g_object_unref(pixbuf); + + dragging = user_data; +} + + +static void drag_end(GtkWidget *widget, GdkDragContext *drag_context, + gpointer user_data) +{ + dragging = NULL; +} + + +static void setup_drag_common(GtkWidget *widget, void *user_arg) +{ + g_signal_connect(G_OBJECT(widget), "drag-begin", + G_CALLBACK(drag_begin), user_arg); + g_signal_connect(G_OBJECT(widget), "drag-end", + G_CALLBACK(drag_end), user_arg); +} + + +/* ----- drag variables ---------------------------------------------------- */ + + +static gboolean drag_var_motion(GtkWidget *widget, + GdkDragContext *drag_context, gint x, gint y, guint time_, + gpointer user_data) +{ + struct var *from = dragging; + struct var *to = user_data; + int from_n, to_n, i; + + if (!has_target(widget, drag_context, "var")) + return FALSE; + if (from == to || from->table != to->table) + return FALSE; + from_n = NDX(from->table->vars, from); + to_n = NDX(to->table->vars, to); + FOR_UNORDERED(i, from_n, to_n) + swap_cols(from->table, i, i+1); + return FALSE; +} + + +void setup_var_drag(struct var *var) +{ + GtkWidget *box; + + box = box_of_label(var->widget); + gtk_drag_source_set(box, GDK_BUTTON1_MASK, + &target_var, 1, GDK_ACTION_PRIVATE); + gtk_drag_dest_set(box, GTK_DEST_DEFAULT_MOTION, + &target_var, 1, GDK_ACTION_PRIVATE); + setup_drag_common(box, var); + g_signal_connect(G_OBJECT(box), "drag-motion", + G_CALLBACK(drag_var_motion), var); +} + + +/* ----- drag values ------------------------------------------------------- */ + + +static gboolean drag_value_motion(GtkWidget *widget, + GdkDragContext *drag_context, gint x, gint y, guint time_, + gpointer user_data) +{ + struct value *from = dragging; + struct value *to = user_data; + struct table *table; + struct row **row, *end; + int from_n, to_n, i; + + if (!has_target(widget, drag_context, "value")) + return FALSE; + table = from->row->table; + if (table != to->row->table) + return FALSE; + + /* columns */ + + from_n = NDX(from->row->values, from); + to_n = NDX(to->row->values, to); + FOR_UNORDERED(i, from_n, to_n) + swap_cols(table, i, i+1); + + /* rows */ + + if (from->row == to->row) + return FALSE; + row = &table->rows; + while (1) { + if (*row == from->row) { + end = to->row; + break; + } + if (*row == to->row) { + end = from->row; + break; + } + row = &(*row)->next; + } + while (1) { + swap_rows(row, &(*row)->next); + if (*row == end) + break; + row = &(*row)->next; + } + + return FALSE; +} + + +void setup_value_drag(struct value *value) +{ + GtkWidget *box; + + box = box_of_label(value->widget); + gtk_drag_source_set(box, GDK_BUTTON1_MASK, + &target_value, 1, GDK_ACTION_PRIVATE); + gtk_drag_dest_set(box, GTK_DEST_DEFAULT_MOTION, + &target_value, 1, GDK_ACTION_PRIVATE); + setup_drag_common(box, value); + g_signal_connect(G_OBJECT(box), "drag-motion", + G_CALLBACK(drag_value_motion), value); +} + + +/* ----- frame to canvas helper functions ---------------------------------- */ + + +static int frame_on_canvas = 0; + + +static void leave_canvas(void) +{ + if (frame_on_canvas) + canvas_frame_end(); + frame_on_canvas = 0; +} + + +/* ----- drag frame labels ------------------------------------------------- */ + + +#if 0 + +/* + * Setting our own icon looks nice but it slows things down to the point where + * cursor movements can lag noticeable and it adds yet another element to an + * already crowded cursor. + */ + +static void drag_frame_begin(GtkWidget *widget, + GtkTextDirection previous_direction, gpointer user_data) +{ + GdkPixmap *pixmap; + GdkBitmap *mask; + GdkColormap *cmap; + + pixmap = gdk_pixmap_create_from_xpm_d(DA, &mask, NULL, xpm_frame); + cmap = gdk_drawable_get_colormap(root->window); + gtk_drag_source_set_icon(widget, cmap, pixmap, mask); + g_object_unref(pixmap); + g_object_unref(mask); + + dragging = user_data; +} + +#endif + + +static gboolean drag_frame_motion(GtkWidget *widget, + GdkDragContext *drag_context, gint x, gint y, guint time_, + gpointer user_data) +{ + struct frame *from = dragging; + struct frame *to = user_data; + int from_n, to_n, i; + + if (!has_target(widget, drag_context, "frame")) + return FALSE; + assert(from != frames); + assert(to != frames); + from_n = NDX(frames, from); + to_n = NDX(frames, to); + FOR_UNORDERED(i, from_n, to_n) + swap_frames(gtk_widget_get_ancestor(widget, GTK_TYPE_TABLE), + i, i+1); + return FALSE; +} + + +static void drag_frame_end(GtkWidget *widget, GdkDragContext *drag_context, + gpointer user_data) +{ + leave_canvas(); + drag_end(widget, drag_context, user_data); +} + + +void setup_frame_drag(struct frame *frame) +{ + GtkWidget *box; + + box = box_of_label(frame->label); + gtk_drag_source_set(box, GDK_BUTTON1_MASK, + &target_frame, 1, GDK_ACTION_COPY | GDK_ACTION_MOVE); + gtk_drag_dest_set(box, GTK_DEST_DEFAULT_MOTION, + &target_frame, 1, GDK_ACTION_MOVE); + setup_drag_common(box, frame); + + /* override */ +#if 0 + g_signal_connect(G_OBJECT(box), "drag-begin", + G_CALLBACK(drag_frame_begin), frame); +#endif + g_signal_connect(G_OBJECT(box), "drag-end", + G_CALLBACK(drag_frame_end), frame); + + g_signal_connect(G_OBJECT(box), "drag-motion", + G_CALLBACK(drag_frame_motion), frame); +} + + +/* ----- drag to the canvas ------------------------------------------------ */ + + +static gboolean drag_canvas_motion(GtkWidget *widget, + GdkDragContext *drag_context, gint x, gint y, guint time_, + gpointer user_data) +{ + if (!has_target(widget, drag_context, "frame")) + return FALSE; + if (!frame_on_canvas) { + frame_on_canvas = 1; + canvas_frame_begin(dragging); + } + if (canvas_frame_motion(dragging, x, y)) { + gdk_drag_status(drag_context, GDK_ACTION_COPY, time_); + return TRUE; + } else { + gdk_drag_status(drag_context, 0, time_); + return FALSE; + } +} + + +static void drag_canvas_leave(GtkWidget *widget, GdkDragContext *drag_context, + guint time_, gpointer user_data) +{ + leave_canvas(); +} + + +static gboolean drag_canvas_drop(GtkWidget *widget, + GdkDragContext *drag_context, gint x, gint y, guint time_, + gpointer user_data) +{ + if (!has_target(widget, drag_context, "frame")) + return FALSE; + if (!canvas_frame_drop(dragging, x, y)) + return FALSE; + gtk_drag_finish(drag_context, TRUE, FALSE, time_); + drag_end(widget, drag_context, user_data); + return TRUE; +} + + +void setup_canvas_drag(GtkWidget *canvas) +{ + gtk_drag_dest_set(canvas, + GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, + &target_frame, 1, GDK_ACTION_COPY); + + g_signal_connect(G_OBJECT(canvas), "drag-motion", + G_CALLBACK(drag_canvas_motion), NULL); + g_signal_connect(G_OBJECT(canvas), "drag-leave", + G_CALLBACK(drag_canvas_leave), NULL); + g_signal_connect(G_OBJECT(canvas), "drag-drop", + G_CALLBACK(drag_canvas_drop), NULL); +} diff --git a/gui_frame_drag.h b/gui_frame_drag.h new file mode 100644 index 0000000..f45c8e8 --- /dev/null +++ b/gui_frame_drag.h @@ -0,0 +1,30 @@ +/* + * gui_frame_drag.h - GUI, dragging of frame items + * + * Written 2010 by Werner Almesberger + * Copyright 2010 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_FRAME_DRAG_H +#define GUI_FRAME_DRAG_H + +#include <gtk/gtk.h> + +#include "obj.h" + + +int is_dragging(void *this); +int is_dragging_anything(void); + +void setup_var_drag(struct var *var); +void setup_value_drag(struct value *value); +void setup_frame_drag(struct frame *frame); +void setup_canvas_drag(GtkWidget *canvas); + +#endif /* !GUI_FRAME_DRAG_H */ diff --git a/gui_inst.c b/gui_inst.c new file mode 100644 index 0000000..eb2498b --- /dev/null +++ b/gui_inst.c @@ -0,0 +1,668 @@ +/* + * gui_inst.c - GUI, instance functions + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <math.h> +#include <gtk/gtk.h> + +#include "util.h" +#include "coord.h" +#include "inst.h" +#include "gui.h" +#include "gui_util.h" +#include "gui_style.h" +#include "gui_status.h" +#include "gui_inst.h" + + +/* ----- coordinate translation -------------------------------------------- */ + + +struct coord translate(struct coord pos) +{ + pos.x -= draw_ctx.center.x; + pos.y -= draw_ctx.center.y; + pos.x /= draw_ctx.scale; + pos.y /= draw_ctx.scale; + pos.y = -pos.y; + pos.x += draw_ctx.widget->allocation.width/2; + pos.y += draw_ctx.widget->allocation.height/2; + return pos; +} + + +struct coord canvas_to_coord(int x, int y) +{ + struct coord pos; + + x -= draw_ctx.widget->allocation.width/2; + y -= draw_ctx.widget->allocation.height/2; + y = -y; + pos.x = x*draw_ctx.scale+draw_ctx.center.x; + pos.y = y*draw_ctx.scale+draw_ctx.center.y; + return pos; +} + + +/* ----- drawing primitives ------------------------------------------------ */ + + +static void draw_eye(GdkGC *gc, struct coord center, int r1, int r2) +{ + draw_circle(DA, gc, TRUE, center.x, center.y, r1); + draw_circle(DA, gc, FALSE, center.x, center.y, r2); +} + + +#define MAX_POINTS 10 + + +static void draw_poly(GdkGC *gc, int fill, + const struct coord *points, int n_points) +{ + GdkPoint gp[MAX_POINTS]; + int i; + + if (n_points > MAX_POINTS) + abort(); + for (i = 0; i != n_points; i++) { + gp[i].x = points[i].x; + gp[i].y = points[i].y; + } + if (fill) { + gdk_draw_polygon(DA, gc, fill, gp, n_points); + } else { + gdk_draw_line(DA, gc, gp[0].x, gp[0].y, gp[1].x, gp[1].y); + gdk_draw_line(DA, gc, gp[1].x, gp[1].y, gp[2].x, gp[2].y); + } +} + + +static void draw_arrow(GdkGC *gc, int fill, + struct coord from, struct coord to, int len, double angle) +{ + struct coord p[3]; + struct coord side; + + if (from.x == to.x && from.y == to.y) { + side.x = 0; + side.y = -len; + } else { + side = normalize(sub_vec(to, from), len); + } + p[0] = add_vec(to, rotate(side, 180-angle)); + p[1] = to; + p[2] = add_vec(to, rotate(side, 180+angle)); + draw_poly(gc, fill, p, 3); +} + + +static enum mode get_mode(const struct inst *self) +{ + if (selected_inst == self) + return mode_selected; + return self->active || bright(self) ? mode_active : mode_inactive; +} + + +/* ----- vec --------------------------------------------------------------- */ + + +unit_type gui_dist_vec(struct inst *self, struct coord pos, unit_type scale) +{ + unit_type d; + + d = dist_point(pos, self->u.vec.end)/scale; + return d > VEC_EYE_R ? -1 : d; +} + + +/* + * The circles at a vector's tip enjoy the highest selection priority. However, + * users will probably also expected a click on a nicely exposed stem to work. + * So we give it second look after having exhausted all other options. + */ + +unit_type gui_dist_vec_fallback(struct inst *self, struct coord pos, + unit_type scale) +{ + unit_type d; + + d = dist_line(pos, self->base, self->u.vec.end)/scale; + return d > SELECT_R ? -1 : d; +} + + +void gui_highlight_vec(struct inst *self) +{ + struct coord center = translate(self->u.vec.end); + + draw_circle(DA, gc_highlight, FALSE, center.x, center.y, VEC_EYE_R); +} + + +void gui_draw_vec(struct inst *self) +{ + struct coord from = translate(self->base); + struct coord to = translate(self->u.vec.end); + GdkGC *gc; + + gc = gc_vec[get_mode(self)]; + draw_arrow(gc, TRUE, from, to, VEC_ARROW_LEN, VEC_ARROW_ANGLE); + gdk_draw_line(DA, gc, from.x, from.y, to.x, to.y); + draw_circle(DA, gc, FALSE, to.x, to.y, VEC_EYE_R); +} + + +/* ----- line -------------------------------------------------------------- */ + + +unit_type gui_dist_line(struct inst *self, struct coord pos, unit_type scale) +{ + unit_type r, d; + + r = self->u.rect.width/scale/2; + if (r < SELECT_R) + r = SELECT_R; + d = dist_line(pos, self->base, self->u.rect.end)/scale; + return d > r ? -1 : d; +} + + +void gui_draw_line(struct inst *self) +{ + struct coord min = translate(self->base); + struct coord max = translate(self->u.rect.end); + GdkGC *gc; + + gc = gc_obj[get_mode(self)]; + set_width(gc, self->u.rect.width/draw_ctx.scale); + gdk_draw_line(DA, gc, min.x, min.y, max.x, max.y); +} + + +/* ----- rect -------------------------------------------------------------- */ + + +unit_type gui_dist_rect(struct inst *self, struct coord pos, unit_type scale) +{ + unit_type r, d; + + r = self->u.rect.width/scale/2; + if (r < SELECT_R) + r = SELECT_R; + d = dist_rect(pos, self->base, self->u.rect.end)/scale; + return d > r ? -1 : d; +} + + +void gui_draw_rect(struct inst *self) +{ + struct coord min = translate(self->base); + struct coord max = translate(self->u.rect.end); + GdkGC *gc; + + sort_coord(&min, &max); + gc = gc_obj[get_mode(self)]; + set_width(gc, self->u.rect.width/draw_ctx.scale); + gdk_draw_rectangle(DA, gc, FALSE, + min.x, min.y, max.x-min.x, max.y-min.y); +} + + +/* ----- pad --------------------------------------------------------------- */ + + +unit_type gui_dist_pad(struct inst *self, struct coord pos, unit_type scale) +{ + unit_type d; + + if (inside_rect(pos, self->base, self->u.pad.other)) + return SELECT_R; + d = dist_rect(pos, self->base, self->u.pad.other)/scale; + return d > SELECT_R ? -1 : d; +} + + +static void pad_text_in_rect(struct inst *self, + struct coord min, struct coord max) +{ + GdkGC *gc; + struct coord c; + unit_type h, w; + int rot; + + w = max.x-min.x; + h = max.y-min.y; + rot = w/1.1 < h; + gc = gc_ptext[get_mode(self)]; + c = add_vec(min, max); + h = max.y-min.y; + w = max.x-min.x; + render_text(DA, gc, c.x/2, c.y/2, rot ? 0 : 90, + self->u.pad.name, PAD_FONT, 0.5, 0.5, + w-2*PAD_BORDER, h-2*PAD_BORDER); +} + + +static void maximize_box(struct coord *min_box, struct coord *max_box, + unit_type x_min, unit_type y_min, unit_type x_max, unit_type y_max) +{ + unit_type d_box, d_new, d; + + d_box = max_box->x-min_box->x; + d = max_box->y-min_box->y; + if (d < d_box) + d_box = d; + + d_new = x_max-x_min; + d = y_max-y_min; + if (d < d_new) + d_new = d; + + if (d_new < d_box) + return; + + min_box->x = x_min; + min_box->y = y_min; + max_box->x = x_max; + max_box->y = y_max; +} + + +static void gui_draw_pad_text(struct inst *self) +{ + struct coord pad_min = translate(self->base); + struct coord pad_max = translate(self->u.pad.other); + struct coord hole_min, hole_max; + struct coord box_min, box_max; + + sort_coord(&pad_min, &pad_max); + if (!self->u.pad.hole) { + pad_text_in_rect(self, pad_min, pad_max); + return; + } + + hole_min = translate(self->u.pad.hole->base); + hole_max = translate(self->u.pad.hole->u.hole.other); + sort_coord(&hole_min, &hole_max); + + box_min.x = pad_min.x; /* top */ + box_min.y = pad_min.y; + box_max.x = pad_max.x; + box_max.y = hole_min.y; + + maximize_box(&box_min, &box_max, + pad_min.x, hole_max.y, pad_max.x, pad_max.y); /* bottom */ + maximize_box(&box_min, &box_max, + pad_min.x, pad_min.y, hole_min.x, pad_max.y); /* left */ + maximize_box(&box_min, &box_max, + hole_max.x, pad_min.y, pad_max.x, pad_max.y); /* right */ + + pad_text_in_rect(self, box_min, box_max); +} + + +static GdkGC *pad_gc(const struct inst *inst, int *fill) +{ + *fill = TRUE; + switch (layers_to_pad_type(inst->u.pad.layers)) { + case pt_bare: + return gc_pad_bare[get_mode(inst)]; + case pt_trace: + return gc_pad_trace[get_mode(inst)]; + case pt_mask: + *fill = FALSE; + return gc_pad_mask[get_mode(inst)]; + default: + return gc_pad[get_mode(inst)]; + } +} + + +void gui_draw_pad(struct inst *self) +{ + struct coord min = translate(self->base); + struct coord max = translate(self->u.pad.other); + GdkGC *gc; + int fill; + + gc = pad_gc(self, &fill); + sort_coord(&min, &max); + gdk_draw_rectangle(DA, gc, fill, + min.x, min.y, max.x-min.x, max.y-min.y); + + gui_draw_pad_text(self); +} + + +static void draw_rounded_rect(GdkGC *gc, struct coord a, struct coord b, + int fill) +{ + struct coord min = translate(a); + struct coord max = translate(b); + unit_type h, w, r; + + sort_coord(&min, &max); + h = max.y-min.y; + w = max.x-min.x; + if (h > w) { + r = w/2; + draw_arc(DA, gc, fill, min.x+r, max.y-r, r, 180, 0); + if (fill) { + gdk_draw_rectangle(DA, gc, fill, + min.x, min.y+r, w, h-2*r); + } else { + gdk_draw_line(DA, gc, min.x, min.y+r, min.x, max.y-r); + gdk_draw_line(DA, gc, max.x, min.y+r, max.x, max.y-r); + } + draw_arc(DA, gc, fill, min.x+r, min.y+r, r, 0, 180); + } else { + r = h/2; + draw_arc(DA, gc, fill, min.x+r, min.y+r, r, 90, 270); + if (fill) { + gdk_draw_rectangle(DA, gc, fill, + min.x+r, min.y, w-2*r, h); + } else { + gdk_draw_line(DA, gc, min.x+r, min.y, max.x-r, min.y); + gdk_draw_line(DA, gc, min.x+r, max.y, max.x-r, max.y); + } + draw_arc(DA, gc, fill, max.x-r, min.y+r, r, 270, 90); + } +} + + +void gui_draw_rpad(struct inst *self) +{ + GdkGC *gc; + int fill; + + gc = pad_gc(self, &fill); + draw_rounded_rect(gc, self->base, self->u.pad.other, fill); + gui_draw_pad_text(self); +} + + +/* ----- hole -------------------------------------------------------------- */ + + +unit_type gui_dist_hole(struct inst *self, struct coord pos, unit_type scale) +{ + unit_type d; + + /* @@@ not quite right ... */ + if (inside_rect(pos, self->base, self->u.hole.other)) + return SELECT_R; + d = dist_rect(pos, self->base, self->u.hole.other)/scale; + return d > SELECT_R ? -1 : d; +} + + +void gui_draw_hole(struct inst *self) +{ + draw_rounded_rect(gc_hole[get_mode(self)], + self->base, self->u.hole.other, 1); + draw_rounded_rect(gc_rim[get_mode(self)], + self->base, self->u.hole.other, 0); +} + + +/* ----- arc --------------------------------------------------------------- */ + + +unit_type gui_dist_arc(struct inst *self, struct coord pos, unit_type scale) +{ + struct coord c = self->base; + struct coord p; + unit_type r, d_min, d; + double angle, a1, a2; + + r = self->u.arc.width/scale/2; + if (r < SELECT_R) + r = SELECT_R; + + /* check endpoints */ + + p = rotate_r(c, self->u.arc.r, self->u.arc.a1); + d_min = hypot(pos.x-p.x, pos.y-p.y); + + p = rotate_r(c, self->u.arc.r, self->u.arc.a2); + d = hypot(pos.x-p.x, pos.y-p.y); + if (d < d_min) + d_min = d; + + if (d_min/scale <= r) + return d; + + /* distance from the circle containing the arc */ + + d = dist_circle(pos, c, self->u.arc.r)/scale; + if (d > r) + return -1; + if (self->u.arc.a1 == self->u.arc.a2) + return d; + + /* see if we're close to the part that's actually drawn */ + + angle = theta(c, pos); + a1 = self->u.arc.a1; + a2 = self->u.arc.a2; + if (angle < 0) + angle += 360; + if (a2 < a1) + a2 += 360; + if (angle < a1) + angle += 360; + return angle >= a1 && angle <= a2 ? d : -1; +} + + +void gui_draw_arc(struct inst *self) +{ + struct coord center = translate(self->base); + GdkGC *gc; + + gc = gc_obj[get_mode(self)]; + set_width(gc, self->u.arc.width/draw_ctx.scale); + draw_arc(DA, gc, FALSE, center.x, center.y, + self->u.arc.r/draw_ctx.scale, self->u.arc.a1, self->u.arc.a2); +} + + +/* ----- meas -------------------------------------------------------------- */ + + +static struct coord offset_vec(struct coord a, struct coord b, + const struct inst *self) +{ + struct coord res; + double f; + + res.x = a.y-b.y; + res.y = b.x-a.x; + if (res.x == 0 && res.y == 0) + return res; + f = self->u.meas.offset/hypot(res.x, res.y); + res.x *= f; + res.y *= f; + return res; +} + + +void project_meas(const struct inst *inst, struct coord *a1, struct coord *b1) +{ + const struct meas *meas = &inst->obj->u.meas; + struct coord off; + + *a1 = inst->base; + *b1 = inst->u.meas.end; + switch (meas->type) { + case mt_xy_next: + case mt_xy_max: + break; + case mt_x_next: + case mt_x_max: + b1->y = a1->y; + break; + case mt_y_next: + case mt_y_max: + b1->x = a1->x; + break; + default: + abort(); + } + off = offset_vec(*a1, *b1, inst); + *a1 = add_vec(*a1, off); + *b1 = add_vec(*b1, off); +} + + +unit_type gui_dist_meas(struct inst *self, struct coord pos, unit_type scale) +{ + struct coord a1, b1; + unit_type d; + + project_meas(self, &a1, &b1); + d = dist_line(pos, a1, b1)/scale; + return d > SELECT_R ? -1 : d; +} + + +char *format_len(const char *label, unit_type len, enum curr_unit unit) +{ + const char *u = ""; + double n; + int mm; + + switch (unit) { + case curr_unit_mm: + n = units_to_mm(len); + mm = 1; + break; + case curr_unit_mil: + n = units_to_mil(len); + mm = 0; + break; + case curr_unit_auto: + n = units_to_best(len, &mm); + u = mm ? "mm" : "mil"; + break; + default: + abort(); + } + return stralloc_printf(mm ? + "%s" MM_FORMAT_SHORT "%s" : + "%s" MIL_FORMAT_SHORT "%s", + label, n, u); +} + + +void gui_draw_meas(struct inst *self) +{ + const struct meas *meas = &self->obj->u.meas; + struct coord a0, b0, a1, b1, c, d; + GdkGC *gc; + double len; + char *s; + + a0 = translate(self->base); + b0 = translate(self->u.meas.end); + project_meas(self, &a1, &b1); + + len = dist_point(a1, b1); + a1 = translate(a1); + b1 = translate(b1); + gc = gc_meas[get_mode(self)]; + gdk_draw_line(DA, gc, a0.x, a0.y, a1.x, a1.y); + gdk_draw_line(DA, gc, b0.x, b0.y, b1.x, b1.y); + gdk_draw_line(DA, gc, a1.x, a1.y, b1.x, b1.y); + draw_arrow(gc, FALSE, a1, b1, MEAS_ARROW_LEN, MEAS_ARROW_ANGLE); + draw_arrow(gc, FALSE, b1, a1, MEAS_ARROW_LEN, MEAS_ARROW_ANGLE); + + c = add_vec(a1, b1); + d = sub_vec(b1, a1); + s = format_len(meas->label ? meas->label : "", len, curr_unit); + render_text(DA, gc, c.x/2, c.y/2, -atan2(d.y, d.x)/M_PI*180, s, + MEAS_FONT, 0.5, -MEAS_BASELINE_OFFSET, + dist_point(a1, b1)-1.5*MEAS_ARROW_LEN, 0); + free(s); +} + + +/* ----- frame ------------------------------------------------------------- */ + + +unit_type gui_dist_frame_eye(struct inst *self, struct coord pos, + unit_type scale) +{ + unit_type d; + + d = dist_point(pos, self->base)/scale; + return d > FRAME_EYE_R2 ? -1 : d; +} + + +static unit_type dist_from_corner_line(struct inst *self, struct coord pos, + struct coord vec, unit_type scale) +{ + struct coord ref; + + ref.x = self->bbox.min.x; + ref.y = self->bbox.max.y; + return dist_line(pos, ref, add_vec(ref, vec))/scale; +} + + +unit_type gui_dist_frame(struct inst *self, struct coord pos, unit_type scale) +{ + unit_type d_min, d; + struct coord vec; + + d_min = dist_point(pos, self->base)/scale; + + vec.x = FRAME_SHORT_X*scale; + vec.y = 0; + d = dist_from_corner_line(self, pos, vec, scale); + if (d < d_min) + d_min = d; + + vec.x = 0; + vec.y = FRAME_SHORT_Y*scale; + d = dist_from_corner_line(self, pos, vec, scale); + if (d < d_min) + d_min = d; + + return d_min > SELECT_R ? -1 : d_min; +} + + +void gui_draw_frame(struct inst *self) +{ + struct coord center = translate(self->base); + struct coord corner = { self->bbox.min.x, self->bbox.max.y }; + GdkGC *gc; + + gc = self->u.frame.active ? gc_active_frame : gc_frame[get_mode(self)]; + draw_eye(gc, center, FRAME_EYE_R1, FRAME_EYE_R2); + if (self->u.frame.ref == frames) + return; + corner = translate(corner); + corner.x -= FRAME_CLEARANCE; + corner.y -= FRAME_CLEARANCE; + gdk_draw_line(DA, gc, corner.x, corner.y, + corner.x+FRAME_SHORT_X, corner.y); + gdk_draw_line(DA, gc, corner.x, corner.y, + corner.x, corner.y+FRAME_SHORT_Y); + render_text(DA, gc, corner.x, corner.y, 0, self->u.frame.ref->name, + FRAME_FONT, 0, -FRAME_BASELINE_OFFSET, 0, 0); +} diff --git a/gui_inst.h b/gui_inst.h new file mode 100644 index 0000000..bd69edc --- /dev/null +++ b/gui_inst.h @@ -0,0 +1,55 @@ +/* + * gui_inst.h - GUI, instance functions + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_INST_H +#define GUI_INST_H + +#include <gtk/gtk.h> + +#include "coord.h" +#include "inst.h" +#include "gui_status.h" + + +struct coord translate(struct coord pos); +struct coord canvas_to_coord(int x, int y); + +unit_type gui_dist_vec(struct inst *self, struct coord pos, unit_type scale); +unit_type gui_dist_vec_fallback(struct inst *self, struct coord pos, + unit_type scale); +unit_type gui_dist_line(struct inst *self, struct coord pos, unit_type scale); +unit_type gui_dist_rect(struct inst *self, struct coord pos, unit_type scale); +unit_type gui_dist_pad(struct inst *self, struct coord pos, unit_type scale); +unit_type gui_dist_hole(struct inst *self, struct coord pos, unit_type scale); +unit_type gui_dist_arc(struct inst *self, struct coord pos, unit_type scale); +unit_type gui_dist_meas(struct inst *self, struct coord pos, unit_type scale); +unit_type gui_dist_frame(struct inst *self, struct coord pos, unit_type scale); +unit_type gui_dist_frame_eye(struct inst *self, struct coord pos, + unit_type scale); + +void project_meas(const struct inst *inst, struct coord *a1, struct coord *b1); +char *format_len(const char *label, unit_type len, enum curr_unit unit); + +void gui_draw_vec(struct inst *self); +void gui_draw_line(struct inst *self); +void gui_draw_rect(struct inst *self); +void gui_draw_pad(struct inst *self); +void gui_draw_rpad(struct inst *self); +void gui_draw_hole(struct inst *self); +void gui_draw_arc(struct inst *self); +void gui_draw_meas(struct inst *self); +void gui_draw_frame(struct inst *self); + +void gui_highlight_vec(struct inst *self); + +#endif /* !GUI_INST_H */ diff --git a/gui_meas.c b/gui_meas.c new file mode 100644 index 0000000..29d1f27 --- /dev/null +++ b/gui_meas.c @@ -0,0 +1,470 @@ +/* + * gui_meas.c - GUI, measurements + * + * Written 2009, 2010 by Werner Almesberger +* Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#include "util.h" +#include "coord.h" +#include "meas.h" +#include "inst.h" +#include "gui_canvas.h" +#include "gui_tool.h" +#include "gui_meas.h" + + +static struct inst *meas_inst; /* point from which we're dragging */ + +static enum { + min_to_next_or_max, + max_to_min, + next_to_min, +} mode; + + +/* ----- measurement type characteristics ---------------------------------- */ + + +static struct meas_dsc { + lt_op_type lt; + enum meas_type type; +} *meas_dsc; + + +static struct meas_dsc meas_dsc_xy = { + .lt = lt_xy, + .type = mt_xy_next, +}; + + +static struct meas_dsc meas_dsc_x = { + .lt = lt_x, + .type = mt_x_next, +}; + + +static struct meas_dsc meas_dsc_y = { + .lt = lt_y, + .type = mt_y_next, +}; + + +/* ----- min/next/max tester ----------------------------------------------- */ + + +static int is_min(lt_op_type lt, const struct inst *inst) +{ + const struct sample *min; + + min = meas_find_min(lt, active_pkg->samples[inst->vec->n], NULL); + return coord_eq(inst->u.vec.end, min->pos); +} + + +static int is_next(lt_op_type lt, + const struct inst *inst, const struct inst *ref) +{ + const struct sample *next; + + next = meas_find_next(lt, active_pkg->samples[inst->vec->n], + ref->u.vec.end, NULL); + return coord_eq(inst->u.vec.end, next->pos); +} + + +static int is_max(lt_op_type lt, const struct inst *inst) +{ + const struct sample *max; + + max = meas_find_max(lt, active_pkg->samples[inst->vec->n], NULL); + return coord_eq(inst->u.vec.end, max->pos); +} + + +static int is_a_next(lt_op_type lt, struct inst *inst) +{ + struct inst *a; + const struct sample *min, *next; + + for (a = insts_ip_vec(); a; a = a->next) { + min = meas_find_min(lt, active_pkg->samples[a->vec->n], NULL); + next = meas_find_next(lt, active_pkg->samples[inst->vec->n], + min->pos, NULL); + if (coord_eq(next->pos, inst->u.vec.end)) + return 1; + } + return 0; +} + + +#if 0 +static int is_min_of_next(lt_op_type lt, + const struct inst *inst, const struct inst *ref) +{ + struct coord min, next; + + min = meas_find_min(lt, inst->vec->samples); + next = meas_find_next(lt, ref->vec->samples, min); + return coord_eq(next, ref->u.vec.end); +} +#endif + + +/* ----- picker functions -------------------------------------------------- */ + + +static int meas_pick_vec_a(struct inst *inst, void *ctx) +{ + struct vec *vec = inst->vec; + + if (!active_pkg->samples[vec->n]) + return 0; + if (is_min(meas_dsc->lt, inst)) { + mode = min_to_next_or_max; + return 1; + } + if (is_max(meas_dsc->lt, inst)) { + mode = max_to_min; + return 1; + } + if (is_a_next(meas_dsc->lt, inst)) { + mode = next_to_min; + return 1; + } + return 0; +} + + +static int meas_pick_vec_b(struct inst *inst, void *ctx) +{ + struct vec *vec = inst->vec; + struct inst *a = ctx; + + if (!active_pkg->samples[vec->n]) + return 0; + switch (mode) { + case min_to_next_or_max: + if (is_max(meas_dsc->lt, inst)) + return 1; + if (is_next(meas_dsc->lt, inst, a)) + return 1; + return 0; + case max_to_min: + return is_min(meas_dsc->lt, inst); + case next_to_min: + if (!is_min(meas_dsc->lt, inst)) + return 0; + return is_next(meas_dsc->lt, a, inst); +// return is_min_of_next(meas_dsc->lt, inst, a); + default: + abort(); + } +} + + +/* ----- highlighting ------------------------------------------------------ */ + + +static void meas_highlight_a(void) +{ + inst_highlight_vecs(meas_pick_vec_a, NULL); +} + + +static void meas_highlight_b(void) +{ + inst_highlight_vecs(meas_pick_vec_b, meas_inst); +} + + +/* ----- meas -------------------------------------------------------------- */ + + +struct pix_buf *draw_move_meas(struct inst *inst, struct coord pos, int i) +{ + return draw_move_line_common(inst, inst->u.meas.end, pos, + inst->obj->u.meas.inverted ? 1-i : i); +} + + +/* ----- tool selection ---------------------------------------------------- */ + + +static void tool_selected_meas(void) +{ + highlight = meas_highlight_a; + redraw(); +} + + +static void tool_selected_meas_xy(void) +{ + meas_dsc = &meas_dsc_xy; + tool_selected_meas(); +} + + +static void tool_selected_meas_x(void) +{ + meas_dsc = &meas_dsc_x; + tool_selected_meas(); +} + + +static void tool_selected_meas_y(void) +{ + meas_dsc = &meas_dsc_y; + tool_selected_meas(); +} + + +static void tool_deselected_meas(void) +{ + highlight = NULL; + redraw(); +} + + +/* ----- find start point (new measurement) -------------------------------- */ + + +static int is_highlighted(struct inst *inst, void *user) +{ + return inst->u.vec.highlighted; +} + + +static struct inst *find_point_meas_new(struct coord pos) +{ + return inst_find_vec(pos, is_highlighted, NULL); +} + + +/* ----- begin dragging new measurement ------------------------------------ */ + + +static void begin_drag_new_meas(struct inst *inst) +{ + highlight = meas_highlight_b; + meas_inst = inst; + if (is_min(meas_dsc->lt, inst)) + mode = min_to_next_or_max; + else if (is_max(meas_dsc->lt, inst)) + mode = max_to_min; + else + mode = next_to_min; + redraw(); +} + + +/* ----- end dragging new measurement -------------------------------------- */ + + +static int end_new_meas(struct inst *from, struct inst *to) +{ + struct obj *obj; + struct meas *meas; + + meas_inst = NULL; + highlight = NULL; + if (from == to) + return 0; + /* it's safe to pass "from" here, but we may change it later */ + obj = new_obj_unconnected(ot_meas, from); + connect_obj(frames, obj); + meas = &obj->u.meas; + meas->label = NULL; + switch (mode) { + case min_to_next_or_max: + if (!is_max(meas_dsc->lt, to)) { + meas->type = meas_dsc->type; + } else { + meas->type = meas_dsc->type+3; + } + obj->base = from->vec; + meas->high = to->vec; + break; + case next_to_min: + meas->type = meas_dsc->type; + obj->base = to->vec; + meas->high = from->vec; + break; + case max_to_min: + meas->type = meas_dsc->type+3; + obj->base = to->vec; + meas->high = from->vec; + break; + default: + abort(); + } + meas->inverted = + mode == min_to_next_or_max && is_min(meas_dsc->lt, to) ? 0 : + meas_dsc->lt(from->u.vec.end, to->u.vec.end) != + (mode == min_to_next_or_max); + meas->offset = NULL; + meas_dsc = NULL; + /* we don't support qualifiers through the GUI yet */ + meas->low_qual = NULL; + meas->high_qual = NULL; + return 1; +} + + +static void cancel_drag_new_meas(void) +{ + meas_inst = NULL; + highlight = NULL; + redraw(); +} + + +/* ----- begin dragging existing measurement ------------------------------- */ + + +/* + * We didn't record which instance provided the vector we're using here, so we + * have to search for it now. + */ + +static struct inst *vec_at(const struct vec *vec, struct coord pos) +{ + struct inst *inst; + const struct sample *s; + + for (inst = insts_ip_vec(); inst; inst = inst->next) + if (inst->vec == vec) + for (s = active_pkg->samples[vec->n]; s; s = s->next) + if (coord_eq(s->pos, pos)) + return inst; + abort(); +} + + +void begin_drag_move_meas(struct inst *inst, int i) +{ + const struct meas *meas = &inst->obj->u.meas; + struct coord a, b; + + switch (meas->type) { + case mt_xy_next: + case mt_xy_max: + meas_dsc = &meas_dsc_xy; + break; + case mt_x_next: + case mt_x_max: + meas_dsc = &meas_dsc_x; + break; + case mt_y_next: + case mt_y_max: + meas_dsc = &meas_dsc_y; + break; + default: + abort(); + } + highlight = meas_highlight_b; + + /* + * We're setting up the same conditions as after picking the first + * point when making a new measurement. Thus, we set meas_inst to the + * vector to the endpoint we're not moving. + */ + a = inst->base; + b = inst->u.meas.end; + if (inst->obj->u.meas.inverted) + SWAP(a, b); + switch (i) { + case 0: + mode = meas->type < 3 ? next_to_min : max_to_min; + meas_inst = vec_at(inst->obj->u.meas.high, b); + break; + case 1: + mode = min_to_next_or_max; + meas_inst = vec_at(inst->obj->base, a); + break; + default: + abort(); + } +// redraw(); +} + + +/* ----- find end point (existing measurement) ----------------------------- */ + + +struct inst *find_point_meas_move(struct inst *inst, struct coord pos) +{ + return inst_find_vec(pos, is_highlighted, NULL); +} + + +/* ----- end dragging existing measurements -------------------------------- */ + + +void end_drag_move_meas(void) +{ + highlight = NULL; + redraw(); +} + + +void do_move_to_meas(struct inst *inst, struct inst *to, int i) +{ + struct meas *meas = &inst->obj->u.meas; + + switch (i) { + case 0: + inst->obj->base = inst_get_vec(to); + break; + case 1: + meas->high = inst_get_vec(to); + if (is_max(meas_dsc->lt, to)) + meas->type = (meas->type % 3)+3; + else + meas->type = (meas->type % 3); + break; + default: + abort(); + } +} + + +/* ----- operations -------------------------------------------------------- */ + + +struct tool_ops tool_meas_ops = { + .tool_selected = tool_selected_meas_xy, + .tool_deselected= tool_deselected_meas, + .find_point = find_point_meas_new, + .begin_drag_new = begin_drag_new_meas, + .drag_new = drag_new_line, + .end_new = end_new_meas, + .cancel_drag_new= cancel_drag_new_meas, +}; + +struct tool_ops tool_meas_ops_x = { + .tool_selected = tool_selected_meas_x, + .tool_deselected= tool_deselected_meas, + .find_point = find_point_meas_new, + .begin_drag_new = begin_drag_new_meas, + .drag_new = drag_new_line, + .end_new = end_new_meas, + .cancel_drag_new= cancel_drag_new_meas, +}; + + +struct tool_ops tool_meas_ops_y = { + .tool_selected = tool_selected_meas_y, + .tool_deselected= tool_deselected_meas, + .find_point = find_point_meas_new, + .begin_drag_new = begin_drag_new_meas, + .drag_new = drag_new_line, + .end_new = end_new_meas, + .cancel_drag_new= cancel_drag_new_meas, +}; diff --git a/gui_meas.h b/gui_meas.h new file mode 100644 index 0000000..e626221 --- /dev/null +++ b/gui_meas.h @@ -0,0 +1,30 @@ +/* + * gui_meas.c - GUI, measurements + * + * Written 2009 by Werner Almesberger + * Copyright 2009 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_MEAS_H +#define GUI_MEAS_H + +#include "gui_tool.h" + + +extern struct tool_ops tool_meas_ops; +extern struct tool_ops tool_meas_ops_x; +extern struct tool_ops tool_meas_ops_y; + + +void begin_drag_move_meas(struct inst *inst, int i); +struct inst *find_point_meas_move(struct inst *inst, struct coord pos); +void end_drag_move_meas(void); +void do_move_to_meas(struct inst *inst, struct inst *to, int i); + +#endif /* !GUI_MEAS_H */ diff --git a/gui_over.c b/gui_over.c new file mode 100644 index 0000000..06e518a --- /dev/null +++ b/gui_over.c @@ -0,0 +1,215 @@ +/* + * gui_over.c - GUI, canvas overlays + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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 file is for the overlay state machine only. Given the heavy use of + * global variables, adding other functionality would quickly render it + * illegible. + */ + + +#include <stdlib.h> +#include <stdio.h> + +#include "coord.h" +#include "gui_util.h" +#include "gui_over.h" + + +#if 0 +#define DPRINTF(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__) +#define DSAVE(pix_buf) debug_save_pixbuf(pix_buf->buf) +#else +#define DPRINTF(fmt, ...) +#define DSAVE(buf) +#endif + + +static enum states { + NOTHING, + HOVER, + DRAG, + BOTH, +} state = NOTHING; + + +/* + * We cache some externally provided state so that we can redraw without the + * outside telling us what to redraw, etc. + */ + +static struct pix_buf *buf_D, *buf_H; +static struct pix_buf *(*over_D_save_and_draw)(void *user, struct coord to); +static void *over_D_user; +static struct pix_buf *(*over_H_save_and_draw)(void *user); +static void *over_H_user; +static struct coord over_pos; + + +/* ----- actions ----------------------------------------------------------- */ + + +static void draw_D(void) +{ + buf_D = over_D_save_and_draw(over_D_user, over_pos); + DSAVE(buf_D); +} + + +static void draw_H(void) +{ + buf_H = over_H_save_and_draw(over_H_user); + DSAVE(buf_H); +} + + +#define STATE(s) DPRINTF("%s", #s); state = s; break; +#define restore(x) DPRINTF(" restore(%s)", #x); restore_pix_buf(buf_##x) +#define drop(x) DPRINTF(" drop(%s)", #x); free_pix_buf(buf_##x) +#define save(x) DPRINTF(" save(%s)", #x) +#define draw(x) DPRINTF(" draw(%s)", #x); draw_##x() +#define update() DPRINTF(" update"); over_pos = pos + + +/* ----- state machine ----------------------------------------------------- */ + + +void over_enter(struct pix_buf *(*save_and_draw)(void *user), void *user) +{ + over_H_save_and_draw = save_and_draw; + over_H_user = user; + + DPRINTF("enter"); + switch (state) { + case NOTHING: + save(H); + draw(H); + STATE(HOVER); + case DRAG: + restore(D); + save(H); + draw(H); + save(D); + draw(D); + STATE(BOTH); + default: + abort(); + } +} + + +void over_leave(void) +{ + DPRINTF("leave"); + switch (state) { + case HOVER: + restore(H); + STATE(NOTHING); + case BOTH: + restore(D); + restore(H); + save(D); + draw(D); + STATE(DRAG); + default: + abort(); + } +} + + +void over_begin(struct pix_buf *(*save_and_draw)(void *user, struct coord to), + void *user, struct coord pos) +{ + over_pos = pos; + over_D_save_and_draw = save_and_draw; + over_D_user = user; + + DPRINTF("begin"); + switch (state) { + case NOTHING: + save(D); + draw(D); + STATE(DRAG); + case HOVER: + save(D); + draw(D); + STATE(BOTH); + default: + abort(); + } +} + + +void over_move(struct coord pos) +{ + over_pos = pos; + + DPRINTF("move"); + switch (state) { + case NOTHING: + break; + case HOVER: + break; + case DRAG: + restore(D); + update(); + save(D); + draw(D); + STATE(DRAG); + case BOTH: + restore(D); + update(); + save(D); + draw(D); + STATE(BOTH); + default: + abort(); + } +} + + +void over_end(void) +{ + DPRINTF("end"); + switch (state) { + case DRAG: + restore(D); + STATE(NOTHING); + case BOTH: + restore(D); + STATE(HOVER); + default: + abort(); + } +} + + +void over_reset(void) +{ + DPRINTF("reset"); + switch (state) { + case NOTHING: + break; + case HOVER: + drop(H); + STATE(NOTHING); + case DRAG: + drop(D); + STATE(NOTHING); + case BOTH: + drop(D); + drop(H); + STATE(NOTHING); + default: + abort(); + } +} diff --git a/gui_over.h b/gui_over.h new file mode 100644 index 0000000..20cb249 --- /dev/null +++ b/gui_over.h @@ -0,0 +1,79 @@ +/* + * gui_over.h - GUI, canvas overlays + * + * Written 2009 by Werner Almesberger + * Copyright 2009 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_OVER_H +#define GUI_OVER_H + +/* + * Dynamic changes around the pointer are affected by the following events: + * + * - enter: enter a circle where we're hovering + * - leave: leave a circle where we've been hovering + * - begin: begin dragging + * - move: move with or without dragging + * - end: end dragging + * - reset: we got a redraw, just drop everything + * + * We have the following states: + * + * - NOTHING: neither hovering nor dragging + * - HOVER: we're hovering but not dragging + * - DRAG: we're dragging but not hovering, e.g., when searching for a place to + * end the drag + * - BOTH: we're dragging and hovering + * + * Both drag and hover save the area being changed and restore it after a + * change. We have to make sure the save/draw/restore operations are properly + * sequenced. We call the hover area H, the drag area D. This is the state + * machine that does the sequencing: + * + * NOTHING (saved: -) + * enter -> save H, draw H, HOVER + * begin -> save D, draw D, DRAG + * move -> NOTHING + * reset -> NOTHING + * + * HOVER: (saved: H) + * leave -> restore H, NOTHING + * begin -> save D, draw D, BOTH + * move -> HOVER + * reset -> drop H, NOTHING + * + * DRAG: (saved: D) + * end -> restore D, NOTHING + * enter -> restore D, save H, draw H, save D, draw D, BOTH + * move -> restore D, update, save D, draw D, DRAG + * reset -> drop D, NOTHING + * + * BOTH: (saved: D on top of H) + * end -> restore D, HOVER + * leave -> restore D, restore H, save D, draw D, DRAG + * move -> restore D, update, save D, draw D, BOTH + * reset -> drop D, drop H, NOTHING + */ + +#include "coord.h" +#include "inst.h" + + +void over_enter(struct pix_buf *(*save_and_draw)(void *user), void *user); +void over_leave(void); + +void over_begin(struct pix_buf *(*save_and_draw)(void *user, struct coord pos), + void *user, struct coord pos); +void over_move(struct coord pos); +void over_end(void); + +void over_reset(void); + +#endif /* !GUI_OVER_H */ diff --git a/gui_status.c b/gui_status.c new file mode 100644 index 0000000..222a144 --- /dev/null +++ b/gui_status.c @@ -0,0 +1,1117 @@ +/* + * gui_status.c - GUI, status area + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "util.h" +#include "coord.h" +#include "error.h" +#include "unparse.h" +#include "obj.h" +#include "layer.h" +#include "gui_util.h" +#include "gui_style.h" +#include "gui_canvas.h" +#include "gui_frame.h" +#include "gui.h" +#include "gui_status.h" + + +enum edit_status { + es_unchanged, + es_good, + es_bad, +}; + + +struct edit_ops { + char *(*retrieve)(void *ctx); + enum edit_status (*status)(const char *s, void *ctx); + void (*store)(const char *s, void *ctx); +}; + + +enum curr_unit curr_unit = curr_unit_mm; + + +static GtkWidget *open_edits = NULL; +static GtkWidget *last_edit = NULL; + + +/* ----- setter functions -------------------------------------------------- */ + + +static GtkWidget *status_icon; +static GtkWidget *status_name, *status_entry; +static GtkWidget *status_type_x, *status_type_y, *status_type_entry; +static GtkWidget *status_box_x, *status_entry_y; +static GtkWidget *status_x, *status_y; +static GtkWidget *status_r, *status_angle; +static GtkWidget *status_sys_x, *status_sys_y; +static GtkWidget *status_user_x, *status_user_y; +static GtkWidget *status_zoom, *status_grid, *status_unit; +static GtkWidget *status_msg; + +/* The x entry area serves multiple purposes */ + +static GtkWidget *status_entry_x; + + +static void set_label(GtkWidget *label, const char *tooltip, + const char *fmt, va_list ap) +{ + char *s; + + s = stralloc_vprintf(fmt, ap); + gtk_label_set_text(GTK_LABEL(label), s); + gtk_widget_set_tooltip_markup(label, tooltip); + free(s); +} + + +#define SETTER(name) \ + void status_set_##name(const char *tooltip, const char *fmt, ...)\ + { \ + va_list ap; \ + \ + va_start(ap, fmt); \ + set_label(status_##name, tooltip, fmt, ap); \ + va_end(ap); \ + } + +SETTER(type_x) +SETTER(type_y) +SETTER(type_entry) +SETTER(name) +SETTER(x) +SETTER(y) +SETTER(r) +SETTER(angle) +SETTER(sys_x) +SETTER(sys_y) +SETTER(user_x) +SETTER(user_y) +SETTER(zoom) +SETTER(grid) +SETTER(unit) + + +/* ----- set things with units --------------------------------------------- */ + + +void set_with_units(void (*set)(const char *tooltip, const char *fmt, ...), + const char *prefix, unit_type u, const char *tooltip) +{ + double n; + int mm; + + switch (curr_unit) { + case curr_unit_mm: + n = units_to_mm(u); + mm = 1; + break; + case curr_unit_mil: + n = units_to_mil(u); + mm = 0; + break; + case curr_unit_auto: + n = units_to_best(u, &mm); + break; + default: + abort(); + } + if (mm) { + /* -NNN.NNN mm */ + set(tooltip, "%s" MM_FORMAT_FIXED " mm", prefix, n); + } else { + /* -NNNN.N mil */ + set(tooltip, "%s" MIL_FORMAT_FIXED " mil", prefix, n); + } +} + + +/* ----- complex status updates -------------------------------------------- */ + + +void status_set_icon(GtkWidget *image) +{ + vacate_widget(status_icon); + if (image) + gtk_container_add(GTK_CONTAINER(status_icon), image); + gtk_widget_show_all(status_icon); +} + + +void status_set_xy(struct coord coord) +{ + /* do dX/dY etc. stuff later */ + status_set_type_x(NULL, "X ="); + status_set_type_y(NULL, "Y ="); + + set_with_units(status_set_x, "", coord.x, "Width"); + set_with_units(status_set_y, "", coord.y, "Height"); +} + + +void status_set_angle_xy(const char *tooltip, struct coord v) +{ + if (!v.x && !v.y) + status_set_angle(tooltip, "a = 0 deg"); + else + status_set_angle(tooltip, "a = %3.1f deg", theta_vec(v)); +} + + +static void entry_color(GtkWidget *widget, const char *color) +{ + GdkColor col; + + col = get_color(color); + gtk_widget_modify_base(widget, GTK_STATE_NORMAL, &col); +} + + +/* ----- pad type display and change --------------------------------------- */ + + +static enum pad_type *curr_pad_type; +static GtkWidget *pad_type; + + +static void show_pad_type(void) +{ + gtk_label_set_text(GTK_LABEL(pad_type), pad_type_name(*curr_pad_type)); +} + + +static gboolean pad_type_button_press_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + switch (event->button) { + case 1: + *curr_pad_type = (*curr_pad_type+1) % pt_n; + show_pad_type(); + break; + } + /* + * We can't just redraw() here, because changing the pad type may also + * affect the visual stacking. So we change the world and hope we end + * up selecting the same pad afterwards. + */ + change_world_reselect(); + return TRUE; +} + + +void edit_pad_type(enum pad_type *type) +{ + vacate_widget(status_box_x); + curr_pad_type = type; + pad_type = label_in_box_new(NULL, "Pad type. Click to cycle."); + gtk_container_add(GTK_CONTAINER(status_box_x), box_of_label(pad_type)); + label_in_box_bg(pad_type, COLOR_SELECTOR); + g_signal_connect(G_OBJECT(box_of_label(pad_type)), + "button_press_event", G_CALLBACK(pad_type_button_press_event), + NULL); + show_pad_type(); + gtk_widget_show_all(status_box_x); +} + + +/* ----- edit helper functions --------------------------------------------- */ + + +static void reset_edit(GtkWidget *widget) +{ + struct edit_ops *ops; + void *ctx; + char *s; + + ops = gtk_object_get_data(GTK_OBJECT(widget), "edit-ops"); + ctx = gtk_object_get_data(GTK_OBJECT(widget), "edit-ctx"); + assert(ops); + s = ops->retrieve(ctx); + gtk_object_set_data(GTK_OBJECT(widget), "edit-ops", NULL); + gtk_entry_set_text(GTK_ENTRY(widget), s); + free(s); + entry_color(widget, COLOR_EDIT_ASIS); + gtk_object_set_data(GTK_OBJECT(widget), "edit-ops", ops); +} + + +static void reset_edits(void) +{ + GtkWidget *edit; + + for (edit = open_edits; edit; + edit = gtk_object_get_data(GTK_OBJECT(edit), "edit-next")) + reset_edit(edit); + gtk_widget_grab_focus(GTK_WIDGET(open_edits)); +} + + +static gboolean edit_key_press_event(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + GtkWidget *next = gtk_object_get_data(GTK_OBJECT(widget), "edit-next"); + + switch (event->keyval) { + case GDK_Tab: + gtk_widget_grab_focus(GTK_WIDGET(next ? next : open_edits)); + return TRUE; + case GDK_Escape: + reset_edits(); + return TRUE; + default: + return FALSE; + } +} + + +static void setup_edit(GtkWidget *widget, struct edit_ops *ops, void *ctx, + const char *tooltip) +{ + gtk_object_set_data(GTK_OBJECT(widget), "edit-ops", ops); + gtk_object_set_data(GTK_OBJECT(widget), "edit-ctx", ctx); + gtk_object_set_data(GTK_OBJECT(widget), "edit-next", NULL); + + reset_edit(widget); + + if (!open_edits) + gtk_widget_grab_focus(GTK_WIDGET(widget)); + gtk_widget_show(widget); + + g_signal_connect(G_OBJECT(widget), "key_press_event", + G_CALLBACK(edit_key_press_event), open_edits); + + if (last_edit) + gtk_object_set_data(GTK_OBJECT(last_edit), "edit-next", widget); + else + open_edits = widget; + last_edit = widget; + gtk_widget_set_tooltip_markup(widget, tooltip); +} + + +/* ----- identifier fields ------------------------------------------------- */ + + +struct edit_unique_ctx { + const char **s; + int (*validate)(const char *s, void *ctx); + void *ctx; +}; + + +/* + * Handle NULL so that we can also use it for unique_null + */ + +static char *unique_retrieve(void *ctx) +{ + struct edit_unique_ctx *unique_ctx = ctx; + + return stralloc(*unique_ctx->s ? *unique_ctx->s : ""); +} + + +static enum edit_status unique_status(const char *s, void *ctx) +{ + const struct edit_unique_ctx *unique_ctx = ctx; + + if (!strcmp(s, *unique_ctx->s)) + return es_unchanged; + return !unique_ctx->validate || + unique_ctx->validate(s, unique_ctx->ctx) ? es_good : es_bad; +} + + +static void unique_store(const char *s, void *ctx) +{ + const struct edit_unique_ctx *unique_ctx = ctx; + + *unique_ctx->s = unique(s); +} + + +static struct edit_ops edit_ops_unique = { + .retrieve = unique_retrieve, + .status = unique_status, + .store = unique_store, +}; + + +void edit_unique(const char **s, int (*validate)(const char *s, void *ctx), + void *ctx, const char *tooltip) +{ + static struct edit_unique_ctx unique_ctx; + + unique_ctx.s = s; + unique_ctx.validate = validate; + unique_ctx.ctx = ctx; + setup_edit(status_entry, &edit_ops_unique, &unique_ctx, tooltip); +} + + +/* ----- identifier fields with NULL --------------------------------------- */ + + +static enum edit_status unique_null_status(const char *s, void *ctx) +{ + const struct edit_unique_ctx *unique_ctx = ctx; + + if (!strcmp(s, *unique_ctx->s ? *unique_ctx->s : "")) + return es_unchanged; + if (!*s) + return es_good; + return !unique_ctx->validate || + unique_ctx->validate(s, unique_ctx->ctx) ? es_good : es_bad; +} + + +static void unique_null_store(const char *s, void *ctx) +{ + const struct edit_unique_ctx *unique_ctx = ctx; + + if (!*s) + *unique_ctx->s = NULL; + else + *unique_ctx->s = unique(s); +} + + +static struct edit_ops edit_ops_null_unique = { + .retrieve = unique_retrieve, + .status = unique_null_status, + .store = unique_null_store, +}; + + +void edit_unique_null(const char **s, + int (*validate)(const char *s, void *ctx), void *ctx, const char *tooltip) +{ + static struct edit_unique_ctx unique_ctx; + + unique_ctx.s = s; + unique_ctx.validate = validate; + unique_ctx.ctx = ctx; + setup_edit(status_entry, &edit_ops_null_unique, &unique_ctx, tooltip); +} + + +/* ----- unique field (variable) optionally followed by values ------------- */ + + +struct edit_unique_with_values_ctx { + const char **s; + int (*validate)(const char *s, void *ctx); + void *ctx; + void (*set_values)(void *user, const struct value *values, + int n_values); + void *user; + int max_values; +}; + + +static int unique_with_values_check(const char *s, + const struct edit_unique_with_values_ctx *unique_ctx) +{ + const char *id; + struct value *values; + int n; + + status_begin_reporting(); + n = parse_var(s, &id, &values, unique_ctx->max_values); + if (n < 0) + return 0; + free_values(values, 0); + return unique_ctx->validate ? + unique_ctx->validate(id, unique_ctx->ctx) : 0; +} + + +static enum edit_status unique_with_values_status(const char *s, void *ctx) +{ + const struct edit_unique_with_values_ctx *unique_ctx = ctx; + + if (!strcmp(s, *unique_ctx->s)) + return es_unchanged; + return unique_with_values_check(s, unique_ctx) ? es_good : es_bad; +} + + +static void unique_with_values_store(const char *s, void *ctx) +{ + const struct edit_unique_with_values_ctx *unique_ctx = ctx; + struct value *values; + int n; + + status_begin_reporting(); + n = parse_var(s, unique_ctx->s, &values, unique_ctx->max_values); + if (!n) + return; + assert(n >= 0); + assert(unique_ctx->max_values == -1 || n <= unique_ctx->max_values); + unique_ctx->set_values(unique_ctx->user, values, n); + free_values(values, 1); +} + + +static struct edit_ops edit_ops_unique_with_values = { + .retrieve = unique_retrieve, + .status = unique_with_values_status, + .store = unique_with_values_store, +}; + + +void edit_unique_with_values(const char **s, + int (*validate)(const char *s, void *ctx), void *ctx, + void (*set_values)(void *user, const struct value *values, int n_values), + void *user, int max_values, + const char *tooltip) +{ + static struct edit_unique_with_values_ctx unique_ctx; + + unique_ctx.s = s; + unique_ctx.validate = validate; + unique_ctx.ctx = ctx; + unique_ctx.set_values = set_values; + unique_ctx.user = user; + unique_ctx.max_values = max_values; + setup_edit(status_entry, &edit_ops_unique_with_values, &unique_ctx, + tooltip); +} + + +/* ----- variable type display and change ---------------------------------- */ + + +static struct var *curr_var; +static GtkWidget *var_type; + + +static void show_var_type(void) +{ + gtk_label_set_text(GTK_LABEL(var_type), + curr_var->key ? "key" : "assign"); +} + + +/* + * This is a bit of a layering violation. Note that we can't just try to + * activate the edit and see what happens, because unique_with_values_status + * would unconditionally accept the previous value, even if it is no longer + * acceptable after the type change. + */ + +static int attempt_var_type_change(struct var *var) +{ + const struct edit_unique_with_values_ctx *ctx; + const char *s; + + ctx = gtk_object_get_data(GTK_OBJECT(status_entry), "edit-ctx"); + s = gtk_entry_get_text(GTK_ENTRY(status_entry)); + var->key = !var->key; + if (unique_with_values_check(s, ctx)) + return 1; + var->key = !var->key; + return 0; +} + + +static gboolean do_activate(void); + + +static gboolean var_type_button_press_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + switch (event->button) { + case 1: + if (!attempt_var_type_change(curr_var)) + return TRUE; + /* commit edit and change_world() */ + do_activate(); + reselect_var(curr_var); + return TRUE; + } + return TRUE; +} + + +void edit_var_type(struct var *var) +{ + vacate_widget(status_box_x); + curr_var = var; + var_type = label_in_box_new(NULL, "Variable type. Click to cycle."); + gtk_container_add(GTK_CONTAINER(status_box_x), box_of_label(var_type)); + label_in_box_bg(var_type, COLOR_SELECTOR); + g_signal_connect(G_OBJECT(box_of_label(var_type)), + "button_press_event", G_CALLBACK(var_type_button_press_event), + NULL); + show_var_type(); + gtk_widget_show_all(status_box_x); +} + + +/* ----- string fields ----------------------------------------------------- */ + + +struct edit_name_ctx { + char **s; + int (*validate)(const char *s, void *ctx); + void *ctx; +}; + + +static char *name_retrieve(void *ctx) +{ + struct edit_name_ctx *name_ctx = ctx; + + return stralloc(*name_ctx->s ? *name_ctx->s : ""); +} + + +static enum edit_status name_status(const char *s, void *ctx) +{ + const struct edit_name_ctx *name_ctx = ctx; + + if (!strcmp(s, *name_ctx->s)) + return es_unchanged; + return !name_ctx->validate || name_ctx->validate(s, name_ctx->ctx) ? + es_good : es_bad; +} + + +static void name_store(const char *s, void *ctx) +{ + const struct edit_name_ctx *name_ctx = ctx; + + free(*name_ctx->s); + *name_ctx->s = stralloc(s); +} + + +static struct edit_ops edit_ops_name = { + .retrieve = name_retrieve, + .status = name_status, + .store = name_store, +}; + + +void edit_name(char **s, int (*validate)(const char *s, void *ctx), void *ctx, + const char *tooltip) +{ + static struct edit_name_ctx name_ctx; + + name_ctx.s = s; + name_ctx.validate = validate; + name_ctx.ctx = ctx; + setup_edit(status_entry, &edit_ops_name, &name_ctx, tooltip); +} + + +/* ----- expression fields ------------------------------------------------- */ + + +static struct expr *try_parse_expr(const char *s) +{ + status_begin_reporting(); + return parse_expr(s); +} + + +static char *expr_retrieve(void *ctx) +{ + struct expr **expr = ctx; + + return unparse(*expr); +} + + +static enum edit_status expr_status(const char *s, void *ctx) +{ + struct expr *expr; + + expr = try_parse_expr(s); + if (!expr) + return es_bad; + free_expr(expr); + return es_good; +} + + +static void expr_store(const char *s, void *ctx) +{ + struct expr **anchor = ctx; + struct expr *expr; + + expr = try_parse_expr(s); + assert(expr); + if (*anchor) + free_expr(*anchor); + *anchor = expr; +} + + +static struct edit_ops edit_ops_expr = { + .retrieve = expr_retrieve, + .status = expr_status, + .store = expr_store, +}; + + +void edit_expr(struct expr **expr, const char *tooltip) +{ + setup_edit(status_entry, &edit_ops_expr, expr, tooltip); +} + + +/* ----- distance expressions ---------------------------------------------- */ + + +static void dist_expr_store(const char *s, void *ctx) +{ + struct expr **anchor = ctx; + struct expr *expr; + + expr_store(s, ctx); + expr = *anchor; + if (expr->op == op_num && !expr->u.num.exponent && !expr->u.num.n) + expr->u.num.exponent = 1; +} + + +static struct edit_ops edit_ops_dist_expr = { + .retrieve = expr_retrieve, + .status = expr_status, + .store = dist_expr_store, +}; + + +static void edit_any_dist_expr(GtkWidget *widget, struct expr **expr, + const char *tooltip) +{ + setup_edit(widget, &edit_ops_dist_expr, expr, tooltip); +} + + +void edit_dist_expr(struct expr **expr, const char *tooltip) +{ + edit_any_dist_expr(status_entry, expr, tooltip); +} + + +void edit_x(struct expr **expr, const char *tooltip) +{ + vacate_widget(status_box_x); + gtk_container_add(GTK_CONTAINER(status_box_x), status_entry_x); + gtk_widget_show(status_box_x); + edit_any_dist_expr(status_entry_x, expr, tooltip); +} + + +void edit_y(struct expr **expr, const char *tooltip) +{ + edit_any_dist_expr(status_entry_y, expr, tooltip); +} + + +/* ----- expression list --------------------------------------------------- */ + + +struct edit_expr_list_ctx { + struct expr *expr; + void (*set_values)(void *user, const struct value *values, + int n_values); + void *user; +}; + + +static char *expr_list_retrieve(void *ctx) +{ + struct edit_expr_list_ctx *expr_list_ctx = ctx; + + return unparse(expr_list_ctx->expr); +} + + +static enum edit_status expr_list_status(const char *s, void *ctx) +{ + struct value *values; + int n; + + status_begin_reporting(); + n = parse_values(s, &values); + if (n < 0) + return es_bad; + free_values(values, 0); + return es_good; +} + + +static void expr_list_store(const char *s, void *ctx) +{ + struct edit_expr_list_ctx *expr_list_ctx = ctx; + struct value *values; + int n; + + status_begin_reporting(); + n = parse_values(s, &values); + assert(n >= 0); + expr_list_ctx->set_values(expr_list_ctx->user, values, n); + free_values(values, 1); +} + + +static struct edit_ops edit_ops_expr_list = { + .retrieve = expr_list_retrieve, + .status = expr_list_status, + .store = expr_list_store, +}; + + +void edit_expr_list(struct expr *expr, + void (*set_values)(void *user, const struct value *values, int n_values), + void *user, const char *tooltip) +{ + static struct edit_expr_list_ctx expr_list_ctx; + + expr_list_ctx.expr = expr; + expr_list_ctx.set_values = set_values; + expr_list_ctx.user = user; + setup_edit(status_entry, &edit_ops_expr_list, &expr_list_ctx, tooltip); +} + + +/* ----- text entry -------------------------------------------------------- */ + + +static enum edit_status get_status(GtkWidget *widget) +{ + struct edit_ops *ops; + void *ctx; + const char *s; + + ops = gtk_object_get_data(GTK_OBJECT(widget), "edit-ops"); + if (!ops) + return es_unchanged; + ctx = gtk_object_get_data(GTK_OBJECT(widget), "edit-ctx"); + s = gtk_entry_get_text(GTK_ENTRY(widget)); + return ops->status(s, ctx); +} + + +static void set_edit(GtkWidget *widget) +{ + struct edit_ops *ops; + void *ctx; + const char *s; + + ops = gtk_object_get_data(GTK_OBJECT(widget), "edit-ops"); + if (!ops) + return; + ctx = gtk_object_get_data(GTK_OBJECT(widget), "edit-ctx"); + s = gtk_entry_get_text(GTK_ENTRY(widget)); + if (ops->store) + ops->store(s, ctx); +} + + +static gboolean changed(GtkWidget *widget, GdkEventMotion *event, + gpointer data) +{ + switch (get_status(widget)) { + case es_unchanged: + entry_color(widget, COLOR_EDIT_ASIS); + break; + case es_good: + entry_color(widget, COLOR_EDIT_GOOD); + break; + case es_bad: + entry_color(widget, COLOR_EDIT_BAD); + break; + default: + abort(); + } + return TRUE; +} + + +static gboolean do_activate(void) +{ + GtkWidget *edit; + enum edit_status status; + int unchanged = 1; + + for (edit = open_edits; edit; + edit = gtk_object_get_data(GTK_OBJECT(edit), "edit-next")) { + status = get_status(edit); + if (status == es_bad) + return TRUE; + if (status == es_good) + unchanged = 0; + } + if (unchanged) { + inst_deselect(); + return TRUE; + } + for (edit = open_edits; edit; + edit = gtk_object_get_data(GTK_OBJECT(edit), "edit-next")) + if (get_status(edit) == es_good) { + entry_color(edit, COLOR_EDIT_ASIS); + set_edit(edit); + } + inst_deselect(); + change_world(); + return TRUE; +} + + +static gboolean activate(GtkWidget *widget, GdkEventMotion *event, + gpointer data) +{ + return do_activate(); +} + + +void edit_nothing(void) +{ + gtk_widget_hide(status_entry); + gtk_widget_hide(status_box_x); + gtk_widget_hide(status_entry_y); + open_edits = NULL; + last_edit = NULL; +} + + +/* ----- status reports ---------------------------------------------------- */ + + +static gint context_id; +static int have_msg = 0; + + +static void clear_status_msg(void) +{ + if (have_msg) { + gtk_statusbar_pop(GTK_STATUSBAR(status_msg), context_id); + have_msg = 0; + } +} + + +static void report_to_gui(const char *s) +{ + if (!have_msg) + gtk_statusbar_push(GTK_STATUSBAR(status_msg), context_id, s); + have_msg = 1; +} + + +void status_begin_reporting(void) +{ + clear_status_msg(); + reporter = report_to_gui; +} + + +/* ----- unit selection ---------------------------------------------------- */ + + +static void show_curr_unit(void) +{ + static const char *tip = "Display unit. Click to cycle."; + + switch (curr_unit) { + case curr_unit_mm: + status_set_unit(tip, "mm"); + break; + case curr_unit_mil: + status_set_unit(tip, "mil"); + break; + case curr_unit_auto: + status_set_unit(tip, "auto"); + break; + default: + abort(); + } +} + + +static gboolean unit_button_press_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + switch (event->button) { + case 1: + curr_unit = (curr_unit+1) % curr_unit_n; + show_curr_unit(); + break; + } + refresh_pos(); + change_world(); + return TRUE; +} + + +/* ----- setup ------------------------------------------------------------- */ + + +static GtkWidget *add_vbox(GtkWidget *tab, int col, int row) +{ + GtkWidget *vbox; + + vbox = gtk_vbox_new(FALSE, 0); + gtk_table_attach_defaults(GTK_TABLE(tab), vbox, + col, col+1, row, row+1); + return vbox; +} + + +static GtkWidget *add_label_basic(GtkWidget *tab, int col, int row) +{ + GtkWidget *label; + + label = label_in_box_new(NULL, NULL); + gtk_table_attach(GTK_TABLE(tab), box_of_label(label), + col, col+1, row, row+1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 1); + /* 0 , 1 - padding */ + return label; +} + + +static GtkWidget *add_label(GtkWidget *tab, int col, int row) +{ + GtkWidget *label; + + label = add_label_basic(tab, col, row); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_label_set_selectable(GTK_LABEL(label), TRUE); + return label; +} + + +static GtkWidget *make_entry(void) +{ + GtkWidget *entry; + + entry = gtk_entry_new(); + gtk_entry_set_has_frame(GTK_ENTRY(entry), FALSE); + + g_signal_connect(G_OBJECT(entry), "changed", + G_CALLBACK(changed), entry); + g_signal_connect(G_OBJECT(entry), "activate", + G_CALLBACK(activate), entry); + + return entry; +} + + +static GtkWidget *add_entry(GtkWidget *tab, int col, int row) +{ + GtkWidget *entry; + + entry = make_entry(); + gtk_table_attach_defaults(GTK_TABLE(tab), entry, + col, col+1, row, row+1); + return entry; +} + + +void make_status_area(GtkWidget *vbox) +{ + GtkWidget *tab, *sep; + GtkWidget *hbox, *vbox2; + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); + + vbox2 = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 1); + + status_icon = gtk_event_box_new(); + gtk_box_pack_start(GTK_BOX(vbox2), status_icon, FALSE, FALSE, 0); + + tab = gtk_table_new(7, 3, FALSE); + gtk_box_pack_start(GTK_BOX(hbox), tab, TRUE, TRUE, 0); + + /* types */ + + status_type_x = add_label(tab, 0, 0); + status_type_y = add_label(tab, 0, 1); + status_type_entry = add_label(tab, 0, 2); + + /* x / y */ + + status_x = add_label(tab, 1, 0); + status_box_x = add_vbox(tab, 2, 0); + status_y = add_label(tab, 1, 1); + status_entry_y = add_entry(tab, 2, 1); + + status_entry_x = gtk_widget_ref(make_entry()); + + /* name and input */ + + status_name = add_label(tab, 1, 2); + status_entry = add_entry(tab, 2, 2); + + /* separator */ + + sep = gtk_vseparator_new(); + gtk_table_attach_defaults(GTK_TABLE(tab), sep, 3, 4, 0, 3); + + /* sys / user pos */ + + status_sys_x = add_label(tab, 4, 0); + status_sys_y = add_label(tab, 4, 1); + status_user_x = add_label(tab, 5, 0); + status_user_y = add_label(tab, 5, 1); + + /* r / angle */ + + status_r = add_label(tab, 4, 2); + status_angle = add_label(tab, 5, 2); + + /* zoom / grid / unit */ + + status_zoom = add_label(tab, 6, 0); + status_grid = add_label(tab, 6, 1); + status_unit = add_label_basic(tab, 6, 2); + + /* unit selection */ + + label_in_box_bg(status_unit, COLOR_SELECTOR); + show_curr_unit(); + g_signal_connect(G_OBJECT(box_of_label(status_unit)), + "button_press_event", G_CALLBACK(unit_button_press_event), NULL); + + /* message bar */ + + status_msg = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(vbox), status_msg, FALSE, FALSE, 0); + + context_id = gtk_statusbar_get_context_id(GTK_STATUSBAR(status_msg), + "messages"); +} + + +void cleanup_status_area(void) +{ + gtk_widget_unref(status_entry_x); +} diff --git a/gui_status.h b/gui_status.h new file mode 100644 index 0000000..0101adc --- /dev/null +++ b/gui_status.h @@ -0,0 +1,91 @@ +/* + * gui_status.h - GUI, status area + * + * Written 2009, 2010, 2012 by Werner Almesberger + * Copyright 2009, 2010, 2012 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_STATUS_H +#define GUI_STATUS_H + +#include <gtk/gtk.h> + +#include "coord.h" +#include "expr.h" +#include "obj.h" + + +enum curr_unit { + curr_unit_mm, + curr_unit_mil, + curr_unit_auto, + curr_unit_n +}; + + +extern enum curr_unit curr_unit; + + +void edit_var_type(struct var *var); +void edit_pad_type(enum pad_type *type); + +void edit_unique(const char **s, int (*validate)(const char *s, void *ctx), + void *ctx, const char *tooltip); +void edit_unique_null(const char **s, int (*validate)(const char *s, void *ctx), + void *ctx, const char *tooltip); +void edit_unique_with_values(const char **s, + int (*validate)(const char *s, void *ctx), void *ctx, + void (*set_values)(void *user, const struct value *values, int n_values), + void *user, int max_values, const char *tooltip); +void edit_name(char **s, int (*validate)(const char *s, void *ctx), void *ctx, + const char *tooltip); +void edit_expr(struct expr **expr, const char *tooltip); +void edit_expr_list(struct expr *expr, + void (*set_values)(void *user, const struct value *values, int n_values), + void *user, const char *tooltip); +void edit_dist_expr(struct expr **expr, const char *tooltip); +void edit_x(struct expr **expr, const char *tooltip); +void edit_y(struct expr **expr, const char *tooltip); +void edit_nothing(void); + +void set_with_units(void (*set)(const char *tooltip, const char *fmt, ...), + const char *prefix, unit_type u, const char *tooltip); + +#define SETTER(name) \ + void status_set_##name(const char *tooltip, const char *fmt, ...) \ + __attribute__((format(printf, 2, 3))) \ + +SETTER(type_x); +SETTER(type_y); +SETTER(type_entry); +SETTER(name); +SETTER(x); +SETTER(y); +SETTER(r); +SETTER(angle); +SETTER(sys_x); +SETTER(sys_y); +SETTER(user_x); +SETTER(user_y); +SETTER(zoom); +SETTER(grid); +SETTER(unit); + +#undef SETTER + +void status_set_icon(GtkWidget *image); +void status_set_xy(struct coord coord); +void status_set_angle_xy(const char *tooltip, struct coord v); + +void status_begin_reporting(void); + +void make_status_area(GtkWidget *vbox); +void cleanup_status_area(void); + +#endif /* !GUI_STATUS_H */ diff --git a/gui_style.c b/gui_style.c new file mode 100644 index 0000000..2ebe96a --- /dev/null +++ b/gui_style.c @@ -0,0 +1,93 @@ +/* + * gui_style.c - GUI, style definitions + * + * Written 2009-2011 by Werner Almesberger + * Copyright 2009-2011 by Werner Almesberger + * + * 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. + */ + + +#include <gtk/gtk.h> + +#include "inst.h" +#include "gui_util.h" +#include "gui.h" +#include "gui_style.h" + + +GdkGC *gc_bg, *gc_bg_error; +GdkGC *gc_drag; +GdkGC *gc_highlight; +GdkGC *gc_active_frame; +GdkGC *gc_vec[mode_n]; +GdkGC *gc_obj[mode_n]; +GdkGC *gc_pad[mode_n]; +GdkGC *gc_pad_bare[mode_n]; +GdkGC *gc_pad_trace[mode_n]; +GdkGC *gc_pad_mask[mode_n]; +GdkGC *gc_ptext[mode_n]; +GdkGC *gc_rim[mode_n]; +GdkGC *gc_hole[mode_n]; +GdkGC *gc_meas[mode_n]; +GdkGC *gc_frame[mode_n]; + +PangoFontDescription *item_list_font; + + +static GdkGC *gc(const char *spec, int width) +{ + GdkGCValues gc_values = { + .background = get_color("black"), + .foreground = get_color(spec), + .line_width = width, + }; + + return gdk_gc_new_with_values(root->window, &gc_values, + GDK_GC_FOREGROUND | GDK_GC_BACKGROUND | GDK_GC_LINE_WIDTH); +} + + +static void style(GdkGC *gcs[mode_n], + const char *in, const char *act, const char *sel, int width) +{ + gcs[mode_inactive] = gc(in, width); + gcs[mode_active] = gc(act, width); + gcs[mode_selected] = gc(sel, 2*width); +} + + +void gui_setup_style(GdkDrawable *drawable) +{ + gc_bg = gc("#000000", 0); + gc_bg_error = gc("#000040", 0); + gc_drag = gc("#ffffff", 2); + /* inactive active selected width */ + style(gc_vec, "#202000", "#b0b050", "#ffff80", 1); + style(gc_obj, "#006060", "#00ffff", "#ffff80", 1); + style(gc_pad, "#400000", "#ff0000", "#ffff80", 1); + style(gc_pad_bare, "#402000", "#ff6000", "#ffff80", 1); + style(gc_pad_trace, "#304000", "#80c000", "#ffff80", 1); + style(gc_pad_mask, "#000040", "#0000ff", "#ffff80", 2); + style(gc_ptext, "#404040", "#ffffff", "#ffffff", 1); + style(gc_hole, "#000000", "#000000", "#000000", 0); + style(gc_rim, "#303030", "#606060", "#ffff80", 3); + style(gc_meas, "#280040", "#ff00ff", "#ffff80", 1); + style(gc_frame, "#005000", "#009000", "#ffff80", 1); + + gc_active_frame = gc("#00ff00", 2); +// gc_highlight = gc("#ff8020", 2); + gc_highlight = gc("#ff90d0", 2); + gc_frame[mode_hover] = gc_vec[mode_hover] = gc("#c00000", 2); + + item_list_font = pango_font_description_from_string(ITEM_LIST_FONT); +} + + +void gui_cleanup_style(void) +{ + pango_font_description_free(item_list_font); +} diff --git a/gui_style.h b/gui_style.h new file mode 100644 index 0000000..04e2cc9 --- /dev/null +++ b/gui_style.h @@ -0,0 +1,130 @@ +/* + * gui_style.h - GUI, style definitions + * + * Written 2009-2011 by Werner Almesberger + * Copyright 2009-2011 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_STYLE_H +#define GUI_STYLE_H + +#include <gtk/gtk.h> + +#include "inst.h" + + +/* ----- screen distances, etc. -------------------------------------------- */ + + +#define CANVAS_CLEARANCE 10 + +#define ZOOM_STOP_BORDER 50 /* stop zoom if we have at least a 50 + pixel border */ + +#define VEC_ARROW_LEN 10 +#define VEC_ARROW_ANGLE 20 +#define VEC_EYE_R 5 + +#define PAD_FONT "Sans Bold 24" +#define PAD_BORDER 2 + +#define MEAS_FONT "Sans 8" +#define MEAS_BASELINE_OFFSET 0.1 +#define MEAS_ARROW_LEN 9 +#define MEAS_ARROW_ANGLE 30 + +#define FRAME_FONT "Sans 8" +#define FRAME_BASELINE_OFFSET 0.1 +#define FRAME_SHORT_X 100 +#define FRAME_SHORT_Y 20 +#define FRAME_CLEARANCE 5 +#define FRAME_EYE_R1 3 +#define FRAME_EYE_R2 5 + +#define ITEM_LIST_FONT "Liberation Mono 8" +//#define ITEM_LIST_FONT "Courier Bold 8" + +#define SELECT_R 6 /* pixels within which we select */ + +#define DRAG_MIN_R 5 + +#define MIN_FONT_SCALE 0.20 /* don't scale fonts below this */ + +#define MM_FORMAT_FIXED "%8.3f" /* -NNN.NNN */ +#define MIL_FORMAT_FIXED "%7.1f" /* -NNNN.N */ +#define MM_FORMAT_SHORT "%.4g" +#define MIL_FORMAT_SHORT "%.4g" + +#define DEFAULT_FRAME_AREA_WIDTH 250 +#define DEFAULT_FRAME_AREA_HEIGHT 100 +#define FRAME_AREA_MISC_WIDTH 24 /* pane, scroll bar, slack */ + + +/* ----- assorted colors --------------------------------------------------- */ + + +#define COLOR_EDIT_ASIS "#ffffff" +#define COLOR_EDIT_GOOD "#a0ffa0" +#define COLOR_EDIT_BAD "#ffa0a0" + +#define COLOR_EDITING "#ff00ff" + +#define COLOR_PART_NAME "#ffa050" +#define COLOR_PART_NAME_EDITING COLOR_EDITING + +#define COLOR_FRAME_UNSELECTED "#c0c0c0" +#define COLOR_FRAME_SELECTED "#fff0a0" +#define COLOR_FRAME_EDITING COLOR_EDITING + +#define COLOR_VAR_PASSIVE COLOR_FRAME_UNSELECTED +#define COLOR_VAR_EDITING COLOR_EDITING +#define COLOR_EXPR_PASSIVE "#f0f0ff" +#define COLOR_EXPR_EDITING COLOR_EDITING +#define COLOR_CHOICE_UNSELECTED COLOR_EXPR_PASSIVE +#define COLOR_CHOICE_SELECTED "#a0a0ff" +#define COLOR_ROW_UNSELECTED COLOR_CHOICE_UNSELECTED +#define COLOR_ROW_SELECTED COLOR_CHOICE_SELECTED + +#define COLOR_VAR_TABLE_SEP "black" + +#define COLOR_TOOL_UNSELECTED "#dcdad5" +#define COLOR_TOOL_SELECTED "red" + +#define COLOR_ITEM_NORMAL "#dcdad5" +#define COLOR_ITEM_SELECTED COLOR_FRAME_SELECTED +#define COLOR_ITEM_ERROR "red" + +#define COLOR_SELECTOR "white" + + +/* ----- canvas drawing styles --------------------------------------------- */ + + +extern GdkGC *gc_bg, *gc_bg_error; +extern GdkGC *gc_drag; +extern GdkGC *gc_highlight; +extern GdkGC *gc_active_frame; +extern GdkGC *gc_vec[mode_n]; +extern GdkGC *gc_obj[mode_n]; +extern GdkGC *gc_pad[mode_n]; +extern GdkGC *gc_pad_bare[mode_n]; +extern GdkGC *gc_pad_trace[mode_n]; +extern GdkGC *gc_pad_mask[mode_n]; +extern GdkGC *gc_ptext[mode_n]; +extern GdkGC *gc_rim[mode_n]; +extern GdkGC *gc_hole[mode_n]; +extern GdkGC *gc_meas[mode_n]; +extern GdkGC *gc_frame[mode_n]; + +extern PangoFontDescription *item_list_font; + +void gui_setup_style(GdkDrawable *drawable); +void gui_cleanup_style(void); + +#endif /* !GUI_STYLE_H */ diff --git a/gui_tool.c b/gui_tool.c new file mode 100644 index 0000000..77f751e --- /dev/null +++ b/gui_tool.c @@ -0,0 +1,1241 @@ +/* + * gui_tool.c - GUI, tool bar + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <math.h> +#include <assert.h> +#include <gtk/gtk.h> + +#include "util.h" +#include "inst.h" +#include "meas.h" +#include "obj.h" +#include "gui_util.h" +#include "gui_style.h" +#include "gui_inst.h" +#include "gui_over.h" +#include "gui_canvas.h" +#include "gui_status.h" +#include "gui.h" +#include "gui_meas.h" +#include "gui_tool.h" + + +#include "icons/arc.xpm" +#include "icons/circ.xpm" +#include "icons/frame.xpm" +#include "icons/line.xpm" +#include "icons/meas.xpm" +#include "icons/meas_x.xpm" +#include "icons/meas_y.xpm" +#include "icons/pad.xpm" +#include "icons/rpad.xpm" +#include "icons/hole.xpm" +#include "icons/point.xpm" +#include "icons/delete.xpm" +#include "icons/delete_off.xpm" +#include "icons/rect.xpm" +#include "icons/vec.xpm" + + +static GtkWidget *ev_point, *ev_delete; +static GtkWidget *active_tool; +static struct tool_ops *active_ops = NULL; +static struct inst *hover_inst = NULL; +static GtkWidget *delete_image[2]; + +static struct drag_state { + struct inst *inst; /* non-NULL if dragging an existing object */ + struct inst *new; /* non-NULL if dragging a new object */ + int anchors_n; /* number of anchors, 0 if no moving */ + int anchor_i; /* current anchor */ + struct vec **anchors[3]; +} drag = { + .new = NULL, + .anchors_n = 0, +}; + +static struct coord last_canvas_pos; + + +static struct vec *new_vec(struct inst *base) +{ + struct vec *vec, **walk; + + vec = alloc_type(struct vec); + vec->nul_tag = 0; + vec->name = NULL; + vec->base = inst_get_vec(base); + vec->next = NULL; + vec->frame = active_frame; + for (walk = &active_frame->vecs; *walk; walk = &(*walk)->next); + *walk = vec; + return vec; +} + + +struct obj *new_obj_unconnected(enum obj_type type, struct inst *base) +{ + struct obj *obj; + + obj = alloc_type(struct obj); + obj->type = type; + obj->name = NULL; + obj->frame = active_frame; + obj->base = inst_get_vec(base); + obj->next = NULL; + obj->lineno = 0; + return obj; +} + + +void connect_obj(struct frame *frame, struct obj *obj) +{ + struct obj **walk; + + obj->frame = frame; + for (walk = &frame->objs; *walk; walk = &(*walk)->next); + *walk = obj; +} + + +static struct obj *new_obj(enum obj_type type, struct inst *base) +{ + struct obj *obj; + + obj = new_obj_unconnected(type, base); + connect_obj(active_frame, obj); + return obj; +} + + +/* ----- shared functions -------------------------------------------------- */ + + +struct pix_buf *draw_move_line_common(struct inst *inst, + struct coord end, struct coord pos, int i) +{ + struct coord from, to; + struct pix_buf *buf; + + from = translate(inst->base); + to = translate(end); + pos = translate(pos); + switch (i) { + case 0: + from = pos; + break; + case 1: + to = pos; + break; + default: + abort(); + } + buf = save_pix_buf(DA, from.x, from.y, to.x, to.y, 1); + gdk_draw_line(DA, gc_drag, from.x, from.y, to.x, to.y); + return buf; +} + + +static struct pix_buf *draw_move_rect_common(struct inst *inst, + struct coord other, struct coord pos, int i) +{ + struct coord min, max; + struct pix_buf *buf; + + min = translate(inst->base); + max = translate(other); + pos = translate(pos); + switch (i) { + case 0: + min = pos; + break; + case 1: + max = pos; + break; + default: + abort(); + } + sort_coord(&min, &max); + buf = save_pix_buf(DA, min.x, min.y, max.x, max.y, 1); + gdk_draw_rectangle(DA, gc_drag, FALSE, + min.x, min.y, max.x-min.x, max.y-min.y); + return buf; +} + + +static struct pix_buf *hover_common(GdkGC *gc, struct coord center, unit_type r) +{ + struct pix_buf *buf; + + center = translate(center); + buf = save_pix_buf(DA, + center.x-r, center.y-r, center.x+r, center.y+r, 2); + draw_circle(DA, gc, FALSE, center.x, center.y, VEC_EYE_R); + return buf; +} + + +/* ----- delete ------------------------------------------------------------ */ + + +static void tool_selected_delete(void) +{ + if (selected_inst) { + tool_dehover(); + inst_delete(selected_inst); + change_world(); + } + tool_reset(); +} + + +static struct tool_ops delete_ops = { + .tool_selected = tool_selected_delete, +}; + + +void tool_selected_inst(struct inst *inst) +{ + set_image(ev_delete, delete_image[inst != NULL]); +} + + +/* ----- vec --------------------------------------------------------------- */ + + +static struct coord gridify(struct coord base, struct coord pos) +{ + struct coord new; + unit_type unit; + + switch (curr_unit) { + case curr_unit_mm: + case curr_unit_auto: + unit = mm_to_units(0.1); + break; + case curr_unit_mil: + unit = mil_to_units(10); + break; + default: + abort(); + } + new.x = pos.x-((pos.x-base.x) % unit); + new.y = pos.y-((pos.y-base.y) % unit); + if (new.x != base.x || new.y != base.y) + return new; + if (fabs(pos.x-base.x) > fabs(pos.y-base.y)) + new.x += pos.x > base.x ? unit : -unit; + else + new.y += pos.y > base.y ? unit : -unit; + return new; +} + + +static struct pix_buf *drag_new_vec(struct inst *from, struct coord to) +{ + struct coord pos; + struct pix_buf *buf; + + pos = inst_get_point(from); + to = gridify(pos, to); + status_set_type_x(NULL, "dX ="); + status_set_type_y(NULL, "dX ="); + /* @@@ use status_set_xy */ + switch (curr_unit) { + case curr_unit_mm: + case curr_unit_auto: + status_set_x(NULL, "%lg mm", units_to_mm(to.x-pos.x)); + status_set_y(NULL, "%lg mm", units_to_mm(to.y-pos.y)); + break; + case curr_unit_mil: + status_set_x(NULL, "%lg mil", units_to_mil(to.x-pos.x)); + status_set_y(NULL, "%lg mil", units_to_mil(to.y-pos.y)); + break; + default: + abort(); + } + pos = translate(pos); + to = translate(to); + buf = save_pix_buf(DA, pos.x, pos.y, to.x, to.y, 1); + gdk_draw_line(DA, gc_drag, pos.x, pos.y, to.x, to.y); + return buf; +} + + +struct pix_buf *draw_move_vec(struct inst *inst, struct coord pos, int i) +{ + return draw_move_line_common(inst, + add_vec(sub_vec(inst->u.vec.end, inst->base), pos), pos, i); +} + + +struct pix_buf *gui_hover_vec(struct inst *self) +{ + return hover_common(gc_vec[mode_hover], + self->u.vec.end, VEC_EYE_R); +} + + +static int end_new_raw_vec(struct inst *from, struct coord to) +{ + struct vec *vec; + struct coord pos; + + vec = new_vec(from); + pos = inst_get_point(from); + to = gridify(pos, to); + switch (curr_unit) { + case curr_unit_mm: + case curr_unit_auto: + vec->x = new_num(make_mm(units_to_mm(to.x-pos.x))); + vec->y = new_num(make_mm(units_to_mm(to.y-pos.y))); + break; + case curr_unit_mil: + vec->x = new_num(make_mil(units_to_mil(to.x-pos.x))); + vec->y = new_num(make_mil(units_to_mil(to.y-pos.y))); + break; + default: + abort(); + } + return 1; +} + + +static struct tool_ops vec_ops = { + .drag_new = drag_new_vec, + .end_new_raw = end_new_raw_vec, +}; + + +/* ----- line -------------------------------------------------------------- */ + + +struct pix_buf *drag_new_line(struct inst *from, struct coord to) +{ + struct coord pos; + struct pix_buf *buf; + + pos = translate(inst_get_point(from)); + to = translate(to); + buf = save_pix_buf(DA, pos.x, pos.y, to.x, to.y, 1); + gdk_draw_line(DA, gc_drag, pos.x, pos.y, to.x, to.y); + return buf; +} + + +struct pix_buf *draw_move_line(struct inst *inst, struct coord pos, int i) +{ + return draw_move_line_common(inst, inst->u.rect.end, pos, i); +} + + +static int end_new_line(struct inst *from, struct inst *to) +{ + struct obj *obj; + + if (from == to) + return 0; + obj = new_obj(ot_line, from); + obj->u.line.other = inst_get_vec(to); + obj->u.line.width = NULL; + return 1; +} + + +static struct tool_ops line_ops = { + .drag_new = drag_new_line, + .end_new = end_new_line, +}; + + +/* ----- rect -------------------------------------------------------------- */ + + +static struct pix_buf *drag_new_rect(struct inst *from, struct coord to) +{ + struct coord pos; + struct pix_buf *buf; + + pos = translate(inst_get_point(from)); + to = translate(to); + sort_coord(&pos, &to); + buf = save_pix_buf(DA, pos.x, pos.y, to.x, to.y, 1); + gdk_draw_rectangle(DA, gc_drag, FALSE, + pos.x, pos.y, to.x-pos.x, to.y-pos.y); + return buf; +} + + +struct pix_buf *draw_move_rect(struct inst *inst, struct coord pos, int i) +{ + return draw_move_rect_common(inst, inst->u.rect.end, pos, i); +} + + +static int end_new_rect(struct inst *from, struct inst *to) +{ + struct obj *obj; + + if (from == to) + return 0; + obj = new_obj(ot_rect, from); + obj->u.rect.other = inst_get_vec(to); + obj->u.rect.width = NULL; + return 1; +} + + +static struct tool_ops rect_ops = { + .drag_new = drag_new_rect, + .end_new = end_new_rect, +}; + + +/* ----- pad --------------------------------------------------------------- */ + + +static int end_new_pad(struct inst *from, struct inst *to) +{ + struct obj *obj; + + if (from == to) + return 0; + obj = new_obj(ot_pad, from); + obj->u.pad.other = inst_get_vec(to); + obj->u.pad.name = stralloc("?"); + obj->u.pad.rounded = 0; + obj->u.pad.type = pt_normal; + return 1; +} + + +struct pix_buf *draw_move_pad(struct inst *inst, struct coord pos, int i) +{ + return draw_move_rect_common(inst, inst->u.pad.other, pos, i); +} + + +static struct tool_ops pad_ops = { + .drag_new = drag_new_rect, + .end_new = end_new_pad, +}; + + +/* ----- rounded pad ------------------------------------------------------- */ + + +static int end_new_rpad(struct inst *from, struct inst *to) +{ + struct obj *obj; + + if (from == to) + return 0; + obj = new_obj(ot_pad, from); + obj->u.pad.other = inst_get_vec(to); + obj->u.pad.name = stralloc("?"); + obj->u.pad.rounded = 1; + obj->u.pad.type = pt_normal; + return 1; +} + + +struct pix_buf *draw_move_rpad(struct inst *inst, struct coord pos, int i) +{ + return draw_move_rect_common(inst, inst->u.pad.other, pos, i); +} + + +static struct tool_ops rpad_ops = { + .drag_new = drag_new_rect, + .end_new = end_new_rpad, +}; + + +/* ----- hole -------------------------------------------------------------- */ + + +static int end_new_hole(struct inst *from, struct inst *to) +{ + struct obj *obj; + + if (from == to) + return 0; + obj = new_obj(ot_hole, from); + obj->u.hole.other = inst_get_vec(to); + return 1; +} + + +struct pix_buf *draw_move_hole(struct inst *inst, struct coord pos, int i) +{ + return draw_move_rect_common(inst, inst->u.hole.other, pos, i); +} + + +static struct tool_ops hole_ops = { + .drag_new = drag_new_rect, + .end_new = end_new_hole, +}; + + +/* ----- circ -------------------------------------------------------------- */ + + +static struct pix_buf *drag_new_circ(struct inst *from, struct coord to) +{ + struct coord pos; + struct pix_buf *buf; + double r; + + pos = translate(inst_get_point(from)); + to = translate(to); + r = hypot(to.x-pos.x, to.y-pos.y); + buf = save_pix_buf(DA, pos.x-r, pos.y-r, pos.x+r, pos.y+r, 1); + draw_circle(DA, gc_drag, FALSE, pos.x, pos.y, r); + return buf; +} + + +static int end_new_circ(struct inst *from, struct inst *to) +{ + struct obj *obj; + + if (from == to) + return 0; + obj = new_obj(ot_arc, from); + obj->u.arc.start = inst_get_vec(to); + obj->u.arc.end = inst_get_vec(to); + obj->u.arc.width = NULL; + return 1; +} + + +struct pix_buf *draw_move_arc(struct inst *inst, struct coord pos, int i) +{ + struct coord c, from, to, end; + double r, r_save, a1, a2; + struct pix_buf *buf; + + c = translate(inst->base); + from = + translate(rotate_r(inst->base, inst->u.arc.r, inst->u.arc.a1)); + to = + translate(rotate_r(inst->base, inst->u.arc.r, inst->u.arc.a2)); + pos = translate(pos); + switch (i) { + case 0: + c = pos; + break; + case 2: + from = pos; + break; + case 1: + to = pos; + break; + default: + abort(); + } + r = hypot(from.x-c.x, from.y-c.y); + /* + * the screen coordinate system is reversed with y growing downward, + * so we have to negate the angles. + */ + a1 = -theta(c, from); + a2 = -theta(c, to); + if (a2 < a1) + a2 += 360.0; + + if (i != 2) { + r_save = r; + } else { + r_save = hypot(to.x-c.x, to.y-c.y); + if (r > r_save) + r_save = r; + } + buf = save_pix_buf(DA, + c.x-r_save, c.y-r_save, c.x+r_save, c.y+r_save, 1); + draw_arc(DA, gc_drag, FALSE, c.x, c.y, r, a1, a2); + if (i == 1) { + end = rotate_r(c, r_save, -a2); + gdk_draw_line(DA, gc_drag, c.x, c.y, end.x, end.y); + } + return buf; +} + + +void do_move_to_arc(struct inst *inst, struct inst *to, int i) +{ + struct vec *vec = inst_get_vec(to); + struct obj *obj = inst->obj; + + switch (i) { + case 0: + obj->base = vec; + break; + case 2: + obj->u.arc.start = vec; + break; + case 1: + obj->u.arc.end = vec; + break; + default: + abort(); + } +} + + +static struct tool_ops circ_ops = { + .drag_new = drag_new_circ, + .end_new = end_new_circ, +}; + + +/* ----- frame helper ------------------------------------------------------ */ + + +int is_parent_of(const struct frame *p, const struct frame *c) +{ + const struct obj *obj; + + if (p == c) + return 1; + for (obj = p->objs; obj; obj = obj->next) + if (obj->type == ot_frame) + if (is_parent_of(obj->u.frame.ref, c)) + return 1; + return 0; +} + + +/* ----- frame ------------------------------------------------------------- */ + + +static struct frame *locked_frame = NULL; + + +struct pix_buf *draw_move_frame(struct inst *inst, struct coord pos, int i) +{ + struct pix_buf *buf; + int r = FRAME_EYE_R2; + + pos = translate(pos); + buf = save_pix_buf(DA, pos.x-r, pos.y-r, pos.x+r, pos.y+r, 1); + draw_arc(DA, gc_drag, FALSE, pos.x, pos.y, r, 0, 360); + return buf; +} + + +struct pix_buf *gui_hover_frame(struct inst *self) +{ + return hover_common(gc_frame[mode_hover], + self->base, FRAME_EYE_R2); +} + + +static int end_new_frame(struct inst *from, struct inst *to) +{ + struct obj *obj; + + if (!locked_frame || is_parent_of(locked_frame, active_frame)) + return 0; + obj = new_obj(ot_frame, from); + obj->u.frame.ref = locked_frame; + obj->u.frame.lineno = 0; + if (!locked_frame->active_ref) + locked_frame->active_ref = obj; + locked_frame = NULL; + tool_reset(); + return 1; +} + + +static struct tool_ops frame_ops = { + .tool_selected = NULL, + .drag_new = NULL, + .end_new = end_new_frame, +}; + + +/* ----- moving references ------------------------------------------------- */ + + +#if 0 +static int may_move(struct inst *curr) +{ + if (!selected_inst) + return 0; + if (drag.anchors_n) + return 0; /* already moving something else */ + drag.anchors_n = inst_anchors(selected_inst, drag.anchors); + for (drag.anchor_i = 0; drag.anchor_i != drag.anchors_n; + drag.anchor_i++) + if (*drag.anchors[drag.anchor_i] == inst_get_vec(curr)) + return 1; + drag.anchors_n = 0; + return 0; +} +#endif + + +static int would_be_equal(const struct drag_state *state, + int a, int b, struct inst *curr) +{ + const struct vec *va; + const struct vec *vb; + + va = a == state->anchor_i ? inst_get_vec(curr) : *state->anchors[a]; + vb = b == state->anchor_i ? inst_get_vec(curr) : *state->anchors[b]; + return va == vb; +} + + +static int may_move_to(const struct drag_state *state, struct inst *curr) +{ + assert(drag.inst); + assert(state->anchors_n); + switch (state->anchors_n) { + case 3: + if (would_be_equal(state, 0, 2, curr)) + return 0; + /* fall through */ + case 2: + if (would_be_equal(state, 0, 1, curr)) + return 0; + /* fall through */ + case 1: + return 1; + default: + abort(); + } +} + + +static void do_move_to(struct drag_state *state, struct inst *curr) +{ + assert(state->inst->ops->find_point || may_move_to(state, curr)); + *state->anchors[state->anchor_i] = inst_get_vec(curr); +} + + +/* ----- hover ------------------------------------------------------------- */ + + +static struct pix_buf *hover_save_and_draw(void *user) +{ + return inst_hover(hover_inst); +} + + +void tool_dehover(void) +{ + if (!hover_inst) + return; + over_leave(); + hover_inst = NULL; +} + + +/* + * When hovering, we have to consider the following states: + * + * selected (selected_inst != NULL) + * | dragging new (drag.new != NULL) + * | | dragging existing (drag.anchors_n != 0) + * | | | tool selected (active_ops) + * | | | | + * Y N N N highlight if inst_find_point_selected else don't + * Y N N Y highlight if inst_find_point_selected else fall over to tool + * - Y N - highlight if inst_find_point / active_ops->find_point else don't + * - N Y - highlight if may_move_to else don't + * - Y Y - invalid state + * N N N Y highlight if inst_find_point / active_ops->find_point else don't + * N N N N don't highlight + */ + +static struct inst *get_hover_inst(struct coord pos) +{ + struct inst *inst; + int i; + + if (drag.new) { + if (active_ops->find_point) + return active_ops->find_point(pos); + return inst_find_point(pos); + } + if (drag.anchors_n) { + if (drag.inst->ops->find_point) + return drag.inst->ops->find_point(drag.inst, pos); + inst = inst_find_point(pos); + if (!inst) + return NULL; + return may_move_to(&drag, inst) ? inst : NULL; + } + if (selected_inst) { + i = inst_find_point_selected(pos, &inst); + if (i != -1) + return inst; + } + if (!active_ops) + return NULL; + if (active_ops->find_point) + return active_ops->find_point(pos); + return inst_find_point(pos); +} + + +int tool_hover(struct coord pos) +{ + struct inst *curr; + + curr = get_hover_inst(pos); +#if 0 + if ((drag.new && curr == drag.new) || (drag.inst && curr == drag.inst)) + return; + if (curr && !active_ops) { + if (drag.anchors_n) { + if (!may_move_to(&drag, curr)) + curr = NULL; + } else { + if (!may_move(curr)) + curr = NULL; + drag.anchors_n = 0; + } + } +got: +#endif + + if (curr == hover_inst) + return !!curr; + if (hover_inst) { + over_leave(); + hover_inst = NULL; + } + if (!curr) + return 0; + hover_inst = curr; + over_enter(hover_save_and_draw, NULL); + return 1; +} + + +/* ----- frame drag and drop ----------------------------------------------- */ + + +/* + * When dragging a frame, we temporarily replace the selected tool (if any) + * with the frame tool. + */ + + +static struct tool_ops *pushed_ops; + + +void tool_push_frame(struct frame *frame) +{ + pushed_ops = active_ops; + locked_frame = frame; + active_ops = &frame_ops; + /* + * We don't need to call tool_selected since, with drag and drop, the + * frame tools doesn't need activation anymore. + */ +} + + +static int do_place_frame(struct frame *frame, struct coord pos) +{ + if (!get_hover_inst(pos)) + return 0; + tool_consider_drag(pos); + return 1; +} + + +/* + * Gtk calls drag-leave, drag-end, and only then drag-drop. So we'll already + * have cleaned up in tool_pop_frame before we get here. In order to place the + * frame, we need to activate the frame tool again. + * + * @@@ bug: there's a tool_reset in this path, so we'll lose the widget of the + * tool that's really active. This problem will vanish when scrapping the + * old-style frame referenes. + */ + +int tool_place_frame(struct frame *frame, struct coord pos) +{ + int ok; + + active_ops = &frame_ops; + ok = do_place_frame(frame, pos); + active_ops = pushed_ops; + return ok; +} + + +void tool_pop_frame(void) +{ + if (!active_tool) + return; + active_ops = pushed_ops; + /* + * We don't need to call tool_selected since the only tool that could + * use this would be the delete tool, and there the semantics would be + * undesirable. Also, the delete tool never stays active, so it can't + * appear together with drag and drop anyway. + */ +} + + +/* ----- tooltip ----------------------------------------------------------- */ + + +const char *tool_tip(struct coord pos) +{ + struct inst *inst; + + inst = get_hover_inst(pos); + if (!inst) + return NULL; + + /* + * The logic below follows exactly what happens in get_hover_inst. + */ + + if (drag.new) + return "End here"; + if (drag.anchors_n) + return "Move here"; + if (selected_inst) + return "Move this point"; + return "Start here"; +} + + +/* ----- mouse actions ----------------------------------------------------- */ + + +static struct pix_buf *drag_save_and_draw(void *user, struct coord to) +{ + if (drag.new) { + assert(active_ops); + return active_ops->drag_new(drag.new, to); + } else { + return inst_draw_move(drag.inst, to, drag.anchor_i); + } +} + + +/* + * When considering dragging, we have the following states: + * + * selected (selected_inst != NULL) + * | tool selected (active_ops) + * | | + * N N don't + * Y - if we could drag, drag_new/end_new, else fall over to tool + * N Y else single-click creation, else drag_new/end_new + */ + +int tool_consider_drag(struct coord pos) +{ + struct inst *curr; + + assert(!drag.new); + assert(!drag.anchors_n); + last_canvas_pos = translate(pos); + + if (!selected_inst && !active_ops) + return 0; + if (selected_inst) { + drag.anchor_i = inst_find_point_selected(pos, NULL); + if (drag.anchor_i != -1) { + tool_dehover(); + drag.inst = selected_inst; + drag.new = NULL; + drag.anchors_n = + inst_anchors(selected_inst, drag.anchors); + over_begin(drag_save_and_draw, NULL, pos); + inst_begin_drag_move(selected_inst, drag.anchor_i); + return 1; + } + } + if (!active_ops) + return 0; + + curr = get_hover_inst(pos); + if (!curr) + return 0; + + tool_dehover(); + + if (active_ops->drag_new) { + if (active_ops->begin_drag_new) + active_ops->begin_drag_new(curr); + drag.inst = NULL; + drag.new = curr; + over_begin(drag_save_and_draw, NULL, pos); + return 1; + } + + /* object is created without dragging */ + if (active_ops->end_new(curr, NULL)) + return -1; + return 0; + +} + + +void tool_drag(struct coord to) +{ + last_canvas_pos = translate(to); + over_move(to); +} + + +void tool_cancel_drag(void) +{ + if (drag.anchors_n && drag.inst->ops->end_drag_move) + drag.inst->ops->end_drag_move(); + drag.new = NULL; + active_ops = NULL; + drag.anchors_n = 0; + over_end(); + tool_dehover(); + tool_reset(); +} + + +int tool_end_drag(struct coord to) +{ + struct drag_state state = drag; + struct inst *end; + struct tool_ops *ops = active_ops; + + tool_cancel_drag(); + if (state.new && ops->end_new_raw) + return ops->end_new_raw(state.new, to); + if (state.new && ops->find_point) { + end = ops->find_point(to); + } else { + if (state.inst && state.inst->ops->find_point) + end = state.inst->ops->find_point(state.inst, to); + else + end = inst_find_point(to); + } + if (!end) { + if (state.new && ops->cancel_drag_new) + ops->cancel_drag_new(); + return 0; + } + if (state.new) + return ops->end_new(state.new, end); + + /* if we got the point from find_point, it's authoritative */ + if (!state.inst->ops->find_point && !may_move_to(&state, end)) + return 0; + if (!inst_do_move_to(state.inst, end, state.anchor_i)) + do_move_to(&state, end); + return 1; +} + + +void tool_redraw(void) +{ + struct coord pos; + + over_reset(); + hover_inst = NULL; + if (!drag.new && !drag.anchors_n) + return; + pos = canvas_to_coord(last_canvas_pos.x, last_canvas_pos.y); + tool_hover(pos); + over_begin(drag_save_and_draw, NULL, pos); +} + + +/* ----- Retrieve icons by instance characteristics ------------------------ */ + + +GtkWidget *get_icon_by_inst(const struct inst *inst) +{ + char **image; + + switch (inst->prio) { + case ip_frame: + image = xpm_frame; + break; + case ip_pad_copper: + case ip_pad_special: + image = inst->obj->u.pad.rounded ? xpm_rpad : xpm_pad; + break; + case ip_hole: + image = xpm_hole; + break; + case ip_circ: + image = xpm_circ; + break; + case ip_arc: + image = xpm_arc; + break; + case ip_rect: + image = xpm_rect; + break; + case ip_meas: + switch (inst->obj->u.meas.type) { + case mt_xy_next: + case mt_xy_max: + image = xpm_meas; + break; + case mt_x_next: + case mt_x_max: + image = xpm_meas_x; + break; + case mt_y_next: + case mt_y_max: + image = xpm_meas_y; + break; + default: + abort(); + } + break; + case ip_line: + image = xpm_line; + break; + case ip_vec: + image = xpm_vec; + break; + default: + abort(); + } + return make_transparent_image(DA, image, NULL); +} + + +/* ----- tool bar creation ------------------------------------------------- */ + + +static void tool_select(GtkWidget *evbox, struct tool_ops *ops) +{ + GdkColor col; + + if (active_tool) { + if (active_ops && active_ops->tool_deselected) + active_ops->tool_deselected(); + col = get_color(COLOR_TOOL_UNSELECTED); + gtk_widget_modify_bg(active_tool, GTK_STATE_NORMAL, &col); + active_tool = NULL; + } + col = get_color(COLOR_TOOL_SELECTED); + gtk_widget_modify_bg(evbox, GTK_STATE_NORMAL, &col); + active_tool = evbox; + active_ops = ops; + if (ops && ops->tool_selected) + ops->tool_selected(); +} + + +void tool_reset(void) +{ + over_reset(); + hover_inst = NULL; + tool_select(ev_point, NULL); +} + + +static gboolean tool_button_press_event(GtkWidget *widget, + GdkEventButton *event, gpointer data) +{ + switch (event->button) { + case 1: + tool_select(widget, data); + break; + } + return TRUE; +} + + +static void tool_separator(GtkWidget *bar) +{ + GtkToolItem *item; + + item = gtk_separator_tool_item_new(); + gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(item), FALSE); + gtk_toolbar_insert(GTK_TOOLBAR(bar), item, -1); +} + + +GtkWidget *gui_setup_tools(GdkDrawable *drawable) +{ + GtkWidget *bar; + + bar = gtk_toolbar_new(); + gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_ICONS); + gtk_toolbar_set_orientation(GTK_TOOLBAR(bar), + GTK_ORIENTATION_VERTICAL); + + ev_point = tool_button(bar, drawable, xpm_point, + "Select and move items", + tool_button_press_event, NULL); + ev_delete = tool_button(bar, drawable, NULL, NULL, + tool_button_press_event, &delete_ops); + tool_separator(bar); + tool_button(bar, drawable, xpm_vec, + "Add a vector", + tool_button_press_event, &vec_ops); + tool_button(bar, drawable, xpm_pad, + "Add a rectangular pad", + tool_button_press_event, &pad_ops); + tool_button(bar, drawable, xpm_rpad, + "Add a rounded pad", + tool_button_press_event, &rpad_ops); + tool_button(bar, drawable, xpm_hole, + "Add a hole", + tool_button_press_event, &hole_ops); + tool_button(bar, drawable, xpm_line, + "Add a silk screen line", + tool_button_press_event, &line_ops); + tool_button(bar, drawable, xpm_rect, + "Add a silk screen rectangle", + tool_button_press_event, &rect_ops); + tool_button(bar, drawable, xpm_circ, + "Add a silk screen circle or arc", + tool_button_press_event, &circ_ops); + tool_separator(bar); + tool_button(bar, drawable, xpm_meas, + "Add a measurement", + tool_button_press_event, &tool_meas_ops); + tool_button(bar, drawable, xpm_meas_x, + "Add a horizontal measurement", + tool_button_press_event, &tool_meas_ops_x); + tool_button(bar, drawable, xpm_meas_y, + "Add a vertical measurement", + tool_button_press_event, &tool_meas_ops_y); + + delete_image[0] = gtk_widget_ref(make_image(drawable, xpm_delete_off, + NULL)); + delete_image[1] = gtk_widget_ref(make_image(drawable, xpm_delete, + "Delete the selected item")); + set_image(ev_delete, delete_image[0]); + + tool_reset(); + + return bar; +} + + +void gui_cleanup_tools(void) +{ + g_object_unref(delete_image[0]); + g_object_unref(delete_image[1]); +} diff --git a/gui_tool.h b/gui_tool.h new file mode 100644 index 0000000..39b3509 --- /dev/null +++ b/gui_tool.h @@ -0,0 +1,84 @@ +/* + * gui_tool.h - GUI, tool bar + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_TOOL_H +#define GUI_TOOL_H + +#include <gtk/gtk.h> + +#include "inst.h" + + +struct tool_ops { + void (*tool_selected)(void); + void (*tool_deselected)(void); + struct inst *(*find_point)(struct coord pos); + void (*begin_drag_new)(struct inst *from); + struct pix_buf *(*drag_new)(struct inst *from, struct coord to); + int (*end_new_raw)(struct inst *from, struct coord to); + int (*end_new)(struct inst *from, struct inst *to); + void (*cancel_drag_new)(void); +}; + + +struct pix_buf *draw_move_vec(struct inst *inst, struct coord pos, int i); +struct pix_buf *draw_move_line(struct inst *inst, struct coord pos, int i); +struct pix_buf *draw_move_rect(struct inst *inst, struct coord pos, int i); +struct pix_buf *draw_move_pad(struct inst *inst, struct coord pos, int i); +struct pix_buf *draw_move_rpad(struct inst *inst, struct coord pos, int i); +struct pix_buf *draw_move_hole(struct inst *inst, struct coord pos, int i); +struct pix_buf *draw_move_arc(struct inst *inst, struct coord pos, int i); +struct pix_buf *draw_move_meas(struct inst *inst, struct coord pos, int i); +struct pix_buf *draw_move_frame(struct inst *inst, struct coord pos, int i); + +struct pix_buf *gui_hover_vec(struct inst *self); +struct pix_buf *gui_hover_frame(struct inst *self); + +void do_move_to_arc(struct inst *inst, struct inst *to, int i); + +void tool_dehover(void); +int tool_hover(struct coord pos); +const char *tool_tip(struct coord pos); +int tool_consider_drag(struct coord pos); +void tool_drag(struct coord to); +void tool_cancel_drag(void); +int tool_end_drag(struct coord to); +void tool_redraw(void); + +/* + * The following functions are for measurements which are now in a separate + * compilation unit. + */ + +struct obj *new_obj_unconnected(enum obj_type type, struct inst *base); +void connect_obj(struct frame *frame, struct obj *obj); +int is_parent_of(const struct frame *p, const struct frame *c); + +struct pix_buf *draw_move_line_common(struct inst *inst, + struct coord end, struct coord pos, int i); +struct pix_buf *drag_new_line(struct inst *from, struct coord to); + +void tool_push_frame(struct frame *frame); +int tool_place_frame(struct frame *frame, struct coord pos); +void tool_pop_frame(void); + +void tool_selected_inst(struct inst *inst); + +GtkWidget *get_icon_by_inst(const struct inst *inst); + +void tool_reset(void); + +GtkWidget *gui_setup_tools(GdkDrawable *drawable); +void gui_cleanup_tools(void); + +#endif /* !GUI_TOOL_H */ diff --git a/gui_util.c b/gui_util.c new file mode 100644 index 0000000..67620a4 --- /dev/null +++ b/gui_util.c @@ -0,0 +1,399 @@ +/* + * gui_util.c - GUI helper functions + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <math.h> +#include <gtk/gtk.h> + +#include "util.h" +#include "gui_style.h" +#include "gui.h" +#include "gui_util.h" + + +struct draw_ctx draw_ctx; + + +/* ----- look up a color --------------------------------------------------- */ + + +GdkColor get_color(const char *spec) +{ + GdkColormap *cmap; + GdkColor color; + + cmap = gdk_drawable_get_colormap(root->window); + if (!gdk_color_parse(spec, &color)) + abort(); + if (!gdk_colormap_alloc_color(cmap, &color, FALSE, TRUE)) + abort(); + return color; +} + + +/* ----- lines with a width ------------------------------------------------ */ + + +void set_width(GdkGC *gc, int width) +{ + gdk_gc_set_line_attributes(gc, width < 1 ? 1 : width, + GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND); +} + + +/* ----- backing store ----------------------------------------------------- */ + + +void free_pix_buf(struct pix_buf *buf) +{ + g_object_unref(G_OBJECT(buf->buf)); + free(buf); +} + + +struct pix_buf *save_pix_buf(GdkDrawable *da, int xa, int ya, int xb, int yb, + int border) +{ + struct pix_buf *buf; + int w, h; + + if (xa > xb) + SWAP(xa, xb); + if (ya > yb) + SWAP(ya, yb); + buf = alloc_type(struct pix_buf); + buf->da = da; + buf->x = xa-border; + buf->y = ya-border; + w = xb-xa+1+2*border; + h = yb-ya+1+2*border; + if (buf->x < 0) { + w += buf->x; + buf->x = 0; + } + if (buf->y < 0) { + h += buf->y; + buf->y = 0; + } + buf->buf = gdk_pixbuf_get_from_drawable(NULL, da, NULL, + buf->x, buf->y, 0, 0, w, h); + return buf; +} + + +void restore_pix_buf(struct pix_buf *buf) +{ + gdk_draw_pixbuf(buf->da, NULL, buf->buf, 0, 0, buf->x, buf->y, -1, -1, + GDK_RGB_DITHER_NORMAL, 0, 0); + free_pix_buf(buf); +} + + +/* ----- arcs and circles -------------------------------------------------- */ + + +void draw_arc(GdkDrawable *da, GdkGC *gc, int fill, + int x, int y, int r, double a1, double a2) +{ + /* + * This adjustment handles two distinct cases: + * - if a1 == a2, we make sure we draw a full circle + * - the end angle a2 must always be greater than the start angle a1 + */ + if (a2 <= a1) + a2 += 360; + gdk_draw_arc(da, gc, fill, x-r, y-r, 2*r, 2*r, a1*64, (a2-a1)*64); +} + + +void draw_circle(GdkDrawable *da, GdkGC *gc, int fill, + int x, int y, int r) +{ + draw_arc(da, gc, fill, x, y, r, 0, 360); +} + + +/* ----- labels in a box --------------------------------------------------- */ + + +GtkWidget *label_in_box_new(const char *s, const char *tooltip) +{ + GtkWidget *evbox, *label; + + evbox = gtk_event_box_new(); + label = gtk_label_new(s); + gtk_misc_set_padding(GTK_MISC(label), 1, 1); + gtk_container_add(GTK_CONTAINER(evbox), label); + if (tooltip) + gtk_widget_set_tooltip_markup(evbox, tooltip); + return label; +} + + +GtkWidget *box_of_label(GtkWidget *label) +{ + return gtk_widget_get_ancestor(label, GTK_TYPE_EVENT_BOX); +} + + +void label_in_box_fg(GtkWidget *label, const char *color) +{ + GdkColor col = get_color(color); + + gtk_widget_modify_fg(label, GTK_STATE_NORMAL, &col); +} + + +void label_in_box_bg(GtkWidget *label, const char *color) +{ + GtkWidget *box; + GdkColor col = get_color(color); + + box = box_of_label(label); + gtk_widget_modify_bg(box, GTK_STATE_NORMAL, &col); +} + + +/* ----- generate a tool button with an XPM image -------------------------- */ + + +GtkWidget *make_image(GdkDrawable *drawable, char **xpm, const char *tooltip) +{ + GdkPixmap *pixmap; + GtkWidget *image; + GdkColor white = get_color("white"); + + pixmap = gdk_pixmap_create_from_xpm_d(drawable, NULL, &white, xpm); + image = gtk_image_new_from_pixmap(pixmap, NULL); + gtk_misc_set_padding(GTK_MISC(image), 1, 1); + if (tooltip) + gtk_widget_set_tooltip_markup(image, tooltip); + return image; +} + + +GtkWidget *make_transparent_image(GdkDrawable *drawable, char **xpm, + const char *tooltip) +{ + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkWidget *image; + + pixmap = gdk_pixmap_create_from_xpm_d(drawable, &mask, NULL, xpm); + image = gtk_image_new_from_pixmap(pixmap, mask); + gtk_misc_set_padding(GTK_MISC(image), 1, 1); + if (tooltip) + gtk_widget_set_tooltip_markup(image, tooltip); + return image; +} + + +static void remove_child(GtkWidget *widget, gpointer data) +{ + gtk_container_remove(GTK_CONTAINER(data), widget); +} + + +void vacate_widget(GtkWidget *widget) +{ + gtk_container_foreach(GTK_CONTAINER(widget), remove_child, widget); +} + + +void set_image(GtkWidget *widget, GtkWidget *image) +{ + vacate_widget(widget); + gtk_container_add(GTK_CONTAINER(widget), image); + gtk_widget_show_all(widget); +} + + +GtkWidget *tool_button(GtkWidget *bar, GdkDrawable *drawable, + char **xpm, const char *tooltip, + gboolean (*cb)(GtkWidget *widget, GdkEventButton *event, gpointer data), + gpointer data) +{ + GtkWidget *image, *evbox; + GtkToolItem *item; + + /* + * gtk_radio_tool_button_new_from_widget is *huge*. We try to do things + * in a + * more compact way. + */ + + evbox = gtk_event_box_new(); + if (xpm) { + image = make_image(drawable, xpm, tooltip); + gtk_container_add(GTK_CONTAINER(evbox), image); + } + g_signal_connect(G_OBJECT(evbox), "button_press_event", + G_CALLBACK(cb), data); + + item = gtk_tool_item_new(); + gtk_container_add(GTK_CONTAINER(item), evbox); + + gtk_container_set_border_width(GTK_CONTAINER(item), 0); + + gtk_toolbar_insert(GTK_TOOLBAR(bar), item, -1); + + return evbox; +} + + +/* ----- render a text string ---------------------------------------------- */ + + +void render_text(GdkDrawable *da, GdkGC *gc, int x, int y, double angle, + const char *s, const char *font, double xalign, double yalign, + int xmax, int ymax) +{ + GdkScreen *screen; + PangoRenderer *renderer; + PangoContext *context; + PangoLayout *layout; + PangoFontDescription *desc; + int width, height; + PangoMatrix m = PANGO_MATRIX_INIT; + double f_min, f; + + /* set up the renderer */ + + screen = gdk_drawable_get_screen(da); + renderer = gdk_pango_renderer_get_default(screen); + gdk_pango_renderer_set_drawable(GDK_PANGO_RENDERER(renderer), da); + gdk_pango_renderer_set_gc(GDK_PANGO_RENDERER(renderer), gc); + + /* start preparing the layout */ + + context = gdk_pango_context_get_for_screen(screen); + layout = pango_layout_new(context); + pango_layout_set_text(layout, s, -1); + + /* apply the font */ + + desc = pango_font_description_from_string(font); + pango_layout_set_font_description(layout, desc); + pango_font_description_free(desc); + + /* align and position the text */ + + pango_layout_get_size(layout, &width, &height); + f_min = 1.0; + if (xmax) { + f = xmax/((double) width/PANGO_SCALE); + if (f < f_min) + f_min = f; + } + if (ymax) { + f = ymax/((double) height/PANGO_SCALE); + if (f < f_min) + f_min = f; + } + if (f_min < MIN_FONT_SCALE) + f_min = MIN_FONT_SCALE; + pango_matrix_translate(&m, x, y); + pango_matrix_rotate(&m, angle); + pango_matrix_translate(&m, + -xalign*f_min*width/PANGO_SCALE, + (yalign-1)*f_min*height/PANGO_SCALE); + pango_matrix_scale(&m, f_min, f_min); + + pango_context_set_matrix(context, &m); + pango_layout_context_changed(layout); + pango_renderer_draw_layout(renderer, layout, 0, 0); + + /* clean up renderer */ + + gdk_pango_renderer_set_drawable(GDK_PANGO_RENDERER(renderer), NULL); + gdk_pango_renderer_set_gc(GDK_PANGO_RENDERER(renderer), NULL); + + /* free objects */ + + g_object_unref(layout); + g_object_unref(context); +} + + +/* ----- Debugging support ------------------------------------------------- */ + + +/* + * View with make montage or something like + * + * montage -label %f -frame 3 __dbg????.png png:- | display - + */ + +void debug_save_pixbuf(GdkPixbuf *buf) +{ + static int buf_num = 0; + char name[20]; /* plenty */ + + sprintf(name, "__dbg%04d.png", buf_num++); + gdk_pixbuf_save(buf, name, "png", NULL, NULL); + fprintf(stderr, "saved to %s\n", name); +} + + +/* + * gtk_widget_get_snapshot seems to use an expose event to do the drawing. This + * means that we can't call debug_save_widget from the expose event handler of + * the widget being dumped. + */ + +#if GTK_CHECK_VERSION(2, 14, 0) + +void debug_save_widget(GtkWidget *widget) +{ + GdkPixmap *pixmap; + GdkPixbuf *pixbuf; + gint w, h; + + pixmap = gtk_widget_get_snapshot(widget, NULL); + gdk_drawable_get_size(GDK_DRAWABLE(pixmap), &w, &h); + pixbuf = gdk_pixbuf_get_from_drawable(NULL, GDK_DRAWABLE(pixmap), + NULL, 0, 0, 0, 0, w, h); + debug_save_pixbuf(pixbuf); + gdk_pixmap_unref(pixmap); + g_object_unref(pixbuf); +} + +#endif /* GTK_CHECK_VERSION(2, 14, 0) */ + + +/* ----- kill the content of a container ----------------------------------- */ + + +static void destroy_callback(GtkWidget *widget, gpointer data) +{ + gtk_widget_destroy(widget); +} + + +void destroy_all_children(GtkContainer *container) +{ + gtk_container_foreach(container, destroy_callback, NULL); +} + + +/* ----- get a widget's desired width -------------------------------------- */ + + +int get_widget_width(GtkWidget *widget) +{ + GtkRequisition req; + + gtk_widget_show_all(widget); + gtk_widget_size_request(widget, &req); + return req.width; +} diff --git a/gui_util.h b/gui_util.h new file mode 100644 index 0000000..20e074a --- /dev/null +++ b/gui_util.h @@ -0,0 +1,84 @@ +/* + * gui_util.h - GUI helper functions + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#ifndef GUI_UTIL_H +#define GUI_UTIL_H + +#include <gtk/gtk.h> + +#include "coord.h" + + +struct draw_ctx { + GtkWidget *widget; + int scale; + struct coord center; +}; + +struct pix_buf { + GdkDrawable *da; + int x, y; + GdkPixbuf *buf; +}; + + +extern struct draw_ctx draw_ctx; + + +#define DA GDK_DRAWABLE(draw_ctx.widget->window) + + +GdkColor get_color(const char *spec); + +void set_width(GdkGC *gc, int width); + +void free_pix_buf(struct pix_buf *buf); +struct pix_buf *save_pix_buf(GdkDrawable *da, int xa, int ya, int xb, int yb, + int border); +void restore_pix_buf(struct pix_buf *buf); + +void draw_arc(GdkDrawable *da, GdkGC *gc, int fill, + int x, int y, int r, double a1, double a2); +void draw_circle(GdkDrawable *da, GdkGC *gc, int fill, + int x, int y, int r); + +/* tooltips are optional (use NULL for none) */ + +GtkWidget *label_in_box_new(const char *s, const char *tooltip); +GtkWidget *box_of_label(GtkWidget *label); +void label_in_box_fg(GtkWidget *box, const char *color); +void label_in_box_bg(GtkWidget *box, const char *color); + +void vacate_widget(GtkWidget *widget); + +GtkWidget *make_image(GdkDrawable *drawable, char **xpm, const char *tooltip); +GtkWidget *make_transparent_image(GdkDrawable *drawable, char **xpm, + const char *tooltip); +void set_image(GtkWidget *widget, GtkWidget *image); +GtkWidget *tool_button(GtkWidget *bar, GdkDrawable *drawable, + char **xpm, const char *tooltip, + gboolean (*cb)(GtkWidget *widget, GdkEventButton *event, gpointer data), + gpointer data); + +void render_text(GdkDrawable *da, GdkGC *gc, int x, int y, double angle, + const char *s, const char *font, double xalign, double yalign, + int xmax, int ymax); + +void debug_save_pixbuf(GdkPixbuf *buf); +void debug_save_widget(GtkWidget *widget); + +void destroy_all_children(GtkContainer *container); + +int get_widget_width(GtkWidget *widget); + +#endif /* !GUI_UTIL_H */ @@ -0,0 +1,86 @@ +/* + * hole.c - Classify holes and connect them with pads + * + * Written 2010 by Werner Almesberger + * Copyright 2010 by Werner Almesberger + * + * 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. + */ + + +#include "error.h" +#include "inst.h" +#include "overlap.h" +#include "hole.h" + + +static int check_through_hole(struct inst *pad, struct inst *hole) +{ + if (!overlap(pad, hole, ao_none)) + return 1; + if (!inside(hole, pad)) { + fail("hole (line %d) not completely inside " + "pad \"%s\" (line %d)", hole->obj->lineno, + pad->u.pad.name, pad->obj->lineno); + instantiation_error = pad->obj; + return 0; + } + if (hole->u.hole.pad) { + /* + * A hole can only be on several pads if the pads themselves + * overlap. We'll catch this error in refine_copper. + */ + return 1; + } + if (pad->u.pad.hole) { + fail("pad \"%s\" (line %d) has multiple holes (lines %d, %d)", + pad->u.pad.name, pad->obj->lineno, + hole->obj->lineno, pad->u.pad.hole->obj->lineno); + instantiation_error = pad->obj; + return 0; + } + pad->u.pad.hole = hole; + hole->u.hole.pad = pad; + return 1; +} + + +static int connect_holes(const struct pkg *pkg) +{ + struct inst *pad, *hole; + + for (pad = pkg->insts[ip_pad_copper]; pad; pad = pad->next) + for (hole = pkg->insts[ip_hole]; hole; hole = hole->next) + if (!check_through_hole(pad, hole)) + return 0; + return 1; +} + + +static void clear_links(const struct pkg *pkg) +{ + struct inst *pad, *hole; + + for (pad = pkg->insts[ip_pad_copper]; pad; pad = pad->next) + pad->u.pad.hole = NULL; + for (pad = pkg->insts[ip_pad_special]; pad; pad = pad->next) + pad->u.pad.hole = NULL; + for (hole = pkg->insts[ip_hole]; hole; hole = hole->next) + hole->u.hole.pad = NULL; +} + + +int link_holes(void) +{ + const struct pkg *pkg; + + for (pkg = pkgs; pkg; pkg = pkg->next) { + clear_links(pkg); + if (!connect_holes(pkg)) + return 0; + } + return 1; +} @@ -0,0 +1,18 @@ +/* + * hole.h - Classify holes and connect them with pads + * + * Written 2010 by Werner Almesberger + * Copyright 2010 by Werner Almesberger + * + * 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. + */ + +#ifndef HOLE_H +#define HOLE_H + +int link_holes(void); + +#endif /* !HOLE_H */ diff --git a/icons/all.fig b/icons/all.fig new file mode 100644 index 0000000..80a0bf2 --- /dev/null +++ b/icons/all.fig @@ -0,0 +1,22 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +0 32 #c0c000 +6 4350 3225 5625 4425 +2 1 0 15 13 7 50 -1 -1 0.000 1 1 -1 0 0 2 + 4500 3825 5475 3825 +2 1 0 15 13 7 50 -1 -1 0.000 1 1 -1 0 0 2 + 4650 3375 5250 4275 +2 1 0 15 13 7 50 -1 -1 0.000 1 1 -1 0 0 2 + 5250 3375 4650 4275 +-6 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 10 12 7 50 -1 -1 0.000 0 1 -1 0 0 3 + 3900 3690 3900 2850 5700 2850 diff --git a/icons/all_off.fig b/icons/all_off.fig new file mode 100644 index 0000000..ed4075a --- /dev/null +++ b/icons/all_off.fig @@ -0,0 +1,22 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +0 32 #c0c000 +6 4350 3225 5625 4425 +2 1 0 15 0 7 50 -1 -1 0.000 1 1 -1 0 0 2 + 4650 3375 5250 4275 +2 1 0 15 0 7 50 -1 -1 0.000 1 1 -1 0 0 2 + 5250 3375 4650 4275 +2 1 0 15 0 7 50 -1 -1 0.000 1 1 -1 0 0 2 + 4500 3825 5475 3825 +-6 +2 2 0 1 0 7 60 -1 10 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 10 0 7 50 -1 -1 0.000 0 1 -1 0 0 3 + 3900 3690 3900 2850 5700 2850 diff --git a/icons/arc.fig b/icons/arc.fig new file mode 100644 index 0000000..dca9d7e --- /dev/null +++ b/icons/arc.fig @@ -0,0 +1,13 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +5 1 0 10 16 7 50 -1 -1 0.000 0 1 1 0 4005.000 4395.000 5550 4500 5100 3300 3900 2850 + 0 0 10.00 450.00 600.00 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 diff --git a/icons/bright.fig b/icons/bright.fig new file mode 100644 index 0000000..4316137 --- /dev/null +++ b/icons/bright.fig @@ -0,0 +1,24 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +0 32 #b0ffff +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 2 0 0 0 4 50 -1 20 0.000 0 0 -1 0 0 5 + 4200 3150 4650 3150 4650 4050 4200 4050 4200 3150 +2 2 0 0 0 4 50 -1 20 0.000 0 0 -1 0 0 5 + 4950 3150 5400 3150 5400 4050 4950 4050 4950 3150 +2 2 0 10 3 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3900 2850 5700 2850 5700 4350 3900 4350 3900 2850 +2 2 0 15 29 7 60 -1 -1 0.000 0 0 -1 0 0 5 + 4950 3150 5400 3150 5400 4050 4950 4050 4950 3150 +2 2 0 15 29 7 60 -1 -1 0.000 0 0 -1 0 0 5 + 4200 3150 4650 3150 4650 4050 4200 4050 4200 3150 +2 2 0 20 32 7 60 -1 -1 0.000 0 0 -1 0 0 5 + 3900 2850 5700 2850 5700 4350 3900 4350 3900 2850 diff --git a/icons/bright_off.fig b/icons/bright_off.fig new file mode 100644 index 0000000..f813d9a --- /dev/null +++ b/icons/bright_off.fig @@ -0,0 +1,18 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +0 32 #b0ffff +2 2 0 0 0 19 50 -1 20 0.000 0 0 -1 0 0 5 + 4200 3150 4650 3150 4650 4050 4200 4050 4200 3150 +2 2 0 0 0 19 50 -1 20 0.000 0 0 -1 0 0 5 + 4950 3150 5400 3150 5400 4050 4950 4050 4950 3150 +2 2 0 1 0 7 65 -1 10 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 2 0 10 16 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3900 2850 5700 2850 5700 4350 3900 4350 3900 2850 diff --git a/icons/circ.fig b/icons/circ.fig new file mode 100644 index 0000000..2656168 --- /dev/null +++ b/icons/circ.fig @@ -0,0 +1,12 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +1 3 0 10 16 7 50 -1 -1 0.000 1 0.0000 4800 3600 900 900 4800 3600 5700 3600 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 diff --git a/icons/delete.fig b/icons/delete.fig new file mode 100644 index 0000000..79ea705 --- /dev/null +++ b/icons/delete.fig @@ -0,0 +1,15 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 15 19 7 50 -1 -1 0.000 0 1 -1 0 0 2 + 4125 2925 5475 4275 +2 1 0 15 19 7 50 -1 -1 0.000 0 1 -1 0 0 2 + 4125 4275 5475 2925 diff --git a/icons/delete_off.fig b/icons/delete_off.fig new file mode 100644 index 0000000..f04e6df --- /dev/null +++ b/icons/delete_off.fig @@ -0,0 +1,15 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 55 -1 10 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 15 0 7 50 -1 -1 0.000 0 1 -1 0 0 2 + 4125 2925 5475 4275 +2 1 0 15 0 7 50 -1 -1 0.000 0 1 -1 0 0 2 + 4125 4275 5475 2925 diff --git a/icons/frame.fig b/icons/frame.fig new file mode 100644 index 0000000..117d3cb --- /dev/null +++ b/icons/frame.fig @@ -0,0 +1,16 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +6 3750 3225 5775 4200 +2 1 0 10 12 7 50 -1 -1 0.000 0 0 -1 0 0 3 + 3900 4125 3900 3525 5700 3525 +4 0 12 50 -1 22 42 0.0000 4 135 450 3750 3375 FRAME\001 +-6 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 diff --git a/icons/hole.fig b/icons/hole.fig new file mode 100644 index 0000000..6c026f2 --- /dev/null +++ b/icons/hole.fig @@ -0,0 +1,22 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +1 3 0 8 0 7 45 -1 20 0.000 1 0.0000 4800 3600 600 600 4800 3600 5400 3600 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 5 0 7 50 -1 20 0.000 0 1 -1 0 0 2 + 4050 4350 5550 2850 +2 1 0 5 0 7 50 -1 20 0.000 0 1 -1 0 0 2 + 3900 3900 5100 2700 +2 1 0 5 0 7 50 -1 20 0.000 0 1 -1 0 0 2 + 3900 3300 4500 2700 +2 1 0 5 0 7 50 -1 20 0.000 0 1 -1 0 0 2 + 4500 4500 5700 3300 +2 1 0 5 0 7 50 -1 20 0.000 0 1 -1 0 0 2 + 5100 4500 5700 3900 diff --git a/icons/line.fig b/icons/line.fig new file mode 100644 index 0000000..0bba78e --- /dev/null +++ b/icons/line.fig @@ -0,0 +1,13 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 10 16 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3900 4200 5700 3000 diff --git a/icons/meas.fig b/icons/meas.fig new file mode 100644 index 0000000..edc649a --- /dev/null +++ b/icons/meas.fig @@ -0,0 +1,19 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 10 21 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3900 3600 4200 4200 +2 1 0 10 21 7 50 -1 -1 0.000 0 0 -1 1 1 2 + 0 0 10.00 450.00 450.00 + 0 0 10.00 450.00 450.00 + 4050 3900 5550 3150 +2 1 0 10 21 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 5400 2850 5700 3450 diff --git a/icons/meas_off.fig b/icons/meas_off.fig new file mode 100644 index 0000000..97e03cb --- /dev/null +++ b/icons/meas_off.fig @@ -0,0 +1,19 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 10 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 10 0 7 40 -1 -1 0.000 0 0 -1 0 0 2 + 3900 3600 4200 4200 +2 1 0 10 0 7 40 -1 -1 0.000 0 0 -1 1 1 2 + 0 0 10.00 450.00 450.00 + 0 0 10.00 450.00 450.00 + 4050 3900 5550 3150 +2 1 0 10 0 7 40 -1 -1 0.000 0 0 -1 0 0 2 + 5400 2850 5700 3450 diff --git a/icons/meas_x.fig b/icons/meas_x.fig new file mode 100644 index 0000000..2213c28 --- /dev/null +++ b/icons/meas_x.fig @@ -0,0 +1,23 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +6 3975 2775 5625 4125 +2 1 0 10 0 7 45 -1 -1 0.000 0 1 -1 0 0 2 + 4050 3600 5550 2850 +2 1 0 10 21 7 50 -1 -1 0.000 0 1 -1 0 0 2 + 5550 2850 5550 3900 +2 1 0 10 21 7 50 -1 -1 0.000 0 1 -1 0 0 2 + 4050 3600 4050 3900 +2 1 0 10 21 7 50 -1 -1 0.000 0 0 -1 1 1 2 + 0 0 10.00 450.00 450.00 + 0 0 10.00 450.00 450.00 + 4050 3900 5550 3900 +-6 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 diff --git a/icons/meas_y.fig b/icons/meas_y.fig new file mode 100644 index 0000000..3bf698e --- /dev/null +++ b/icons/meas_y.fig @@ -0,0 +1,23 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +6 3825 2700 5475 4200 +2 1 0 10 21 7 50 -1 -1 0.000 0 0 -1 1 1 2 + 0 0 10.00 450.00 450.00 + 0 0 10.00 450.00 450.00 + 5250 2775 5250 4125 +2 1 0 10 21 7 50 -1 -1 0.000 0 1 -1 0 0 2 + 3900 4125 5250 4125 +2 1 0 10 0 7 45 -1 -1 0.000 0 1 -1 0 0 2 + 3900 4125 4650 2775 +2 1 0 10 21 7 50 -1 -1 0.000 0 1 -1 0 0 2 + 4650 2775 5250 2775 +-6 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 diff --git a/icons/pad.fig b/icons/pad.fig new file mode 100644 index 0000000..b7a2169 --- /dev/null +++ b/icons/pad.fig @@ -0,0 +1,15 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 2 0 0 0 4 50 -1 20 0.000 0 0 -1 0 0 5 + 4200 2700 5400 2700 5400 4500 4200 4500 4200 2700 +2 1 0 15 7 7 45 -1 -1 0.000 1 1 -1 0 0 3 + 4875 4050 4875 3150 4650 3375 diff --git a/icons/point.fig b/icons/point.fig new file mode 100644 index 0000000..4d5d645 --- /dev/null +++ b/icons/point.fig @@ -0,0 +1,14 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 3 0 0 0 7 50 -1 10 0.000 0 0 -1 0 0 8 + 3900 4350 4050 4500 5250 3300 5325 3675 5700 2700 4725 3075 + 5100 3150 3900 4350 diff --git a/icons/rect.fig b/icons/rect.fig new file mode 100644 index 0000000..04723b5 --- /dev/null +++ b/icons/rect.fig @@ -0,0 +1,13 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 2 0 10 16 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3900 3000 5700 3000 5700 4200 3900 4200 3900 3000 diff --git a/icons/rpad.fig b/icons/rpad.fig new file mode 100644 index 0000000..18d7f3f --- /dev/null +++ b/icons/rpad.fig @@ -0,0 +1,17 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +5 1 0 0 0 4 50 -1 20 0.000 0 1 0 0 4800.000 3825.000 4125 3825 4800 4500 5475 3825 +5 1 0 0 0 4 50 -1 20 0.000 0 0 0 0 4800.000 3375.000 4125 3375 4800 2700 5475 3375 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 15 7 7 45 -1 -1 0.000 1 1 -1 0 0 3 + 4875 4050 4875 3150 4650 3375 +2 2 0 0 0 4 50 -1 20 0.000 0 0 -1 0 0 5 + 4125 3375 5475 3375 5475 3825 4125 3825 4125 3375 diff --git a/icons/stuff.fig b/icons/stuff.fig new file mode 100644 index 0000000..08f2d82 --- /dev/null +++ b/icons/stuff.fig @@ -0,0 +1,17 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +0 32 #c0c000 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 10 32 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 10.00 300.00 300.00 + 3900 4515 5700 3315 +2 1 0 10 12 7 50 -1 -1 0.000 0 1 -1 0 0 3 + 3900 3600 3900 2760 5700 2760 diff --git a/icons/stuff_off.fig b/icons/stuff_off.fig new file mode 100644 index 0000000..b921711 --- /dev/null +++ b/icons/stuff_off.fig @@ -0,0 +1,17 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +0 32 #c0c000 +2 1 0 10 0 7 40 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 10.00 300.00 300.00 + 3900 4515 5700 3315 +2 1 0 10 0 7 40 -1 -1 0.000 0 1 -1 0 0 3 + 3900 3600 3900 2760 5700 2760 +2 2 0 1 0 7 50 -1 10 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 diff --git a/icons/template.fig b/icons/template.fig new file mode 100644 index 0000000..2979ddd --- /dev/null +++ b/icons/template.fig @@ -0,0 +1,11 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 diff --git a/icons/vec.fig b/icons/vec.fig new file mode 100644 index 0000000..2e2e58b --- /dev/null +++ b/icons/vec.fig @@ -0,0 +1,17 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Inches +A4 +100.00 +Single +-2 +1200 2 +0 32 #a0a000 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3600 2400 6000 2400 6000 4800 3600 4800 3600 2400 +2 1 0 10 32 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 10.00 300.00 300.00 + 3900 4275 5700 3075 +2 1 0 10 32 7 50 -1 -1 0.000 1 1 -1 0 0 3 + 4050 3150 4575 3525 4425 2925 @@ -0,0 +1,1423 @@ +/* + * inst.c - Instance structures + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> + +#include "util.h" +#include "error.h" +#include "coord.h" +#include "expr.h" +#include "layer.h" +#include "obj.h" +#include "delete.h" +#include "gui_util.h" +#include "gui_status.h" +#include "gui_canvas.h" +#include "gui_tool.h" +#include "gui_meas.h" +#include "gui_inst.h" +#include "gui_frame.h" +#include "gui.h" +#include "inst.h" + + +struct inst *selected_inst = NULL; +struct bbox active_frame_bbox; +struct pkg *pkgs, *active_pkg, *curr_pkg; +struct pkg *reachable_pkg = NULL; +struct inst *frame_instantiating = NULL; + +static struct pkg *prev_pkgs, *prev_reachable_pkg; + +static unsigned long active_set = 0; + +static struct inst_ops vec_ops; +static struct inst_ops frame_ops; +static struct inst_ops meas_ops; + + +#define IS_ACTIVE ((active_set & 1)) + + +/* ----- selective visibility ---------------------------------------------- */ + + +static int show(enum inst_prio prio) +{ + switch (prio) { + case ip_vec: + case ip_frame: + return show_stuff; + case ip_meas: + return show_meas; + default: + return 1; + } +} + + +int bright(const struct inst *inst) +{ + if (!show_bright) + return 0; + return inst->ops != &vec_ops && inst->ops != &frame_ops && + inst->ops != &meas_ops; +} + + +static int show_this(const struct inst *inst) +{ + if (show_all) + return 1; + if (inst->ops == &frame_ops && inst->u.frame.ref == active_frame) + return 1; + if (!inst->outer) + return active_frame == frames; + return inst->outer->u.frame.ref == active_frame; +} + + +/* ----- selection of items not on the canvas ------------------------------ */ + + +static void *selected_outside = NULL; +static void (*outside_deselect)(void *item); + + +static void deselect_outside(void) +{ + if (selected_outside && outside_deselect) + outside_deselect(selected_outside); + selected_outside = NULL; +} + + +void inst_select_outside(void *item, void (*deselect)(void *item)) +{ + if (item == selected_outside) + return; + deselect_outside(); + inst_deselect(); + selected_outside = item; + outside_deselect = deselect; +} + + +/* ----- check connectedness ----------------------------------------------- */ + + +/* + * After an instantiation failure, the instances can get out of sync with the + * object tree, and attempts to select an item on the canvas can cause accesses + * to objects that aren't there anymore. So we need to check if we can still + * reach the corresponding object. + * + * Note: even this isn't bullet-proof. Theoretically, we may get a new object + * in the old place. However, this probably doesn't do any serious damage. + */ + + +static int inst_connected(const struct inst *inst) +{ + const struct frame *frame; + const struct vec *vec; + const struct obj *obj; + + for (frame = frames; frame; frame = frame->next) { + if (inst->ops == &vec_ops) { + for (vec = frame->vecs; vec; vec = vec->next) + if (vec == inst->vec) + return 1; + } else { + for (obj = frame->objs; obj; obj = obj->next) + if (obj == inst->obj) + return 1; + } + } + return 0; +} + + +/* ----- selection --------------------------------------------------------- */ + + +static void inst_select_inst(struct inst *inst) +{ + selected_inst = inst; + tool_selected_inst(inst); + gui_frame_select_inst(inst); + if (inst->ops->select) + selected_inst->ops->select(inst); + status_set_icon(get_icon_by_inst(inst)); +} + + +/* + * @@@ This logic is overly complicated and should be simplified. The general + * idea was to avoid making unnecessary changes to the user's selections, but + * that risk doesn't exist. Furthermore, the way activate_item is used, its + * preconditions aren't met. It works anyway but it could be simpler as a + * consequence. + * + * activate_item tries to activate the path through the frame references, + * leading to a specific instance. It returns whether this is failed or whether + * it may have been successful. + * + * The initial condition is that we want to activate an item on a frame + * instance that's not active. Since the frame has been instantiated, there + * must be a way to activate it. We just have to find out how. + * + * The first test eliminates the root frame. If we're at the root frame and + * still haven't figured out what to do, something is wrong and we give up. + * + * The next test skips references that are already right. Since we know that + * there must be at least one reference that leads elsewhere, and we haven't + * found it yet, the recursion will tell us whether it can find it at all. + * + * Finally, if we've found a mismatch, we correct it. We then try to fix any + * further mismatches. Since we've made progress, we return 1, even if the + * other fixes should fail (or reach the root frame). + * + */ + +static int activate_item(struct inst *inst) +{ + if (!inst->outer) + return 0; + if (inst->outer->u.frame.ref->active_ref == inst->outer->obj) + return activate_item(inst->outer); + inst->outer->u.frame.ref->active_ref = inst->outer->obj; + activate_item(inst->outer); + return 1; +} + + +static int __inst_select(struct coord pos, int tries) +{ + enum inst_prio prio; + const struct inst *prev; + struct inst *inst; + struct inst *first = NULL; /* first active item */ + struct inst *next = NULL; /* active item after currently sel. */ + struct inst *any_first = NULL; /* first item, active or inactive */ + struct inst *any_same_frame = NULL; /* first item on active frame */ + struct frame *frame; + int best_dist = 0; /* keep gcc happy */ + int select_next; + int dist, i; + + if (!tries) { + fprintf(stderr, "__inst_select: tries exhausted\n"); + return 0; + } + prev = selected_inst; + deselect_outside(); + edit_nothing(); + if (selected_inst) { + gui_frame_deselect_inst(selected_inst); + tool_selected_inst(NULL); + } + inst_deselect(); + select_next = 0; + FOR_INST_PRIOS_DOWN(prio) { + if (!show(prio)) + continue; + FOR_ALL_INSTS(i, prio, inst) { + if (!show_this(inst)) + continue; + if (!inst->ops->distance) + continue; + if (!inst_connected(inst)) + continue; + dist = inst->ops->distance(inst, pos, draw_ctx.scale); + if (dist >= 0) { + if (!any_first) + any_first = inst; + if (!any_same_frame && inst->outer && + inst->outer->u.frame.ref == active_frame) + any_same_frame = inst; + if (!inst->active) + continue; + if (!first) + first = inst; + if (!next && select_next) + next = inst; + if (inst == prev) + select_next = 1; + if (!selected_inst || best_dist > dist) { + selected_inst = inst; + best_dist = dist; + } + } + } + } + if (select_next) { + selected_inst = next ? next : first; + goto selected; + } + if (selected_inst) + goto selected; + + /* give vectors a second chance */ + + if (show_stuff) { + FOR_ALL_INSTS(i, ip_vec, inst) { + if (!inst->active) + continue; + if (!inst_connected(inst)) + continue; + dist = gui_dist_vec_fallback(inst, pos, draw_ctx.scale); + if (dist >= 0 && (!selected_inst || best_dist > dist)) { + selected_inst = inst; + best_dist = dist; + } + } + + if (selected_inst) + goto selected; + } + + if (!show_all) + return 0; + + if (any_same_frame) { + activate_item(any_same_frame); + search_inst(any_same_frame); + instantiate(); + change_world(); + return __inst_select(pos, tries-1); + } + if (any_first) { + frame = any_first->outer ? any_first->outer->u.frame.ref : NULL; + if (frame != active_frame) { + select_frame(frame); + return __inst_select(pos, tries-1); + } + } + + return 0; + +selected: + inst_select_inst(selected_inst); + return 1; +} + + +int inst_select(struct coord pos) +{ + /* + * We shouldn't need more than 2 tries to select any item, so 5 is more + * than enough. This can still fail, but then it would for any number + * of tries. + */ + return __inst_select(pos, 5); +} + + +struct inst *inst_find_point(struct coord pos) +{ + struct inst *inst, *found; + int best_dist = 0; /* keep gcc happy */ + int dist, i; + + found = NULL; + FOR_ALL_INSTS(i, ip_frame, inst) { + if (!inst->u.frame.active) + continue; + dist = gui_dist_frame_eye(inst, pos, draw_ctx.scale); + if (dist >= 0 && (!found || best_dist > dist)) { + found = inst; + best_dist = dist; + } + } + if (found) + return found; + + FOR_ALL_INSTS(i, ip_vec, inst) { + if (!inst->active || !inst->ops->distance) + continue; + dist = inst->ops->distance(inst, pos, draw_ctx.scale); + if (dist >= 0 && (!found || best_dist > dist)) { + found = inst; + best_dist = dist; + } + } + return found; +} + + +int inst_find_point_selected(struct coord pos, struct inst **res) +{ + struct vec **anchors[3]; + int n, best_i, i; + struct inst *best = NULL; + struct inst *inst; + int d_min, d, j; + + assert(selected_inst); + n = inst_anchors(selected_inst, anchors); + for (i = 0; i != n; i++) { + if (*anchors[i]) { + FOR_ALL_INSTS(j, ip_vec, inst) { + if (inst->vec != *anchors[i]) + continue; + d = gui_dist_vec(inst, pos, draw_ctx.scale); + if (d != -1 && (!best || d < d_min)) { + best = inst; + best_i = i; + d_min = d; + } + } + } else { + FOR_ALL_INSTS(j, ip_frame, inst) { + if (inst != selected_inst->outer) + continue; + d = gui_dist_frame(inst, pos, draw_ctx.scale); + if (d != -1 && (!best || d < d_min)) { + best = inst; + best_i = i; + d_min = d; + } + } + } + } + if (!best) + return -1; + if (res) + *res = best; + return best_i; +} + + +struct coord inst_get_point(const struct inst *inst) +{ + if (inst->ops == &vec_ops) + return inst->u.vec.end; + if (inst->ops == &frame_ops) + return inst->base; + abort(); +} + + +struct vec *inst_get_vec(const struct inst *inst) +{ + if (inst->ops == &vec_ops) + return inst->vec; + if (inst->ops == &frame_ops) + return NULL; + abort(); +} + + +int inst_anchors(struct inst *inst, struct vec ***anchors) +{ + if (inst->vec) { + anchors[0] = &inst->vec->base; + return 1; + } + return obj_anchors(inst->obj, anchors); +} + + +void inst_deselect(void) +{ + if (selected_inst) { + tool_selected_inst(NULL); + gui_frame_deselect_inst(selected_inst); + } + deselect_outside(); + status_set_type_x(NULL, ""); + status_set_type_y(NULL, ""); + status_set_type_entry(NULL, ""); + status_set_name(NULL, ""); + status_set_x(NULL, ""); + status_set_y(NULL, ""); + status_set_r(NULL, ""); + status_set_angle(NULL, ""); + selected_inst = NULL; + edit_nothing(); + refresh_pos(); + status_set_icon(NULL); +} + + +/* ----- select instance by vector/object ---------------------------------- */ + + +static void vec_edit(struct vec *vec); +static void obj_edit(struct obj *obj); + + +void inst_select_vec(struct vec *vec) +{ + struct inst *inst; + int i; + + if (vec->frame != active_frame) + select_frame(vec->frame); + FOR_ALL_INSTS(i, ip_vec, inst) + if (inst->vec == vec && inst->active) { + inst_deselect(); + inst_select_inst(inst); + return; + } + vec_edit(vec); +} + + +void inst_select_obj(struct obj *obj) +{ + enum inst_prio prio; + struct inst *inst; + int i; + + if (obj->frame != active_frame) + select_frame(obj->frame); + FOR_INST_PRIOS_DOWN(prio) + FOR_ALL_INSTS(i, prio, inst) + if (inst->obj && inst->obj == obj && inst->active) + goto found; + obj_edit(obj); + return; + +found: + inst_deselect(); + inst_select_inst(inst); +} + + +/* ----- common status reporting ------------------------------------------- */ + + +static void rect_status(struct coord a, struct coord b, unit_type width, + int rounded) +{ + const char *tip; + struct coord d = sub_vec(b, a); + double r; + unit_type diag; + + status_set_xy(d); + tip = "Angle of diagonal"; + if (!d.x && !d.y) { + status_set_angle(tip, "a = 0 deg"); + } else { + status_set_angle(tip, "a = %3.1f deg", theta(a, b)); + } + if (d.x < 0) + d.x = -d.x; + if (d.y < 0) + d.y = -d.y; + diag = hypot(d.x, d.y); + if (rounded) { + /* + * Only consider the part of the diagonal that is on the pad + * surface. + * + * The circle: (x-r)^2+(y-r)^2 = r^2 + * The diagonal: x = t*cos(theta), y = t*sin(theta) + * + * t is the distance from the corner of the surrounding + * rectangle to the half-circle: + * + * t = 2*r*(s+c-sqrt(2*s*c)) + * + * With s = sin(theta) and c = cos(theta). + * + * Since d.x = diag*cos(theta), we don't need to calculate the + * sinus and cosinus but can use d.x and d.y directly. + */ + r = (d.x > d.y ? d.y : d.x)/2; + diag -= 2*r*(d.x+d.y-sqrt(2*d.x*d.y))/diag; + } + set_with_units(status_set_r, "d = ", diag, "Length of diagonal"); + if (width != -1) { + status_set_type_entry(NULL, "width ="); + set_with_units(status_set_name, "", width, "Line width"); + } +} + + +static void rect_status_sort(struct coord a, struct coord b, unit_type width, + int rounded) +{ + sort_coord(&a, &b); + rect_status(a, b, width, rounded); +} + + +/* ----- helper functions for instance creation ---------------------------- */ + + +static void update_bbox(struct bbox *bbox, struct coord coord) +{ + if (bbox->min.x > coord.x) + bbox->min.x = coord.x; + if (bbox->max.x < coord.x) + bbox->max.x = coord.x; + if (bbox->min.y > coord.y) + bbox->min.y = coord.y; + if (bbox->max.y < coord.y) + bbox->max.y = coord.y; +} + + +static void propagate_bbox(const struct inst *inst) +{ + struct inst *frame = frame_instantiating ? + frame_instantiating : curr_pkg->insts[ip_frame]; + + update_bbox(&frame->bbox, inst->bbox.min); + update_bbox(&frame->bbox, inst->bbox.max); + + if (curr_pkg->bbox.min.x || curr_pkg->bbox.min.y || + curr_pkg->bbox.max.x || curr_pkg->bbox.max.y) { + update_bbox(&curr_pkg->bbox, inst->bbox.min); + update_bbox(&curr_pkg->bbox, inst->bbox.max); + } else { + curr_pkg->bbox = inst->bbox; + } +} + + +static void grow_bbox_by_width(struct bbox *bbox, unit_type width) +{ + bbox->min.x -= width/2; + bbox->min.y -= width/2; + bbox->max.x += width/2; + bbox->max.y += width/2; +} + + +static int zero_sized(struct coord a, struct coord b, const char *fmt, + const char *arg) +{ + if (a.x == b.x && a.y == b.y) { + fail(fmt, "zero-sized", arg); + return 1; + } + if (a.x == b.x) { + fail(fmt, "zero-width", arg); + return 1; + } + if (a.y == b.y) { + fail(fmt, "zero-height", arg); + return 1; + } + return 0; +} + + +static struct inst *add_inst(const struct inst_ops *ops, enum inst_prio prio, + struct coord base) +{ + struct inst *inst; + + inst = alloc_type(struct inst); + inst->ops = ops; + inst->prio = prio; + inst->vec = NULL; + inst->obj = NULL; + inst->base = inst->bbox.min = inst->bbox.max = base; + inst->outer = frame_instantiating; + inst->active = IS_ACTIVE; + inst->next = NULL; + *curr_pkg->next_inst[prio] = inst; + curr_pkg->next_inst[prio] = &inst->next; + return inst; +} + + +/* ----- vec --------------------------------------------------------------- */ + + +static int validate_vec_name(const char *s, void *ctx) +{ + struct vec *vec = ctx; + const struct vec *walk; + + if (!is_id(s)) + return 0; + for (walk = vec->frame->vecs; walk; walk = walk->next) + if (walk->name && !strcmp(walk->name, s)) + return 0; + return 1; +} + + +static void vec_edit(struct vec *vec) +{ + edit_x(&vec->x, "X distance"); + edit_y(&vec->y, "Y distance"); + edit_unique_null(&vec->name, validate_vec_name, vec, "Vector name"); +} + + +static void vec_op_select(struct inst *self) +{ + status_set_type_entry(NULL, "ref ="); + status_set_name("Vector reference (name)", + "%s", self->vec->name ? self->vec->name : ""); + rect_status(self->base, self->u.vec.end, -1, 0); + vec_edit(self->vec); +} + + +/* + * @@@ The logic of gui_find_point_vec isn't great. Instead of selecting a + * point and then filtering, we should filter the candidates, so that a point + * that's close end eligible can win against one that's closer but not + * eligible. + */ + +static struct inst *find_point_vec(struct inst *self, struct coord pos) +{ + struct inst *inst; + const struct vec *vec; + + inst = inst_find_point(pos); + if (!inst) + return NULL; + if (inst->ops == &frame_ops) + return inst; + for (vec = inst->vec; vec; vec = vec->base) + if (vec == self->vec) + return NULL; + return inst; +} + + +/* + * When instantiating and when dumping, we assume that bases appear in the + * frame->vecs list before vectors using them. A move may change this order. + * We therefore have to sort the list after the move. + * + * Since the list is already ordered, cleaning it up is just O(n). + */ + + +static void do_move_to_vec(struct inst *inst, struct inst *to, int i) +{ + struct vec *to_vec = inst_get_vec(to); + struct vec *vec = inst->vec; + struct frame *frame = vec->frame; + struct vec *v, **anchor, **walk; + + assert(!i); + vec->base = to_vec; + + /* + * Mark the vector that's being rebased and all vectors that + * (recursively) depend on it. + * + * We're mainly interested in the range between the vector being moved + * and the new base. If the vector follows the base, the list is + * already in the correct order and nothing needs moving. + */ + for (v = frame->vecs; v != vec; v = v->next) + v->mark = 0; + vec->mark = 1; + for (v = vec->next; v && v != to_vec; v = v->next) + v->mark = v->base ? v->base->mark : 0; + if (!v) + return; + + /* + * All the marked vectors appearing on the list before the new base + * are moved after the new base, preserving their order. + * + * Start at frame->vecs, not "vec", so that we move the the vector + * being rebased as well. + */ + anchor = &to_vec->next; + walk = &frame->vecs; + while (*walk != to_vec) { + v = *walk; + if (!v->mark) { + walk = &v->next; + } else { + *walk = v->next; + v->next = *anchor; + *anchor = v; + anchor = &v->next; + } + } +} + + +static struct inst_ops vec_ops = { + .draw = gui_draw_vec, + .hover = gui_hover_vec, + .distance = gui_dist_vec, + .select = vec_op_select, + .find_point = find_point_vec, + .draw_move = draw_move_vec, + .do_move_to = do_move_to_vec, +}; + + +int inst_vec(struct vec *vec, struct coord base) +{ + struct inst *inst; + + inst = add_inst(&vec_ops, ip_vec, base); + inst->vec = vec; + inst->u.vec.end = vec->pos; + find_inst(inst); + update_bbox(&inst->bbox, vec->pos); + propagate_bbox(inst); + return 1; +} + + +/* ----- line -------------------------------------------------------------- */ + + +static void obj_line_edit(struct obj *obj) +{ + edit_dist_expr(&obj->u.line.width, "Line width"); +} + + +static void line_op_select(struct inst *self) +{ + rect_status_sort(self->base, self->u.rect.end, self->u.rect.width, 0); + obj_line_edit(self->obj); +} + + +static struct inst_ops line_ops = { + .draw = gui_draw_line, + .distance = gui_dist_line, + .select = line_op_select, + .draw_move = draw_move_line, +}; + + +int inst_line(struct obj *obj, struct coord a, struct coord b, unit_type width) +{ + struct inst *inst; + + inst = add_inst(&line_ops, ip_line, a); + inst->obj = obj; + inst->u.rect.end = b; + inst->u.rect.width = width; + find_inst(inst); + update_bbox(&inst->bbox, b); + grow_bbox_by_width(&inst->bbox, width); + propagate_bbox(inst); + return 1; +} + + +/* ----- rect -------------------------------------------------------------- */ + + +static void obj_rect_edit(struct obj *obj) +{ + edit_dist_expr(&obj->u.rect.width, "Line width"); +} + + +static void rect_op_select(struct inst *self) +{ + rect_status_sort(self->base, self->u.rect.end, self->u.rect.width, 0); + obj_rect_edit(self->obj); +} + + +static struct inst_ops rect_ops = { + .draw = gui_draw_rect, + .distance = gui_dist_rect, + .select = rect_op_select, + .draw_move = draw_move_rect, +}; + + +int inst_rect(struct obj *obj, struct coord a, struct coord b, unit_type width) +{ + struct inst *inst; + + inst = add_inst(&rect_ops, ip_rect, a); + inst->obj = obj; + inst->u.rect.end = b; + inst->u.rect.width = width; + find_inst(inst); + update_bbox(&inst->bbox, b); + grow_bbox_by_width(&inst->bbox, width); + propagate_bbox(inst); + return 1; +} + + +/* ----- pad / rpad -------------------------------------------------------- */ + + +static int validate_pad_name(const char *s, void *ctx) +{ + char *tmp; + + status_begin_reporting(); + tmp = expand(s, NULL); + if (!tmp) + return 0; + free(tmp); + return 1; +} + + +static void obj_pad_edit(struct obj *obj) +{ + edit_pad_type(&obj->u.pad.type); + edit_name(&obj->u.pad.name, validate_pad_name, NULL, + "Pad name (template)"); +} + + +static void pad_op_select(struct inst *self) +{ + status_set_type_entry(NULL, "label ="); + status_set_name("Pad name (actual)", "%s", self->u.pad.name); + rect_status_sort(self->base, self->u.pad.other, -1, 0); + obj_pad_edit(self->obj); +} + + +static struct inst_ops pad_ops = { + .draw = gui_draw_pad, + .distance = gui_dist_pad, + .select = pad_op_select, + .draw_move = draw_move_pad, +}; + + +static void rpad_op_select(struct inst *self) +{ + status_set_type_entry(NULL, "label ="); + status_set_name("Pad name (actual)", "%s", self->u.pad.name); + rect_status_sort(self->base, self->u.pad.other, -1, 1); + obj_pad_edit(self->obj); +} + + +static struct inst_ops rpad_ops = { + .draw = gui_draw_rpad, + .distance = gui_dist_pad, /* @@@ */ + .select = rpad_op_select, + .draw_move = draw_move_rpad, +}; + + +int inst_pad(struct obj *obj, const char *name, struct coord a, struct coord b) +{ + struct inst *inst; + + if (zero_sized(a, b, "%s pad \"%s\"", name)) + return 0; + inst = add_inst(obj->u.pad.rounded ? &rpad_ops : &pad_ops, + obj->u.pad.type == pt_normal || obj->u.pad.type == pt_bare || + obj->u.pad.type == pt_trace ? + ip_pad_copper : ip_pad_special, a); + inst->obj = obj; + inst->u.pad.name = stralloc(name); + inst->u.pad.other = b; + inst->u.pad.layers = pad_type_to_layers(obj->u.pad.type); + find_inst(inst); + update_bbox(&inst->bbox, b); + propagate_bbox(inst); + return 1; +} + + +/* ----- hole -------------------------------------------------------------- */ + + +static void hole_op_select(struct inst *self) +{ + rect_status_sort(self->base, self->u.hole.other, -1, 1); +} + + +static struct inst_ops hole_ops = { + .draw = gui_draw_hole, + .distance = gui_dist_hole, + .select = hole_op_select, + .draw_move = draw_move_hole, +}; + + +int inst_hole(struct obj *obj, struct coord a, struct coord b) +{ + struct inst *inst; + + if (zero_sized(a, b, "%s hole", NULL)) + return 0; + inst = add_inst(&hole_ops, ip_hole, a); + inst->obj = obj; + inst->u.hole.other = b; + inst->u.hole.layers = mech_hole_layers(); + find_inst(inst); + update_bbox(&inst->bbox, b); + propagate_bbox(inst); + return 1; +} + + +/* ----- arc --------------------------------------------------------------- */ + + +static void obj_arc_edit(struct obj *obj) +{ + edit_dist_expr(&obj->u.arc.width, "Line width"); +} + + +static void arc_op_select(struct inst *self) +{ + status_set_xy(self->base); + status_set_angle("Angle", "a = %3.1f deg", + self->u.arc.a1 == self->u.arc.a2 ? 360 : + self->u.arc.a2-self->u.arc.a1); + set_with_units(status_set_r, "r = ", self->u.arc.r, "Radius"); + status_set_type_entry(NULL, "width ="); + set_with_units(status_set_name, "", self->u.arc.width, "Line width"); + obj_arc_edit(self->obj); +} + + +static struct inst_ops arc_ops = { + .draw = gui_draw_arc, + .distance = gui_dist_arc, + .select = arc_op_select, + .draw_move = draw_move_arc, + .do_move_to = do_move_to_arc, +}; + + +int inst_arc(struct obj *obj, struct coord center, struct coord start, + struct coord end, unit_type width) +{ + struct inst *inst; + double r, a1, a2; + + a1 = theta(center, start); + a2 = theta(center, end); + inst = add_inst(&arc_ops, + fmod(a1, 360) == fmod(a2, 360) ? ip_circ : ip_arc, center); + inst->obj = obj; + r = hypot(start.x-center.x, start.y-center.y); + inst->u.arc.r = r; + inst->u.arc.a1 = a1; + inst->u.arc.a2 = a2; + inst->u.arc.width = width; + inst->bbox.min.x = center.x-r; + inst->bbox.max.x = center.x+r; + inst->bbox.min.y = center.y-r; + inst->bbox.max.y = center.y+r; + find_inst(inst); + grow_bbox_by_width(&inst->bbox, width); + propagate_bbox(inst); + return 1; +} + + +/* ----- measurement ------------------------------------------------------- */ + + +static void obj_meas_edit(struct obj *obj) +{ + edit_dist_expr(&obj->u.meas.offset, "Measurement line offset"); +} + + +static void meas_op_select(struct inst *self) +{ + rect_status_sort(self->base, self->u.meas.end, -1, 0); + status_set_type_entry(NULL, "offset ="); + set_with_units(status_set_name, "", self->u.meas.offset, + "Measurement line offset"); + obj_meas_edit(self->obj); +} + + +static struct inst_ops meas_ops = { + .draw = gui_draw_meas, + .distance = gui_dist_meas, + .select = meas_op_select, + .begin_drag_move= begin_drag_move_meas, + .find_point = find_point_meas_move, + .draw_move = draw_move_meas, + .end_drag_move = end_drag_move_meas, + .do_move_to = do_move_to_meas, +}; + + +struct inst *find_meas_hint(const struct obj *obj) +{ + struct inst *inst; + + for (inst = curr_pkg->insts[ip_meas]; inst; inst = inst->next) + if (inst->obj == obj) + break; + return inst; +} + + +int inst_meas(struct obj *obj, struct coord from, struct coord to) +{ + struct inst *inst; + struct coord a1, b1; + + inst = find_meas_hint(obj); + assert(inst); + inst->base = from; + inst->u.meas.end = to; + inst->u.meas.valid = 1; + /* @@@ we still need to consider the text size as well */ + update_bbox(&inst->bbox, from); + update_bbox(&inst->bbox, to); + project_meas(inst, &a1, &b1); + update_bbox(&inst->bbox, a1); + update_bbox(&inst->bbox, b1); + propagate_bbox(inst); + return 1; +} + + +void inst_meas_hint(struct obj *obj, unit_type offset) +{ + static const struct coord zero = { 0, 0 }; + struct inst *inst; + + inst = find_meas_hint(obj); + if (inst) + return; + inst = add_inst(&meas_ops, ip_meas, zero); + inst->obj = obj; + inst->u.meas.offset = offset; + inst->u.meas.valid = 0; + inst->active = 1; /* measurements are always active */ +} + + +/* ----- direct editing of objects ----------------------------------------- */ + + +static void obj_edit(struct obj *obj) +{ + switch (obj->type) { + case ot_frame: + break; + case ot_line: + obj_line_edit(obj); + break; + case ot_rect: + obj_rect_edit(obj); + break; + case ot_arc: + obj_arc_edit(obj); + break; + case ot_pad: + obj_pad_edit(obj); + break; + case ot_meas: + obj_meas_edit(obj); + break; + default: + abort(); + } +} + + +/* ----- active instance --------------------------------------------------- */ + + +void inst_begin_active(int active) +{ + active_set = (active_set << 1) | active; +} + + +void inst_end_active(void) +{ + active_set >>= 1; +} + + +/* ----- frame ------------------------------------------------------------- */ + + +static void frame_op_select(struct inst *self) +{ + rect_status(self->bbox.min, self->bbox.max, -1, 0); + status_set_type_entry(NULL, "name ="); + status_set_name("Frame name", "%s", self->u.frame.ref->name); +} + + +static struct inst_ops frame_ops = { + .draw = gui_draw_frame, + .hover = gui_hover_frame, + .distance = gui_dist_frame, + .select = frame_op_select, + .draw_move = draw_move_frame, +}; + + +void inst_begin_frame(struct obj *obj, struct frame *frame, + struct coord base, int active, int is_active_frame) +{ + struct inst *inst; + + inst = add_inst(&frame_ops, ip_frame, base); + inst->obj = obj; + inst->u.frame.ref = frame; + inst->u.frame.active = is_active_frame; + inst->active = active; + find_inst(inst); + frame_instantiating = inst; +} + + +void inst_end_frame(const struct frame *frame) +{ + struct inst *inst = frame_instantiating; + + frame_instantiating = frame_instantiating->outer; + if (frame_instantiating) + propagate_bbox(inst); + if (inst->u.frame.active && frame == active_frame) + active_frame_bbox = inst->bbox; +} + + +/* ----- package ----------------------------------------------------------- */ + + +void inst_select_pkg(const char *name, int active) +{ + struct pkg **pkg; + enum inst_prio prio; + + name = name ? unique(name) : NULL; + for (pkg = &pkgs; *pkg; pkg = &(*pkg)->next) + if ((*pkg)->name == name) + break; + if (!*pkg) { + *pkg = zalloc_type(struct pkg); + (*pkg)->name = name; + FOR_INST_PRIOS_UP(prio) + (*pkg)->next_inst[prio] = &(*pkg)->insts[prio]; + (*pkg)->samples = + zalloc_size(sizeof(struct sample *)*n_samples); + (*pkg)->n_samples = n_samples; + } + curr_pkg = *pkg; + if (active && name) + reachable_pkg = curr_pkg; +} + + +/* ----- misc. ------------------------------------------------------------- */ + + +struct bbox inst_get_bbox(const struct pkg *pkg) +{ + if (pkg) + return pkg->bbox; + else + return pkgs->insts[ip_frame]->bbox; +} + + +static void cleanup_inst(enum inst_prio prio, const struct inst *inst) +{ + switch (prio) { + case ip_pad_copper: + case ip_pad_special: + free(inst->u.pad.name); + break; + default: + break; + } +} + + +static void free_pkgs(struct pkg *pkg) +{ + enum inst_prio prio; + struct pkg *next_pkg; + struct inst *inst, *next; + + while (pkg) { + next_pkg = pkg->next; + FOR_INST_PRIOS_UP(prio) + for (inst = pkg->insts[prio]; inst; inst = next) { + next = inst->next; + cleanup_inst(prio, inst); + free(inst); + } + reset_samples(pkg->samples, pkg->n_samples); + free(pkg->samples); + free(pkg); + pkg = next_pkg; + } +} + + +void inst_start(void) +{ + static struct bbox bbox_zero = { { 0, 0 }, { 0, 0 }}; + + active_frame_bbox = bbox_zero; + prev_pkgs = pkgs; + prev_reachable_pkg = reachable_pkg; + pkgs = NULL; + reachable_pkg = NULL; + inst_select_pkg(NULL, 0); + curr_pkg = pkgs; + frame_instantiating = NULL; +} + + +void inst_commit(void) +{ + struct pkg *pkg; + + if (active_pkg) { + for (pkg = pkgs; pkg && pkg->name != active_pkg->name; + pkg = pkg->next); + active_pkg = pkg; + } + if (!active_pkg) + active_pkg = pkgs->next; + free_pkgs(prev_pkgs); +} + + +void inst_revert(void) +{ + free_pkgs(pkgs); + pkgs = prev_pkgs; + reachable_pkg = prev_reachable_pkg; +} + + +void inst_draw(void) +{ + enum inst_prio prio; + struct inst *inst; + int i; + + FOR_INST_PRIOS_UP(prio) + FOR_ALL_INSTS(i, prio, inst) + if (show_this(inst)) + if (show(prio) && !inst->active && + inst->ops->draw) + inst->ops->draw(inst); + FOR_INST_PRIOS_UP(prio) + FOR_ALL_INSTS(i, prio, inst) + if (show(prio) && prio != ip_frame && inst->active && + inst != selected_inst && inst->ops->draw) + inst->ops->draw(inst); + if (show_stuff) + FOR_ALL_INSTS(i, ip_frame, inst) + if (inst->active && inst != selected_inst && + inst->ops->draw) + inst->ops->draw(inst); + if (selected_inst && selected_inst->ops->draw) + selected_inst->ops->draw(selected_inst); +} + + +void inst_highlight_vecs(int (*pick)(struct inst *inst, void *user), void *user) +{ + struct inst *inst; + int i; + + FOR_ALL_INSTS(i, ip_vec, inst) { + inst->u.vec.highlighted = pick(inst, user); + if (inst->u.vec.highlighted) + gui_highlight_vec(inst); + } +} + + +struct inst *inst_find_vec(struct coord pos, + int (*pick)(struct inst *inst, void *user), void *user) +{ + struct inst *inst, *found; + int best_dist = 0; /* keep gcc happy */ + int dist, i; + + found = NULL; + FOR_ALL_INSTS(i, ip_vec, inst) { + if (!inst->ops->distance) + continue; + dist = inst->ops->distance(inst, pos, draw_ctx.scale); + if (dist < 0 || (found && best_dist <= dist)) + continue; + if (!pick(inst, user)) + continue; + found = inst; + best_dist = dist; + } + return found; +} + + +struct inst *insts_ip_vec(void) +{ + return active_pkg->insts[ip_vec]; +} + + +struct pix_buf *inst_draw_move(struct inst *inst, struct coord pos, int i) +{ + return inst->ops->draw_move(inst, pos, i); +} + + +int inst_do_move_to(struct inst *inst, struct inst *to, int i) +{ + if (!inst->ops->do_move_to) + return 0; + inst->ops->do_move_to(inst, to, i); + return 1; +} + + +struct pix_buf *inst_hover(struct inst *inst) +{ + if (!inst->ops->hover) + return NULL; + return inst->ops->hover(inst); +} + + +void inst_begin_drag_move(struct inst *inst, int i) +{ + if (inst->ops->begin_drag_move) + inst->ops->begin_drag_move(inst, i); +} + + +void inst_delete(struct inst *inst) +{ + if (inst->ops == &vec_ops) + delete_vec(inst->vec); + else + delete_obj(inst->obj); +} @@ -0,0 +1,221 @@ +/* + * inst.h - Instance structures + * + * Written 2009, 2010, 2012 by Werner Almesberger + * Copyright 2009, 2010, 2012 by Werner Almesberger + * + * 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. + */ + + +#ifndef INST_H +#define INST_H + +#include <stdint.h> +#include <stdio.h> + +#include "coord.h" +#include "obj.h" +#include "meas.h" + + +enum mode { + mode_inactive, /* on inactive frame */ + mode_active, /* on active frame */ + mode_selected, /* item is selected */ + mode_hover, /* hovering over item's contact area */ + mode_n /* number of modes */ +}; + +struct bbox { + struct coord min; + struct coord max; +}; + + +enum inst_prio { + ip_frame, /* frames have their own selection */ + ip_pad_copper, /* pads also accept clicks inside; pads with copper */ + ip_pad_special, /* pads with only solder paste or mask, on top */ + ip_hole, /* holes in pads must be on top to be seen */ + ip_circ, /* circles don't overlap easily */ + ip_arc, /* arcs are like circles, just shorter */ + ip_rect, /* rectangles have plenty of sides */ + ip_meas, /* mesurements are like lines but set a bit apart */ + ip_line, /* lines are easly overlapped by other things */ + ip_vec, /* vectors only have the end point */ + ip_n, /* number of priorities */ +}; + + +struct inst; + + +struct inst_ops { + void (*debug)(struct inst *self); + void (*save)(FILE *file, struct inst *self); + void (*draw)(struct inst *self); + struct pix_buf *(*hover)(struct inst *self); + unit_type (*distance)(struct inst *self, struct coord pos, + unit_type scale); + void (*select)(struct inst *self); + void (*begin_drag_move)(struct inst *from, int i); + struct inst *(*find_point)(struct inst *self, struct coord pos); + struct pix_buf *(*draw_move)(struct inst *inst, + struct coord pos, int i); + void (*end_drag_move)(void); + /* arcs and measurements need this special override */ + void (*do_move_to)(struct inst *inst, struct inst *to, int i); +}; + +struct inst { + const struct inst_ops *ops; + enum inst_prio prio; /* currently only used for icon selection */ + struct coord base; + struct bbox bbox; + struct vec *vec; /* NULL if not vector */ + struct obj *obj; /* NULL if not object */ + struct inst *outer; /* frame containing this item */ + int active; + union { + struct { + int highlighted; /* for measurements */ + struct coord end; + } vec; + struct { + struct frame *ref; + int active; + } frame; + const char *name; + struct { + unit_type width; + struct coord end; + } rect; + struct { + char *name; + struct coord other; + layer_type layers; /* bit-set of layers */ + struct inst *hole; /* through-hole or NULL */ + } pad; + struct { + struct coord other; + layer_type layers; /* bit-set of layers (mech only) */ + struct inst *pad; /* through-hole pad of NULL */ + } hole; + struct { + unit_type r; + double a1, a2; + unit_type width; + } arc; + struct { + struct coord end; + double offset; + int valid; /* only set if references exist */ + } meas; + } u; + struct inst *next; +}; + + +struct pkg { + const char *name; /* NULL if global package */ + struct inst *insts[ip_n]; + struct inst **next_inst[ip_n]; + struct bbox bbox; /* bbox only of items in this package */ + struct sample **samples; + int n_samples; + struct pkg *next; +}; + + +extern struct inst *selected_inst; +extern struct pkg *pkgs; /* list of packages */ +extern struct pkg *active_pkg; /* package selected in GUI */ +extern struct pkg *curr_pkg; /* package currently being instantiated */ +extern struct pkg *reachable_pkg; /* package reachable with active vars */ +extern struct bbox active_frame_bbox; + +/* + * frame being instantiated - we need to export this one for meas.c, so that + * measurements can update the root frame's bounding box. + */ +extern struct inst *frame_instantiating; + +/* + * @@@ Note that we over-generalize a bit here: the only item that ever ends up + * in the global package is currently the root frame. However, we may later + * allow other items shared by all packages be there as well. + */ + +#define FOR_INST_PRIOS_UP(prio) \ + for (prio = 0; prio != ip_n; prio++) + +#define FOR_INST_PRIOS_DOWN(prio) \ + for (prio = ip_n-1; prio != (enum inst_prio) -1; prio--) + +#define FOR_PKG_INSTS(pkg, prio, inst) \ + for (inst = (pkg) ? (pkg)->insts[prio] : NULL; inst; inst = inst->next) + +#define FOR_ALL_INSTS(i, prio, inst) \ + for (i = 0; i != 2; i++) \ + FOR_PKG_INSTS(i ? active_pkg : pkgs, prio, inst) + + +int bright(const struct inst *inst); + +void inst_select_outside(void *item, void (*deselect)(void *item)); +int inst_select(struct coord pos); +void inst_deselect(void); + +void inst_select_vec(struct vec *vec); +void inst_select_obj(struct obj *obj); + +struct inst *inst_find_point(struct coord pos); +int inst_find_point_selected(struct coord pos, struct inst **res); +struct coord inst_get_point(const struct inst *inst); +int inst_anchors(struct inst *inst, struct vec ***anchors); +struct vec *inst_get_vec(const struct inst *inst); + +int inst_vec(struct vec *vec, struct coord base); +int inst_line(struct obj *obj, struct coord a, struct coord b, unit_type width); +int inst_rect(struct obj *obj, struct coord a, struct coord b, unit_type width); +int inst_pad(struct obj *obj, const char *name, struct coord a, struct coord b); +int inst_hole(struct obj *obj, struct coord a, struct coord b); +int inst_arc(struct obj *obj, struct coord center, struct coord start, + struct coord stop, unit_type width); +struct inst *find_meas_hint(const struct obj *obj); +int inst_meas(struct obj *obj, struct coord from, struct coord to); +void inst_meas_hint(struct obj *obj, unit_type offset); + +void inst_begin_active(int active); +void inst_end_active(void); + +void inst_begin_frame(struct obj *obj, struct frame *frame, + struct coord base, int active, int is_active_frame); +void inst_end_frame(const struct frame *frame); + +void inst_select_pkg(const char *name, int active); + +struct bbox inst_get_bbox(const struct pkg *pkg); + +void inst_start(void); +void inst_commit(void); +void inst_revert(void); + +void inst_draw(void); +void inst_highlight_vecs(int (*pick)(struct inst *inst, void *user), + void *user); +struct inst *inst_find_vec(struct coord pos, + int (*pick)(struct inst *inst, void *user), void *user); +struct inst *insts_ip_vec(void); + +struct pix_buf *inst_draw_move(struct inst *inst, struct coord pos, int i); +int inst_do_move_to(struct inst *inst, struct inst *to, int i); +struct pix_buf *inst_hover(struct inst *inst); +void inst_begin_drag_move(struct inst *inst, int i); +void inst_delete(struct inst *inst); + +#endif /* !INST_H */ @@ -0,0 +1,327 @@ +/* + * kicad.c - Dump objects in the KiCad board/module format + * + * Written 2009-2011 by Werner Almesberger + * Copyright 2009-2011 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <assert.h> + +#include "coord.h" +#include "inst.h" +#include "kicad.h" + + +static unit_type zeroize(unit_type n) +{ + return n == -1 || n == 1 ? 0 : n; +} + + +static void kicad_centric(struct coord a, struct coord b, + struct coord *center, struct coord *size) +{ + struct coord min, max; + + min.x = units_to_kicad(a.x); + min.y = units_to_kicad(a.y); + max.x = units_to_kicad(b.x); + max.y = units_to_kicad(b.y); + + sort_coord(&min, &max); + + size->x = max.x-min.x; + size->y = max.y-min.y; + center->x = (min.x+max.x)/2; + center->y = -(min.y+max.y)/2; +} + + +static void do_drill(FILE *file, const struct inst *pad, struct coord *ref) +{ + const struct inst *hole = pad->u.pad.hole; + struct coord center, size; + + if (!hole) + return; + + kicad_centric(hole->base, hole->u.hole.other, ¢er, &size); + + /* Allow for rounding errors */ + + fprintf(file, "Dr %d %d %d", size.x, + -zeroize(center.x-ref->x), -zeroize(center.y-ref->y)); + if (size.x < size.y-1 || size.x > size.y+1) + fprintf(file, " O %d %d", size.x, size.y); + fprintf(file, "\n"); + *ref = center; +} + + +static void kicad_pad(FILE *file, const struct inst *inst) +{ + struct coord center, size; + + kicad_centric(inst->base, inst->u.pad.other, ¢er, &size); + + fprintf(file, "$PAD\n"); + + /* + * name, shape (rectangle), Xsize, Ysize, Xdelta, Ydelta, Orientation + */ + fprintf(file, "Sh \"%s\" %c %d %d 0 0 0\n", + inst->u.pad.name, inst->obj->u.pad.rounded ? 'O' : 'R', + size.x, size.y); + + /* + * Drill hole + */ + do_drill(file, inst, ¢er); + + /* + * Attributes: pad type, N, layer mask + */ + fprintf(file, "At %s N %8.8X\n", + inst->u.pad.hole ? "STD" : "SMD", (unsigned) inst->u.pad.layers); + + /* + * Position: Xpos, Ypos + */ + fprintf(file, "Po %d %d\n", center.x, center.y); + + fprintf(file, "$EndPAD\n"); +} + + +static void kicad_hole(FILE *file, const struct inst *inst) +{ + struct coord center, size; + + if (inst->u.hole.pad) + return; + kicad_centric(inst->base, inst->u.hole.other, ¢er, &size); + fprintf(file, "$PAD\n"); + if (size.x < size.y-1 || size.x > size.y+1) { + fprintf(file, "Sh \"HOLE\" O %d %d 0 0 0\n", size.x, size.y); + fprintf(file, "Dr %d 0 0 O %d %d\n", size.x, size.x, size.y); + } else { + fprintf(file, "Sh \"HOLE\" C %d %d 0 0 0\n", size.x, size.x); + fprintf(file, "Dr %d 0 0\n", size.x); + } + fprintf(file, "At HOLE N %8.8X\n", (unsigned) inst->u.hole.layers); + fprintf(file, "Po %d %d\n", center.x, center.y); + fprintf(file, "$EndPAD\n"); +} + + +static void kicad_line(FILE *file, const struct inst *inst) +{ + /* + * Xstart, Ystart, Xend, Yend, Width, Layer + */ + fprintf(file, "DS %d %d %d %d %d %d\n", + units_to_kicad(inst->base.x), + -units_to_kicad(inst->base.y), + units_to_kicad(inst->u.rect.end.x), + -units_to_kicad(inst->u.rect.end.y), + units_to_kicad(inst->u.rect.width), + layer_silk_top); +} + + +static void kicad_rect(FILE *file, const struct inst *inst) +{ + unit_type xa, ya, xb, yb; + unit_type width; + + xa = units_to_kicad(inst->base.x); + ya = units_to_kicad(inst->base.y); + xb = units_to_kicad(inst->u.rect.end.x); + yb = units_to_kicad(inst->u.rect.end.y); + width = units_to_kicad(inst->u.rect.width); + + fprintf(file, "DS %d %d %d %d %d %d\n", + xa, -ya, xa, -yb, width, layer_silk_top); + fprintf(file, "DS %d %d %d %d %d %d\n", + xa, -yb, xb, -yb, width, layer_silk_top); + fprintf(file, "DS %d %d %d %d %d %d\n", + xb, -yb, xb, -ya, width, layer_silk_top); + fprintf(file, "DS %d %d %d %d %d %d\n", + xb, -ya, xa, -ya, width, layer_silk_top); +} + + +static void kicad_circ(FILE *file, const struct inst *inst) +{ + /* + * Xcenter, Ycenter, Xpoint, Ypoint, Width, Layer + */ + fprintf(file, "DC %d %d %d %d %d %d\n", + units_to_kicad(inst->base.x), + -units_to_kicad(inst->base.y), + units_to_kicad(inst->base.x), + -units_to_kicad(inst->base.y+inst->u.arc.r), + units_to_kicad(inst->u.arc.width), + layer_silk_top); +} + + +static void kicad_arc(FILE *file, const struct inst *inst) +{ + struct coord p; + double a; + + /* + * The documentation says: + * Xstart, Ystart, Xend, Yend, Angle, Width, Layer + * + * But it's really: + * Xcenter, Ycenter, Xend, Yend, ... + */ + p = rotate_r(inst->base, inst->u.arc.r, inst->u.arc.a2); + a = inst->u.arc.a2-inst->u.arc.a1; + while (a <= 0) + a += 360; + while (a > 360) + a -= 360; + fprintf(file, "DA %d %d %d %d %d %d %d\n", + units_to_kicad(inst->base.x), + -units_to_kicad(inst->base.y), + units_to_kicad(p.x), + -units_to_kicad(p.y), + (int) (a*10.0), + units_to_kicad(inst->u.arc.width), + layer_silk_top); +} + + +static void kicad_inst(FILE *file, enum inst_prio prio, const struct inst *inst) +{ + switch (prio) { + case ip_pad_copper: + case ip_pad_special: + kicad_pad(file, inst); + break; + case ip_hole: + kicad_hole(file, inst); + break; + case ip_line: + kicad_line(file, inst); + break; + case ip_rect: + kicad_rect(file, inst); + break; + case ip_circ: + kicad_circ(file, inst); + break; + case ip_arc: + kicad_arc(file, inst); + break; + default: + /* + * Don't try to export vectors, frame references, or + * measurements. + */ + break; + } +} + + +static void kicad_module(FILE *file, const struct pkg *pkg, time_t now) +{ + enum inst_prio prio; + const struct inst *inst; + + /* + * Module library name + */ + fprintf(file, "$MODULE %s\n", pkg->name); + + /* + * Xpos = 0, Ypos = 0, 15 layers, last modification, timestamp, + * moveable, not autoplaced. + */ + fprintf(file, "Po 0 0 0 15 %8.8lX 00000000 ~~\n", (long) now); + + /* + * Module library name again + */ + fprintf(file, "Li %s\n", pkg->name); + +#if 0 /* optional */ + /* + * Description + */ + fprintf(file, "Cd %s\n", pkg->name); +#endif + + /* + * + */ + fprintf(file, "Sc %8.8lX\n", (long) now); + + /* + * Attributes: SMD = listed in the automatic insertion list + */ + fprintf(file, "At SMD\n"); + + /* + * Rotation cost: 0 for 90 deg, 0 for 180 deg, 0 = disable rotation + */ + fprintf(file, "Op 0 0 0\n"); + + /* + * Text fields: Tn = field number, Xpos, Ypos, Xsize ("emspace"), + * Ysize ("emspace"), rotation, pen width, N (none), V = visible, + * comment layer. All dimensions are 1/10 mil. + */ + + fprintf(file, "T0 0 -150 200 200 0 40 N V %d \"%s\"\n", + layer_comment, pkg->name); + fprintf(file, "T1 0 150 200 200 0 40 N I %d \"Val*\"\n", + layer_comment); + + FOR_INST_PRIOS_UP(prio) { + for (inst = pkgs->insts[prio]; inst; inst = inst->next) + kicad_inst(file, prio, inst); + for (inst = pkg->insts[prio]; inst; inst = inst->next) + kicad_inst(file, prio, inst); + } + + fprintf(file, "$EndMODULE %s\n", pkg->name); +} + + +int kicad(FILE *file, const char *one) +{ + const struct pkg *pkg; + time_t now = time(NULL); + + assert(!one); + + fprintf(file, "PCBNEW-LibModule-V1 %s", ctime(&now)); + + fprintf(file, "$INDEX\n"); + for (pkg = pkgs; pkg; pkg = pkg->next) + if (pkg->name) + fprintf(file, "%s\n", pkg->name); + fprintf(file, "$EndINDEX\n"); + + for (pkg = pkgs; pkg; pkg = pkg->next) + if (pkg->name) + kicad_module(file, pkg, now); + + fprintf(file, "$EndLIBRARY\n"); + + fflush(file); + return !ferror(file); +} @@ -0,0 +1,22 @@ +/* + * kicad.h - Dump objects in the KiCad board/module format + * + * Written 2009, 2011 by Werner Almesberger + * Copyright 2009, 2011 by Werner Almesberger + * + * 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. + */ + + +#ifndef KICAD_H +#define KICAD_H + +#include <stdio.h> + + +int kicad(FILE *file, const char *one); + +#endif /* !KICAD_H */ @@ -0,0 +1,196 @@ +/* + * layer.c - PCB layers on a pad + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + +/* + * We don't reject solder paste pads that don't cover anything yet. + * That way, things can be constructed step by step without getting blue + * screens all the time. + */ + + +#include <stdlib.h> + +#include "error.h" +#include "overlap.h" +#include "inst.h" +#include "obj.h" +#include "layer.h" + + +/* + * Shorthands for the layers we use in a general sense. + */ + +#define LAYER_COPPER_TOP (1 << layer_top) +#define LAYER_PASTE_TOP (1 << layer_paste_top) +#define LAYER_MASK_TOP (1 << layer_mask_top) +#define LAYER_COPPER_BOTTOM (1 << layer_bottom) +#define LAYER_PASTE_BOTTOM (1 << layer_paste_bottom) +#define LAYER_MASK_BOTTOM (1 << layer_mask_bottom) + + +/* ----- Conversion between pad types and layer sets ----------------------- */ + + +layer_type pad_type_to_layers(enum pad_type type) +{ + layer_type layers = 0; + + switch (type) { + case pt_normal: + layers = LAYER_PASTE_TOP; + /* fall through */ + case pt_bare: + layers |= LAYER_COPPER_TOP | LAYER_MASK_TOP; + break; + case pt_trace: + layers = LAYER_COPPER_TOP; + break; + case pt_paste: + layers = LAYER_PASTE_TOP; + break; + case pt_mask: + layers = LAYER_MASK_TOP; + break; + default: + abort(); + } + return layers; +} + + +enum pad_type layers_to_pad_type(layer_type layers) +{ + if (layers & LAYER_COPPER_TOP) { + if (layers & LAYER_PASTE_TOP) + return pt_normal; + if (layers & LAYER_MASK_TOP) + return pt_bare; + return pt_trace; + } else { + if (layers & LAYER_PASTE_TOP) + return pt_paste; + if (layers & LAYER_MASK_TOP) + return pt_mask; + abort(); + } +} + + +const char *pad_type_name(enum pad_type type) +{ + switch (type) { + case pt_normal: + return "normal"; + case pt_bare: + return "bare"; + case pt_trace: + return "trace"; + case pt_paste: + return "paste"; + case pt_mask: + return "mask"; + default: + abort(); + } +} + + +/* ----- layers in mechanical holes ---------------------------------------- */ + + +layer_type mech_hole_layers(void) +{ + return LAYER_MASK_TOP | LAYER_MASK_BOTTOM; +} + + +/* ----- Refine layers after instantiation --------------------------------- */ + + +static int refine_overlapping(struct inst *copper, struct inst *other) +{ + if (other->u.pad.layers & LAYER_PASTE_TOP) { + copper->u.pad.layers &= ~LAYER_PASTE_TOP; + if (!inside(other, copper)) { + fail("solder paste without copper underneath " + "(\"%s\" line %d, \"%s\" line %d)", + copper->u.pad.name, copper->obj->lineno, + other->u.pad.name, other->obj->lineno); + instantiation_error = other->obj; + return 0; + } + } + if (other->u.pad.layers & LAYER_MASK_TOP) + copper->u.pad.layers &= ~LAYER_MASK_TOP; + return 1; +} + + +static int refine_copper(const struct pkg *pkg_copper, struct inst *copper, + enum allow_overlap allow) +{ + const struct pkg *pkg; + struct inst *other; + + for (pkg = pkgs; pkg; pkg = pkg->next) { + /* + * Pads in distinct packages can happily coexist. + */ + if (pkg != pkgs && pkg_copper != pkgs && pkg_copper != pkg) + continue; + for (other = pkg->insts[ip_pad_copper]; other; + other = other->next) + if (copper != other && overlap(copper, other, allow)) { + fail("overlapping copper pads " + "(\"%s\" line %d, \"%s\" line %d)", + copper->u.pad.name, copper->obj->lineno, + other->u.pad.name, other->obj->lineno); + instantiation_error = copper->obj; + return 0; + } + for (other = pkg->insts[ip_pad_special]; other; + other = other->next) + if (overlap(copper, other, ao_none)) + if (!refine_overlapping(copper, other)) + return 0; + } + return 1; +} + + +static void mirror_layers(layer_type *layers) +{ + if (*layers & LAYER_COPPER_TOP) + *layers |= LAYER_COPPER_BOTTOM; + if (*layers & LAYER_PASTE_TOP) + *layers |= LAYER_PASTE_BOTTOM; + if (*layers & LAYER_MASK_TOP) + *layers |= LAYER_MASK_BOTTOM; +} + + +int refine_layers(enum allow_overlap allow) +{ + const struct pkg *pkg; + struct inst *copper; + + for (pkg = pkgs; pkg; pkg = pkg->next) + for (copper = pkg->insts[ip_pad_copper]; copper; + copper = copper->next) { + if (!refine_copper(pkg, copper, allow)) + return 0; + if (copper->u.pad.hole) + mirror_layers(&copper->u.pad.layers); + } + return 1; +} @@ -0,0 +1,84 @@ +/* + * layer.h - PCB layers on a pad + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + +#ifndef LAYER_H +#define LAYER_H + +#include <stdint.h> + +#include "overlap.h" + + +typedef uint32_t layer_type; + + +enum kicad_layer { + layer_bottom, /* "copper" */ + layer_l15, + layer_l14, + layer_l13, + layer_l12, + layer_l11, + layer_l10, + layer_l9, + layer_l8, + layer_l7, + layer_l6, + layer_l5, + layer_l4, + layer_l3, + layer_l2, + layer_top, /* "component" */ + layer_glue_bottom, /* adhesive, copper side */ + layer_glue_top, /* adhesive, component side */ + layer_paste_bottom, /* solder paste */ + layer_paste_top, + layer_silk_bottom, /* silk screen */ + layer_silk_top, + layer_mask_bottom, /* solder mask */ + layer_mask_top, + layer_draw, /* general drawing */ + layer_comment, + layer_eco1, + layer_eco2, + layer_edge, /* edge */ +}; + + +enum pad_type { + pt_normal, /* copper and solder mask */ + pt_bare, /* only copper (and finish) */ + pt_trace, /* only copper, without solder mask opening */ + pt_paste, /* only solder paste */ + pt_mask, /* only solder mask */ + pt_n +}; + + +/* + * pad_type_to_layers returns the initial set of layers. This set can then be + * modified by overlaying other pads. For display purposes, we translate back + * to the effective pad type with layers_to_pad_type. + * + * What this basically means is that pt_normal becomes pt_bare if its solder + * paste mask has been removed. + */ + +layer_type pad_type_to_layers(enum pad_type type); +enum pad_type layers_to_pad_type(layer_type layers); +const char *pad_type_name(enum pad_type type); + +layer_type mech_hole_layers(void); + +int refine_layers(enum allow_overlap allow); + +#endif /* !LAYER_H */ diff --git a/leak.supp b/leak.supp new file mode 100644 index 0000000..cc6770d --- /dev/null +++ b/leak.supp @@ -0,0 +1,31 @@ +{ + gtk_internal + Memcheck:Leak + ... + fun:gtk_init +} + +{ + lex + Memcheck:Leak + fun:malloc + ... + fun:yyensure_buffer_stack + ... +} + +{ + pango_leaks_like_crazy + Memcheck:Leak + ... + fun:pango_* + ... +} + +{ + gdk_pixbuf_new_from_xpm_data_leaks_through_dlopen + Memcheck:Leak + ... + fun:dlopen + ... +} diff --git a/leakcheck b/leakcheck new file mode 100755 index 0000000..1d4c060 --- /dev/null +++ b/leakcheck @@ -0,0 +1,4 @@ +#!/bin/sh +valgrind --leak-check=full --show-reachable=yes --num-callers=50 \ + --suppressions=leak.supp \ + ./fped "$@" diff --git a/manual/concept-inst.fig b/manual/concept-inst.fig new file mode 100644 index 0000000..3945885 --- /dev/null +++ b/manual/concept-inst.fig @@ -0,0 +1,35 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +0 32 #c0c000 +1 1 0 5 3 7 40 -1 -1 0.000 1 0.0000 3600 4500 450 225 3600 4500 4050 4500 +1 1 0 5 3 7 40 -1 -1 0.000 1 0.0000 6300 4501 450 225 6300 4501 6750 4501 +1 1 0 5 3 7 40 -1 -1 0.000 1 0.0000 4950 4500 450 225 4950 4500 5400 4500 +1 1 0 1 12 12 50 -1 20 0.000 1 0.0000 3150 6075 90 45 3150 6075 3240 6075 +1 1 0 3 12 7 50 -1 -1 0.000 1 0.0000 3150 6075 180 90 3150 6075 3330 6075 +1 1 0 5 16 7 40 -1 -1 0.000 1 0.0000 4500 6075 450 225 4500 6075 4950 6075 +2 1 0 2 0 7 45 -1 -1 0.000 0 0 -1 0 0 3 + 2925 3600 1800 5175 6975 5175 +2 1 1 2 0 7 35 -1 -1 6.000 0 0 -1 0 0 2 + 3150 6075 3600 4500 +2 1 0 2 0 7 35 -1 -1 6.000 0 0 -1 0 0 2 + 3825 6165 4275 6750 +2 1 0 3 32 7 45 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 3.00 135.00 120.00 + 4500 6075 4635 5850 +2 1 0 3 32 7 45 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 3.00 135.00 135.00 + 3150 6075 4500 6075 +4 1 0 35 -1 18 12 0.0000 4 165 390 3600 4140 n=0\001 +4 1 0 35 -1 18 12 0.0000 4 165 390 4950 4140 n=1\001 +4 1 0 35 -1 18 12 0.0000 4 165 390 6300 4140 n=2\001 +4 1 0 35 -1 18 12 0.0000 4 210 660 3150 6435 Origin\001 +4 0 0 35 -1 18 12 0.0000 4 180 1305 4050 6975 n*4mm, 0mm\001 +4 0 0 35 -1 18 12 0.0000 4 210 1650 1800 7200 Objects (model)\001 +4 0 0 35 -1 18 12 0.0000 4 165 1005 4050 3600 Instances\001 diff --git a/manual/intro-1.png b/manual/intro-1.png Binary files differnew file mode 100644 index 0000000..d3edfca --- /dev/null +++ b/manual/intro-1.png diff --git a/manual/intro-2.png b/manual/intro-2.png Binary files differnew file mode 100644 index 0000000..842e887 --- /dev/null +++ b/manual/intro-2.png diff --git a/manual/intro-3.png b/manual/intro-3.png Binary files differnew file mode 100644 index 0000000..e466c17 --- /dev/null +++ b/manual/intro-3.png diff --git a/manual/intro-4.png b/manual/intro-4.png Binary files differnew file mode 100644 index 0000000..6bf0fd1 --- /dev/null +++ b/manual/intro-4.png diff --git a/manual/intro-5.png b/manual/intro-5.png Binary files differnew file mode 100644 index 0000000..01bcc97 --- /dev/null +++ b/manual/intro-5.png diff --git a/manual/intro-6.png b/manual/intro-6.png Binary files differnew file mode 100644 index 0000000..20c8e31 --- /dev/null +++ b/manual/intro-6.png @@ -0,0 +1,326 @@ +/* + * meas.c - Measurements + * + * Written 2009, 2010, 2012 by Werner Almesberger + * Copyright 2009, 2010, 2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> + +#include "util.h" +#include "coord.h" +#include "expr.h" +#include "obj.h" +#include "inst.h" +#include "meas.h" + + +int n_samples; + + +struct num eval_unit(const struct expr *expr, const struct frame *frame); + + +void reset_samples(struct sample **samples, int n) +{ + struct sample *next; + int i; + + for (i = 0; i != n; i++) + while (samples[i]) { + next = samples[i]->next; + bitset_free(samples[i]->frame_set); + free(samples[i]); + samples[i] = next; + } +} + + +void meas_start(void) +{ + const struct frame *frame; + struct vec *vec; + + n_samples = 0; + for (frame = frames; frame; frame = frame->next) + for (vec = frame->vecs; vec; vec = vec->next) + vec->n = n_samples++; +} + + +void meas_post(const struct vec *vec, struct coord pos, + const struct bitset *frame_set) +{ + struct sample **walk, *new; + + for (walk = &curr_pkg->samples[vec->n]; *walk; walk = &(*walk)->next) { + if (pos.y < (*walk)->pos.y) + break; + if (pos.y > (*walk)->pos.y) + continue; + if (pos.x < (*walk)->pos.x) + break; + if (pos.x != (*walk)->pos.x) + continue; + if (bitset_ge((*walk)->frame_set, frame_set)) + return; + if (bitset_ge(frame_set, (*walk)->frame_set)) { + bitset_or((*walk)->frame_set, frame_set); + return; + } + } + new = alloc_type(struct sample); + new->pos = pos; + new->frame_set = bitset_clone(frame_set); + new->next = *walk; + *walk = new; +} + + +/* ----- lt operators ------------------------------------------------------ */ + + +int lt_x(struct coord a, struct coord b) +{ + return a.x < b.x; +} + + +int lt_y(struct coord a, struct coord b) +{ + return a.y < b.y; +} + + +int lt_xy(struct coord a, struct coord b) +{ + return a.y < b.y || (a.y == b.y && a.x < b.x); +} + + +/* ----- measurement type map ---------------------------------------------- */ + + +static lt_op_type lt_op[mt_n] = { + lt_xy, + lt_x, + lt_y, + lt_xy, + lt_x, + lt_y +}; + + +static int is_next[mt_n] = { + 1, 1, 1, + 0, 0, 0 +}; + + +/* ----- search functions -------------------------------------------------- */ + + +static int closer(int da, int db) +{ + int abs_a, abs_b; + + abs_a = da < 0 ? -da : da; + abs_b = db < 0 ? -db : db; + if (abs_a < abs_b) + return 1; + if (abs_a > abs_b) + return 0; + /* + * Really *all* other things being equal, pick the one that protrudes + * in the positive direction. + */ + return da > db; +} + + +static int better_next(lt_op_type lt, + struct coord a0, struct coord b0, struct coord b) +{ + /* if we don't have any suitable point A0 < B0 yet, use this one */ + if (!lt(a0, b0)) + return 1; + + /* B must be strictly greater than A0 */ + if (!lt(a0, b)) + return 0; + + /* if we can get closer to A0, do so */ + if (lt(b, b0)) + return 1; + + /* reject B > B0 */ + if (lt(b0, b)) + return 0; + + /* + * B == B0 along the coordinate we measure. Now give the other + * coordinate a chance. This gives us a stable sort order and it + * makes meas/measx/measy usually select the same point. + */ + if (lt == lt_xy) + return 0; + if (lt == lt_x) + return closer(b.y-a0.y, b0.y-a0.y); + if (lt == lt_y) + return closer(b.x-a0.x, b0.x-a0.x); + abort(); +} + + +/* + * In order to obtain a stable order, we sort points equal on the measured + * coordinate also by xy: + * + * if (*a < a0) use *a + * else if (*a == a0 && *a <xy a0) use *a + */ + +const struct sample *meas_find_min(lt_op_type lt, const struct sample *s, + const struct bitset *qual) +{ + const struct sample *min = NULL; + + while (s) { + if (!qual || bitset_ge(s->frame_set, qual)) + if (!min || lt(s->pos, min->pos) || + (!lt(min->pos, s->pos) && lt_xy(s->pos, min->pos))) + min = s; + s = s->next; + } + return min; +} + + +const struct sample *meas_find_next(lt_op_type lt, const struct sample *s, + struct coord ref, const struct bitset *qual) +{ + const struct sample *next = NULL; + + while (s) { + if (!qual || bitset_ge(s->frame_set, qual)) + if (!next || better_next(lt, ref, next->pos, s->pos)) + next = s; + s = s->next; + } + return next; +} + + +const struct sample *meas_find_max(lt_op_type lt, const struct sample *s, + const struct bitset *qual) +{ + const struct sample *max = NULL; + + while (s) { + if (!qual || bitset_ge(s->frame_set, qual)) + if (!max || lt(max->pos, s->pos) || + (!lt(s->pos, max->pos) && lt_xy(max->pos, s->pos))) + max = s; + s = s->next; + } + return max; +} + + +/* ----- instantiation ----------------------------------------------------- */ + + +static struct bitset *make_frame_set(struct frame_qual *qual, int n_frames) +{ + struct bitset *set; + + set = bitset_new(n_frames); + while (qual) { + bitset_set(set, qual->frame->n); + qual = qual->next; + } + return set; +} + + +static int instantiate_meas_pkg(int n_frames) +{ + struct obj *obj; + const struct meas *meas; + struct bitset *set; + const struct sample *a0, *b0; + lt_op_type lt; + + for (obj = frames->objs; obj; obj = obj->next) { + if (obj->type != ot_meas) + continue; + meas = &obj->u.meas; + + /* optimization. not really needed anymore. */ + if (!curr_pkg->samples[obj->base->n] || + !curr_pkg->samples[meas->high->n]) + continue; + + lt = lt_op[meas->type]; + + set = make_frame_set(meas->low_qual, n_frames); + a0 = meas_find_min(lt, curr_pkg->samples[obj->base->n], set); + bitset_free(set); + if (!a0) + continue; + + set = make_frame_set(meas->high_qual, n_frames); + if (is_next[meas->type]) + b0 = meas_find_next(lt, + curr_pkg->samples[meas->high->n], a0->pos, set); + else + b0 = meas_find_max(lt, + curr_pkg->samples[meas->high->n], set); + bitset_free(set); + if (!b0) + continue; + + inst_meas(obj, + meas->inverted ? b0->pos : a0->pos, + meas->inverted ? a0->pos : b0->pos); + } + return 1; +} + + +static void purge_meas(struct pkg *pkg) +{ + struct inst **anchor, *inst; + + anchor = pkg->insts+ip_meas; + while (*anchor) + if ((*anchor)->u.meas.valid) { + anchor = &(*anchor)->next; + } else { + inst = *anchor; + *anchor = inst->next; + free(inst); + } +} + + +int instantiate_meas(int n_frames) +{ + struct pkg *pkg; + + frame_instantiating = pkgs->insts[ip_frame]; + for (pkg = pkgs; pkg; pkg = pkg->next) + if (pkg->name) { + inst_select_pkg(pkg->name, 0); + if (!instantiate_meas_pkg(n_frames)) + return 0; + purge_meas(pkg); + } + return 1; +} @@ -0,0 +1,82 @@ +/* + * meas.h - Measurements + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#ifndef MEAS_H +#define MEAS_H + + +#include "coord.h" +#include "expr.h" +#include "bitset.h" + + +typedef int (*lt_op_type)(struct coord a, struct coord b); + +struct vec; +struct obj; + +struct frame_qual { + const struct frame *frame; + struct frame_qual *next; +}; + +struct meas { + enum meas_type { + mt_xy_next, + mt_x_next, + mt_y_next, + mt_xy_max, + mt_x_max, + mt_y_max, + mt_n + } type; + char *label; /* or NULL */ + int inverted; + /* low is obj->base */ + struct vec *high; + struct expr *offset; + + /* frame qualifiers */ + struct frame_qual *low_qual; + struct frame_qual *high_qual; +}; + +struct sample { + struct coord pos; + struct bitset *frame_set; + struct sample *next; +}; + + +extern int n_samples; + + +int lt_x(struct coord a, struct coord b); +int lt_y(struct coord a, struct coord b); +int lt_xy(struct coord a, struct coord b); + +const struct sample *meas_find_min(lt_op_type lt, const struct sample *s, + const struct bitset *qual); +const struct sample *meas_find_next(lt_op_type lt, const struct sample *s, + struct coord ref, const struct bitset *qual); +const struct sample *meas_find_max(lt_op_type lt, const struct sample *s, + const struct bitset *qual); + + +void reset_samples(struct sample **samples, int n); +void meas_start(void); +void meas_post(const struct vec *vec, struct coord pos, + const struct bitset *frame_set); +int instantiate_meas(int n_frames); + +#endif /* !MEAS_H */ @@ -0,0 +1,604 @@ +/* + * obj.c - Object definition model + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "util.h" +#include "error.h" +#include "expr.h" +#include "bitset.h" +#include "meas.h" +#include "inst.h" +#include "hole.h" +#include "overlap.h" +#include "layer.h" +#include "delete.h" +#include "fpd.h" +#include "obj.h" + + +#define DEFAULT_SILK_WIDTH make_mil(15) /* @@@ */ +#define DEFAULT_OFFSET make_mil(0) /* @@@ */ + +#define MAX_ITERATIONS 1000 /* abort "loop"s at this limit */ + + +char *pkg_name = NULL; +struct frame *frames = NULL; +struct frame *active_frame = NULL; +void *instantiation_error = NULL; +enum allow_overlap allow_overlap = ao_none; + + +static struct bitset *frame_set; /* frames visited in "call chain" */ + + +/* ----- Searching --------------------------------------------------------- */ + + +/* + * @@@ Known bug: we should compare all parameters of an instance, not just the + * object's base or the vectors end. + */ + +static int found = 0; +static int search_suspended = 0; +static const struct vec *find_vec = NULL; +static const struct obj *find_obj = NULL; +static struct coord find_pos; + + +static void suspend_search(void) +{ + search_suspended++; +} + +static void resume_search(void) +{ + assert(search_suspended > 0); + search_suspended--; +} + + +static struct coord get_pos(const struct inst *inst) +{ + return inst->obj ? inst->base : inst->u.vec.end; +} + + +void find_inst(const struct inst *inst) +{ + struct coord pos; + + if (search_suspended) + return; + if (find_vec != inst->vec) + return; + if (find_obj != inst->obj) + return; + pos = get_pos(inst); + if (pos.x != find_pos.x || pos.y != find_pos.y) + return; + found++; +} + + +void search_inst(const struct inst *inst) +{ + find_vec = inst->vec; + find_obj = inst->obj; + find_pos = get_pos(inst); +} + + +/* ----- Get the list of anchors of an object ------------------------------ */ + + +int obj_anchors(struct obj *obj, struct vec ***anchors) +{ + anchors[0] = &obj->base; + switch (obj->type) { + case ot_frame: + return 1; + case ot_rect: + case ot_line: + anchors[1] = &obj->u.rect.other; + return 2; + case ot_pad: + anchors[1] = &obj->u.pad.other; + return 2; + case ot_hole: + anchors[1] = &obj->u.hole.other; + return 2; + case ot_meas: + anchors[1] = &obj->u.meas.high; + return 2; + case ot_arc: + /* + * Put end point first so that this is what we grab if dragging + * a circle (thereby turning it into an arc). + */ + anchors[1] = &obj->u.arc.end; + anchors[2] = &obj->u.arc.start; + return 3; + default: + abort(); + } +} + + +/* ----- Instantiation ----------------------------------------------------- */ + + +static int generate_frame(struct frame *frame, struct coord base, + const struct frame *parent, struct obj *frame_ref, int active); + + +struct num eval_unit(const struct expr *expr, const struct frame *frame); +/*static*/ struct num eval_unit(const struct expr *expr, const struct frame *frame) +{ + struct num d; + + d = eval_num(expr, frame); + if (!is_undef(d) && to_unit(&d)) + return d; + fail_expr(expr); + return undef; +} + + +static struct num eval_unit_default(const struct expr *expr, + const struct frame *frame, struct num def) +{ + if (expr) + return eval_unit(expr, frame); + to_unit(&def); + return def; +} + + +static int recurse_vec(const char *name, const struct frame *frame, + struct coord *res) +{ + const struct vec *v; + + if (!frame) + return 0; + for (v = frame->vecs; v; v = v->next) + if (v->name == name) { + *res = v->pos; + return 1; + } + return recurse_vec(name, frame->curr_parent, res); +} + + +static int resolve_vec(const struct vec *vec, struct coord base_pos, + const struct frame *frame, struct coord *res) +{ + const char *name = (const char *) vec; + + if (!vec) { + *res = base_pos; + return 1; + } + if (!*name) { + *res = vec->pos; + return 1; + } + if (recurse_vec(name, frame->curr_parent, res)) + return 1; + fail("unknown vector \"%s\"", name); + return 0; +} + + +static int generate_vecs(struct frame *frame, struct coord base_pos) +{ + struct coord vec_base; + struct vec *vec; + struct num x, y; + + for (vec = frame->vecs; vec; vec = vec->next) { + x = eval_unit(vec->x, frame); + if (is_undef(x)) + goto error; + y = eval_unit(vec->y, frame); + if (is_undef(y)) + goto error; + if (!resolve_vec(vec->base, base_pos, frame, &vec_base)) + goto error; + vec->pos = vec_base; + vec->pos.x += x.n; + vec->pos.y += y.n; + if (!inst_vec(vec, vec_base)) + goto error; + meas_post(vec, vec->pos, frame_set); + } + return 1; + +error: + instantiation_error = vec; + return 0; +} + + +static int generate_objs(struct frame *frame, struct coord base_pos, + int active) +{ + struct obj *obj; + char *name; + int ok; + struct num width, offset; + struct coord base, other, start, end; + + for (obj = frame->objs; obj; obj = obj->next) { + if (obj->type != ot_meas) + if (!resolve_vec(obj->base, base_pos, frame, &base)) + goto error; + switch (obj->type) { + case ot_frame: + if (!generate_frame(obj->u.frame.ref, base, frame, obj, + active && obj->u.frame.ref->active_ref == obj)) + return 0; + break; + case ot_line: + if (!resolve_vec(obj->u.line.other, base_pos, frame, + &other)) + goto error; + width = eval_unit_default(obj->u.line.width, frame, + DEFAULT_SILK_WIDTH); + if (is_undef(width)) + goto error; + if (!inst_line(obj, base, other, width.n)) + goto error; + break; + case ot_rect: + if (!resolve_vec(obj->u.rect.other, base_pos, frame, + &other)) + goto error; + width = eval_unit_default(obj->u.rect.width, frame, + DEFAULT_SILK_WIDTH); + if (is_undef(width)) + goto error; + if (!inst_rect(obj, base, other, width.n)) + goto error; + break; + case ot_pad: + if (!resolve_vec(obj->u.pad.other, base_pos, frame, + &other)) + goto error; + name = expand(obj->u.pad.name, frame); + if (!name) + goto error; + ok = inst_pad(obj, name, base, other); + free(name); + if (!ok) + goto error; + break; + case ot_hole: + if (!resolve_vec(obj->u.hole.other, base_pos, frame, + &other)) + goto error; + if (!inst_hole(obj, base, other)) + goto error; + break; + case ot_arc: + if (!resolve_vec(obj->u.arc.start, base_pos, frame, + &start)) + goto error; + if (!resolve_vec(obj->u.arc.end, base_pos, frame, + &end)) + goto error; + width = eval_unit_default(obj->u.arc.width, frame, + DEFAULT_SILK_WIDTH); + if (is_undef(width)) + goto error; + if (!inst_arc(obj, base, start, end, width.n)) + goto error; + break; + case ot_meas: + assert(frame == frames); + offset = eval_unit_default(obj->u.meas.offset, frame, + DEFAULT_OFFSET); + if (is_undef(offset)) + goto error; + inst_meas_hint(obj, offset.n); + break; + case ot_iprint: + dbg_print(obj->u.iprint.expr, frame); + break; + default: + abort(); + } + } + return 1; + +error: + instantiation_error = obj; + return 0; +} + + +static int generate_items(struct frame *frame, struct coord base, int active) +{ + char *s; + int ok; + + if (frame == frames) { + s = expand(pkg_name, frame); + /* s is NULL if expansion failed */ + inst_select_pkg(s ? s : "_", active); + free(s); + } + inst_begin_active(active && frame == active_frame); + ok = generate_vecs(frame, base) && generate_objs(frame, base, active); + inst_end_active(); + return ok; +} + + +static int match_keys(struct frame *frame, struct coord base, int active) +{ + const struct table *table; + const struct var *var; + const struct value *value; + int res; + + for (table = frame->tables; table; table = table->next) { + value = table->curr_row->values; + for (var = table->vars; var; var = var->next) { + if (var->key) { + res = var_eq(frame, var->name, value->expr); + if (!res) + return 1; + if (res < 0) + return 0; + } + value = value->next; + } + } + return generate_items(frame, base, active); +} + + +static int run_loops(struct frame *frame, struct loop *loop, + struct coord base, int active) +{ + struct num from, to; + int n; + int found_before, ok; + + if (!loop) + return match_keys(frame, base, active); + from = eval_num(loop->from.expr, frame); + if (is_undef(from)) { + fail_expr(loop->from.expr); + instantiation_error = loop; + return 0; + } + if (!is_dimensionless(from)) { + fail("incompatible type for start value"); + fail_expr(loop->from.expr); + instantiation_error = loop; + return 0; + } + + to = eval_num(loop->to.expr, frame); + if (is_undef(to)) { + fail_expr(loop->to.expr); + instantiation_error = loop; + return 0; + } + if (!is_dimensionless(to)) { + fail("incompatible type for end value"); + fail_expr(loop->to.expr); + instantiation_error = loop; + return 0; + } + + assert(!loop->initialized); + loop->curr_value = from.n; + loop->initialized = 1; + + n = 0; + for (; loop->curr_value <= to.n; loop->curr_value += 1) { + if (n >= MAX_ITERATIONS) { + fail("%s: too many iterations (%d)", loop->var.name, + MAX_ITERATIONS); + instantiation_error = loop; + goto fail; + } + found_before = found; + if (loop->found == loop->active) + suspend_search(); + ok = run_loops(frame, loop->next, base, + active && loop->active == n); + if (loop->found == loop->active) + resume_search(); + if (!ok) + goto fail; + if (found_before != found) + loop->found = n; + n++; + } + loop->initialized = 0; + loop->curr_value = UNDEF; + if (active) { + loop->n = from.n; + loop->iterations = n; + } + return 1; + +fail: + loop->initialized = 0; + return 0; +} + + +static int iterate_tables(struct frame *frame, struct table *table, + struct coord base, int active) +{ + int found_before, ok; + + if (!table) + return run_loops(frame, frame->loops, base, active); + for (table->curr_row = table->rows; table->curr_row; + table->curr_row = table->curr_row->next) { + found_before = found; + if (table->found_row == table->active_row) + suspend_search(); + ok = iterate_tables(frame, table->next, base, + active && table->active_row == table->curr_row); + if (table->found_row == table->active_row) + resume_search(); + if (!ok) + return 0; + if (found_before != found) + table->found_row = table->curr_row; + } + return 1; +} + + +static int generate_frame(struct frame *frame, struct coord base, + const struct frame *parent, struct obj *frame_ref, int active) +{ + int ok; + + /* + * We ensure during construction that frames can never recurse. + */ + inst_begin_frame(frame_ref, frame, base, + active && parent == active_frame, + active && frame == active_frame); + bitset_set(frame_set, frame->n); + frame->curr_parent = parent; + ok = iterate_tables(frame, frame->tables, base, active); + inst_end_frame(frame); + bitset_clear(frame_set, frame->n); + frame->curr_parent = NULL; + return ok; +} + + +static void reset_all_loops(void) +{ + const struct frame *frame; + struct loop *loop; + + for (frame = frames; frame; frame = frame->next) + for (loop = frame->loops; loop; loop = loop->next) + loop->iterations = 0; +} + + +static void reset_found(void) +{ + struct frame *frame; + struct table *table; + struct loop *loop; + + for (frame = frames; frame; frame = frame->next) { + for (table = frame->tables; table; table = table->next) + table->found_row = NULL; + for (loop = frame->loops; loop; loop = loop->next) + loop->found = -1; + frame->found_ref = NULL; + } +} + + +/* + * Note: we don't use frame->found_ref yet. Instead, we adjust the frame + * references with activate_item in inst.c + */ + +static void activate_found(void) +{ + struct frame *frame; + struct table *table; + struct loop *loop; + + for (frame = frames; frame; frame = frame->next) { + for (table = frame->tables; table; table = table->next) + if (table->found_row) + table->active_row = table->found_row; + for (loop = frame->loops; loop; loop = loop->next) + if (loop->found != -1) + loop->active = loop->found; + if (frame->found_ref) + frame->active_ref = frame->found_ref; + } +} + + +static int enumerate_frames(void) +{ + struct frame *frame; + int n = 0; + + for (frame = frames; frame; frame = frame->next) + frame->n = n++; + return n; +} + + +int instantiate(void) +{ + struct coord zero = { 0, 0 }; + int n_frames; + int ok; + + meas_start(); + inst_start(); + n_frames = enumerate_frames(); + frame_set = bitset_new(n_frames); + instantiation_error = NULL; + reset_all_loops(); + reset_found(); + found = 0; + search_suspended = 0; + ok = generate_frame(frames, zero, NULL, NULL, 1); + if (ok && (find_vec || find_obj) && found) + activate_found(); + find_vec = NULL; + find_obj = NULL; + if (ok) + ok = link_holes(); + if (ok) + ok = refine_layers(allow_overlap); + if (ok) + ok = instantiate_meas(n_frames); + if (ok) + inst_commit(); + else + inst_revert(); + bitset_free(frame_set); + return ok; +} + + +/* ----- deallocation ------------------------------------------------------ */ + + +void obj_cleanup(void) +{ + free(pkg_name); + while (frames) { + delete_frame(frames); + destroy(); + } +} @@ -0,0 +1,283 @@ +/* + * obj.h - Object definition model + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#ifndef OBJ_H +#define OBJ_H + +#include <assert.h> +#include <gtk/gtk.h> + +#include "expr.h" +#include "coord.h" +#include "meas.h" +#include "overlap.h" +#include "layer.h" + + +/* + * Objects contain various fields that help to select instances under various + * conditions. They are "current", "active", and "found": + * + * - current: the path taken while instantiating. E.g., we may make one frame + * reference the "current" reference of this frame and then recurse into it. + * "Current" is reset to a null value after instantiation is complete, to + * allow other functions (such as expression evaluation) to distinguish + * between instantiation and editing. + * + * - active: the path selected by the user, through the GUI. This allows the + * user to reach any instance, similar to how instantiation visits all + * instances. The difference to "current" is that "active" is persistent + * across instantiation while "current" iterates through all possible values + * during instantiation. + * + * - found: then clicking on an unselected instance, fped will try to activate + * this instance. In order to do so, it needs to determine which choices need + * to be activated to reach the instance. "Found" records this information. + * At the end of the search, all "found" choices become "active". + * + * If, during the search, an instance can be reached with the "found" choice + * being equal to the choice active at that time, "found" will not be set to + * any other value. This prevents searches from affecting choices that play + * no role in the selection of the instance. + */ + + +struct var { + const char *name; + struct var *next; + + /* back reference */ + struct frame *frame; + struct table *table; /* NULL if loop */ + + + /* key: 0 if the variable is set, 1 if the variable is compared */ + int key; + + /* for the GUI */ + GtkWidget *widget; + + /* for evaluation */ + int visited; +}; + +struct value { + struct expr *expr; + struct value *next; + + /* back reference, NULL if loop */ + struct row *row; + + /* for the GUI */ + GtkWidget *widget; +}; + +struct row { + struct value *values; + struct row *next; + + /* back reference */ + struct table *table; +}; + +struct table { + struct var *vars; + struct row *rows; + struct table *next; + + /* used during generation and when editing */ + struct row *curr_row; + + /* GUI use */ + struct row *active_row; + + /* For searching */ + struct row *found_row; /* NULL if not found yet */ +}; + +struct loop { + struct var var; + struct value from; + struct value to; + struct loop *next; + + /* used during generation */ + double curr_value; + + /* GUI use */ + int active; /* n-th iteration is active, 0 based */ + double n; /* start value when it was active */ + int iterations; /* iterations when it was active */ + + /* For searching */ + int found; /* -1 if not found yet */ + + /* for evaluation */ + int initialized; +}; + +struct sample; + +struct vec { + char nul_tag; /* tag for identifying vectors */ + const char *name; /* NULL if anonymous */ + struct expr *x; + struct expr *y; + struct vec *base; /* NULL if frame */ + struct vec *next; + + /* used during generation */ + struct coord pos; + + /* used when editing */ + struct frame *frame; + + /* index into table of samples */ + int n; + + /* for re-ordering after a move */ + int mark; + + /* for dumping */ + int dumped; + + /* for the GUI */ + GtkWidget *list_widget; /* NULL if items aren't shown */ +}; + +struct frame { + const char *name; /* NULL if top-level */ + struct table *tables; + struct loop *loops; + struct vec *vecs; + struct obj *objs; + struct frame *next; + + /* used during generation */ + const struct frame *curr_parent; + + /* generating and editing */ + struct obj *active_ref; + + /* for searching */ + struct obj *found_ref; /* NULL if not found yet */ + + /* index into bit vector in samples */ + int n; + + /* for dumping */ + int dumped; + + /* for the GUI */ + GtkWidget *label; +}; + +enum obj_type { + ot_frame, + ot_rect, + ot_pad, + ot_hole, + ot_line, + ot_arc, + ot_meas, + ot_iprint, +}; + +struct frame_ref { + struct frame *ref; + int lineno; +}; + +struct rect { + struct vec *other; /* NULL if frame origin */ + struct expr *width; +}; + +struct pad { + char *name; + struct vec *other; /* NULL if frame origin */ + int rounded; + enum pad_type type; +}; + +struct hole { + struct vec *other; /* NULL if frame origin */ +}; + +struct arc { + struct vec *start; /* NULL if frame origin */ + struct vec *end; /* NULL if this is a circle */ + struct expr *width; +}; + +struct iprint { + struct expr *expr; +}; + +struct obj { + enum obj_type type; + const char *name; /* NULL if anonymous */ + union { + struct frame_ref frame; + struct rect rect; + struct rect line; + struct pad pad; + struct hole hole; + struct arc arc; + struct meas meas; + struct iprint iprint; + } u; + struct frame *frame; + struct vec *base; + struct obj *next; + int lineno; + + /* for dumping */ + int dumped; + + /* for the GUI */ + GtkWidget *list_widget; /* NULL if items are not shown */ +}; + + +extern char *pkg_name; /* anonymous common package first */ +extern struct frame *frames; /* root frame first */ +extern struct frame *active_frame; +extern void *instantiation_error; +extern enum allow_overlap allow_overlap; + + +struct inst; + +/* + * Search callback from inst, invoked after the instance has been populated. + */ + +void find_inst(const struct inst *inst); + +/* + * If invoking search_inst before calling "instantiate", loop and tables are + * adjusted such that an instance matching the one passed to search_inst will + * become active. Note that this doesn't necessarily succeed, in which case no + * change is made. Also, if multiple matches are encountered, the result is + * arbitrary. + */ + +void search_inst(const struct inst *inst); + +int obj_anchors(struct obj *obj, struct vec ***anchors); + +int instantiate(void); +void obj_cleanup(void); + +#endif /* !OBJ_H */ diff --git a/overlap.c b/overlap.c new file mode 100644 index 0000000..e77c7f3 --- /dev/null +++ b/overlap.c @@ -0,0 +1,226 @@ +/* + * overlap.c - Test for overlaps + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> + +#include "coord.h" +#include "obj.h" +#include "inst.h" +#include "overlap.h" + + +/* + * @@@ result may be too optimistic if "b" is a rounded pad + */ + +int inside(const struct inst *a, const struct inst *b) +{ + struct coord min_a, max_a; + struct coord min_b, max_b; + + min_a = a->base; + switch (a->obj->type) { + case ot_pad: + max_a = a->u.pad.other; + break; + case ot_hole: + max_a = a->u.hole.other; + break; + default: + abort(); + } + sort_coord(&min_a, &max_a); + + min_b = b->base; + switch (b->obj->type) { + case ot_pad: + max_b = b->u.pad.other; + break; + case ot_hole: + max_b = b->u.hole.other; + break; + default: + abort(); + } + sort_coord(&min_b, &max_b); + + return min_a.x >= min_b.x && max_a.x <= max_b.x && + min_a.y >= min_b.y && max_a.y <= max_b.y; +} + + +/* ----- Overlap test for primitives --------------------------------------- */ + + +struct shape { + int circle; + struct coord center; + unit_type r; + struct coord min, max; +}; + + +static int circle_circle_overlap(struct coord c1, unit_type r1, + struct coord c2, unit_type r2, enum allow_overlap allow) +{ + if (allow == ao_touch) + return dist_point(c1, c2) < r1+r2; + return dist_point(c1, c2) <= r1+r2; +} + + +static int circle_rect_overlap(struct coord c, unit_type r, + struct coord min, struct coord max, enum allow_overlap allow) +{ + if (allow == ao_touch) { + if (c.x <= min.x-r || c.x >= max.x+r) + return 0; + if (c.y <= min.y-r || c.y >= max.y+r) + return 0; + } + if (c.x < min.x-r || c.x > max.x+r) + return 0; + if (c.y < min.y-r || c.y > max.y+r) + return 0; + return 1; +} + + +static int rect_rect_overlap(struct coord min1, struct coord max1, + struct coord min2, struct coord max2, enum allow_overlap allow) +{ + if (allow == ao_touch) { + if (max1.x <= min2.x || max2.x <= min1.x) + return 0; + if (max1.y <= min2.y || max2.y <= min1.y) + return 0; + } + if (max1.x < min2.x || max2.x < min1.x) + return 0; + if (max1.y < min2.y || max2.y < min1.y) + return 0; + return 1; +} + + +static int shapes_overlap(const struct shape *a, const struct shape *b, + enum allow_overlap allow) +{ + if (a->circle && !b->circle) + return shapes_overlap(b, a, allow); + if (a->circle) /* b must be circle, too */ + return circle_circle_overlap(a->center, a->r, b->center, b->r, + allow); + if (b->circle) /* a must be rect */ + return circle_rect_overlap(b->center, b->r, a->min, a->max, + allow); + return rect_rect_overlap(a->min, a->max, b->min, b->max, allow); +} + + +/* ----- Recursive overlap tester ------------------------------------------ */ + + +static int test_overlap(const struct inst *a, const struct inst *b, + const struct shape *other, enum allow_overlap allow); + + +static int do_circle(const struct inst *next, const struct shape *other, + unit_type x, unit_type y, unit_type r, enum allow_overlap allow) +{ + struct shape shape = { + .circle = 1, + .center = { + .x = x, + .y = y, + }, + .r = r, + }; + + if (next) + return test_overlap(next, NULL, &shape, allow); + return shapes_overlap(other, &shape, allow); +} + + +static int do_rect(const struct inst *next, const struct shape *other, + unit_type x, unit_type y, unit_type w, unit_type h, + enum allow_overlap allow) +{ + struct shape shape = { + .circle = 0, + .min = { + .x = x, + .y = y, + }, + .max = { + .x = x+w, + .y = y+h, + }, + }; + + if (next) + return test_overlap(next, NULL, &shape, allow); + return shapes_overlap(other, &shape, allow); +} + + +static int test_overlap(const struct inst *a, const struct inst *b, + const struct shape *other, enum allow_overlap allow) +{ + struct coord min, max; + unit_type h, w, r; + int rounded; + + min = a->base; + switch (a->obj->type) { + case ot_pad: + max = a->u.pad.other; + rounded = a->obj->u.pad.rounded; + break; + case ot_hole: + max = a->u.hole.other; + rounded = 1; + break; + default: + abort(); + } + sort_coord(&min, &max); + + h = max.y-min.y; + w = max.x-min.x; + + if (!rounded) + return do_rect(b, other, min.x, min.y, w, h, allow); + + if (h > w) { + r = w/2; + return do_circle(b, other, min.x+r, max.y-r, r, allow) || + do_rect(b, other, min.x, min.y+r, w, h-2*r, allow) || + do_circle(b, other, min.x+r, min.y+r, r, allow); + } else { + r = h/2; + return do_circle(b, other, min.x+r, min.y+r, r, allow) || + do_rect(b, other, min.x+r, min.y, w-2*r, h, allow) || + do_circle(b, other, max.x-r, min.y+r, r, allow); + } +} + + +int overlap(const struct inst *a, const struct inst *b, + enum allow_overlap allow) +{ + if (allow == ao_any) + return 0; + return test_overlap(a, b, NULL, allow); +} diff --git a/overlap.h b/overlap.h new file mode 100644 index 0000000..30b42c1 --- /dev/null +++ b/overlap.h @@ -0,0 +1,45 @@ +/* + * overlap.h - Test for overlaps + * + * Written 2009, 2010 by Werner Almesberger + * Copyright 2009, 2010 by Werner Almesberger + * + * 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. + */ + +#ifndef OVERLAP_H +#define OVERLAP_H + + +enum allow_overlap { + ao_none, + ao_touch, + ao_any, +}; + + +/* + * Avoid inst.h -> layer.h -> overlay.h -> inst.h loop + */ + +struct inst; + + +/* + * "inside" returns 1 if "a" is completely enclosed by "b". If "a" == "b", + * that also counts as "a" being inside "b". + */ + +int inside(const struct inst *a, const struct inst *b); + +/* + * "overlap" returns 1 if "a" and "b" have at least one point in common. + */ + +int overlap(const struct inst *a, const struct inst *b, + enum allow_overlap allow); + +#endif /* !OVERLAP_H */ diff --git a/postscript.c b/postscript.c new file mode 100644 index 0000000..e20ae66 --- /dev/null +++ b/postscript.c @@ -0,0 +1,1234 @@ +/* + * postscript.c - Dump objects in Postscript + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> + +#include "util.h" +#include "coord.h" +#include "layer.h" +#include "obj.h" +#include "inst.h" +#include "unparse.h" +#include "gui_status.h" +#include "gui_inst.h" +#include "postscript.h" + + +/* + * A4 is 210 mm x 297 mm + * US Letter is 216 mm x 279 mm + * + * We pick the smallest dimensions minus a bit of slack and center on the + * printer page. + */ + +#define PAGE_HALF_WIDTH mm_to_units(210/2.0-10) /* A4 */ +#define PAGE_HALF_HEIGHT mm_to_units(279/2.0-15) /* US Letter */ + +/* + * Page layout: + * + * HEADER DATE + * --------------------------- HEADER_HEIGHT+DIVIDER_BORDER below top + * | + * | 2x + * 10 x |<------------- roughly at 10/12 + * | 1x + * | + * --------------------------- 50% height + * Frames in boxes + * + */ + + +#define PS_HEADER_HEIGHT mm_to_units(8) +#define PS_DIVIDER_BORDER mm_to_units(2) +#define PS_DIVIDER_WIDTH mm_to_units(0.5) + +#define PS_MISC_TEXT_HEIGHT mm_to_units(3) + +#define PS_DOT_DIST mm_to_units(0.03) +#define PS_DOT_DIAM mm_to_units(0.01) + +#define PS_HATCH mm_to_units(0.1) +#define PS_HATCH_LINE mm_to_units(0.015) + +#define PS_STRIPE mm_to_units(0.08) + +#define PS_RIM_LINE mm_to_units(0.02) + +#define PS_FONT_OUTLINE mm_to_units(0.025) + +#define PS_VEC_LINE mm_to_units(0.02) +#define PS_VEC_ARROW_LEN mm_to_units(0.3) +#define PS_VEC_ARROW_ANGLE 20 +#define PS_VEC_TEXT_HEIGHT mm_to_units(3) /* ~8.5 pt, real mm */ +#define PS_VEC_BASE_OFFSET mm_to_units(0.5) /* real mm */ + +#define PS_MEAS_LINE mm_to_units(0.1) /* real mm */ +#define PS_MEAS_ARROW_LEN mm_to_units(0.15) +#define PS_MEAS_ARROW_ANGLE 30 +#define PS_MEAS_TEXT_HEIGHT mm_to_units(3) /* ~8.5 pt, real mm */ +#define PS_MEAS_BASE_OFFSET mm_to_units(0.5) /* real mm */ +#define PS_MEAS_MIN_HEIGHT (PS_MEAS_TEXT_HEIGHT/2) + +#define PS_CROSS_WIDTH mm_to_units(0.01) +#define PS_CROSS_DASH mm_to_units(0.1) + +#define PS_KEY_X_GAP mm_to_units(8) +#define PS_KEY_Y_GAP mm_to_units(4) +#define PS_KEY_HEIGTH mm_to_units(8) + +#define TEXT_HEIGHT_FACTOR 1.5 /* height/width of typical text */ + + +struct postscript_params postscript_params = { + .zoom = 0, + .max_width = 0, + .max_height = 0, + .show_pad_names = 1, + .show_stuff = 0, + .label_vecs = 0, + .show_meas = 1, + .show_key = 0, +}; + +static const struct postscript_params minimal_params; +static struct postscript_params active_params; +static int pad_type_seen[pt_n]; + + +/* ----- Boxes ------------------------------------------------------------- */ + + +static struct box { + unit_type x, y; /* width and height */ + unit_type x0, y0; /* lower left corner */ + struct box *next; +} *boxes = NULL; + + +static void add_box(unit_type xa, unit_type ya, unit_type xb, unit_type yb) +{ + struct box *box; + + box = alloc_type(struct box); + box->x = xb-xa; + box->y = yb-ya; + box->x0 = xa; + box->y0 = ya; + box->next = boxes; + boxes = box; +} + + +static void free_boxes(void) +{ + struct box *next; + + while (boxes) { + next = boxes->next; + free(boxes); + boxes = next; + } +} + + +static int get_box(unit_type x, unit_type y, unit_type *xa, unit_type *ya) +{ + struct box **box, **best = NULL; + struct box *b; + double size, best_size; + + for (box = &boxes; *box; box = &(*box)->next) { + if ((*box)->x < x || (*box)->y < y) + continue; + size = (double) (*box)->x*(*box)->y; + if (!best || size < best_size) { + best = box; + best_size = size; + } + } + if (!best) + return 0; + b = *best; + if (xa) + *xa = b->x0; + if (ya) + *ya = b->y0+b->y-y; + + *best = b->next; + add_box(b->x0+x, b->y0, b->x0+b->x, b->y0+b->y); + add_box(b->x0, b->y0, b->x0+x, b->y0+b->y-y); + free(b); + + return 1; +} + + +/* ----- Helper functions -------------------------------------------------- */ + + +static void ps_string(FILE *file, const char *s) +{ + fputc('(', file); + while (*s) { + if (*s == '(' || *s == ')' || *s == '\\') + fputc('\\', file); + fputc(*s, file); + s++; + } + fputc(')', file); +} + + +static void ps_filled_box(FILE *file, struct coord a, struct coord b, + const char *pattern) +{ + fprintf(file, "0 setgray %d setlinewidth\n", PS_HATCH_LINE); + fprintf(file, " %d %d moveto\n", a.x, a.y); + fprintf(file, " %d %d lineto\n", b.x, a.y); + fprintf(file, " %d %d lineto\n", b.x, b.y); + fprintf(file, " %d %d lineto\n", a.x, b.y); + fprintf(file, " closepath gsave %s grestore stroke\n", pattern); +} + + +static void ps_outlined_text_in_rect(FILE *file, const char *s, + struct coord a, struct coord b) +{ + const char *t; + unit_type h, w; + + for (t = s; *t == ' '; t++); + if (!*t) + return; + h = a.y-b.y; + w = a.x-b.x; + if (h < 0) + h = -h; + if (w < 0) + w = -w; + fprintf(file, "0 setgray /Helvetica-Bold findfont dup\n"); + fprintf(file, " "); + ps_string(file, s); + fprintf(file, " %d %d\n", w/2, h/2); + fprintf(file, " boxfont\n"); + fprintf(file, " %d %d moveto\n", (a.x+b.x)/2, (a.y+b.y)/2); + fprintf(file, " "); + ps_string(file, s); + fprintf(file, " center %d showoutlined newpath\n", PS_FONT_OUTLINE); +} + + +/* ----- Items ------------------------------------------------------------- */ + + +static void ps_pad_name(FILE *file, const struct inst *inst) +{ + ps_outlined_text_in_rect(file, inst->u.pad.name, + inst->base, inst->u.pad.other); +} + + +static const char *hatch(enum pad_type type) +{ + switch (type) { + case pt_normal: + return "crosspath"; + case pt_bare: + return "hatchpath"; + case pt_paste: + return "backhatchpath"; + case pt_mask: + return "dotpath"; + case pt_trace: + return "horpath"; + default: + abort(); + } +} + + +static void ps_pad(FILE *file, const struct inst *inst, int show_name) +{ + enum pad_type type = layers_to_pad_type(inst->u.pad.layers); + + pad_type_seen[type] = 1; + ps_filled_box(file, inst->base, inst->u.pad.other, hatch(type)); + + if (show_name && !inst->u.pad.hole) + ps_pad_name(file, inst); +} + + +static void ps_rounded_rect(FILE *file, struct coord a, struct coord b) +{ + unit_type h, w, r; + + sort_coord(&a, &b); + h = b.y-a.y; + w = b.x-a.x; + + if (h > w) { + r = w/2; + fprintf(file, " %d %d moveto\n", b.x, b.y-r); + fprintf(file, " %d %d %d 0 180 arc\n", a.x+r, b.y-r, r); + fprintf(file, " %d %d lineto\n", a.x, a.y+r); + fprintf(file, " %d %d %d 180 360 arc\n", a.x+r, a.y+r, r); + } else { + r = h/2; + fprintf(file, " %d %d moveto\n", b.x-r, a.y); + fprintf(file, " %d %d %d -90 90 arc\n", b.x-r, a.y+r, r); + fprintf(file, " %d %d lineto\n", a.x+r, b.y); + fprintf(file, " %d %d %d 90 270 arc\n", a.x+r, a.y+r, r); + } +} + + +static void ps_rpad(FILE *file, const struct inst *inst, int show_name) +{ + enum pad_type type = layers_to_pad_type(inst->u.pad.layers); + + pad_type_seen[type] = 1; + + fprintf(file, "0 setgray %d setlinewidth\n", PS_HATCH_LINE); + ps_rounded_rect(file, inst->base, inst->u.pad.other); + fprintf(file, " closepath gsave %s grestore stroke\n", hatch(type)); + + if (show_name && !inst->u.pad.hole) + ps_pad_name(file, inst); +} + + +static void ps_hole(FILE *file, const struct inst *inst, int show_name) +{ + fprintf(file, "1 setgray %d setlinewidth\n", PS_RIM_LINE); + ps_rounded_rect(file, inst->base, inst->u.hole.other); + fprintf(file, " closepath gsave fill grestore\n"); + fprintf(file, " 0 setgray stroke\n"); + + if (show_name && inst->u.hole.pad) + ps_pad_name(file, inst->u.hole.pad); +} + + +static void ps_line(FILE *file, const struct inst *inst) +{ + struct coord a = inst->base; + struct coord b = inst->u.rect.end; + + fprintf(file, "1 setlinecap 0.5 setgray %d setlinewidth\n", + inst->u.rect.width); + fprintf(file, " %d %d moveto %d %d lineto stroke\n", + a.x, a.y, b.x, b.y); +} + + +static void ps_rect(FILE *file, const struct inst *inst) +{ + struct coord a = inst->base; + struct coord b = inst->u.rect.end; + + fprintf(file, "1 setlinecap 0.5 setgray %d setlinewidth\n", + inst->u.rect.width); + fprintf(file, " %d %d moveto\n", a.x, a.y); + fprintf(file, " %d %d lineto\n", b.x, a.y); + fprintf(file, " %d %d lineto\n", b.x, b.y); + fprintf(file, " %d %d lineto\n", a.x, b.y); + fprintf(file, " closepath stroke\n"); +} + + +static void ps_arc(FILE *file, const struct inst *inst) +{ + double a1, a2; + + a1 = inst->u.arc.a1; + a2 = inst->u.arc.a2; + if (a2 <= a1) + a2 += 360; + + fprintf(file, "1 setlinecap 0.5 setgray %d setlinewidth\n", + inst->u.arc.width); + fprintf(file, " newpath %d %d %d %f %f arc stroke\n", + inst->base.x, inst->base.y, inst->u.arc.r, a1, a2); +} + + +static void ps_frame(FILE *file, const struct inst *inst) +{ +} + + +static void ps_arrow(FILE *file, struct coord from, struct coord to, int len, + int angle) +{ + struct coord side, p; + + if (from.x == to.x && from.y == to.y) { + side.x = 0; + side.y = -len; + } else { + side = normalize(sub_vec(to, from), len); + } + + p = add_vec(to, rotate(side, 180-angle)); + fprintf(file, " %d %d moveto\n", p.x, p.y); + fprintf(file, " %d %d lineto\n", to.x, to.y); + + p = add_vec(to, rotate(side, 180+angle)); + fprintf(file, " %d %d moveto\n", p.x, p.y); + fprintf(file, " %d %d lineto\n", to.x, to.y); + fprintf(file, " stroke\n"); +} + + +static void ps_vec(FILE *file, const struct inst *inst) +{ + struct coord a, b, c, d; + char *s, *sx, *sy; + + a = inst->base; + b = inst->u.vec.end; + fprintf(file, "1 setlinecap 0 setgray %d setlinewidth\n", PS_VEC_LINE); + fprintf(file, " %d %d moveto\n", a.x, a.y); + fprintf(file, " %d %d lineto\n", b.x, b.y); + fprintf(file, " stroke\n"); + + ps_arrow(file, a, b, PS_VEC_ARROW_LEN, PS_VEC_ARROW_ANGLE); + + if (!active_params.label_vecs) + return; + + sx = unparse(inst->vec->x); + sy = unparse(inst->vec->y); + s = stralloc_printf("(%s, %s)", sx, sy); + free(sx); + free(sy); + c = add_vec(a, b); + d = sub_vec(b, a); + fprintf(file, "gsave %d %d moveto\n", c.x/2, c.y/2); + fprintf(file, " /Helvetica-Bold findfont dup\n"); + fprintf(file, " "); + ps_string(file, s); + fprintf(file, " %d %d realsize\n", + (int) (dist_point(a, b)-2*PS_VEC_ARROW_LEN), + PS_VEC_TEXT_HEIGHT); + fprintf(file, " boxfont\n"); + fprintf(file, " %f rotate\n", atan2(d.y, d.x)/M_PI*180); + fprintf(file, " "); + ps_string(file, s); + fprintf(file, " %d realsize pop 0 hcenter\n", PS_VEC_BASE_OFFSET); + fprintf(file, " show grestore\n"); + free(s); +} + + +/* ----- Measurements ------------------------------------------------------ */ + + +static unit_type guesstimate_text_height(const char *s, unit_type width, + double zoom) +{ + return width/strlen(s)*TEXT_HEIGHT_FACTOR*zoom; +} + + +static void ps_meas(FILE *file, const struct inst *inst, + enum curr_unit unit, double zoom) +{ + struct coord a0, b0, a1, b1; + struct coord c, d; + char *s; + unit_type height, width, offset; + + a0 = inst->base; + b0 = inst->u.meas.end; + project_meas(inst, &a1, &b1); + fprintf(file, "1 setlinecap 0 setgray %d realsize setlinewidth\n", + PS_MEAS_LINE); + fprintf(file, " %d %d moveto\n", a0.x, a0.y); + fprintf(file, " %d %d lineto\n", a1.x, a1.y); + fprintf(file, " %d %d lineto\n", b1.x, b1.y); + fprintf(file, " %d %d lineto\n", b0.x, b0.y); + fprintf(file, " stroke\n"); + + ps_arrow(file, a1, b1, PS_MEAS_ARROW_LEN, PS_MEAS_ARROW_ANGLE); + ps_arrow(file, b1, a1, PS_MEAS_ARROW_LEN, PS_MEAS_ARROW_ANGLE); + + s = format_len(inst->obj->u.meas.label ? inst->obj->u.meas.label : "", + dist_point(a1, b1), unit); + + c = add_vec(a1, b1); + d = sub_vec(b1, a1); + + /* + * First try: put text between the arrows + */ + width = dist_point(a1, b1)-1.5*PS_MEAS_ARROW_LEN; + offset = PS_MEAS_BASE_OFFSET; + height = 0; + if (guesstimate_text_height(s, width, zoom) < PS_MEAS_MIN_HEIGHT) { +#if 0 +fprintf(stderr, "%s -> width %d height %d vs. %d\n", + s, width, guesstimate_text_height(s, width, zoom), PS_MEAS_MIN_HEIGHT); +#endif + /* + * Second try: push it above the arrows + */ + width = dist_point(a1, b1); + offset += + PS_MEAS_ARROW_LEN*sin(PS_MEAS_ARROW_ANGLE*M_PI/180)*zoom; + + if (guesstimate_text_height(s, width, zoom) < + PS_MEAS_MIN_HEIGHT) { + height = PS_MEAS_MIN_HEIGHT; + width = strlen(s)*height; + } + } + + if (height) { + fprintf(file, "gsave %d %d moveto\n", c.x/2, c.y/2); + fprintf(file, " /Helvetica-Bold findfont dup\n"); + fprintf(file, " "); + ps_string(file, s); + fprintf(file, " %d realsize %d realsize\n", width, height); + fprintf(file, " boxfont\n"); + fprintf(file, " %f rotate\n", atan2(d.y, d.x)/M_PI*180); + fprintf(file, " "); + ps_string(file, s); + fprintf(file, " %d realsize hcenter\n", offset); + fprintf(file, " show grestore\n"); + } else { + fprintf(file, "gsave %d %d moveto\n", c.x/2, c.y/2); + fprintf(file, " /Helvetica-Bold findfont dup\n"); + fprintf(file, " "); + ps_string(file, s); + fprintf(file, " %d %d realsize\n", width, PS_MEAS_TEXT_HEIGHT); + fprintf(file, " boxfont\n"); + fprintf(file, " %f rotate\n", atan2(d.y, d.x)/M_PI*180); + fprintf(file, " "); + ps_string(file, s); + fprintf(file, " %d realsize hcenter\n", offset); + fprintf(file, " show grestore\n"); + } + free(s); +} + + +/* ----- Print layers ------------------------------------------------------ */ + + +static void ps_background(FILE *file, enum inst_prio prio, + const struct inst *inst) +{ + switch (prio) { + case ip_line: + ps_line(file, inst); + break; + case ip_rect: + ps_rect(file, inst); + break; + case ip_circ: + case ip_arc: + ps_arc(file, inst); + break; + default: + break; + } +} + + +static void ps_foreground(FILE *file, enum inst_prio prio, + const struct inst *inst, double zoom) +{ + switch (prio) { + case ip_pad_copper: + case ip_pad_special: + if (inst->obj->u.pad.rounded) + ps_rpad(file, inst, active_params.show_pad_names); + else + ps_pad(file, inst, active_params.show_pad_names); + break; + case ip_hole: + ps_hole(file, inst, active_params.show_pad_names); + break; + case ip_vec: + if (active_params.show_stuff) + ps_vec(file, inst); + break; + case ip_frame: + if (active_params.show_stuff) + ps_frame(file, inst); + break; + case ip_meas: + if (active_params.show_meas) + ps_meas(file, inst, curr_unit, zoom); + break; + default: + break; + } +} + + +/* ----- Package level ----------------------------------------------------- */ + + +static void ps_cross(FILE *file, const struct inst *inst) +{ + fprintf(file, "gsave 0 setgray %d setlinewidth\n", PS_CROSS_WIDTH); + fprintf(file, " [%d] 0 setdash\n", PS_CROSS_DASH); + fprintf(file, " %d 0 moveto %d 0 lineto\n", + inst->bbox.min.x, inst->bbox.max.x); + fprintf(file, " 0 %d moveto 0 %d lineto\n", + inst->bbox.min.y, inst->bbox.max.y); + fprintf(file, " stroke grestore\n"); +} + + +static void ps_draw_package(FILE *file, const struct pkg *pkg, double zoom, + int cross) +{ + enum inst_prio prio; + const struct inst *inst; + + fprintf(file, "gsave %f dup scale\n", zoom); + if (cross) + ps_cross(file, pkgs->insts[ip_frame]); + FOR_INST_PRIOS_UP(prio) { + FOR_PKG_INSTS(pkgs, prio, inst) + ps_background(file, prio, inst); + FOR_PKG_INSTS(pkg, prio, inst) + ps_background(file, prio, inst); + } + FOR_INST_PRIOS_UP(prio) { + FOR_PKG_INSTS(pkgs, prio, inst) + ps_foreground(file, prio, inst, zoom); + FOR_PKG_INSTS(pkg, prio, inst) + ps_foreground(file, prio, inst, zoom); + } + fprintf(file, "grestore\n"); +} + + +/* ----- Object frames ----------------------------------------------------- */ + + +static void ps_draw_frame(FILE *file, const struct pkg *pkg, + const struct inst *outer, double zoom) +{ + enum inst_prio prio; + const struct inst *inst; + + fprintf(file, "gsave %f dup scale\n", zoom); + ps_cross(file, outer); + FOR_INST_PRIOS_UP(prio) { + FOR_PKG_INSTS(pkgs, prio, inst) + if (inst->outer == outer) + ps_background(file, prio, inst); + FOR_PKG_INSTS(pkg, prio, inst) + if (inst->outer == outer) + ps_background(file, prio, inst); + } + FOR_INST_PRIOS_UP(prio) { + FOR_PKG_INSTS(pkgs, prio, inst) + if (inst->outer == outer) + ps_foreground(file, prio, inst, zoom); + FOR_PKG_INSTS(pkg, prio, inst) + if (inst->outer == outer) + ps_foreground(file, prio, inst, zoom); + } + fprintf(file, "grestore\n"); +} + + +static int generate_frames(FILE *file, const struct pkg *pkg, + const struct frame *frame, double zoom) +{ + const struct inst *inst; + unit_type x, y, xa, ya; + unit_type cx, cy, border; + int ok; + + /* + * This doesn't work yet. The whole idea of just picking the current + * instance of each object and drawing it is flawed, since we may have + * very different sizes in a frame, so one big vector may dominate all + * the finer details. + * + * Also, the amount of text can be large and force tiny fonts to make + * things fit. + * + * A better approach would be to use a more qualitative display than a + * quantitative one, emphasizing the logical structure of the drawing + * and not the actual sizes. + * + * This could be done by ranking vectors by current, average, maximum, + * etc. size, then let their size be determined by the amount of text + * that's needed and the size of subordinate vectors. One difficulty + * would be in making vectors with a fixed length ratio look correct, + * particularly 1:1. + * + * Furthermore, don't write on the vector but put the text horizontally + * on either the left or the right side. + * + * Frame references could be drawn by simply connecting a line to the + * area of the respective frame. And let's not forget that we also need + * to list the variables somewhere. + */ + return 0; + + while (frame) { + if (frame->name) + for (inst = pkg->insts[ip_frame]; inst; + inst = inst->next) + if (inst->u.frame.ref == frame) + goto found_frame; + frame = frame->next; + } + if (!frame) + return 1; + +found_frame: + border = PS_MEAS_TEXT_HEIGHT+PS_DIVIDER_WIDTH+PS_DIVIDER_BORDER/2; + x = (inst->bbox.max.x-inst->bbox.min.x)*zoom+2*border; + y = (inst->bbox.max.y-inst->bbox.min.y)*zoom+2*border; + if (!get_box(x, y, &xa, &ya)) + return 0; + + /* + * Recurse down first, so that we only draw something if we can be sure + * that all the rest can be drawn too. + */ + + ok = generate_frames(file, pkg, frame->next, zoom); + if (!ok) + return 0; + + +#if 1 +fprintf(file, "0 setlinewidth 0.8 setgray\n"); +fprintf(file, "%d %d moveto\n", xa+border, ya+border); +fprintf(file, "%d %d lineto\n", xa+x-border, ya+border); +fprintf(file, "%d %d lineto\n", xa+x-border, ya+y-border); +fprintf(file, "%d %d lineto\n", xa+border, ya+y-border); +fprintf(file, "closepath fill\n"); +#endif + cx = xa+x/2-(inst->bbox.min.x+inst->bbox.max.x)/2*zoom; + cy = ya+y/2-(inst->bbox.min.y+inst->bbox.max.y)/2*zoom; + + fprintf(file, "%% Frame %s\n", frame->name ? frame->name : "(root)"); + fprintf(file, "gsave %d %d translate\n", cx, cy); + ps_draw_frame(file, pkg, inst, zoom); + fprintf(file, "grestore\n"); + + return 1; +} + + +/* ----- Page level -------------------------------------------------------- */ + + +static void ps_hline(FILE *file, int y) +{ + fprintf(file, "gsave %d setlinewidth\n", PS_DIVIDER_WIDTH); + fprintf(file, " %d %d moveto\n", -PAGE_HALF_WIDTH, y); + fprintf(file, " %d 0 rlineto stroke grestore\n", PAGE_HALF_WIDTH*2); +} + + +static void ps_header(FILE *file, const struct pkg *pkg) +{ + fprintf(file, "gsave %d %d moveto\n", + -PAGE_HALF_WIDTH, PAGE_HALF_HEIGHT-PS_HEADER_HEIGHT); + fprintf(file, " /Helvetica-Bold findfont dup\n"); + fprintf(file, " "); + ps_string(file, pkg->name); + fprintf(file, " %d %d\n", PAGE_HALF_WIDTH, PS_HEADER_HEIGHT); + fprintf(file, " boxfont\n"); + fprintf(file, " "); + ps_string(file, pkg->name); + fprintf(file, " show grestore\n"); + + ps_hline(file, PAGE_HALF_HEIGHT-PS_HEADER_HEIGHT-PS_DIVIDER_BORDER); +} + + +static void ps_page(FILE *file, int page, const struct pkg *pkg) +{ + fprintf(file, "%%%%Page: %d %d\n", page, page); + + fprintf(file, "%%%%BeginPageSetup\n"); + fprintf(file, +"currentpagedevice /PageSize get\n" +" aload pop\n" +" 2 div exch 2 div exch\n" +" translate\n" +" 72 %d div 1000 div dup scale\n", + (int) MIL_UNITS); + fprintf(file, "%%%%EndPageSetup\n"); + fprintf(file, "[ /Title "); + ps_string(file, pkg->name); + fprintf(file, " /OUT pdfmark\n"); +} + + +static void ps_unit(FILE *file, + unit_type x, unit_type y, unit_type w, unit_type h) +{ + const char *s; + + switch (curr_unit) { + case curr_unit_mm: + s = "Dimensions in mm"; + break; + case curr_unit_mil: + s = "Dimensions in mil"; + break; + case curr_unit_auto: + return; + default: + abort(); + } + + fprintf(file, "gsave %d %d moveto\n", x, y); + fprintf(file, " /Helvetica findfont dup\n"); + fprintf(file, " "); + ps_string(file, s); + fprintf(file, " %d %d\n", w, h); + fprintf(file, " boxfont\n"); + fprintf(file, " "); + ps_string(file, s); + fprintf(file, " show grestore\n"); +} + + +static void ps_key(FILE *file, double w, double h, enum pad_type type) +{ + char tmp[20]; /* @@@ plenty :) */ + double f = 32; + struct coord a, b; + unit_type key_w; + + key_w = (w-2*PS_KEY_X_GAP-PS_KEY_X_GAP*(pt_n-1))/pt_n; + a.x = b.x = (key_w+PS_KEY_X_GAP)*type-w/2+PS_KEY_X_GAP; + a.y = b.y = -h/2-PS_KEY_Y_GAP; + b.x += key_w; + b.y -= PS_KEY_HEIGTH; + + a.x /= f; + a.y /= f; + b.x /= f; + b.y /= f; + + strcpy(tmp, pad_type_name(type)); + tmp[0] = toupper(tmp[0]); + fprintf(file, "gsave %f %f scale\n", f, f); + ps_filled_box(file, a, b, hatch(type)); + ps_outlined_text_in_rect(file, tmp, a, b); + fprintf(file, "grestore\n"); +} + + +static void ps_keys(FILE *file, double w, double h) +{ + enum pad_type i; + + for (i = 0; i != pt_n; i++) + if (pad_type_seen[i]) + ps_key(file, w, h, i); +} + + +static void ps_package(FILE *file, const struct pkg *pkg, int page) +{ + struct bbox bbox; + unit_type x, y; + unit_type w, h; + double f; + unit_type c, d; + int done; + + ps_page(file, page, pkg); + ps_header(file, pkg); + + x = 2*PAGE_HALF_WIDTH-2*PS_DIVIDER_BORDER; + y = PAGE_HALF_HEIGHT-PS_HEADER_HEIGHT-3*PS_DIVIDER_BORDER; + + bbox = inst_get_bbox(pkg); + w = 2*(-bbox.min.x > bbox.max.x ? -bbox.min.x : bbox.max.x); + h = 2*(-bbox.min.y > bbox.max.y ? -bbox.min.y : bbox.max.y); + + /* + * Zoom such that we can fit at least one drawing + */ + + if (w > x/2 || h > y) { + f = (double) x/w; + if ((double) y/h < f) + f = (double) y/h; + if (f > 1) + f = 1; + } else { + for (f = 20; f > 1; f--) + if (x/(f+2) >= w && y/f >= h) + break; + } + + /* + * Decide if we have room for two, one, or zero smaller views + */ + + c = y/2+PS_DIVIDER_BORDER; + active_params = postscript_params; + if (x/(f+2) >= w && y/3 > h) { + /* main drawing */ + fprintf(file, "gsave %d %d translate\n", + (int) (x/(f+2)*f/2)-PAGE_HALF_WIDTH, c); + ps_draw_package(file, pkg, f, 1); + + active_params = minimal_params; + + /* divider */ + d = PAGE_HALF_WIDTH-2*x/(f+2); + fprintf(file, "grestore gsave %d setlinewidth\n", + PS_DIVIDER_WIDTH); + fprintf(file, " %d %d moveto 0 %d rlineto stroke\n", + d-PS_DIVIDER_BORDER, PS_DIVIDER_BORDER, y); + + /* x1 package */ + fprintf(file, "grestore gsave %d %d translate\n", + (d+PAGE_HALF_WIDTH)/2, y/6*5+PS_DIVIDER_BORDER); + ps_draw_package(file, pkg, 1, 1); + + /* x2 package */ + fprintf(file, "grestore gsave %d %d translate\n", + (d+PAGE_HALF_WIDTH)/2, y/3+PS_DIVIDER_BORDER); + ps_draw_package(file, pkg, 2, 1); + } else if (x/(f+1) >= w && y/2 > h) { + /* main drawing */ + fprintf(file, "gsave %d %d translate\n", + (int) (x/(f+1)*f/2)-PAGE_HALF_WIDTH, c); + ps_draw_package(file, pkg, f, 1); + + active_params = minimal_params; + + /* divider */ + d = PAGE_HALF_WIDTH-x/(f+1); + fprintf(file, "grestore gsave %d setlinewidth\n", + PS_DIVIDER_WIDTH); + fprintf(file, " %d %d moveto 0 %d rlineto stroke\n", + d-PS_DIVIDER_BORDER, PS_DIVIDER_BORDER, y); + + /* x1 package */ + fprintf(file, "grestore gsave %d %d translate\n", + (d+PAGE_HALF_WIDTH)/2, c); + ps_draw_package(file, pkg, 1, 1); + } else { + fprintf(file, "gsave 0 %d translate\n", c); + ps_draw_package(file, pkg, f, 1); + } + fprintf(file, "grestore\n"); + + ps_unit(file, -PAGE_HALF_WIDTH, PS_DIVIDER_BORDER, PAGE_HALF_WIDTH, + PS_MISC_TEXT_HEIGHT); + ps_hline(file, 0); + + /* + * Put the frames + * + * @@@ is it really a good idea to use the same zoom for all of them ? + */ + + active_params.show_stuff = 1; + active_params.label_vecs = 1; + for (f = 20; f >= 0.1; f = f > 1 ? f-1 : f-0.1) { + add_box(-PAGE_HALF_WIDTH, -PAGE_HALF_HEIGHT, PAGE_HALF_WIDTH, + -PS_DIVIDER_BORDER); + done = generate_frames(file, pkg, frames, f); + free_boxes(); + if (done) + break; + } + + fprintf(file, "showpage\n"); +} + + +/* ----- File level -------------------------------------------------------- */ + + +static void prologue(FILE *file, int pages) +{ + fprintf(file, "%%!PS-Adobe-3.0\n"); + fprintf(file, "%%%%Pages: %d\n", pages); + fprintf(file, "%%%%EndComments\n"); + + fprintf(file, "%%%%BeginDefaults\n"); + fprintf(file, "%%%%PageResources: font Helvetica Helvetica-Bold\n"); + fprintf(file, "%%%%EndDefaults\n"); + + fprintf(file, "%%%%BeginProlog\n"); + + fprintf(file, +"/dotpath {\n" +" gsave flattenpath pathbbox clip newpath\n" +" 1 setlinecap %d setlinewidth\n" +" /ury exch def /urx exch def /lly exch def /llx exch def\n" +" llx %d urx {\n" +" lly %d ury {\n" +" 1 index exch moveto 0 0 rlineto stroke\n" +" } for\n" +" } for\n" +" grestore newpath } def\n", PS_DOT_DIAM, PS_DOT_DIST, PS_DOT_DIST); + + fprintf(file, +"/hatchpath {\n" +" gsave flattenpath pathbbox clip newpath\n" +" /ury exch def /urx exch def /lly exch def /llx exch def\n" +" lly ury sub %d urx llx sub {\n" /* for -(ury-lly) to urx-llx */ +" llx add dup lly moveto\n" +" ury lly sub add ury lineto stroke\n" +" } for\n" +" grestore newpath } def\n", PS_HATCH); + + fprintf(file, +"/backhatchpath {\n" +" gsave flattenpath pathbbox clip newpath\n" +" /ury exch def /urx exch def /lly exch def /llx exch def\n" +" 0 %d ury lly sub urx llx sub add {\n" /* for 0 to urx-llx+ury-lly */ +" llx add dup lly moveto\n" +" ury lly sub sub ury lineto stroke\n" +" } for\n" +" grestore newpath } def\n", PS_HATCH); + +fprintf(file, +"/crosspath {\n" +" gsave hatchpath grestore backhatchpath } def\n"); + + fprintf(file, +"/horpath {\n" +" gsave flattenpath pathbbox clip newpath\n" +" /ury exch def /urx exch def /lly exch def /llx exch def\n" +" lly %d ury {\n" /* for lly to ury */ +" dup llx exch moveto\n" +" urx exch lineto stroke\n" +" } for\n" +" grestore newpath } def\n", PS_STRIPE); + + /* + * Stack: font string width height factor -> factor + * + * Hack: sometimes, scalefont can't produce a suitable font and just + * gives us something zero-sized, which trips the division. We just + * ignore this case for now. Since maxfont is used in pairs, the + * second one may still succeed. + */ + + fprintf(file, +"/sdiv { dup 0 eq { pop 1 } if div } def\n" +"/maxfont {\n" +" gsave 0 0 moveto\n" +" /f exch def /h exch def /w exch def\n" +" exch f scalefont setfont\n" +" false charpath flattenpath pathbbox\n" +" /ury exch def /urx exch def /lly exch def /llx exch def\n" +" w urx llx sub sdiv h ury lly sub sdiv 2 copy gt { exch } if pop\n" +" f mul grestore } def\n"); + + /* + * Unrotate: - -> - + */ + + fprintf(file, +"/getscale { matrix currentmatrix dup 0 get dup mul exch 1 get dup mul\n" +" add sqrt } def\n"); + + /* + * Stack: string -> string + */ + + fprintf(file, +"/center {\n" +" currentpoint /y exch def /x exch def\n" +" gsave dup false charpath flattenpath pathbbox\n" +" /ury exch def /urx exch def\n" +" /lly exch def /llx exch def\n" +" grestore\n" +" x llx urx add 2 div sub y lly ury add 2 div sub rmoveto } def\n"); + + /* + * Stack: string dist -> string + */ + + fprintf(file, +"/hcenter {\n" +" /off exch def\n" +" gsave matrix setmatrix dup false charpath flattenpath pathbbox\n" +" /ury exch def /urx exch def /lly exch def /llx exch def\n" +" grestore\n" +//" /currscale getscale def\n" +" llx urx sub 2 div\n" +//" off lly sub rmoveto } def\n"); +" off rmoveto } def\n"); + + /* + * Stack: string outline_width -> - + */ + + fprintf(file, +"/showoutlined {\n" +" gsave 2 mul setlinewidth 1 setgray 1 setlinejoin\n" +" dup false charpath flattenpath stroke grestore\n" +" show } def\n"); + + /* + * Stack: string -> string + */ + + fprintf(file, +"/debugbox { gsave dup false charpath flattenpath pathbbox\n" +" /ury exch def /urx exch def /lly exch def /llx exch def\n" +" 0 setgray 100 setlinewidth\n" +" llx lly urx llx sub ury lly sub rectstroke grestore } def\n"); + + /* + * Stack: int -> int + */ + + fprintf(file, +"/originalsize 1 0 matrix currentmatrix idtransform pop def\n" +"/realsize {\n" +" 254 div 72 mul 1000 div 0 matrix currentmatrix idtransform\n" +" dup mul exch dup mul add sqrt\n" +" originalsize div } def\n"); + + /* + * Stack: font string x-size y-size -> - + */ + + fprintf(file, +"/boxfont { 4 copy 1000 maxfont maxfont scalefont setfont } def\n"); + + /* + * Ignore pdfmark. From + * http://www.adobe.com/devnet/acrobat/pdfs/pdfmark_reference.pdf + * Page 10, Example 1.1. + */ + + fprintf(file, +"/pdfmark where { pop }\n" +" { /globaldict where { pop globaldict } { userdict } ifelse" +" /pdfmark /cleartomark load put } ifelse\n"); + + fprintf(file, "%%%%EndProlog\n"); +} + + +static void epilogue(FILE *file) +{ + fprintf(file, "%%%%EOF\n"); +} + + +static int ps_for_all_pkg(FILE *file, + void (*fn)(FILE *file, const struct pkg *pkg, int page), + const char *one) +{ + struct pkg *pkg; + int pages = 0; + + for (pkg = pkgs; pkg; pkg = pkg->next) + if (pkg->name) + if (!one || !strcmp(pkg->name, one)) + pages++; + if (one && !pages) { + fprintf(stderr, "no package \"%s\" to select\n", one); + errno = ENOENT; + return 0; + } + prologue(file, pages); + pages = 0; + for (pkg = pkgs; pkg; pkg = pkg->next) + if (pkg->name) + if (!one || !strcmp(pkg->name, one)) + fn(file, pkg, ++pages); + epilogue(file); + + fflush(file); + return !ferror(file); +} + + +int postscript(FILE *file, const char *one) +{ + return ps_for_all_pkg(file, ps_package, one); +} + + +/* + * Experimental. Doesn't work properly. + */ + +static void ps_package_fullpage(FILE *file, const struct pkg *pkg, int page) +{ + unit_type cx, cy; + struct bbox bbox; + double fx, fy, f; + double w = 2.0*PAGE_HALF_WIDTH; + double h = 2.0*PAGE_HALF_HEIGHT; + int yoff = 0; + + ps_page(file, page, pkg); + active_params = postscript_params; + bbox = inst_get_bbox(pkg); + cx = (bbox.min.x+bbox.max.x)/2; + cy = (bbox.min.y+bbox.max.y)/2; + if (active_params.zoom) { + f = active_params.zoom; + } else { + if (active_params.max_width) + w = active_params.max_width; + fx = w/(bbox.max.x-bbox.min.x); + if (active_params.max_height) + h = active_params.max_height; + if (active_params.show_key) { + yoff = PS_KEY_HEIGTH+PS_KEY_Y_GAP; + h -= yoff; + } + fy = h/(bbox.max.y-bbox.min.y); + f = fx < fy ? fx : fy; + } + fprintf(file, "gsave\n"); + fprintf(file, "%d %d translate\n", (int) (-cx*f), (int) (-cy*f)+yoff); + memset(pad_type_seen, 0, sizeof(pad_type_seen)); + ps_draw_package(file, pkg, f, 0); + fprintf(file, "grestore\n"); + if (active_params.show_key) { + fprintf(file, "gsave 0 %d translate\n", yoff); + ps_keys(file, w, h); + fprintf(file, "grestore\n"); + } + fprintf(file, "showpage\n"); +} + + +int postscript_fullpage(FILE *file, const char *one) +{ + return ps_for_all_pkg(file, ps_package_fullpage, one); +} diff --git a/postscript.h b/postscript.h new file mode 100644 index 0000000..bde649b --- /dev/null +++ b/postscript.h @@ -0,0 +1,35 @@ +/* + * postscript.h - Dump objects in Postscript + * + * Written 2009-2012 by Werner Almesberger + * Copyright 2009-2012 by Werner Almesberger + * + * 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. + */ + + +#ifndef POSTSCRIPT_H +#define POSTSCRIPT_H + +#include <stdio.h> + + +struct postscript_params { + double zoom; /* 0 for auto-zoom */ + double max_width; /* in fped units; 0 for paper width */ + double max_height; /* in fped units; 0 for paper height */ + int show_pad_names; + int show_stuff; /* vecs and frames */ + int label_vecs; + int show_meas; + int show_key; +} postscript_params; + + +int postscript(FILE *file, const char *one); +int postscript_fullpage(FILE *file, const char *one); + +#endif /* !POSTSCRIPT_H */ diff --git a/test/Common b/test/Common new file mode 100755 index 0000000..271064b --- /dev/null +++ b/test/Common @@ -0,0 +1,85 @@ +#!/bin/sh +# +# Common - Elements shared by all regression tests for fped +# +# Written 2010, 2011 by Werner Almesberger +# Copyright 2010, 2011 Werner Almesberger +# +# 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. +# + + +fped() +{ + echo -n "$1: " 1>&2 + shift + cat >_in + $VALGRIND ${FPED:-../fped} -T _in "$@" >_out 2>&1 || { + echo FAILED "($SCRIPT)" 1>&2 + cat _out + rm -f _in _out + exit 1 + } + rm -f _in +} + + +fped_dump() +{ + fped "$@" -T -T +} + + +fped_fail() +{ + echo -n "$1: " 1>&2 + shift + cat >_in + $VALGRIND ${FPED:-../fped} -T _in "$@" >_out 2>&1 && { + echo FAILED "($SCRIPT)" 1>&2 + cat _out + rm -f _in _out + exit 1 + } + rm -f _in +} + + +expect() +{ + diff -u - "$@" _out >_diff || { + echo FAILED "($SCRIPT)" 1>&2 + cat _diff 1>&2 + rm -f _out _diff + exit 1 + } + echo PASSED 1>&2 + rm -f _out _diff + passed=`expr ${passed:-0} + 1` +} + + +expect_grep() +{ + grep "$1" <_out >_tmp || exit 1 + mv _tmp _out + shift + expect "$@" +} + + +expect_sed() +{ + sed "$1" <_out >_tmp || exit 1 + mv _tmp _out + shift + expect "$@" +} + + +if [ ! -z "$CWD_PREFIX" -a ! -z "$FPED" -a "$FPED" = "${FPED#/}" ]; then + FPED="$CWD_PREFIX/$FPED" +fi diff --git a/test/dbg_meas b/test/dbg_meas new file mode 100755 index 0000000..87312d8 --- /dev/null +++ b/test/dbg_meas @@ -0,0 +1,54 @@ +#!/bin/sh +. ./Common + +############################################################################### + +fped "%meas: print mm (default)" <<EOF +a: vec @(0mm, 0mm) +b: vec @(3mm, 4mm) +meas a >> b /* work-around to simplify grammar */ +m: meas a >> b +%meas m +EOF +expect <<EOF +5 +EOF + +#------------------------------------------------------------------------------ + +fped "%meas: print mil" <<EOF +unit mil +a: vec @(0mm, 0mm) +b: vec @(2.54mm, 0mm) +meas a >> b /* work-around to simplify grammar */ +m: meas a >> b +%meas m +EOF +expect <<EOF +100 +EOF + +#------------------------------------------------------------------------------ + +fped_fail "%meas: invalid ID" <<EOF +%meas m +EOF +expect <<EOF +1: unknown object "m" near "m" +EOF + +#------------------------------------------------------------------------------ + +fped_fail "%meas: measurement not instantiated" <<EOF +a: vec @(0mm, 0mm) +loop i = 1, 0 +b: vec @(i*1mm, 0mm) +meas a >> b /* work-around to simplify grammar */ +m: meas a >> b +%meas m +EOF +expect <<EOF +measurement "m" was not instantiated +EOF + +############################################################################### diff --git a/test/del_frame b/test/del_frame new file mode 100755 index 0000000..25ad2f1 --- /dev/null +++ b/test/del_frame @@ -0,0 +1,97 @@ +#!/bin/sh +. ./Common + +############################################################################### + +fped_fail "delete frame: can't self-destruct" <<EOF +frame f { + %del f +} +EOF +expect <<EOF +3: a frame can't delete itself near "}" +EOF + +#------------------------------------------------------------------------------ + +fped_dump "delete frame: content disappears" <<EOF +frame f { + vec @(0mm, 0mm) +} + +%del f +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +package "_" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "delete frame: references disappear" <<EOF +frame f { + vec @(0mm, 0mm) +} + +frame f @ + +%del f +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +package "_" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "delete frame: measurements disappear" <<EOF +frame f { + v: vec @(0mm, 0mm) +} + +frame f @ +meas f.v -> f.v + +%del f +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +package "_" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "delete frame: measurements with qualifiers disappear" <<EOF +frame f { + v: vec @(0mm, 0mm) +} + +frame g { frame f @ } + +frame g @ +meas g/f.v -> f.v + +%del g +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame f { + v: vec @(0mm, 0mm) +} + +package "_" +unit mm + +EOF + +############################################################################### diff --git a/test/del_vec b/test/del_vec new file mode 100755 index 0000000..8bb35bb --- /dev/null +++ b/test/del_vec @@ -0,0 +1,70 @@ +#!/bin/sh +. ./Common + +############################################################################### + +fped_dump "delete vector: it disappears" <<EOF +v: vec @(0mm, 0mm) +%del v +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +package "_" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "delete vector: references disappear" <<EOF +v: vec @(0mm, 0mm) +line v v +%del v +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +package "_" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "delete vector: measurements disappear (same frame)" <<EOF +v: vec @(0mm, 0mm) +meas v -> v +%del v +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +package "_" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "delete vector: measurements disappear (other frame)" <<EOF +frame f { + v: vec @(0mm, 0mm) +} +frame f @ +meas f.v -> f.v +%del f.v +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame f { +} + +package "_" +unit mm + +frame f @ +EOF + +############################################################################### diff --git a/test/floor b/test/floor new file mode 100755 index 0000000..e0c399b --- /dev/null +++ b/test/floor @@ -0,0 +1,40 @@ +#!/bin/sh +. ./Common + +############################################################################### + +fped "floor: 4.7mm" <<EOF +%print floor(4.7mm) +EOF +expect <<EOF +4mm +EOF + +#------------------------------------------------------------------------------ + +fped "floor: -1.2m" <<EOF +%print floor(-1.2) +EOF +expect <<EOF +-2 +EOF + +#------------------------------------------------------------------------------ + +fped "floor: round 7 mil (0.1778 mm) to two digits in mm" <<EOF +%print floor(7mil/0.01mm+0.5)*0.01mm +EOF +expect <<EOF +0.18mm +EOF + +#------------------------------------------------------------------------------ + +fped "floor: round 12 mil (0.3048 mm) to two digits in mm" <<EOF +%print floor(12mil/0.01mm+0.5)*0.01mm +EOF +expect <<EOF +0.3mm +EOF + +############################################################################### diff --git a/test/frame_ref b/test/frame_ref new file mode 100755 index 0000000..85ef8e9 --- /dev/null +++ b/test/frame_ref @@ -0,0 +1,149 @@ +#!/bin/sh +. ./Common + +############################################################################### + +fped_dump "frame reference: \"frame\" (origin)" <<EOF +frame f {} +frame f @ +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame f { +} + +package "_" +unit mm + +frame f @ +EOF + +#------------------------------------------------------------------------------ + +fped_dump "frame reference: \"%frame\" (current frame origin)" <<EOF +frame f {} +%frame f @ +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame f { +} + +package "_" +unit mm + +frame f @ +EOF + +#------------------------------------------------------------------------------ + +fped_dump "frame reference: \"%frame\" (current frame vector)" <<EOF +frame f {} +v: vec @(0mm, 0mm) +%frame f v +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame f { +} + +package "_" +unit mm + +v: vec @(0mm, 0mm) +frame f . +EOF + +#------------------------------------------------------------------------------ + +fped_dump "frame reference: \"%frame\" (other frame origin)" <<EOF +frame f {} +frame g {} +%frame f g.@ +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame f { +} + +frame g { + frame f @ +} + +package "_" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "frame reference: \"%frame\" (other frame base)" <<EOF +frame f {} +frame g { + v: vec @(0mm, 0mm) +} +%frame f g.v +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame f { +} + +frame g { + v: vec @(0mm, 0mm) + frame f . +} + +package "_" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_fail "frame reference: \"%frame\" (cycle)" <<EOF +frame f { +} + +frame g { + frame f @ +} + +%frame g f.@ +EOF +expect <<EOF +8: frame "g" is a parent of "f" near "@" +EOF + +#------------------------------------------------------------------------------ + +fped_dump "frame reference: \"%frame\" (out-of-order)" <<EOF +frame f { +} + +frame g { +} + +%frame g f.@ +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame g { +} + +frame f { + frame g @ +} + +package "_" +unit mm + +EOF + +############################################################################### diff --git a/test/iprint b/test/iprint new file mode 100755 index 0000000..84c1ca2 --- /dev/null +++ b/test/iprint @@ -0,0 +1,101 @@ +#!/bin/sh +. ./Common + +############################################################################### + +fped "iprint: loop" <<EOF +loop x = 1, 3 +%iprint x +EOF +expect <<EOF +1 +2 +3 +EOF + +#------------------------------------------------------------------------------ + +fped "iprint: two tables (independent)" <<EOF +table { a } { 1 } { 2 } +table { b } { 3 } { 4 } + +%iprint a*10+b +EOF +expect <<EOF +13 +14 +23 +24 +EOF + +#------------------------------------------------------------------------------ + +fped "iprint: two tables (2nd references 1st)" <<EOF +table { a } { 1 } { 2 } +table { b } { 3+a } { 4+a } + +%iprint a*10+b +EOF +expect <<EOF +14 +15 +25 +26 +EOF + +#------------------------------------------------------------------------------ + +fped "iprint: two tables (1st references 2nd)" <<EOF +table { a } { 1+b } { 2+b } +table { b } { 3 } { 4 } + +%iprint a*10+b +EOF +expect <<EOF +43 +54 +53 +64 +EOF + +#------------------------------------------------------------------------------ + +fped "iprint: inside frame (global variable)" <<EOF +frame foo { + %iprint n +} + +loop n = 1, 2 +frame foo @ +EOF +expect <<EOF +1 +2 +EOF + +#------------------------------------------------------------------------------ + +fped "iprint: inside frame (local variable) " <<EOF +frame foo { + set n1 = n+1 + %iprint n1 +} + +loop n = 1, 2 +frame foo @ +EOF +expect <<EOF +2 +3 +EOF + +#------------------------------------------------------------------------------ + +fped_fail "iprint: undefined variable" <<EOF +%iprint foo +EOF +expect <<EOF +undefined variable "foo" +EOF + +############################################################################### diff --git a/test/keys b/test/keys new file mode 100755 index 0000000..77a4314 --- /dev/null +++ b/test/keys @@ -0,0 +1,134 @@ +#!/bin/sh +. ./Common + +############################################################################### + +fped "keys: tables, master before slave" <<EOF +table { a, eng } { 1, "one" } { 2, "two" } +table { ?a, ger } { 1, "eins" } { 2, "zwei" } + +%iprint eng +%iprint ger +EOF +expect <<EOF +one +eins +two +zwei +EOF + +#------------------------------------------------------------------------------ + +fped "keys: tables, master after slave" <<EOF +table { ?a, eng } { 1, "one" } { 2, "two" } +table { a, spa } { 1, "uno" } { 2, "dos" } + +%iprint eng +%iprint spa +EOF +expect <<EOF +one +uno +two +dos +EOF + +#------------------------------------------------------------------------------ + +fped_fail "keys: tables, slaves without master" <<EOF +table { ?a, eng } { 1, "one" } { 2, "two" } +table { ?a, lat } { 1, "unum" } { 2, "duo" } + +%iprint eng +%iprint lat +EOF +expect <<EOF +undefined variable "a" +EOF + +#------------------------------------------------------------------------------ + +fped_fail "keys: tables, both masters" <<EOF +table { a, eng } { 1, "one" } { 2, "two" } +table { a, lat } { 1, "unum" } { 2, "duo" } + +%iprint eng +%iprint lat +EOF +expect <<EOF +2: duplicate variable "a" near "a" +EOF + +#------------------------------------------------------------------------------ + +fped "keys: master is single variable, slave is table" <<EOF +set n = 2 +table { ?n, square } { 1, 1 } { 2, 4 } { 3, 9 } { 4, 16 } + +%iprint square +EOF +expect <<EOF +4 +EOF + +#------------------------------------------------------------------------------ + +fped "keys: master is table, slave is single variable" <<EOF +table { n, cube } { 1, 1 } { 2, 8 } { 3, 27 } { 4, 64 } +set ?n = 3 + +%iprint cube +EOF +expect <<EOF +27 +EOF + +#------------------------------------------------------------------------------ + +fped "keys: master is loop, slave is table" <<EOF +loop n = 1, 3 +table { ?n, sqr } { 1, 1 } { 2, 4 } { 3, 9 } { 4, 16 } + +%iprint sqr +EOF +expect <<EOF +1 +4 +9 +EOF + +#------------------------------------------------------------------------------ + +fped "keys: two keys" <<EOF +table { a, an } { 1, "one" } { 2, "two" } +table { b, bn } { 3, "three" } { 4, "four" } { 5, "five" } +table { ?a, ?b, sum } + { 1, 3, "four" } + { 2, 4, "six" } + { 3, 4, "seven" } + +%iprint sum +EOF +expect <<EOF +four +six +EOF + +#------------------------------------------------------------------------------ + +fped "keys: key set by outer frame" <<EOF +frame tab { + table { sqrt, ?n } { 1, 1 } { 2, 4 } { 3, 9 } { 4, 16 } { 5, 25 } + %iprint sqrt +} + +table { n } { 25 } { 9 } + +frame tab @ +EOF +expect <<EOF +5 +3 +EOF + +############################################################################### diff --git a/test/meas_qual b/test/meas_qual new file mode 100755 index 0000000..cad9931 --- /dev/null +++ b/test/meas_qual @@ -0,0 +1,232 @@ +#!/bin/sh +. ./Common + +############################################################################### + +fped_dump "qualified measurements: no qualifier" <<EOF +frame c { v: vec @(0mm, 0mm) } +frame b { frame c @ } +frame a { frame b @ } +frame a @ +meas c.v >> c.v +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame c { + v: vec @(0mm, 0mm) +} + +frame b { + frame c @ +} + +frame a { + frame b @ +} + +package "_" +unit mm + +frame a @ +meas c.v >> c.v +EOF + +#------------------------------------------------------------------------------ + +fped_dump "qualified measurements: fully qualified" <<EOF +frame c { v: vec @(0mm, 0mm) } +frame b { frame c @ } +frame a { frame b @ } +frame a @ +meas a/b/c.v >> c.v +EOF +expect_grep '^meas' <<EOF +meas a/b/c.v >> c.v +EOF + +#------------------------------------------------------------------------------ + +fped_dump "qualified measurements: partially qualified" <<EOF +frame c { v: vec @(0mm, 0mm) } +frame b { frame c @ } +frame a { frame b @ } +frame a @ +meas a/c.v >> c.v +EOF +expect_grep '^meas' <<EOF +meas a/c.v >> c.v +EOF + +#------------------------------------------------------------------------------ + +fped_dump "qualified measurements: wrong order" <<EOF +frame c { v: vec @(0mm, 0mm) } +frame b { frame c @ } +frame a { frame b @ } +frame a @ +meas b/a/c.v >> c.v +EOF +expect_grep 'warning' <<EOF +5: warning: not all qualifiers can be reached near "v" +EOF + +#------------------------------------------------------------------------------ + +fped_dump "qualified measurements: unlinked frame" <<EOF +frame c { v: vec @(0mm, 0mm) } +frame b { frame c @ } +frame a { frame b @ } +frame x {} +frame a @ +frame x @ +meas a/c.v >> x/c.v +EOF +expect_grep 'warning' <<EOF +7: warning: not all qualifiers can be reached near "v" +EOF + +#------------------------------------------------------------------------------ + +fped_fail "qualified measurements: duplicate qualifier" <<EOF +frame c { v: vec @(0mm, 0mm) } +frame b { frame c @ } +frame a { frame b @ } +frame a @ +meas b/b/c.v >> c.v +EOF +expect <<EOF +5: duplicate qualifier "b" near "v" +EOF + +#------------------------------------------------------------------------------ + +fped "qualified measurements: \"macro\" unqualified" <<EOF +frame x { + a: vec @(0mm, 0mm) + b: vec .(d, 0mm) +} +frame a { + set d = 2mm + frame x @ +} +frame b { + set d = 3mm + frame x @ +} +frame a @ +vec @(1mm, 0mm) +frame b . +meas x.a >> x.b /* dummy */ +m: meas x.a >> x.b +%meas m +EOF +expect <<EOF +4 +EOF + +#------------------------------------------------------------------------------ + +fped "qualified measurements: \"macro\" qualified (a)" <<EOF +frame x { + a: vec @(0mm, 0mm) + b: vec .(d, 0mm) +} +frame a { + set d = 2mm + frame x @ +} +frame b { + set d = 3mm + frame x @ +} +frame a @ +vec @(1mm, 0mm) +frame b . +meas x.a >> x.b /* dummy */ +m: meas a/x.a >> a/x.b +%meas m +EOF +expect <<EOF +2 +EOF + +#------------------------------------------------------------------------------ + +fped "qualified measurements: \"macro\" qualified (b)" <<EOF +frame x { + a: vec @(0mm, 0mm) + b: vec .(d, 0mm) +} +frame a { + set d = 2mm + frame x @ +} +frame b { + set d = 3mm + frame x @ +} +frame a @ +vec @(1mm, 0mm) +frame b . +meas x.a >> x.b /* dummy */ +m: meas b/x.a >> b/x.b +%meas m +EOF +expect <<EOF +3 +EOF + +#------------------------------------------------------------------------------ + +fped "qualified measurements: \"macro\" qualified (a/b)" <<EOF +frame x { + a: vec @(0mm, 0mm) + b: vec .(d, 0mm) +} +frame a { + set d = 2mm + frame x @ +} +frame b { + set d = 3mm + frame x @ +} +frame a @ +vec @(1mm, 0mm) +frame b . +meas x.a >> x.b /* dummy */ +m: meas a/x.a >> b/x.b +%meas m +EOF +expect <<EOF +4 +EOF + +#------------------------------------------------------------------------------ + +fped "qualified measurements: \"macro\" qualified (b/a)" <<EOF +frame x { + a: vec @(0mm, 0mm) + b: vec .(d, 0mm) +} +frame a { + set d = 2mm + frame x @ +} +frame b { + set d = 3mm + frame x @ +} +frame a @ +vec @(1mm, 0mm) +frame b . +meas x.a >> x.b /* dummy */ +m: meas b/x.a >> a/x.b +%meas m +EOF +expect <<EOF +1 +EOF + +############################################################################### diff --git a/test/structure b/test/structure new file mode 100755 index 0000000..bcb124b --- /dev/null +++ b/test/structure @@ -0,0 +1,111 @@ +#!/bin/sh +. ./Common + +############################################################################### + +fped_dump "structure: empty file" <<EOF +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +package "_" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "structure: just an empty frame definition" <<EOF +frame foo { +} +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame foo { +} + +package "_" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "structure: just the package name" <<EOF +package "hello" +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +package "hello" +unit mm + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "structure: just the unit" <<EOF +unit mil +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +package "_" +unit mil + +EOF + +#------------------------------------------------------------------------------ + +fped_dump "structure: just one root frame item" <<EOF +vec @(1mm, 1mm) +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +package "_" +unit mm + +__0: vec @(1mm, 1mm) +EOF + +#------------------------------------------------------------------------------ + +fped_dump "structure: frame plus measurement" <<EOF +frame f { + a: vec @(0mm, 0mm) + b: vec @(1mm, 1mm) +} +frame f @ +meas f.a -> f.b +EOF +expect <<EOF +/* MACHINE-GENERATED ! */ + +frame f { + a: vec @(0mm, 0mm) + b: vec @(1mm, 1mm) +} + +package "_" +unit mm + +frame f @ +meas f.a -> f.b +EOF + +#------------------------------------------------------------------------------ + +fped_fail "structure: measurement in frame" <<EOF +frame f { + a: vec @(0mm, 0mm) + b: vec @(1mm, 1mm) + meas f.a -> f.b +} +EOF +expect <<EOF +4: syntax error near "meas" +EOF + +############################################################################### diff --git a/test/tsort b/test/tsort new file mode 100755 index 0000000..a4b4108 --- /dev/null +++ b/test/tsort @@ -0,0 +1,137 @@ +#!/bin/sh +. ./Common + +############################################################################### + +fped "tsort: total order" <<EOF +%tsort { + a b + a c + a d + b c + b d + c d +} +EOF +expect <<EOF +a +b +c +d +EOF + +#------------------------------------------------------------------------------ + +fped "tsort: partial order change (1)" <<EOF +%tsort { + a b + a c + a d + d b +} +EOF +expect <<EOF +a +c +d +b +EOF + +#------------------------------------------------------------------------------ + +fped "tsort: partial order change (2)" <<EOF +%tsort { + b c + c d + a b +} +EOF +expect <<EOF +a +b +c +d +EOF + +#------------------------------------------------------------------------------ + +fped "tsort: old order differs from resolution order" <<EOF +%tsort { + +a +b +c +d + a c + a b + a d +} +EOF +expect <<EOF +a +b +c +d +EOF + +#------------------------------------------------------------------------------ + +fped "tsort: order change due to priority" <<EOF +%tsort { + a b + a c 1 + a d +} +EOF +expect <<EOF +a +c +b +d +EOF + +#------------------------------------------------------------------------------ + +fped "tsort: priority accumulation without decay" <<EOF +%tsort { + +a +b +c +d + a b 1 + a d 1 +} +EOF +expect <<EOF +a +b +d +c +EOF + +#------------------------------------------------------------------------------ + +fped "tsort: priority accumulation with decay" <<EOF +%tsort { + +a -b +c +d + a b 1 + a d 1 +} +EOF +expect <<EOF +a +b +c +d +EOF + +#------------------------------------------------------------------------------ + +fped_fail "tsort: cycle" <<EOF +%tsort { + a b + b a +} +EOF +expect_sed '/Aborted/d' <<EOF +cycle detected in partial order +EOF + +# The "Aborted" can be reported with or without "(core dumped)", and sometimes +# not at all. So we just remove it. We already know that tsort has detected +# the problem. + +############################################################################### @@ -0,0 +1,162 @@ +/* + * tsort.c - Topological sort + * + * Written 2010 by Werner Almesberger + * Copyright 2010 by Werner Almesberger + * + * 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. + */ + +/* + * We use a slight variation of Kahn's algorithm. The difference is that we add + * a priority. Edges with the highest priority get selected before edges with + * lower priority. + * + * We maintain the initial list of nodes in the order in which they were added. + * Therefore, the first node with inbound edges will always be sorted first. + * E.g., the root frame. + * + * add_node and add_edge can be invoked multiple times with the same + * parameters. In the case of add_node, simply the existing node is returned. + * In the case of add_edge, the new edge's priority is added to the priority of + * the previous edges. + * + * Priority is accumulated in a node until the node is output. If a node has + * the "decay" flag set, it resets the priorities of all other nodes when + * output. E.g., when outputting a vector, all priorities accumulated from + * previous vectors (towards referencing them with ".") lose their effect. + * + * Last but not least, the algorithm is stable: a pre-existing order that + * conflicts neither with the partial order nor the priorities is preserved. + * + * Thus, we have the following sorting criteria, in decreasing importance: + * - the destination if an edge never precedes its origin + * - higher priority comes before lower priority + * - earlier add_node comes before later + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <limits.h> + +#include "util.h" +#include "tsort.h" + + +struct edge { + struct node *to; + int priority; /* edge priority */ + struct edge *next; +}; + +struct node { + void *user; + struct edge *edges; /* outbound edges */ + int incoming; /* number of incoming edges */ + int priority; /* cumulative node priority */ + int decay; /* all node prio. decay after issuing this */ + struct node *next; +}; + +struct tsort { + struct node *nodes; + struct node **next_node; + int n_nodes; +}; + + +void add_edge(struct node *from, struct node *to, int priority) +{ + struct edge **edge; + + for (edge = &from->edges; *edge; edge = &(*edge)->next) + if ((*edge)->to == to) { + (*edge)->priority += priority; + return; + } + *edge = alloc_type(struct edge); + (*edge)->to = to; + (*edge)->priority = priority; + (*edge)->next = NULL; + to->incoming++; +} + + +struct node *add_node(struct tsort *tsort, void *user, int decay) +{ + struct node *node; + + for (node = tsort->nodes; node; node = node->next) + if (node->user == user) + return node; + node = alloc_type(struct node); + node->user = user; + node->edges = NULL; + node->incoming = 0; + node->priority = 0; + node->decay = decay; + node->next = NULL; + *tsort->next_node = node; + tsort->next_node = &node->next; + tsort->n_nodes++; + return node; +} + + +struct tsort *begin_tsort(void) +{ + struct tsort *tsort; + + tsort = alloc_type(struct tsort); + tsort->nodes = NULL; + tsort->next_node = &tsort->nodes; + tsort->n_nodes = 0; + return tsort; +} + + +void **end_tsort(struct tsort *tsort) +{ + struct node **walk, **first, *node; + struct edge *edge; + void **res; + int n = 0; + + res = alloc_size(sizeof(void *)*(tsort->n_nodes+1)); + while (1) { + first = NULL; + for (walk = &tsort->nodes; *walk; walk = &(*walk)->next) { + if ((*walk)->incoming) + continue; + if (!first || (*first)->priority < (*walk)->priority) + first = walk; + } + if (!first) + break; + if ((*first)->decay) + for (node = tsort->nodes; node; node = node->next) + node->priority = 0; + node = *first; + *first = node->next; + res[n++] = node->user; + while (node->edges) { + edge = node->edges; + edge->to->incoming--; + edge->to->priority += edge->priority; + node->edges = edge->next; + free(edge); + } + free(node); + } + if (tsort->nodes) { + fprintf(stderr, "cycle detected in partial order\n"); + abort(); + } + free(tsort); + res[n] = NULL; + return res; +} @@ -0,0 +1,25 @@ +/* + * tsort.h - Topological sort + * + * Written 2010 by Werner Almesberger + * Copyright 2010 by Werner Almesberger + * + * 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. + */ + +#ifndef TSORT_H +#define TSORT_H + +struct node; +struct tsort; + +struct node *add_node(struct tsort *tsort, void *user, int decay); +void add_edge(struct node *from, struct node *to, int priority); + +struct tsort *begin_tsort(void); +void **end_tsort(struct tsort *tsort); + +#endif /* !TSORT_H */ diff --git a/unparse.c b/unparse.c new file mode 100644 index 0000000..d7b7202 --- /dev/null +++ b/unparse.c @@ -0,0 +1,136 @@ +/* + * unparse.c - Dump an expression tree into a string + * + * Written 2009, 2012 by Werner Almesberger + * Copyright 2009, 2012 by Werner Almesberger + * + * 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 is crazily inefficient but who cares :-) + */ + +#include <stdlib.h> +#include <stdio.h> + +#include "util.h" +#include "expr.h" +#include "unparse.h" + + +enum prec { + prec_add, + prec_mult, + prec_unary, + prec_primary, +}; + + +static int precedence(op_type op) +{ + if (op == op_add || op == op_sub) + return prec_add; + if (op == op_mult || op == op_div) + return prec_mult; + if (op == op_minus) + return prec_unary; + if (op == op_num || op == op_string || op == op_var || + op == op_sin || op == op_cos || op == op_sqrt || op == op_floor) + return prec_primary; + abort(); +} + + +static char *merge3(char *a, const char *op, char *b) +{ + char *buf; + + buf = alloc_size(strlen(op)+strlen(a)+strlen(b)+1); + sprintf(buf, "%s%s%s", a, op, b); + free(a); + free(b); + return buf; +} + + +static char *merge2(const char *op, char *a) +{ + char *buf; + + buf = alloc_size(strlen(op)+strlen(a)+1); + sprintf(buf, "%s%s", op, a); + free(a); + return buf; +} + + +static char *unparse_op(const struct expr *expr, enum prec prec); + + +static char *unparse_fn(const char *name, const struct expr *expr) +{ + char *buf, *tmp; + + tmp = unparse_op(expr->u.op.a, prec_add); + buf = alloc_size(strlen(name)+strlen(tmp)+3); + sprintf(buf, "%s(%s)", name, tmp); + free(tmp); + return buf; +} + + +static char *unparse_op(const struct expr *expr, enum prec prec) +{ + char tmp[100]; + char *buf, *temp; + + if (prec > precedence(expr->op)) { + temp = unparse_op(expr, prec_add); + buf = alloc_size(strlen(temp)+3); + sprintf(buf, "(%s)", temp); + free(temp); + return buf; + } + if (expr->op == op_num) { + snprintf(tmp, sizeof(tmp), "%lg%s", + expr->u.num.n, str_unit(expr->u.num)); + return stralloc(tmp); + } + if (expr->op == op_string) + return stralloc_printf("\"%s\"", expr->u.str); + if (expr->op == op_var) + return stralloc(expr->u.var); + if (expr->op == op_minus) + return merge2("-", unparse_op(expr->u.op.a, prec_unary)); + if (expr->op == op_add) + return merge3(unparse_op(expr->u.op.a, prec_add), "+", + unparse_op(expr->u.op.b, prec_add)); + if (expr->op == op_sub) + return merge3(unparse_op(expr->u.op.a, prec_add), "-", + unparse_op(expr->u.op.b, prec_mult)); + if (expr->op == op_mult) + return merge3(unparse_op(expr->u.op.a, prec_mult), "*", + unparse_op(expr->u.op.b, prec_mult)); + if (expr->op == op_div) + return merge3(unparse_op(expr->u.op.a, prec_mult), "/", + unparse_op(expr->u.op.b, prec_primary)); + if (expr->op == op_sin) + return unparse_fn("sin", expr); + if (expr->op == op_cos) + return unparse_fn("cos", expr); + if (expr->op == op_sqrt) + return unparse_fn("sqrt", expr); + if (expr->op == op_floor) + return unparse_fn("floor", expr); + abort(); +} + + +char *unparse(const struct expr *expr) +{ + return expr ? unparse_op(expr, prec_add) : stralloc(""); +} diff --git a/unparse.h b/unparse.h new file mode 100644 index 0000000..8eaf7ab --- /dev/null +++ b/unparse.h @@ -0,0 +1,22 @@ +/* + * unparse.h - Dump an expression tree into a string + * + * Written 2009 by Werner Almesberger + * Copyright 2009 by Werner Almesberger + * + * 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. + */ + + +#ifndef UNPARSE_H +#define UNPARSE_H + +#include "expr.h" + + +char *unparse(const struct expr *expr); + +#endif /* !UNPARSE_H */ @@ -0,0 +1,114 @@ +/* + * util.c - Common utility functions + * + * Written 2009 by Werner Almesberger + * Copyright 2009 by Werner Almesberger + * + * 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. + */ + + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include "util.h" + + + +/* ----- printf buffer allocation ------------------------------------------ */ + + +char *stralloc_vprintf(const char *fmt, va_list ap) +{ + va_list aq; + char *buf; + int n; + + va_copy(aq, ap); + n = vsnprintf(NULL, 0, fmt, aq); + va_end(aq); + buf = alloc_size(n+1); + vsnprintf(buf, n+1, fmt, ap); + return buf; +} + + +char *stralloc_printf(const char *fmt, ...) +{ + va_list ap; + char *s; + + va_start(ap, fmt); + s = stralloc_vprintf(fmt, ap); + va_end(ap); + return s; +} + + +/* ----- identifier syntax check ------------------------------------------- */ + + +int is_id_char(char c, int first) +{ + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') + return 1; + if (first) + return 0; + return c >= '0' && c <= '9'; +} + + +int is_id(const char *s) +{ + const char *p; + + if (!*s) + return 0; + for (p = s; *p; p++) + if (!is_id_char(*p, s == p)) + return 0; + return 1; +} + + +/* ----- unique identifiers ------------------------------------------------ */ + + +static struct unique { + char *s; + struct unique *next; +} *uniques = NULL; + + +/* @@@ consider using rb trees */ + +const char *unique(const char *s) +{ + struct unique **walk; + + for (walk = &uniques; *walk; walk = &(*walk)->next) + if (!strcmp(s, (*walk)->s)) + return (*walk)->s; + *walk = alloc_type(struct unique); + (*walk)->s = stralloc(s); + (*walk)->next = NULL; + return (*walk)->s; +} + + +void unique_cleanup(void) +{ + struct unique *next; + + while (uniques) { + next = uniques->next; + free(uniques->s); + free(uniques); + uniques = next; + } +} + @@ -0,0 +1,70 @@ +/* + * util.h - Common utility functions + * + * Written 2006, 2009, 2010 by Werner Almesberger + * Copyright 2006, 2009, 2010 Werner Almesberger + * + * 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. + */ + + +#ifndef UTIL_H +#define UTIL_H + +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + + +#define alloc_size(s) \ + ({ void *alloc_size_tmp = malloc(s); \ + if (!alloc_size_tmp) \ + abort(); \ + alloc_size_tmp; }) + +#define alloc_type(t) ((t *) alloc_size(sizeof(t))) + +#define zalloc_size(s) \ + ({ void *zalloc_size_tmp = alloc_size(s); \ + memset(zalloc_size_tmp, 0, (s)); \ + zalloc_size_tmp; }) + +#define zalloc_type(t) \ + ({ t *zalloc_type_tmp = alloc_type(t); \ + memset(zalloc_type_tmp, 0, sizeof(t)); \ + zalloc_type_tmp; }) + +#define stralloc(s) \ + ({ char *stralloc_tmp = strdup(s); \ + if (!stralloc_tmp) \ + abort(); \ + stralloc_tmp; }) + +#define strnalloc(s, n) \ + ({ char *strnalloc_tmp = alloc_size((n)+1); \ + if (!strnalloc_tmp) \ + abort(); \ + strncpy(strnalloc_tmp, (s), (n)); \ + strnalloc_tmp[n] = 0; \ + strnalloc_tmp; }) + +#define SWAP(a, b) \ + ({ typeof(a) SWAP_tmp = (a); \ + (a) = (b); \ + (b) = SWAP_tmp; }) + + +char *stralloc_vprintf(const char *fmt, va_list ap); +char *stralloc_printf(const char *fmt, ...) + __attribute__((format(printf, 1, 2))); + +int is_id_char(char c, int first); +int is_id(const char *s); + +const char *unique(const char *s); +void unique_cleanup(void); + +#endif /* !UTIL_H */ |