summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXiangfu Liu <xiangfu@openmobilefree.net>2012-10-08 05:38:57 +0200
committerXiangfu Liu <xiangfu@openmobilefree.net>2012-10-08 05:38:57 +0200
commit4edcd19776f36123e6bcb7581b6863af719da84b (patch)
tree7d11bc6c0272a4a9c3499d544f36e00ab6474a28
Import fped_0.1+201210.orig.tar.gz
[dgit import orig fped_0.1+201210.orig.tar.gz]
-rw-r--r--.gitignore3
-rw-r--r--COPYING.GPLv2339
-rw-r--r--Makefile195
-rw-r--r--README749
-rw-r--r--TODO80
-rw-r--r--bitset.c133
-rw-r--r--bitset.h34
-rw-r--r--coord.c250
-rw-r--r--coord.h98
-rw-r--r--cpp.c217
-rw-r--r--cpp.h26
-rw-r--r--delete.c705
-rw-r--r--delete.h32
-rw-r--r--dump.c625
-rw-r--r--dump.h49
-rw-r--r--error.c83
-rw-r--r--error.h34
-rw-r--r--examples/fbga.fpd56
-rw-r--r--examples/meas.fpd24
-rw-r--r--examples/qfn.fpd73
-rw-r--r--examples/quad.fpd15
-rw-r--r--examples/sc89.fpd80
-rw-r--r--examples/tab.fpd16
-rw-r--r--expr.c677
-rw-r--r--expr.h154
-rw-r--r--file.c215
-rw-r--r--file.h37
-rw-r--r--fpd.h36
-rw-r--r--fpd.l204
-rw-r--r--fpd.y1288
-rw-r--r--fped.151
-rw-r--r--fped.c274
-rw-r--r--fped.h23
-rw-r--r--gnuplot.c195
-rw-r--r--gnuplot.h22
-rw-r--r--gui.c430
-rw-r--r--gui.h38
-rw-r--r--gui.html282
-rw-r--r--gui_canvas.c589
-rw-r--r--gui_canvas.h45
-rw-r--r--gui_frame.c1875
-rw-r--r--gui_frame.h36
-rw-r--r--gui_frame_drag.c589
-rw-r--r--gui_frame_drag.h30
-rw-r--r--gui_inst.c668
-rw-r--r--gui_inst.h55
-rw-r--r--gui_meas.c470
-rw-r--r--gui_meas.h30
-rw-r--r--gui_over.c215
-rw-r--r--gui_over.h79
-rw-r--r--gui_status.c1117
-rw-r--r--gui_status.h91
-rw-r--r--gui_style.c93
-rw-r--r--gui_style.h130
-rw-r--r--gui_tool.c1241
-rw-r--r--gui_tool.h84
-rw-r--r--gui_util.c399
-rw-r--r--gui_util.h84
-rw-r--r--hole.c86
-rw-r--r--hole.h18
-rw-r--r--icons/all.fig22
-rw-r--r--icons/all_off.fig22
-rw-r--r--icons/arc.fig13
-rw-r--r--icons/bright.fig24
-rw-r--r--icons/bright_off.fig18
-rw-r--r--icons/circ.fig12
-rw-r--r--icons/delete.fig15
-rw-r--r--icons/delete_off.fig15
-rw-r--r--icons/frame.fig16
-rw-r--r--icons/hole.fig22
-rw-r--r--icons/line.fig13
-rw-r--r--icons/meas.fig19
-rw-r--r--icons/meas_off.fig19
-rw-r--r--icons/meas_x.fig23
-rw-r--r--icons/meas_y.fig23
-rw-r--r--icons/pad.fig15
-rw-r--r--icons/point.fig14
-rw-r--r--icons/rect.fig13
-rw-r--r--icons/rpad.fig17
-rw-r--r--icons/stuff.fig17
-rw-r--r--icons/stuff_off.fig17
-rw-r--r--icons/template.fig11
-rw-r--r--icons/vec.fig17
-rw-r--r--inst.c1423
-rw-r--r--inst.h221
-rw-r--r--kicad.c327
-rw-r--r--kicad.h22
-rw-r--r--layer.c196
-rw-r--r--layer.h84
-rw-r--r--leak.supp31
-rwxr-xr-xleakcheck4
-rw-r--r--manual/concept-inst.fig35
-rw-r--r--manual/intro-1.pngbin0 -> 4561 bytes
-rw-r--r--manual/intro-2.pngbin0 -> 4544 bytes
-rw-r--r--manual/intro-3.pngbin0 -> 3700 bytes
-rw-r--r--manual/intro-4.pngbin0 -> 461 bytes
-rw-r--r--manual/intro-5.pngbin0 -> 1217 bytes
-rw-r--r--manual/intro-6.pngbin0 -> 19348 bytes
-rw-r--r--meas.c326
-rw-r--r--meas.h82
-rw-r--r--obj.c604
-rw-r--r--obj.h283
-rw-r--r--overlap.c226
-rw-r--r--overlap.h45
-rw-r--r--postscript.c1234
-rw-r--r--postscript.h35
-rwxr-xr-xtest/Common85
-rwxr-xr-xtest/dbg_meas54
-rwxr-xr-xtest/del_frame97
-rwxr-xr-xtest/del_vec70
-rwxr-xr-xtest/floor40
-rwxr-xr-xtest/frame_ref149
-rwxr-xr-xtest/iprint101
-rwxr-xr-xtest/keys134
-rwxr-xr-xtest/meas_qual232
-rwxr-xr-xtest/structure111
-rwxr-xr-xtest/tsort137
-rw-r--r--tsort.c162
-rw-r--r--tsort.h25
-rw-r--r--unparse.c136
-rw-r--r--unparse.h22
-rw-r--r--util.c114
-rw-r--r--util.h70
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
diff --git a/README b/README
new file mode 100644
index 0000000..e8aca0a
--- /dev/null
+++ b/README
@@ -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.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..44b347a
--- /dev/null
+++ b/TODO
@@ -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 */
diff --git a/coord.c b/coord.c
new file mode 100644
index 0000000..84168c7
--- /dev/null
+++ b/coord.c
@@ -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);
+}
diff --git a/coord.h b/coord.h
new file mode 100644
index 0000000..dd9f235
--- /dev/null
+++ b/coord.h
@@ -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 */
diff --git a/cpp.c b/cpp.c
new file mode 100644
index 0000000..fbf98a6
--- /dev/null
+++ b/cpp.c
@@ -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);
+}
diff --git a/cpp.h b/cpp.h
new file mode 100644
index 0000000..26a660d
--- /dev/null
+++ b/cpp.h
@@ -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 */
diff --git a/dump.c b/dump.c
new file mode 100644
index 0000000..cf874d4
--- /dev/null
+++ b/dump.c
@@ -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);
+}
diff --git a/dump.h b/dump.h
new file mode 100644
index 0000000..1da0894
--- /dev/null
+++ b/dump.h
@@ -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 */
diff --git a/error.c b/error.c
new file mode 100644
index 0000000..104507d
--- /dev/null
+++ b/error.c
@@ -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);
+}
diff --git a/error.h b/error.h
new file mode 100644
index 0000000..ac2778e
--- /dev/null
+++ b/error.h
@@ -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 .
diff --git a/expr.c b/expr.c
new file mode 100644
index 0000000..822f4a2
--- /dev/null
+++ b/expr.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;
+ }
+}
diff --git a/expr.h b/expr.h
new file mode 100644
index 0000000..1a97ca4
--- /dev/null
+++ b/expr.h
@@ -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 */
diff --git a/file.c b/file.c
new file mode 100644
index 0000000..469f44b
--- /dev/null
+++ b/file.c
@@ -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");
+ }
+}
diff --git a/file.h b/file.h
new file mode 100644
index 0000000..4d6c828
--- /dev/null
+++ b/file.h
@@ -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 */
diff --git a/fpd.h b/fpd.h
new file mode 100644
index 0000000..99843be
--- /dev/null
+++ b/fpd.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 */
diff --git a/fpd.l b/fpd.l
new file mode 100644
index 0000000..8265adf
--- /dev/null
+++ b/fpd.l
@@ -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;
diff --git a/fpd.y b/fpd.y
new file mode 100644
index 0000000..2919961
--- /dev/null
+++ b/fpd.y
@@ -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;
+ }
+ ;
diff --git a/fped.1 b/fped.1
new file mode 100644
index 0000000..f8b680d
--- /dev/null
+++ b/fped.1
@@ -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).
diff --git a/fped.c b/fped.c
new file mode 100644
index 0000000..608f5a2
--- /dev/null
+++ b/fped.c
@@ -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;
+}
diff --git a/fped.h b/fped.h
new file mode 100644
index 0000000..e579102
--- /dev/null
+++ b/fped.h
@@ -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 */
diff --git a/gui.c b/gui.c
new file mode 100644
index 0000000..b4d2fb4
--- /dev/null
+++ b/gui.c
@@ -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;
+}
diff --git a/gui.h b/gui.h
new file mode 100644
index 0000000..7f8f876
--- /dev/null
+++ b/gui.h
@@ -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&amp;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">&nbsp;<IMG src="manual/all_off.png">
+ <DD>Show all frames. If disabled, only show the currently active frame.
+ <DT><IMG src="manual/stuff.png">&nbsp;<IMG src="manual/stuff_off.png">
+ <DD>Show vectors and frames.
+ <DT><IMG src="manual/meas.png">&nbsp;<IMG src="manual/meas_off.png">
+ <DD>Show measurements.
+ <DT><IMG src="manual/bright.png">&nbsp;<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">&nbsp;<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">&nbsp;<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">&nbsp;<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">&nbsp;<IMG src="manual/meas_x.png">&nbsp;
+ <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 */
diff --git a/hole.c b/hole.c
new file mode 100644
index 0000000..1b90995
--- /dev/null
+++ b/hole.c
@@ -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;
+}
diff --git a/hole.h b/hole.h
new file mode 100644
index 0000000..6c51ad0
--- /dev/null
+++ b/hole.h
@@ -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
diff --git a/inst.c b/inst.c
new file mode 100644
index 0000000..4a08715
--- /dev/null
+++ b/inst.c
@@ -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);
+}
diff --git a/inst.h b/inst.h
new file mode 100644
index 0000000..232f299
--- /dev/null
+++ b/inst.h
@@ -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 */
diff --git a/kicad.c b/kicad.c
new file mode 100644
index 0000000..be3a4f3
--- /dev/null
+++ b/kicad.c
@@ -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, &center, &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, &center, &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, &center);
+
+ /*
+ * 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, &center, &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);
+}
diff --git a/kicad.h b/kicad.h
new file mode 100644
index 0000000..b5b17d6
--- /dev/null
+++ b/kicad.h
@@ -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 */
diff --git a/layer.c b/layer.c
new file mode 100644
index 0000000..8f2ff21
--- /dev/null
+++ b/layer.c
@@ -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;
+}
diff --git a/layer.h b/layer.h
new file mode 100644
index 0000000..b52a362
--- /dev/null
+++ b/layer.h
@@ -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
new file mode 100644
index 0000000..d3edfca
--- /dev/null
+++ b/manual/intro-1.png
Binary files differ
diff --git a/manual/intro-2.png b/manual/intro-2.png
new file mode 100644
index 0000000..842e887
--- /dev/null
+++ b/manual/intro-2.png
Binary files differ
diff --git a/manual/intro-3.png b/manual/intro-3.png
new file mode 100644
index 0000000..e466c17
--- /dev/null
+++ b/manual/intro-3.png
Binary files differ
diff --git a/manual/intro-4.png b/manual/intro-4.png
new file mode 100644
index 0000000..6bf0fd1
--- /dev/null
+++ b/manual/intro-4.png
Binary files differ
diff --git a/manual/intro-5.png b/manual/intro-5.png
new file mode 100644
index 0000000..01bcc97
--- /dev/null
+++ b/manual/intro-5.png
Binary files differ
diff --git a/manual/intro-6.png b/manual/intro-6.png
new file mode 100644
index 0000000..20c8e31
--- /dev/null
+++ b/manual/intro-6.png
Binary files differ
diff --git a/meas.c b/meas.c
new file mode 100644
index 0000000..234dea2
--- /dev/null
+++ b/meas.c
@@ -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;
+}
diff --git a/meas.h b/meas.h
new file mode 100644
index 0000000..83edd80
--- /dev/null
+++ b/meas.h
@@ -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 */
diff --git a/obj.c b/obj.c
new file mode 100644
index 0000000..7f3fa73
--- /dev/null
+++ b/obj.c
@@ -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();
+ }
+}
diff --git a/obj.h b/obj.h
new file mode 100644
index 0000000..2515125
--- /dev/null
+++ b/obj.h
@@ -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.
+
+###############################################################################
diff --git a/tsort.c b/tsort.c
new file mode 100644
index 0000000..1345ae5
--- /dev/null
+++ b/tsort.c
@@ -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;
+}
diff --git a/tsort.h b/tsort.h
new file mode 100644
index 0000000..7e10731
--- /dev/null
+++ b/tsort.h
@@ -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 */
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..8b43682
--- /dev/null
+++ b/util.c
@@ -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;
+ }
+}
+
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..f8191ef
--- /dev/null
+++ b/util.h
@@ -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 */