diff options
-rw-r--r-- | AUTHORS | 4 | ||||
-rw-r--r-- | COPYING | 699 | ||||
-rw-r--r-- | INSTALL | 59 | ||||
-rw-r--r-- | NEWS | 58 | ||||
-rw-r--r-- | README | 14 | ||||
-rw-r--r-- | ganv.pc.in | 11 | ||||
-rw-r--r-- | ganv/Box.hpp | 50 | ||||
-rw-r--r-- | ganv/Canvas.hpp | 158 | ||||
-rw-r--r-- | ganv/Circle.hpp | 79 | ||||
-rw-r--r-- | ganv/Edge.hpp | 90 | ||||
-rw-r--r-- | ganv/Item.hpp | 90 | ||||
-rw-r--r-- | ganv/Module.hpp | 130 | ||||
-rw-r--r-- | ganv/Node.hpp | 106 | ||||
-rw-r--r-- | ganv/Port.hpp | 76 | ||||
-rw-r--r-- | ganv/box.h | 78 | ||||
-rw-r--r-- | ganv/canvas.h | 630 | ||||
-rw-r--r-- | ganv/circle.h | 78 | ||||
-rw-r--r-- | ganv/edge.h | 129 | ||||
-rw-r--r-- | ganv/ganv.h | 31 | ||||
-rw-r--r-- | ganv/ganv.hpp | 26 | ||||
-rw-r--r-- | ganv/group.h | 55 | ||||
-rw-r--r-- | ganv/item.h | 184 | ||||
-rw-r--r-- | ganv/module.h | 95 | ||||
-rw-r--r-- | ganv/node.h | 179 | ||||
-rw-r--r-- | ganv/port.h | 103 | ||||
-rw-r--r-- | ganv/text.h | 52 | ||||
-rw-r--r-- | ganv/types.h | 26 | ||||
-rw-r--r-- | ganv/types.hpp | 30 | ||||
-rw-r--r-- | ganv/widget.h | 54 | ||||
-rw-r--r-- | ganv/wrap.hpp | 131 | ||||
-rwxr-xr-x | gtkdoc.sh | 26 | ||||
-rw-r--r-- | src/Canvas.cpp | 4193 | ||||
-rw-r--r-- | src/Port.cpp | 57 | ||||
-rw-r--r-- | src/boilerplate.h | 43 | ||||
-rw-r--r-- | src/box.c | 563 | ||||
-rw-r--r-- | src/circle.c | 469 | ||||
-rw-r--r-- | src/color.h | 63 | ||||
-rw-r--r-- | src/edge.c | 788 | ||||
-rw-r--r-- | src/fdgl.hpp | 166 | ||||
-rw-r--r-- | src/ganv-marshal.list | 4 | ||||
-rw-r--r-- | src/ganv-private.h | 403 | ||||
-rw-r--r-- | src/ganv_bench.cpp | 178 | ||||
-rw-r--r-- | src/ganv_test.c | 119 | ||||
-rwxr-xr-x | src/ganv_test.py | 22 | ||||
-rw-r--r-- | src/gettext.h | 26 | ||||
-rw-r--r-- | src/group.c | 448 | ||||
-rw-r--r-- | src/item.c | 709 | ||||
-rw-r--r-- | src/module.c | 861 | ||||
-rw-r--r-- | src/node.c | 899 | ||||
-rw-r--r-- | src/port.c | 737 | ||||
-rw-r--r-- | src/text.c | 383 | ||||
-rw-r--r-- | src/widget.c | 505 | ||||
-rwxr-xr-x | waf | 171 | ||||
-rw-r--r-- | waflib/.gitignore (renamed from .gitignore) | 0 | ||||
-rw-r--r-- | waflib/Build.py (renamed from Build.py) | 0 | ||||
-rw-r--r-- | waflib/COPYING | 25 | ||||
-rw-r--r-- | waflib/ConfigSet.py (renamed from ConfigSet.py) | 0 | ||||
-rw-r--r-- | waflib/Configure.py (renamed from Configure.py) | 0 | ||||
-rw-r--r-- | waflib/Context.py (renamed from Context.py) | 0 | ||||
-rw-r--r-- | waflib/Errors.py (renamed from Errors.py) | 0 | ||||
-rw-r--r-- | waflib/Logs.py (renamed from Logs.py) | 0 | ||||
-rw-r--r-- | waflib/Node.py (renamed from Node.py) | 0 | ||||
-rw-r--r-- | waflib/Options.py (renamed from Options.py) | 0 | ||||
-rw-r--r-- | waflib/README.md (renamed from README.md) | 0 | ||||
-rw-r--r-- | waflib/Runner.py (renamed from Runner.py) | 0 | ||||
-rw-r--r-- | waflib/Scripting.py (renamed from Scripting.py) | 0 | ||||
-rw-r--r-- | waflib/Task.py (renamed from Task.py) | 0 | ||||
-rw-r--r-- | waflib/TaskGen.py (renamed from TaskGen.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/__init__.py (renamed from Tools/__init__.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/ar.py (renamed from Tools/ar.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/asm.py (renamed from Tools/asm.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/bison.py (renamed from Tools/bison.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c.py (renamed from Tools/c.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c_aliases.py (renamed from Tools/c_aliases.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c_config.py (renamed from Tools/c_config.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c_osx.py (renamed from Tools/c_osx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c_preproc.py (renamed from Tools/c_preproc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c_tests.py (renamed from Tools/c_tests.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/ccroot.py (renamed from Tools/ccroot.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/clang.py (renamed from Tools/clang.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/clangxx.py (renamed from Tools/clangxx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/compiler_c.py (renamed from Tools/compiler_c.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/compiler_cxx.py (renamed from Tools/compiler_cxx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/compiler_d.py (renamed from Tools/compiler_d.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/compiler_fc.py (renamed from Tools/compiler_fc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/cs.py (renamed from Tools/cs.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/cxx.py (renamed from Tools/cxx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/d.py (renamed from Tools/d.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/d_config.py (renamed from Tools/d_config.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/d_scan.py (renamed from Tools/d_scan.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/dbus.py (renamed from Tools/dbus.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/dmd.py (renamed from Tools/dmd.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/errcheck.py (renamed from Tools/errcheck.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/fc.py (renamed from Tools/fc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/fc_config.py (renamed from Tools/fc_config.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/fc_scan.py (renamed from Tools/fc_scan.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/flex.py (renamed from Tools/flex.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/g95.py (renamed from Tools/g95.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gas.py (renamed from Tools/gas.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gcc.py (renamed from Tools/gcc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gdc.py (renamed from Tools/gdc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gfortran.py (renamed from Tools/gfortran.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/glib2.py (renamed from Tools/glib2.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gnu_dirs.py (renamed from Tools/gnu_dirs.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gxx.py (renamed from Tools/gxx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/icc.py (renamed from Tools/icc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/icpc.py (renamed from Tools/icpc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/ifort.py (renamed from Tools/ifort.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/intltool.py (renamed from Tools/intltool.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/irixcc.py (renamed from Tools/irixcc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/javaw.py (renamed from Tools/javaw.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/ldc2.py (renamed from Tools/ldc2.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/lua.py (renamed from Tools/lua.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/md5_tstamp.py (renamed from Tools/md5_tstamp.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/msvc.py (renamed from Tools/msvc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/nasm.py (renamed from Tools/nasm.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/nobuild.py (renamed from Tools/nobuild.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/perl.py (renamed from Tools/perl.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/python.py (renamed from Tools/python.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/qt5.py (renamed from Tools/qt5.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/ruby.py (renamed from Tools/ruby.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/suncc.py (renamed from Tools/suncc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/suncxx.py (renamed from Tools/suncxx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/tex.py (renamed from Tools/tex.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/vala.py (renamed from Tools/vala.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/waf_unit_test.py (renamed from Tools/waf_unit_test.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/winres.py (renamed from Tools/winres.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/xlc.py (renamed from Tools/xlc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/xlcxx.py (renamed from Tools/xlcxx.py) | 0 | ||||
-rw-r--r-- | waflib/Utils.py (renamed from Utils.py) | 0 | ||||
-rw-r--r-- | waflib/__init__.py (renamed from __init__.py) | 0 | ||||
-rw-r--r-- | waflib/ansiterm.py (renamed from ansiterm.py) | 0 | ||||
-rw-r--r-- | waflib/extras/__init__.py (renamed from extras/__init__.py) | 0 | ||||
-rw-r--r-- | waflib/extras/autowaf.py (renamed from extras/autowaf.py) | 0 | ||||
-rw-r--r-- | waflib/extras/batched_cc.py (renamed from extras/batched_cc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/biber.py (renamed from extras/biber.py) | 0 | ||||
-rw-r--r-- | waflib/extras/bjam.py (renamed from extras/bjam.py) | 0 | ||||
-rw-r--r-- | waflib/extras/blender.py (renamed from extras/blender.py) | 0 | ||||
-rw-r--r-- | waflib/extras/boo.py (renamed from extras/boo.py) | 0 | ||||
-rw-r--r-- | waflib/extras/boost.py (renamed from extras/boost.py) | 0 | ||||
-rw-r--r-- | waflib/extras/build_file_tracker.py (renamed from extras/build_file_tracker.py) | 0 | ||||
-rw-r--r-- | waflib/extras/build_logs.py (renamed from extras/build_logs.py) | 0 | ||||
-rw-r--r-- | waflib/extras/buildcopy.py (renamed from extras/buildcopy.py) | 0 | ||||
-rw-r--r-- | waflib/extras/c_bgxlc.py (renamed from extras/c_bgxlc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/c_dumbpreproc.py (renamed from extras/c_dumbpreproc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/c_emscripten.py (renamed from extras/c_emscripten.py) | 0 | ||||
-rw-r--r-- | waflib/extras/c_nec.py (renamed from extras/c_nec.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cabal.py (renamed from extras/cabal.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cfg_altoptions.py (renamed from extras/cfg_altoptions.py) | 0 | ||||
-rw-r--r-- | waflib/extras/clang_compilation_database.py (renamed from extras/clang_compilation_database.py) | 0 | ||||
-rw-r--r-- | waflib/extras/codelite.py (renamed from extras/codelite.py) | 0 | ||||
-rw-r--r-- | waflib/extras/color_gcc.py (renamed from extras/color_gcc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/color_rvct.py (renamed from extras/color_rvct.py) | 0 | ||||
-rw-r--r-- | waflib/extras/compat15.py (renamed from extras/compat15.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cppcheck.py (renamed from extras/cppcheck.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cpplint.py (renamed from extras/cpplint.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cross_gnu.py (renamed from extras/cross_gnu.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cython.py (renamed from extras/cython.py) | 0 | ||||
-rw-r--r-- | waflib/extras/dcc.py (renamed from extras/dcc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/distnet.py (renamed from extras/distnet.py) | 0 | ||||
-rw-r--r-- | waflib/extras/doxygen.py (renamed from extras/doxygen.py) | 0 | ||||
-rw-r--r-- | waflib/extras/dpapi.py (renamed from extras/dpapi.py) | 0 | ||||
-rw-r--r-- | waflib/extras/eclipse.py (renamed from extras/eclipse.py) | 0 | ||||
-rw-r--r-- | waflib/extras/erlang.py (renamed from extras/erlang.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fast_partial.py (renamed from extras/fast_partial.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_bgxlf.py (renamed from extras/fc_bgxlf.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_cray.py (renamed from extras/fc_cray.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_nag.py (renamed from extras/fc_nag.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_nec.py (renamed from extras/fc_nec.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_open64.py (renamed from extras/fc_open64.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_pgfortran.py (renamed from extras/fc_pgfortran.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_solstudio.py (renamed from extras/fc_solstudio.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_xlf.py (renamed from extras/fc_xlf.py) | 0 | ||||
-rw-r--r-- | waflib/extras/file_to_object.py (renamed from extras/file_to_object.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fluid.py (renamed from extras/fluid.py) | 0 | ||||
-rw-r--r-- | waflib/extras/freeimage.py (renamed from extras/freeimage.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fsb.py (renamed from extras/fsb.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fsc.py (renamed from extras/fsc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/gccdeps.py (renamed from extras/gccdeps.py) | 0 | ||||
-rw-r--r-- | waflib/extras/gdbus.py (renamed from extras/gdbus.py) | 0 | ||||
-rw-r--r-- | waflib/extras/gob2.py (renamed from extras/gob2.py) | 0 | ||||
-rw-r--r-- | waflib/extras/halide.py (renamed from extras/halide.py) | 0 | ||||
-rwxr-xr-x | waflib/extras/javatest.py (renamed from extras/javatest.py) | 0 | ||||
-rw-r--r-- | waflib/extras/kde4.py (renamed from extras/kde4.py) | 0 | ||||
-rw-r--r-- | waflib/extras/local_rpath.py (renamed from extras/local_rpath.py) | 0 | ||||
-rw-r--r-- | waflib/extras/lv2.py (renamed from extras/lv2.py) | 0 | ||||
-rw-r--r-- | waflib/extras/make.py (renamed from extras/make.py) | 0 | ||||
-rw-r--r-- | waflib/extras/midl.py (renamed from extras/midl.py) | 0 | ||||
-rw-r--r-- | waflib/extras/msvcdeps.py (renamed from extras/msvcdeps.py) | 0 | ||||
-rw-r--r-- | waflib/extras/msvs.py (renamed from extras/msvs.py) | 0 | ||||
-rw-r--r-- | waflib/extras/netcache_client.py (renamed from extras/netcache_client.py) | 0 | ||||
-rw-r--r-- | waflib/extras/objcopy.py (renamed from extras/objcopy.py) | 0 | ||||
-rw-r--r-- | waflib/extras/ocaml.py (renamed from extras/ocaml.py) | 0 | ||||
-rw-r--r-- | waflib/extras/package.py (renamed from extras/package.py) | 0 | ||||
-rw-r--r-- | waflib/extras/parallel_debug.py (renamed from extras/parallel_debug.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pch.py (renamed from extras/pch.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pep8.py (renamed from extras/pep8.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pgicc.py (renamed from extras/pgicc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pgicxx.py (renamed from extras/pgicxx.py) | 0 | ||||
-rw-r--r-- | waflib/extras/proc.py (renamed from extras/proc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/protoc.py (renamed from extras/protoc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pyqt5.py (renamed from extras/pyqt5.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pytest.py (renamed from extras/pytest.py) | 0 | ||||
-rw-r--r-- | waflib/extras/qnxnto.py (renamed from extras/qnxnto.py) | 0 | ||||
-rw-r--r-- | waflib/extras/qt4.py (renamed from extras/qt4.py) | 0 | ||||
-rw-r--r-- | waflib/extras/relocation.py (renamed from extras/relocation.py) | 0 | ||||
-rw-r--r-- | waflib/extras/remote.py (renamed from extras/remote.py) | 0 | ||||
-rw-r--r-- | waflib/extras/resx.py (renamed from extras/resx.py) | 0 | ||||
-rw-r--r-- | waflib/extras/review.py (renamed from extras/review.py) | 0 | ||||
-rw-r--r-- | waflib/extras/rst.py (renamed from extras/rst.py) | 0 | ||||
-rw-r--r-- | waflib/extras/run_do_script.py (renamed from extras/run_do_script.py) | 0 | ||||
-rw-r--r-- | waflib/extras/run_m_script.py (renamed from extras/run_m_script.py) | 0 | ||||
-rw-r--r-- | waflib/extras/run_py_script.py (renamed from extras/run_py_script.py) | 0 | ||||
-rw-r--r-- | waflib/extras/run_r_script.py (renamed from extras/run_r_script.py) | 0 | ||||
-rw-r--r-- | waflib/extras/sas.py (renamed from extras/sas.py) | 0 | ||||
-rw-r--r-- | waflib/extras/satellite_assembly.py (renamed from extras/satellite_assembly.py) | 0 | ||||
-rw-r--r-- | waflib/extras/scala.py (renamed from extras/scala.py) | 0 | ||||
-rw-r--r-- | waflib/extras/slow_qt4.py (renamed from extras/slow_qt4.py) | 0 | ||||
-rw-r--r-- | waflib/extras/softlink_libs.py (renamed from extras/softlink_libs.py) | 0 | ||||
-rw-r--r-- | waflib/extras/stale.py (renamed from extras/stale.py) | 0 | ||||
-rw-r--r-- | waflib/extras/stracedeps.py (renamed from extras/stracedeps.py) | 0 | ||||
-rw-r--r-- | waflib/extras/swig.py (renamed from extras/swig.py) | 0 | ||||
-rw-r--r-- | waflib/extras/syms.py (renamed from extras/syms.py) | 0 | ||||
-rw-r--r-- | waflib/extras/ticgt.py (renamed from extras/ticgt.py) | 0 | ||||
-rw-r--r-- | waflib/extras/unity.py (renamed from extras/unity.py) | 0 | ||||
-rw-r--r-- | waflib/extras/use_config.py (renamed from extras/use_config.py) | 0 | ||||
-rw-r--r-- | waflib/extras/valadoc.py (renamed from extras/valadoc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/waf_xattr.py (renamed from extras/waf_xattr.py) | 0 | ||||
-rw-r--r-- | waflib/extras/why.py (renamed from extras/why.py) | 0 | ||||
-rw-r--r-- | waflib/extras/win32_opts.py (renamed from extras/win32_opts.py) | 0 | ||||
-rw-r--r-- | waflib/extras/wix.py (renamed from extras/wix.py) | 0 | ||||
-rw-r--r-- | waflib/extras/xcode6.py (renamed from extras/xcode6.py) | 0 | ||||
-rw-r--r-- | waflib/fixpy2.py (renamed from fixpy2.py) | 0 | ||||
-rwxr-xr-x | waflib/processor.py (renamed from processor.py) | 0 | ||||
-rwxr-xr-x | waflib/waf | 16 | ||||
-rw-r--r-- | wscript | 256 |
236 files changed, 15601 insertions, 34 deletions
@@ -0,0 +1,4 @@ +Author: + +David Robillard <d@drobilla.net> + @@ -1,25 +1,674 @@ -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 3 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, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU 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. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. @@ -0,0 +1,59 @@ +Installation Instructions +========================= + +Basic Installation +------------------ + +Building this software requires only Python. To install with default options: + + ./waf configure + ./waf + ./waf install + +You may need to become root for the install stage, for example: + + sudo ./waf install + +Configuration Options +--------------------- + +All supported options can be viewed using the command: + + ./waf --help + +Most options only need to be passed during the configure stage, for example: + + ./waf configure --prefix=/usr + ./waf + ./waf install + +Compiler Configuration +---------------------- + +Several standard environment variables can be used to control how compilers are +invoked: + + * CC: Path to C compiler + * CFLAGS: C compiler options + * CXX: Path to C++ compiler + * CXXFLAGS: C++ compiler options + * CPPFLAGS: C preprocessor options + * LINKFLAGS: Linker options + +Installation Directories +------------------------ + +The --prefix option (or the PREFIX environment variable) can be used to change +the prefix which all files are installed under. There are also several options +allowing for more fine-tuned control, see the --help output for details. + +Packaging +--------- + +Everything can be installed to a specific root directory by passing a --destdir +option to the install stage (or setting the DESTDIR environment variable), +which adds a prefix to all install paths. For example: + + ./waf configure --prefix=/usr + ./waf + ./waf install --destdir=/tmp/package @@ -0,0 +1,58 @@ +ganv (1.4.3) unstable; + + * Fix positioning of embedded widgets when changing layout. + * Fix port position on modules with embedded widgets. + * Fix unexpected node jumping when dragging new connections. + * Preserve selection for quickly making several connections. + * Dampen sprung layout energy over time to prevent oscillation. + * Add support for edges that do not constrain the layout. + * Distinguish edge color from port color slighly. + * Highlight connected edges on port hover. + * Add support for PDF and PS export. + * Improve text rendering at high zoom. + * Improve appearance of graphs with circle nodes. + * Improve update performance. + * Add API to specify module port order. + * Add support for beveled box corners. + * Fix various minor visual alignment/sizing issues. + * Fix size of vertical flow modules. + * Fix compilation with --no-fdgl (patch from Vlad Glagolev). + * Fix crash when destroying canvas. + * Upgrade to waf 1.8.14 + + -- David Robillard <d@drobilla.net> Fri, 14 Oct 2016 18:55:26 -0400 + +ganv (1.4.2) stable; + + * Fix bug where edges would not update when nodes are moved after the canvas + is cleared (fix stuck connections in Patchage after refresh). + * Upgrade to waf 1.7.16 + + -- David Robillard <d@drobilla.net> Fri, 08 Aug 2014 18:24:33 -0400 + +ganv (1.4.0) stable; + + * Begin using library and pkg-config names suitable for parallel + installation. This version of flowcanvas is flowcanvas-1 and is + NOT compatible with previous versions + * Clean up API and improve documentation. + * Add font size API + * Use system theme font size by default + * Size empty ports in font based units so they look right when zoomed + * Adjust padding and placement to precisely fit text + * Add ability to select connections directly + * Add Connection::set_curved() + * Fix lingering handle when deleting connections + * Dramatically increase performance by rendering text manually + rather than using the truly awful Gnome::Canvas::Text. + * Remove use of boost smart pointers. Adding and removing from containers + (e.g. Canvas, Module) is now done automatically. + * Clean up API/ABI by hiding private implementations. + * Add ability to select connections by their handles, either individually + or in groups with rect select. + * Further slight improvements in memory consumption and alignment. + * Improve scalability to graphs with many connections (eliminate linear + connection searches and redundant connection collections). + * Switch to GPLv3+ + + -- David Robillard <d@drobilla.net> Sun, 27 Apr 2014 23:44:29 -0400 @@ -0,0 +1,14 @@ +Ganv +==== + +Ganv is an interactive Gtk canvas widget for graph-based interfaces (patchers, +modular synthesizers, finite state automata, interactive graphs, etc). +For more information, see <http://drobilla.net/software/ganv>. + +Ganv provides classes for "Modules" (boxes with "Ports"), Circles, and +Edges (lines that connect either Ports or Circles). The user can rearrange +items, or Ganv can automatically arrange items using GraphViz. Edges can +be made by the user one at a time with the mouse, or in groups using the mouse +and keyboard. + + -- David Robillard <d@drobilla.net> diff --git a/ganv.pc.in b/ganv.pc.in new file mode 100644 index 0000000..3edf190 --- /dev/null +++ b/ganv.pc.in @@ -0,0 +1,11 @@ +prefix=@PREFIX@ +exec_prefix=@EXEC_PREFIX@ +libdir=@LIBDIR@ +includedir=@INCLUDEDIR@ + +Name: ganv +Version: @GANV_VERSION@ +Description: A Gtk canvas widget for graph based interfaces +Requires: gtk+-2.0 +Libs: -L${libdir} -l@LIB_GANV@ +Cflags: -I${includedir}/ganv-@GANV_MAJOR_VERSION@ diff --git a/ganv/Box.hpp b/ganv/Box.hpp new file mode 100644 index 0000000..1b95859 --- /dev/null +++ b/ganv/Box.hpp @@ -0,0 +1,50 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_BOX_HPP +#define GANV_BOX_HPP + +#include "ganv/Node.hpp" +#include "ganv/box.h" + +namespace Ganv { + +class Box : public Node +{ +public: + Box(Canvas* canvas, GanvBox* gobj) + : Node(canvas, GANV_NODE(gobj)) + {} + + RW_PROPERTY(const char*, label) + RW_PROPERTY(gboolean, beveled) + + METHODRET0(ganv_box, double, get_x1) + METHODRET0(ganv_box, double, get_y1) + METHODRET0(ganv_box, double, get_x2) + METHODRET0(ganv_box, double, get_y2) + METHODRET0(ganv_box, double, get_width) + METHOD1(ganv_box, set_width, double, width) + METHODRET0(ganv_box, double, get_height) + METHOD1(ganv_box, set_height, double, height) + METHODRET0(ganv_box, double, get_border_width) + + GanvBox* gobj() { return GANV_BOX(_gobj); } + const GanvBox* gobj() const { return GANV_BOX(_gobj); } +}; + +} // namespace Ganv + +#endif // GANV_BOX_HPP diff --git a/ganv/Canvas.hpp b/ganv/Canvas.hpp new file mode 100644 index 0000000..8db1f66 --- /dev/null +++ b/ganv/Canvas.hpp @@ -0,0 +1,158 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_CANVAS_HPP +#define GANV_CANVAS_HPP + +#include <string> + +#include <glib.h> +#include <glibmm.h> +#include <gtkmm/layout.h> + +#include "ganv/canvas.h" +#include "ganv/wrap.hpp" + +/** Ganv namespace, everything is defined under this. + * + * @ingroup Ganv + */ +namespace Ganv { + +class Edge; +class Item; +class Node; +class Port; + +/** @defgroup Ganv Ganv + * + * A canvas widget for graph-like UIs. + */ + +/** The 'master' canvas widget which contains all other objects. + * + * Applications must override some virtual methods to make the widget actually + * do anything (e.g. connect). + * + * @ingroup Ganv + */ +class Canvas +{ +public: + Canvas(double width, double height); + virtual ~Canvas(); + + GanvItem* root() { return ganv_canvas_root(gobj()); } + + METHOD0(ganv_canvas, clear); + METHODRET0(ganv_canvas, gboolean, empty) + METHOD2(ganv_canvas, resize, double, width, double, height); + METHOD4(ganv_canvas, set_scroll_region, double, x1, double, y1, double, x2, double, y2); + METHOD4(ganv_canvas, get_scroll_region, double*, x1, double*, y1, double*, x2, double*, y2); + METHOD1(ganv_canvas, set_center_scroll_region, gboolean, c); + METHODRET0(ganv_canvas, gboolean, get_center_scroll_region); + METHOD2(ganv_canvas, scroll_to, int, x, int, y); + + void get_scroll_offsets(int& cx, int& cy) const { + ganv_canvas_get_scroll_offsets(gobj(), &cx, &cy); + } + + METHOD1(ganv_canvas, w2c_affine, cairo_matrix_t*, matrix); + METHOD4(ganv_canvas, w2c, double, wx, double, wy, int*, cx, int*, cy); + METHOD4(ganv_canvas, w2c_d, double, wx, double, wy, double*, cx, double*, cy); + METHOD4(ganv_canvas, c2w, int, cx, int, cy, double*, wx, double*, wy); + METHOD4(ganv_canvas, window_to_world, double, winx, double, winy, double*, worldx, double*, worldy); + METHOD4(ganv_canvas, world_to_window, double, worldx, double, worldy, double*, winx, double*, winy); + + Item* get_item_at(double x, double y) const; + Edge* get_edge(Node* tail, Node* head) const; + void remove_edge_between(Node* tail, Node* head); + void remove_edge(Edge* edge); + + METHOD0(ganv_canvas, arrange); + METHODRET2(ganv_canvas, int, export_image, const char*, filename, bool, draw_background); + METHOD1(ganv_canvas, export_dot, const char*, filename); + METHODRET0(ganv_canvas, gboolean, supports_sprung_layout); + METHODRET1(ganv_canvas, gboolean, set_sprung_layout, gboolean, sprung_layout); + METHOD2(ganv_canvas, for_each_node, GanvNodeFunc, f, void*, data) + METHOD2(ganv_canvas, for_each_selected_node, GanvNodeFunc, f, void*, data) + METHOD2(ganv_canvas, for_each_edge, GanvEdgeFunc, f, void*, data) + METHOD3(ganv_canvas, for_each_edge_from, + const GanvNode*, tail, + GanvEdgeFunc, f, + void*, data); + METHOD3(ganv_canvas, for_each_edge_to, + const GanvNode*, head, + GanvEdgeFunc, f, + void*, data); + METHOD3(ganv_canvas, for_each_edge_on, + const GanvNode*, node, + GanvEdgeFunc, f, + void*, data); + METHOD2(ganv_canvas, for_each_selected_edge, GanvEdgeFunc, f, void*, data) + + METHOD0(ganv_canvas, select_all); + METHOD0(ganv_canvas, clear_selection); + METHODRET0(ganv_canvas, double, get_zoom); + METHOD1(ganv_canvas, set_zoom, double, pix_per_unit); + METHOD0(ganv_canvas, zoom_full); + METHODRET0(ganv_canvas, double, get_default_font_size) + METHODRET0(ganv_canvas, double, get_font_size) + METHOD1(ganv_canvas, set_font_size, double, points); + METHOD0(ganv_canvas, get_move_cursor); + METHOD2(ganv_canvas, move_contents_to, double, x, double, y); + + RW_PROPERTY(gboolean, locked) + RW_PROPERTY(double, width) + RW_PROPERTY(double, height) + RW_PROPERTY(GanvDirection, direction) + + void set_port_order(GanvPortOrderFunc port_cmp, void* data) { + ganv_canvas_set_port_order(gobj(), port_cmp, data); + } + + Gtk::Layout& widget() { + return *Glib::wrap(&_gobj->layout); + } + + GQuark wrapper_key(); + + GanvCanvas* gobj() { return GANV_CANVAS(_gobj); } + const GanvCanvas* gobj() const { return GANV_CANVAS(_gobj); } + + sigc::signal<bool, GdkEvent*> signal_event; + sigc::signal<void, Node*, Node*> signal_connect; + sigc::signal<void, Node*, Node*> signal_disconnect; + +private: + Canvas(const Canvas&); ///< Noncopyable + const Canvas& operator=(const Canvas&); ///< Noncopyable + + GanvCanvas* const _gobj; +}; + +} // namespace Ganv + +namespace Glib { + +static inline Ganv::Canvas* +wrap(GanvCanvas* canvas) +{ + return (Ganv::Canvas*)ganv_canvas_get_wrapper(canvas); +} + +} // namespace Glib + +#endif // GANV_CANVAS_HPP diff --git a/ganv/Circle.hpp b/ganv/Circle.hpp new file mode 100644 index 0000000..ab47669 --- /dev/null +++ b/ganv/Circle.hpp @@ -0,0 +1,79 @@ +/* This file is part of Ganv. + * Copyright 2007-2013 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_CIRCLE_HPP +#define GANV_CIRCLE_HPP + +#include <algorithm> +#include <map> +#include <string> +#include <stdint.h> + +#include <gdkmm/types.h> + +#include "ganv/types.hpp" +#include "ganv/Node.hpp" +#include "ganv/circle.h" + +GANV_GLIB_WRAP(Circle) + +namespace Ganv { + +class Canvas; + +/** An elliptical Item which is Node. + * + * Unlike a Module, this doesn't contain ports, but is directly joinable itself + * (think your classic circles 'n' lines diagram, ala FSM). + * + * @ingroup Ganv + */ +class Circle : public Node +{ +public: + static const uint32_t FILL_COLOUR = 0x1E2224FF; + static const uint32_t BORDER_COLOUR = 0xD3D7CFFF; + + Circle(Canvas& canvas, + const std::string& name, + double x, + double y) + : Node(&canvas, + GANV_NODE( + ganv_item_new( + GANV_ITEM(canvas.root()), + ganv_circle_get_type(), + "x", x, + "y", y, + "can-tail", TRUE, + "can-head", TRUE, + "fill-color", FILL_COLOUR, + "border-color", BORDER_COLOUR, + "label", name.c_str(), + "draggable", TRUE, + NULL))) + {} + + RW_PROPERTY(double, radius) + RW_PROPERTY(double, radius_ems) + RW_PROPERTY(gboolean, fit_label) + + GanvCircle* gobj() { return GANV_CIRCLE(_gobj); } + const GanvCircle* gobj() const { return GANV_CIRCLE(_gobj); } +}; + +} // namespace Ganv + +#endif // GANV_CIRCLE_HPP diff --git a/ganv/Edge.hpp b/ganv/Edge.hpp new file mode 100644 index 0000000..a01de44 --- /dev/null +++ b/ganv/Edge.hpp @@ -0,0 +1,90 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_EDGE_HPP +#define GANV_EDGE_HPP + +#include <stdint.h> + +#include <gdk/gdkevents.h> + +#include "ganv/Canvas.hpp" +#include "ganv/Item.hpp" +#include "ganv/Node.hpp" +#include "ganv/edge.h" + +GANV_GLIB_WRAP(Edge) + +namespace Ganv { + +class Canvas; + +/** A edge (line) between two Node objects. + * + * @ingroup Ganv + */ +class Edge : public Item +{ +public: + Edge(Canvas& canvas, + Node* tail, + Node* head, + uint32_t color = 0, + bool show_arrowhead = false, + bool curved = true) + : Item(GANV_ITEM(ganv_edge_new(canvas.gobj(), + tail->gobj(), + head->gobj(), + "color", color, + "curved", (gboolean)curved, + "arrowhead", (gboolean)show_arrowhead, + NULL))) + {} + + Edge(GanvEdge* gobj) + : Item(GANV_ITEM(gobj)) + {} + + virtual ~Edge() { + if (_gobj && ganv_item_get_parent(_gobj)) { + g_object_unref(_gobj); + } + } + + gboolean is_within(double x1, double y1, double x2, double y2) const { + return ganv_edge_is_within(gobj(), x1, y1, x2, y2); + } + + RW_PROPERTY(gboolean, constraining) + RW_PROPERTY(gboolean, curved) + RW_PROPERTY(gboolean, selected) + RW_PROPERTY(gboolean, highlighted) + RW_PROPERTY(guint, color) + RW_PROPERTY(gdouble, handle_radius) + + METHODRETWRAP0(ganv_edge, Node*, get_tail); + METHODRETWRAP0(ganv_edge, Node*, get_head); + + GanvEdge* gobj() { return (GanvEdge*)_gobj; } + const GanvEdge* gobj() const { return (GanvEdge*)_gobj; } + +private: + Edge(const Edge& copy); + Edge& operator=(const Edge& other); +}; + +} // namespace Ganv + +#endif // GANV_EDGE_HPP diff --git a/ganv/Item.hpp b/ganv/Item.hpp new file mode 100644 index 0000000..680b991 --- /dev/null +++ b/ganv/Item.hpp @@ -0,0 +1,90 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_ITEM_HPP +#define GANV_ITEM_HPP + +#include <assert.h> + +#include <glib.h> + +#include <sigc++/signal.h> +#include <sigc++/trackable.h> + +#include "ganv/Canvas.hpp" +#include "ganv/item.h" +#include "ganv/wrap.hpp" + +GANV_GLIB_WRAP(Item) + +namespace Ganv { + +class Canvas; + +/** An item on the canvas. + */ +class Item : public sigc::trackable { +public: + Item(GanvItem* gobj) + : _gobj(gobj) + { + ganv_item_set_wrapper(gobj, this); + if (gobj && ganv_item_get_parent(gobj)) { + g_signal_connect( + G_OBJECT(_gobj), "event", G_CALLBACK(on_item_event), this); + } + } + + virtual ~Item() { + gtk_object_destroy(GTK_OBJECT(_gobj)); + } + + RW_PROPERTY(double, x) + RW_PROPERTY(double, y) + + METHOD0(ganv_item, raise); + METHOD0(ganv_item, lower); + METHOD2(ganv_item, move, double, dx, double, dy); + METHOD0(ganv_item, show); + METHOD0(ganv_item, hide); + METHOD2(ganv_item, i2w, double*, x, double*, y); + METHOD2(ganv_item, w2i, double*, x, double*, y); + METHOD0(ganv_item, grab_focus); + + Canvas* canvas() const { + return Glib::wrap(ganv_item_get_canvas(_gobj)); + } + + GanvItem* gobj() const { return _gobj; } + + SIGNAL1(event, GdkEvent*) + SIGNAL1(click, GdkEventButton*) + +protected: + GanvItem* const _gobj; + +private: + static gboolean + on_item_event(GanvItem* canvasitem, + GdkEvent* ev, + void* item) + { + return ((Item*)item)->signal_event().emit(ev); + } +}; + +} // namespace Ganv + +#endif // GANV_ITEM_HPP diff --git a/ganv/Module.hpp b/ganv/Module.hpp new file mode 100644 index 0000000..8ca4393 --- /dev/null +++ b/ganv/Module.hpp @@ -0,0 +1,130 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_MODULE_HPP +#define GANV_MODULE_HPP + +#include <string> +#include <vector> + +#include <gtkmm/container.h> + +#include "ganv/Canvas.hpp" +#include "ganv/Node.hpp" +#include "ganv/Port.hpp" +#include "ganv/module.h" + +GANV_GLIB_WRAP(Module) + +namespace Ganv { + +class Canvas; + +/** A rectangular Item which can hold a Port. + * + * @ingroup Ganv + */ +class Module : public Box +{ +public: + Module(Canvas& canvas, + const std::string& name, + double x = 0, + double y = 0, + bool show_title = true) + : Box(&canvas, + GANV_BOX(ganv_item_new(GANV_ITEM(canvas.root()), + ganv_module_get_type(), + "x", x, + "y", y, + "can-tail", FALSE, + "can-head", FALSE, + "radius-tl", 4.0, + "radius-tr", 4.0, + "radius-br", 4.0, + "radius-bl", 4.0, + "border-width", 2.0, + "label", name.c_str(), + "draggable", TRUE, + NULL))) + {} + + template<typename P, typename C> + class iterator_base { + public: + iterator_base(GanvModule* m, guint i) : _module(m), _index(i) {} + template<typename T, typename U> + iterator_base(const iterator_base<T, U>& i) + : _module(i._module) + , _index(i._index) + {} + P* operator*() const { + return Glib::wrap(ganv_module_get_port(_module, _index)); + } + P* operator->() const { + return Glib::wrap(ganv_module_get_port(_module, _index)); + } + iterator_base operator++(int) const { + return iterator_base<P, C>(_index + 1); + } + iterator_base& operator++() { + ++_index; return *this; + } + bool operator==(const iterator_base<P, C>& i) const { + return _index == i._index; + } + bool operator!=(const iterator_base<P, C>& i) const { + return _index != i._index; + } + private: + template<typename T, typename U> friend class iterator_base; + GanvModule* _module; + guint _index; + }; + + typedef iterator_base<Port, GanvPort> iterator; + typedef iterator_base<const Port, const GanvPort> const_iterator; + + iterator begin() { return iterator(gobj(), 0); } + iterator end() { return iterator(gobj(), num_ports()); } + iterator back() { return iterator(gobj(), num_ports() - 1); } + const_iterator begin() const { return iterator(const_cast<GanvModule*>(gobj()), 0); } + const_iterator end() const { return iterator(const_cast<GanvModule*>(gobj()), num_ports()); } + const_iterator back() const { return iterator(const_cast<GanvModule*>(gobj()), num_ports() - 1); } + + void embed(Gtk::Widget* widget) { + ganv_module_embed(gobj(), widget ? widget->gobj() : NULL); + } + + Port* get_port(guint index) { + return Glib::wrap(ganv_module_get_port(gobj(), index)); + } + + METHOD2(ganv_module, for_each_port, GanvPortFunc, f, void*, data); + + METHODRET0(ganv_module, guint, num_ports); + + RW_PROPERTY(gboolean, stacked) + + METHODRET0(ganv_module, double, get_empty_port_breadth) + METHODRET0(ganv_module, double, get_empty_port_depth) + + GanvModule* gobj() { return GANV_MODULE(_gobj); } + const GanvModule* gobj() const { return GANV_MODULE(_gobj); } +}; + +} // namespace Ganv + +#endif // GANV_MODULE_HPP diff --git a/ganv/Node.hpp b/ganv/Node.hpp new file mode 100644 index 0000000..2fef74c --- /dev/null +++ b/ganv/Node.hpp @@ -0,0 +1,106 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_NODE_HPP +#define GANV_NODE_HPP + +#include <glib.h> +#include <assert.h> + +#include "ganv/node.h" +#include "ganv/Item.hpp" + +GANV_GLIB_WRAP(Node) + +namespace Ganv { + +class Canvas; +class Node; + +/** An object a Edge can connect to. + */ +class Node : public Item { +public: + Node(Canvas* canvas, GanvNode* gobj) + : Item(GANV_ITEM(g_object_ref(gobj))) + { + g_signal_connect(gobj, "moved", G_CALLBACK(on_moved), this); + CONNECT_PROP_SIGNAL(gobj, selected, on_notify_bool, &Node::on_selected) + } + + ~Node() { + g_object_unref(_gobj); + } + + RW_PROPERTY(gboolean, can_tail) + RW_PROPERTY(gboolean, can_head) + RW_PROPERTY(gboolean, is_source) + + gboolean is_within(double x1, double y1, double x2, double y2) const { + return ganv_node_is_within(gobj(), x1, y1, x2, y2); + } + + RW_PROPERTY(const char*, label) + RW_PROPERTY(double, border_width) + RW_PROPERTY(double, dash_length) + RW_PROPERTY(double, dash_offset) + RW_PROPERTY(guint, fill_color) + RW_PROPERTY(guint, border_color) + RW_PROPERTY(gboolean, selected) + RW_PROPERTY(gboolean, highlighted) + RW_PROPERTY(gboolean, draggable) + RW_PROPERTY(gboolean, grabbed) + + RW_OBJECT_PROPERTY(Node*, partner); + + GanvNode* gobj() { return GANV_NODE(_gobj); } + const GanvNode* gobj() const { return GANV_NODE(_gobj); } + + METHOD2(ganv_node, move, double, dx, double, dy) + METHOD2(ganv_node, move_to, double, x, double, y) + + METHOD0(ganv_node, disconnect); + + sigc::signal<void, double, double>& signal_moved() { + return _signal_moved; + } + +private: + sigc::signal<void, double, double> _signal_moved; + + static void on_moved(GanvNode* node, double x, double y) { + Glib::wrap(node)->_signal_moved.emit(x, y); + } + + /* GCC 4.6 can't handle this + template<typename T> + static void on_notify(GObject* gobj, GParamSpec* pspec, gpointer signal) { + T value; + g_object_get(gobj, g_param_spec_get_name(pspec), &value, NULL); + ((sigc::signal<bool, T>*)signal)->emit(value); + } + */ + static void on_notify_bool(GObject* gobj, + GParamSpec* pspec, + gpointer signal) { + gboolean value; + g_object_get(gobj, g_param_spec_get_name(pspec), &value, NULL); + ((sigc::signal<bool, gboolean>*)signal)->emit(value); + } +}; + +} // namespace Ganv + +#endif // GANV_NODE_HPP diff --git a/ganv/Port.hpp b/ganv/Port.hpp new file mode 100644 index 0000000..94fb760 --- /dev/null +++ b/ganv/Port.hpp @@ -0,0 +1,76 @@ +/* This file is part of Ganv. + * Copyright 2007-2014 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_PORT_HPP +#define GANV_PORT_HPP + +#include <stdint.h> + +#include <string> + +#include <gdkmm/types.h> + +#include "ganv/Box.hpp" +#include "ganv/port.h" + +GANV_GLIB_WRAP(Port) + +namespace Ganv { + +class Module; + +/** A port on a Module. + * + * This is a group that contains both the label and rectangle for a port. + * + * @ingroup Ganv + */ +class Port : public Box +{ +public: + Port(Module& module, + const std::string& name, + bool is_input, + uint32_t color); + + RW_PROPERTY(gboolean, is_controllable) + + METHODRET0(ganv_port, gboolean, is_input) + METHODRET0(ganv_port, gboolean, is_output) + + METHODRET0(ganv_port, double, get_natural_width); + METHODRET0(ganv_port, float, get_control_value) + METHODRET0(ganv_port, float, get_control_min) + METHODRET0(ganv_port, float, get_control_max) + METHOD0(ganv_port, show_control) + METHOD0(ganv_port, hide_control) + METHOD1(ganv_port, set_control_is_toggle, gboolean, is_toggle) + METHOD1(ganv_port, set_control_is_integer, gboolean, is_integer) + METHOD1(ganv_port, set_control_value, float, value) + METHOD1(ganv_port, set_control_min, float, min) + METHOD1(ganv_port, set_control_max, float, max) + METHOD1(ganv_port, set_value_label, const char*, str); + + sigc::signal<void, double> signal_value_changed; + + Module* get_module() const; + + GanvPort* gobj() { return GANV_PORT(_gobj); } + const GanvPort* gobj() const { return GANV_PORT(_gobj); } +}; + +} // namespace Ganv + +#endif // GANV_PORT_HPP diff --git a/ganv/box.h b/ganv/box.h new file mode 100644 index 0000000..203be69 --- /dev/null +++ b/ganv/box.h @@ -0,0 +1,78 @@ +/* This file is part of Ganv. + * Copyright 2007-2014 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_BOX_H +#define GANV_BOX_H + +#include "ganv/node.h" + +G_BEGIN_DECLS + +#define GANV_TYPE_BOX (ganv_box_get_type()) +#define GANV_BOX(obj) (GTK_CHECK_CAST((obj), GANV_TYPE_BOX, GanvBox)) +#define GANV_BOX_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), GANV_TYPE_BOX, GanvBoxClass)) +#define GANV_IS_BOX(obj) (GTK_CHECK_TYPE((obj), GANV_TYPE_BOX)) +#define GANV_IS_BOX_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), GANV_TYPE_BOX)) +#define GANV_BOX_GET_CLASS(obj) (GTK_CHECK_GET_CLASS((obj), GANV_TYPE_BOX, GanvBoxClass)) + +typedef struct _GanvBoxClass GanvBoxClass; +typedef struct _GanvBoxImpl GanvBoxImpl; + +struct _GanvBox { + GanvNode node; + GanvBoxImpl* impl; +}; + +/** + * GanvBoxClass: + * @parent_class: Node superclass. + * @set_width: Set the width of the box. + * @set_height: Set the height of the box. + */ +struct _GanvBoxClass { + GanvNodeClass parent_class; + + void (*set_width)(GanvBox* box, + double width); + + void (*set_height)(GanvBox* box, + double height); + + /* Reserved for future expansion */ + gpointer spare_vmethods[4]; +}; + +GType ganv_box_get_type(void) G_GNUC_CONST; +double ganv_box_get_x1(const GanvBox* box); +double ganv_box_get_y1(const GanvBox* box); +double ganv_box_get_x2(const GanvBox* box); +double ganv_box_get_y2(const GanvBox* box); +double ganv_box_get_width(const GanvBox* box); +void ganv_box_set_width(GanvBox* box, double width); +double ganv_box_get_height(const GanvBox* box); +void ganv_box_set_height(GanvBox* box, double height); +double ganv_box_get_border_width(const GanvBox* box); + +/** + * ganv_box_normalize: + * @box: The box to normalize. + * + * Normalize the box coordinates such that x1 < x2 and y1 < y2. + */ +void ganv_box_normalize(GanvBox* box); + +G_END_DECLS + +#endif /* GANV_BOX_H */ diff --git a/ganv/canvas.h b/ganv/canvas.h new file mode 100644 index 0000000..fc42c6f --- /dev/null +++ b/ganv/canvas.h @@ -0,0 +1,630 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_CANVAS_H +#define GANV_CANVAS_H + +#include <stdarg.h> + +#include <cairo.h> +#include <gtk/gtk.h> + +#include "ganv/canvas.h" +#include "ganv/types.h" +#include "ganv/edge.h" +#include "ganv/item.h" + +G_BEGIN_DECLS + +#define GANV_TYPE_CANVAS (ganv_canvas_get_type()) +#define GANV_CANVAS(obj) (GTK_CHECK_CAST((obj), GANV_TYPE_CANVAS, GanvCanvas)) +#define GANV_CANVAS_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), GANV_TYPE_CANVAS, GanvCanvasClass)) +#define GANV_IS_CANVAS(obj) (GTK_CHECK_TYPE((obj), GANV_TYPE_CANVAS)) +#define GANV_IS_CANVAS_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), GANV_TYPE_CANVAS)) +#define GANV_CANVAS_GET_CLASS(obj) (GTK_CHECK_GET_CLASS((obj), GANV_TYPE_CANVAS, GanvCanvasClass)) + +typedef struct GanvCanvasImpl GanvCanvasImpl; +typedef struct _GanvCanvasClass GanvCanvasClass; + +/** + * GanvDirection: + * @GANV_DIRECTION_DOWN: Signal flows from top to bottom. + * @GANV_DIRECTION_RIGHT: Signal flows from left to right. + * + * Specifies the direction of signal flow on the canvas, which affects the + * appearance of modules and how the canvas is auto-arranged. + */ +typedef enum { + GANV_DIRECTION_DOWN, + GANV_DIRECTION_RIGHT +} GanvDirection; + +struct _GanvCanvas { + GtkLayout layout; + GanvCanvasImpl* impl; +}; + +struct _GanvCanvasClass { + GtkLayoutClass parent_class; + + /* Reserved for future expansion */ + gpointer spare_vmethods[4]; +}; + +GType ganv_canvas_get_type(void) G_GNUC_CONST; + +/** + * GanvEdgeFunc: + * @edge: Canvas edge. + * @data: User callback data. + * + * A node function that takes a user data argument (for callbacks). + * + * Note that in the Gtk world it is considered safe to cast a function to a + * function with more arguments and call the resulting pointer, so functions + * like ganv_edge_select can safely be used where a GanvEdgeFunc is expected. + */ +typedef void (*GanvEdgeFunc)(GanvEdge* edge, void* data); + +/** + * GanvNodeFunc: + * @node: Canvas node. + * @data: User callback data. + * + * A node function that takes a user data argument (for callbacks). + * + * Note that in the Gtk world it is considered safe to cast a function to a + * function with more arguments and call the resulting pointer, so functions + * like ganv_node_select can safely be used where a GanvNodeFunc is expected. + */ +typedef void (*GanvNodeFunc)(GanvNode* node, void* data); + +typedef int (*GanvPortOrderFunc)(const GanvPort*, const GanvPort*, void* data); + +/** + * ganv_canvas_new: + * + * Return value: A newly-created canvas. + */ +GanvCanvas* +ganv_canvas_new(double width, double height); + +/** + * ganv_canvas_set_wrapper: + * + * Set the opaque wrapper object for the canvas. + */ +void +ganv_canvas_set_wrapper(GanvCanvas* canvas, void* wrapper); + +/** + * ganv_canvas_get_wrapper: + * + * Return an opaque pointer to the wrapper for the canvas. + */ +void* +ganv_canvas_get_wrapper(GanvCanvas* canvas); + +/** + * ganv_canvas_clear: + * + * Remove all items from the canvas. + */ +void +ganv_canvas_clear(GanvCanvas* canvas); + +/** + * ganv_canvas_empty: + * + * Return value: True if there are no items on the canvas. + */ +gboolean +ganv_canvas_empty(const GanvCanvas* canvas); + +/** + * ganv_canvas_resize: + * + * Resize the canvas to the given dimensions. + */ +void +ganv_canvas_resize(GanvCanvas* canvas, double width, double height); + +/** + * ganv_canvas_root: + * @canvas: A canvas. + * + * Return value: (transfer none): The root group of the canvas. + */ +GanvItem* +ganv_canvas_root(GanvCanvas* canvas); + +/** + * ganv_canvas_set_scroll_region: + * @canvas: A canvas. + * @x1: Leftmost limit of the scrolling region. + * @y1: Upper limit of the scrolling region. + * @x2: Rightmost limit of the scrolling region. + * @y2: Lower limit of the scrolling region. + * + * Sets the scrolling region of a canvas to the specified rectangle. The + * canvas will then be able to scroll only within this region. The view of the + * canvas is adjusted as appropriate to display as much of the new region as + * possible. + */ +void +ganv_canvas_set_scroll_region(GanvCanvas* canvas, + double x1, double y1, double x2, double y2); + +/** + * ganv_canvas_get_scroll_region: + * @canvas: A canvas. + * @x1: Leftmost limit of the scrolling region (return value). + * @y1: Upper limit of the scrolling region (return value). + * @x2: Rightmost limit of the scrolling region (return value). + * @y2: Lower limit of the scrolling region (return value). + * + * Queries the scrolling region of a canvas. + */ +void +ganv_canvas_get_scroll_region(GanvCanvas* canvas, + double* x1, double* y1, double* x2, double* y2); + +/** + * ganv_canvas_set_center_scroll_region: + * @canvas: A canvas. + * @center_scroll_region: Whether to center the scrolling region in the canvas + * window when it is smaller than the canvas' allocation. + * + * When the scrolling region of the canvas is smaller than the canvas window, + * e.g. the allocation of the canvas, it can be either centered on the window + * or simply made to be on the upper-left corner on the window. This function + * lets you configure this property. + */ +void +ganv_canvas_set_center_scroll_region(GanvCanvas* canvas, + gboolean center_scroll_region); + +/** + * ganv_canvas_get_center_scroll_region: + * @canvas: A canvas. + * + * Returns whether the canvas is set to center the scrolling region in the + * window if the former is smaller than the canvas' allocation. + * + * Returns: Whether the scroll region is being centered in the canvas window. + */ +gboolean +ganv_canvas_get_center_scroll_region(const GanvCanvas* canvas); + +/** + * ganv_canvas_scroll_to: + * @canvas: A canvas. + * @cx: Horizontal scrolling offset in canvas pixel units. + * @cy: Vertical scrolling offset in canvas pixel units. + * + * Makes a canvas scroll to the specified offsets, given in canvas pixel units. + * The canvas will adjust the view so that it is not outside the scrolling + * region. This function is typically not used, as it is better to hook + * scrollbars to the canvas layout's scrolling adjusments. + */ +void +ganv_canvas_scroll_to(GanvCanvas* canvas, int cx, int cy); + +/** + * ganv_canvas_get_scroll_offsets: + * @canvas: A canvas. + * @cx: Horizontal scrolling offset (return value). + * @cy: Vertical scrolling offset (return value). + * + * Queries the scrolling offsets of a canvas. The values are returned in canvas + * pixel units. + */ +void +ganv_canvas_get_scroll_offsets(const GanvCanvas* canvas, int* cx, int* cy); + +/** + * ganv_canvas_w2c_affine: + * @canvas: A canvas. + * @matrix: An affine transformation matrix (return value). + * + * Gets the affine transform that converts from world coordinates to canvas + * pixel coordinates. + */ +void +ganv_canvas_w2c_affine(GanvCanvas* canvas, cairo_matrix_t* matrix); + +/** + * ganv_canvas_w2c: + * @canvas: A canvas. + * @wx: World X coordinate. + * @wy: World Y coordinate. + * @cx: X pixel coordinate (return value). + * @cy: Y pixel coordinate (return value). + * + * Converts world coordinates into canvas pixel coordinates. + */ +void +ganv_canvas_w2c(GanvCanvas* canvas, double wx, double wy, int* cx, int* cy); + +/** + * ganv_canvas_w2c_d: + * @canvas: A canvas. + * @wx: World X coordinate. + * @wy: World Y coordinate. + * @cx: X pixel coordinate (return value). + * @cy: Y pixel coordinate (return value). + * + * Converts world coordinates into canvas pixel coordinates. This version + * uses floating point coordinates for greater precision. + */ +void +ganv_canvas_w2c_d(GanvCanvas* canvas, + double wx, + double wy, + double* cx, + double* cy); + +/** + * ganv_canvas_c2w: + * @canvas: A canvas. + * @cx: Canvas pixel X coordinate. + * @cy: Canvas pixel Y coordinate. + * @wx: X world coordinate (return value). + * @wy: Y world coordinate (return value). + * + * Converts canvas pixel coordinates to world coordinates. + */ +void +ganv_canvas_c2w(GanvCanvas* canvas, int cx, int cy, double* wx, double* wy); + +/** + * ganv_canvas_window_to_world: + * @canvas: A canvas. + * @winx: Window-relative X coordinate. + * @winy: Window-relative Y coordinate. + * @worldx: X world coordinate (return value). + * @worldy: Y world coordinate (return value). + * + * Converts window-relative coordinates into world coordinates. You can use + * this when you need to convert mouse coordinates into world coordinates, for + * example. + */ +void +ganv_canvas_window_to_world(GanvCanvas* canvas, + double winx, + double winy, + double* worldx, + double* worldy); + +/** + * ganv_canvas_world_to_window: + * @canvas: A canvas. + * @worldx: World X coordinate. + * @worldy: World Y coordinate. + * @winx: X window-relative coordinate. + * @winy: Y window-relative coordinate. + * + * Converts world coordinates into window-relative coordinates. + */ +void +ganv_canvas_world_to_window(GanvCanvas* canvas, + double worldx, + double worldy, + double* winx, + double* winy); + +/** + * ganv_canvas_get_item_at: + * @canvas: A canvas. + * @x: X position in world coordinates. + * @y: Y position in world coordinates. + * + * Looks for the item that is under the specified position, which must be + * specified in world coordinates. + * + * Returns: (transfer none): The sought item, or NULL if no item is at the + * specified coordinates. + */ +GanvItem* +ganv_canvas_get_item_at(GanvCanvas* canvas, double x, double y); + +/** + * ganv_canvas_get_edge: + * + * Get the edge between two nodes, or NULL if none exists. + * + * Return value: (transfer none): The root group of @canvas. + */ +GanvEdge* +ganv_canvas_get_edge(GanvCanvas* canvas, + GanvNode* tail, + GanvNode* head); + +/** + * ganv_canvas_remove_edge: + * + * Remove @edge from the canvas. + */ +void +ganv_canvas_remove_edge(GanvCanvas* canvas, + GanvEdge* edge); + +/** + * ganv_canvas_remove_edge_between: + * + * Remove the edge from @tail to @head if one exists. + */ +void +ganv_canvas_remove_edge_between(GanvCanvas* canvas, + GanvNode* tail, + GanvNode* head); + +/** + * ganv_canvas_get_direction: + * + * Return the direction of signal flow. + */ +GanvDirection +ganv_canvas_get_direction(GanvCanvas* canvas); + +/** + * ganv_canvas_set_direction: + * + * Set the direction of signal flow. + */ +void +ganv_canvas_set_direction(GanvCanvas* canvas, GanvDirection dir); + +/** + * ganv_canvas_arrange: + * + * Automatically arrange the canvas contents. + */ +void +ganv_canvas_arrange(GanvCanvas* canvas); + +/** + * ganv_canvas_export_image: + * + * Draw the canvas to an image file. The file type is determined by extension, + * currently supported: pdf, ps, svg, dot. + * + * Returns: 0 on success. + */ +int +ganv_canvas_export_image(GanvCanvas* canvas, + const char* filename, + gboolean draw_background); + +/** + * ganv_canvas_export_dot: + * + * Write a Graphviz DOT description of the canvas to a file. + */ +void +ganv_canvas_export_dot(GanvCanvas* canvas, const char* filename); + +/** + * ganv_canvas_supports_sprung_layout: + * + * Returns: true iff ganv is compiled with sprung layout support. + */ +gboolean +ganv_canvas_supports_sprung_layout(const GanvCanvas* canvas); + +/** + * ganv_canvas_set_sprung_layout: + * + * Enable or disable "live" force-directed canvas layout. + * + * Returns: true iff sprung layout was enabled. + */ +gboolean +ganv_canvas_set_sprung_layout(GanvCanvas* canvas, gboolean sprung_layout); + +/** + * ganv_canvas_get_locked: + * + * Return true iff the canvas is locked and nodes may not move. + */ +gboolean +ganv_canvas_get_locked(const GanvCanvas* canvas); + +/** + * ganv_canvas_for_each_node: + * @canvas: The canvas. + * @f: (scope call): A function to call on every node on @canvas. + * @data: Data to pass to @f. + */ +void +ganv_canvas_for_each_node(GanvCanvas* canvas, + GanvNodeFunc f, + void* data); + +/** + * ganv_canvas_for_each_selected_node: + * @canvas: The canvas. + * @f: (scope call): A function to call on every selected node on @canvas. + * @data: Data to pass to @f. + */ +void +ganv_canvas_for_each_selected_node(GanvCanvas* canvas, + GanvNodeFunc f, + void* data); + +/** + * ganv_canvas_for_each_edge: + * @canvas: The canvas. + * @f: (scope call): A function to call on every edge on @canvas. + * @data: Data to pass to @f. + */ +void +ganv_canvas_for_each_edge(GanvCanvas* canvas, + GanvEdgeFunc f, + void* data); + +/** + * ganv_canvas_for_each_edge_from: + * @canvas: The canvas. + * @tail: The tail to enumerate every edge for. + * @f: (scope call): A function to call on every edge leaving @tail. + */ +void +ganv_canvas_for_each_edge_from(GanvCanvas* canvas, + const GanvNode* tail, + GanvEdgeFunc f, + void* data); + +/** + * ganv_canvas_for_each_edge_to: + * @canvas: The canvas. + * @head: The head to enumerate every edge for. + * @f: (scope call): A function to call on every edge entering @head. + */ +void +ganv_canvas_for_each_edge_to(GanvCanvas* canvas, + const GanvNode* head, + GanvEdgeFunc f, + void* data); + +/** + * ganv_canvas_for_each_edge_on: + * @canvas: The canvas. + * @node: The node to enumerate every edge for. + * @f: (scope call): A function to call on every edge attached to @node. + */ +void +ganv_canvas_for_each_edge_on(GanvCanvas* canvas, + const GanvNode* node, + GanvEdgeFunc f, + void* data); + +/** + * ganv_canvas_for_each_selected_edge: + * @canvas: The canvas. + * @f: (scope call): A function to call on every edge attached to @node. + * @data: Data to pass to @f. + */ +void +ganv_canvas_for_each_selected_edge(GanvCanvas* canvas, + GanvEdgeFunc f, + void* data); + +/** + * ganv_canvas_select_all: + * + * Select all items on the canvas. + */ +void +ganv_canvas_select_all(GanvCanvas* canvas); + +/** + * ganv_canvas_clear_selection: + * + * Deselect any selected items on the canvas. + */ +void +ganv_canvas_clear_selection(GanvCanvas* canvas); + +/** + * ganv_canvas_get_zoom: + * + * Return the current zoom factor (pixels per unit). + */ +double +ganv_canvas_get_zoom(const GanvCanvas* canvas); + +/** + * ganv_canvas_set_zoom: + * @canvas: A canvas. + * @zoom: The number of pixels that correspond to one canvas unit. + * + * The anchor point for zooming, i.e. the point that stays fixed and all others + * zoom inwards or outwards from it, depends on whether the canvas is set to + * center the scrolling region or not. You can control this using the + * ganv_canvas_set_center_scroll_region() function. If the canvas is set to + * center the scroll region, then the center of the canvas window is used as + * the anchor point for zooming. Otherwise, the upper-left corner of the + * canvas window is used as the anchor point. + */ +void +ganv_canvas_set_zoom(GanvCanvas* canvas, double zoom); + +/** + * ganv_canvas_zoom_full: + * + * Zoom so all canvas contents are visible. + */ +void +ganv_canvas_zoom_full(GanvCanvas* canvas); + +/** + * ganv_canvas_get_default_font_size: + * + * Get the default font size in points. + */ +double +ganv_canvas_get_default_font_size(const GanvCanvas* canvas); + +/** + * ganv_canvas_get_font_size: + * + * Get the current font size in points. + */ +double +ganv_canvas_get_font_size(const GanvCanvas* canvas); + +/** + * ganv_canvas_set_font_size: + * + * Set the current font size in points. + */ +void +ganv_canvas_set_font_size(GanvCanvas* canvas, double points); + +/** + * ganv_canvas_get_move_cursor: + * + * Return the cursor to use while dragging canvas objects. + */ +GdkCursor* +ganv_canvas_get_move_cursor(const GanvCanvas* canvas); + +/** + * ganv_canvas_move_contents_to: + * + * Shift all canvas contents so the top-left object is at (x, y). + */ +void +ganv_canvas_move_contents_to(GanvCanvas* canvas, double x, double y); + +/** + * ganv_canvas_set_port_order: + * @canvas The canvas to set the default port order on. + * @port_cmp Port comparison function. + * @data Data to be passed to order. + * + * Set a comparator function to use as the default order for ports on modules. + * If left unset, ports are shown in the order they are added. + */ +void +ganv_canvas_set_port_order(GanvCanvas* canvas, + GanvPortOrderFunc port_cmp, + void* data); + + +G_END_DECLS + +#endif /* GANV_CANVAS_H */ diff --git a/ganv/circle.h b/ganv/circle.h new file mode 100644 index 0000000..cca9e5f --- /dev/null +++ b/ganv/circle.h @@ -0,0 +1,78 @@ +/* This file is part of Ganv. + * Copyright 2007-2014 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_CIRCLE_H +#define GANV_CIRCLE_H + +#include "ganv/node.h" + +G_BEGIN_DECLS + +#define GANV_TYPE_CIRCLE (ganv_circle_get_type()) +#define GANV_CIRCLE(obj) (GTK_CHECK_CAST((obj), GANV_TYPE_CIRCLE, GanvCircle)) +#define GANV_CIRCLE_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), GANV_TYPE_CIRCLE, GanvCircleClass)) +#define GANV_IS_CIRCLE(obj) (GTK_CHECK_TYPE((obj), GANV_TYPE_CIRCLE)) +#define GANV_IS_CIRCLE_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), GANV_TYPE_CIRCLE)) +#define GANV_CIRCLE_GET_CLASS(obj) (GTK_CHECK_GET_CLASS((obj), GANV_TYPE_CIRCLE, GanvCircleClass)) + +typedef struct _GanvCircle GanvCircle; +typedef struct _GanvCircleClass GanvCircleClass; +typedef struct _GanvCircleImpl GanvCircleImpl; + +/** + * GanvCircle: + * + * A circular #GanvNode. A #GanvCircle is a leaf, that is, it does not contain + * any child nodes (though, like any #GanvNode, it may have a label). + */ +struct _GanvCircle { + GanvNode node; + GanvCircleImpl* impl; +}; + +struct _GanvCircleClass { + GanvNodeClass parent_class; + + /* Reserved for future expansion */ + gpointer spare_vmethods[4]; +}; + +GType ganv_circle_get_type(void) G_GNUC_CONST; + +GanvCircle* +ganv_circle_new(GanvCanvas* canvas, + const char* first_prop_name, ...); + +double +ganv_circle_get_radius(const GanvCircle* circle); + +void +ganv_circle_set_radius(GanvCircle* circle, double radius); + +double +ganv_circle_get_radius_ems(const GanvCircle* circle); + +void +ganv_circle_set_radius_ems(GanvCircle* circle, double radius); + +gboolean +ganv_circle_get_fit_label(const GanvCircle* circle); + +void +ganv_circle_set_fit_label(GanvCircle* circle, gboolean fit_label); + +G_END_DECLS + +#endif /* GANV_CIRCLE_H */ diff --git a/ganv/edge.h b/ganv/edge.h new file mode 100644 index 0000000..d135bad --- /dev/null +++ b/ganv/edge.h @@ -0,0 +1,129 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_EDGE_H +#define GANV_EDGE_H + +#include "ganv/item.h" +#include "ganv/node.h" +#include "ganv/types.h" + +G_BEGIN_DECLS + +#define GANV_TYPE_EDGE (ganv_edge_get_type()) +#define GANV_EDGE(obj) (GTK_CHECK_CAST((obj), GANV_TYPE_EDGE, GanvEdge)) +#define GANV_EDGE_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), GANV_TYPE_EDGE, GanvEdgeClass)) +#define GANV_IS_EDGE(obj) (GTK_CHECK_TYPE((obj), GANV_TYPE_EDGE)) +#define GANV_IS_EDGE_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), GANV_TYPE_EDGE)) +#define GANV_EDGE_GET_CLASS(obj) (GTK_CHECK_GET_CLASS((obj), GANV_TYPE_EDGE, GanvEdgeClass)) + +typedef struct _GanvEdgeClass GanvEdgeClass; +typedef struct _GanvEdgeImpl GanvEdgeImpl; + +struct _GanvEdge { + GanvItem item; + GanvEdgeImpl* impl; +}; + +struct _GanvEdgeClass { + GanvItemClass parent_class; + + /* Reserved for future expansion */ + gpointer spare_vmethods[4]; +}; + +GType ganv_edge_get_type(void) G_GNUC_CONST; + +GanvEdge* +ganv_edge_new(GanvCanvas* canvas, + GanvNode* tail, + GanvNode* head, + const char* first_prop_name, ...); + +gboolean +ganv_edge_is_within(const GanvEdge* edge, + double x1, + double y1, + double x2, + double y2); + +gboolean +ganv_edge_get_curved(const GanvEdge* edge); + +void +ganv_edge_set_curved(GanvEdge* edge, gboolean curved); + +gboolean +ganv_edge_get_constraining(const GanvEdge* edge); + +void +ganv_edge_set_constraining(GanvEdge* edge, gboolean constraining); + +void +ganv_edge_set_selected(GanvEdge* edge, gboolean selected); + +void +ganv_edge_set_highlighted(GanvEdge* edge, gboolean highlighted); + +void +ganv_edge_select(GanvEdge* edge); + +void +ganv_edge_unselect(GanvEdge* edge); + +void +ganv_edge_highlight(GanvEdge* edge); + +void +ganv_edge_unhighlight(GanvEdge* edge); + +/** + * ganv_edge_disconnect: + * + * Disconnect the edge. This will disconnect the edge just as if it had been + * disconnected by the user via the canvas. The canvas disconnect signal will + * be emitted, allowing the application to control disconnect logic. + */ +void +ganv_edge_disconnect(GanvEdge* edge); + +/** + * ganv_edge_remove: + * + * Remove the edge from the canvas. This will only remove the edge visually, + * it will not emit the canvas disconnect signal to notify the application. + */ +void +ganv_edge_remove(GanvEdge* edge); + +/** + * ganv_edge_get_tail: + * + * Return value: (transfer none): The tail of `edge`. + */ +GanvNode* +ganv_edge_get_tail(const GanvEdge* edge); + +/** + * ganv_edge_get_head: + * + * Return value: (transfer none): The head of `edge`. + */ +GanvNode* +ganv_edge_get_head(const GanvEdge* edge); + +G_END_DECLS + +#endif /* GANV_EDGE_H */ diff --git a/ganv/ganv.h b/ganv/ganv.h new file mode 100644 index 0000000..9a00143 --- /dev/null +++ b/ganv/ganv.h @@ -0,0 +1,31 @@ +/* This file is part of Ganv. + * Copyright 2007-2016 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_GANV_H +#define GANV_GANV_H + +#include "ganv/box.h" +#include "ganv/canvas.h" +#include "ganv/circle.h" +#include "ganv/edge.h" +#include "ganv/ganv.h" +#include "ganv/group.h" +#include "ganv/module.h" +#include "ganv/node.h" +#include "ganv/port.h" +#include "ganv/text.h" +#include "ganv/types.h" + +#endif /* GANV_GANV_H */ diff --git a/ganv/ganv.hpp b/ganv/ganv.hpp new file mode 100644 index 0000000..9b0334d --- /dev/null +++ b/ganv/ganv.hpp @@ -0,0 +1,26 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_GANV_HPP +#define GANV_GANV_HPP + +#include "ganv/Canvas.hpp" +#include "ganv/Circle.hpp" +#include "ganv/Edge.hpp" +#include "ganv/Module.hpp" +#include "ganv/Node.hpp" +#include "ganv/Port.hpp" + +#endif // GANV_GANV_HPP diff --git a/ganv/group.h b/ganv/group.h new file mode 100644 index 0000000..42ad5df --- /dev/null +++ b/ganv/group.h @@ -0,0 +1,55 @@ +/* This file is part of Ganv. + * Copyright 2007-2014 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_GROUP_H +#define GANV_GROUP_H + +#include "ganv/item.h" + +G_BEGIN_DECLS + +/* Based on GnomeCanvasGroup, by Federico Mena <federico@nuclecu.unam.mx> + * and Raph Levien <raph@gimp.org> + * Copyright 1997-2000 Free Software Foundation + */ + +#define GANV_TYPE_GROUP (ganv_group_get_type()) +#define GANV_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GANV_TYPE_GROUP, GanvGroup)) +#define GANV_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GANV_TYPE_GROUP, GanvGroupClass)) +#define GANV_IS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GANV_TYPE_GROUP)) +#define GANV_IS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GANV_TYPE_GROUP)) +#define GANV_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GANV_TYPE_GROUP, GanvGroupClass)) + +typedef struct _GanvGroup GanvGroup; +typedef struct _GanvGroupImpl GanvGroupImpl; +typedef struct _GanvGroupClass GanvGroupClass; + +struct _GanvGroup { + GanvItem item; + GanvGroupImpl* impl; +}; + +struct _GanvGroupClass { + GanvItemClass parent_class; + + /* Reserved for future expansion */ + gpointer spare_vmethods[4]; +}; + +GType ganv_group_get_type(void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GANV_GROUP_H */ diff --git a/ganv/item.h b/ganv/item.h new file mode 100644 index 0000000..2da061b --- /dev/null +++ b/ganv/item.h @@ -0,0 +1,184 @@ +/* This file is part of Ganv. + * Copyright 2007-2016 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +/* Based on GnomeCanvas, by Federico Mena <federico@nuclecu.unam.mx> + * and Raph Levien <raph@gimp.org> + * Copyright 1997-2000 Free Software Foundation + */ + +#ifndef GANV_ITEM_H +#define GANV_ITEM_H + +#include <stdarg.h> + +#include <cairo.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +struct _GanvCanvas; + +typedef struct _GanvItem GanvItem; +typedef struct _GanvItemImpl GanvItemImpl; +typedef struct _GanvItemClass GanvItemClass; + +/* Object flags for items */ +enum { + GANV_ITEM_REALIZED = 1 << 1, + GANV_ITEM_MAPPED = 1 << 2, + GANV_ITEM_ALWAYS_REDRAW = 1 << 3, + GANV_ITEM_VISIBLE = 1 << 4, + GANV_ITEM_NEED_UPDATE = 1 << 5, + GANV_ITEM_NEED_VIS = 1 << 6 +}; + +#define GANV_TYPE_ITEM (ganv_item_get_type()) +#define GANV_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GANV_TYPE_ITEM, GanvItem)) +#define GANV_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GANV_TYPE_ITEM, GanvItemClass)) +#define GANV_IS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GANV_TYPE_ITEM)) +#define GANV_IS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GANV_TYPE_ITEM)) +#define GANV_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GANV_TYPE_ITEM, GanvItemClass)) + +struct _GanvItem { + GtkObject object; + GanvItemImpl* impl; +}; + +struct _GanvItemClass { + GtkObjectClass parent_class; + + /* Add a child to this item (optional). */ + void (*add)(GanvItem* item, GanvItem* child); + + /* Remove a child from this item (optional). */ + void (*remove)(GanvItem* item, GanvItem* child); + + /* Tell the item to update itself. + * + * The flags are from the update flags defined above. The item should + * update its internal state from its queued state, and recompute and + * request its repaint area. The update method also recomputes the + * bounding box of the item. + */ + void (*update)(GanvItem* item, int flags); + + /* Realize an item (create GCs, etc.). */ + void (*realize)(GanvItem* item); + + /* Unrealize an item. */ + void (*unrealize)(GanvItem* item); + + /* Map an item - normally only need by items with their own GdkWindows. */ + void (*map)(GanvItem* item); + + /* Unmap an item */ + void (*unmap)(GanvItem* item); + + /* Draw an item of this type. + * + * (cx, cy) and (width, height) describe the rectangle being drawn in + * world-relative coordinates. + */ + void (*draw)(GanvItem* item, + cairo_t* cr, + double cx, + double cy, + double cw, + double ch); + + /* Calculate the distance from an item to the specified point. + * + * It also returns a canvas item which is actual item the point is within, + * which may not be equal to @item if @item has children. + * (x, y) are item-relative coordinates. + */ + double (*point)(GanvItem* item, + double x, + double y, + GanvItem** actual_item); + + /* Fetch the item's bounding box (need not be exactly tight). + * + * This should be in item-relative coordinates. + */ + void (*bounds)(GanvItem* item, double* x1, double* y1, double* x2, double* y2); + + /* Signal: an event occurred for an item of this type. + * + * The (x, y) coordinates are in the canvas world coordinate system. + */ + gboolean (*event)(GanvItem* item, GdkEvent* event); + + /* Reserved for future expansion */ + gpointer spare_vmethods[4]; +}; + +GType ganv_item_get_type(void) G_GNUC_CONST; + +GanvItem* ganv_item_new(GanvItem* parent, GType type, + const gchar* first_arg_name, ...); + +void ganv_item_construct(GanvItem* item, GanvItem* parent, + const gchar* first_arg_name, va_list args); + +void ganv_item_set(GanvItem* item, const gchar* first_arg_name, ...); + +void ganv_item_set_valist(GanvItem* item, + const gchar* first_arg_name, va_list args); + +/** + * ganv_item_get_canvas: + * @item: The item. + * + * Return value: (transfer none): The canvas @item is on. + */ +struct _GanvCanvas* ganv_item_get_canvas(GanvItem* item); + +/** + * ganv_item_get_parent: + * @item: The item. + * + * Return value: (transfer none): The parent of @item. + */ +GanvItem* ganv_item_get_parent(GanvItem* item); + +void ganv_item_raise(GanvItem* item); + +void ganv_item_lower(GanvItem* item); + +void ganv_item_move(GanvItem* item, double dx, double dy); + +void ganv_item_show(GanvItem* item); + +void ganv_item_hide(GanvItem* item); + +void ganv_item_i2w(GanvItem* item, double* x, double* y); + +void ganv_item_w2i(GanvItem* item, double* x, double* y); + +void ganv_item_grab_focus(GanvItem* item); + +void ganv_item_get_bounds(GanvItem* item, + double* x1, double* y1, double* x2, double* y2); + +void ganv_item_request_update(GanvItem* item); + +void ganv_item_set_wrapper(GanvItem* item, void* wrapper); + +void* ganv_item_get_wrapper(GanvItem* item); + +G_END_DECLS + +#endif /* GANV_ITEM_H */ diff --git a/ganv/module.h b/ganv/module.h new file mode 100644 index 0000000..6314383 --- /dev/null +++ b/ganv/module.h @@ -0,0 +1,95 @@ +/* This file is part of Ganv. + * Copyright 2007-2014 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_MODULE_H +#define GANV_MODULE_H + +#include <glib.h> +#include <gtk/gtk.h> + +#include "ganv/box.h" + +G_BEGIN_DECLS + +#define GANV_TYPE_MODULE (ganv_module_get_type()) +#define GANV_MODULE(obj) (GTK_CHECK_CAST((obj), GANV_TYPE_MODULE, GanvModule)) +#define GANV_MODULE_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), GANV_TYPE_MODULE, GanvModuleClass)) +#define GANV_IS_MODULE(obj) (GTK_CHECK_TYPE((obj), GANV_TYPE_MODULE)) +#define GANV_IS_MODULE_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), GANV_TYPE_MODULE)) +#define GANV_MODULE_GET_CLASS(obj) (GTK_CHECK_GET_CLASS((obj), GANV_TYPE_MODULE, GanvModuleClass)) + +typedef struct _GanvModuleClass GanvModuleClass; +typedef struct _GanvModuleImpl GanvModuleImpl; + +typedef void (*GanvPortFunc)(GanvPort* port, void* data); + +struct _GanvModule { + GanvBox box; + GanvModuleImpl* impl; +}; + +struct _GanvModuleClass { + GanvBoxClass parent_class; + + /* Reserved for future expansion */ + gpointer spare_vmethods[4]; +}; + +GType ganv_module_get_type(void) G_GNUC_CONST; + +GanvModule* +ganv_module_new(GanvCanvas* canvas, + const char* first_prop_name, ...); + +guint +ganv_module_num_ports(const GanvModule* module); + +/** + * ganv_module_get_port: + * + * Get a port by index. + * + * Return value: (transfer none): The port on @module at @index. + */ +GanvPort* +ganv_module_get_port(GanvModule* module, + guint index); + +double +ganv_module_get_empty_port_breadth(const GanvModule* module); + +double +ganv_module_get_empty_port_depth(const GanvModule* module); + +void +ganv_module_embed(GanvModule* module, GtkWidget* widget); + +void +ganv_module_set_direction(GanvModule* module, GanvDirection direction); + +/** + * ganv_module_for_each_port: + * @module: The module. + * @f: (scope call): A function to call on every port on @module. + * @data: User data to pass to @f. + */ +void +ganv_module_for_each_port(GanvModule* module, + GanvPortFunc f, + void* data); + +G_END_DECLS + +#endif /* GANV_MODULE_H */ diff --git a/ganv/node.h b/ganv/node.h new file mode 100644 index 0000000..c98e2b2 --- /dev/null +++ b/ganv/node.h @@ -0,0 +1,179 @@ +/* This file is part of Ganv. + * Copyright 2007-2014 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_NODE_H +#define GANV_NODE_H + +#include "ganv/item.h" +#include "ganv/types.h" +#include "ganv/text.h" + +G_BEGIN_DECLS + +#define GANV_TYPE_NODE (ganv_node_get_type()) +#define GANV_NODE(obj) (GTK_CHECK_CAST((obj), GANV_TYPE_NODE, GanvNode)) +#define GANV_NODE_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), GANV_TYPE_NODE, GanvNodeClass)) +#define GANV_IS_NODE(obj) (GTK_CHECK_TYPE((obj), GANV_TYPE_NODE)) +#define GANV_IS_NODE_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), GANV_TYPE_NODE)) +#define GANV_NODE_GET_CLASS(obj) (GTK_CHECK_GET_CLASS((obj), GANV_TYPE_NODE, GanvNodeClass)) + +typedef struct _GanvNodeClass GanvNodeClass; +typedef struct _GanvNodeImpl GanvNodeImpl; + +struct _GanvNode { + GanvItem item; + GanvNodeImpl* impl; +}; + +struct _GanvNodeClass { + GanvItemClass parent_class; + + void (*tick)(GanvNode* self, + double seconds); + + void (*move)(GanvNode* node, + double dx, + double dy); + + void (*move_to)(GanvNode* node, + double x, + double y); + + void (*resize)(GanvNode* node); + + void (*redraw_text)(GanvNode* node); + + void (*disconnect)(GanvNode* node); + + gboolean (*is_within)(const GanvNode* self, + double x1, + double y1, + double x2, + double y2); + + void (*tail_vector)(const GanvNode* self, + const GanvNode* head, + double* x, + double* y, + double* dx, + double* dy); + + void (*head_vector)(const GanvNode* self, + const GanvNode* tail, + double* x, + double* y, + double* dx, + double* dy); + + /* Reserved for future expansion */ + gpointer spare_vmethods[4]; +}; + +GType ganv_node_get_type(void) G_GNUC_CONST; + +/** + * ganv_node_can_tail: + * + * Return value: True iff node can act as the tail of an edge. + */ +gboolean +ganv_node_can_tail(const GanvNode* node); + +/** + * ganv_node_can_head: + * + * Return value: True iff node can act as the head of an edge. + */ +gboolean +ganv_node_can_head(const GanvNode* node); + +/** + * ganv_node_set_is_source: + * + * Flag a node as a source. This information is used to influence layout. + */ +void +ganv_node_set_is_source(const GanvNode* node, gboolean is_source); + +/** + * ganv_node_is_within: + * + * Return value: True iff node is entirely within the given rectangle. + */ +gboolean +ganv_node_is_within(const GanvNode* node, + double x1, + double y1, + double x2, + double y2); + +const char* ganv_node_get_label(const GanvNode* node); + +double ganv_node_get_border_width(const GanvNode* node); + +void ganv_node_set_border_width(const GanvNode* node, double border_width); + +double ganv_node_get_dash_length(const GanvNode* node); + +void ganv_node_set_dash_length(const GanvNode* node, double dash_length); + +double ganv_node_get_dash_offset(const GanvNode* node); + +void ganv_node_set_dash_offset(const GanvNode* node, double dash_offset); + +guint ganv_node_get_fill_color(const GanvNode* node); + +void ganv_node_set_fill_color(const GanvNode* node, guint fill_color); + +guint ganv_node_get_border_color(const GanvNode* node); + +void ganv_node_set_border_color(const GanvNode* node, guint border_color); + +/** + * ganv_node_get_partner: + * + * Return value: (transfer none): The partner of @node. + */ +GanvNode* +ganv_node_get_partner(const GanvNode* node); + +void ganv_node_set_label(GanvNode* node, const char* str); +void ganv_node_set_show_label(GanvNode* node, gboolean show); + +void +ganv_node_move(GanvNode* node, + double dx, + double dy); + +void +ganv_node_move_to(GanvNode* node, + double x, + double y); + +void +ganv_node_resize(GanvNode* node); + +void +ganv_node_redraw_text(GanvNode* node); + +void +ganv_node_disconnect(GanvNode* node); + +gboolean +ganv_node_is_selected(GanvNode* node); + +G_END_DECLS + +#endif /* GANV_NODE_H */ diff --git a/ganv/port.h b/ganv/port.h new file mode 100644 index 0000000..70e801e --- /dev/null +++ b/ganv/port.h @@ -0,0 +1,103 @@ +/* This file is part of Ganv. + * Copyright 2007-2014 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_PORT_H +#define GANV_PORT_H + +#include "ganv/box.h" + +struct _GanvModule; + +G_BEGIN_DECLS + +#define GANV_TYPE_PORT (ganv_port_get_type()) +#define GANV_PORT(obj) (GTK_CHECK_CAST((obj), GANV_TYPE_PORT, GanvPort)) +#define GANV_PORT_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), GANV_TYPE_PORT, GanvPortClass)) +#define GANV_IS_PORT(obj) (GTK_CHECK_TYPE((obj), GANV_TYPE_PORT)) +#define GANV_IS_PORT_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), GANV_TYPE_PORT)) +#define GANV_PORT_GET_CLASS(obj) (GTK_CHECK_GET_CLASS((obj), GANV_TYPE_PORT, GanvPortClass)) + +typedef struct _GanvPortClass GanvPortClass; +typedef struct _GanvPortImpl GanvPortImpl; + +struct _GanvPort { + GanvBox box; + GanvPortImpl* impl; +}; + +struct _GanvPortClass { + GanvBoxClass parent_class; + + /* Reserved for future expansion */ + gpointer spare_vmethods[4]; +}; + +GType ganv_port_get_type(void) G_GNUC_CONST; + +GanvPort* +ganv_port_new(GanvModule* module, + gboolean is_input, + const char* first_prop_name, ...); + +void +ganv_port_set_value_label(GanvPort* port, + const char* str); + +void +ganv_port_show_control(GanvPort* port); + +void +ganv_port_hide_control(GanvPort* port); + +void +ganv_port_set_control_is_toggle(GanvPort* port, + gboolean is_toggle); + +void +ganv_port_set_control_is_integer(GanvPort* port, + gboolean is_integer); + +void +ganv_port_set_control_value(GanvPort* port, + float value); + +void +ganv_port_set_control_min(GanvPort* port, + float min); + +void +ganv_port_set_control_max(GanvPort* port, + float max); + +double +ganv_port_get_natural_width(const GanvPort* port); + +/** + * ganv_port_get_module: + * @port: The port. + * + * Return value: (transfer none): The module @port is on. + */ +GanvModule* ganv_port_get_module(const GanvPort* port); + +float ganv_port_get_control_value(const GanvPort* port); +float ganv_port_get_control_min(const GanvPort* port); +float ganv_port_get_control_max(const GanvPort* port); +gboolean ganv_port_is_input(const GanvPort* port); +gboolean ganv_port_is_output(const GanvPort* port); + +G_END_DECLS + +#endif /* GANV_PORT_H */ diff --git a/ganv/text.h b/ganv/text.h new file mode 100644 index 0000000..52d87d6 --- /dev/null +++ b/ganv/text.h @@ -0,0 +1,52 @@ +/* This file is part of Ganv. + * Copyright 2007-2014 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_TEXT_H +#define GANV_TEXT_H + +#include "ganv/item.h" + +G_BEGIN_DECLS + +#define GANV_TYPE_TEXT (ganv_text_get_type()) +#define GANV_TEXT(obj) (GTK_CHECK_CAST((obj), GANV_TYPE_TEXT, GanvText)) +#define GANV_TEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), GANV_TYPE_TEXT, GanvTextClass)) +#define GANV_IS_TEXT(obj) (GTK_CHECK_TYPE((obj), GANV_TYPE_TEXT)) +#define GANV_IS_TEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), GANV_TYPE_TEXT)) +#define GANV_TEXT_GET_CLASS(obj) (GTK_CHECK_GET_CLASS((obj), GANV_TYPE_TEXT, GanvTextClass)) + +typedef struct _GanvText GanvText; +typedef struct _GanvTextClass GanvTextClass; +typedef struct _GanvTextImpl GanvTextImpl; + +struct _GanvText { + GanvItem item; + GanvTextImpl* impl; +}; + +struct _GanvTextClass { + GanvItemClass parent_class; + + /* Reserved for future expansion */ + gpointer spare_vmethodsx[4]; +}; + +GType ganv_text_get_type(void) G_GNUC_CONST; + +void ganv_text_layout(GanvText* text); + +G_END_DECLS + +#endif /* GANV_TEXT_H */ diff --git a/ganv/types.h b/ganv/types.h new file mode 100644 index 0000000..70c21ec --- /dev/null +++ b/ganv/types.h @@ -0,0 +1,26 @@ +/* This file is part of Ganv. + * Copyright 2007-2016 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_TYPES_H +#define GANV_TYPES_H + +typedef struct _GanvCanvas GanvCanvas; +typedef struct _GanvEdge GanvEdge; +typedef struct _GanvModule GanvModule; +typedef struct _GanvNode GanvNode; +typedef struct _GanvPort GanvPort; +typedef struct _GanvBox GanvBox; + +#endif /* GANV_TYPES_H */ diff --git a/ganv/types.hpp b/ganv/types.hpp new file mode 100644 index 0000000..280af16 --- /dev/null +++ b/ganv/types.hpp @@ -0,0 +1,30 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_TYPES_HPP +#define GANV_TYPES_HPP + +namespace Ganv { + +class Canvas; +class Edge; +class Item; +class Module; +class Node; +class Port; + +}; // namespace Ganv + +#endif // GANV_TYPES_HPP diff --git a/ganv/widget.h b/ganv/widget.h new file mode 100644 index 0000000..5aa81e9 --- /dev/null +++ b/ganv/widget.h @@ -0,0 +1,54 @@ +/* This file is part of Ganv. + * Copyright 2007-2014 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +/* Based on GnomeCanvasWidget, by Federico Mena <federico@nuclecu.unam.mx> + * Copyright 1997-2000 Free Software Foundation + */ + +#ifndef GANV_WIDGET_H +#define GANV_WIDGET_H + +#include "ganv/item.h" + +G_BEGIN_DECLS + +#define GANV_TYPE_WIDGET (ganv_widget_get_type ()) +#define GANV_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GANV_TYPE_WIDGET, GanvWidget)) +#define GANV_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GANV_TYPE_WIDGET, GanvWidgetClass)) +#define GANV_IS_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GANV_TYPE_WIDGET)) +#define GANV_IS_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GANV_TYPE_WIDGET)) +#define GANV_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GANV_TYPE_WIDGET, GanvWidgetClass)) + +typedef struct _GanvWidget GanvWidget; +typedef struct _GanvWidgetImpl GanvWidgetImpl; +typedef struct _GanvWidgetClass GanvWidgetClass; + +struct _GanvWidget { + GanvItem item; + GanvWidgetImpl* impl; +}; + +struct _GanvWidgetClass { + GanvItemClass parent_class; + + /* Reserved for future expansion */ + gpointer spare_vmethods [4]; +}; + +GType ganv_widget_get_type(void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GANV_WIDGET_H */ diff --git a/ganv/wrap.hpp b/ganv/wrap.hpp new file mode 100644 index 0000000..26e04d1 --- /dev/null +++ b/ganv/wrap.hpp @@ -0,0 +1,131 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_WRAP_HPP +#define GANV_WRAP_HPP + +#include <glib.h> + +#define CONNECT_PROP_SIGNAL(gobj, name, notify, handler) \ + g_signal_connect(gobj, "notify::" #name, \ + G_CALLBACK(notify), &_signal_##name); \ + _signal_##name.connect(sigc::mem_fun(this, handler)); + +#define SIGNAL1(name, argtype) \ +public: \ + virtual bool on_##name(argtype arg) { return true; } \ + sigc::signal<bool, argtype>& signal_##name() { return _signal_##name; } \ +private: \ + sigc::signal<bool, argtype> _signal_##name; + +#define RW_PROPERTY(type, name) \ + virtual type get_##name() const { \ + type value; \ + g_object_get(G_OBJECT(_gobj), #name, &value, NULL); \ + return value; \ + } \ + virtual void set_##name(type value) { \ + g_object_set(G_OBJECT(_gobj), #name, value, NULL); \ + } \ + SIGNAL1(name, type) \ + public: + +#define RW_OBJECT_PROPERTY(type, name) \ + type get_##name() const { \ + if (!_gobj) return NULL; \ + Ganv##type ptr; \ + g_object_get(G_OBJECT(_gobj), #name, &ptr, NULL); \ + return Glib::wrap(ptr); \ + } \ + void set_##name(type value) { \ + if (!_gobj) return; \ + ganv_item_set(GANV_ITEM(_gobj), \ + #name, value->gobj(), \ + NULL); \ + } + +#define METHOD0(prefix, name) \ + virtual void name() { \ + prefix##_##name(gobj()); \ + } + +#define METHOD1(prefix, name, t1, a1) \ + virtual void name(t1 a1) { \ + prefix##_##name(gobj(), a1); \ + } + +#define METHODRET0(prefix, ret, name) \ + virtual ret name() const { \ + return prefix##_##name(gobj()); \ + } + +#define METHODRET1(prefix, ret, name, t1, a1) \ + virtual ret name(t1 a1) { \ + return prefix##_##name(gobj(), a1); \ + } + +#define METHODRET2(prefix, ret, name, t1, a1, t2, a2) \ + virtual ret name(t1 a1, t2 a2) { \ + return prefix##_##name(gobj(), a1, a2); \ + } + +#define METHODRETWRAP0(prefix, ret, name) \ + virtual ret name() const { \ + if (gobj()) { \ + return Glib::wrap(prefix##_##name(gobj())); \ + } else { \ + return NULL; \ + } \ + } + +#define METHOD2(prefix, name, t1, a1, t2, a2) \ + virtual void name(t1 a1, t2 a2) { \ + prefix##_##name(gobj(), a1, a2); \ + } + +#define METHOD3(prefix, name, t1, a1, t2, a2, t3, a3) \ + virtual void name(t1 a1, t2 a2, t3 a3) { \ + prefix##_##name(gobj(), a1, a2, a3); \ + } + +#define METHOD4(prefix, name, t1, a1, t2, a2, t3, a3, t4, a4) \ + virtual void name(t1 a1, t2 a2, t3 a3, t4 a4) { \ + prefix##_##name(gobj(), a1, a2, a3, a4); \ + } + +#define GANV_GLIB_WRAP(Name) \ + namespace Ganv { \ + class Name; \ + } \ + namespace Glib { \ + /** Return a Ganv::CPPType wrapper for a CType. */ \ + static inline Ganv::Name* \ + wrap(Ganv##Name* gobj) \ + { \ + if (gobj) { \ + return (Ganv::Name*)ganv_item_get_wrapper(GANV_ITEM(gobj)); \ + } else { \ + return NULL; \ + } \ + } \ + /** Return a Ganv::CPPType wrapper for a CType. */ \ + static inline const Ganv::Name* \ + wrap(const Ganv##Name* gobj) \ + { \ + return wrap((Ganv##Name*)gobj); \ + } \ + } + +#endif // GANV_WRAP_HPP diff --git a/gtkdoc.sh b/gtkdoc.sh new file mode 100755 index 0000000..42ff39d --- /dev/null +++ b/gtkdoc.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +DOC_MODULE=ganv + +pwd=`pwd` +echo "gtkdoc.sh: Entering directory \`$pwd/docs'" + +mkdir -p docs +cd docs + +export CFLAGS="`pkg-config --cflags ganv-1`" +export LDFLAGS="`pkg-config --libs ganv-1`" + +# Sources have changed + +gtkdoc-scan --rebuild-sections --rebuild-types --ignore-headers=types.h --module=$DOC_MODULE --source-dir=../ganv +gtkdoc-scangobj --module=$DOC_MODULE +gtkdoc-mkdb --module=$DOC_MODULE --output-format=xml --source-dir=../ganv + +# XML files have changed +mkdir -p html +cd html && gtkdoc-mkhtml $DOC_MODULE ../ganv-docs.xml && cd - +gtkdoc-fixxref --module=$DOC_MODULE --module-dir=html + +echo "gtkdoc.sh: Leaving directory \`$pwd/docs'" +cd - diff --git a/src/Canvas.cpp b/src/Canvas.cpp new file mode 100644 index 0000000..987eb6c --- /dev/null +++ b/src/Canvas.cpp @@ -0,0 +1,4193 @@ +/* This file is part of Ganv. + * Copyright 2007-2016 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +/* Parts based on GnomeCanvas, by Federico Mena <federico@nuclecu.unam.mx> + * and Raph Levien <raph@gimp.org> + * Copyright 1997-2000 Free Software Foundation + */ + +#define _POSIX_C_SOURCE 200809L // strdup +#define _XOPEN_SOURCE 600 // isascii on BSD + +#include <math.h> +#include <stdio.h> +#include <string.h> + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <iostream> +#include <map> +#include <set> +#include <sstream> +#include <string> +#include <vector> + +#include <cairo.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <gtk/gtkstyle.h> +#include <gtkmm/widget.h> + +#include "ganv/Canvas.hpp" +#include "ganv/Circle.hpp" +#include "ganv/Edge.hpp" +#include "ganv/Module.hpp" +#include "ganv/Port.hpp" +#include "ganv/box.h" +#include "ganv/canvas.h" +#include "ganv/edge.h" +#include "ganv/group.h" +#include "ganv/node.h" +#include "ganv_config.h" + +#include "./color.h" +#include "./ganv-marshal.h" +#include "./ganv-private.h" +#include "./gettext.h" + +#if defined(HAVE_AGRAPH_2_20) || defined(HAVE_AGRAPH_2_30) +// Deal with graphviz API amateur hour... +# define _DLL_BLD 0 +# define _dll_import 0 +# define _BLD_cdt 0 +# define _PACKAGE_ast 0 +# include <gvc.h> +#endif +#ifdef GANV_FDGL +# include "./fdgl.hpp" +#endif + +#define CANVAS_IDLE_PRIORITY (GDK_PRIORITY_REDRAW - 5) + +static const double GANV_CANVAS_PAD = 8.0; + +typedef struct { + int x; + int y; + int width; + int height; +} IRect; + +extern "C" { +static void add_idle(GanvCanvas* canvas); +static void ganv_canvas_destroy(GtkObject* object); +static void ganv_canvas_map(GtkWidget* widget); +static void ganv_canvas_unmap(GtkWidget* widget); +static void ganv_canvas_realize(GtkWidget* widget); +static void ganv_canvas_unrealize(GtkWidget* widget); +static void ganv_canvas_size_allocate(GtkWidget* widget, + GtkAllocation* allocation); +static gint ganv_canvas_button(GtkWidget* widget, + GdkEventButton* event); +static gint ganv_canvas_motion(GtkWidget* widget, + GdkEventMotion* event); +static gint ganv_canvas_expose(GtkWidget* widget, + GdkEventExpose* event); +static gboolean ganv_canvas_key(GtkWidget* widget, + GdkEventKey* event); +static gboolean ganv_canvas_scroll(GtkWidget* widget, + GdkEventScroll* event); +static gint ganv_canvas_crossing(GtkWidget* widget, + GdkEventCrossing* event); +static gint ganv_canvas_focus_in(GtkWidget* widget, + GdkEventFocus* event); +static gint ganv_canvas_focus_out(GtkWidget* widget, + GdkEventFocus* event); + +static GtkLayoutClass* canvas_parent_class; +} + +static guint signal_connect; +static guint signal_disconnect; + +static GEnumValue dir_values[3]; + +typedef std::set<GanvNode*> Items; + +#define FOREACH_ITEM(items, i) \ + for (Items::const_iterator i = items.begin(); i != items.end(); ++i) + +#define FOREACH_ITEM_MUT(items, i) \ + for (Items::iterator i = items.begin(); i != items.end(); ++i) + +#define FOREACH_EDGE(edges, i) \ + for (GanvCanvasImpl::Edges::const_iterator i = edges.begin(); \ + i != edges.end(); \ + ++i) + +#define FOREACH_EDGE_MUT(edges, i) \ + for (GanvCanvasImpl::Edges::iterator i = edges.begin(); \ + i != edges.end(); \ + ++i) + +#define FOREACH_SELECTED_EDGE(edges, i) \ + for (GanvCanvasImpl::SelectedEdges::const_iterator i = edges.begin(); \ + i != edges.end(); \ + ++i) + +#define FOREACH_SELECTED_PORT(p) \ + for (SelectedPorts::iterator p = _selected_ports.begin(); \ + p != _selected_ports.end(); ++p) + +#if defined(HAVE_AGRAPH_2_20) || defined(HAVE_AGRAPH_2_30) +class GVNodes : public std::map<GanvNode*, Agnode_t*> { +public: + GVNodes() : gvc(0), G(0) {} + + void cleanup() { + gvFreeLayout(gvc, G); + agclose (G); + gvc = 0; + G = 0; + } + + GVC_t* gvc; + Agraph_t* G; +}; +#endif + +static const uint32_t SELECT_RECT_FILL_COLOUR = 0x2E444577; +static const uint32_t SELECT_RECT_BORDER_COLOUR = 0x2E4445FF; + +/* Order edges by (tail, head) */ +struct TailHeadOrder { + inline bool operator()(const GanvEdge* a, const GanvEdge* b) const { + return ((a->impl->tail < b->impl->tail) + || (a->impl->tail == b->impl->tail + && a->impl->head < b->impl->head)); + } +}; + +/* Order edges by (head, tail) */ +struct HeadTailOrder { + inline bool operator()(const GanvEdge* a, const GanvEdge* b) const { + return ((a->impl->head < b->impl->head) + || (a->impl->head == b->impl->head + && a->impl->tail < b->impl->tail)); + } +}; + +/* Callback used when the root item of a canvas is destroyed. The user should + * never ever do this, so we panic if this happens. + */ +static void +panic_root_destroyed(GtkObject* object, gpointer data) +{ + g_error("Eeeek, root item %p of canvas %p was destroyed!", (void*)object, data); +} + +struct GanvCanvasImpl { + GanvCanvasImpl(GanvCanvas* canvas) + : _gcanvas(canvas) + , _wrapper(NULL) + , _connect_port(NULL) + , _last_selected_port(NULL) + , _drag_edge(NULL) + , _drag_node(NULL) + , _select_rect(NULL) + , _select_start_x(0.0) + , _select_start_y(0.0) + , _drag_state(NOT_DRAGGING) + { + this->root = GANV_ITEM(g_object_new(ganv_group_get_type(), NULL)); + this->root->impl->canvas = canvas; + g_object_ref_sink(this->root); + + this->direction = GANV_DIRECTION_RIGHT; + this->width = 0; + this->height = 0; + + this->redraw_region = NULL; + this->current_item = NULL; + this->new_current_item = NULL; + this->grabbed_item = NULL; + this->focused_item = NULL; + this->pixmap_gc = NULL; + + this->pick_event.type = GDK_LEAVE_NOTIFY; + this->pick_event.crossing.x = 0; + this->pick_event.crossing.y = 0; + + this->scroll_x1 = 0.0; + this->scroll_y1 = 0.0; + this->scroll_x2 = canvas->layout.width; + this->scroll_y2 = canvas->layout.height; + + this->pixels_per_unit = 1.0; + this->font_size = ganv_canvas_get_default_font_size(canvas); + + this->idle_id = 0; + this->root_destroy_id = g_signal_connect( + this->root, "destroy", G_CALLBACK(panic_root_destroyed), canvas); + + this->redraw_x1 = 0; + this->redraw_y1 = 0; + this->redraw_x2 = 0; + this->redraw_y2 = 0; + + this->draw_xofs = 0; + this->draw_yofs = 0; + this->zoom_xofs = 0; + this->zoom_yofs = 0; + + this->state = 0; + this->grabbed_event_mask = 0; + + this->center_scroll_region = FALSE; + this->need_update = FALSE; + this->need_redraw = FALSE; + this->need_repick = TRUE; + this->left_grabbed_item = FALSE; + this->in_repick = FALSE; + this->locked = FALSE; + this->exporting = FALSE; + +#ifdef GANV_FDGL + this->layout_idle_id = 0; + this->layout_energy = 0.4; + this->sprung_layout = FALSE; +#endif + + _animate_idle_id = 0; + + _port_order.port_cmp = NULL; + _port_order.data = NULL; + + gtk_layout_set_hadjustment(GTK_LAYOUT(canvas), NULL); + gtk_layout_set_vadjustment(GTK_LAYOUT(canvas), NULL); + + _move_cursor = gdk_cursor_new(GDK_FLEUR); + } + + ~GanvCanvasImpl() + { + if (_animate_idle_id) { + g_source_remove(_animate_idle_id); + _animate_idle_id = 0; + } + + while (g_idle_remove_by_data(this)) {} + ganv_canvas_clear(_gcanvas); + gdk_cursor_unref(_move_cursor); + } + + static gboolean on_animate_timeout(gpointer impl); + +#ifdef GANV_FDGL + static gboolean on_layout_timeout(gpointer impl) { + return ((GanvCanvasImpl*)impl)->layout_iteration(); + } + + static void on_layout_done(gpointer impl) { + ((GanvCanvasImpl*)impl)->layout_idle_id = 0; + } + + gboolean layout_iteration(); + gboolean layout_calculate(double dur, bool update); +#endif + + void unselect_ports(); + +#if defined(HAVE_AGRAPH_2_20) || defined(HAVE_AGRAPH_2_30) + GVNodes layout_dot(const std::string& filename); +#endif + + typedef std::set<GanvEdge*, TailHeadOrder> Edges; + typedef std::set<GanvEdge*, HeadTailOrder> DstEdges; + typedef std::set<GanvEdge*> SelectedEdges; + typedef std::set<GanvPort*> SelectedPorts; + + Edges::const_iterator first_edge_from(const GanvNode* src); + DstEdges::const_iterator first_edge_to(const GanvNode* dst); + + void select_port(GanvPort* p, bool unique=false); + void select_port_toggle(GanvPort* p, int mod_state); + void unselect_port(GanvPort* p); + void selection_joined_with(GanvPort* port); + void join_selection(); + + GanvNode* get_node_at(double x, double y); + + bool on_event(GdkEvent* event); + + bool scroll_drag_handler(GdkEvent* event); + bool select_drag_handler(GdkEvent* event); + bool connect_drag_handler(GdkEvent* event); + void end_connect_drag(); + + /* + Event handler for ports. + + This must be implemented as a Canvas method since port event handling + depends on shared data (for selection and connecting). This function + should only be used by Port implementations. + */ + bool port_event(GdkEvent* event, GanvPort* port); + + void ports_joined(GanvPort* port1, GanvPort* port2); + void port_clicked(GdkEvent* event, GanvPort* port); + void highlight_port(GanvPort* port, bool highlight); + + void move_contents_to_internal(double x, double y, double min_x, double min_y); + + GanvCanvas* _gcanvas; + Ganv::Canvas* _wrapper; + + Items _items; ///< Items on this canvas + Edges _edges; ///< Edges ordered (src, dst) + DstEdges _dst_edges; ///< Edges ordered (dst, src) + Items _selected_items; ///< Currently selected items + SelectedEdges _selected_edges; ///< Currently selected edges + + SelectedPorts _selected_ports; ///< Selected ports (hilited red) + GanvPort* _connect_port; ///< Port for which a edge is being made + GanvPort* _last_selected_port; + GanvEdge* _drag_edge; + GanvNode* _drag_node; + + GanvBox* _select_rect; ///< Rectangle for drag selection + double _select_start_x; ///< Selection drag start x coordinate + double _select_start_y; ///< Selection drag start y coordinate + + enum DragState { NOT_DRAGGING, EDGE, SCROLL, SELECT }; + DragState _drag_state; + + GdkCursor* _move_cursor; + guint _animate_idle_id; + + PortOrderCtx _port_order; + + /* Root canvas item */ + GanvItem* root; + + /* Flow direction */ + GanvDirection direction; + + /* Canvas width */ + double width; + + /* Canvas height */ + double height; + + /* Region that needs redrawing (list of rectangles) */ + GSList* redraw_region; + + /* The item containing the mouse pointer, or NULL if none */ + GanvItem* current_item; + + /* Item that is about to become current (used to track deletions and such) */ + GanvItem* new_current_item; + + /* Item that holds a pointer grab, or NULL if none */ + GanvItem* grabbed_item; + + /* If non-NULL, the currently focused item */ + GanvItem* focused_item; + + /* GC for temporary draw pixmap */ + GdkGC* pixmap_gc; + + /* Event on which selection of current item is based */ + GdkEvent pick_event; + + /* Scrolling region */ + double scroll_x1; + double scroll_y1; + double scroll_x2; + double scroll_y2; + + /* Scaling factor to be used for display */ + double pixels_per_unit; + + /* Font size in points */ + double font_size; + + /* Idle handler ID */ + guint idle_id; + + /* Signal handler ID for destruction of the root item */ + guint root_destroy_id; + + /* Area that is being redrawn. Contains (x1, y1) but not (x2, y2). + * Specified in canvas pixel coordinates. + */ + int redraw_x1; + int redraw_y1; + int redraw_x2; + int redraw_y2; + + /* Offsets of the temprary drawing pixmap */ + int draw_xofs; + int draw_yofs; + + /* Internal pixel offsets when zoomed out */ + int zoom_xofs; + int zoom_yofs; + + /* Last known modifier state, for deferred repick when a button is down */ + int state; + + /* Event mask specified when grabbing an item */ + guint grabbed_event_mask; + + /* Whether the canvas should center the scroll region in the middle of + * the window if the scroll region is smaller than the window. + */ + gboolean center_scroll_region; + + /* Whether items need update at next idle loop iteration */ + gboolean need_update; + + /* Whether the canvas needs redrawing at the next idle loop iteration */ + gboolean need_redraw; + + /* Whether current item will be repicked at next idle loop iteration */ + gboolean need_repick; + + /* For use by internal pick_current_item() function */ + gboolean left_grabbed_item; + + /* For use by internal pick_current_item() function */ + gboolean in_repick; + + /* Disable changes to canvas */ + gboolean locked; + + /* True if the current draw is an export */ + gboolean exporting; + +#ifdef GANV_FDGL + guint layout_idle_id; + gdouble layout_energy; + gboolean sprung_layout; +#endif +}; + +typedef struct { + GanvItem item; + GanvEdgeImpl* impl; + GanvEdgeImpl impl_data; +} GanvEdgeKey; + +static void +make_edge_search_key(GanvEdgeKey* key, + const GanvNode* tail, + const GanvNode* head) +{ + memset(key, '\0', sizeof(GanvEdgeKey)); + key->impl = &key->impl_data; + key->impl->tail = const_cast<GanvNode*>(tail); + key->impl->head = const_cast<GanvNode*>(head); +} + +GanvCanvasImpl::Edges::const_iterator +GanvCanvasImpl::first_edge_from(const GanvNode* tail) +{ + GanvEdgeKey key; + make_edge_search_key(&key, tail, NULL); + return _edges.lower_bound((GanvEdge*)&key); +} + +GanvCanvasImpl::DstEdges::const_iterator +GanvCanvasImpl::first_edge_to(const GanvNode* head) +{ + GanvEdgeKey key; + make_edge_search_key(&key, NULL, head); + return _dst_edges.lower_bound((GanvEdge*)&key); +} + +static void +select_if_tail_is_selected(GanvEdge* edge, void* data) +{ + GanvNode* tail = edge->impl->tail; + gboolean selected; + g_object_get(tail, "selected", &selected, NULL); + if (!selected && GANV_IS_PORT(tail)) { + g_object_get(ganv_port_get_module(GANV_PORT(tail)), + "selected", &selected, NULL); + } + + if (selected) { + ganv_edge_select(edge); + } +} + +static void +select_if_head_is_selected(GanvEdge* edge, void* data) +{ + GanvNode* head = edge->impl->head; + gboolean selected; + g_object_get(head, "selected", &selected, NULL); + if (!selected && GANV_IS_PORT(head)) { + g_object_get(ganv_port_get_module(GANV_PORT(head)), + "selected", &selected, NULL); + } + + if (selected) { + ganv_edge_set_selected(edge, TRUE); + } +} + +static void +select_edges(GanvPort* port, void* data) +{ + GanvCanvasImpl* impl = (GanvCanvasImpl*)data; + if (port->impl->is_input) { + ganv_canvas_for_each_edge_to(impl->_gcanvas, + GANV_NODE(port), + select_if_tail_is_selected, + NULL); + } else { + ganv_canvas_for_each_edge_from(impl->_gcanvas, + GANV_NODE(port), + select_if_head_is_selected, + NULL); + } +} + +#if defined(HAVE_AGRAPH_2_20) || defined(HAVE_AGRAPH_2_30) +static void +gv_set(void* subject, const char* key, double value) +{ + std::ostringstream ss; + ss << value; + agsafeset(subject, (char*)key, (char*)ss.str().c_str(), (char*)""); +} + +GVNodes +GanvCanvasImpl::layout_dot(const std::string& filename) +{ + GVNodes nodes; + + const double dpi = gdk_screen_get_resolution(gdk_screen_get_default()); + + GVC_t* gvc = gvContext(); + +#ifndef HAVE_AGRAPH_2_30 +#define agstrdup_html(g, str) agstrdup_html(str) +#define agedge(g, t, h, name, flag) agedge(g, t, h) +#define agnode(g, name, flag) agnode(g, name) +#define agattr(g, t, k, v) agraphattr(g, k, v) + Agraph_t* G = agopen((char*)"g", AGDIGRAPH); +#else + Agraph_t* G = agopen((char*)"g", Agdirected, NULL); +#endif + + agsafeset(G, (char*)"splines", (char*)"false", (char*)""); + agsafeset(G, (char*)"compound", (char*)"true", (char*)""); + agsafeset(G, (char*)"remincross", (char*)"true", (char*)""); + agsafeset(G, (char*)"overlap", (char*)"scale", (char*)""); + agsafeset(G, (char*)"nodesep", (char*)"0.05", (char*)""); + gv_set(G, "fontsize", ganv_canvas_get_font_size(_gcanvas)); + gv_set(G, "dpi", dpi); + + nodes.gvc = gvc; + nodes.G = G; + + const bool flow_right = _gcanvas->impl->direction; + if (flow_right) { + agattr(G, AGRAPH, (char*)"rankdir", (char*)"LR"); + } else { + agattr(G, AGRAPH, (char*)"rankdir", (char*)"TD"); + } + + unsigned id = 0; + std::ostringstream ss; + FOREACH_ITEM(_items, i) { + ss.str(""); + ss << "n" << id++; + const std::string node_id = ss.str(); + + Agnode_t* node = agnode(G, strdup(node_id.c_str()), true); + nodes.insert(std::make_pair(*i, node)); + + if (GANV_IS_MODULE(*i)) { + GanvModule* const m = GANV_MODULE(*i); + + agsafeset(node, (char*)"shape", (char*)"plaintext", (char*)""); + gv_set(node, "width", ganv_box_get_width(GANV_BOX(*i)) / dpi); + gv_set(node, "height", ganv_box_get_height(GANV_BOX(*i)) / dpi); + + std::string inputs; // Down flow + std::string outputs; // Down flow + std::string ports; // Right flow + unsigned n_inputs = 0; + unsigned n_outputs = 0; + for (size_t i = 0; i < ganv_module_num_ports(m); ++i) { + GanvPort* port = ganv_module_get_port(m, i); + ss.str(""); + ss << port; + + if (port->impl->is_input) { + ++n_inputs; + } else { + ++n_outputs; + } + + std::string cell = std::string("<TD PORT=\"") + ss.str() + "\""; + + cell += " FIXEDSIZE=\"TRUE\""; + ss.str(""); + ss << ganv_box_get_width(GANV_BOX(port));// / dpp * 1.3333333; + cell += " WIDTH=\"" + ss.str() + "\""; + + ss.str(""); + ss << ganv_box_get_height(GANV_BOX(port));// / dpp * 1.333333; + cell += " HEIGHT=\"" + ss.str() + "\""; + + cell += ">"; + const char* label = ganv_node_get_label(GANV_NODE(port)); + if (label && flow_right) { + cell += label; + } + cell += "</TD>"; + + if (flow_right) { + ports += "<TR>" + cell + "</TR>"; + } else if (port->impl->is_input) { + inputs += cell; + } else { + outputs += cell; + } + + nodes.insert(std::make_pair(GANV_NODE(port), node)); + } + + const unsigned n_cols = std::max(n_inputs, n_outputs); + + std::string html = "<TABLE CELLPADDING=\"0\" CELLSPACING=\"0\">"; + + // Input row (down flow only) + if (!inputs.empty()) { + for (unsigned i = n_inputs; i < n_cols + 1; ++i) { + inputs += "<TD BORDER=\"0\"></TD>"; + } + html += std::string("<TR>") + inputs + "</TR>"; + } + + // Label row + std::stringstream colspan; + colspan << (flow_right ? 1 : (n_cols + 1)); + html += std::string("<TR><TD BORDER=\"0\" CELLPADDING=\"2\" COLSPAN=\"") + + colspan.str() + + "\">"; + const char* label = ganv_node_get_label(GANV_NODE(m)); + if (label) { + html += label; + } + html += "</TD></TR>"; + + // Ports rows (right flow only) + if (!ports.empty()) { + html += ports; + } + + // Output row (down flow only) + if (!outputs.empty()) { + for (unsigned i = n_outputs; i < n_cols + 1; ++i) { + outputs += "<TD BORDER=\"0\"></TD>"; + } + html += std::string("<TR>") + outputs + "</TR>"; + } + html += "</TABLE>"; + + char* html_label_str = agstrdup_html(G, (char*)html.c_str()); + + agsafeset(node, (char*)"label", (char*)html_label_str, (char*)""); + } else if (GANV_IS_CIRCLE(*i)) { + agsafeset(node, (char*)"shape", (char*)"circle", (char*)""); + agsafeset(node, (char*)"fixedsize", (char*)"true", (char*)""); + agsafeset(node, (char*)"margin", (char*)"0.0,0.0", (char*)""); + + const double radius = ganv_circle_get_radius(GANV_CIRCLE(*i)); + const double penwidth = ganv_node_get_border_width(GANV_NODE(*i)); + const double span = (radius + penwidth) * 2.3 / dpi; + gv_set(node, (char*)"width", span); + gv_set(node, (char*)"height", span); + gv_set(node, (char*)"penwidth", penwidth); + + if (ganv_node_get_dash_length(GANV_NODE(*i)) > 0.0) { + agsafeset(node, (char*)"style", (char*)"dashed", (char*)""); + } + + const char* label = ganv_node_get_label(GANV_NODE(*i)); + if (label) { + agsafeset(node, (char*)"label", (char*)label, (char*)""); + } else { + agsafeset(node, (char*)"label", (char*)"", (char*)""); + } + } else { + std::cerr << "Unable to arrange item of unknown type" << std::endl; + } + } + + FOREACH_EDGE(_edges, i) { + const GanvEdge* const edge = *i; + GVNodes::iterator tail_i = nodes.find(edge->impl->tail); + GVNodes::iterator head_i = nodes.find(edge->impl->head); + + if (tail_i != nodes.end() && head_i != nodes.end()) { + Agedge_t* e = agedge(G, tail_i->second, head_i->second, NULL, true); + if (GANV_IS_PORT(edge->impl->tail)) { + ss.str((char*)""); + ss << edge->impl->tail << (flow_right ? ":e" : ":s"); + agsafeset(e, (char*)"tailport", (char*)ss.str().c_str(), (char*)""); + } + if (GANV_IS_PORT(edge->impl->head)) { + ss.str((char*)""); + ss << edge->impl->head << (flow_right ? ":w" : ":n"); + agsafeset(e, (char*)"headport", (char*)ss.str().c_str(), (char*)""); + } + if (!ganv_edge_get_constraining(edge)) { + agsafeset(e, (char*)"constraint", (char*)"false", (char*)""); + } + } else { + std::cerr << "Unable to find graphviz node" << std::endl; + } + } + + // Add edges between partners to have them lined up as if connected + for (GVNodes::iterator i = nodes.begin(); i != nodes.end(); ++i) { + GanvNode* partner = ganv_node_get_partner(i->first); + if (partner) { + GVNodes::iterator p = nodes.find(partner); + if (p != nodes.end()) { + Agedge_t* e = agedge(G, i->second, p->second, NULL, true); + agsafeset(e, (char*)"style", (char*)"invis", (char*)""); + } + } + } + + gvLayout(gvc, G, (char*)"dot"); + FILE* tmp = fopen("/dev/null", "w"); + gvRender(gvc, G, (char*)"dot", tmp); + fclose(tmp); + + if (filename != "") { + FILE* fd = fopen(filename.c_str(), "w"); + gvRender(gvc, G, (char*)"dot", fd); + fclose(fd); + } + + return nodes; +} +#endif + +inline uint64_t +get_monotonic_time() +{ +#if GLIB_CHECK_VERSION(2, 28, 0) + return g_get_monotonic_time(); +#else + GTimeVal time; + g_get_current_time(&time); + return time.tv_sec + time.tv_usec; +#endif +} + +#ifdef GANV_FDGL + +inline Region +get_region(GanvNode* node) +{ + GanvItem* item = &node->item; + + double x1, y1, x2, y2; + ganv_item_get_bounds(item, &x1, &y1, &x2, &y2); + + Region reg; + ganv_item_get_bounds(item, ®.pos.x, ®.pos.y, ®.area.x, ®.area.y); + reg.area.x = x2 - x1; + reg.area.y = y2 - y1; + reg.pos.x = item->impl->x + (reg.area.x / 2.0); + reg.pos.y = item->impl->y + (reg.area.y / 2.0); + + // No need for i2w here since we only care about top-level items + return reg; +} + +inline void +apply_force(GanvNode* a, GanvNode* b, const Vector& f) +{ + a->impl->force = vec_add(a->impl->force, f); + b->impl->force = vec_sub(b->impl->force, f); +} + +gboolean +GanvCanvasImpl::layout_iteration() +{ + if (_drag_state == EDGE) { + return FALSE; // Canvas is locked, halt layout process + } else if (!sprung_layout) { + return FALSE; // We shouldn't be running at all + } + + static const double T_PER_US = .0001; // Sym time per real microsecond + + static uint64_t prev = 0; // Previous iteration time + + const uint64_t now = get_monotonic_time(); + const double time_to_run = std::min((now - prev) * T_PER_US, 10.0); + + prev = now; + + const double QUANTUM = 0.05; + double sym_time = 0.0; + while (sym_time + QUANTUM < time_to_run) { + if (!layout_calculate(QUANTUM, FALSE)) { + break; + } + sym_time += QUANTUM; + } + + return layout_calculate(QUANTUM, TRUE); +} + +gboolean +GanvCanvasImpl::layout_calculate(double dur, bool update) +{ + // A light directional force to push sources to the top left + static const double DIR_MAGNITUDE = -1000.0; + Vector dir = { 0.0, 0.0 }; + switch (_gcanvas->impl->direction) { + case GANV_DIRECTION_RIGHT: dir.x = DIR_MAGNITUDE; break; + case GANV_DIRECTION_DOWN: dir.y = DIR_MAGNITUDE; break; + } + + // Calculate attractive spring forces for edges + FOREACH_EDGE(_edges, i) { + const GanvEdge* const edge = *i; + if (!ganv_edge_get_constraining(edge)) { + continue; + } + + GanvNode* tail = ganv_edge_get_tail(edge); + GanvNode* head = ganv_edge_get_head(edge); + if (GANV_IS_PORT(tail)) { + tail = GANV_NODE(ganv_port_get_module(GANV_PORT(tail))); + } + if (GANV_IS_PORT(head)) { + head = GANV_NODE(ganv_port_get_module(GANV_PORT(head))); + } + if (tail == head) { + continue; + } + + head->impl->connected = tail->impl->connected = TRUE; + + GanvEdgeCoords coords; + ganv_edge_get_coords(edge, &coords); + + const Vector tpos = { coords.x1, coords.y1 }; + const Vector hpos = { coords.x2, coords.y2 }; + apply_force(tail, head, edge_force(dir, hpos, tpos)); + } + + // Calculate repelling forces between nodes + FOREACH_ITEM(_items, i) { + if (!GANV_IS_MODULE(*i) && !GANV_IS_CIRCLE(*i)) { + continue; + } + + GanvNode* const node = *i; + GanvNode* partner = ganv_node_get_partner(node); + if (!partner && !node->impl->connected) { + continue; + } + + const Region reg = get_region(node); + if (partner) { + // Add fake long spring to partner to line up as if connected + const Region preg = get_region(partner); + apply_force(node, partner, edge_force(dir, preg.pos, reg.pos)); + } + + /* Add tide force which pulls all objects as if the layout is happening + on a flowing river surface. This prevents disconnected components + from being ejected, since at some point the tide force will be + greater than distant repelling charges. */ + const Vector mouth = { -100000.0, -100000.0 }; + node->impl->force = vec_add( + node->impl->force, + tide_force(mouth, reg.pos, 4000000000000.0)); + + // Add slight noise to force to limit oscillation + const Vector noise = { rand() / (float)RAND_MAX * 128.0, + rand() / (float)RAND_MAX * 128.0 }; + node->impl->force = vec_add(noise, node->impl->force); + + FOREACH_ITEM(_items, j) { + if (i == j || (!GANV_IS_MODULE(*i) && !GANV_IS_CIRCLE(*i))) { + continue; + } + apply_force(node, *j, repel_force(reg, get_region(*j))); + } + } + + // Update positions based on calculated forces + size_t n_moved = 0; + FOREACH_ITEM(_items, i) { + if (!GANV_IS_MODULE(*i) && !GANV_IS_CIRCLE(*i)) { + continue; + } + + GanvNode* const node = *i; + + if (node->impl->grabbed || + (!node->impl->connected && !ganv_node_get_partner(node))) { + node->impl->vel.x = 0.0; + node->impl->vel.y = 0.0; + } else { + node->impl->vel = vec_add(node->impl->vel, + vec_mult(node->impl->force, dur)); + node->impl->vel = vec_mult(node->impl->vel, layout_energy); + + static const double MAX_VEL = 1000.0; + static const double MIN_COORD = 4.0; + + // Clamp velocity + const double vel_mag = vec_mag(node->impl->vel); + if (vel_mag > MAX_VEL) { + node->impl->vel = vec_mult( + vec_mult(node->impl->vel, 1.0 / vel_mag), + MAX_VEL); + } + + // Update position + GanvItem* item = &node->item; + const double x0 = item->impl->x; + const double y0 = item->impl->y; + const Vector dpos = vec_mult(node->impl->vel, dur); + + item->impl->x = std::max(MIN_COORD, item->impl->x + dpos.x); + item->impl->y = std::max(MIN_COORD, item->impl->y + dpos.y); + + if (update) { + ganv_item_request_update(item); + item->impl->canvas->impl->need_repick = TRUE; + } + + if (lrint(x0) != lrint(item->impl->x) || lrint(y0) != lrint(item->impl->y)) { + ++n_moved; + } + } + + // Reset forces for next time + node->impl->force.x = 0.0; + node->impl->force.y = 0.0; + node->impl->connected = FALSE; + } + + if (update) { + // Now update edge positions to reflect new node positions + FOREACH_EDGE(_edges, i) { + GanvEdge* const edge = *i; + ganv_edge_update_location(edge); + } + } + + layout_energy *= 0.999; + return n_moved > 0; +} + +#endif // GANV_FDGL + +void +GanvCanvasImpl::select_port(GanvPort* p, bool unique) +{ + if (unique) { + unselect_ports(); + } + g_object_set(G_OBJECT(p), "selected", TRUE, NULL); + _selected_ports.insert(p); + _last_selected_port = p; +} + +void +GanvCanvasImpl::select_port_toggle(GanvPort* port, int mod_state) +{ + gboolean selected; + g_object_get(G_OBJECT(port), "selected", &selected, NULL); + if ((mod_state & GDK_CONTROL_MASK)) { + if (selected) + unselect_port(port); + else + select_port(port); + } else if ((mod_state & GDK_SHIFT_MASK)) { + GanvModule* const m = ganv_port_get_module(port); + if (_last_selected_port && m + && ganv_port_get_module(_last_selected_port) == m) { + // Pivot around _last_selected_port in a single pass over module ports each click + GanvPort* old_last_selected = _last_selected_port; + GanvPort* first = NULL; + bool done = false; + for (size_t i = 0; i < ganv_module_num_ports(m); ++i) { + GanvPort* const p = ganv_module_get_port(m, i); + if (!first && !done && (p == _last_selected_port || p == port)) { + first = p; + } + + if (first && !done && p->impl->is_input == first->impl->is_input) { + select_port(p, false); + } else { + unselect_port(p); + } + + if (p != first && (p == old_last_selected || p == port)) { + done = true; + } + } + _last_selected_port = old_last_selected; + } else { + if (selected) { + unselect_port(port); + } else { + select_port(port); + } + } + } else { + if (selected) { + unselect_ports(); + } else { + select_port(port, true); + } + } +} + +void +GanvCanvasImpl::unselect_port(GanvPort* p) +{ + _selected_ports.erase(p); + g_object_set(G_OBJECT(p), "selected", FALSE, NULL); + if (_last_selected_port == p) { + _last_selected_port = NULL; + } +} + +void +GanvCanvasImpl::selection_joined_with(GanvPort* port) +{ + FOREACH_SELECTED_PORT(i) + ports_joined(*i, port); +} + +void +GanvCanvasImpl::join_selection() +{ + std::vector<GanvPort*> inputs; + std::vector<GanvPort*> outputs; + FOREACH_SELECTED_PORT(i) { + if ((*i)->impl->is_input) { + inputs.push_back(*i); + } else { + outputs.push_back(*i); + } + } + + if (inputs.size() == 1) { // 1 -> n + for (size_t i = 0; i < outputs.size(); ++i) + ports_joined(inputs[0], outputs[i]); + } else if (outputs.size() == 1) { // n -> 1 + for (size_t i = 0; i < inputs.size(); ++i) + ports_joined(inputs[i], outputs[0]); + } else { // n -> m + size_t num_to_connect = std::min(inputs.size(), outputs.size()); + for (size_t i = 0; i < num_to_connect; ++i) { + ports_joined(inputs[i], outputs[i]); + } + } +} + +GanvNode* +GanvCanvasImpl::get_node_at(double x, double y) +{ + GanvItem* item = ganv_canvas_get_item_at(GANV_CANVAS(_gcanvas), x, y); + while (item) { + if (GANV_IS_NODE(item)) { + return GANV_NODE(item); + } else { + item = item->impl->parent; + } + } + + return NULL; +} + +bool +GanvCanvasImpl::on_event(GdkEvent* event) +{ + static const int scroll_increment = 10; + int scroll_x, scroll_y; + + bool handled = false; + switch (event->type) { + case GDK_KEY_PRESS: + handled = true; + ganv_canvas_get_scroll_offsets(GANV_CANVAS(_gcanvas), &scroll_x, &scroll_y); + switch (event->key.keyval) { + case GDK_Up: + scroll_y -= scroll_increment; + break; + case GDK_Down: + scroll_y += scroll_increment; + break; + case GDK_Left: + scroll_x -= scroll_increment; + break; + case GDK_Right: + scroll_x += scroll_increment; + break; + case GDK_Return: + if (_selected_ports.size() > 1) { + join_selection(); + ganv_canvas_clear_selection(_gcanvas); + } + break; + default: + handled = false; + } + if (handled) { + ganv_canvas_scroll_to(GANV_CANVAS(_gcanvas), scroll_x, scroll_y); + return true; + } + break; + + case GDK_SCROLL: + if ((event->scroll.state & GDK_CONTROL_MASK)) { + const double zoom = ganv_canvas_get_zoom(_gcanvas); + if (event->scroll.direction == GDK_SCROLL_UP) { + ganv_canvas_set_zoom(_gcanvas, zoom * 1.25); + return true; + } else if (event->scroll.direction == GDK_SCROLL_DOWN) { + ganv_canvas_set_zoom(_gcanvas, zoom * 0.75); + return true; + } + } + break; + + default: + break; + } + + return scroll_drag_handler(event) + || select_drag_handler(event) + || connect_drag_handler(event); +} + +bool +GanvCanvasImpl::scroll_drag_handler(GdkEvent* event) +{ + bool handled = true; + + static int original_scroll_x = 0; + static int original_scroll_y = 0; + static double origin_x = 0; + static double origin_y = 0; + static double scroll_offset_x = 0; + static double scroll_offset_y = 0; + static double last_x = 0; + static double last_y = 0; + + GanvItem* root = ganv_canvas_root(_gcanvas); + + if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) { + ganv_canvas_grab_item( + root, + GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK, + NULL, event->button.time); + ganv_canvas_get_scroll_offsets(GANV_CANVAS(_gcanvas), &original_scroll_x, &original_scroll_y); + scroll_offset_x = 0; + scroll_offset_y = 0; + origin_x = event->button.x_root; + origin_y = event->button.y_root; + //cerr << "Origin: (" << origin_x << "," << origin_y << ")\n"; + last_x = origin_x; + last_y = origin_y; + _drag_state = SCROLL; + + } else if (event->type == GDK_MOTION_NOTIFY && _drag_state == SCROLL) { + const double x = event->motion.x_root; + const double y = event->motion.y_root; + const double x_offset = last_x - x; + const double y_offset = last_y - y; + + //cerr << "Coord: (" << x << "," << y << ")\n"; + //cerr << "Offset: (" << x_offset << "," << y_offset << ")\n"; + + scroll_offset_x += x_offset; + scroll_offset_y += y_offset; + ganv_canvas_scroll_to(GANV_CANVAS(_gcanvas), + lrint(original_scroll_x + scroll_offset_x), + lrint(original_scroll_y + scroll_offset_y)); + last_x = x; + last_y = y; + } else if (event->type == GDK_BUTTON_RELEASE && _drag_state == SCROLL) { + ganv_canvas_ungrab_item(root, event->button.time); + _drag_state = NOT_DRAGGING; + } else { + handled = false; + } + + return handled; +} + +static void +get_motion_coords(GdkEventMotion* motion, double* x, double* y) +{ + if (motion->is_hint) { + gint px; + gint py; + GdkModifierType state; + gdk_window_get_pointer(motion->window, &px, &py, &state); + *x = px; + *y = py; + } else { + *x = motion->x; + *y = motion->y; + } +} + +bool +GanvCanvasImpl::select_drag_handler(GdkEvent* event) +{ + GanvItem* root = ganv_canvas_root(_gcanvas); + if (event->type == GDK_BUTTON_PRESS && event->button.button == 1) { + assert(_select_rect == NULL); + _drag_state = SELECT; + if ( !(event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ) + ganv_canvas_clear_selection(_gcanvas); + _select_rect = GANV_BOX( + ganv_item_new( + root, + ganv_box_get_type(), + "x1", event->button.x, + "y1", event->button.y, + "x2", event->button.x, + "y2", event->button.y, + "fill-color", SELECT_RECT_FILL_COLOUR, + "border-color", SELECT_RECT_BORDER_COLOUR, + NULL)); + _select_start_x = event->button.x; + _select_start_y = event->button.y; + ganv_canvas_grab_item( + root, GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK, + NULL, event->button.time); + return true; + } else if (event->type == GDK_MOTION_NOTIFY && _drag_state == SELECT) { + assert(_select_rect); + double x, y; + get_motion_coords(&event->motion, &x, &y); + _select_rect->impl->coords.x1 = MIN(_select_start_x, x); + _select_rect->impl->coords.y1 = MIN(_select_start_y, y); + _select_rect->impl->coords.x2 = MAX(_select_start_x, x); + _select_rect->impl->coords.y2 = MAX(_select_start_y, y); + ganv_item_request_update(&_select_rect->node.item); + return true; + } else if (event->type == GDK_BUTTON_RELEASE && _drag_state == SELECT) { + // Normalize select rect + ganv_box_normalize(_select_rect); + + // Select all modules within rect + FOREACH_ITEM(_items, i) { + GanvNode* node = *i; + if ((void*)node != (void*)_select_rect && + ganv_node_is_within( + node, + ganv_box_get_x1(_select_rect), + ganv_box_get_y1(_select_rect), + ganv_box_get_x2(_select_rect), + ganv_box_get_y2(_select_rect))) { + gboolean selected; + g_object_get(G_OBJECT(node), "selected", &selected, NULL); + if (selected) { + ganv_canvas_unselect_node(_gcanvas, node); + } else { + ganv_canvas_select_node(_gcanvas, node); + } + } + } + + // Select all edges with handles within rect + FOREACH_EDGE(_edges, i) { + if (ganv_edge_is_within( + (*i), + ganv_box_get_x1(_select_rect), + ganv_box_get_y1(_select_rect), + ganv_box_get_x2(_select_rect), + ganv_box_get_y2(_select_rect))) { + ganv_canvas_select_edge(_gcanvas, *i); + } + } + + ganv_canvas_ungrab_item(root, event->button.time); + + gtk_object_destroy(GTK_OBJECT(_select_rect)); + _select_rect = NULL; + _drag_state = NOT_DRAGGING; + return true; + } + return false; +} + +bool +GanvCanvasImpl::connect_drag_handler(GdkEvent* event) +{ + static bool snapped = false; + + if (_drag_state != EDGE) { + return false; + } + + if (event->type == GDK_MOTION_NOTIFY) { + double x, y; + get_motion_coords(&event->motion, &x, &y); + + if (!_drag_edge) { + // Create drag edge + assert(!_drag_node); + assert(_connect_port); + + _drag_node = GANV_NODE( + ganv_item_new( + GANV_ITEM(ganv_canvas_root(GANV_CANVAS(_gcanvas))), + ganv_node_get_type(), + "x", x, + "y", y, + NULL)); + + _drag_edge = ganv_edge_new( + _gcanvas, + GANV_NODE(_connect_port), + _drag_node, + "color", GANV_NODE(_connect_port)->impl->fill_color, + "curved", TRUE, + "ghost", TRUE, + NULL); + } + + GanvNode* joinee = get_node_at(x, y); + if (joinee && ganv_node_can_head(joinee) && joinee != _drag_node) { + // Snap to item + snapped = true; + ganv_item_set(&_drag_edge->item, "head", joinee, NULL); + } else if (snapped) { + // Unsnap from item + snapped = false; + ganv_item_set(&_drag_edge->item, "head", _drag_node, NULL); + } + + // Update drag edge for pointer position + ganv_node_move_to(_drag_node, x, y); + ganv_item_request_update(GANV_ITEM(_drag_node)); + ganv_item_request_update(GANV_ITEM(_drag_edge)); + + return true; + + } else if (event->type == GDK_BUTTON_RELEASE) { + ganv_canvas_ungrab_item(root, event->button.time); + + double x = event->button.x; + double y = event->button.y; + + GanvNode* joinee = get_node_at(x, y); + + if (GANV_IS_PORT(joinee)) { + if (joinee == GANV_NODE(_connect_port)) { + // Drag ended on the same port it started on, port clicked + if (_selected_ports.empty()) { + // No selected ports, port clicked + select_port(_connect_port); + } else { + // Connect to selected ports + selection_joined_with(_connect_port); + _connect_port = NULL; + } + } else { // drag ended on different port + ports_joined(_connect_port, GANV_PORT(joinee)); + _connect_port = NULL; + } + } + + end_connect_drag(); + return true; + } + + return false; +} + +void +GanvCanvasImpl::end_connect_drag() +{ + if (_connect_port) { + highlight_port(_connect_port, false); + } + gtk_object_destroy(GTK_OBJECT(_drag_edge)); + gtk_object_destroy(GTK_OBJECT(_drag_node)); + _drag_state = NOT_DRAGGING; + _connect_port = NULL; + _drag_edge = NULL; + _drag_node = NULL; +} + +bool +GanvCanvasImpl::port_event(GdkEvent* event, GanvPort* port) +{ + static bool port_pressed = true; + static bool port_dragging = false; + static bool control_dragging = false; + static double control_start_x = 0; + static double control_start_y = 0; + static float control_start_value = 0; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1) { + GanvModule* const module = ganv_port_get_module(port); + double port_x = event->button.x; + double port_y = event->button.y; + ganv_item_w2i(GANV_ITEM(port), &port_x, &port_y); + + if (_selected_ports.empty() && module && port->impl->control && + (port->impl->is_input || + (port->impl->is_controllable && + port_x < ganv_box_get_width(GANV_BOX(port)) / 2.0))) { + if (port->impl->control->is_toggle) { + if (port->impl->control->value >= 0.5) { + ganv_port_set_control_value_internal(port, 0.0); + } else { + ganv_port_set_control_value_internal(port, 1.0); + } + } else { + control_dragging = port_pressed = true; + control_start_x = event->button.x_root; + control_start_y = event->button.y_root; + control_start_value = ganv_port_get_control_value(port); + ganv_canvas_grab_item( + GANV_ITEM(port), + GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK, + NULL, event->button.time); + GANV_NODE(port)->impl->grabbed = TRUE; + + } + } else if (!port->impl->is_input) { + port_dragging = port_pressed = true; + ganv_canvas_grab_item( + GANV_ITEM(port), + GDK_BUTTON_RELEASE_MASK|GDK_POINTER_MOTION_MASK| + GDK_ENTER_NOTIFY_MASK|GDK_LEAVE_NOTIFY_MASK, + NULL, event->button.time); + } else { + port_pressed = true; + ganv_canvas_grab_item(GANV_ITEM(port), + GDK_BUTTON_RELEASE_MASK, + NULL, event->button.time); + } + return true; + } + break; + + case GDK_MOTION_NOTIFY: + if (control_dragging) { + const double mouse_x = event->button.x_root; + const double mouse_y = event->button.y_root; + GdkScreen* screen = gdk_screen_get_default(); + const int screen_width = gdk_screen_get_width(screen); + const int screen_height = gdk_screen_get_height(screen); + const double drag_dx = mouse_x - control_start_x; + const double drag_dy = mouse_y - control_start_y; + const double ythresh = 0.2; // Minimum y fraction for fine + + const double range_x = ((drag_dx > 0) + ? (screen_width - control_start_x) + : control_start_x) - GANV_CANVAS_PAD; + + const double range_y = ((drag_dy > 0) + ? (screen_height - control_start_y) + : control_start_y); + + const double dx = drag_dx / range_x; + const double dy = fabs(drag_dy / range_y); + + const double value_range = (drag_dx > 0) + ? port->impl->control->max - control_start_value + : control_start_value - port->impl->control->min; + + const double sens = (dy < ythresh) + ? 1.0 + : 1.0 - fabs(drag_dy / (range_y + ythresh)); + + const double dvalue = (dx * value_range) * sens; + double value = control_start_value + dvalue; + if (value < port->impl->control->min) { + value = port->impl->control->min; + } else if (value > port->impl->control->max) { + value = port->impl->control->max; + } + ganv_port_set_control_value_internal(port, value); + return true; + } else if (port_dragging) { + return true; + } + break; + + case GDK_BUTTON_RELEASE: + if (port_pressed) { + ganv_canvas_ungrab_item(GANV_ITEM(port), event->button.time); + } + + if (port_dragging) { + if (_connect_port) { // dragging + ports_joined(port, _connect_port); + } else { + port_clicked(event, port); + } + port_dragging = false; + } else if (control_dragging) { + control_dragging = false; + GANV_NODE(port)->impl->grabbed = FALSE; + if (event->button.x_root == control_start_x && + event->button.y_root == control_start_y) { + select_port_toggle(port, event->button.state); + } + } else { + port_clicked(event, port); + } + return true; + + case GDK_ENTER_NOTIFY: + gboolean selected; + g_object_get(G_OBJECT(port), "selected", &selected, NULL); + if (!control_dragging && !selected) { + highlight_port(port, true); + return true; + } + break; + + case GDK_LEAVE_NOTIFY: + if (port_dragging) { + _drag_state = GanvCanvasImpl::EDGE; + _connect_port = port; + port_dragging = false; + ganv_canvas_ungrab_item(GANV_ITEM(port), event->crossing.time); + ganv_canvas_grab_item( + root, + GDK_BUTTON_PRESS_MASK|GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK, + NULL, event->crossing.time); + return true; + } else if (!control_dragging) { + highlight_port(port, false); + return true; + } + break; + + default: + break; + } + + return false; +} + +/* Called when two ports are 'joined' (connected or disconnected) */ +void +GanvCanvasImpl::ports_joined(GanvPort* port1, GanvPort* port2) +{ + if (port1 == port2 || !port1 || !port2 || !port1->impl || !port2->impl) { + return; + } + + highlight_port(port1, false); + highlight_port(port2, false); + + GanvNode* src_node; + GanvNode* dst_node; + + if (port2->impl->is_input && !port1->impl->is_input) { + src_node = GANV_NODE(port1); + dst_node = GANV_NODE(port2); + } else if (!port2->impl->is_input && port1->impl->is_input) { + src_node = GANV_NODE(port2); + dst_node = GANV_NODE(port1); + } else { + return; + } + + if (!ganv_canvas_get_edge(_gcanvas, src_node, dst_node)) { + g_signal_emit(_gcanvas, signal_connect, 0, + src_node, dst_node, NULL); + } else { + g_signal_emit(_gcanvas, signal_disconnect, 0, + src_node, dst_node, NULL); + } +} + +void +GanvCanvasImpl::port_clicked(GdkEvent* event, GanvPort* port) +{ + const bool modded = event->button.state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK); + if (!modded && _last_selected_port && + _last_selected_port->impl->is_input != port->impl->is_input) { + selection_joined_with(port); + } else { + select_port_toggle(port, event->button.state); + } +} + +void +GanvCanvasImpl::highlight_port(GanvPort* port, bool highlight) +{ + g_object_set(G_OBJECT(port), "highlighted", highlight, NULL); + ganv_canvas_for_each_edge_on(_gcanvas, + GANV_NODE(port), + (highlight + ? (GanvEdgeFunc)ganv_edge_highlight + : (GanvEdgeFunc)ganv_edge_unhighlight), + NULL); +} + +/* Update animated "rubber band" selection effect. */ +gboolean +GanvCanvasImpl::on_animate_timeout(gpointer data) +{ + GanvCanvasImpl* impl = (GanvCanvasImpl*)data; + if (!impl->pixmap_gc) { + return FALSE; // Unrealized + } + + const double seconds = get_monotonic_time() / 1000000.0; + + FOREACH_ITEM(impl->_selected_items, s) { + ganv_node_tick(*s, seconds); + } + + for (SelectedPorts::iterator p = impl->_selected_ports.begin(); + p != impl->_selected_ports.end(); + ++p) { + ganv_node_tick(GANV_NODE(*p), seconds); + } + + FOREACH_EDGE(impl->_selected_edges, c) { + ganv_edge_tick(*c, seconds); + } + + return TRUE; +} + +void +GanvCanvasImpl::move_contents_to_internal(double x, double y, double min_x, double min_y) +{ + FOREACH_ITEM(_items, i) { + ganv_node_move(*i, + x - min_x, + y - min_y); + } +} + +void +GanvCanvasImpl::unselect_ports() +{ + for (GanvCanvasImpl::SelectedPorts::iterator i = _selected_ports.begin(); + i != _selected_ports.end(); ++i) + g_object_set(G_OBJECT(*i), "selected", FALSE, NULL); + + _selected_ports.clear(); + _last_selected_port = NULL; +} + +namespace Ganv { + +static gboolean +on_event_after(GanvItem* canvasitem, + GdkEvent* ev, + void* canvas) +{ + return ((Canvas*)canvas)->signal_event.emit(ev); +} + +static void +on_connect(GanvCanvas* canvas, GanvNode* tail, GanvNode* head, void* data) +{ + Canvas* canvasmm = (Canvas*)data; + canvasmm->signal_connect.emit(Glib::wrap(tail), Glib::wrap(head)); +} + +static void +on_disconnect(GanvCanvas* canvas, GanvNode* tail, GanvNode* head, void* data) +{ + Canvas* canvasmm = (Canvas*)data; + canvasmm->signal_disconnect.emit(Glib::wrap(tail), Glib::wrap(head)); +} + +Canvas::Canvas(double width, double height) + : _gobj(GANV_CANVAS(ganv_canvas_new(width, height))) +{ + ganv_canvas_set_wrapper(_gobj, this); + + g_signal_connect_after(ganv_canvas_root(_gobj), "event", + G_CALLBACK(on_event_after), this); + g_signal_connect(gobj(), "connect", + G_CALLBACK(on_connect), this); + g_signal_connect(gobj(), "disconnect", + G_CALLBACK(on_disconnect), this); +} + +Canvas::~Canvas() +{ + delete _gobj->impl; +} + +void +Canvas::remove_edge_between(Node* item1, Node* item2) +{ + GanvEdge* edge = ganv_canvas_get_edge(_gobj, item1->gobj(), item2->gobj()); + if (edge) { + ganv_canvas_remove_edge(_gobj, edge); + } +} + +void +Canvas::remove_edge(Edge* edge) +{ + ganv_canvas_remove_edge(_gobj, edge->gobj()); +} + +Item* +Canvas::get_item_at(double x, double y) const +{ + GanvItem* item = ganv_canvas_get_item_at(_gobj, x, y); + if (item) { + return Glib::wrap(item); + } + return NULL; +} + +Edge* +Canvas::get_edge(Node* tail, Node* head) const +{ + GanvEdge* e = ganv_canvas_get_edge(_gobj, tail->gobj(), head->gobj()); + if (e) { + return Glib::wrap(e); + } + return NULL; +} + +} // namespace Ganv + +extern "C" { + +#include "ganv/canvas.h" + +#include "./boilerplate.h" +#include "./color.h" +#include "./gettext.h" + +G_DEFINE_TYPE(GanvCanvas, ganv_canvas, GTK_TYPE_LAYOUT) + +enum { + PROP_0, + PROP_WIDTH, + PROP_HEIGHT, + PROP_DIRECTION, + PROP_FONT_SIZE, + PROP_LOCKED, + PROP_FOCUSED_ITEM +}; + +static gboolean +on_canvas_event(GanvItem* canvasitem, + GdkEvent* ev, + void* impl) +{ + return ((GanvCanvasImpl*)impl)->on_event(ev); +} + +static void +ganv_canvas_init(GanvCanvas* canvas) +{ + GTK_WIDGET_SET_FLAGS(canvas, GTK_CAN_FOCUS); + + canvas->impl = new GanvCanvasImpl(canvas); + + g_signal_connect(G_OBJECT(ganv_canvas_root(canvas)), + "event", G_CALLBACK(on_canvas_event), canvas->impl); +} + +static void +ganv_canvas_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_CANVAS(object)); + + GanvCanvas* canvas = GANV_CANVAS(object); + + switch (prop_id) { + case PROP_WIDTH: + ganv_canvas_resize(canvas, g_value_get_double(value), canvas->impl->height); + break; + case PROP_HEIGHT: + ganv_canvas_resize(canvas, canvas->impl->width, g_value_get_double(value)); + break; + case PROP_DIRECTION: + ganv_canvas_set_direction(canvas, (GanvDirection)g_value_get_enum(value)); + break; + case PROP_FONT_SIZE: + ganv_canvas_set_font_size(canvas, g_value_get_double(value)); + break; + case PROP_LOCKED: + canvas->impl->locked = g_value_get_boolean(value); + break; + case PROP_FOCUSED_ITEM: + canvas->impl->focused_item = GANV_ITEM(g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_canvas_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_CANVAS(object)); + + GanvCanvas* canvas = GANV_CANVAS(object); + + switch (prop_id) { + GET_CASE(WIDTH, double, canvas->impl->width) + GET_CASE(HEIGHT, double, canvas->impl->height) + GET_CASE(LOCKED, boolean, canvas->impl->locked); + case PROP_FOCUSED_ITEM: + g_value_set_object(value, GANV_CANVAS(object)->impl->focused_item); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_canvas_class_init(GanvCanvasClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GtkWidgetClass* widget_class = (GtkWidgetClass*)klass; + + canvas_parent_class = GTK_LAYOUT_CLASS(g_type_class_peek_parent(klass)); + + g_type_class_add_private(klass, sizeof(GanvCanvasImpl)); + + gobject_class->set_property = ganv_canvas_set_property; + gobject_class->get_property = ganv_canvas_get_property; + + object_class->destroy = ganv_canvas_destroy; + + widget_class->map = ganv_canvas_map; + widget_class->unmap = ganv_canvas_unmap; + widget_class->realize = ganv_canvas_realize; + widget_class->unrealize = ganv_canvas_unrealize; + widget_class->size_allocate = ganv_canvas_size_allocate; + widget_class->button_press_event = ganv_canvas_button; + widget_class->button_release_event = ganv_canvas_button; + widget_class->motion_notify_event = ganv_canvas_motion; + widget_class->expose_event = ganv_canvas_expose; + widget_class->key_press_event = ganv_canvas_key; + widget_class->key_release_event = ganv_canvas_key; + widget_class->enter_notify_event = ganv_canvas_crossing; + widget_class->leave_notify_event = ganv_canvas_crossing; + widget_class->focus_in_event = ganv_canvas_focus_in; + widget_class->focus_out_event = ganv_canvas_focus_out; + widget_class->scroll_event = ganv_canvas_scroll; + + g_object_class_install_property( + gobject_class, PROP_FOCUSED_ITEM, g_param_spec_object( + "focused-item", + _("Focused item"), + _("The item that currently has keyboard focus."), + GANV_TYPE_ITEM, + (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_object_class_install_property( + gobject_class, PROP_WIDTH, g_param_spec_double( + "width", + _("Width"), + _("The width of the canvas."), + 0.0, G_MAXDOUBLE, + 800.0, + (GParamFlags)G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_HEIGHT, g_param_spec_double( + "height", + _("Height"), + _("The height of the canvas"), + 0.0, G_MAXDOUBLE, + 600.0, + (GParamFlags)G_PARAM_READWRITE)); + + GEnumValue down_dir = { GANV_DIRECTION_DOWN, "down", "down" }; + GEnumValue right_dir = { GANV_DIRECTION_RIGHT, "right", "right" }; + GEnumValue null_dir = { 0, 0, 0 }; + dir_values[0] = down_dir; + dir_values[1] = right_dir; + dir_values[2] = null_dir; + GType dir_type = g_enum_register_static("GanvDirection", + dir_values); + + g_object_class_install_property( + gobject_class, PROP_DIRECTION, g_param_spec_enum( + "direction", + _("Direction"), + _("The direction of the signal flow on the canvas."), + dir_type, + GANV_DIRECTION_RIGHT, + (GParamFlags)G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_FONT_SIZE, g_param_spec_double( + "font-size", + _("Font size"), + _("The default font size for the canvas"), + 0.0, G_MAXDOUBLE, + 12.0, + (GParamFlags)G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_LOCKED, g_param_spec_boolean( + "locked", + _("Locked"), + _("If true, nodes on the canvas can not be moved by the user."), + FALSE, + (GParamFlags)G_PARAM_READWRITE)); + + signal_connect = g_signal_new("connect", + ganv_canvas_get_type(), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + ganv_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, + 2, + ganv_node_get_type(), + ganv_node_get_type(), + 0); + + signal_disconnect = g_signal_new("disconnect", + ganv_canvas_get_type(), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + ganv_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, + 2, + ganv_node_get_type(), + ganv_node_get_type(), + 0); +} + +void +ganv_canvas_resize(GanvCanvas* canvas, double width, double height) +{ + if (width != canvas->impl->width || height != canvas->impl->height) { + canvas->impl->width = width; + canvas->impl->height = height; + ganv_canvas_set_scroll_region(canvas, 0.0, 0.0, width, height); + } +} + +void +ganv_canvas_contents_changed(GanvCanvas* canvas) +{ +#ifdef GANV_FDGL + if (!canvas->impl->layout_idle_id && canvas->impl->sprung_layout) { + canvas->impl->layout_energy = 0.4; + canvas->impl->layout_idle_id = g_timeout_add_full( + G_PRIORITY_DEFAULT_IDLE, + 33, + GanvCanvasImpl::on_layout_timeout, + canvas->impl, + GanvCanvasImpl::on_layout_done); + } +#endif +} + +double +ganv_canvas_get_default_font_size(const GanvCanvas* canvas) +{ + GtkStyle* style = gtk_rc_get_style(GTK_WIDGET(canvas)); + const PangoFontDescription* font = style->font_desc; + return pango_font_description_get_size(font) / (double)PANGO_SCALE; +} + +double +ganv_canvas_get_font_size(const GanvCanvas* canvas) +{ + return canvas->impl->font_size; +} + +void +ganv_canvas_set_zoom(GanvCanvas* canvas, double zoom) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + zoom = std::max(zoom, 0.01); + if (zoom == canvas->impl->pixels_per_unit) { + return; + } + + const int anchor_x = (canvas->impl->center_scroll_region) + ? GTK_WIDGET(canvas)->allocation.width / 2 + : 0; + const int anchor_y = (canvas->impl->center_scroll_region) + ? GTK_WIDGET(canvas)->allocation.height / 2 + : 0; + + /* Find the coordinates of the anchor point in units. */ + const double ax = (canvas->layout.hadjustment) + ? ((canvas->layout.hadjustment->value + anchor_x) + / canvas->impl->pixels_per_unit + + canvas->impl->scroll_x1 + canvas->impl->zoom_xofs) + : ((0.0 + anchor_x) / canvas->impl->pixels_per_unit + + canvas->impl->scroll_x1 + canvas->impl->zoom_xofs); + const double ay = (canvas->layout.hadjustment) + ? ((canvas->layout.vadjustment->value + anchor_y) + / canvas->impl->pixels_per_unit + + canvas->impl->scroll_y1 + canvas->impl->zoom_yofs) + : ((0.0 + anchor_y) / canvas->impl->pixels_per_unit + + canvas->impl->scroll_y1 + canvas->impl->zoom_yofs); + + /* Now calculate the new offset of the upper left corner. */ + const int x1 = ((ax - canvas->impl->scroll_x1) * zoom) - anchor_x; + const int y1 = ((ay - canvas->impl->scroll_y1) * zoom) - anchor_y; + + canvas->impl->pixels_per_unit = zoom; + ganv_canvas_scroll_to(canvas, x1, y1); + + ganv_canvas_request_update(canvas); + gtk_widget_queue_draw(GTK_WIDGET(canvas)); + + canvas->impl->need_repick = TRUE; +} + +void +ganv_canvas_set_font_size(GanvCanvas* canvas, double points) +{ + points = std::max(points, 1.0); + if (points != canvas->impl->font_size) { + canvas->impl->font_size = points; + FOREACH_ITEM(canvas->impl->_items, i) { + ganv_node_redraw_text(*i); + } + } +} + +void +ganv_canvas_zoom_full(GanvCanvas* canvas) +{ + if (canvas->impl->_items.empty()) + return; + + int win_width, win_height; + GdkWindow* win = gtk_widget_get_window( + GTK_WIDGET(canvas->impl->_gcanvas)); + gdk_window_get_size(win, &win_width, &win_height); + + // Box containing all canvas items + double left = DBL_MAX; + double right = DBL_MIN; + double top = DBL_MIN; + double bottom = DBL_MAX; + + FOREACH_ITEM(canvas->impl->_items, i) { + GanvItem* const item = GANV_ITEM(*i); + const double x = item->impl->x; + const double y = item->impl->y; + if (GANV_IS_CIRCLE(*i)) { + const double r = GANV_CIRCLE(*i)->impl->coords.radius; + left = MIN(left, x - r); + right = MAX(right, x + r); + bottom = MIN(bottom, y - r); + top = MAX(top, y + r); + } else { + left = MIN(left, x); + right = MAX(right, x + ganv_box_get_width(GANV_BOX(*i))); + bottom = MIN(bottom, y); + top = MAX(top, y + ganv_box_get_height(GANV_BOX(*i))); + } + } + + static const double pad = GANV_CANVAS_PAD; + + const double new_zoom = std::min( + ((double)win_width / (double)(right - left + pad*2.0)), + ((double)win_height / (double)(top - bottom + pad*2.0))); + + ganv_canvas_set_zoom(canvas, new_zoom); + + int scroll_x, scroll_y; + ganv_canvas_w2c(canvas->impl->_gcanvas, + lrintf(left - pad), lrintf(bottom - pad), + &scroll_x, &scroll_y); + + ganv_canvas_scroll_to(canvas->impl->_gcanvas, + scroll_x, scroll_y); +} + +static void +set_node_direction(GanvNode* node, void* data) +{ + if (GANV_IS_MODULE(node)) { + ganv_module_set_direction(GANV_MODULE(node), *(GanvDirection*)data); + } +} + +GanvDirection +ganv_canvas_get_direction(GanvCanvas* canvas) +{ + return canvas->impl->direction; +} + +void +ganv_canvas_set_direction(GanvCanvas* canvas, GanvDirection dir) +{ + if (canvas->impl->direction != dir) { + canvas->impl->direction = dir; + ganv_canvas_for_each_node(canvas, set_node_direction, &dir); + ganv_canvas_contents_changed(canvas); + } +} + +void +ganv_canvas_clear_selection(GanvCanvas* canvas) +{ + canvas->impl->unselect_ports(); + + Items items(canvas->impl->_selected_items); + canvas->impl->_selected_items.clear(); + FOREACH_ITEM(items, i) { + ganv_item_set(GANV_ITEM(*i), "selected", FALSE, NULL); + } + + GanvCanvasImpl::SelectedEdges edges(canvas->impl->_selected_edges); + canvas->impl->_selected_edges.clear(); + FOREACH_SELECTED_EDGE(edges, c) { + ganv_item_set(GANV_ITEM(*c), "selected", FALSE, NULL); + } +} + +void +ganv_canvas_move_selected_items(GanvCanvas* canvas, + double dx, + double dy) +{ + FOREACH_ITEM(canvas->impl->_selected_items, i) { + if ((*i)->item.impl->parent == canvas->impl->root) { + ganv_node_move(*i, dx, dy); + } + } +} + +void +ganv_canvas_selection_move_finished(GanvCanvas* canvas) +{ + FOREACH_ITEM(canvas->impl->_selected_items, i) { + const double x = GANV_ITEM(*i)->impl->x; + const double y = GANV_ITEM(*i)->impl->y; + g_signal_emit(*i, signal_moved, 0, x, y, NULL); + } +} + +static void +select_if_ends_are_selected(GanvEdge* edge, void* data) +{ + if (ganv_node_is_selected(ganv_edge_get_tail(edge)) && + ganv_node_is_selected(ganv_edge_get_head(edge))) { + ganv_edge_set_selected(edge, TRUE); + } +} + +static void +unselect_edges(GanvPort* port, void* data) +{ + GanvCanvasImpl* impl = (GanvCanvasImpl*)data; + if (port->impl->is_input) { + ganv_canvas_for_each_edge_to(impl->_gcanvas, + GANV_NODE(port), + (GanvEdgeFunc)ganv_edge_unselect, + NULL); + } else { + ganv_canvas_for_each_edge_from(impl->_gcanvas, + GANV_NODE(port), + (GanvEdgeFunc)ganv_edge_unselect, + NULL); + } +} + +void +ganv_canvas_select_node(GanvCanvas* canvas, + GanvNode* node) +{ + canvas->impl->_selected_items.insert(node); + + // Select any connections to or from this node + if (GANV_IS_MODULE(node)) { + ganv_module_for_each_port(GANV_MODULE(node), select_edges, canvas->impl); + } else { + ganv_canvas_for_each_edge_on( + canvas, node, select_if_ends_are_selected, canvas->impl); + } + + g_object_set(node, "selected", TRUE, NULL); +} + +void +ganv_canvas_unselect_node(GanvCanvas* canvas, + GanvNode* node) +{ + // Unselect any connections to or from canvas->impl node + if (GANV_IS_MODULE(node)) { + ganv_module_for_each_port(GANV_MODULE(node), unselect_edges, canvas->impl); + } else { + ganv_canvas_for_each_edge_on( + canvas, node, (GanvEdgeFunc)ganv_edge_unselect, NULL); + } + + // Unselect item + canvas->impl->_selected_items.erase(node); + g_object_set(node, "selected", FALSE, NULL); +} + +void +ganv_canvas_add_node(GanvCanvas* canvas, + GanvNode* node) +{ + GanvItem* item = GANV_ITEM(node); + if (item->impl->parent == ganv_canvas_root(canvas)) { + canvas->impl->_items.insert(node); + } +} + +void +ganv_canvas_remove_node(GanvCanvas* canvas, + GanvNode* node) +{ + if (node == (GanvNode*)canvas->impl->_connect_port) { + if (canvas->impl->_drag_state == GanvCanvasImpl::EDGE) { + ganv_canvas_ungrab_item(ganv_canvas_root(canvas), 0); + canvas->impl->end_connect_drag(); + } + canvas->impl->_connect_port = NULL; + } + + // Remove from selection + canvas->impl->_selected_items.erase(node); + + // Remove children ports from selection if item is a module + if (GANV_IS_MODULE(node)) { + GanvModule* const module = GANV_MODULE(node); + for (unsigned i = 0; i < ganv_module_num_ports(module); ++i) { + canvas->impl->unselect_port(ganv_module_get_port(module, i)); + } + } + + // Remove from items + canvas->impl->_items.erase(node); +} + +GanvEdge* +ganv_canvas_get_edge(GanvCanvas* canvas, + GanvNode* tail, + GanvNode* head) +{ + GanvEdgeKey key; + make_edge_search_key(&key, tail, head); + GanvCanvasImpl::Edges::const_iterator i = canvas->impl->_edges.find((GanvEdge*)&key); + return (i != canvas->impl->_edges.end()) ? *i : NULL; +} + +void +ganv_canvas_remove_edge_between(GanvCanvas* canvas, + GanvNode* tail, + GanvNode* head) +{ + ganv_canvas_remove_edge(canvas, ganv_canvas_get_edge(canvas, tail, head)); +} + +void +ganv_canvas_disconnect_edge(GanvCanvas* canvas, + GanvEdge* edge) +{ + g_signal_emit(canvas, signal_disconnect, 0, + edge->impl->tail, edge->impl->head, NULL); +} + +void +ganv_canvas_add_edge(GanvCanvas* canvas, + GanvEdge* edge) +{ + canvas->impl->_edges.insert(edge); + canvas->impl->_dst_edges.insert(edge); + ganv_canvas_contents_changed(canvas); +} + +void +ganv_canvas_remove_edge(GanvCanvas* canvas, + GanvEdge* edge) +{ + if (edge) { + canvas->impl->_selected_edges.erase(edge); + canvas->impl->_edges.erase(edge); + canvas->impl->_dst_edges.erase(edge); + ganv_edge_request_redraw(GANV_ITEM(edge), &edge->impl->coords); + gtk_object_destroy(GTK_OBJECT(edge)); + ganv_canvas_contents_changed(canvas); + } +} + +void +ganv_canvas_select_edge(GanvCanvas* canvas, + GanvEdge* edge) +{ + ganv_item_set(GANV_ITEM(edge), "selected", TRUE, NULL); + canvas->impl->_selected_edges.insert(edge); +} + +void +ganv_canvas_unselect_edge(GanvCanvas* canvas, + GanvEdge* edge) +{ + ganv_item_set(GANV_ITEM(edge), "selected", FALSE, NULL); + canvas->impl->_selected_edges.erase(edge); +} + +void +ganv_canvas_for_each_node(GanvCanvas* canvas, + GanvNodeFunc f, + void* data) +{ + FOREACH_ITEM(canvas->impl->_items, i) { + f(*i, data); + } +} + +void +ganv_canvas_for_each_selected_node(GanvCanvas* canvas, + GanvNodeFunc f, + void* data) +{ + FOREACH_ITEM(canvas->impl->_selected_items, i) { + f(*i, data); + } +} + +gboolean +ganv_canvas_empty(const GanvCanvas* canvas) +{ + return canvas->impl->_items.empty(); +} + +void +ganv_canvas_for_each_edge(GanvCanvas* canvas, + GanvEdgeFunc f, + void* data) +{ + GanvCanvasImpl* impl = canvas->impl; + for (GanvCanvasImpl::Edges::const_iterator i = impl->_edges.begin(); + i != impl->_edges.end();) { + GanvCanvasImpl::Edges::const_iterator next = i; + ++next; + f((*i), data); + i = next; + } +} + +void +ganv_canvas_for_each_edge_from(GanvCanvas* canvas, + const GanvNode* tail, + GanvEdgeFunc f, + void* data) +{ + GanvCanvasImpl* impl = canvas->impl; + for (GanvCanvasImpl::Edges::const_iterator i = impl->first_edge_from(tail); + i != impl->_edges.end() && (*i)->impl->tail == tail;) { + GanvCanvasImpl::Edges::const_iterator next = i; + ++next; + f((*i), data); + i = next; + } +} + +void +ganv_canvas_for_each_edge_to(GanvCanvas* canvas, + const GanvNode* head, + GanvEdgeFunc f, + void* data) +{ + GanvCanvasImpl* impl = canvas->impl; + for (GanvCanvasImpl::Edges::const_iterator i = impl->first_edge_to(head); + i != impl->_dst_edges.end() && (*i)->impl->head == head;) { + GanvCanvasImpl::Edges::const_iterator next = i; + ++next; + f((*i), data); + i = next; + } +} + +void +ganv_canvas_for_each_edge_on(GanvCanvas* canvas, + const GanvNode* node, + GanvEdgeFunc f, + void* data) +{ + ganv_canvas_for_each_edge_from(canvas, node, f, data); + ganv_canvas_for_each_edge_to(canvas, node, f, data); +} + +void +ganv_canvas_for_each_selected_edge(GanvCanvas* canvas, + GanvEdgeFunc f, + void* data) +{ + FOREACH_EDGE(canvas->impl->_selected_edges, i) { + f((*i), data); + } +} + +GdkCursor* +ganv_canvas_get_move_cursor(const GanvCanvas* canvas) +{ + return canvas->impl->_move_cursor; +} + +gboolean +ganv_canvas_port_event(GanvCanvas* canvas, + GanvPort* port, + GdkEvent* event) +{ + return canvas->impl->port_event(event, port); +} + +void +ganv_canvas_clear(GanvCanvas* canvas) +{ + canvas->impl->_selected_items.clear(); + canvas->impl->_selected_edges.clear(); + + Items items = canvas->impl->_items; // copy + FOREACH_ITEM(items, i) { + gtk_object_destroy(GTK_OBJECT(*i)); + } + canvas->impl->_items.clear(); + + GanvCanvasImpl::Edges edges = canvas->impl->_edges; // copy + FOREACH_EDGE(edges, i) { + gtk_object_destroy(GTK_OBJECT(*i)); + } + canvas->impl->_edges.clear(); + canvas->impl->_dst_edges.clear(); + + canvas->impl->_selected_ports.clear(); + canvas->impl->_connect_port = NULL; +} + +void +ganv_canvas_select_all(GanvCanvas* canvas) +{ + ganv_canvas_clear_selection(canvas); + FOREACH_ITEM(canvas->impl->_items, i) { + ganv_canvas_select_node(canvas, *i); + } +} + +double +ganv_canvas_get_zoom(const GanvCanvas* canvas) +{ + return canvas->impl->pixels_per_unit; +} + +void +ganv_canvas_move_contents_to(GanvCanvas* canvas, double x, double y) +{ + double min_x=HUGE_VAL, min_y=HUGE_VAL; + FOREACH_ITEM(canvas->impl->_items, i) { + const double x = GANV_ITEM(*i)->impl->x; + const double y = GANV_ITEM(*i)->impl->y; + min_x = std::min(min_x, x); + min_y = std::min(min_y, y); + } + canvas->impl->move_contents_to_internal(x, y, min_x, min_y); +} + +void +ganv_canvas_arrange(GanvCanvas* canvas) +{ +#if defined(HAVE_AGRAPH_2_20) || defined(HAVE_AGRAPH_2_30) + GVNodes nodes = canvas->impl->layout_dot((char*)""); + + double least_x=HUGE_VAL, least_y=HUGE_VAL, most_x=0, most_y=0; + + // Set numeric locale to POSIX for reading graphviz output with strtod + char* locale = strdup(setlocale(LC_NUMERIC, NULL)); + setlocale(LC_NUMERIC, "POSIX"); + + const double dpi = gdk_screen_get_resolution(gdk_screen_get_default()); + const double dpp = dpi / 72.0; + + // Arrange to graphviz coordinates + for (GVNodes::iterator i = nodes.begin(); i != nodes.end(); ++i) { + if (GANV_ITEM(i->first)->impl->parent != GANV_ITEM(ganv_canvas_root(canvas))) { + continue; + } + const std::string pos = agget(i->second, (char*)"pos"); + const std::string x_str = pos.substr(0, pos.find(",")); + const std::string y_str = pos.substr(pos.find(",") + 1); + const double cx = lrint(strtod(x_str.c_str(), NULL) * dpp); + const double cy = lrint(strtod(y_str.c_str(), NULL) * dpp); + + double w, h; + if (GANV_IS_BOX(i->first)) { + w = ganv_box_get_width(GANV_BOX(i->first)); + h = ganv_box_get_height(GANV_BOX(i->first)); + } else { + w = h = ganv_circle_get_radius(GANV_CIRCLE(i->first)) * 2.3; + } + + /* Dot node positions are supposedly node centers, but things only + match up if x is interpreted as center and y as top... + */ + double x = cx - (w / 2.0); + double y = -cy - (h / 2.0); + + ganv_node_move_to(i->first, x, y); + + if (GANV_IS_CIRCLE(i->first)) { + // Offset least x and y to avoid cutting off circles at origin + const double r = ganv_circle_get_radius(GANV_CIRCLE(i->first)); + x -= r; + y -= r; + } + + least_x = std::min(least_x, x); + least_y = std::min(least_y, y); + most_x = std::max(most_x, x + w); + most_y = std::max(most_y, y + h); + } + + // Reset numeric locale to original value + setlocale(LC_NUMERIC, locale); + free(locale); + + const double graph_width = most_x - least_x; + const double graph_height = most_y - least_y; + + //cerr << "CWH: " << _width << ", " << _height << endl; + //cerr << "GWH: " << graph_width << ", " << graph_height << endl; + + double old_width, old_height; + g_object_get(G_OBJECT(canvas), + "width", &old_width, + "height", &old_height, + NULL); + + const double new_width = std::max(graph_width + 10.0, old_width); + const double new_height = std::max(graph_height + 10.0, old_height); + if (new_width != old_width || new_height != old_height) { + ganv_canvas_resize(canvas, new_width, new_height); + } + nodes.cleanup(); + + static const double border_width = GANV_CANVAS_PAD; + canvas->impl->move_contents_to_internal(border_width, border_width, least_x, least_y); + ganv_canvas_scroll_to(canvas->impl->_gcanvas, 0, 0); + + FOREACH_ITEM(canvas->impl->_items, i) { + const double x = GANV_ITEM(*i)->impl->x; + const double y = GANV_ITEM(*i)->impl->y; + g_signal_emit(*i, signal_moved, 0, x, y, NULL); + } +#endif +} + +int +ganv_canvas_export_image(GanvCanvas* canvas, + const char* filename, + gboolean draw_background) +{ + const char* ext = strrchr(filename, '.'); + if (!ext) { + return 1; + } else if (!strcmp(ext, ".dot")) { + ganv_canvas_export_dot(canvas, filename); + return 0; + } + + cairo_surface_t* rec_surface = cairo_recording_surface_create( + CAIRO_CONTENT_COLOR_ALPHA, NULL); + + // Draw to recording surface + cairo_t* cr = cairo_create(rec_surface); + canvas->impl->exporting = TRUE; + (*GANV_ITEM_GET_CLASS(canvas->impl->root)->draw)( + canvas->impl->root, cr, + 0, 0, canvas->impl->width, canvas->impl->height); + canvas->impl->exporting = FALSE; + cairo_destroy(cr); + + // Get draw extent + double x, y, w, h; + cairo_recording_surface_ink_extents(rec_surface, &x, &y, &w, &h); + + // Create image surface with the appropriate size + const double pad = GANV_CANVAS_PAD; + const double img_w = w + pad * 2; + const double img_h = h + pad * 2; + cairo_surface_t* img = NULL; + if (!strcmp(ext, ".svg")) { + img = cairo_svg_surface_create(filename, img_w, img_h); + } else if (!strcmp(ext, ".pdf")) { + img = cairo_pdf_surface_create(filename, img_w, img_h); + } else if (!strcmp(ext, ".ps")) { + img = cairo_ps_surface_create(filename, img_w, img_h); + } else { + cairo_surface_destroy(rec_surface); + return 1; + } + + // Draw recording to image surface + cr = cairo_create(img); + if (draw_background) { + double r, g, b, a; + color_to_rgba(DEFAULT_BACKGROUND_COLOR, &r, &g, &b, &a); + cairo_set_source_rgba(cr, r, g, b, a); + cairo_rectangle(cr, 0, 0, w + 2 * pad, h + 2 * pad); + cairo_fill(cr); + } + cairo_set_source_surface(cr, rec_surface, -x + pad, -y + pad); + cairo_paint(cr); + cairo_destroy(cr); + cairo_surface_destroy(rec_surface); + cairo_surface_destroy(img); + return 0; +} + +void +ganv_canvas_export_dot(GanvCanvas* canvas, const char* filename) +{ +#if defined(HAVE_AGRAPH_2_20) || defined(HAVE_AGRAPH_2_30) + GVNodes nodes = canvas->impl->layout_dot(filename); + nodes.cleanup(); +#endif +} + +gboolean +ganv_canvas_supports_sprung_layout(const GanvCanvas* canvas) +{ +#ifdef GANV_FDGL + return TRUE; +#else + return FALSE; +#endif +} + +gboolean +ganv_canvas_set_sprung_layout(GanvCanvas* canvas, gboolean sprung_layout) +{ +#ifndef GANV_FDGL + return FALSE; +#else + canvas->impl->sprung_layout = sprung_layout; + ganv_canvas_contents_changed(canvas); + return TRUE; +#endif +} + +gboolean +ganv_canvas_get_locked(const GanvCanvas* canvas) +{ + return canvas->impl->locked; +} + +/* Convenience function to remove the idle handler of a canvas */ +static void +remove_idle(GanvCanvas* canvas) +{ + if (canvas->impl->idle_id == 0) { + return; + } + + g_source_remove(canvas->impl->idle_id); + canvas->impl->idle_id = 0; +} + +/* Removes the transient state of the canvas (idle handler, grabs). */ +static void +shutdown_transients(GanvCanvas* canvas) +{ + /* We turn off the need_redraw flag, since if the canvas is mapped again + * it will request a redraw anyways. We do not turn off the need_update + * flag, though, because updates are not queued when the canvas remaps + * itself. + */ + if (canvas->impl->need_redraw) { + canvas->impl->need_redraw = FALSE; + g_slist_foreach(canvas->impl->redraw_region, (GFunc)g_free, NULL); + g_slist_free(canvas->impl->redraw_region); + canvas->impl->redraw_region = NULL; + canvas->impl->redraw_x1 = 0; + canvas->impl->redraw_y1 = 0; + canvas->impl->redraw_x2 = 0; + canvas->impl->redraw_y2 = 0; + } + + if (canvas->impl->grabbed_item) { + canvas->impl->grabbed_item = NULL; + gdk_pointer_ungrab(GDK_CURRENT_TIME); + } + + remove_idle(canvas); +} + +/* Destroy handler for GanvCanvas */ +static void +ganv_canvas_destroy(GtkObject* object) +{ + g_return_if_fail(GANV_IS_CANVAS(object)); + + /* remember, destroy can be run multiple times! */ + + GanvCanvas* canvas = GANV_CANVAS(object); + + if (canvas->impl->root_destroy_id) { + g_signal_handler_disconnect(canvas->impl->root, canvas->impl->root_destroy_id); + canvas->impl->root_destroy_id = 0; + } + if (canvas->impl->root) { + gtk_object_destroy(GTK_OBJECT(canvas->impl->root)); + g_object_unref(G_OBJECT(canvas->impl->root)); + canvas->impl->root = NULL; + } + + shutdown_transients(canvas); + + if (GTK_OBJECT_CLASS(canvas_parent_class)->destroy) { + (*GTK_OBJECT_CLASS(canvas_parent_class)->destroy)(object); + } +} + +GanvCanvas* +ganv_canvas_new(double width, double height) +{ + GanvCanvas* canvas = GANV_CANVAS( + g_object_new(ganv_canvas_get_type(), + "width", width, + "height", height, + NULL)); + + ganv_canvas_set_scroll_region(canvas, 0.0, 0.0, width, height); + + return canvas; +} + +void +ganv_canvas_set_wrapper(GanvCanvas* canvas, void* wrapper) +{ + canvas->impl->_wrapper = (Ganv::Canvas*)wrapper; +} + +void* +ganv_canvas_get_wrapper(GanvCanvas* canvas) +{ + return canvas->impl->_wrapper; +} + +/* Map handler for the canvas */ +static void +ganv_canvas_map(GtkWidget* widget) +{ + g_return_if_fail(GANV_IS_CANVAS(widget)); + + /* Normal widget mapping stuff */ + + if (GTK_WIDGET_CLASS(canvas_parent_class)->map) { + (*GTK_WIDGET_CLASS(canvas_parent_class)->map)(widget); + } + + GanvCanvas* canvas = GANV_CANVAS(widget); + + if (canvas->impl->need_update) { + add_idle(canvas); + } + + /* Map items */ + + if (GANV_ITEM_GET_CLASS(canvas->impl->root)->map) { + (*GANV_ITEM_GET_CLASS(canvas->impl->root)->map)(canvas->impl->root); + } +} + +/* Unmap handler for the canvas */ +static void +ganv_canvas_unmap(GtkWidget* widget) +{ + g_return_if_fail(GANV_IS_CANVAS(widget)); + + GanvCanvas* canvas = GANV_CANVAS(widget); + + shutdown_transients(canvas); + + /* Unmap items */ + + if (GANV_ITEM_GET_CLASS(canvas->impl->root)->unmap) { + (*GANV_ITEM_GET_CLASS(canvas->impl->root)->unmap)(canvas->impl->root); + } + + /* Normal widget unmapping stuff */ + + if (GTK_WIDGET_CLASS(canvas_parent_class)->unmap) { + (*GTK_WIDGET_CLASS(canvas_parent_class)->unmap)(widget); + } +} + +/* Realize handler for the canvas */ +static void +ganv_canvas_realize(GtkWidget* widget) +{ + g_return_if_fail(GANV_IS_CANVAS(widget)); + + /* Normal widget realization stuff */ + + if (GTK_WIDGET_CLASS(canvas_parent_class)->realize) { + (*GTK_WIDGET_CLASS(canvas_parent_class)->realize)(widget); + } + + GanvCanvas* canvas = GANV_CANVAS(widget); + + gdk_window_set_events( + canvas->layout.bin_window, + (GdkEventMask)(gdk_window_get_events(canvas->layout.bin_window) + | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_POINTER_MOTION_MASK + | GDK_KEY_PRESS_MASK + | GDK_KEY_RELEASE_MASK + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_FOCUS_CHANGE_MASK)); + + /* Create our own temporary pixmap gc and realize all the items */ + + canvas->impl->pixmap_gc = gdk_gc_new(canvas->layout.bin_window); + + (*GANV_ITEM_GET_CLASS(canvas->impl->root)->realize)(canvas->impl->root); + + canvas->impl->_animate_idle_id = g_timeout_add( + 120, GanvCanvasImpl::on_animate_timeout, canvas->impl); +} + +/* Unrealize handler for the canvas */ +static void +ganv_canvas_unrealize(GtkWidget* widget) +{ + g_return_if_fail(GANV_IS_CANVAS(widget)); + + GanvCanvas* canvas = GANV_CANVAS(widget); + + if (canvas->impl->_animate_idle_id) { + g_source_remove(canvas->impl->_animate_idle_id); + canvas->impl->_animate_idle_id = 0; + } + while (g_idle_remove_by_data(canvas->impl)) {} + + shutdown_transients(canvas); + + /* Unrealize items and parent widget */ + + (*GANV_ITEM_GET_CLASS(canvas->impl->root)->unrealize)(canvas->impl->root); + + g_object_unref(canvas->impl->pixmap_gc); + canvas->impl->pixmap_gc = NULL; + + if (GTK_WIDGET_CLASS(canvas_parent_class)->unrealize) { + (*GTK_WIDGET_CLASS(canvas_parent_class)->unrealize)(widget); + } +} + +/* Handles scrolling of the canvas. Adjusts the scrolling and zooming offset to + * keep as much as possible of the canvas scrolling region in view. + */ +static void +scroll_to(GanvCanvas* canvas, int cx, int cy) +{ + int scroll_width, scroll_height; + int right_limit, bottom_limit; + int old_zoom_xofs, old_zoom_yofs; + int changed_x = FALSE, changed_y = FALSE; + int canvas_width, canvas_height; + + canvas_width = GTK_WIDGET(canvas)->allocation.width; + canvas_height = GTK_WIDGET(canvas)->allocation.height; + + scroll_width = floor((canvas->impl->scroll_x2 - canvas->impl->scroll_x1) * canvas->impl->pixels_per_unit + + 0.5); + scroll_height = floor((canvas->impl->scroll_y2 - canvas->impl->scroll_y1) * canvas->impl->pixels_per_unit + + 0.5); + + right_limit = scroll_width - canvas_width; + bottom_limit = scroll_height - canvas_height; + + old_zoom_xofs = canvas->impl->zoom_xofs; + old_zoom_yofs = canvas->impl->zoom_yofs; + + if (right_limit < 0) { + cx = 0; + + if (canvas->impl->center_scroll_region) { + canvas->impl->zoom_xofs = (canvas_width - scroll_width) / 2; + scroll_width = canvas_width; + } else { + canvas->impl->zoom_xofs = 0; + } + } else if (cx < 0) { + cx = 0; + canvas->impl->zoom_xofs = 0; + } else if (cx > right_limit) { + cx = right_limit; + canvas->impl->zoom_xofs = 0; + } else { + canvas->impl->zoom_xofs = 0; + } + + if (bottom_limit < 0) { + cy = 0; + + if (canvas->impl->center_scroll_region) { + canvas->impl->zoom_yofs = (canvas_height - scroll_height) / 2; + scroll_height = canvas_height; + } else { + canvas->impl->zoom_yofs = 0; + } + } else if (cy < 0) { + cy = 0; + canvas->impl->zoom_yofs = 0; + } else if (cy > bottom_limit) { + cy = bottom_limit; + canvas->impl->zoom_yofs = 0; + } else { + canvas->impl->zoom_yofs = 0; + } + + if ((canvas->impl->zoom_xofs != old_zoom_xofs) || (canvas->impl->zoom_yofs != old_zoom_yofs)) { + ganv_canvas_request_update(canvas); + gtk_widget_queue_draw(GTK_WIDGET(canvas)); + } + + if (canvas->layout.hadjustment && ( ((int)canvas->layout.hadjustment->value) != cx) ) { + canvas->layout.hadjustment->value = cx; + changed_x = TRUE; + } + + if (canvas->layout.vadjustment && ( ((int)canvas->layout.vadjustment->value) != cy) ) { + canvas->layout.vadjustment->value = cy; + changed_y = TRUE; + } + + if ((scroll_width != (int)canvas->layout.width) + || (scroll_height != (int)canvas->layout.height)) { + gtk_layout_set_size(GTK_LAYOUT(canvas), scroll_width, scroll_height); + } + + /* Signal GtkLayout that it should do a redraw. */ + + if (changed_x) { + g_signal_emit_by_name(canvas->layout.hadjustment, "value_changed"); + } + + if (changed_y) { + g_signal_emit_by_name(canvas->layout.vadjustment, "value_changed"); + } +} + +/* Size allocation handler for the canvas */ +static void +ganv_canvas_size_allocate(GtkWidget* widget, GtkAllocation* allocation) +{ + g_return_if_fail(GANV_IS_CANVAS(widget)); + g_return_if_fail(allocation != NULL); + + if (GTK_WIDGET_CLASS(canvas_parent_class)->size_allocate) { + (*GTK_WIDGET_CLASS(canvas_parent_class)->size_allocate)(widget, allocation); + } + + GanvCanvas* canvas = GANV_CANVAS(widget); + + /* Recenter the view, if appropriate */ + + canvas->layout.hadjustment->page_size = allocation->width; + canvas->layout.hadjustment->page_increment = allocation->width / 2; + + canvas->layout.vadjustment->page_size = allocation->height; + canvas->layout.vadjustment->page_increment = allocation->height / 2; + + scroll_to(canvas, + canvas->layout.hadjustment->value, + canvas->layout.vadjustment->value); + + g_signal_emit_by_name(canvas->layout.hadjustment, "changed"); + g_signal_emit_by_name(canvas->layout.vadjustment, "changed"); +} + +/* Returns whether the item is an inferior of or is equal to the parent. */ +static gboolean +is_descendant(GanvItem* item, GanvItem* parent) +{ + for (; item; item = item->impl->parent) { + if (item == parent) { + return TRUE; + } + } + + return FALSE; +} + + +/* Emits an event for an item in the canvas, be it the current item, grabbed + * item, or focused item, as appropriate. + */ +int +ganv_canvas_emit_event(GanvCanvas* canvas, GdkEvent* event) +{ + GdkEvent* ev; + gint finished; + GanvItem* item; + GanvItem* parent; + guint mask; + + /* Perform checks for grabbed items */ + + if (canvas->impl->grabbed_item + && !is_descendant(canvas->impl->current_item, canvas->impl->grabbed_item)) { + /* I think this warning is annoying and I don't know what it's for + * so I'll disable it for now. + */ + /* g_warning ("emit_event() returning FALSE!\n");*/ + return FALSE; + } + + if (canvas->impl->grabbed_item) { + switch (event->type) { + case GDK_ENTER_NOTIFY: + mask = GDK_ENTER_NOTIFY_MASK; + break; + + case GDK_LEAVE_NOTIFY: + mask = GDK_LEAVE_NOTIFY_MASK; + break; + + case GDK_MOTION_NOTIFY: + mask = GDK_POINTER_MOTION_MASK; + break; + + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + mask = GDK_BUTTON_PRESS_MASK; + break; + + case GDK_BUTTON_RELEASE: + mask = GDK_BUTTON_RELEASE_MASK; + break; + + case GDK_KEY_PRESS: + mask = GDK_KEY_PRESS_MASK; + break; + + case GDK_KEY_RELEASE: + mask = GDK_KEY_RELEASE_MASK; + break; + + case GDK_SCROLL: + mask = GDK_SCROLL_MASK; + break; + + default: + mask = 0; + break; + } + + if (!(mask & canvas->impl->grabbed_event_mask)) { + return FALSE; + } + } + + /* Convert to world coordinates -- we have two cases because of diferent + * offsets of the fields in the event structures. + */ + + ev = gdk_event_copy(event); + + switch (ev->type) { + case GDK_ENTER_NOTIFY: + case GDK_LEAVE_NOTIFY: + ganv_canvas_window_to_world(canvas, + ev->crossing.x, ev->crossing.y, + &ev->crossing.x, &ev->crossing.y); + break; + + case GDK_MOTION_NOTIFY: + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + ganv_canvas_window_to_world(canvas, + ev->motion.x, ev->motion.y, + &ev->motion.x, &ev->motion.y); + break; + + default: + break; + } + + /* Choose where we send the event */ + + item = canvas->impl->current_item; + + if (canvas->impl->focused_item + && ((event->type == GDK_KEY_PRESS) + || (event->type == GDK_KEY_RELEASE) + || (event->type == GDK_FOCUS_CHANGE))) { + item = canvas->impl->focused_item; + } + + /* The event is propagated up the hierarchy (for if someone connected to + * a group instead of a leaf event), and emission is stopped if a + * handler returns TRUE, just like for GtkWidget events. + */ + + finished = FALSE; + + while (item && !finished) { + g_object_ref(G_OBJECT(item)); + + ganv_item_emit_event(item, ev, &finished); + + parent = item->impl->parent; + g_object_unref(G_OBJECT(item)); + + item = parent; + } + + gdk_event_free(ev); + + return finished; +} + +void +ganv_canvas_set_need_repick(GanvCanvas* canvas) +{ + canvas->impl->need_repick = TRUE; +} + +void +ganv_canvas_forget_item(GanvCanvas* canvas, GanvItem* item) +{ + if (canvas->impl && item == canvas->impl->current_item) { + canvas->impl->current_item = NULL; + canvas->impl->need_repick = TRUE; + } + + if (canvas->impl && item == canvas->impl->new_current_item) { + canvas->impl->new_current_item = NULL; + canvas->impl->need_repick = TRUE; + } + + if (canvas->impl && item == canvas->impl->grabbed_item) { + canvas->impl->grabbed_item = NULL; + gdk_pointer_ungrab(GDK_CURRENT_TIME); + } + + if (canvas->impl && item == canvas->impl->focused_item) { + canvas->impl->focused_item = NULL; + } +} + +void +ganv_canvas_grab_focus(GanvCanvas* canvas, GanvItem* item) +{ + g_return_if_fail(GANV_IS_ITEM(item)); + g_return_if_fail(GTK_WIDGET_CAN_FOCUS(GTK_WIDGET(canvas))); + + GanvItem* focused_item = canvas->impl->focused_item; + GdkEvent ev; + + if (focused_item) { + ev.focus_change.type = GDK_FOCUS_CHANGE; + ev.focus_change.window = canvas->layout.bin_window; + ev.focus_change.send_event = FALSE; + ev.focus_change.in = FALSE; + + ganv_canvas_emit_event(canvas, &ev); + } + + canvas->impl->focused_item = item; + gtk_widget_grab_focus(GTK_WIDGET(canvas)); + + if (focused_item) { + ev.focus_change.type = GDK_FOCUS_CHANGE; + ev.focus_change.window = canvas->layout.bin_window; + ev.focus_change.send_event = FALSE; + ev.focus_change.in = TRUE; + + ganv_canvas_emit_event(canvas, &ev); + } +} + +/** + * ganv_canvas_grab_item: + * @item: A canvas item. + * @event_mask: Mask of events that will be sent to this item. + * @cursor: If non-NULL, the cursor that will be used while the grab is active. + * @etime: The timestamp required for grabbing the mouse, or GDK_CURRENT_TIME. + * + * Specifies that all events that match the specified event mask should be sent + * to the specified item, and also grabs the mouse by calling + * gdk_pointer_grab(). The event mask is also used when grabbing the pointer. + * If @cursor is not NULL, then that cursor is used while the grab is active. + * The @etime parameter is the timestamp required for grabbing the mouse. + * + * Return value: If an item was already grabbed, it returns %GDK_GRAB_ALREADY_GRABBED. If + * the specified item was hidden by calling ganv_item_hide(), then it + * returns %GDK_GRAB_NOT_VIEWABLE. Else, it returns the result of calling + * gdk_pointer_grab(). + **/ +int +ganv_canvas_grab_item(GanvItem* item, guint event_mask, GdkCursor* cursor, guint32 etime) +{ + g_return_val_if_fail(GANV_IS_ITEM(item), GDK_GRAB_NOT_VIEWABLE); + g_return_val_if_fail(GTK_WIDGET_MAPPED(item->impl->canvas), GDK_GRAB_NOT_VIEWABLE); + + if (item->impl->canvas->impl->grabbed_item) { + return GDK_GRAB_ALREADY_GRABBED; + } + + if (!(item->object.flags & GANV_ITEM_VISIBLE)) { + return GDK_GRAB_NOT_VIEWABLE; + } + + int retval = gdk_pointer_grab(item->impl->canvas->layout.bin_window, + FALSE, + (GdkEventMask)event_mask, + NULL, + cursor, + etime); + + if (retval != GDK_GRAB_SUCCESS) { + return retval; + } + + item->impl->canvas->impl->grabbed_item = item; + item->impl->canvas->impl->grabbed_event_mask = event_mask; + item->impl->canvas->impl->current_item = item; /* So that events go to the grabbed item */ + + return retval; +} + +/** + * ganv_canvas_ungrab_item: + * @item: A canvas item that holds a grab. + * @etime: The timestamp for ungrabbing the mouse. + * + * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the + * mouse. + **/ +void +ganv_canvas_ungrab_item(GanvItem* item, guint32 etime) +{ + g_return_if_fail(GANV_IS_ITEM(item)); + + if (item->impl->canvas->impl->grabbed_item != item) { + return; + } + + item->impl->canvas->impl->grabbed_item = NULL; + + gdk_pointer_ungrab(etime); +} + +void +ganv_canvas_get_zoom_offsets(GanvCanvas* canvas, int* x, int* y) +{ + *x = canvas->impl->zoom_xofs; + *y = canvas->impl->zoom_yofs; +} + +/* Re-picks the current item in the canvas, based on the event's coordinates. + * Also emits enter/leave events for items as appropriate. + */ +static int +pick_current_item(GanvCanvas* canvas, GdkEvent* event) +{ + int retval = FALSE; + + /* If a button is down, we'll perform enter and leave events on the + * current item, but not enter on any other item. This is more or less + * like X pointer grabbing for canvas items. + */ + int button_down = canvas->impl->state & (GDK_BUTTON1_MASK + | GDK_BUTTON2_MASK + | GDK_BUTTON3_MASK + | GDK_BUTTON4_MASK + | GDK_BUTTON5_MASK); + if (!button_down) { + canvas->impl->left_grabbed_item = FALSE; + } + + /* Save the event in the canvas. This is used to synthesize enter and + * leave events in case the current item changes. It is also used to + * re-pick the current item if the current one gets deleted. Also, + * synthesize an enter event. + */ + if (event != &canvas->impl->pick_event) { + if ((event->type == GDK_MOTION_NOTIFY) || (event->type == GDK_BUTTON_RELEASE)) { + /* these fields have the same offsets in both types of events */ + + canvas->impl->pick_event.crossing.type = GDK_ENTER_NOTIFY; + canvas->impl->pick_event.crossing.window = event->motion.window; + canvas->impl->pick_event.crossing.send_event = event->motion.send_event; + canvas->impl->pick_event.crossing.subwindow = NULL; + canvas->impl->pick_event.crossing.x = event->motion.x; + canvas->impl->pick_event.crossing.y = event->motion.y; + canvas->impl->pick_event.crossing.mode = GDK_CROSSING_NORMAL; + canvas->impl->pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR; + canvas->impl->pick_event.crossing.focus = FALSE; + canvas->impl->pick_event.crossing.state = event->motion.state; + + /* these fields don't have the same offsets in both types of events */ + + if (event->type == GDK_MOTION_NOTIFY) { + canvas->impl->pick_event.crossing.x_root = event->motion.x_root; + canvas->impl->pick_event.crossing.y_root = event->motion.y_root; + } else { + canvas->impl->pick_event.crossing.x_root = event->button.x_root; + canvas->impl->pick_event.crossing.y_root = event->button.y_root; + } + } else { + canvas->impl->pick_event = *event; + } + } + + /* Don't do anything else if this is a recursive call */ + + if (canvas->impl->in_repick) { + return retval; + } + + /* LeaveNotify means that there is no current item, so we don't look for one */ + + if (canvas->impl->pick_event.type != GDK_LEAVE_NOTIFY) { + /* these fields don't have the same offsets in both types of events */ + + double x, y; + if (canvas->impl->pick_event.type == GDK_ENTER_NOTIFY) { + x = canvas->impl->pick_event.crossing.x - canvas->impl->zoom_xofs; + y = canvas->impl->pick_event.crossing.y - canvas->impl->zoom_yofs; + } else { + x = canvas->impl->pick_event.motion.x - canvas->impl->zoom_xofs; + y = canvas->impl->pick_event.motion.y - canvas->impl->zoom_yofs; + } + + /* world coords */ + + x = canvas->impl->scroll_x1 + x / canvas->impl->pixels_per_unit; + y = canvas->impl->scroll_y1 + y / canvas->impl->pixels_per_unit; + + /* find the closest item */ + + if (canvas->impl->root->object.flags & GANV_ITEM_VISIBLE) { + GANV_ITEM_GET_CLASS(canvas->impl->root)->point( + canvas->impl->root, + x - canvas->impl->root->impl->x, y - canvas->impl->root->impl->y, + &canvas->impl->new_current_item); + } else { + canvas->impl->new_current_item = NULL; + } + } else { + canvas->impl->new_current_item = NULL; + } + + if ((canvas->impl->new_current_item == canvas->impl->current_item) && !canvas->impl->left_grabbed_item) { + return retval; /* current item did not change */ + + } + /* Synthesize events for old and new current items */ + + if ((canvas->impl->new_current_item != canvas->impl->current_item) + && (canvas->impl->current_item != NULL) + && !canvas->impl->left_grabbed_item) { + GdkEvent new_event; + + new_event = canvas->impl->pick_event; + new_event.type = GDK_LEAVE_NOTIFY; + + new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; + new_event.crossing.subwindow = NULL; + canvas->impl->in_repick = TRUE; + retval = ganv_canvas_emit_event(canvas, &new_event); + canvas->impl->in_repick = FALSE; + } + + /* new_current_item may have been set to NULL during the call to ganv_canvas_emit_event() above */ + + if ((canvas->impl->new_current_item != canvas->impl->current_item) && button_down) { + canvas->impl->left_grabbed_item = TRUE; + return retval; + } + + /* Handle the rest of cases */ + + canvas->impl->left_grabbed_item = FALSE; + canvas->impl->current_item = canvas->impl->new_current_item; + + if (canvas->impl->current_item != NULL) { + GdkEvent new_event; + + new_event = canvas->impl->pick_event; + new_event.type = GDK_ENTER_NOTIFY; + new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; + new_event.crossing.subwindow = NULL; + retval = ganv_canvas_emit_event(canvas, &new_event); + } + + return retval; +} + +/* Button event handler for the canvas */ +static gint +ganv_canvas_button(GtkWidget* widget, GdkEventButton* event) +{ + int mask; + int retval; + + g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE); + g_return_val_if_fail(event != NULL, FALSE); + + retval = FALSE; + + GanvCanvas* canvas = GANV_CANVAS(widget); + + /* + * dispatch normally regardless of the event's window if an item has + * has a pointer grab in effect + */ + if (!canvas->impl->grabbed_item && ( event->window != canvas->layout.bin_window) ) { + return retval; + } + + switch (event->button) { + case 1: + mask = GDK_BUTTON1_MASK; + break; + case 2: + mask = GDK_BUTTON2_MASK; + break; + case 3: + mask = GDK_BUTTON3_MASK; + break; + case 4: + mask = GDK_BUTTON4_MASK; + break; + case 5: + mask = GDK_BUTTON5_MASK; + break; + default: + mask = 0; + } + + switch (event->type) { + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + /* Pick the current item as if the button were not pressed, and + * then process the event. + */ + canvas->impl->state = event->state; + pick_current_item(canvas, (GdkEvent*)event); + canvas->impl->state ^= mask; + retval = ganv_canvas_emit_event(canvas, (GdkEvent*)event); + break; + + case GDK_BUTTON_RELEASE: + /* Process the event as if the button were pressed, then repick + * after the button has been released + */ + canvas->impl->state = event->state; + retval = ganv_canvas_emit_event(canvas, (GdkEvent*)event); + event->state ^= mask; + canvas->impl->state = event->state; + pick_current_item(canvas, (GdkEvent*)event); + event->state ^= mask; + break; + + default: + g_assert_not_reached(); + } + + return retval; +} + +/* Motion event handler for the canvas */ +static gint +ganv_canvas_motion(GtkWidget* widget, GdkEventMotion* event) +{ + g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE); + g_return_val_if_fail(event != NULL, FALSE); + + GanvCanvas* canvas = GANV_CANVAS(widget); + + if (event->window != canvas->layout.bin_window) { + return FALSE; + } + + canvas->impl->state = event->state; + pick_current_item(canvas, (GdkEvent*)event); + return ganv_canvas_emit_event(canvas, (GdkEvent*)event); +} + +static gboolean +ganv_canvas_scroll(GtkWidget* widget, GdkEventScroll* event) +{ + g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE); + g_return_val_if_fail(event != NULL, FALSE); + + GanvCanvas* canvas = GANV_CANVAS(widget); + + if (event->window != canvas->layout.bin_window) { + return FALSE; + } + + canvas->impl->state = event->state; + pick_current_item(canvas, (GdkEvent*)event); + return ganv_canvas_emit_event(canvas, (GdkEvent*)event); +} + +/* Key event handler for the canvas */ +static gboolean +ganv_canvas_key(GtkWidget* widget, GdkEventKey* event) +{ + g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE); + g_return_val_if_fail(event != NULL, FALSE); + + GanvCanvas* canvas = GANV_CANVAS(widget); + + if (!ganv_canvas_emit_event(canvas, (GdkEvent*)event)) { + GtkWidgetClass* widget_class; + + widget_class = GTK_WIDGET_CLASS(canvas_parent_class); + + if (event->type == GDK_KEY_PRESS) { + if (widget_class->key_press_event) { + return (*widget_class->key_press_event)(widget, event); + } + } else if (event->type == GDK_KEY_RELEASE) { + if (widget_class->key_release_event) { + return (*widget_class->key_release_event)(widget, event); + } + } else { + g_assert_not_reached(); + } + + return FALSE; + } else { + return TRUE; + } +} + +/* Crossing event handler for the canvas */ +static gint +ganv_canvas_crossing(GtkWidget* widget, GdkEventCrossing* event) +{ + g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE); + g_return_val_if_fail(event != NULL, FALSE); + + GanvCanvas* canvas = GANV_CANVAS(widget); + + if (event->window != canvas->layout.bin_window) { + return FALSE; + } + + canvas->impl->state = event->state; + return pick_current_item(canvas, (GdkEvent*)event); +} + +/* Focus in handler for the canvas */ +static gint +ganv_canvas_focus_in(GtkWidget* widget, GdkEventFocus* event) +{ + GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS); + + GanvCanvas* canvas = GANV_CANVAS(widget); + + if (canvas->impl->focused_item) { + return ganv_canvas_emit_event(canvas, (GdkEvent*)event); + } else { + return FALSE; + } +} + +/* Focus out handler for the canvas */ +static gint +ganv_canvas_focus_out(GtkWidget* widget, GdkEventFocus* event) +{ + GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS); + + GanvCanvas* canvas = GANV_CANVAS(widget); + + if (canvas->impl->focused_item) { + return ganv_canvas_emit_event(canvas, (GdkEvent*)event); + } else { + return FALSE; + } +} + +#define REDRAW_QUANTUM_SIZE 512 + +static void +ganv_canvas_paint_rect(GanvCanvas* canvas, gint x0, gint y0, gint x1, gint y1) +{ + gint draw_x1, draw_y1; + gint draw_x2, draw_y2; + gint draw_width, draw_height; + + g_return_if_fail(!canvas->impl->need_update); + + draw_x1 = MAX(x0, canvas->layout.hadjustment->value - canvas->impl->zoom_xofs); + draw_y1 = MAX(y0, canvas->layout.vadjustment->value - canvas->impl->zoom_yofs); + draw_x2 = MIN(draw_x1 + GTK_WIDGET(canvas)->allocation.width, x1); + draw_y2 = MIN(draw_y1 + GTK_WIDGET(canvas)->allocation.height, y1); + + draw_width = draw_x2 - draw_x1; + draw_height = draw_y2 - draw_y1; + + if ((draw_width < 1) || (draw_height < 1)) { + return; + } + + canvas->impl->redraw_x1 = draw_x1; + canvas->impl->redraw_y1 = draw_y1; + canvas->impl->redraw_x2 = draw_x2; + canvas->impl->redraw_y2 = draw_y2; + canvas->impl->draw_xofs = draw_x1; + canvas->impl->draw_yofs = draw_y1; + + cairo_t* cr = gdk_cairo_create(canvas->layout.bin_window); + + double win_x, win_y; + ganv_canvas_window_to_world(canvas, 0, 0, &win_x, &win_y); + cairo_translate(cr, -win_x, -win_y); + cairo_scale(cr, canvas->impl->pixels_per_unit, canvas->impl->pixels_per_unit); + + if (canvas->impl->root->object.flags & GANV_ITEM_VISIBLE) { + double wx1, wy1, ww, wh; + ganv_canvas_c2w(canvas, draw_x1, draw_y1, &wx1, &wy1); + ganv_canvas_c2w(canvas, draw_width, draw_height, &ww, &wh); + + // Draw background + double r, g, b, a; + color_to_rgba(DEFAULT_BACKGROUND_COLOR, &r, &g, &b, &a); + cairo_set_source_rgba(cr, r, g, b, a); + cairo_rectangle(cr, wx1, wy1, ww, wh); + cairo_fill(cr); + + // Draw root group + (*GANV_ITEM_GET_CLASS(canvas->impl->root)->draw)( + canvas->impl->root, cr, + wx1, wy1, ww, wh); + } + + cairo_destroy(cr); +} + +/* Expose handler for the canvas */ +static gint +ganv_canvas_expose(GtkWidget* widget, GdkEventExpose* event) +{ + GanvCanvas* canvas = GANV_CANVAS(widget); + if (!GTK_WIDGET_DRAWABLE(widget) || + (event->window != canvas->layout.bin_window)) { + return FALSE; + } + + /* Find a single bounding rectangle for all rectangles in the region. + Since drawing the root group is O(n) and thus very expensive for large + canvases, it's much faster to do a single paint than many, even though + more area may be painted that way. With a better group implementation, + it would likely be better to paint each changed rectangle separately. */ + GdkRectangle clip; + gdk_region_get_clipbox(event->region, &clip); + + const int x2 = clip.x + clip.width; + const int y2 = clip.y + clip.height; + + if (canvas->impl->need_update || canvas->impl->need_redraw) { + /* Update or drawing is scheduled, so just mark exposed area as dirty */ + ganv_canvas_request_redraw_c(canvas, clip.x, clip.y, x2, y2); + } else { + /* No pending updates, draw exposed area immediately */ + ganv_canvas_paint_rect(canvas, clip.x, clip.y, x2, y2); + + /* And call expose on parent container class */ + if (GTK_WIDGET_CLASS(canvas_parent_class)->expose_event) { + (*GTK_WIDGET_CLASS(canvas_parent_class)->expose_event)( + widget, event); + } + } + + return FALSE; +} + +/* Repaints the areas in the canvas that need it */ +static void +paint(GanvCanvas* canvas) +{ + for (GSList* l = canvas->impl->redraw_region; l; l = l->next) { + IRect* rect = (IRect*)l->data; + + const GdkRectangle gdkrect = { + rect->x + canvas->impl->zoom_xofs, + rect->y + canvas->impl->zoom_yofs, + rect->width, + rect->height + }; + + gdk_window_invalidate_rect(canvas->layout.bin_window, &gdkrect, FALSE); + g_free(rect); + } + + g_slist_free(canvas->impl->redraw_region); + canvas->impl->redraw_region = NULL; + canvas->impl->need_redraw = FALSE; + + canvas->impl->redraw_x1 = 0; + canvas->impl->redraw_y1 = 0; + canvas->impl->redraw_x2 = 0; + canvas->impl->redraw_y2 = 0; +} + +static void +do_update(GanvCanvas* canvas) +{ + /* Cause the update if necessary */ + +update_again: + if (canvas->impl->need_update) { + ganv_item_invoke_update(canvas->impl->root, 0); + + canvas->impl->need_update = FALSE; + } + + /* Pick new current item */ + + while (canvas->impl->need_repick) { + canvas->impl->need_repick = FALSE; + pick_current_item(canvas, &canvas->impl->pick_event); + } + + /* it is possible that during picking we emitted an event in which + the user then called some function which then requested update + of something. Without this we'd be left in a state where + need_update would have been left TRUE and the canvas would have + been left unpainted. */ + if (canvas->impl->need_update) { + goto update_again; + } + + /* Paint if able to */ + + if (GTK_WIDGET_DRAWABLE(canvas) && canvas->impl->need_redraw) { + paint(canvas); + } +} + +/* Idle handler for the canvas. It deals with pending updates and redraws. */ +static gboolean +idle_handler(gpointer data) +{ + GDK_THREADS_ENTER(); + + GanvCanvas* canvas = GANV_CANVAS(data); + + do_update(canvas); + + /* Reset idle id */ + canvas->impl->idle_id = 0; + + GDK_THREADS_LEAVE(); + + return FALSE; +} + +/* Convenience function to add an idle handler to a canvas */ +static void +add_idle(GanvCanvas* canvas) +{ + g_assert(canvas->impl->need_update || canvas->impl->need_redraw); + + if (!canvas->impl->idle_id) { + canvas->impl->idle_id = g_idle_add_full(CANVAS_IDLE_PRIORITY, + idle_handler, + canvas, + NULL); + } + + /* canvas->idle_id = gtk_idle_add (idle_handler, canvas); */ +} + +GanvItem* +ganv_canvas_root(GanvCanvas* canvas) +{ + g_return_val_if_fail(GANV_IS_CANVAS(canvas), NULL); + + return canvas->impl->root; +} + +void +ganv_canvas_set_scroll_region(GanvCanvas* canvas, + double x1, double y1, double x2, double y2) +{ + double wxofs, wyofs; + int xofs, yofs; + + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + /* + * Set the new scrolling region. If possible, do not move the visible contents of the + * canvas. + */ + + ganv_canvas_c2w(canvas, + GTK_LAYOUT(canvas)->hadjustment->value + canvas->impl->zoom_xofs, + GTK_LAYOUT(canvas)->vadjustment->value + canvas->impl->zoom_yofs, + /*canvas->impl->zoom_xofs, + canvas->impl->zoom_yofs,*/ + &wxofs, &wyofs); + + canvas->impl->scroll_x1 = x1; + canvas->impl->scroll_y1 = y1; + canvas->impl->scroll_x2 = x2; + canvas->impl->scroll_y2 = y2; + + ganv_canvas_w2c(canvas, wxofs, wyofs, &xofs, &yofs); + + scroll_to(canvas, xofs, yofs); + + canvas->impl->need_repick = TRUE; +#if 0 + /* todo: should be requesting update */ + (*GANV_ITEM_CLASS(canvas->impl->root->object.klass)->update)( + canvas->impl->root, NULL, NULL, 0); +#endif +} + +void +ganv_canvas_get_scroll_region(GanvCanvas* canvas, + double* x1, double* y1, double* x2, double* y2) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + if (x1) { + *x1 = canvas->impl->scroll_x1; + } + + if (y1) { + *y1 = canvas->impl->scroll_y1; + } + + if (x2) { + *x2 = canvas->impl->scroll_x2; + } + + if (y2) { + *y2 = canvas->impl->scroll_y2; + } +} + +void +ganv_canvas_set_center_scroll_region(GanvCanvas* canvas, gboolean center_scroll_region) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + canvas->impl->center_scroll_region = center_scroll_region != 0; + + scroll_to(canvas, + canvas->layout.hadjustment->value, + canvas->layout.vadjustment->value); +} + +gboolean +ganv_canvas_get_center_scroll_region(const GanvCanvas* canvas) +{ + g_return_val_if_fail(GANV_IS_CANVAS(canvas), FALSE); + + return canvas->impl->center_scroll_region ? TRUE : FALSE; +} + +void +ganv_canvas_scroll_to(GanvCanvas* canvas, int cx, int cy) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + scroll_to(canvas, cx, cy); +} + +void +ganv_canvas_get_scroll_offsets(const GanvCanvas* canvas, int* cx, int* cy) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + if (cx) { + *cx = canvas->layout.hadjustment->value; + } + + if (cy) { + *cy = canvas->layout.vadjustment->value; + } +} + +GanvItem* +ganv_canvas_get_item_at(GanvCanvas* canvas, double x, double y) +{ + g_return_val_if_fail(GANV_IS_CANVAS(canvas), NULL); + + GanvItem* item = NULL; + double dist = GANV_ITEM_GET_CLASS(canvas->impl->root)->point( + canvas->impl->root, + x - canvas->impl->root->impl->x, + y - canvas->impl->root->impl->y, + &item); + if ((int)(dist * canvas->impl->pixels_per_unit + 0.5) <= GANV_CLOSE_ENOUGH) { + return item; + } else { + return NULL; + } +} + +void +ganv_canvas_request_update(GanvCanvas* canvas) +{ + if (canvas->impl->need_update) { + return; + } + + canvas->impl->need_update = TRUE; + if (GTK_WIDGET_MAPPED((GtkWidget*)canvas)) { + add_idle(canvas); + } +} + +static inline gboolean +rect_overlaps(const IRect* a, const IRect* b) +{ + if ((a->x > b->x + b->width) || + (a->y > b->y + b->height) || + (a->x + a->width < b->x) || + (a->y + a->height < b->y)) { + return FALSE; + } + return TRUE; +} + +static inline gboolean +rect_is_visible(GanvCanvas* canvas, const IRect* r) +{ + const IRect rect = { + (int)(canvas->layout.hadjustment->value - canvas->impl->zoom_xofs), + (int)(canvas->layout.vadjustment->value - canvas->impl->zoom_yofs), + GTK_WIDGET(canvas)->allocation.width, + GTK_WIDGET(canvas)->allocation.height + }; + + return rect_overlaps(&rect, r); +} + +void +ganv_canvas_request_redraw_c(GanvCanvas* canvas, + int x1, int y1, int x2, int y2) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + if (!GTK_WIDGET_DRAWABLE(canvas) || (x1 >= x2) || (y1 >= y2)) { + return; + } + + const IRect rect = { x1, y1, x2 - x1, y2 - y1 }; + + if (!rect_is_visible(canvas, &rect)) { + return; + } + + IRect* r = (IRect*)g_malloc(sizeof(IRect)); + *r = rect; + + canvas->impl->redraw_region = g_slist_prepend(canvas->impl->redraw_region, r); + canvas->impl->need_redraw = TRUE; + + if (canvas->impl->idle_id == 0) { + add_idle(canvas); + } +} + +/* Request a redraw of the specified rectangle in world coordinates */ +void +ganv_canvas_request_redraw_w(GanvCanvas* canvas, + double x1, double y1, double x2, double y2) +{ + int cx1, cx2, cy1, cy2; + ganv_canvas_w2c(canvas, x1, y1, &cx1, &cy1); + ganv_canvas_w2c(canvas, x2, y2, &cx2, &cy2); + ganv_canvas_request_redraw_c(canvas, cx1, cy1, cx2, cy2); +} + +void +ganv_canvas_w2c_affine(GanvCanvas* canvas, cairo_matrix_t* matrix) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + g_return_if_fail(matrix != NULL); + + cairo_matrix_init_translate(matrix, + -canvas->impl->scroll_x1, + -canvas->impl->scroll_y1); + + cairo_matrix_scale(matrix, + canvas->impl->pixels_per_unit, + canvas->impl->pixels_per_unit); +} + +void +ganv_canvas_w2c(GanvCanvas* canvas, double wx, double wy, int* cx, int* cy) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + cairo_matrix_t matrix; + ganv_canvas_w2c_affine(canvas, &matrix); + + cairo_matrix_transform_point(&matrix, &wx, &wy); + + if (cx) { + *cx = floor(wx + 0.5); + } + if (cy) { + *cy = floor(wy + 0.5); + } +} + +void +ganv_canvas_w2c_d(GanvCanvas* canvas, double wx, double wy, double* cx, double* cy) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + cairo_matrix_t matrix; + ganv_canvas_w2c_affine(canvas, &matrix); + + cairo_matrix_transform_point(&matrix, &wx, &wy); + + if (cx) { + *cx = wx; + } + if (cy) { + *cy = wy; + } +} + +void +ganv_canvas_c2w(GanvCanvas* canvas, int cx, int cy, double* wx, double* wy) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + cairo_matrix_t matrix; + ganv_canvas_w2c_affine(canvas, &matrix); + cairo_matrix_invert(&matrix); + + double x = cx; + double y = cy; + cairo_matrix_transform_point(&matrix, &x, &y); + + if (wx) { + *wx = x; + } + if (wy) { + *wy = y; + } +} + +void +ganv_canvas_window_to_world(GanvCanvas* canvas, double winx, double winy, + double* worldx, double* worldy) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + if (worldx) { + *worldx = canvas->impl->scroll_x1 + ((winx - canvas->impl->zoom_xofs) + / canvas->impl->pixels_per_unit); + } + + if (worldy) { + *worldy = canvas->impl->scroll_y1 + ((winy - canvas->impl->zoom_yofs) + / canvas->impl->pixels_per_unit); + } +} + +void +ganv_canvas_world_to_window(GanvCanvas* canvas, double worldx, double worldy, + double* winx, double* winy) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + if (winx) { + *winx = (canvas->impl->pixels_per_unit) * (worldx - canvas->impl->scroll_x1) + canvas->impl->zoom_xofs; + } + + if (winy) { + *winy = (canvas->impl->pixels_per_unit) * (worldy - canvas->impl->scroll_y1) + canvas->impl->zoom_yofs; + } +} + +void +ganv_canvas_set_port_order(GanvCanvas* canvas, + GanvPortOrderFunc port_cmp, + void* data) +{ + g_return_if_fail(GANV_IS_CANVAS(canvas)); + + canvas->impl->_port_order.port_cmp = port_cmp; + canvas->impl->_port_order.data = data; +} + +PortOrderCtx +ganv_canvas_get_port_order(GanvCanvas* canvas) +{ + return canvas->impl->_port_order; +} + +gboolean +ganv_canvas_exporting(GanvCanvas* canvas) +{ + return canvas->impl->exporting; +} + +} // extern "C" diff --git a/src/Port.cpp b/src/Port.cpp new file mode 100644 index 0000000..3f0d02b --- /dev/null +++ b/src/Port.cpp @@ -0,0 +1,57 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <string> + +#include <glib.h> + +#include "ganv/Canvas.hpp" +#include "ganv/Module.hpp" +#include "ganv/Port.hpp" + +#include "./color.h" + +namespace Ganv { + +static void +on_value_changed(GanvPort* port, double value, void* portmm) +{ + ((Port*)portmm)->signal_value_changed.emit(value); +} + +/* Construct a Port on an existing module. */ +Port::Port(Module& module, + const std::string& name, + bool is_input, + uint32_t color) + : Box(module.canvas(), + GANV_BOX(ganv_port_new(module.gobj(), is_input, + "fill-color", color, + "border-color", PORT_BORDER_COLOR(color), + "border-width", 2.0, + "label", name.c_str(), + NULL))) +{ + g_signal_connect(gobj(), "value-changed", + G_CALLBACK(on_value_changed), this); +} + +Module* +Port::get_module() const +{ + return Glib::wrap(ganv_port_get_module(gobj())); +} + +} // namespace Ganv diff --git a/src/boilerplate.h b/src/boilerplate.h new file mode 100644 index 0000000..a419711 --- /dev/null +++ b/src/boilerplate.h @@ -0,0 +1,43 @@ +/* This file is part of Ganv. + * Copyright 2007-2016 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +typedef gpointer gobject; + +/** + A case in a switch statement in a set_properties implementation. + @prop: Property enumeration ID. + @type: Name of the value type, e.g. uint for guint. + @field: Field to set to the new value. +*/ +#define SET_CASE(prop, type, field) \ + case PROP_##prop: { \ + const g##type tmp = g_value_get_##type(value); \ + if ((field) != tmp) { \ + (field) = tmp; \ + ganv_item_request_update(GANV_ITEM(object)); \ + } \ + break; \ + } + +/** + A case in a switch statement in a get_properties implementation. + @prop: Property enumeration ID. + @type: Name of the value type, e.g. uint for guint. + @field: Field to set to the new value. +*/ +#define GET_CASE(prop, type, field) \ + case PROP_##prop: \ + g_value_set_##type(value, field); \ + break; diff --git a/src/box.c b/src/box.c new file mode 100644 index 0000000..78afe69 --- /dev/null +++ b/src/box.c @@ -0,0 +1,563 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <string.h> + +#include <cairo.h> + +#include "ganv/box.h" + +#include "./boilerplate.h" +#include "./color.h" +#include "./gettext.h" +#include "./ganv-private.h" + +static const double STACKED_OFFSET = 4.0; + +G_DEFINE_TYPE(GanvBox, ganv_box, GANV_TYPE_NODE) + +static GanvNodeClass* parent_class; + +enum { + PROP_0, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2, + PROP_RADIUS_TL, + PROP_RADIUS_TR, + PROP_RADIUS_BR, + PROP_RADIUS_BL, + PROP_STACKED, + PROP_BEVELED +}; + +static void +ganv_box_init(GanvBox* box) +{ + box->impl = G_TYPE_INSTANCE_GET_PRIVATE( + box, GANV_TYPE_BOX, GanvBoxImpl); + + memset(&box->impl->coords, '\0', sizeof(GanvBoxCoords)); + + box->impl->coords.border_width = GANV_NODE(box)->impl->border_width; + box->impl->old_coords = box->impl->coords; + box->impl->radius_tl = 0.0; + box->impl->radius_tr = 0.0; + box->impl->radius_br = 0.0; + box->impl->radius_bl = 0.0; + box->impl->beveled = FALSE; +} + +static void +ganv_box_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_BOX(object)); + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +static void +ganv_box_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_BOX(object)); + + GanvBox* box = GANV_BOX(object); + GanvBoxImpl* impl = box->impl; + GanvBoxCoords* coords = &impl->coords; + + switch (prop_id) { + SET_CASE(X1, double, coords->x1); + SET_CASE(Y1, double, coords->y1); + SET_CASE(X2, double, coords->x2); + SET_CASE(Y2, double, coords->y2); + SET_CASE(RADIUS_TL, double, impl->radius_tl); + SET_CASE(RADIUS_TR, double, impl->radius_tr); + SET_CASE(RADIUS_BR, double, impl->radius_br); + SET_CASE(RADIUS_BL, double, impl->radius_bl); + SET_CASE(STACKED, boolean, coords->stacked); + SET_CASE(BEVELED, boolean, impl->beveled); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_box_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_BOX(object)); + + GanvBox* box = GANV_BOX(object); + GanvBoxImpl* impl = box->impl; + GanvBoxCoords* coords = &impl->coords; + + switch (prop_id) { + GET_CASE(X1, double, coords->x1); + GET_CASE(X2, double, coords->x2); + GET_CASE(Y1, double, coords->y1); + GET_CASE(Y2, double, coords->y2); + GET_CASE(RADIUS_TL, double, impl->radius_tl); + GET_CASE(RADIUS_TR, double, impl->radius_tr); + GET_CASE(RADIUS_BR, double, impl->radius_br); + GET_CASE(RADIUS_BL, double, impl->radius_bl); + GET_CASE(STACKED, boolean, impl->coords.stacked); + GET_CASE(BEVELED, boolean, impl->beveled); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_box_bounds_item(const GanvBoxCoords* coords, + double* x1, double* y1, + double* x2, double* y2) +{ + *x1 = coords->x1 - coords->border_width; + *y1 = coords->y1 - coords->border_width; + *x2 = coords->x2 + coords->border_width + (coords->stacked * STACKED_OFFSET); + *y2 = coords->y2 + coords->border_width + (coords->stacked * STACKED_OFFSET); +} + +void +ganv_box_request_redraw(GanvItem* item, + const GanvBoxCoords* coords, + gboolean world) +{ + double x1, y1, x2, y2; + ganv_box_bounds_item(coords, &x1, &y1, &x2, &y2); + + if (!world) { + // Convert from item-relative coordinates to world coordinates + ganv_item_i2w_pair(item, &x1, &y1, &x2, &y2); + } + + ganv_canvas_request_redraw_w(item->impl->canvas, x1, y1, x2, y2); +} + +static void +coords_i2w(GanvItem* item, GanvBoxCoords* coords) +{ + ganv_item_i2w_pair(item, &coords->x1, &coords->y1, &coords->x2, &coords->y2); +} + +static void +ganv_box_bounds(GanvItem* item, + double* x1, double* y1, + double* x2, double* y2) +{ + // Note this will not be correct if children are outside the box bounds + GanvBox* box = (GanvBox*)item; + ganv_box_bounds_item(&box->impl->coords, x1, y1, x2, y2); +} + +static void +ganv_box_update(GanvItem* item, int flags) +{ + GanvBox* box = GANV_BOX(item); + GanvBoxImpl* impl = box->impl; + impl->coords.border_width = box->node.impl->border_width; + + // Request redraw of old location + ganv_box_request_redraw(item, &impl->old_coords, TRUE); + + // Store old coordinates in world relative coordinates in case the + // group we are in moves between now and the next update + impl->old_coords = impl->coords; + coords_i2w(item, &impl->old_coords); + + // Call parent class update method, resizing if necessary + GANV_ITEM_CLASS(parent_class)->update(item, flags); + ganv_box_normalize(box); + + // Update world-relative bounding box + ganv_box_bounds(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2); + ganv_item_i2w_pair(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2); + + // Request redraw of new location + ganv_box_request_redraw(item, &impl->coords, FALSE); +} + +void +ganv_box_path(GanvBox* box, + cairo_t* cr, double x1, double y1, double x2, double y2, + double dr) +{ + static const double degrees = G_PI / 180.0; + + GanvBoxImpl* impl = box->impl; + + if (impl->radius_tl == 0.0 && impl->radius_tr == 0.0 + && impl->radius_br == 0.0 && impl->radius_bl == 0.0) { + // Simple rectangle + cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1); + } else if (impl->beveled) { + // Beveled rectangle + cairo_new_sub_path(cr); + cairo_move_to(cr, x1 + impl->radius_tl, y1); + cairo_line_to(cr, x2 - impl->radius_tr, y1); + cairo_line_to(cr, x2, y1 + impl->radius_tr); + cairo_line_to(cr, x2, y2 - impl->radius_br); + cairo_line_to(cr, x2 - impl->radius_br, y2); + cairo_line_to(cr, x1 + impl->radius_bl, y2); + cairo_line_to(cr, x1, y2 - impl->radius_bl); + cairo_line_to(cr, x1, y2 - impl->radius_bl); + cairo_line_to(cr, x1, y1 + impl->radius_tl); + cairo_close_path(cr); + } else { + // Rounded rectangle + cairo_new_sub_path(cr); + cairo_arc(cr, + x2 - impl->radius_tr - dr, + y1 + impl->radius_tr + dr, + impl->radius_tr + dr, -90 * degrees, 0 * degrees); + cairo_arc(cr, + x2 - impl->radius_br - dr, y2 - impl->radius_br - dr, + impl->radius_br + dr, 0 * degrees, 90 * degrees); + cairo_arc(cr, + x1 + impl->radius_bl + dr, y2 - impl->radius_bl - dr, + impl->radius_bl + dr, 90 * degrees, 180 * degrees); + cairo_arc(cr, + x1 + impl->radius_tl + dr, y1 + impl->radius_tl + dr, + impl->radius_tl + dr, 180 * degrees, 270 * degrees); + cairo_close_path(cr); + } +} + +static void +ganv_box_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + GanvBox* box = GANV_BOX(item); + GanvBoxImpl* impl = box->impl; + + double x1 = impl->coords.x1; + double y1 = impl->coords.y1; + double x2 = impl->coords.x2; + double y2 = impl->coords.y2; + ganv_item_i2w_pair(item, &x1, &y1, &x2, &y2); + + double dash_length, border_color, fill_color; + ganv_node_get_draw_properties( + &box->node, &dash_length, &border_color, &fill_color); + + double r, g, b, a; + + for (int i = (impl->coords.stacked ? 1 : 0); i >= 0; --i) { + const double x = 0.0 - (STACKED_OFFSET * i); + const double y = 0.0 - (STACKED_OFFSET * i); + + // Trace basic box path + ganv_box_path(box, cr, x1 - x, y1 - y, x2 - x, y2 - y, 0.0); + + // Fill + color_to_rgba(fill_color, &r, &g, &b, &a); + cairo_set_source_rgba(cr, r, g, b, a); + + // Border + if (impl->coords.border_width > 0.0) { + cairo_fill_preserve(cr); + color_to_rgba(border_color, &r, &g, &b, &a); + cairo_set_source_rgba(cr, r, g, b, a); + cairo_set_line_width(cr, impl->coords.border_width); + if (dash_length > 0) { + cairo_set_dash(cr, &dash_length, 1, box->node.impl->dash_offset); + } else { + cairo_set_dash(cr, &dash_length, 0, 0); + } + cairo_stroke(cr); + } else { + cairo_fill(cr); + } + } + + GanvItemClass* item_class = GANV_ITEM_CLASS(parent_class); + item_class->draw(item, cr, cx, cy, cw, ch); +} + +static double +ganv_box_point(GanvItem* item, double x, double y, GanvItem** actual_item) +{ + GanvBox* box = GANV_BOX(item); + GanvBoxImpl* impl = box->impl; + + *actual_item = NULL; + + double x1, y1, x2, y2; + ganv_box_bounds_item(&impl->coords, &x1, &y1, &x2, &y2); + + // Point is inside the box (distance 0) + if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) { + *actual_item = item; + return 0.0; + } + + // Point is outside the box + double dx = 0.0; + double dy = 0.0; + + // Find horizontal distance to nearest edge + if (x < x1) { + dx = x1 - x; + } else if (x > x2) { + dx = x - x2; + } + + // Find vertical distance to nearest edge + if (y < y1) { + dy = y1 - y; + } else if (y > y2) { + dy = y - y2; + } + + return sqrt((dx * dx) + (dy * dy)); +} + +static gboolean +ganv_box_is_within(const GanvNode* self, + double x1, + double y1, + double x2, + double y2) +{ + double bx1, by1, bx2, by2; + g_object_get(G_OBJECT(self), + "x1", &bx1, + "y1", &by1, + "x2", &bx2, + "y2", &by2, + NULL); + + ganv_item_i2w_pair(GANV_ITEM(self), &bx1, &by1, &bx2, &by2); + + return ( bx1 >= x1 + && by2 >= y1 + && bx2 <= x2 + && by2 <= y2); +} + +static void +ganv_box_default_set_width(GanvBox* box, double width) +{ + box->impl->coords.x2 = ganv_box_get_x1(box) + width; + ganv_item_request_update(GANV_ITEM(box)); +} + +static void +ganv_box_default_set_height(GanvBox* box, double height) +{ + box->impl->coords.y2 = ganv_box_get_y1(box) + height; + ganv_item_request_update(GANV_ITEM(box)); +} + +static void +ganv_box_class_init(GanvBoxClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GanvItemClass* item_class = (GanvItemClass*)klass; + GanvNodeClass* node_class = (GanvNodeClass*)klass; + + parent_class = GANV_NODE_CLASS(g_type_class_peek_parent(klass)); + + g_type_class_add_private(klass, sizeof(GanvBoxImpl)); + + gobject_class->set_property = ganv_box_set_property; + gobject_class->get_property = ganv_box_get_property; + + g_object_class_install_property( + gobject_class, PROP_X1, g_param_spec_double( + "x1", + _("x1"), + _("Top left x coordinate."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_Y1, g_param_spec_double( + "y1", + _("y1"), + _("Top left y coordinate."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_X2, g_param_spec_double( + "x2", + _("x2"), + _("Bottom right x coordinate."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_Y2, g_param_spec_double( + "y2", + _("y2"), + _("Bottom right y coordinate."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_RADIUS_TL, g_param_spec_double( + "radius-tl", + _("Top left radius"), + _("The radius of the top left corner."), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_RADIUS_TR, g_param_spec_double( + "radius-tr", + _("Top right radius"), + _("The radius of the top right corner."), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_RADIUS_BR, g_param_spec_double( + "radius-br", + _("Bottom right radius"), + _("The radius of the bottom right corner."), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_RADIUS_BL, g_param_spec_double( + "radius-bl", + _("Bottom left radius"), + _("The radius of the bottom left corner."), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_STACKED, g_param_spec_boolean( + "stacked", + _("Stacked"), + _("Show box with a stacked appearance."), + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_BEVELED, g_param_spec_boolean( + "beveled", + _("Beveled"), + _("Show radiused corners with a sharp bevel."), + FALSE, + G_PARAM_READWRITE)); + + object_class->destroy = ganv_box_destroy; + + item_class->update = ganv_box_update; + item_class->bounds = ganv_box_bounds; + item_class->point = ganv_box_point; + item_class->draw = ganv_box_draw; + + node_class->is_within = ganv_box_is_within; + + klass->set_width = ganv_box_default_set_width; + klass->set_height = ganv_box_default_set_height; +} + +void +ganv_box_normalize(GanvBox* box) +{ + if (box->impl->coords.x2 < box->impl->coords.x1) { + const double tmp = box->impl->coords.x1; + box->impl->coords.x1 = box->impl->coords.x2; + box->impl->coords.x2 = tmp; + } + if (box->impl->coords.y2 < box->impl->coords.y1) { + const double tmp = box->impl->coords.y1; + box->impl->coords.y1 = box->impl->coords.y2; + box->impl->coords.y2 = tmp; + } +} + +double +ganv_box_get_x1(const GanvBox* box) +{ + return box->impl->coords.x1; +} + +double +ganv_box_get_y1(const GanvBox* box) +{ + return box->impl->coords.y1; +} + +double +ganv_box_get_x2(const GanvBox* box) +{ + return box->impl->coords.x2; +} + +double +ganv_box_get_y2(const GanvBox* box) +{ + return box->impl->coords.y2; +} + +double +ganv_box_get_width(const GanvBox* box) +{ + return box->impl->coords.x2 - box->impl->coords.x1; +} + +void +ganv_box_set_width(GanvBox* box, + double width) +{ + GANV_BOX_GET_CLASS(box)->set_width(box, width); +} + +double +ganv_box_get_height(const GanvBox* box) +{ + return box->impl->coords.y2 - box->impl->coords.y1; +} + +void +ganv_box_set_height(GanvBox* box, + double height) +{ + GANV_BOX_GET_CLASS(box)->set_height(box, height); +} + +double +ganv_box_get_border_width(const GanvBox* box) +{ + return box->impl->coords.border_width; +} diff --git a/src/circle.c b/src/circle.c new file mode 100644 index 0000000..87cc59f --- /dev/null +++ b/src/circle.c @@ -0,0 +1,469 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <string.h> + +#include "ganv/canvas.h" +#include "ganv/circle.h" + +#include "./color.h" +#include "./boilerplate.h" +#include "./gettext.h" +#include "./ganv-private.h" + +G_DEFINE_TYPE(GanvCircle, ganv_circle, GANV_TYPE_NODE) + +static GanvNodeClass* parent_class; + +enum { + PROP_0, + PROP_RADIUS, + PROP_RADIUS_EMS, + PROP_FIT_LABEL +}; + +static void +ganv_circle_init(GanvCircle* circle) +{ + circle->impl = G_TYPE_INSTANCE_GET_PRIVATE( + circle, GANV_TYPE_CIRCLE, GanvCircleImpl); + + memset(&circle->impl->coords, '\0', sizeof(GanvCircleCoords)); + circle->impl->coords.radius = 0.0; + circle->impl->coords.radius_ems = 1.0; + circle->impl->coords.width = 2.0; + circle->impl->old_coords = circle->impl->coords; + circle->impl->fit_label = TRUE; +} + +static void +ganv_circle_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_CIRCLE(object)); + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +static void +ganv_circle_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_CIRCLE(object)); + + GanvCircle* circle = GANV_CIRCLE(object); + + switch (prop_id) { + SET_CASE(RADIUS, double, circle->impl->coords.radius); + SET_CASE(RADIUS_EMS, double, circle->impl->coords.radius_ems); + SET_CASE(FIT_LABEL, boolean, circle->impl->fit_label); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } + + if (prop_id == PROP_RADIUS_EMS) { + ganv_circle_set_radius_ems(circle, circle->impl->coords.radius_ems); + } +} + +static void +ganv_circle_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_CIRCLE(object)); + + GanvCircle* circle = GANV_CIRCLE(object); + + switch (prop_id) { + GET_CASE(RADIUS, double, circle->impl->coords.radius); + GET_CASE(RADIUS_EMS, double, circle->impl->coords.radius_ems); + GET_CASE(FIT_LABEL, boolean, circle->impl->fit_label); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_circle_resize(GanvNode* self) +{ + GanvNode* node = GANV_NODE(self); + GanvCircle* circle = GANV_CIRCLE(self); + GanvCanvas* canvas = GANV_CANVAS(GANV_ITEM(node)->impl->canvas); + + if (node->impl->label) { + if (node->impl->label->impl->needs_layout) { + ganv_text_layout(node->impl->label); + } + + const double label_w = node->impl->label->impl->coords.width; + const double label_h = node->impl->label->impl->coords.height; + if (circle->impl->fit_label) { + // Resize to fit text + const double radius = MAX(label_w, label_h) / 2.0 + 3.0; + if (radius != circle->impl->coords.radius) { + ganv_item_set(GANV_ITEM(self), + "radius", radius, + NULL); + } + } + + // Center label + ganv_item_set(GANV_ITEM(node->impl->label), + "x", label_w / -2.0, + "y", label_h / -2.0, + NULL); + } + + if (parent_class->resize) { + parent_class->resize(self); + } + + ganv_canvas_for_each_edge_on( + canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL); +} + +static void +ganv_circle_redraw_text(GanvNode* self) +{ + GanvCircle* circle = GANV_CIRCLE(self); + if (circle->impl->coords.radius_ems) { + ganv_circle_set_radius_ems(circle, circle->impl->coords.radius_ems); + } + + if (parent_class->redraw_text) { + parent_class->redraw_text(self); + } +} + +static gboolean +ganv_circle_is_within(const GanvNode* self, + double x1, + double y1, + double x2, + double y2) +{ + const double x = GANV_ITEM(self)->impl->x; + const double y = GANV_ITEM(self)->impl->y; + + return x >= x1 + && x <= x2 + && y >= y1 + && y <= y2; +} + +static void +ganv_circle_vector(const GanvNode* self, + const GanvNode* other, + double* x, + double* y, + double* dx, + double* dy) +{ + GanvCircle* circle = GANV_CIRCLE(self); + + const double cx = GANV_ITEM(self)->impl->x; + const double cy = GANV_ITEM(self)->impl->y; + const double other_x = GANV_ITEM(other)->impl->x; + const double other_y = GANV_ITEM(other)->impl->y; + + const double border = circle->node.impl->border_width; + const double xdist = other_x - cx; + const double ydist = other_y - cy; + const double h = sqrt((xdist * xdist) + (ydist * ydist)); + const double theta = asin(xdist / (h + DBL_EPSILON)); + const double y_mod = (cy < other_y) ? 1 : -1; + const double ret_h = h - circle->impl->coords.radius - border / 2.0; + const double ret_x = other_x - sin(theta) * ret_h; + const double ret_y = other_y - cos(theta) * ret_h * y_mod; + + *x = ret_x; + *y = ret_y; + *dx = 0.0; + *dy = 0.0; + + ganv_item_i2w(GANV_ITEM(circle)->impl->parent, x, y); +} + +static void +request_redraw(GanvItem* item, + const GanvCircleCoords* coords, + gboolean world) +{ + const double w = coords->width; + + double x1 = coords->x - coords->radius - w; + double y1 = coords->y - coords->radius - w; + double x2 = coords->x + coords->radius + w; + double y2 = coords->y + coords->radius + w; + + if (!world) { + // Convert from parent-relative coordinates to world coordinates + ganv_item_i2w_pair(item, &x1, &y1, &x2, &y2); + } + + ganv_canvas_request_redraw_w(item->impl->canvas, x1, y1, x2, y2); +} + +static void +coords_i2w(GanvItem* item, GanvCircleCoords* coords) +{ + ganv_item_i2w(item, &coords->x, &coords->y); +} + +static void +ganv_circle_bounds_item(GanvItem* item, + double* x1, double* y1, + double* x2, double* y2) +{ + const GanvCircle* circle = GANV_CIRCLE(item); + const GanvCircleCoords* coords = &circle->impl->coords; + *x1 = coords->x - coords->radius - coords->width; + *y1 = coords->y - coords->radius - coords->width; + *x2 = coords->x + coords->radius + coords->width; + *y2 = coords->y + coords->radius + coords->width; +} + +static void +ganv_circle_bounds(GanvItem* item, + double* x1, double* y1, + double* x2, double* y2) +{ + ganv_circle_bounds_item(item, x1, y1, x2, y2); +} + +static void +ganv_circle_update(GanvItem* item, int flags) +{ + GanvCircle* circle = GANV_CIRCLE(item); + GanvCircleImpl* impl = circle->impl; + impl->coords.width = circle->node.impl->border_width; + + // Request redraw of old location + request_redraw(item, &impl->old_coords, TRUE); + + // Store old coordinates in world relative coordinates in case the + // group we are in moves between now and the next update + impl->old_coords = impl->coords; + coords_i2w(item, &impl->old_coords); + + // Update world-relative bounding box + ganv_circle_bounds(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2); + ganv_item_i2w_pair(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2); + + // Request redraw of new location + request_redraw(item, &impl->coords, FALSE); + + GANV_ITEM_CLASS(parent_class)->update(item, flags); +} + +static void +ganv_circle_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + GanvNode* node = GANV_NODE(item); + GanvCircle* circle = GANV_CIRCLE(item); + GanvCircleImpl* impl = circle->impl; + + double r, g, b, a; + + double x = impl->coords.x; + double y = impl->coords.y; + ganv_item_i2w(item, &x, &y); + + double dash_length, border_color, fill_color; + ganv_node_get_draw_properties( + &circle->node, &dash_length, &border_color, &fill_color); + + // Fill + cairo_new_path(cr); + cairo_arc(cr, + x, + y, + impl->coords.radius + (impl->coords.width / 2.0), + 0, 2 * G_PI); + color_to_rgba(fill_color, &r, &g, &b, &a); + cairo_set_source_rgba(cr, r, g, b, a); + cairo_fill(cr); + + // Border + cairo_arc(cr, + x, + y, + impl->coords.radius, + 0, 2 * G_PI); + color_to_rgba(border_color, &r, &g, &b, &a); + cairo_set_source_rgba(cr, r, g, b, a); + cairo_set_line_width(cr, impl->coords.width); + if (dash_length > 0) { + cairo_set_dash(cr, &dash_length, 1, circle->node.impl->dash_offset); + } else { + cairo_set_dash(cr, &dash_length, 0, 0); + } + cairo_stroke(cr); + + // Draw label + if (node->impl->label) { + GanvItem* label_item = GANV_ITEM(node->impl->label); + + if (label_item->object.flags & GANV_ITEM_VISIBLE) { + GANV_ITEM_GET_CLASS(label_item)->draw( + label_item, cr, cx, cy, cw, ch); + } + } +} + +static double +ganv_circle_point(GanvItem* item, double x, double y, GanvItem** actual_item) +{ + const GanvCircle* circle = GANV_CIRCLE(item); + const GanvCircleCoords* coords = &circle->impl->coords; + + *actual_item = item; + + const double dx = fabs(x - coords->x); + const double dy = fabs(y - coords->y); + const double d = sqrt((dx * dx) + (dy * dy)); + + if (d <= coords->radius + coords->width) { + // Point is inside the circle + return 0.0; + } else { + // Distance from the edge of the circle + return d - (coords->radius + coords->width); + } +} + +static void +ganv_circle_class_init(GanvCircleClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GanvItemClass* item_class = (GanvItemClass*)klass; + GanvNodeClass* node_class = (GanvNodeClass*)klass; + + parent_class = GANV_NODE_CLASS(g_type_class_peek_parent(klass)); + + g_type_class_add_private(klass, sizeof(GanvCircleImpl)); + + gobject_class->set_property = ganv_circle_set_property; + gobject_class->get_property = ganv_circle_get_property; + + g_object_class_install_property( + gobject_class, PROP_RADIUS, g_param_spec_double( + "radius", + _("Radius"), + _("The radius of the circle."), + 0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_RADIUS_EMS, g_param_spec_double( + "radius-ems", + _("Radius in ems"), + _("The radius of the circle in ems."), + 0, G_MAXDOUBLE, + 1.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_FIT_LABEL, g_param_spec_boolean( + "fit-label", + _("Fit label"), + _("If true, expand circle to fit its label"), + TRUE, + (GParamFlags)G_PARAM_READWRITE)); + + object_class->destroy = ganv_circle_destroy; + + node_class->resize = ganv_circle_resize; + node_class->is_within = ganv_circle_is_within; + node_class->tail_vector = ganv_circle_vector; + node_class->head_vector = ganv_circle_vector; + node_class->redraw_text = ganv_circle_redraw_text; + + item_class->update = ganv_circle_update; + item_class->bounds = ganv_circle_bounds; + item_class->point = ganv_circle_point; + item_class->draw = ganv_circle_draw; +} + +GanvCircle* +ganv_circle_new(GanvCanvas* canvas, + const char* first_property_name, ...) +{ + GanvCircle* circle = GANV_CIRCLE( + g_object_new(ganv_circle_get_type(), "canvas", canvas, NULL)); + + va_list args; + va_start(args, first_property_name); + g_object_set_valist(G_OBJECT(circle), first_property_name, args); + va_end(args); + + return circle; +} + +double +ganv_circle_get_radius(const GanvCircle* circle) +{ + return circle->impl->coords.radius; +} + +void +ganv_circle_set_radius(GanvCircle* circle, double radius) +{ + circle->impl->coords.radius = radius; + ganv_item_request_update(GANV_ITEM(circle)); +} + +double +ganv_circle_get_radius_ems(const GanvCircle* circle) +{ + return circle->impl->coords.radius_ems; +} + +void +ganv_circle_set_radius_ems(GanvCircle* circle, double ems) +{ + GanvCanvas* canvas = GANV_CANVAS(GANV_ITEM(circle)->impl->canvas); + const double points = ganv_canvas_get_font_size(canvas); + circle->impl->coords.radius_ems = ems; + circle->impl->coords.radius = points * ems; + ganv_item_request_update(GANV_ITEM(circle)); +} + +gboolean +ganv_circle_get_fit_label(const GanvCircle* circle) +{ + return circle->impl->fit_label; +} + +void +ganv_circle_set_fit_label(GanvCircle* circle, gboolean fit_label) +{ + circle->impl->fit_label = fit_label; + ganv_item_request_update(GANV_ITEM(circle)); +} diff --git a/src/color.h b/src/color.h new file mode 100644 index 0000000..ca52d98 --- /dev/null +++ b/src/color.h @@ -0,0 +1,63 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_UTIL_H +#define GANV_UTIL_H + +#include <glib.h> + +#ifdef GANV_USE_LIGHT_THEME +# define DEFAULT_BACKGROUND_COLOR 0xFFFFFFFF +# define DEFAULT_TEXT_COLOR 0x000000FF +# define DIM_TEXT_COLOR 0x333333BB +# define DEFAULT_FILL_COLOR 0xEEEEEEFF +# define DEFAULT_BORDER_COLOR 0x000000FF +# define PORT_BORDER_COLOR(fill) 0x000000FF +# define EDGE_COLOR(base) highlight_color(tail_color, -48) +#else +# define DEFAULT_BACKGROUND_COLOR 0x000000FF +# define DEFAULT_TEXT_COLOR 0xFFFFFFFF +# define DIM_TEXT_COLOR 0xCCCCCCBB +# define DEFAULT_FILL_COLOR 0x1E2224FF +# define DEFAULT_BORDER_COLOR 0x3E4244FF +# define PORT_BORDER_COLOR(fill) highlight_color(fill, 0x20) +# define EDGE_COLOR(base) highlight_color(tail_color, 48) +#endif + +static inline void +color_to_rgba(guint color, double* r, double* g, double* b, double* a) +{ + *r = ((color >> 24) & 0xFF) / 255.0; + *g = ((color >> 16) & 0xFF) / 255.0; + *b = ((color >> 8) & 0xFF) / 255.0; + *a = ((color) & 0xFF) / 255.0; +} + +static inline guint +highlight_color(guint c, guint delta) +{ + const guint max_char = 255; + const guint r = MIN((c >> 24) + delta, max_char); + const guint g = MIN(((c >> 16) & 0xFF) + delta, max_char); + const guint b = MIN(((c >> 8) & 0xFF) + delta, max_char); + const guint a = c & 0xFF; + + return ((((guint)(r)) << 24) | + (((guint)(g)) << 16) | + (((guint)(b)) << 8) | + (((guint)(a)))); +} + +#endif // GANV_UTIL_H diff --git a/src/edge.c b/src/edge.c new file mode 100644 index 0000000..c609e01 --- /dev/null +++ b/src/edge.c @@ -0,0 +1,788 @@ +/* This file is part of Ganv. + * Copyright 2007-2016 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <string.h> + +#include <cairo.h> + +#include "ganv/canvas.h" +#include "ganv/edge.h" +#include "ganv/node.h" + +#include "./boilerplate.h" +#include "./color.h" +#include "./gettext.h" + +#include "color.h" +#include "ganv-private.h" + +#define ARROW_DEPTH 32 +#define ARROW_BREADTH 32 + +// Uncomment to see control point path as straight lines +//#define GANV_DEBUG_CURVES 1 + +// Uncomment along with GANV_DEBUG_CURVES to see bounding box (buggy) +//#define GANV_DEBUG_BOUNDS 1 + +enum { + PROP_0, + PROP_TAIL, + PROP_HEAD, + PROP_WIDTH, + PROP_HANDLE_RADIUS, + PROP_DASH_LENGTH, + PROP_DASH_OFFSET, + PROP_COLOR, + PROP_CONSTRAINING, + PROP_CURVED, + PROP_ARROWHEAD, + PROP_SELECTED, + PROP_HIGHLIGHTED, + PROP_GHOST +}; + +G_DEFINE_TYPE(GanvEdge, ganv_edge, GANV_TYPE_ITEM) + +static GanvItemClass* parent_class; + +static void +ganv_edge_init(GanvEdge* edge) +{ + GanvEdgeImpl* impl = G_TYPE_INSTANCE_GET_PRIVATE( + edge, GANV_TYPE_EDGE, GanvEdgeImpl); + + edge->impl = impl; + + impl->tail = NULL; + impl->head = NULL; + + memset(&impl->coords, '\0', sizeof(GanvEdgeCoords)); + impl->coords.width = 2.0; + impl->coords.handle_radius = 4.0; + impl->coords.constraining = TRUE; + impl->coords.curved = FALSE; + impl->coords.arrowhead = FALSE; + + impl->old_coords = impl->coords; + impl->dash_length = 0.0; + impl->dash_offset = 0.0; + impl->color = 0; +} + +static void +ganv_edge_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_EDGE(object)); + + GanvEdge* edge = GANV_EDGE(object); + GanvCanvas* canvas = GANV_CANVAS(edge->item.impl->canvas); + if (canvas && !edge->impl->ghost) { + edge->item.impl->canvas = NULL; + } + edge->item.impl->parent = NULL; + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +static void +ganv_edge_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_EDGE(object)); + + GanvItem* item = GANV_ITEM(object); + GanvEdge* edge = GANV_EDGE(object); + GanvEdgeImpl* impl = edge->impl; + GanvEdgeCoords* coords = &impl->coords; + + switch (prop_id) { + SET_CASE(WIDTH, double, coords->width); + SET_CASE(HANDLE_RADIUS, double, coords->handle_radius); + SET_CASE(DASH_LENGTH, double, impl->dash_length); + SET_CASE(DASH_OFFSET, double, impl->dash_offset); + SET_CASE(COLOR, uint, impl->color); + SET_CASE(CONSTRAINING, boolean, impl->coords.constraining); + SET_CASE(CURVED, boolean, impl->coords.curved); + SET_CASE(ARROWHEAD, boolean, impl->coords.arrowhead); + SET_CASE(SELECTED, boolean, impl->selected); + SET_CASE(HIGHLIGHTED, boolean, impl->highlighted); + SET_CASE(GHOST, boolean, impl->ghost); + case PROP_TAIL: { + const gobject tmp = g_value_get_object(value); + if (impl->tail != tmp) { + impl->tail = GANV_NODE(tmp); + ganv_item_request_update(item); + } + break; + } + case PROP_HEAD: { + const gobject tmp = g_value_get_object(value); + if (impl->head != tmp) { + impl->head = GANV_NODE(tmp); + ganv_item_request_update(item); + } + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_edge_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_EDGE(object)); + + GanvEdge* edge = GANV_EDGE(object); + GanvEdgeImpl* impl = edge->impl; + + switch (prop_id) { + GET_CASE(TAIL, object, impl->tail); + GET_CASE(HEAD, object, impl->head); + GET_CASE(WIDTH, double, impl->coords.width); + SET_CASE(HANDLE_RADIUS, double, impl->coords.handle_radius); + GET_CASE(DASH_LENGTH, double, impl->dash_length); + GET_CASE(DASH_OFFSET, double, impl->dash_offset); + GET_CASE(COLOR, uint, impl->color); + GET_CASE(CONSTRAINING, boolean, impl->coords.constraining); + GET_CASE(CURVED, boolean, impl->coords.curved); + GET_CASE(ARROWHEAD, boolean, impl->coords.arrowhead); + GET_CASE(SELECTED, boolean, impl->selected); + GET_CASE(HIGHLIGHTED, boolean, impl->highlighted); + SET_CASE(GHOST, boolean, impl->ghost); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +void +ganv_edge_request_redraw(GanvItem* item, + const GanvEdgeCoords* coords) +{ + GanvCanvas* canvas = item->impl->canvas; + const double w = coords->width; + if (coords->curved) { + const double src_x = coords->x1; + const double src_y = coords->y1; + const double dst_x = coords->x2; + const double dst_y = coords->y2; + const double join_x = (src_x + dst_x) / 2.0; + const double join_y = (src_y + dst_y) / 2.0; + const double src_x1 = coords->cx1; + const double src_y1 = coords->cy1; + const double dst_x1 = coords->cx2; + const double dst_y1 = coords->cy2; + + const double r1x1 = MIN(MIN(src_x, join_x), src_x1); + const double r1y1 = MIN(MIN(src_y, join_y), src_y1); + const double r1x2 = MAX(MAX(src_x, join_x), src_x1); + const double r1y2 = MAX(MAX(src_y, join_y), src_y1); + ganv_canvas_request_redraw_w(canvas, + r1x1 - w, r1y1 - w, + r1x2 + w, r1y2 + w); + + const double r2x1 = MIN(MIN(dst_x, join_x), dst_x1); + const double r2y1 = MIN(MIN(dst_y, join_y), dst_y1); + const double r2x2 = MAX(MAX(dst_x, join_x), dst_x1); + const double r2y2 = MAX(MAX(dst_y, join_y), dst_y1); + ganv_canvas_request_redraw_w(canvas, + r2x1 - w, r2y1 - w, + r2x2 + w, r2y2 + w); + + } else { + const double x1 = MIN(coords->x1, coords->x2); + const double y1 = MIN(coords->y1, coords->y2); + const double x2 = MAX(coords->x1, coords->x2); + const double y2 = MAX(coords->y1, coords->y2); + + ganv_canvas_request_redraw_w(canvas, + x1 - w, y1 - w, + x2 + w, y2 + w); + } + + if (coords->handle_radius > 0.0) { + ganv_canvas_request_redraw_w( + canvas, + coords->handle_x - coords->handle_radius - w, + coords->handle_y - coords->handle_radius - w, + coords->handle_x + coords->handle_radius + w, + coords->handle_y + coords->handle_radius + w); + } + + if (coords->arrowhead) { + ganv_canvas_request_redraw_w( + canvas, + coords->x2 - ARROW_DEPTH, + coords->y2 - ARROW_BREADTH, + coords->x2 + ARROW_DEPTH, + coords->y2 + ARROW_BREADTH); + } +} + +static void +ganv_edge_bounds(GanvItem* item, + double* x1, double* y1, + double* x2, double* y2) +{ + GanvEdge* edge = GANV_EDGE(item); + GanvEdgeImpl* impl = edge->impl; + GanvEdgeCoords* coords = &impl->coords; + const double w = coords->width; + + if (coords->curved) { + *x1 = MIN(coords->x1, MIN(coords->cx1, MIN(coords->x2, coords->cx2))) - w; + *y1 = MIN(coords->y1, MIN(coords->cy1, MIN(coords->y2, coords->cy2))) - w; + *x2 = MAX(coords->x1, MAX(coords->cx1, MAX(coords->x2, coords->cx2))) + w; + *y2 = MAX(coords->y1, MAX(coords->cy1, MAX(coords->y2, coords->cy2))) + w; + } else { + *x1 = MIN(impl->coords.x1, impl->coords.x2) - w; + *y1 = MIN(impl->coords.y1, impl->coords.y2) - w; + *x2 = MAX(impl->coords.x1, impl->coords.x2) + w; + *y2 = MAX(impl->coords.y1, impl->coords.y2) + w; + } +} + +void +ganv_edge_get_coords(const GanvEdge* edge, GanvEdgeCoords* coords) +{ + GanvEdgeImpl* impl = edge->impl; + + GANV_NODE_GET_CLASS(impl->tail)->tail_vector( + impl->tail, impl->head, + &coords->x1, &coords->y1, &coords->cx1, &coords->cy1); + GANV_NODE_GET_CLASS(impl->head)->head_vector( + impl->head, impl->tail, + &coords->x2, &coords->y2, &coords->cx2, &coords->cy2); + + const double dx = coords->x2 - coords->x1; + const double dy = coords->y2 - coords->y1; + + coords->handle_x = coords->x1 + (dx / 2.0); + coords->handle_y = coords->y1 + (dy / 2.0); + + const double abs_dx = fabs(dx); + const double abs_dy = fabs(dy); + + coords->cx1 = coords->x1 + (coords->cx1 * (abs_dx / 4.0)); + coords->cy1 = coords->y1 + (coords->cy1 * (abs_dy / 4.0)); + coords->cx2 = coords->x2 + (coords->cx2 * (abs_dx / 4.0)); + coords->cy2 = coords->y2 + (coords->cy2 * (abs_dy / 4.0)); +} + +static void +ganv_edge_update(GanvItem* item, int flags) +{ + GanvEdge* edge = GANV_EDGE(item); + GanvEdgeImpl* impl = edge->impl; + + // Request redraw of old location + ganv_edge_request_redraw(item, &impl->old_coords); + + // Calculate new coordinates from tail and head + ganv_edge_get_coords(edge, &impl->coords); + + // Update old coordinates + impl->old_coords = impl->coords; + + // Get bounding box + double x1, x2, y1, y2; + ganv_edge_bounds(item, &x1, &y1, &x2, &y2); + + // Ensure bounding box has non-zero area + if (x1 == x2) { + x2 += 1.0; + } + if (y1 == y2) { + y2 += 1.0; + } + + // Update world-relative bounding box + item->impl->x1 = x1; + item->impl->y1 = y1; + item->impl->x2 = x2; + item->impl->y2 = y2; + ganv_item_i2w_pair(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2); + + // Request redraw of new location + ganv_edge_request_redraw(item, &impl->coords); + + parent_class->update(item, flags); +} + +static void +ganv_edge_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + GanvEdge* edge = GANV_EDGE(item); + GanvEdgeImpl* impl = edge->impl; + + double src_x = impl->coords.x1; + double src_y = impl->coords.y1; + double dst_x = impl->coords.x2; + double dst_y = impl->coords.y2; + double dx = src_x - dst_x; + double dy = src_y - dst_y; + + double r, g, b, a; + if (impl->highlighted) { + color_to_rgba(highlight_color(impl->color, 0x40), &r, &g, &b, &a); + } else { + color_to_rgba(impl->color, &r, &g, &b, &a); + } + cairo_set_source_rgba(cr, r, g, b, a); + + cairo_set_line_width(cr, impl->coords.width); + cairo_move_to(cr, src_x, src_y); + + const double dash_length = (impl->selected ? 4.0 : impl->dash_length); + if (dash_length > 0.0) { + double dashed[2] = { dash_length, dash_length }; + cairo_set_dash(cr, dashed, 2, impl->dash_offset); + } else { + cairo_set_dash(cr, &dash_length, 0, 0); + } + + const double join_x = (src_x + dst_x) / 2.0; + const double join_y = (src_y + dst_y) / 2.0; + + if (impl->coords.curved) { + // Curved line as 2 paths which join at the middle point + + // Path 1 (src_x, src_y) -> (join_x, join_y) + // Control point 1 + const double src_x1 = impl->coords.cx1; + const double src_y1 = impl->coords.cy1; + // Control point 2 + const double src_x2 = (join_x + src_x1) / 2.0; + const double src_y2 = (join_y + src_y1) / 2.0; + + // Path 2, (join_x, join_y) -> (dst_x, dst_y) + // Control point 1 + const double dst_x1 = impl->coords.cx2; + const double dst_y1 = impl->coords.cy2; + // Control point 2 + const double dst_x2 = (join_x + dst_x1) / 2.0; + const double dst_y2 = (join_y + dst_y1) / 2.0; + + cairo_move_to(cr, src_x, src_y); + cairo_curve_to(cr, src_x1, src_y1, src_x2, src_y2, join_x, join_y); + cairo_curve_to(cr, dst_x2, dst_y2, dst_x1, dst_y1, dst_x, dst_y); + +#ifdef GANV_DEBUG_CURVES + cairo_stroke(cr); + cairo_save(cr); + cairo_set_source_rgba(cr, 1.0, 0, 0, 0.5); + + cairo_move_to(cr, src_x, src_y); + cairo_line_to(cr, src_x1, src_y1); + cairo_stroke(cr); + + cairo_move_to(cr, join_x, join_y); + cairo_line_to(cr, src_x2, src_y2); + cairo_stroke(cr); + + cairo_move_to(cr, join_x, join_y); + cairo_line_to(cr, dst_x2, dst_y2); + cairo_stroke(cr); + + cairo_move_to(cr, dst_x, dst_y); + cairo_line_to(cr, dst_x1, dst_y1); + cairo_stroke(cr); + +#ifdef GANV_DEBUG_BOUNDS + double bounds_x1, bounds_y1, bounds_x2, bounds_y2; + ganv_edge_bounds(item, &bounds_x1, &bounds_y1, &bounds_x2, &bounds_y2); + cairo_rectangle(cr, + bounds_x1, bounds_y1, + bounds_x2 - bounds_x1, bounds_y2 - bounds_y1); +#endif + + cairo_restore(cr); +#endif + + cairo_stroke(cr); + if (impl->coords.arrowhead) { + cairo_move_to(cr, dst_x - 12, dst_y - 4); + cairo_line_to(cr, dst_x, dst_y); + cairo_line_to(cr, dst_x - 12, dst_y + 4); + cairo_close_path(cr); + cairo_stroke_preserve(cr); + cairo_fill(cr); + } + + } else { + // Straight line from (x1, y1) to (x2, y2) + cairo_move_to(cr, src_x, src_y); + cairo_line_to(cr, dst_x, dst_y); + cairo_stroke(cr); + + if (impl->coords.arrowhead) { + const double ah = sqrt(dx * dx + dy * dy); + const double adx = dx / ah * 8.0; + const double ady = dy / ah * 8.0; + + cairo_move_to(cr, + dst_x + adx - ady/1.5, + dst_y + ady + adx/1.5); + cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); + cairo_line_to(cr, dst_x, dst_y); + cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER); + cairo_line_to(cr, + dst_x + adx + ady/1.5, + dst_y + ady - adx/1.5); + cairo_close_path(cr); + cairo_stroke_preserve(cr); + cairo_fill(cr); + } + } + + if (!ganv_canvas_exporting(item->impl->canvas) && + impl->coords.handle_radius > 0.0) { + cairo_move_to(cr, join_x, join_y); + cairo_arc(cr, join_x, join_y, impl->coords.handle_radius, 0, 2 * G_PI); + cairo_fill(cr); + } +} + +static double +ganv_edge_point(GanvItem* item, double x, double y, GanvItem** actual_item) +{ + const GanvEdge* edge = GANV_EDGE(item); + const GanvEdgeCoords* coords = &edge->impl->coords; + + const double dx = fabs(x - coords->handle_x); + const double dy = fabs(y - coords->handle_y); + const double d = sqrt((dx * dx) + (dy * dy)); + + *actual_item = item; + + if (d <= coords->handle_radius) { + // Point is inside the handle + return 0.0; + } else { + // Distance from the edge of the handle + return d - (coords->handle_radius + coords->width); + } +} + +gboolean +ganv_edge_is_within(const GanvEdge* edge, + double x1, + double y1, + double x2, + double y2) +{ + const double handle_x = edge->impl->coords.handle_x; + const double handle_y = edge->impl->coords.handle_y; + + return handle_x >= x1 + && handle_x <= x2 + && handle_y >= y1 + && handle_y <= y2; +} + +static void +ganv_edge_class_init(GanvEdgeClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GanvItemClass* item_class = (GanvItemClass*)klass; + + parent_class = GANV_ITEM_CLASS(g_type_class_peek_parent(klass)); + + g_type_class_add_private(klass, sizeof(GanvEdgeImpl)); + + gobject_class->set_property = ganv_edge_set_property; + gobject_class->get_property = ganv_edge_get_property; + + g_object_class_install_property( + gobject_class, PROP_TAIL, g_param_spec_object( + "tail", + _("Tail"), + _("Node this edge starts from."), + GANV_TYPE_NODE, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_HEAD, g_param_spec_object( + "head", + _("Head"), + _("Node this edge ends at."), + GANV_TYPE_NODE, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_WIDTH, g_param_spec_double( + "width", + _("Line width"), + _("Width of edge line."), + 0.0, G_MAXDOUBLE, + 2.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_HANDLE_RADIUS, g_param_spec_double( + "handle-radius", + _("Gandle radius"), + _("Radius of handle in canvas units."), + 0.0, G_MAXDOUBLE, + 4.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_DASH_LENGTH, g_param_spec_double( + "dash-length", + _("Line dash length"), + _("Length of line dashes, or zero for no dashing."), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_DASH_OFFSET, g_param_spec_double( + "dash-offset", + _("Line dash offset"), + _("Start offset for line dashes, used for selected animation."), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_COLOR, g_param_spec_uint( + "color", + _("Color"), + _("Line color as an RGBA integer."), + 0, G_MAXUINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_CONSTRAINING, g_param_spec_boolean( + "constraining", + _("Constraining"), + _("Whether edge should constrain the layout."), + 1, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_CURVED, g_param_spec_boolean( + "curved", + _("Curved"), + _("Whether line should be curved rather than straight."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_ARROWHEAD, g_param_spec_boolean( + "arrowhead", + _("Arrowhead"), + _("Whether to show an arrowhead at the head of this edge."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_SELECTED, g_param_spec_boolean( + "selected", + _("Selected"), + _("Whether this edge is selected."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_HIGHLIGHTED, g_param_spec_boolean( + "highlighted", + _("Highlighted"), + _("Whether to highlight the edge."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_GHOST, g_param_spec_boolean( + "ghost", + _("Ghost"), + _("Whether this edge is a `ghost', which is an edge that is not " + "added to the canvas data structures. Ghost edges are used for " + "temporary edges that are not considered `real', e.g. the edge " + "made while dragging to make a connection."), + 0, + G_PARAM_READWRITE)); + + object_class->destroy = ganv_edge_destroy; + + item_class->update = ganv_edge_update; + item_class->bounds = ganv_edge_bounds; + item_class->point = ganv_edge_point; + item_class->draw = ganv_edge_draw; +} + +GanvEdge* +ganv_edge_new(GanvCanvas* canvas, + GanvNode* tail, + GanvNode* head, + const char* first_prop_name, ...) +{ + GanvEdge* edge = GANV_EDGE( + g_object_new(ganv_edge_get_type(), NULL)); + + va_list args; + va_start(args, first_prop_name); + ganv_item_construct(&edge->item, + GANV_ITEM(ganv_canvas_root(canvas)), + first_prop_name, args); + va_end(args); + + edge->impl->tail = tail; + edge->impl->head = head; + + if (!edge->impl->color) { + const guint tail_color = GANV_NODE(tail)->impl->fill_color; + g_object_set(G_OBJECT(edge), + "color", EDGE_COLOR(tail_color), + NULL); + } + + if (!edge->impl->ghost) { + ganv_canvas_add_edge(canvas, edge); + } + return edge; +} + +void +ganv_edge_update_location(GanvEdge* edge) +{ + ganv_item_request_update(GANV_ITEM(edge)); +} + +gboolean +ganv_edge_get_constraining(const GanvEdge* edge) +{ + return edge->impl->coords.constraining; +} + +void +ganv_edge_set_constraining(GanvEdge* edge, gboolean constraining) +{ + edge->impl->coords.constraining = constraining; + ganv_edge_request_redraw(GANV_ITEM(edge), &edge->impl->coords); +} + +gboolean +ganv_edge_get_curved(const GanvEdge* edge) +{ + return edge->impl->coords.curved; +} + +void +ganv_edge_set_curved(GanvEdge* edge, gboolean curved) +{ + edge->impl->coords.curved = curved; + ganv_edge_request_redraw(GANV_ITEM(edge), &edge->impl->coords); +} + +void +ganv_edge_set_selected(GanvEdge* edge, gboolean selected) +{ + GanvCanvas* canvas = GANV_CANVAS(edge->item.impl->canvas); + if (selected) { + ganv_canvas_select_edge(canvas, edge); + } else { + ganv_canvas_unselect_edge(canvas, edge); + } +} + +void +ganv_edge_select(GanvEdge* edge) +{ + ganv_edge_set_selected(edge, TRUE); +} + +void +ganv_edge_unselect(GanvEdge* edge) +{ + ganv_edge_set_selected(edge, FALSE); +} + +void +ganv_edge_highlight(GanvEdge* edge) +{ + ganv_edge_set_highlighted(edge, TRUE); +} + +void +ganv_edge_unhighlight(GanvEdge* edge) +{ + ganv_edge_set_highlighted(edge, FALSE); +} + +void +ganv_edge_set_highlighted(GanvEdge* edge, gboolean highlighted) +{ + edge->impl->highlighted = highlighted; + ganv_edge_request_redraw(GANV_ITEM(edge), &edge->impl->coords); +} + +void +ganv_edge_tick(GanvEdge* edge, double seconds) +{ + ganv_item_set(GANV_ITEM(edge), + "dash-offset", seconds * 8.0, + NULL); +} + +void +ganv_edge_disconnect(GanvEdge* edge) +{ + if (!edge->impl->ghost) { + ganv_canvas_disconnect_edge( + GANV_CANVAS(edge->item.impl->canvas), + edge); + } +} + +void +ganv_edge_remove(GanvEdge* edge) +{ + if (!edge->impl->ghost) { + ganv_canvas_remove_edge( + GANV_CANVAS(edge->item.impl->canvas), + edge); + } +} + +GanvNode* +ganv_edge_get_tail(const GanvEdge* edge) +{ + return edge->impl->tail; +} + +GanvNode* +ganv_edge_get_head(const GanvEdge* edge) +{ + return edge->impl->head; +} diff --git a/src/fdgl.hpp b/src/fdgl.hpp new file mode 100644 index 0000000..38fded1 --- /dev/null +++ b/src/fdgl.hpp @@ -0,0 +1,166 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <float.h> +#include <math.h> + +static const double CHARGE_KE = 4000000.0; +static const double EDGE_K = 16.0; +static const double EDGE_LEN = 0.1; + +struct Region { + Vector pos; + Vector area; +}; + +inline Vector +vec_add(const Vector& a, const Vector& b) +{ + const Vector result = { a.x + b.x, a.y + b.y }; + return result; +} + +inline Vector +vec_sub(const Vector& a, const Vector& b) +{ + const Vector result = { a.x - b.x, a.y - b.y }; + return result; +} + +inline Vector +vec_mult(const Vector& a, double m) +{ + const Vector result = { a.x * m, a.y * m }; + return result; +} + +inline double +vec_mult(const Vector& a, const Vector& b) +{ + return a.x * b.x + a.y * b.y; +} + +/** Magnitude. */ +inline double +vec_mag(const Vector& vec) +{ + return sqrt(vec.x * vec.x + vec.y * vec.y); +} + +/** Reciprocal of magnitude. */ +inline double +vec_rmag(const Vector& vec) +{ + return 1.0 / sqrt(vec.x * vec.x + vec.y * vec.y); +} + +/** Hooke's law */ +inline Vector +spring_force(const Vector& a, const Vector& b, double length, double k) +{ + const Vector vec = vec_sub(b, a); + const double mag = vec_mag(vec); + const double displacement = length - mag; + return vec_mult(vec, k * displacement * 0.5 / mag); +} + +/** Spring force with a directional force to align with flow direction. */ +static const Vector +edge_force(const Vector& dir, const Vector& hpos, const Vector& tpos) +{ + return vec_add(dir, spring_force(hpos, tpos, EDGE_LEN, EDGE_K)); +} + +/** Constant tide force, does not vary with distance. */ +inline Vector +tide_force(const Vector& a, const Vector& b, double power) +{ + static const double G = 0.0000000000667; + const Vector vec = vec_sub(a, b); + const double mag = vec_mag(vec); + return vec_mult(vec, G * power / mag); +} + +inline double +rect_distance(Vector* vec, + const double ax1, const double ay1, + const double ax2, const double ay2, + const double bx1, const double by1, + const double bx2, const double by2) +{ + vec->x = 0.0; + vec->y = 0.0; + + if (ax2 <= bx1) { // A is completely to the left of B + vec->x = ax2 - bx1; + if (ay2 <= by1) { // Top Left + const double dx = bx1 - ax2; + const double dy = by1 - ay2; + vec->y = ay2 - by1; + return sqrt(dx * dx + dy * dy); + } else if (ay1 >= by2) { // Bottom left + const double dx = bx1 - ax2; + const double dy = ay1 - by2; + vec->y = ay1 - by2; + return sqrt(dx * dx + dy * dy); + } else { // Left + return bx1 - ax2; + } + } else if (ax1 >= bx2) { // A is completely to the right of B + vec->x = ax1 - bx2; + if (ay2 <= by1) { // Top right + const double dx = ax1 - bx2; + const double dy = by1 - ay2; + vec->y = ay2 - by1; + return sqrt(dx * dx + dy * dy); + } else if (ay1 >= by2) { // Bottom right + const double dx = ax1 - bx2; + const double dy = ay1 - by2; + vec->y = ay1 - by2; + return sqrt(dx * dx + dy * dy); + } else { // Right + return ax1 - bx2; + } + } else if (ay2 <= by1) { // Top + vec->y = ay2 - by1; + return by1 - ay2; + } else if (ay1 >= by2) { // Bottom + vec->y = ay1 - by2; + return ay1 - by2; + } else { // Overlap + return 0.0; + } +} + +/** Repelling charge force, ala Coulomb's law. */ +inline Vector +repel_force(const Region& a, const Region& b) +{ + static const double MIN_DIST = 1.0; + + Vector vec; + double dist = rect_distance( + &vec, + a.pos.x - (a.area.x / 2.0), a.pos.y - (a.area.y / 2.0), + a.pos.x + (a.area.x / 2.0), a.pos.y + (a.area.y / 2.0), + b.pos.x - (b.area.x / 2.0), b.pos.y - (b.area.y / 2.0), + b.pos.x + (b.area.x / 2.0), b.pos.y + (b.area.y / 2.0)); + + if (dist <= MIN_DIST) { + dist = MIN_DIST; + vec = vec_sub(a.pos, b.pos); + } + return vec_mult(vec, (CHARGE_KE * 0.5 / (vec_mag(vec) * dist * dist))); +} diff --git a/src/ganv-marshal.list b/src/ganv-marshal.list new file mode 100644 index 0000000..64e6c6b --- /dev/null +++ b/src/ganv-marshal.list @@ -0,0 +1,4 @@ +BOOLEAN:BOXED +VOID:DOUBLE,DOUBLE +VOID:OBJECT,INT,INT,INT,INT +VOID:OBJECT,OBJECT diff --git a/src/ganv-private.h b/src/ganv-private.h new file mode 100644 index 0000000..39824ba --- /dev/null +++ b/src/ganv-private.h @@ -0,0 +1,403 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_PRIVATE_H +#define GANV_PRIVATE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <cairo.h> + +#include "ganv/canvas.h" +#include "ganv/text.h" +#include "ganv/types.h" + +#define GANV_CLOSE_ENOUGH 1 + +extern guint signal_moved; + +/* Box */ + +typedef struct { + double x1, y1, x2, y2; + double border_width; + gboolean stacked; +} GanvBoxCoords; + +struct _GanvBoxImpl { + GanvBoxCoords coords; + GanvBoxCoords old_coords; + double radius_tl; + double radius_tr; + double radius_br; + double radius_bl; + gboolean beveled; +}; + +/* Circle */ + +typedef struct { + double x, y, radius, radius_ems; + double width; +} GanvCircleCoords; + +struct _GanvCircleImpl { + GanvCircleCoords coords; + GanvCircleCoords old_coords; + gboolean fit_label; +}; + +/* Edge */ + +typedef struct { + double x1, y1, x2, y2; + double cx1, cy1, cx2, cy2; + double handle_x, handle_y, handle_radius; + double width; + gboolean constraining; + gboolean curved; + gboolean arrowhead; +} GanvEdgeCoords; + +struct _GanvEdgeImpl +{ + GanvNode* tail; + GanvNode* head; + GanvEdgeCoords coords; + GanvEdgeCoords old_coords; + double dash_length; + double dash_offset; + guint color; + gboolean selected; + gboolean highlighted; + gboolean ghost; +}; + +/* Module */ + +struct _GanvModuleImpl +{ + GPtrArray* ports; + GanvItem* embed_item; + int embed_width; + int embed_height; + double widest_input; + double widest_output; + gboolean must_reorder; +}; + +/* Node */ + +#ifdef GANV_FDGL +typedef struct { + double x; + double y; +} Vector; +#endif + +struct _GanvNodeImpl { + struct _GanvNode* partner; + GanvText* label; + double dash_length; + double dash_offset; + double border_width; + guint fill_color; + guint border_color; + gboolean can_tail; + gboolean can_head; + gboolean is_source; + gboolean selected; + gboolean highlighted; + gboolean draggable; + gboolean show_label; + gboolean grabbed; + gboolean must_resize; +#ifdef GANV_FDGL + Vector force; + Vector vel; + gboolean connected; +#endif +}; + +/* Widget */ + +struct _GanvWidgetImpl { + GtkWidget* widget; /* The child widget */ + + double x, y; /* Position at anchor */ + double width, height; /* Dimensions of widget */ + GtkAnchorType anchor; /* Anchor side for widget */ + + int cx, cy; /* Top-left canvas coordinates for widget */ + int cwidth, cheight; /* Size of widget in pixels */ + + guint destroy_id; /* Signal connection id for destruction of child widget */ + + guint size_pixels : 1; /* Is size specified in (unchanging) pixels or units (get scaled)? */ + guint in_destroy : 1; /* Is child widget being destroyed? */ +}; + +/* Group */ +struct _GanvGroupImpl { + GList* item_list; + GList* item_list_end; +}; + +/* Item */ +struct _GanvItemImpl { + /* Parent canvas for this item */ + struct _GanvCanvas* canvas; + + /* Parent for this item */ + GanvItem* parent; + + /* Wrapper object for this item, if any */ + void* wrapper; + + /* Layer (z order), higher values are on top */ + guint layer; + + /* Position in parent-relative coordinates. */ + double x, y; + + /* Bounding box for this item (in world coordinates) */ + double x1, y1, x2, y2; + + /* True if parent manages this item (don't call add/remove) */ + gboolean managed; +}; + +void +ganv_node_tick(GanvNode* self, double seconds); + +void +ganv_node_tail_vector(const GanvNode* self, + const GanvNode* head, + double* x1, + double* y1, + double* x2, + double* y2); + +void +ganv_node_head_vector(const GanvNode* self, + const GanvNode* tail, + double* x1, + double* y1, + double* x2, + double* y2); + +/** + * ganv_node_get_draw_properties: + * + * Get the colours that should currently be used for drawing this node. Note + * these may not be identical to the property values because of highlighting + * and selection. + */ +void +ganv_node_get_draw_properties(const GanvNode* node, + double* dash_length, + double* border_color, + double* fill_color); + +/* Port */ + +typedef struct { + GanvBox* rect; + float value; + float min; + float max; + gboolean is_toggle; + gboolean is_integer; +} GanvPortControl; + +struct _GanvPortImpl { + GanvPortControl* control; + GanvText* value_label; + gboolean is_input; + gboolean is_controllable; +}; + +/* Text */ + +typedef struct +{ + double x; + double y; + double width; + double height; +} GanvTextCoords; + +struct _GanvTextImpl +{ + PangoLayout* layout; + char* text; + GanvTextCoords coords; + GanvTextCoords old_coords; + double font_size; + guint color; + gboolean needs_layout; +}; + +/* Canvas */ + +typedef struct { + GanvPortOrderFunc port_cmp; + void* data; +} PortOrderCtx; + +void +ganv_canvas_move_selected_items(GanvCanvas* canvas, + double dx, + double dy); + +void +ganv_canvas_selection_move_finished(GanvCanvas* canvas); + +void +ganv_canvas_add_node(GanvCanvas* canvas, + GanvNode* node); + +void +ganv_canvas_remove_node(GanvCanvas* canvas, + GanvNode* node); + +void +ganv_canvas_select_node(GanvCanvas* canvas, + GanvNode* node); + +void +ganv_canvas_unselect_node(GanvCanvas* canvas, + GanvNode* node); + +void +ganv_canvas_add_edge(GanvCanvas* canvas, + GanvEdge* edge); + +void +ganv_canvas_select_edge(GanvCanvas* canvas, + GanvEdge* edge); + +void +ganv_canvas_unselect_edge(GanvCanvas* canvas, + GanvEdge* edge); + +void +ganv_canvas_disconnect_edge(GanvCanvas* canvas, + GanvEdge* edge); + +gboolean +ganv_canvas_port_event(GanvCanvas* canvas, + GanvPort* port, + GdkEvent* event); + +void +ganv_canvas_contents_changed(GanvCanvas* canvas); + +void +ganv_item_i2w_offset(GanvItem* item, double* px, double* py); + +void +ganv_item_i2w_pair(GanvItem* item, double* x1, double* y1, double* x2, double* y2); + +void +ganv_item_invoke_update(GanvItem* item, int flags); + +void +ganv_item_emit_event(GanvItem* item, GdkEvent* event, gint* finished); + +void +ganv_canvas_request_update(GanvCanvas* canvas); + +int +ganv_canvas_emit_event(GanvCanvas* canvas, GdkEvent* event); + +void +ganv_canvas_set_need_repick(GanvCanvas* canvas); + +void +ganv_canvas_forget_item(GanvCanvas* canvas, GanvItem* item); + +void +ganv_canvas_grab_focus(GanvCanvas* canvas, GanvItem* item); + +void +ganv_canvas_get_zoom_offsets(GanvCanvas* canvas, int* x, int* y); + +int +ganv_canvas_grab_item(GanvItem* item, guint event_mask, GdkCursor* cursor, guint32 etime); + +void +ganv_canvas_ungrab_item(GanvItem* item, guint32 etime); + +/* Request a redraw of the specified rectangle in canvas coordinates */ +void +ganv_canvas_request_redraw_c(GanvCanvas* canvas, + int x1, int y1, int x2, int y2); + +/* Request a redraw of the specified rectangle in world coordinates */ +void +ganv_canvas_request_redraw_w(GanvCanvas* canvas, + double x1, double y1, double x2, double y2); + +PortOrderCtx +ganv_canvas_get_port_order(GanvCanvas* canvas); + +gboolean +ganv_canvas_exporting(GanvCanvas* canvas); + +/* Edge */ + +void +ganv_edge_update_location(GanvEdge* edge); + +void +ganv_edge_get_coords(const GanvEdge* edge, GanvEdgeCoords* coords); + +void +ganv_edge_request_redraw(GanvItem* item, + const GanvEdgeCoords* coords); + +void +ganv_edge_tick(GanvEdge* edge, double seconds); + +/* Box */ + +void +ganv_box_path(GanvBox* box, + cairo_t* cr, double x1, double y1, double x2, double y2, + double dr); + +void +ganv_box_request_redraw(GanvItem* item, + const GanvBoxCoords* coords, + gboolean world); + +/* Port */ + +void +ganv_port_set_control_value_internal(GanvPort* port, + float value); + +void +ganv_port_set_direction(GanvPort* port, + GanvDirection direction); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* GANV_PRIVATE_H */ diff --git a/src/ganv_bench.cpp b/src/ganv_bench.cpp new file mode 100644 index 0000000..b1bdefb --- /dev/null +++ b/src/ganv_bench.cpp @@ -0,0 +1,178 @@ +/* This file is part of Ganv. + * Copyright 2013 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <glibmm.h> +#include <gtkmm/main.h> +#include <gtkmm/scrolledwindow.h> +#include <gtkmm/window.h> + +#include "ganv/ganv.hpp" + +using namespace std; +using namespace Ganv; + +static const int MAX_NUM_PORTS = 16; + +vector<Node*> ins; +vector<Node*> outs; + +static Module* +make_module(Canvas* canvas) +{ + char name[8]; + + snprintf(name, 8, "mod%d", rand() % 10000); + Module* m(new Module(*canvas, name, + rand() % (int)canvas->get_width(), + rand() % (int)canvas->get_height(), + true)); + + int n_ins = rand() % MAX_NUM_PORTS; + for (int i = 0; i < n_ins; ++i) { + snprintf(name, 8, "in%d", rand() % 10000); + Port* p(new Port(*m, name, true, + ((rand() % 0xFFFFFF) << 8) | 0xFF)); + ins.push_back(p); + } + + int n_outs = rand() % MAX_NUM_PORTS; + for (int i = 0; i < n_outs; ++i) { + snprintf(name, 8, "out%d", rand() % 10000); + Port* p(new Port(*m, name, false, + ((rand() % 0xFFFFFF) << 8) | 0xFF)); + outs.push_back(p); + } + + m->show(); + return m; +} + +static Circle* +make_circle(Canvas* canvas) +{ + char name[8]; + + snprintf(name, 8, "%d", rand() % 10000); + Circle* e(new Circle(*canvas, name, + rand() % (int)canvas->get_width(), + rand() % (int)canvas->get_height())); + + ins.push_back(e); + outs.push_back(e); + + return e; +} + +static bool +quit() +{ + Gtk::Main::quit(); + return true; +} + +static int +print_usage(const char* name) +{ + fprintf(stderr, + "USAGE: %s [OPTION]... CANVAS_W CANVAS_H N_MODULES N_CIRCLES N_EDGES\n\n" + "Options:\n" + " -o Remain open (do not close immediately)\n" + " -a Arrange canvas\n" + " -s Straight edges\n", + name); + return 1; +} + +int +main(int argc, char** argv) +{ + if (argc < 5) { + return print_usage(argv[0]); + } + + int arg = 1; + + bool remain_open = false; + bool arrange = false; + bool straight = false; + for (; arg < argc && argv[arg][0] == '-'; ++arg) { + if (argv[arg][1] == 'o') { + remain_open = true; + } else if (argv[arg][1] == 'a') { + arrange = true; + } else if (argv[arg][1] == 's') { + straight = true; + } else { + return print_usage(argv[0]); + } + } + + const int canvas_w = atoi(argv[arg++]); + const int canvas_h = atoi(argv[arg++]); + + if (argc - arg < 3) { + return print_usage(argv[0]); + } + + const int n_modules = atoi(argv[arg++]); + const int n_circles = atoi(argv[arg++]); + const int n_edges = atoi(argv[arg++]); + + srand(time(NULL)); + + Gtk::Main kit(argc, argv); + + Gtk::Window window; + Gtk::ScrolledWindow* scroller = Gtk::manage(new Gtk::ScrolledWindow()); + + Canvas* canvas = new Canvas(canvas_w, canvas_h); + scroller->add(canvas->widget()); + window.add(*scroller); + + window.show_all(); + + for (int i = 0; i < n_modules; ++i) { + make_module(canvas); + } + + for (int i = 0; i < n_circles; ++i) { + make_circle(canvas); + } + + for (int i = 0; i < n_edges; ++i) { + Node* src = outs[rand() % outs.size()]; + Node* dst = ins[rand() % ins.size()]; + Edge* c = new Edge(*canvas, src, dst, 0x808080FF); + if (straight) { + c->set_curved(false); + } + } + + if (arrange) { + canvas->arrange(); + } + + if (!remain_open) { + Glib::signal_idle().connect(sigc::ptr_fun(quit)); + } + + Gtk::Main::run(window); + + return 0; +} diff --git a/src/ganv_test.c b/src/ganv_test.c new file mode 100644 index 0000000..ec1b0a8 --- /dev/null +++ b/src/ganv_test.c @@ -0,0 +1,119 @@ +/* This file is part of Ganv. + * Copyright 2007-2013 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <gtk/gtk.h> + +#include "ganv/ganv.h" + +static void +on_window_destroy(GtkWidget* widget, void* data) +{ + gtk_main_quit(); +} + +static void +on_connect(GanvCanvas* canvas, GanvNode* tail, GanvNode* head, void* data) +{ + ganv_edge_new(canvas, tail, head, "color", 0xFFFFFFFF, NULL); +} + +static void +on_disconnect(GanvCanvas* canvas, GanvNode* tail, GanvNode* head, void* data) +{ + ganv_canvas_remove_edge_between(canvas, tail, head); +} + +static void +on_value_changed(GanvPort* port, double value, void* data) +{ + fprintf(stderr, "Value changed: port %p = %lf\n", (void*)port, value); +} + +int +main(int argc, char** argv) +{ + gtk_init(&argc, &argv); + + GtkWindow* win = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + gtk_window_set_title(win, "Ganv Test"); + g_signal_connect(win, "destroy", + G_CALLBACK(on_window_destroy), NULL); + + GanvCanvas* canvas = ganv_canvas_new(1024, 768); + gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(canvas)); + + GanvCircle* circle = ganv_circle_new(canvas, + "x", 400.0, + "y", 400.0, + "draggable", TRUE, + "label", "state", + "radius", 32.0, + NULL); + ganv_item_show(GANV_ITEM(circle)); + + GanvModule* module = ganv_module_new(canvas, + "x", 10.0, + "y", 10.0, + "draggable", TRUE, + "label", "test", + NULL); + + ganv_port_new(module, FALSE, + "label", "Signal", + NULL); + + GanvPort* cport = ganv_port_new(module, TRUE, + "label", "Control", + NULL); + ganv_port_show_control(cport); + g_signal_connect(cport, "value-changed", + G_CALLBACK(on_value_changed), NULL); + + //GtkWidget* entry = gtk_entry_new(); + //ganv_module_embed(module, entry); + + GanvPort* tport = ganv_port_new(module, TRUE, + "label", "Toggle", + NULL); + ganv_port_show_control(tport); + ganv_port_set_control_is_toggle(tport, TRUE); + + ganv_item_show(GANV_ITEM(module)); + + GanvModule* module2 = ganv_module_new(canvas, + "x", 200.0, + "y", 10.0, + "draggable", TRUE, + "label", "test2", + NULL); + + ganv_port_new(module2, TRUE, + "label", "Signal", + NULL); + + g_signal_connect(canvas, "connect", + G_CALLBACK(on_connect), canvas); + + g_signal_connect(canvas, "disconnect", + G_CALLBACK(on_disconnect), canvas); + + ganv_item_show(GANV_ITEM(module2)); + + gtk_widget_show_all(GTK_WIDGET(win)); + gtk_window_present(win); + gtk_main(); + + return 0; +} diff --git a/src/ganv_test.py b/src/ganv_test.py new file mode 100755 index 0000000..1ea53c2 --- /dev/null +++ b/src/ganv_test.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +from gi.repository import Ganv, Gtk + +win = Gtk.Window() +win.set_title("Ganv Python Test") +win.connect("destroy", lambda obj: Gtk.main_quit()) + +canvas = Ganv.Canvas.new(1024, 768) +module = Ganv.Module(canvas=canvas, + label="Test") +# iport = Ganv.Port(module=module, +# is_input=True, +# label="In") +# oport = Ganv.Port(module=module, +# is_input=False, +# label="In") + +win.add(canvas) +win.show_all() +win.present() +Gtk.main() diff --git a/src/gettext.h b/src/gettext.h new file mode 100644 index 0000000..d32bb70 --- /dev/null +++ b/src/gettext.h @@ -0,0 +1,26 @@ +/* This file is part of Ganv. + * Copyright 2007-2013 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GANV_GETTEXT_H +#define GANV_GETTEXT_H + +#ifdef ENABLE_NLS +# include <libintl.h> +# define _(str) dgettext("ganv", str) +#else +# define _(str) str +#endif + +#endif /* GANV_GETTEXT_H */ diff --git a/src/group.c b/src/group.c new file mode 100644 index 0000000..b13dc13 --- /dev/null +++ b/src/group.c @@ -0,0 +1,448 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +/* Based on GnomeCanvasGroup, by Federico Mena <federico@nuclecu.unam.mx> + * and Raph Levien <raph@gimp.org> + * Copyright 1997-2000 Free Software Foundation + */ + +#include <math.h> + +#include "ganv/canvas.h" +#include "ganv/group.h" + +#include "./gettext.h" +#include "./ganv-private.h" + +enum { + GROUP_PROP_0 +}; + +G_DEFINE_TYPE(GanvGroup, ganv_group, GANV_TYPE_ITEM) + +static GanvItemClass* group_parent_class; + +static void +ganv_group_init(GanvGroup* group) +{ + GanvGroupImpl* impl = G_TYPE_INSTANCE_GET_PRIVATE( + group, GANV_TYPE_GROUP, GanvGroupImpl); + + group->impl = impl; + group->impl->item_list = NULL; + group->impl->item_list_end = NULL; +} + +static void +ganv_group_set_property(GObject* gobject, guint param_id, + const GValue* value, GParamSpec* pspec) +{ + g_return_if_fail(GANV_IS_GROUP(gobject)); + + switch (param_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, param_id, pspec); + break; + } +} + +static void +ganv_group_get_property(GObject* gobject, guint param_id, + GValue* value, GParamSpec* pspec) +{ + g_return_if_fail(GANV_IS_GROUP(gobject)); + + switch (param_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, param_id, pspec); + break; + } +} + +static void +ganv_group_destroy(GtkObject* object) +{ + GanvGroup* group; + + g_return_if_fail(GANV_IS_GROUP(object)); + + group = GANV_GROUP(object); + + while (group->impl->item_list) { + // child is unref'ed by the child's group_remove(). + gtk_object_destroy(GTK_OBJECT(group->impl->item_list->data)); + } + if (GTK_OBJECT_CLASS(group_parent_class)->destroy) { + (*GTK_OBJECT_CLASS(group_parent_class)->destroy)(object); + } +} + +static void +ganv_group_update(GanvItem* item, int flags) +{ + GanvGroup* group = GANV_GROUP(item); + + double min_x = 0.0; + double min_y = 0.0; + double max_x = 0.0; + double max_y = 0.0; + + for (GList* list = group->impl->item_list; list; list = list->next) { + GanvItem* i = (GanvItem*)list->data; + + ganv_item_invoke_update(i, flags); + + min_x = fmin(min_x, fmin(i->impl->x1, i->impl->x2)); + min_y = fmin(min_y, fmin(i->impl->y1, i->impl->y2)); + max_x = fmax(max_x, fmax(i->impl->x1, i->impl->x2)); + max_y = fmax(max_y, fmax(i->impl->y2, i->impl->y2)); + } + item->impl->x1 = min_x; + item->impl->y1 = min_y; + item->impl->x2 = max_x; + item->impl->y2 = max_y; + + (*group_parent_class->update)(item, flags); +} + +static void +ganv_group_realize(GanvItem* item) +{ + GanvGroup* group; + GList* list; + GanvItem* i; + + group = GANV_GROUP(item); + + for (list = group->impl->item_list; list; list = list->next) { + i = (GanvItem*)list->data; + + if (!(i->object.flags & GANV_ITEM_REALIZED)) { + (*GANV_ITEM_GET_CLASS(i)->realize)(i); + } + } + + (*group_parent_class->realize)(item); +} + +static void +ganv_group_unrealize(GanvItem* item) +{ + GanvGroup* group; + GList* list; + GanvItem* i; + + group = GANV_GROUP(item); + + for (list = group->impl->item_list; list; list = list->next) { + i = (GanvItem*)list->data; + + if (i->object.flags & GANV_ITEM_REALIZED) { + (*GANV_ITEM_GET_CLASS(i)->unrealize)(i); + } + } + + (*group_parent_class->unrealize)(item); +} + +static void +ganv_group_map(GanvItem* item) +{ + GanvGroup* group; + GList* list; + GanvItem* i; + + group = GANV_GROUP(item); + + for (list = group->impl->item_list; list; list = list->next) { + i = (GanvItem*)list->data; + + if (!(i->object.flags & GANV_ITEM_MAPPED)) { + (*GANV_ITEM_GET_CLASS(i)->map)(i); + } + } + + (*group_parent_class->map)(item); +} + +static void +ganv_group_unmap(GanvItem* item) +{ + GanvGroup* group; + GList* list; + GanvItem* i; + + group = GANV_GROUP(item); + + for (list = group->impl->item_list; list; list = list->next) { + i = (GanvItem*)list->data; + + if (i->object.flags & GANV_ITEM_MAPPED) { + (*GANV_ITEM_GET_CLASS(i)->unmap)(i); + } + } + + (*group_parent_class->unmap)(item); +} + +static void +ganv_group_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + GanvGroup* group = GANV_GROUP(item); + + // TODO: Layered drawing + + for (GList* list = group->impl->item_list; list; list = list->next) { + GanvItem* child = (GanvItem*)list->data; + + if (((child->object.flags & GANV_ITEM_VISIBLE) + && ((child->impl->x1 < (cx + cw)) + && (child->impl->y1 < (cy + ch)) + && (child->impl->x2 > cx) + && (child->impl->y2 > cy)))) { + if (GANV_ITEM_GET_CLASS(child)->draw) { + (*GANV_ITEM_GET_CLASS(child)->draw)( + child, cr, cx, cy, cw, ch); + } + } + } +} + +static double +ganv_group_point(GanvItem* item, double x, double y, GanvItem** actual_item) +{ + GanvGroup* group = GANV_GROUP(item); + + const double x1 = x - GANV_CLOSE_ENOUGH; + const double y1 = y - GANV_CLOSE_ENOUGH; + const double x2 = x + GANV_CLOSE_ENOUGH; + const double y2 = y + GANV_CLOSE_ENOUGH; + + double dist = 0.0; + double best = 0.0; + + *actual_item = NULL; + + for (GList* list = group->impl->item_list; list; list = list->next) { + GanvItem* child = (GanvItem*)list->data; + if ((child->impl->x1 > x2) || (child->impl->y1 > y2) || (child->impl->x2 < x1) || (child->impl->y2 < y1)) { + continue; + } + + GanvItem* point_item = NULL; + + int has_point = FALSE; + if ((child->object.flags & GANV_ITEM_VISIBLE) + && GANV_ITEM_GET_CLASS(child)->point) { + dist = GANV_ITEM_GET_CLASS(child)->point( + child, + x - child->impl->x, y - child->impl->y, + &point_item); + has_point = TRUE; + } + + if (has_point + && point_item + && ((int)(dist + 0.5) <= GANV_CLOSE_ENOUGH)) { + best = dist; + *actual_item = point_item; + } + } + + if (*actual_item) { + return best; + } else { + *actual_item = item; + return 0.0; + } +} + +/* Get bounds of child item in group-relative coordinates. */ +static void +get_child_bounds(GanvItem* child, double* x1, double* y1, double* x2, double* y2) +{ + ganv_item_get_bounds(child, x1, y1, x2, y2); + + // Make bounds relative to the item's parent coordinate system + *x1 -= child->impl->x; + *y1 -= child->impl->y; + *x2 -= child->impl->x; + *y2 -= child->impl->y; +} + +static void +ganv_group_bounds(GanvItem* item, double* x1, double* y1, double* x2, double* y2) +{ + GanvGroup* group; + GanvItem* child; + GList* list; + double tx1, ty1, tx2, ty2; + double minx, miny, maxx, maxy; + int set; + + group = GANV_GROUP(item); + + /* Get the bounds of the first visible item */ + + child = NULL; /* Unnecessary but eliminates a warning. */ + + set = FALSE; + + for (list = group->impl->item_list; list; list = list->next) { + child = (GanvItem*)list->data; + + if (child->object.flags & GANV_ITEM_VISIBLE) { + set = TRUE; + get_child_bounds(child, &minx, &miny, &maxx, &maxy); + break; + } + } + + /* If there were no visible items, return an empty bounding box */ + + if (!set) { + *x1 = *y1 = *x2 = *y2 = 0.0; + return; + } + + /* Now we can grow the bounds using the rest of the items */ + + list = list->next; + + for (; list; list = list->next) { + child = (GanvItem*)list->data; + + if (!(child->object.flags & GANV_ITEM_VISIBLE)) { + continue; + } + + get_child_bounds(child, &tx1, &ty1, &tx2, &ty2); + + if (tx1 < minx) { + minx = tx1; + } + + if (ty1 < miny) { + miny = ty1; + } + + if (tx2 > maxx) { + maxx = tx2; + } + + if (ty2 > maxy) { + maxy = ty2; + } + } + + *x1 = minx; + *y1 = miny; + *x2 = maxx; + *y2 = maxy; +} + +static void +ganv_group_add(GanvItem* parent, GanvItem* item) +{ + GanvGroup* group = GANV_GROUP(parent); + g_object_ref_sink(G_OBJECT(item)); + + if (!group->impl->item_list) { + group->impl->item_list = g_list_append(group->impl->item_list, item); + group->impl->item_list_end = group->impl->item_list; + } else { + group->impl->item_list_end = g_list_append(group->impl->item_list_end, item)->next; + } + + if (group->item.object.flags & GANV_ITEM_REALIZED) { + (*GANV_ITEM_GET_CLASS(item)->realize)(item); + } + + if (group->item.object.flags & GANV_ITEM_MAPPED) { + (*GANV_ITEM_GET_CLASS(item)->map)(item); + } + + g_object_notify(G_OBJECT(item), "parent"); +} + +static void +ganv_group_remove(GanvItem* parent, GanvItem* item) +{ + GanvGroup* group = GANV_GROUP(parent); + GList* children; + + g_return_if_fail(GANV_IS_GROUP(group)); + g_return_if_fail(GANV_IS_ITEM(item)); + + for (children = group->impl->item_list; children; children = children->next) { + if (children->data == item) { + if (item->object.flags & GANV_ITEM_MAPPED) { + (*GANV_ITEM_GET_CLASS(item)->unmap)(item); + } + + if (item->object.flags & GANV_ITEM_REALIZED) { + (*GANV_ITEM_GET_CLASS(item)->unrealize)(item); + } + + /* Unparent the child */ + + item->impl->parent = NULL; + g_object_unref(G_OBJECT(item)); + + /* Remove it from the list */ + + if (children == group->impl->item_list_end) { + group->impl->item_list_end = children->prev; + } + + group->impl->item_list = g_list_remove_link(group->impl->item_list, children); + g_list_free(children); + break; + } + } +} + +static void +ganv_group_class_init(GanvGroupClass* klass) +{ + GObjectClass* gobject_class; + GtkObjectClass* object_class; + GanvItemClass* item_class; + + gobject_class = (GObjectClass*)klass; + object_class = (GtkObjectClass*)klass; + item_class = (GanvItemClass*)klass; + + group_parent_class = (GanvItemClass*)g_type_class_peek_parent(klass); + + g_type_class_add_private(klass, sizeof(GanvGroupImpl)); + + gobject_class->set_property = ganv_group_set_property; + gobject_class->get_property = ganv_group_get_property; + + object_class->destroy = ganv_group_destroy; + + item_class->add = ganv_group_add; + item_class->remove = ganv_group_remove; + item_class->update = ganv_group_update; + item_class->realize = ganv_group_realize; + item_class->unrealize = ganv_group_unrealize; + item_class->map = ganv_group_map; + item_class->unmap = ganv_group_unmap; + item_class->draw = ganv_group_draw; + item_class->point = ganv_group_point; + item_class->bounds = ganv_group_bounds; +} diff --git a/src/item.c b/src/item.c new file mode 100644 index 0000000..f3ee636 --- /dev/null +++ b/src/item.c @@ -0,0 +1,709 @@ +/* This file is part of Ganv. + * Copyright 2007-2016 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +/* Based on GnomeCanvas, by Federico Mena <federico@nuclecu.unam.mx> + * and Raph Levien <raph@gimp.org> + * Copyright 1997-2000 Free Software Foundation + */ + +#include "ganv/canvas.h" +#include "ganv/node.h" + +#include "./boilerplate.h" +#include "./ganv-marshal.h" +#include "./ganv-private.h" +#include "./gettext.h" + +/* All canvas items are derived from GanvItem. The only information a GanvItem + * contains is its parent canvas, its parent canvas item, its bounding box in + * world coordinates, and its current affine transformation. + * + * Items inside a canvas are organized in a tree, where leaves are items + * without any children. Each canvas has a single root item, which can be + * obtained with the ganv_canvas_base_get_root() function. + * + * The abstract GanvItem class does not have any configurable or queryable + * attributes. + */ + +/* Update flags for items */ +enum { + GANV_CANVAS_UPDATE_REQUESTED = 1 << 0, + GANV_CANVAS_UPDATE_AFFINE = 1 << 1, + GANV_CANVAS_UPDATE_VISIBILITY = 1 << 2 +}; + +#define GCI_UPDATE_MASK (GANV_CANVAS_UPDATE_REQUESTED \ + | GANV_CANVAS_UPDATE_AFFINE \ + | GANV_CANVAS_UPDATE_VISIBILITY) + +enum { + ITEM_PROP_0, + ITEM_PROP_PARENT, + ITEM_PROP_X, + ITEM_PROP_Y, + ITEM_PROP_MANAGED +}; + +enum { + ITEM_EVENT, + ITEM_LAST_SIGNAL +}; + +static guint item_signals[ITEM_LAST_SIGNAL]; + +G_DEFINE_TYPE(GanvItem, ganv_item, GTK_TYPE_OBJECT) + +static GtkObjectClass* item_parent_class; + +/* Object initialization function for GanvItem */ +static void +ganv_item_init(GanvItem* item) +{ + GanvItemImpl* impl = G_TYPE_INSTANCE_GET_PRIVATE( + item, GANV_TYPE_ITEM, GanvItemImpl); + + item->object.flags |= GANV_ITEM_VISIBLE; + item->impl = impl; + item->impl->managed = FALSE; + item->impl->wrapper = NULL; +} + +/** + * ganv_item_new: + * @parent: The parent group for the new item. + * @type: The object type of the item. + * @first_arg_name: A list of object argument name/value pairs, NULL-terminated, + * used to configure the item. For example, "fill_color", "black", + * "width_units", 5.0, NULL. + * @...: first argument value, second argument name, second argument value, ... + * + * Creates a new canvas item with @parent as its parent group. The item is + * created at the top of its parent's stack, and starts up as visible. The item + * is of the specified @type, for example, it can be + * ganv_canvas_rect_get_type(). The list of object arguments/value pairs is + * used to configure the item. If you need to pass construct time parameters, you + * should use g_object_new() to pass the parameters and + * ganv_item_construct() to set up the canvas item. + * + * Return value: (transfer full): The newly-created item. + **/ +GanvItem* +ganv_item_new(GanvItem* parent, GType type, const gchar* first_arg_name, ...) +{ + g_return_val_if_fail(g_type_is_a(type, ganv_item_get_type()), NULL); + + GanvItem* item = GANV_ITEM(g_object_new(type, NULL)); + + va_list args; + va_start(args, first_arg_name); + ganv_item_construct(item, parent, first_arg_name, args); + va_end(args); + + return item; +} + +/* Performs post-creation operations on a canvas item (adding it to its parent + * group, etc.) + */ +static void +item_post_create_setup(GanvItem* item) +{ + GanvItemClass* parent_class = GANV_ITEM_GET_CLASS(item->impl->parent); + if (!item->impl->managed) { + if (parent_class->add) { + parent_class->add(item->impl->parent, item); + } else { + g_warning("item added to non-parent item\n"); + } + } + ganv_canvas_request_redraw_w(item->impl->canvas, + item->impl->x1, item->impl->y1, + item->impl->x2 + 1, item->impl->y2 + 1); + ganv_canvas_set_need_repick(item->impl->canvas); +} + +static void +ganv_item_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_ITEM(object)); + + GanvItem* item = GANV_ITEM(object); + + switch (prop_id) { + case ITEM_PROP_PARENT: + if (item->impl->parent != NULL) { + g_warning("Cannot set `parent' argument after item has " + "already been constructed."); + } else if (g_value_get_object(value)) { + item->impl->parent = GANV_ITEM(g_value_get_object(value)); + item->impl->canvas = item->impl->parent->impl->canvas; + item_post_create_setup(item); + } + break; + case ITEM_PROP_X: + item->impl->x = g_value_get_double(value); + ganv_item_request_update(item); + break; + case ITEM_PROP_Y: + item->impl->y = g_value_get_double(value); + ganv_item_request_update(item); + break; + case ITEM_PROP_MANAGED: + item->impl->managed = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_item_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_ITEM(object)); + + GanvItem* item = GANV_ITEM(object); + + switch (prop_id) { + case ITEM_PROP_PARENT: + g_value_set_object(value, item->impl->parent); + break; + case ITEM_PROP_X: + g_value_set_double(value, item->impl->x); + break; + case ITEM_PROP_Y: + g_value_set_double(value, item->impl->y); + break; + case ITEM_PROP_MANAGED: + g_value_set_boolean(value, item->impl->managed); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/** + * ganv_item_construct: + * @item: An unconstructed canvas item. + * @parent: The parent group for the item. + * @first_arg_name: The name of the first argument for configuring the item. + * @args: The list of arguments used to configure the item. + * + * Constructs a canvas item; meant for use only by item implementations. + **/ +void +ganv_item_construct(GanvItem* item, GanvItem* parent, + const gchar* first_arg_name, va_list args) +{ + g_return_if_fail(GANV_IS_ITEM(item)); + + item->impl->parent = parent; + item->impl->wrapper = NULL; + item->impl->canvas = item->impl->parent->impl->canvas; + item->impl->layer = 0; + + g_object_set_valist(G_OBJECT(item), first_arg_name, args); + + item_post_create_setup(item); +} + +/* If the item is visible, requests a redraw of it. */ +static void +redraw_if_visible(GanvItem* item) +{ + if (item->object.flags & GANV_ITEM_VISIBLE) { + ganv_canvas_request_redraw_w(item->impl->canvas, + item->impl->x1, item->impl->y1, + item->impl->x2 + 1, item->impl->y2 + 1); + } +} + +/* Standard object dispose function for canvas items */ +static void +ganv_item_dispose(GObject* object) +{ + GanvItem* item; + + g_return_if_fail(GANV_IS_ITEM(object)); + + item = GANV_ITEM(object); + + if (item->impl->canvas) { + redraw_if_visible(item); + ganv_canvas_forget_item(item->impl->canvas, item); + } + + /* Normal destroy stuff */ + + if (item->object.flags & GANV_ITEM_MAPPED) { + (*GANV_ITEM_GET_CLASS(item)->unmap)(item); + } + + if (item->object.flags & GANV_ITEM_REALIZED) { + (*GANV_ITEM_GET_CLASS(item)->unrealize)(item); + } + + if (!item->impl->managed && item->impl->parent) { + if (GANV_ITEM_GET_CLASS(item->impl->parent)->remove) { + GANV_ITEM_GET_CLASS(item->impl->parent)->remove(item->impl->parent, item); + } else { + fprintf(stderr, "warning: Item parent has no remove method\n"); + } + } + + G_OBJECT_CLASS(item_parent_class)->dispose(object); + /* items should remove any reference to item->impl->canvas after the + first ::destroy */ + item->impl->canvas = NULL; +} + +/* Realize handler for canvas items */ +static void +ganv_item_realize(GanvItem* item) +{ + GTK_OBJECT_SET_FLAGS(item, GANV_ITEM_REALIZED); + + ganv_item_request_update(item); +} + +/* Unrealize handler for canvas items */ +static void +ganv_item_unrealize(GanvItem* item) +{ + GTK_OBJECT_UNSET_FLAGS(item, GANV_ITEM_REALIZED); +} + +/* Map handler for canvas items */ +static void +ganv_item_map(GanvItem* item) +{ + GTK_OBJECT_SET_FLAGS(item, GANV_ITEM_MAPPED); +} + +/* Unmap handler for canvas items */ +static void +ganv_item_unmap(GanvItem* item) +{ + GTK_OBJECT_UNSET_FLAGS(item, GANV_ITEM_MAPPED); +} + +/* Update handler for canvas items */ +static void +ganv_item_update(GanvItem* item, int flags) +{ + GTK_OBJECT_UNSET_FLAGS(item, GANV_ITEM_NEED_UPDATE); + GTK_OBJECT_UNSET_FLAGS(item, GANV_ITEM_NEED_VIS); +} + +/* Point handler for canvas items */ +static double +ganv_item_point(GanvItem* item, double x, double y, GanvItem** actual_item) +{ + *actual_item = NULL; + return G_MAXDOUBLE; +} + +void +ganv_item_invoke_update(GanvItem* item, int flags) +{ + int child_flags = flags; + + /* apply object flags to child flags */ + + child_flags &= ~GANV_CANVAS_UPDATE_REQUESTED; + + if (item->object.flags & GANV_ITEM_NEED_UPDATE) { + child_flags |= GANV_CANVAS_UPDATE_REQUESTED; + } + + if (item->object.flags & GANV_ITEM_NEED_VIS) { + child_flags |= GANV_CANVAS_UPDATE_VISIBILITY; + } + + if (child_flags & GCI_UPDATE_MASK) { + if (GANV_ITEM_GET_CLASS(item)->update) { + GANV_ITEM_GET_CLASS(item)->update(item, child_flags); + g_assert(!(GTK_OBJECT_FLAGS(item) & GANV_ITEM_NEED_UPDATE)); + } + } +} + +/** + * ganv_item_set: + * @item: A canvas item. + * @first_arg_name: The list of object argument name/value pairs used to configure the item. + * @...: first argument value, second argument name, second argument value, ... + * + * Configures a canvas item. The arguments in the item are set to the specified + * values, and the item is repainted as appropriate. + **/ +void +ganv_item_set(GanvItem* item, const gchar* first_arg_name, ...) +{ + va_list args; + + va_start(args, first_arg_name); + ganv_item_set_valist(item, first_arg_name, args); + va_end(args); +} + +/** + * ganv_item_set_valist: + * @item: A canvas item. + * @first_arg_name: The name of the first argument used to configure the item. + * @args: The list of object argument name/value pairs used to configure the item. + * + * Configures a canvas item. The arguments in the item are set to the specified + * values, and the item is repainted as appropriate. + **/ +void +ganv_item_set_valist(GanvItem* item, const gchar* first_arg_name, va_list args) +{ + g_return_if_fail(GANV_IS_ITEM(item)); + + g_object_set_valist(G_OBJECT(item), first_arg_name, args); + + ganv_canvas_set_need_repick(item->impl->canvas); +} + +GanvCanvas* +ganv_item_get_canvas(GanvItem* item) +{ + return item->impl->canvas; +} + +GanvItem* +ganv_item_get_parent(GanvItem* item) +{ + return item->impl->parent; +} + +void +ganv_item_raise(GanvItem* item) +{ + ++item->impl->layer; +} + +void +ganv_item_lower(GanvItem* item) +{ + --item->impl->layer; +} + +/** + * ganv_item_move: + * @item: A canvas item. + * @dx: Horizontal offset. + * @dy: Vertical offset. + **/ +void +ganv_item_move(GanvItem* item, double dx, double dy) +{ + if (!item || !GANV_IS_ITEM(item)) { + return; + } + + item->impl->x += dx; + item->impl->y += dy; + + ganv_item_request_update(item); + ganv_canvas_set_need_repick(item->impl->canvas); +} + +/** + * ganv_item_show: + * @item: A canvas item. + * + * Shows a canvas item. If the item was already shown, then no action is taken. + **/ +void +ganv_item_show(GanvItem* item) +{ + g_return_if_fail(GANV_IS_ITEM(item)); + + if (!(item->object.flags & GANV_ITEM_VISIBLE)) { + item->object.flags |= GANV_ITEM_VISIBLE; + ganv_canvas_request_redraw_w(item->impl->canvas, + item->impl->x1, item->impl->y1, + item->impl->x2 + 1, item->impl->y2 + 1); + ganv_canvas_set_need_repick(item->impl->canvas); + } +} + +/** + * ganv_item_hide: + * @item: A canvas item. + * + * Hides a canvas item. If the item was already hidden, then no action is + * taken. + **/ +void +ganv_item_hide(GanvItem* item) +{ + g_return_if_fail(GANV_IS_ITEM(item)); + + if (item->object.flags & GANV_ITEM_VISIBLE) { + item->object.flags &= ~GANV_ITEM_VISIBLE; + ganv_canvas_request_redraw_w(item->impl->canvas, + item->impl->x1, item->impl->y1, + item->impl->x2 + 1, item->impl->y2 + 1); + ganv_canvas_set_need_repick(item->impl->canvas); + } +} + +void +ganv_item_i2w_offset(GanvItem* item, double* px, double* py) +{ + double x = 0.0; + double y = 0.0; + while (item) { + x += item->impl->x; + y += item->impl->y; + item = item->impl->parent; + } + *px = x; + *py = y; +} + +/** + * ganv_item_i2w: + * @item: A canvas item. + * @x: X coordinate to convert (input/output value). + * @y: Y coordinate to convert (input/output value). + * + * Converts a coordinate pair from item-relative coordinates to world + * coordinates. + **/ +void +ganv_item_i2w(GanvItem* item, double* x, double* y) +{ + /*g_return_if_fail(GANV_IS_ITEM(item)); + g_return_if_fail(x != NULL); + g_return_if_fail(y != NULL);*/ + + double off_x; + double off_y; + ganv_item_i2w_offset(item, &off_x, &off_y); + + *x += off_x; + *y += off_y; +} + +void +ganv_item_i2w_pair(GanvItem* item, double* x1, double* y1, double* x2, double* y2) +{ + double off_x; + double off_y; + ganv_item_i2w_offset(item, &off_x, &off_y); + + *x1 += off_x; + *y1 += off_y; + *x2 += off_x; + *y2 += off_y; +} + +/** + * ganv_item_w2i: + * @item: A canvas item. + * @x: X coordinate to convert (input/output value). + * @y: Y coordinate to convert (input/output value). + * + * Converts a coordinate pair from world coordinates to item-relative + * coordinates. + **/ +void +ganv_item_w2i(GanvItem* item, double* x, double* y) +{ + double off_x; + double off_y; + ganv_item_i2w_offset(item, &off_x, &off_y); + + *x -= off_x; + *y -= off_y; +} + +/** + * ganv_item_grab_focus: + * @item: A canvas item. + * + * Makes the specified item take the keyboard focus, so all keyboard events will + * be sent to it. If the canvas widget itself did not have the focus, it grabs + * it as well. + **/ +void +ganv_item_grab_focus(GanvItem* item) +{ + ganv_canvas_grab_focus(item->impl->canvas, item); +} + +void +ganv_item_emit_event(GanvItem* item, GdkEvent* event, gint* finished) +{ + g_signal_emit(item, item_signals[ITEM_EVENT], 0, event, finished); +} + +static void +ganv_item_default_bounds(GanvItem* item, double* x1, double* y1, double* x2, double* y2) +{ + *x1 = *y1 = *x2 = *y2 = 0.0; +} + +/** + * ganv_item_get_bounds: + * @item: A canvas item. + * @x1: Leftmost edge of the bounding box (return value). + * @y1: Upper edge of the bounding box (return value). + * @x2: Rightmost edge of the bounding box (return value). + * @y2: Lower edge of the bounding box (return value). + * + * Queries the bounding box of a canvas item. The bounding box may not be + * exactly tight, but the canvas items will do the best they can. The bounds + * are returned in the coordinate system of the item's parent. + **/ +void +ganv_item_get_bounds(GanvItem* item, double* x1, double* y1, double* x2, double* y2) +{ + GANV_ITEM_GET_CLASS(item)->bounds(item, x1, y1, x2, y2); +} + +/** + * ganv_item_request_update: + * @item: A canvas item. + * + * To be used only by item implementations. Requests that the canvas queue an + * update for the specified item. + **/ +void +ganv_item_request_update(GanvItem* item) +{ + if (!item->impl->canvas) { + /* Item is being / has been destroyed, ignore */ + return; + } + + item->object.flags |= GANV_ITEM_NEED_UPDATE; + + if (item->impl->parent != NULL && + !(item->impl->parent->object.flags & GANV_ITEM_NEED_UPDATE)) { + /* Recurse up the tree */ + ganv_item_request_update(item->impl->parent); + } else { + /* Have reached the top of the tree, make sure the update call gets scheduled. */ + ganv_canvas_request_update(item->impl->canvas); + } +} + +void +ganv_item_set_wrapper(GanvItem* item, void* wrapper) +{ + item->impl->wrapper = wrapper; +} + +void* +ganv_item_get_wrapper(GanvItem* item) +{ + return item->impl->wrapper; +} + +static gboolean +boolean_handled_accumulator(GSignalInvocationHint* ihint, + GValue* return_accu, + const GValue* handler_return, + gpointer dummy) +{ + gboolean continue_emission; + gboolean signal_handled; + + signal_handled = g_value_get_boolean(handler_return); + g_value_set_boolean(return_accu, signal_handled); + continue_emission = !signal_handled; + + return continue_emission; +} + +/* Class initialization function for GanvItemClass */ +static void +ganv_item_class_init(GanvItemClass* klass) +{ + GObjectClass* gobject_class; + + gobject_class = (GObjectClass*)klass; + + item_parent_class = (GtkObjectClass*)g_type_class_peek_parent(klass); + + g_type_class_add_private(klass, sizeof(GanvItemImpl)); + + gobject_class->set_property = ganv_item_set_property; + gobject_class->get_property = ganv_item_get_property; + + g_object_class_install_property + (gobject_class, ITEM_PROP_PARENT, + g_param_spec_object("parent", NULL, NULL, + GANV_TYPE_ITEM, + (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_object_class_install_property + (gobject_class, ITEM_PROP_X, + g_param_spec_double("x", + _("X"), + _("X"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, ITEM_PROP_Y, + g_param_spec_double("y", + _("Y"), + _("Y"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_object_class_install_property + (gobject_class, ITEM_PROP_MANAGED, + g_param_spec_boolean("managed", + _("Managed"), + _("Whether the item is managed by its parent"), + 0, + (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE))); + + item_signals[ITEM_EVENT] + = g_signal_new("event", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GanvItemClass, event), + boolean_handled_accumulator, NULL, + ganv_marshal_BOOLEAN__BOXED, + G_TYPE_BOOLEAN, 1, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + + gobject_class->dispose = ganv_item_dispose; + + klass->realize = ganv_item_realize; + klass->unrealize = ganv_item_unrealize; + klass->map = ganv_item_map; + klass->unmap = ganv_item_unmap; + klass->update = ganv_item_update; + klass->point = ganv_item_point; + klass->bounds = ganv_item_default_bounds; +} diff --git a/src/module.c b/src/module.c new file mode 100644 index 0000000..5310191 --- /dev/null +++ b/src/module.c @@ -0,0 +1,861 @@ +/* This file is part of Ganv. + * Copyright 2007-2016 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <string.h> + +#include "ganv/canvas.h" +#include "ganv/module.h" +#include "ganv/port.h" +#include "ganv/widget.h" + +#include "./color.h" +#include "./boilerplate.h" +#include "./gettext.h" +#include "./ganv-private.h" + +#define FOREACH_PORT(ports, i) \ + for (GanvPort** i = (GanvPort**)ports->pdata; \ + i != (GanvPort**)ports->pdata + ports->len; ++i) + +#define FOREACH_PORT_CONST(ports, i) \ + for (const GanvPort** i = (const GanvPort**)ports->pdata; \ + i != (const GanvPort**)ports->pdata + ports->len; ++i) + +static const double PAD = 2.0; +static const double EDGE_PAD = 5.0; +static const double MODULE_LABEL_PAD = 2.0; + +G_DEFINE_TYPE(GanvModule, ganv_module, GANV_TYPE_BOX) + +static GanvBoxClass* parent_class; + +enum { + PROP_0 +}; + +static void +ganv_module_init(GanvModule* module) +{ + GanvModuleImpl* impl = G_TYPE_INSTANCE_GET_PRIVATE( + module, GANV_TYPE_MODULE, GanvModuleImpl); + + module->impl = impl; + + GANV_NODE(module)->impl->can_head = FALSE; + GANV_NODE(module)->impl->can_tail = FALSE; + + impl->ports = g_ptr_array_new(); + impl->embed_item = NULL; + impl->embed_width = 0; + impl->embed_height = 0; + impl->widest_input = 0.0; + impl->widest_output = 0.0; + impl->must_reorder = FALSE; +} + +static void +ganv_module_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_MODULE(object)); + + GanvModule* module = GANV_MODULE(object); + GanvModuleImpl* impl = module->impl; + + if (impl->ports) { + FOREACH_PORT(impl->ports, p) { + g_object_unref(GTK_OBJECT(*p)); + } + g_ptr_array_free(impl->ports, TRUE); + impl->ports = NULL; + } + + if (impl->embed_item) { + g_object_unref(GTK_OBJECT(impl->embed_item)); + impl->embed_item = NULL; + } + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +static void +ganv_module_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_MODULE(object)); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_module_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_MODULE(object)); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +typedef struct { + double embed_x; + double width; + double input_width; + double output_width; + gboolean horiz; + gboolean embed_between; +} Metrics; + +static void +title_size(GanvModule* module, double* w, double* h) +{ + if (module->box.node.impl->label) { + g_object_get(G_OBJECT(module->box.node.impl->label), + "width", w, + "height", h, + NULL); + } else { + *w = *h = 0.0; + } +} + +static void +measure(GanvModule* module, Metrics* m) +{ + memset(m, '\0', sizeof(Metrics)); + + double title_w, title_h; + title_size(module, &title_w, &title_h); + + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module)); + GanvText* canvas_title = GANV_NODE(module)->impl->label; + GanvModuleImpl* impl = module->impl; + + if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_DOWN) { + double contents_width = 0.0; + if (canvas_title) { + contents_width += title_w + (2.0 * PAD); + } + + m->embed_x = 0; + m->input_width = ganv_module_get_empty_port_breadth(module); + m->output_width = ganv_module_get_empty_port_breadth(module); + + // TODO: cache this or merge with resize_right + unsigned n_inputs = 0; + unsigned n_outputs = 0; + FOREACH_PORT(impl->ports, pi) { + if ((*pi)->impl->is_input) { + ++n_inputs; + } else { + ++n_outputs; + } + } + + const unsigned hor_ports = MAX(1, MAX(n_inputs, n_outputs)); + const double ports_width = (2 * EDGE_PAD) + + ((m->input_width) * hor_ports) + + ((PAD + 1.0) * (hor_ports - 1)); + + m->width = MAX(contents_width, ports_width); + m->width = MAX(m->width, impl->embed_width); + + if (impl->embed_item) { + m->width = MAX(impl->embed_width + 2.0 * PAD, m->width); + m->embed_x = PAD; + } + return; + } + + // The amount of space between a port edge and the module edge (on the + // side that the port isn't right on the edge). + const double hor_pad = (canvas_title ? 10.0 : 20.0); + + m->width = (canvas_title) ? title_w + 10.0 : 1.0; + + // Title is wide or there is an embedded widget, + // put inputs and outputs beside each other + m->horiz = (impl->embed_item || + (impl->widest_input + impl->widest_output + 10.0 + < MAX(m->width, impl->embed_width))); + + // Fit ports to module (or vice-versa) + m->input_width = impl->widest_input; + m->output_width = impl->widest_output; + double expand_w = (m->horiz ? (m->width / 2.0) : m->width) - hor_pad; + if (!impl->embed_item) { + m->input_width = MAX(impl->widest_input, expand_w); + m->output_width = MAX(impl->widest_output, expand_w); + } + + const double widest = MAX(m->input_width, m->output_width); + + if (impl->embed_item) { + double above_w = MAX(m->width, widest + hor_pad); + double between_w = MAX(m->width, + (m->input_width + + m->output_width + + impl->embed_width)); + + above_w = MAX(above_w, impl->embed_width); + + // Decide where to place embedded widget if necessary) + if (impl->embed_width < impl->embed_height * 2.0) { + m->embed_between = TRUE; + m->width = between_w; + m->embed_x = m->input_width; + } else { + m->width = above_w; + m->embed_x = 2.0; + } + } + + if (!canvas_title && (impl->widest_input == 0.0 + || impl->widest_output == 0.0)) { + m->width += 10.0; + } + + m->width += 4.0; + m->width = MAX(m->width, widest + hor_pad); +} + +static void +place_title(GanvModule* module, GanvDirection dir) +{ + GanvBox* box = GANV_BOX(module); + GanvText* canvas_title = GANV_NODE(module)->impl->label; + + double title_w, title_h; + title_size(module, &title_w, &title_h); + + if (!canvas_title) { + return; + } + + GanvItem* t = GANV_ITEM(canvas_title); + if (dir == GANV_DIRECTION_RIGHT) { + t->impl->x = (ganv_box_get_width(box) - title_w) / 2.0; + t->impl->y = 1.0; + } else { + t->impl->x = (ganv_box_get_width(box) - title_w) / 2.0; + t->impl->y = ganv_module_get_empty_port_depth(module) + 1.0; + } +} + +static void +resize_right(GanvModule* module) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module)); + GanvModuleImpl* impl = module->impl; + + Metrics m; + measure(module, &m); + + double title_w, title_h; + title_size(module, &title_w, &title_h); + + // Basic height contains title + double header_height = title_h ? (3.0 + title_h) : EDGE_PAD; + + if (impl->embed_item) { + ganv_item_set(impl->embed_item, + "x", (double)m.embed_x, + "y", header_height, + NULL); + } + + // Actually set width and height + ganv_box_set_width(GANV_BOX(module), m.width); + + // Offset ports below embedded widget + if (!m.embed_between) { + header_height += impl->embed_height; + } + + // Move ports to appropriate locations + double in_y = header_height; + double out_y = header_height; + FOREACH_PORT(impl->ports, pi) { + GanvPort* const p = (*pi); + GanvBox* const pbox = GANV_BOX(p); + GanvNode* const pnode = GANV_NODE(p); + const double h = ganv_box_get_height(pbox); + + // Offset to shift ports to make borders line up + const double border_off = (GANV_NODE(module)->impl->border_width - + pnode->impl->border_width) / 2.0; + + if (p->impl->is_input) { + ganv_node_move_to(pnode, -border_off, in_y + 1.0); + ganv_box_set_width(pbox, m.input_width); + in_y += h + pnode->impl->border_width + 1.0; + + ganv_canvas_for_each_edge_to( + canvas, pnode, + (GanvEdgeFunc)ganv_edge_update_location, NULL); + } else { + ganv_node_move_to(pnode, m.width - m.output_width + border_off, out_y + 1.0); + ganv_box_set_width(pbox, m.output_width); + out_y += h + pnode->impl->border_width + 1.0; + + ganv_canvas_for_each_edge_from( + canvas, pnode, + (GanvEdgeFunc)ganv_edge_update_location, NULL); + } + + if (!m.horiz) { + in_y = MAX(in_y, out_y); + out_y = MAX(in_y, out_y); + } + } + + double height = MAX(in_y, out_y) + EDGE_PAD; + if (impl->embed_item && m.embed_between) + height = MAX(height, impl->embed_height + header_height + 2.0); + + ganv_box_set_height(GANV_BOX(module), height); + + place_title(module, GANV_DIRECTION_RIGHT); +} + +static void +resize_down(GanvModule* module) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module)); + GanvModuleImpl* impl = module->impl; + + Metrics m; + measure(module, &m); + + double title_w, title_h; + title_size(module, &title_w, &title_h); + + const double port_depth = ganv_module_get_empty_port_depth(module); + const double port_breadth = ganv_module_get_empty_port_breadth(module); + + if (impl->embed_item) { + ganv_item_set(impl->embed_item, + "x", (double)m.embed_x, + "y", port_depth + title_h, + NULL); + } + + const double height = PAD + title_h + + impl->embed_height + (port_depth * 2.0); + + // Move ports to appropriate locations + guint in_count = 0; + guint out_count = 0; + double in_x = 0.0; + double out_x = 0.0; + FOREACH_PORT(impl->ports, pi) { + GanvPort* const p = (*pi); + GanvBox* const pbox = GANV_BOX(p); + GanvNode* const pnode = GANV_NODE(p); + ganv_box_set_width(pbox, port_breadth); + ganv_box_set_height(pbox, port_depth); + + // Offset to shift ports to make borders line up + const double border_off = (GANV_NODE(module)->impl->border_width - + pnode->impl->border_width) / 2.0; + + if (p->impl->is_input) { + in_x = EDGE_PAD + (in_count++ * (port_breadth + PAD + 1.0)); + ganv_node_move_to(pnode, in_x, -border_off); + ganv_canvas_for_each_edge_to( + canvas, pnode, + (GanvEdgeFunc)ganv_edge_update_location, NULL); + } else { + out_x = EDGE_PAD + (out_count++ * (port_breadth + PAD + 1.0)); + ganv_node_move_to(pnode, out_x, height - port_depth + border_off); + ganv_canvas_for_each_edge_from( + canvas, pnode, + (GanvEdgeFunc)ganv_edge_update_location, NULL); + } + } + + ganv_box_set_height(GANV_BOX(module), height); + ganv_box_set_width(GANV_BOX(module), m.width); + place_title(module, GANV_DIRECTION_DOWN); +} + +static void +measure_ports(GanvModule* module) +{ + GanvModuleImpl* impl = module->impl; + + impl->widest_input = 0.0; + impl->widest_output = 0.0; + FOREACH_PORT_CONST(impl->ports, pi) { + const GanvPort* const p = (*pi); + const double w = ganv_port_get_natural_width(p); + if (p->impl->is_input) { + if (w > impl->widest_input) { + impl->widest_input = w; + } + } else { + if (w > impl->widest_output) { + impl->widest_output = w; + } + } + } +} + +static void +ganv_module_resize(GanvNode* self) +{ + GanvModule* module = GANV_MODULE(self); + GanvNode* node = GANV_NODE(self); + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module)); + + double label_w = 0.0; + double label_h = 0.0; + if (node->impl->label) { + g_object_get(node->impl->label, + "width", &label_w, + "height", &label_h, + NULL); + } + + measure_ports(module); + + ganv_box_set_width(GANV_BOX(module), label_w + (MODULE_LABEL_PAD * 2.0)); + ganv_box_set_height(GANV_BOX(module), label_h); + + switch (ganv_canvas_get_direction(canvas)) { + case GANV_DIRECTION_RIGHT: + resize_right(module); + break; + case GANV_DIRECTION_DOWN: + resize_down(module); + break; + } + + if (GANV_NODE_CLASS(parent_class)->resize) { + GANV_NODE_CLASS(parent_class)->resize(self); + } +} + +static void +ganv_module_redraw_text(GanvNode* self) +{ + FOREACH_PORT(GANV_MODULE(self)->impl->ports, p) { + ganv_node_redraw_text(GANV_NODE(*p)); + } + + if (parent_class->parent_class.redraw_text) { + parent_class->parent_class.redraw_text(self); + } +} + +static void +ganv_module_add_port(GanvModule* module, + GanvPort* port) +{ + GanvModuleImpl* impl = module->impl; + + // Update widest input/output measurements if necessary + const double width = ganv_port_get_natural_width(port); + if (port->impl->is_input && width > impl->widest_input) { + impl->widest_input = width; + } else if (!port->impl->is_input && width > impl->widest_output) { + impl->widest_output = width; + } + + // Add to port array + g_ptr_array_add(impl->ports, port); + + // Request update with resize and reorder + GANV_NODE(module)->impl->must_resize = TRUE; + impl->must_reorder = TRUE; +} + +static void +ganv_module_remove_port(GanvModule* module, + GanvPort* port) +{ + gboolean removed = g_ptr_array_remove(module->impl->ports, port); + if (removed) { + const double width = ganv_box_get_width(GANV_BOX(port)); + // Find new widest input or output, if necessary + if (port->impl->is_input && width >= module->impl->widest_input) { + module->impl->widest_input = 0; + FOREACH_PORT_CONST(module->impl->ports, i) { + const GanvPort* const p = (*i); + const double w = ganv_box_get_width(GANV_BOX(p)); + if (p->impl->is_input && w >= module->impl->widest_input) { + module->impl->widest_input = w; + } + } + } else if (!port->impl->is_input && width >= module->impl->widest_output) { + module->impl->widest_output = 0; + FOREACH_PORT_CONST(module->impl->ports, i) { + const GanvPort* const p = (*i); + const double w = ganv_box_get_width(GANV_BOX(p)); + if (!p->impl->is_input && w >= module->impl->widest_output) { + module->impl->widest_output = w; + } + } + } + + GANV_NODE(module)->impl->must_resize = TRUE; + } else { + fprintf(stderr, "Failed to find port to remove\n"); + } +} + +static void +ganv_module_add(GanvItem* item, GanvItem* child) +{ + if (GANV_IS_PORT(child)) { + ganv_module_add_port(GANV_MODULE(item), GANV_PORT(child)); + } + ganv_item_request_update(item); + if (GANV_ITEM_CLASS(parent_class)->add) { + GANV_ITEM_CLASS(parent_class)->add(item, child); + } +} + +static void +ganv_module_remove(GanvItem* item, GanvItem* child) +{ + if (GANV_IS_PORT(child)) { + ganv_module_remove_port(GANV_MODULE(item), GANV_PORT(child)); + } + ganv_item_request_update(item); + if (GANV_ITEM_CLASS(parent_class)->remove) { + GANV_ITEM_CLASS(parent_class)->remove(item, child); + } +} + +static int +ptr_sort(const GanvPort** a, const GanvPort** b, const PortOrderCtx* ctx) +{ + return ctx->port_cmp(*a, *b, ctx->data); +} + +static void +ganv_module_update(GanvItem* item, int flags) +{ + GanvModule* module = GANV_MODULE(item); + GanvCanvas* canvas = ganv_item_get_canvas(item); + + if (module->impl->must_reorder) { + // Sort ports array + PortOrderCtx ctx = ganv_canvas_get_port_order(canvas); + if (ctx.port_cmp) { + g_ptr_array_sort_with_data(module->impl->ports, + (GCompareDataFunc)ptr_sort, + + &ctx); + } + module->impl->must_reorder = FALSE; + } + + if (module->impl->embed_item) { + // Kick the embedded item to update position if we have moved + ganv_item_move(GANV_ITEM(module->impl->embed_item), 0.0, 0.0); + } + + FOREACH_PORT(module->impl->ports, p) { + ganv_item_invoke_update(GANV_ITEM(*p), flags); + } + + if (module->impl->embed_item) { + ganv_item_invoke_update(GANV_ITEM(module->impl->embed_item), flags); + } + + GANV_ITEM_CLASS(parent_class)->update(item, flags); +} + +static void +ganv_module_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + GanvNode* node = GANV_NODE(item); + GanvModule* module = GANV_MODULE(item); + + // Draw box + if (GANV_ITEM_CLASS(parent_class)->draw) { + (*GANV_ITEM_CLASS(parent_class)->draw)(item, cr, cx, cy, cw, ch); + } + + // Draw label + if (node->impl->label) { + GanvItem* label_item = GANV_ITEM(node->impl->label); + GANV_ITEM_GET_CLASS(label_item)->draw(label_item, cr, cx, cy, cw, ch); + } + + // Draw ports + FOREACH_PORT(module->impl->ports, p) { + GANV_ITEM_GET_CLASS(GANV_ITEM(*p))->draw( + GANV_ITEM(*p), cr, cx, cy, cw, ch); + } + + // Draw embed item + if (module->impl->embed_item) { + GANV_ITEM_GET_CLASS(module->impl->embed_item)->draw( + module->impl->embed_item, cr, cx, cy, cw, ch); + } +} + +static void +ganv_module_move_to(GanvNode* node, + double x, + double y) +{ + GanvModule* module = GANV_MODULE(node); + GANV_NODE_CLASS(parent_class)->move_to(node, x, y); + FOREACH_PORT(module->impl->ports, p) { + ganv_node_move(GANV_NODE(*p), 0.0, 0.0); + } + if (module->impl->embed_item) { + ganv_item_move(GANV_ITEM(module->impl->embed_item), 0.0, 0.0); + } +} + +static void +ganv_module_move(GanvNode* node, + double dx, + double dy) +{ + GanvModule* module = GANV_MODULE(node); + GANV_NODE_CLASS(parent_class)->move(node, dx, dy); + FOREACH_PORT(module->impl->ports, p) { + ganv_node_move(GANV_NODE(*p), 0.0, 0.0); + } + if (module->impl->embed_item) { + ganv_item_move(GANV_ITEM(module->impl->embed_item), 0.0, 0.0); + } +} + +static double +ganv_module_point(GanvItem* item, double x, double y, GanvItem** actual_item) +{ + GanvModule* module = GANV_MODULE(item); + + double d = GANV_ITEM_CLASS(parent_class)->point(item, x, y, actual_item); + + if (!*actual_item) { + // Point is not inside module at all, no point in checking children + return d; + } + + FOREACH_PORT(module->impl->ports, p) { + GanvItem* const port = GANV_ITEM(*p); + + *actual_item = NULL; + d = GANV_ITEM_GET_CLASS(port)->point( + port, x - port->impl->x, y - port->impl->y, actual_item); + + if (*actual_item) { + // Point is inside a port + return d; + } + } + + // Point is inside module, but not a child port + *actual_item = item; + return 0.0; +} + +static void +ganv_module_class_init(GanvModuleClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GanvItemClass* item_class = (GanvItemClass*)klass; + GanvNodeClass* node_class = (GanvNodeClass*)klass; + + parent_class = GANV_BOX_CLASS(g_type_class_peek_parent(klass)); + + g_type_class_add_private(klass, sizeof(GanvModuleImpl)); + + gobject_class->set_property = ganv_module_set_property; + gobject_class->get_property = ganv_module_get_property; + + object_class->destroy = ganv_module_destroy; + + item_class->add = ganv_module_add; + item_class->remove = ganv_module_remove; + item_class->update = ganv_module_update; + item_class->draw = ganv_module_draw; + item_class->point = ganv_module_point; + + node_class->move = ganv_module_move; + node_class->move_to = ganv_module_move_to; + node_class->resize = ganv_module_resize; + node_class->redraw_text = ganv_module_redraw_text; +} + +GanvModule* +ganv_module_new(GanvCanvas* canvas, + const char* first_property_name, ...) +{ + GanvModule* module = GANV_MODULE( + g_object_new(ganv_module_get_type(), "canvas", canvas, NULL)); + + va_list args; + va_start(args, first_property_name); + g_object_set_valist(G_OBJECT(module), first_property_name, args); + va_end(args); + + return module; +} + +guint +ganv_module_num_ports(const GanvModule* module) +{ + return module->impl->ports ? module->impl->ports->len : 0; +} + +GanvPort* +ganv_module_get_port(GanvModule* module, + guint index) +{ + return (GanvPort*)g_ptr_array_index(module->impl->ports, index); +} + +double +ganv_module_get_empty_port_breadth(const GanvModule* module) +{ + return ganv_module_get_empty_port_depth(module) * 2.0; +} + +double +ganv_module_get_empty_port_depth(const GanvModule* module) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(module)); + + return ganv_canvas_get_font_size(canvas) * 1.1; +} + +static void +on_embed_size_request(GtkWidget* widget, + GtkRequisition* r, + void* user_data) +{ + GanvModule* module = GANV_MODULE(user_data); + GanvModuleImpl* impl = module->impl; + if (impl->embed_width == r->width && impl->embed_height == r->height) { + return; + } + + impl->embed_width = r->width; + impl->embed_height = r->height; + GANV_NODE(module)->impl->must_resize = TRUE; + + GtkAllocation allocation; + allocation.width = r->width; + allocation.height = r->width; + + gtk_widget_size_allocate(widget, &allocation); + ganv_item_set(impl->embed_item, + "width", (double)r->width, + "height", (double)r->height, + NULL); +} + +void +ganv_module_embed(GanvModule* module, + GtkWidget* widget) +{ + GanvModuleImpl* impl = module->impl; + if (!widget && !impl->embed_item) { + return; + } + + if (impl->embed_item) { + // Free existing embedded widget + gtk_object_destroy(GTK_OBJECT(impl->embed_item)); + impl->embed_item = NULL; + } + + if (!widget) { + // Removing an existing embedded widget + impl->embed_width = 0; + impl->embed_height = 0; + GANV_NODE(module)->impl->must_resize = TRUE; + ganv_item_request_update(GANV_ITEM(module)); + return; + } + + double title_w, title_h; + title_size(module, &title_w, &title_h); + + impl->embed_item = ganv_item_new( + GANV_ITEM(module), + ganv_widget_get_type(), + "x", 2.0, + "y", 4.0 + title_h, + "widget", widget, + NULL); + + GtkRequisition r; + gtk_widget_show_all(widget); + gtk_widget_size_request(widget, &r); + on_embed_size_request(widget, &r, module); + ganv_item_show(impl->embed_item); + + g_signal_connect(widget, "size-request", + G_CALLBACK(on_embed_size_request), module); + + GANV_NODE(module)->impl->must_resize = TRUE; + ganv_item_request_update(GANV_ITEM(module)); +} + +void +ganv_module_set_direction(GanvModule* module, + GanvDirection direction) +{ + FOREACH_PORT(module->impl->ports, p) { + ganv_port_set_direction(*p, direction); + } + GANV_NODE(module)->impl->must_resize = TRUE; + ganv_item_request_update(GANV_ITEM(module)); +} + +void +ganv_module_for_each_port(GanvModule* module, + GanvPortFunc f, + void* data) +{ + GanvModuleImpl* impl = module->impl; + const int len = impl->ports->len; + GanvPort** copy = (GanvPort**)malloc(sizeof(GanvPort*) * len); + memcpy(copy, impl->ports->pdata, sizeof(GanvPort*) * len); + + for (int i = 0; i < len; ++i) { + f(copy[i], data); + } + + free(copy); +} diff --git a/src/node.c b/src/node.c new file mode 100644 index 0000000..be7cc24 --- /dev/null +++ b/src/node.c @@ -0,0 +1,899 @@ +/* This file is part of Ganv. + * Copyright 2007-2016 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ganv/canvas.h" +#include "ganv/node.h" + +#include "./boilerplate.h" +#include "./color.h" +#include "./ganv-marshal.h" +#include "./ganv-private.h" +#include "./gettext.h" + +guint signal_moved; + +G_DEFINE_TYPE(GanvNode, ganv_node, GANV_TYPE_ITEM) + +static GanvItemClass* parent_class; + +enum { + PROP_0, + PROP_CANVAS, + PROP_PARTNER, + PROP_LABEL, + PROP_SHOW_LABEL, + PROP_DASH_LENGTH, + PROP_DASH_OFFSET, + PROP_BORDER_WIDTH, + PROP_FILL_COLOR, + PROP_BORDER_COLOR, + PROP_CAN_TAIL, + PROP_CAN_HEAD, + PROP_IS_SOURCE, + PROP_SELECTED, + PROP_HIGHLIGHTED, + PROP_DRAGGABLE, + PROP_GRABBED +}; + +static void +ganv_node_init(GanvNode* node) +{ + GanvNodeImpl* impl = G_TYPE_INSTANCE_GET_PRIVATE( + node, GANV_TYPE_NODE, GanvNodeImpl); + + node->impl = impl; + + impl->partner = NULL; + impl->label = NULL; + impl->dash_length = 0.0; + impl->dash_offset = 0.0; + impl->border_width = 2.0; + impl->fill_color = DEFAULT_FILL_COLOR; + impl->border_color = DEFAULT_BORDER_COLOR; + impl->can_tail = FALSE; + impl->can_head = FALSE; + impl->is_source = FALSE; + impl->selected = FALSE; + impl->highlighted = FALSE; + impl->draggable = FALSE; + impl->show_label = TRUE; + impl->grabbed = FALSE; + impl->must_resize = FALSE; +#ifdef GANV_FDGL + impl->force.x = 0.0; + impl->force.y = 0.0; + impl->vel.x = 0.0; + impl->vel.y = 0.0; + impl->connected = FALSE; +#endif +} + +static void +ganv_node_realize(GanvItem* item) +{ + GANV_ITEM_CLASS(parent_class)->realize(item); + ganv_canvas_add_node(ganv_item_get_canvas(item), GANV_NODE(item)); +} + +static void +ganv_node_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_NODE(object)); + + GanvNode* node = GANV_NODE(object); + GanvNodeImpl* impl = node->impl; + if (impl->label) { + g_object_unref(impl->label); + impl->label = NULL; + } + + GanvItem* item = GANV_ITEM(object); + ganv_node_disconnect(node); + if (item->impl->canvas) { + ganv_canvas_remove_node(item->impl->canvas, node); + } + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } + + impl->partner = NULL; + item->impl->canvas = NULL; +} + +static void +ganv_node_update(GanvItem* item, int flags) +{ + GanvNode* node = GANV_NODE(item); + if (node->impl->must_resize) { + ganv_node_resize(node); + node->impl->must_resize = FALSE; + } + + if (node->impl->label) { + ganv_item_invoke_update(GANV_ITEM(node->impl->label), flags); + } + + GANV_ITEM_CLASS(parent_class)->update(item, flags); +} + +static void +ganv_node_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + /* TODO: Label is not drawn here because ports need to draw control + rects then the label on top. I can't see a way of solving this since + there's no single time parent class draw needs to be called, so perhaps + label shouldn't be part of this class... */ +} + +static void +ganv_node_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_NODE(object)); + + GanvNode* node = GANV_NODE(object); + GanvNodeImpl* impl = node->impl; + + switch (prop_id) { + SET_CASE(DASH_LENGTH, double, impl->dash_length); + SET_CASE(DASH_OFFSET, double, impl->dash_offset); + SET_CASE(BORDER_WIDTH, double, impl->border_width); + SET_CASE(FILL_COLOR, uint, impl->fill_color); + SET_CASE(BORDER_COLOR, uint, impl->border_color); + SET_CASE(CAN_TAIL, boolean, impl->can_tail); + SET_CASE(CAN_HEAD, boolean, impl->can_head); + SET_CASE(IS_SOURCE, boolean, impl->is_source); + SET_CASE(HIGHLIGHTED, boolean, impl->highlighted); + SET_CASE(DRAGGABLE, boolean, impl->draggable); + SET_CASE(GRABBED, boolean, impl->grabbed); + case PROP_PARTNER: + impl->partner = (GanvNode*)g_value_get_object(value); + break; + case PROP_SELECTED: + if (impl->selected != g_value_get_boolean(value)) { + GanvItem* item = GANV_ITEM(object); + impl->selected = g_value_get_boolean(value); + if (item->impl->canvas) { + if (impl->selected) { + ganv_canvas_select_node(ganv_item_get_canvas(item), node); + } else { + ganv_canvas_unselect_node(ganv_item_get_canvas(item), node); + } + ganv_item_request_update(item); + } + } + break; + case PROP_CANVAS: + if (!GANV_ITEM(object)->impl->parent) { + GanvCanvas* canvas = GANV_CANVAS(g_value_get_object(value)); + g_object_set(object, "parent", ganv_canvas_root(canvas), NULL); + ganv_canvas_add_node(canvas, node); + } else { + g_warning("Cannot change `canvas' property after construction"); + } + break; + case PROP_LABEL: + ganv_node_set_label(node, g_value_get_string(value)); + break; + case PROP_SHOW_LABEL: + ganv_node_set_show_label(node, g_value_get_boolean(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_node_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_NODE(object)); + + GanvNode* node = GANV_NODE(object); + GanvNodeImpl* impl = node->impl; + + switch (prop_id) { + GET_CASE(PARTNER, object, impl->partner); + GET_CASE(LABEL, string, impl->label ? impl->label->impl->text : NULL); + GET_CASE(DASH_LENGTH, double, impl->dash_length); + GET_CASE(DASH_OFFSET, double, impl->dash_offset); + GET_CASE(BORDER_WIDTH, double, impl->border_width); + GET_CASE(FILL_COLOR, uint, impl->fill_color); + GET_CASE(BORDER_COLOR, uint, impl->border_color); + GET_CASE(CAN_TAIL, boolean, impl->can_tail); + GET_CASE(CAN_HEAD, boolean, impl->can_head); + GET_CASE(IS_SOURCE, boolean, impl->is_source); + GET_CASE(SELECTED, boolean, impl->selected); + GET_CASE(HIGHLIGHTED, boolean, impl->highlighted); + GET_CASE(DRAGGABLE, boolean, impl->draggable); + GET_CASE(GRABBED, boolean, impl->grabbed); + case PROP_CANVAS: + g_value_set_object(value, ganv_item_get_canvas(GANV_ITEM(object))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_node_default_tail_vector(const GanvNode* self, + const GanvNode* head, + double* x, + double* y, + double* dx, + double* dy) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(self)); + + *x = GANV_ITEM(self)->impl->x; + *y = GANV_ITEM(self)->impl->y; + + switch (ganv_canvas_get_direction(canvas)) { + case GANV_DIRECTION_RIGHT: + *dx = 1.0; + *dy = 0.0; + break; + case GANV_DIRECTION_DOWN: + *dx = 0.0; + *dy = 1.0; + break; + } + + ganv_item_i2w(GANV_ITEM(self)->impl->parent, x, y); +} + +static void +ganv_node_default_head_vector(const GanvNode* self, + const GanvNode* tail, + double* x, + double* y, + double* dx, + double* dy) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(self)); + + *x = GANV_ITEM(self)->impl->x; + *y = GANV_ITEM(self)->impl->y; + + switch (ganv_canvas_get_direction(canvas)) { + case GANV_DIRECTION_RIGHT: + *dx = -1.0; + *dy = 0.0; + break; + case GANV_DIRECTION_DOWN: + *dx = 0.0; + *dy = -1.0; + break; + } + + ganv_item_i2w(GANV_ITEM(self)->impl->parent, x, y); +} + +void +ganv_node_get_draw_properties(const GanvNode* node, + double* dash_length, + double* border_color, + double* fill_color) +{ + GanvNodeImpl* impl = node->impl; + + *dash_length = impl->dash_length; + *border_color = impl->border_color; + *fill_color = impl->fill_color; + + if (impl->selected) { + *dash_length = 4.0; + *border_color = highlight_color(impl->border_color, 0x40); + } + + if (impl->highlighted) { + *border_color = highlight_color(impl->border_color, 0x40); + *fill_color = impl->fill_color; + } +} + +void +ganv_node_set_label(GanvNode* node, const char* str) +{ + GanvNodeImpl* impl = node->impl; + if (!str || str[0] == '\0') { + if (impl->label) { + gtk_object_destroy(GTK_OBJECT(impl->label)); + impl->label = NULL; + } + } else if (impl->label) { + ganv_item_set(GANV_ITEM(impl->label), + "text", str, + NULL); + } else { + impl->label = GANV_TEXT(ganv_item_new(GANV_ITEM(node), + ganv_text_get_type(), + "text", str, + "color", DEFAULT_TEXT_COLOR, + "managed", TRUE, + NULL)); + } + + impl->must_resize = TRUE; + ganv_item_request_update(GANV_ITEM(node)); +} + +void +ganv_node_set_show_label(GanvNode* node, gboolean show) +{ + if (node->impl->label) { + if (show) { + ganv_item_show(GANV_ITEM(node->impl->label)); + } else { + ganv_item_hide(GANV_ITEM(node->impl->label)); + } + } + node->impl->show_label = show; + ganv_item_request_update(GANV_ITEM(node)); +} + +static void +ganv_node_default_tick(GanvNode* self, + double seconds) +{ + GanvNode* node = GANV_NODE(self); + node->impl->dash_offset = seconds * 8.0; + ganv_item_request_update(GANV_ITEM(self)); +} + +static void +ganv_node_default_disconnect(GanvNode* node) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(node)); + if (canvas) { + ganv_canvas_for_each_edge_on( + canvas, node, (GanvEdgeFunc)ganv_edge_disconnect, NULL); + } +} + +static void +ganv_node_default_move(GanvNode* node, + double dx, + double dy) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(node)); + ganv_item_move(GANV_ITEM(node), dx, dy); + ganv_canvas_for_each_edge_on( + canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL); + ganv_item_request_update(GANV_ITEM(node)); +} + +static void +ganv_node_default_move_to(GanvNode* node, + double x, + double y) +{ + GanvItem* item = GANV_ITEM(node); + GanvCanvas* canvas = ganv_item_get_canvas(item); + item->impl->x = x; + item->impl->y = y; + if (node->impl->can_tail) { + ganv_canvas_for_each_edge_from( + canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL); + } else if (node->impl->can_head) { + ganv_canvas_for_each_edge_to( + canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL); + } + ganv_item_request_update(GANV_ITEM(node)); +} + +static void +ganv_node_default_resize(GanvNode* node) +{ + GanvItem* item = GANV_ITEM(node); + if (GANV_IS_NODE(item->impl->parent)) { + ganv_node_resize(GANV_NODE(item->impl->parent)); + } + node->impl->must_resize = FALSE; +} + +static void +ganv_node_default_redraw_text(GanvNode* node) +{ + if (node->impl->label) { + ganv_text_layout(node->impl->label); + node->impl->must_resize = TRUE; + ganv_item_request_update(GANV_ITEM(node)); + } +} + +static gboolean +ganv_node_default_event(GanvItem* item, + GdkEvent* event) +{ + GanvNode* node = GANV_NODE(item); + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(node)); + + // FIXME: put these somewhere better + static double last_x, last_y; + static double drag_start_x, drag_start_y; + static gboolean dragging = FALSE; + + switch (event->type) { + case GDK_ENTER_NOTIFY: + ganv_item_raise(GANV_ITEM(node)); + node->impl->highlighted = TRUE; + ganv_item_request_update(item); + return TRUE; + + case GDK_LEAVE_NOTIFY: + ganv_item_lower(GANV_ITEM(node)); + node->impl->highlighted = FALSE; + ganv_item_request_update(item); + return TRUE; + + case GDK_BUTTON_PRESS: + drag_start_x = event->button.x; + drag_start_y = event->button.y; + last_x = event->button.x; + last_y = event->button.y; + if (!ganv_canvas_get_locked(canvas) && node->impl->draggable && event->button.button == 1) { + ganv_canvas_grab_item( + GANV_ITEM(node), + GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK|GDK_BUTTON_PRESS_MASK, + ganv_canvas_get_move_cursor(canvas), + event->button.time); + node->impl->grabbed = TRUE; + dragging = TRUE; + return TRUE; + } + break; + + case GDK_BUTTON_RELEASE: + if (dragging) { + gboolean selected; + g_object_get(G_OBJECT(node), "selected", &selected, NULL); + ganv_canvas_ungrab_item(GANV_ITEM(node), event->button.time); + node->impl->grabbed = FALSE; + dragging = FALSE; + if (event->button.x != drag_start_x || event->button.y != drag_start_y) { + ganv_canvas_contents_changed(canvas); + if (selected) { + ganv_canvas_selection_move_finished(canvas); + } else { + const double x = GANV_ITEM(node)->impl->x; + const double y = GANV_ITEM(node)->impl->y; + g_signal_emit(node, signal_moved, 0, x, y, NULL); + } + } else { + // Clicked + if (selected) { + ganv_canvas_unselect_node(canvas, node); + } else { + if (!(event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { + ganv_canvas_clear_selection(canvas); + } + ganv_canvas_select_node(canvas, node); + } + } + return TRUE; + } + break; + + case GDK_MOTION_NOTIFY: + if ((dragging && (event->motion.state & GDK_BUTTON1_MASK))) { + gboolean selected; + g_object_get(G_OBJECT(node), "selected", &selected, NULL); + + double new_x = event->motion.x; + double new_y = event->motion.y; + + if (event->motion.is_hint) { + int t_x; + int t_y; + GdkModifierType state; + gdk_window_get_pointer(event->motion.window, &t_x, &t_y, &state); + new_x = t_x; + new_y = t_y; + } + + const double dx = new_x - last_x; + const double dy = new_y - last_y; + if (selected) { + ganv_canvas_move_selected_items(canvas, dx, dy); + } else { + ganv_node_move(node, dx, dy); + } + + last_x = new_x; + last_y = new_y; + return TRUE; + } + + default: + break; + } + + return FALSE; +} + +static void +ganv_node_class_init(GanvNodeClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GanvItemClass* item_class = (GanvItemClass*)klass; + + parent_class = GANV_ITEM_CLASS(g_type_class_peek_parent(klass)); + + g_type_class_add_private(klass, sizeof(GanvNodeImpl)); + + gobject_class->set_property = ganv_node_set_property; + gobject_class->get_property = ganv_node_get_property; + + g_object_class_install_property( + gobject_class, PROP_CANVAS, g_param_spec_object( + "canvas", + _("Canvas"), + _("The canvas this node is on."), + GANV_TYPE_CANVAS, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_PARTNER, g_param_spec_object( + "partner", + _("Partner"), + _("Partners are nodes that should be visually aligned to correspond" + " to each other, even if they are not necessarily connected (e.g." + " for separate modules representing the inputs and outputs of a" + " single thing). When the canvas is arranged, the partner will" + " be aligned as if there was an edge from this node to its" + " partner."), + GANV_TYPE_NODE, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_LABEL, g_param_spec_string( + "label", + _("Label"), + _("The text to display as a label on this node."), + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_SHOW_LABEL, g_param_spec_boolean( + "show-label", + _("Show label"), + _("Whether or not to show the label."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_DASH_LENGTH, g_param_spec_double( + "dash-length", + _("Border dash length"), + _("Length of border dashes, or zero for no dashing."), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_DASH_OFFSET, g_param_spec_double( + "dash-offset", + _("Border dash offset"), + _("Start offset for border dashes, used for selected animation."), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_BORDER_WIDTH, g_param_spec_double( + "border-width", + _("Border width"), + _("Width of the border line."), + 0.0, G_MAXDOUBLE, + 2.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_FILL_COLOR, g_param_spec_uint( + "fill-color", + _("Fill color"), + _("Color of internal area."), + 0, G_MAXUINT, + DEFAULT_FILL_COLOR, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_BORDER_COLOR, g_param_spec_uint( + "border-color", + _("Border color"), + _("Color of border line."), + 0, G_MAXUINT, + DEFAULT_BORDER_COLOR, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_CAN_TAIL, g_param_spec_boolean( + "can-tail", + _("Can tail"), + _("Whether this node can be the tail of an edge."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_CAN_HEAD, g_param_spec_boolean( + "can-head", + _("Can head"), + _("Whether this object can be the head of an edge."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_IS_SOURCE, g_param_spec_boolean( + "is-source", + _("Is source"), + _("Whether this object should be positioned at the start of signal flow."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_SELECTED, g_param_spec_boolean( + "selected", + _("Selected"), + _("Whether this object is selected."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_HIGHLIGHTED, g_param_spec_boolean( + "highlighted", + _("Highlighted"), + _("Whether this object is highlighted."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_DRAGGABLE, g_param_spec_boolean( + "draggable", + _("Draggable"), + _("Whether this object is draggable."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_GRABBED, g_param_spec_boolean( + "grabbed", + _("Grabbed"), + _("Whether this object is grabbed by the user."), + 0, + G_PARAM_READWRITE)); + + signal_moved = g_signal_new("moved", + ganv_node_get_type(), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + ganv_marshal_VOID__DOUBLE_DOUBLE, + G_TYPE_NONE, + 2, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + 0); + + object_class->destroy = ganv_node_destroy; + + item_class->realize = ganv_node_realize; + item_class->event = ganv_node_default_event; + item_class->update = ganv_node_update; + item_class->draw = ganv_node_draw; + + klass->disconnect = ganv_node_default_disconnect; + klass->move = ganv_node_default_move; + klass->move_to = ganv_node_default_move_to; + klass->resize = ganv_node_default_resize; + klass->redraw_text = ganv_node_default_redraw_text; + klass->tick = ganv_node_default_tick; + klass->tail_vector = ganv_node_default_tail_vector; + klass->head_vector = ganv_node_default_head_vector; +} + +gboolean +ganv_node_can_tail(const GanvNode* self) +{ + return self->impl->can_tail; +} + +gboolean +ganv_node_can_head(const GanvNode* self) +{ + return self->impl->can_head; +} + +void +ganv_node_set_is_source(const GanvNode* node, gboolean is_source) +{ + node->impl->is_source = is_source; +} + +gboolean +ganv_node_is_within(const GanvNode* node, + double x1, + double y1, + double x2, + double y2) +{ + return GANV_NODE_GET_CLASS(node)->is_within(node, x1, y1, x2, y2); +} + +void +ganv_node_tick(GanvNode* node, + double seconds) +{ + GanvNodeClass* klass = GANV_NODE_GET_CLASS(node); + if (klass->tick) { + klass->tick(node, seconds); + } +} + +void +ganv_node_tail_vector(const GanvNode* self, + const GanvNode* head, + double* x1, + double* y1, + double* x2, + double* y2) +{ + GANV_NODE_GET_CLASS(self)->tail_vector( + self, head, x1, y1, x2, y2); +} + +void +ganv_node_head_vector(const GanvNode* self, + const GanvNode* tail, + double* x1, + double* y1, + double* x2, + double* y2) +{ + GANV_NODE_GET_CLASS(self)->head_vector( + self, tail, x1, y1, x2, y2); +} + +const char* +ganv_node_get_label(const GanvNode* node) +{ + return node->impl->label ? node->impl->label->impl->text : NULL; +} + +double +ganv_node_get_border_width(const GanvNode* node) +{ + return node->impl->border_width; +} + +void +ganv_node_set_border_width(const GanvNode* node, double border_width) +{ + node->impl->border_width = border_width; + ganv_item_request_update(GANV_ITEM(node)); +} + +double +ganv_node_get_dash_length(const GanvNode* node) +{ + return node->impl->dash_length; +} + +void +ganv_node_set_dash_length(const GanvNode* node, double dash_length) +{ + node->impl->dash_length = dash_length; + ganv_item_request_update(GANV_ITEM(node)); +} + +double +ganv_node_get_dash_offset(const GanvNode* node) +{ + return node->impl->dash_offset; +} + +void +ganv_node_set_dash_offset(const GanvNode* node, double dash_offset) +{ + node->impl->dash_offset = dash_offset; + ganv_item_request_update(GANV_ITEM(node)); +} + +guint +ganv_node_get_fill_color(const GanvNode* node) +{ + return node->impl->fill_color; +} + +void +ganv_node_set_fill_color(const GanvNode* node, guint fill_color) +{ + node->impl->fill_color = fill_color; + ganv_item_request_update(GANV_ITEM(node)); +} + +guint +ganv_node_get_border_color(const GanvNode* node) +{ + return node->impl->border_color; +} + +void +ganv_node_set_border_color(const GanvNode* node, guint border_color) +{ + node->impl->border_color = border_color; + ganv_item_request_update(GANV_ITEM(node)); +} + +GanvNode* +ganv_node_get_partner(const GanvNode* node) +{ + return node->impl->partner; +} + +void +ganv_node_move(GanvNode* node, + double dx, + double dy) +{ + GANV_NODE_GET_CLASS(node)->move(node, dx, dy); +} + +void +ganv_node_move_to(GanvNode* node, + double x, + double y) +{ + GANV_NODE_GET_CLASS(node)->move_to(node, x, y); +} + +void +ganv_node_resize(GanvNode* node) +{ + GANV_NODE_GET_CLASS(node)->resize(node); + node->impl->must_resize = FALSE; +} + +void +ganv_node_redraw_text(GanvNode* node) +{ + GANV_NODE_GET_CLASS(node)->redraw_text(node); +} + +void +ganv_node_disconnect(GanvNode* node) +{ + GANV_NODE_GET_CLASS(node)->disconnect(node); +} + +gboolean +ganv_node_is_selected(GanvNode* node) +{ + gboolean selected = FALSE; + g_object_get(node, "selected", &selected, NULL); + return selected; +} diff --git a/src/port.c b/src/port.c new file mode 100644 index 0000000..4e11b18 --- /dev/null +++ b/src/port.c @@ -0,0 +1,737 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <stdlib.h> + +#include "ganv/canvas.h" +#include "ganv/port.h" +#include "ganv/module.h" + +#include "./boilerplate.h" +#include "./color.h" +#include "./ganv-private.h" +#include "./gettext.h" + +static const double PORT_LABEL_HPAD = 4.0; +static const double PORT_LABEL_VPAD = 1.0; + +static void +ganv_port_update_control_slider(GanvPort* port, float value, gboolean force); + +G_DEFINE_TYPE(GanvPort, ganv_port, GANV_TYPE_BOX) + +static GanvBoxClass* parent_class; + +enum { + PROP_0, + PROP_IS_INPUT, + PROP_IS_CONTROLLABLE +}; + +enum { + PORT_VALUE_CHANGED, + PORT_LAST_SIGNAL +}; + +static guint port_signals[PORT_LAST_SIGNAL]; + +static void +ganv_port_init(GanvPort* port) +{ + port->impl = G_TYPE_INSTANCE_GET_PRIVATE( + port, GANV_TYPE_PORT, GanvPortImpl); + + port->impl->control = NULL; + port->impl->value_label = NULL; + port->impl->is_input = TRUE; + port->impl->is_controllable = FALSE; +} + +static void +ganv_port_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_PORT(object)); + + GanvItem* item = GANV_ITEM(object); + GanvPort* port = GANV_PORT(object); + GanvCanvas* canvas = ganv_item_get_canvas(item); + if (canvas) { + if (port->impl->is_input) { + ganv_canvas_for_each_edge_to( + canvas, &port->box.node, (GanvEdgeFunc)ganv_edge_remove, NULL); + } else { + ganv_canvas_for_each_edge_from( + canvas, &port->box.node, (GanvEdgeFunc)ganv_edge_remove, NULL); + } + } + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +static void +ganv_port_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_PORT(object)); + + GanvPort* port = GANV_PORT(object); + + switch (prop_id) { + SET_CASE(IS_INPUT, boolean, port->impl->is_input); + SET_CASE(IS_CONTROLLABLE, boolean, port->impl->is_controllable); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_port_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_PORT(object)); + + GanvPort* port = GANV_PORT(object); + + switch (prop_id) { + GET_CASE(IS_INPUT, boolean, port->impl->is_input); + GET_CASE(IS_CONTROLLABLE, boolean, port->impl->is_controllable); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_port_update(GanvItem* item, int flags) +{ + GanvPort* port = GANV_PORT(item); + GanvPortImpl* impl = port->impl; + + if (impl->control) { + ganv_item_invoke_update(GANV_ITEM(impl->control->rect), flags); + } + + if (impl->value_label) { + ganv_item_invoke_update(GANV_ITEM(port->impl->value_label), flags); + } + + GanvItemClass* item_class = GANV_ITEM_CLASS(parent_class); + item_class->update(item, flags); +} + +static void +ganv_port_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + GanvPort* port = GANV_PORT(item); + GanvCanvas* canvas = ganv_item_get_canvas(item); + + // Draw Box + GanvItemClass* item_class = GANV_ITEM_CLASS(parent_class); + item_class->draw(item, cr, cx, cy, cw, ch); + + if (port->impl->control) { + // Clip to port boundaries (to stay within radiused borders) + cairo_save(cr); + const double pad = GANV_NODE(port)->impl->border_width / 2.0; + GanvBoxCoords coords = GANV_BOX(port)->impl->coords; + ganv_item_i2w_pair(GANV_ITEM(port), + &coords.x1, &coords.y1, &coords.x2, &coords.y2); + ganv_box_path(GANV_BOX(port), cr, + coords.x1 + pad, coords.y1 + pad, + coords.x2 - pad, coords.y2 - pad, + -pad); + cairo_clip(cr); + + GanvItem* const rect = GANV_ITEM(port->impl->control->rect); + GANV_ITEM_GET_CLASS(rect)->draw(rect, cr, cx, cy, cw, ch); + + cairo_restore(cr); + } + + if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_DOWN || + !GANV_NODE(port)->impl->show_label) { + return; + } + + GanvItem* labels[2] = { + GANV_ITEM(GANV_NODE(item)->impl->label), + port->impl->value_label ? GANV_ITEM(port->impl->value_label) : NULL + }; + for (int i = 0; i < 2; ++i) { + if (labels[i] && (labels[i]->object.flags & GANV_ITEM_VISIBLE)) { + GANV_ITEM_GET_CLASS(labels[i])->draw( + labels[i], cr, cx, cy, cw, ch); + } + } +} + +static void +ganv_port_tail_vector(const GanvNode* self, + const GanvNode* head, + double* x, + double* y, + double* dx, + double* dy) +{ + GanvPort* port = GANV_PORT(self); + GanvItem* item = &port->box.node.item; + GanvCanvas* canvas = ganv_item_get_canvas(item); + + const double px = item->impl->x; + const double py = item->impl->y; + const double border_width = GANV_NODE(port)->impl->border_width; + + switch (ganv_canvas_get_direction(canvas)) { + case GANV_DIRECTION_RIGHT: + *x = px + ganv_box_get_width(&port->box) + (border_width / 2.0); + *y = py + ganv_box_get_height(&port->box) / 2.0; + *dx = 1.0; + *dy = 0.0; + break; + case GANV_DIRECTION_DOWN: + *x = px + ganv_box_get_width(&port->box) / 2.0; + *y = py + ganv_box_get_height(&port->box) + (border_width / 2.0); + *dx = 0.0; + *dy = 1.0; + break; + } + + ganv_item_i2w(item->impl->parent, x, y); +} + +static void +ganv_port_head_vector(const GanvNode* self, + const GanvNode* tail, + double* x, + double* y, + double* dx, + double* dy) +{ + GanvPort* port = GANV_PORT(self); + GanvItem* item = &port->box.node.item; + GanvCanvas* canvas = ganv_item_get_canvas(item); + + const double px = item->impl->x; + const double py = item->impl->y; + const double border_width = GANV_NODE(port)->impl->border_width; + + switch (ganv_canvas_get_direction(canvas)) { + case GANV_DIRECTION_RIGHT: + *x = px - (border_width / 2.0); + *y = py + ganv_box_get_height(&port->box) / 2.0; + *dx = -1.0; + *dy = 0.0; + break; + case GANV_DIRECTION_DOWN: + *x = px + ganv_box_get_width(&port->box) / 2.0; + *y = py - (border_width / 2.0); + *dx = 0.0; + *dy = -1.0; + break; + } + + ganv_item_i2w(item->impl->parent, x, y); +} + +static void +ganv_port_place_labels(GanvPort* port) +{ + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(port)); + GanvPortImpl* impl = port->impl; + GanvText* label = GANV_NODE(port)->impl->label; + const double port_w = ganv_box_get_width(&port->box); + const double port_h = ganv_box_get_height(&port->box); + double vlabel_w = 0.0; + if (impl->value_label) { + const double vlabel_h = impl->value_label->impl->coords.height; + vlabel_w = impl->value_label->impl->coords.width; + if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_RIGHT) { + ganv_item_set(GANV_ITEM(impl->value_label), + "x", PORT_LABEL_HPAD, + "y", (port_h - vlabel_h) / 2.0 - PORT_LABEL_VPAD, + NULL); + } else { + ganv_item_set(GANV_ITEM(impl->value_label), + "x", (port_w - vlabel_w) / 2.0, + "y", (port_h - vlabel_h) / 2.0 - PORT_LABEL_VPAD, + NULL); + } + vlabel_w += PORT_LABEL_HPAD; + } + if (label) { + const double label_h = label->impl->coords.height; + if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_RIGHT) { + ganv_item_set(GANV_ITEM(label), + "x", vlabel_w + PORT_LABEL_HPAD, + "y", (port_h - label_h) / 2.0 - PORT_LABEL_VPAD, + NULL); + } + } +} + +static void +ganv_port_resize(GanvNode* self) +{ + GanvPort* port = GANV_PORT(self); + GanvNode* node = GANV_NODE(self); + GanvText* label = node->impl->label; + GanvText* vlabel = port->impl->value_label; + + double label_w = 0.0; + double label_h = 0.0; + double vlabel_w = 0.0; + double vlabel_h = 0.0; + if (label && (GANV_ITEM(label)->object.flags & GANV_ITEM_VISIBLE)) { + g_object_get(label, "width", &label_w, "height", &label_h, NULL); + } + if (vlabel && (GANV_ITEM(vlabel)->object.flags & GANV_ITEM_VISIBLE)) { + g_object_get(vlabel, "width", &vlabel_w, "height", &vlabel_h, NULL); + } + + if (label || vlabel) { + double labels_w = label_w + PORT_LABEL_HPAD * 2.0; + if (vlabel_w != 0.0) { + labels_w += vlabel_w + PORT_LABEL_HPAD; + } + ganv_box_set_width(&port->box, labels_w); + ganv_box_set_height(&port->box, + MAX(label_h, vlabel_h) + (PORT_LABEL_VPAD * 2.0)); + + ganv_port_place_labels(port); + } + + if (GANV_NODE_CLASS(parent_class)->resize) { + GANV_NODE_CLASS(parent_class)->resize(self); + } +} + +static void +ganv_port_redraw_text(GanvNode* node) +{ + GanvPort* port = GANV_PORT(node); + if (port->impl->value_label) { + ganv_text_layout(port->impl->value_label); + } + if (GANV_NODE_CLASS(parent_class)->redraw_text) { + (*GANV_NODE_CLASS(parent_class)->redraw_text)(node); + } + ganv_port_place_labels(port); +} + +static void +ganv_port_set_width(GanvBox* box, + double width) +{ + GanvPort* port = GANV_PORT(box); + parent_class->set_width(box, width); + if (port->impl->control) { + ganv_port_update_control_slider(port, port->impl->control->value, TRUE); + } + ganv_port_place_labels(port); +} + +static void +ganv_port_set_height(GanvBox* box, + double height) +{ + GanvPort* port = GANV_PORT(box); + parent_class->set_height(box, height); + if (port->impl->control) { + ganv_item_set(GANV_ITEM(port->impl->control->rect), + "y1", box->impl->coords.border_width / 2.0, + "y2", height - box->impl->coords.border_width / 2.0, + NULL); + } + ganv_port_place_labels(port); +} + +static gboolean +ganv_port_event(GanvItem* item, GdkEvent* event) +{ + GanvCanvas* canvas = ganv_item_get_canvas(item); + + return ganv_canvas_port_event(canvas, GANV_PORT(item), event); +} + +static void +ganv_port_class_init(GanvPortClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GanvItemClass* item_class = (GanvItemClass*)klass; + GanvNodeClass* node_class = (GanvNodeClass*)klass; + GanvBoxClass* box_class = (GanvBoxClass*)klass; + + parent_class = GANV_BOX_CLASS(g_type_class_peek_parent(klass)); + + g_type_class_add_private(klass, sizeof(GanvPortImpl)); + + gobject_class->set_property = ganv_port_set_property; + gobject_class->get_property = ganv_port_get_property; + + g_object_class_install_property( + gobject_class, PROP_IS_INPUT, g_param_spec_boolean( + "is-input", + _("Is input"), + _("Whether this port is an input, rather than an output."), + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_IS_CONTROLLABLE, g_param_spec_boolean( + "is-controllable", + _("Is controllable"), + _("Whether this port can be controlled by the user."), + 0, + G_PARAM_READWRITE)); + + port_signals[PORT_VALUE_CHANGED] + = g_signal_new("value-changed", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + G_TYPE_DOUBLE); + + object_class->destroy = ganv_port_destroy; + + item_class->update = ganv_port_update; + item_class->event = ganv_port_event; + item_class->draw = ganv_port_draw; + + node_class->tail_vector = ganv_port_tail_vector; + node_class->head_vector = ganv_port_head_vector; + node_class->resize = ganv_port_resize; + node_class->redraw_text = ganv_port_redraw_text; + + box_class->set_width = ganv_port_set_width; + box_class->set_height = ganv_port_set_height; +} + +GanvPort* +ganv_port_new(GanvModule* module, + gboolean is_input, + const char* first_prop_name, ...) +{ + GanvPort* port = GANV_PORT(g_object_new(ganv_port_get_type(), NULL)); + + port->impl->is_input = is_input; + + GanvItem* item = GANV_ITEM(port); + va_list args; + va_start(args, first_prop_name); + ganv_item_construct(item, + GANV_ITEM(module), + first_prop_name, args); + va_end(args); + + GanvBox* box = GANV_BOX(port); + box->impl->coords.border_width = 1.0; + + GanvNode* node = GANV_NODE(port); + node->impl->can_tail = !is_input; + node->impl->can_head = is_input; + node->impl->draggable = FALSE; + node->impl->border_width = 2.0; + + GanvCanvas* canvas = ganv_item_get_canvas(GANV_ITEM(port)); + ganv_port_set_direction(port, ganv_canvas_get_direction(canvas)); + + return port; +} + +void +ganv_port_set_direction(GanvPort* port, + GanvDirection direction) +{ + GanvNode* node = GANV_NODE(port); + GanvBox* box = GANV_BOX(port); + gboolean is_input = port->impl->is_input; + switch (direction) { + case GANV_DIRECTION_RIGHT: + box->impl->radius_tl = (is_input ? 0.0 : 5.0); + box->impl->radius_tr = (is_input ? 5.0 : 0.0); + box->impl->radius_br = (is_input ? 5.0 : 0.0); + box->impl->radius_bl = (is_input ? 0.0 : 5.0); + break; + case GANV_DIRECTION_DOWN: + box->impl->radius_tl = (is_input ? 0.0 : 5.0); + box->impl->radius_tr = (is_input ? 0.0 : 5.0); + box->impl->radius_br = (is_input ? 5.0 : 0.0); + box->impl->radius_bl = (is_input ? 5.0 : 0.0); + break; + } + + node->impl->must_resize = TRUE; + ganv_item_request_update(GANV_ITEM(node)); +} + +void +ganv_port_show_control(GanvPort* port) +{ + if (port->impl->control) { + return; + } + + const guint color = 0xFFFFFF66; + const double border_width = GANV_NODE(port)->impl->border_width; + + GanvPortControl* control = (GanvPortControl*)malloc(sizeof(GanvPortControl)); + port->impl->control = control; + + control->value = 0.0f; + control->min = 0.0f; + control->max = 1.0f; + control->is_toggle = FALSE; + control->is_integer = FALSE; + control->rect = GANV_BOX( + ganv_item_new(GANV_ITEM(port), + ganv_box_get_type(), + "x1", border_width / 2.0, + "y1", border_width / 2.0, + "x2", 0.0, + "y2", ganv_box_get_height(&port->box) - border_width / 2.0, + "fill-color", color, + "border-color", color, + "border-width", 0.0, + "managed", TRUE, + NULL)); + ganv_item_show(GANV_ITEM(control->rect)); +} + +void +ganv_port_hide_control(GanvPort* port) +{ + gtk_object_destroy(GTK_OBJECT(port->impl->control->rect)); + free(port->impl->control); + port->impl->control = NULL; +} + +void +ganv_port_set_value_label(GanvPort* port, + const char* str) +{ + GanvPortImpl* impl = port->impl; + + if (!str || str[0] == '\0') { + if (impl->value_label) { + gtk_object_destroy(GTK_OBJECT(impl->value_label)); + impl->value_label = NULL; + } + } else if (impl->value_label) { + ganv_item_set(GANV_ITEM(impl->value_label), + "text", str, + NULL); + } else { + impl->value_label = GANV_TEXT(ganv_item_new(GANV_ITEM(port), + ganv_text_get_type(), + "text", str, + "color", DIM_TEXT_COLOR, + "managed", TRUE, + NULL)); + } +} + +static void +ganv_port_update_control_slider(GanvPort* port, float value, gboolean force) +{ + GanvPortImpl* impl = port->impl; + if (!impl->control) { + return; + } + + // Clamp to toggle or integer value if applicable + if (impl->control->is_toggle) { + if (value != 0.0f) { + value = impl->control->max; + } else { + value = impl->control->min; + } + } else if (impl->control->is_integer) { + value = lrintf(value); + } + + // Clamp to range + if (value < impl->control->min) { + value = impl->control->min; + } + if (value > impl->control->max) { + value = impl->control->max; + } + + if (!force && value == impl->control->value) { + return; // No change, do nothing + } + + const double span = (ganv_box_get_width(&port->box) - + GANV_NODE(port)->impl->border_width); + + const double w = (value - impl->control->min) + / (impl->control->max - impl->control->min) + * span; + + if (isnan(w)) { + return; // Shouldn't happen, but ignore crazy values + } + + // Redraw port + impl->control->value = value; + ganv_box_set_width(impl->control->rect, MAX(0.0, w)); + ganv_box_request_redraw( + GANV_ITEM(port), &GANV_BOX(port)->impl->coords, FALSE); +} + +void +ganv_port_set_control_is_toggle(GanvPort* port, + gboolean is_toggle) +{ + if (port->impl->control) { + port->impl->control->is_toggle = is_toggle; + ganv_port_update_control_slider(port, port->impl->control->value, TRUE); + } +} + +void +ganv_port_set_control_is_integer(GanvPort* port, + gboolean is_integer) +{ + if (port->impl->control) { + port->impl->control->is_integer = is_integer; + const float rounded = rintf(port->impl->control->value); + ganv_port_update_control_slider(port, rounded, TRUE); + } +} + +void +ganv_port_set_control_value(GanvPort* port, + float value) +{ + ganv_port_update_control_slider(port, value, FALSE); +} + +void +ganv_port_set_control_value_internal(GanvPort* port, + float value) +{ + // Update slider + ganv_port_set_control_value(port, value); + + // Fire signal to notify user value has changed + const double dvalue = port->impl->control->value; + g_signal_emit(port, port_signals[PORT_VALUE_CHANGED], 0, dvalue, NULL); +} + +void +ganv_port_set_control_min(GanvPort* port, + float min) +{ + if (port->impl->control) { + const gboolean force = port->impl->control->min != min; + port->impl->control->min = min; + if (port->impl->control->max < min) { + port->impl->control->max = min; + } + ganv_port_update_control_slider(port, port->impl->control->value, force); + } +} + +void +ganv_port_set_control_max(GanvPort* port, + float max) +{ + if (port->impl->control) { + const gboolean force = port->impl->control->max != max; + port->impl->control->max = max; + if (port->impl->control->min > max) { + port->impl->control->min = max; + } + ganv_port_update_control_slider(port, port->impl->control->value, force); + } +} + +double +ganv_port_get_natural_width(const GanvPort* port) +{ + GanvCanvas* const canvas = ganv_item_get_canvas(GANV_ITEM(port)); + GanvText* const label = port->box.node.impl->label; + double w = 0.0; + if (ganv_canvas_get_direction(canvas) == GANV_DIRECTION_DOWN) { + w = ganv_module_get_empty_port_breadth(ganv_port_get_module(port)); + } else if (label && (GANV_ITEM(label)->object.flags & GANV_ITEM_VISIBLE)) { + double label_w; + g_object_get(port->box.node.impl->label, "width", &label_w, NULL); + w = label_w + (PORT_LABEL_HPAD * 2.0); + } else { + w = ganv_module_get_empty_port_depth(ganv_port_get_module(port)); + } + if (port->impl->value_label && + (GANV_ITEM(port->impl->value_label)->object.flags + & GANV_ITEM_VISIBLE)) { + double label_w; + g_object_get(port->impl->value_label, "width", &label_w, NULL); + w += label_w + PORT_LABEL_HPAD; + } + return w; +} + +GanvModule* +ganv_port_get_module(const GanvPort* port) +{ + return GANV_MODULE(GANV_ITEM(port)->impl->parent); +} + +float +ganv_port_get_control_value(const GanvPort* port) +{ + return port->impl->control ? port->impl->control->value : 0.0f; +} + +float +ganv_port_get_control_min(const GanvPort* port) +{ + return port->impl->control ? port->impl->control->min : 0.0f; +} + +float +ganv_port_get_control_max(const GanvPort* port) +{ + return port->impl->control ? port->impl->control->max : 0.0f; +} + +gboolean +ganv_port_is_input(const GanvPort* port) +{ + return port->impl->is_input; +} + +gboolean +ganv_port_is_output(const GanvPort* port) +{ + return !port->impl->is_input; +} diff --git a/src/text.c b/src/text.c new file mode 100644 index 0000000..d519186 --- /dev/null +++ b/src/text.c @@ -0,0 +1,383 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include <gtk/gtkstyle.h> + +#include "ganv/canvas.h" +#include "ganv/text.h" + +#include "./color.h" +#include "./boilerplate.h" +#include "./gettext.h" +#include "./ganv-private.h" + +G_DEFINE_TYPE(GanvText, ganv_text, GANV_TYPE_ITEM) + +static GanvItemClass* parent_class; + +enum { + PROP_0, + PROP_TEXT, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_COLOR, + PROP_FONT_SIZE +}; + +static void +ganv_text_init(GanvText* text) +{ + GanvTextImpl* impl = G_TYPE_INSTANCE_GET_PRIVATE( + text, GANV_TYPE_TEXT, GanvTextImpl); + + text->impl = impl; + + memset(&impl->coords, '\0', sizeof(GanvTextCoords)); + impl->coords.width = 1.0; + impl->coords.height = 1.0; + impl->old_coords = impl->coords; + + impl->layout = NULL; + impl->text = NULL; + impl->font_size = 0.0; + impl->color = DEFAULT_TEXT_COLOR; + impl->needs_layout = FALSE; +} + +static void +ganv_text_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_TEXT(object)); + + GanvText* text = GANV_TEXT(object); + GanvTextImpl* impl = text->impl; + + if (impl->text) { + g_free(impl->text); + impl->text = NULL; + } + + if (impl->layout) { + g_object_unref(impl->layout); + impl->layout = NULL; + } + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +void +ganv_text_layout(GanvText* text) +{ + GanvTextImpl* impl = text->impl; + GanvItem* item = GANV_ITEM(text); + GanvCanvas* canvas = ganv_item_get_canvas(item); + GtkWidget* widget = GTK_WIDGET(canvas); + double points = impl->font_size; + GtkStyle* style = gtk_rc_get_style(widget); + + if (impl->font_size == 0.0) { + points = ganv_canvas_get_font_size(canvas); + } + + if (impl->layout) { + g_object_unref(impl->layout); + } + impl->layout = gtk_widget_create_pango_layout(widget, impl->text); + + PangoFontDescription* font = pango_font_description_copy(style->font_desc); + PangoContext* ctx = pango_layout_get_context(impl->layout); + cairo_font_options_t* opt = cairo_font_options_copy( + pango_cairo_context_get_font_options(ctx)); + + pango_font_description_set_size(font, points * (double)PANGO_SCALE); + pango_layout_set_font_description(impl->layout, font); + pango_cairo_context_set_font_options(ctx, opt); + cairo_font_options_destroy(opt); + pango_font_description_free(font); + + int width, height; + pango_layout_get_pixel_size(impl->layout, &width, &height); + + impl->coords.width = width; + impl->coords.height = height; + impl->needs_layout = FALSE; + + ganv_item_request_update(GANV_ITEM(text)); +} + +static void +ganv_text_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_TEXT(object)); + + GanvText* text = GANV_TEXT(object); + GanvTextImpl* impl = text->impl; + + switch (prop_id) { + case PROP_X: + impl->coords.x = g_value_get_double(value); + break; + case PROP_Y: + impl->coords.y = g_value_get_double(value); + break; + case PROP_COLOR: + impl->color = g_value_get_uint(value); + break; + case PROP_FONT_SIZE: + impl->font_size = g_value_get_double(value); + impl->needs_layout = TRUE; + break; + case PROP_TEXT: + free(impl->text); + impl->text = g_value_dup_string(value); + impl->needs_layout = TRUE; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + return; + } + if (impl->needs_layout) { + if (GANV_IS_NODE(GANV_ITEM(text)->impl->parent)) { + GANV_NODE(GANV_ITEM(text)->impl->parent)->impl->must_resize = TRUE; + } + } + ganv_item_request_update(GANV_ITEM(text)); +} + +static void +ganv_text_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_TEXT(object)); + + GanvText* text = GANV_TEXT(object); + GanvTextImpl* impl = text->impl; + + if (impl->needs_layout && (prop_id == PROP_WIDTH + || prop_id == PROP_HEIGHT)) { + ganv_text_layout(text); + } + + switch (prop_id) { + GET_CASE(TEXT, string, impl->text); + GET_CASE(X, double, impl->coords.x); + GET_CASE(Y, double, impl->coords.y); + GET_CASE(WIDTH, double, impl->coords.width); + GET_CASE(HEIGHT, double, impl->coords.height); + GET_CASE(COLOR, uint, impl->color); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_text_bounds_item(GanvItem* item, + double* x1, double* y1, + double* x2, double* y2) +{ + GanvText* text = GANV_TEXT(item); + GanvTextImpl* impl = text->impl; + + if (impl->needs_layout) { + ganv_text_layout(text); + } + + *x1 = impl->coords.x; + *y1 = impl->coords.y; + *x2 = impl->coords.x + impl->coords.width; + *y2 = impl->coords.y + impl->coords.height; +} + +static void +ganv_text_bounds(GanvItem* item, + double* x1, double* y1, + double* x2, double* y2) +{ + ganv_text_bounds_item(item, x1, y1, x2, y2); +} + +static void +ganv_text_update(GanvItem* item, int flags) +{ + // Update world-relative bounding box + ganv_text_bounds(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2); + ganv_item_i2w_pair(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2); + + ganv_canvas_request_redraw_w( + item->impl->canvas, item->impl->x1, item->impl->y1, item->impl->x2, item->impl->y2); + + parent_class->update(item, flags); +} + +static double +ganv_text_point(GanvItem* item, double x, double y, GanvItem** actual_item) +{ + *actual_item = NULL; + + double x1, y1, x2, y2; + ganv_text_bounds_item(item, &x1, &y1, &x2, &y2); + if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) { + return 0.0; + } + + // Point is outside the box + double dx, dy; + + // Find horizontal distance to nearest edge + if (x < x1) { + dx = x1 - x; + } else if (x > x2) { + dx = x - x2; + } else { + dx = 0.0; + } + + // Find vertical distance to nearest edge + if (y < y1) { + dy = y1 - y; + } else if (y > y2) { + dy = y - y2; + } else { + dy = 0.0; + } + + return sqrt((dx * dx) + (dy * dy)); +} + +static void +ganv_text_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + GanvText* text = GANV_TEXT(item); + GanvTextImpl* impl = text->impl; + + double wx = impl->coords.x; + double wy = impl->coords.y; + ganv_item_i2w(item, &wx, &wy); + + if (impl->needs_layout) { + ganv_text_layout(text); + } + + double r, g, b, a; + color_to_rgba(impl->color, &r, &g, &b, &a); + + cairo_set_source_rgba(cr, r, g, b, a); + cairo_move_to(cr, wx, wy); + pango_cairo_show_layout(cr, impl->layout); +} + +static void +ganv_text_class_init(GanvTextClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GanvItemClass* item_class = (GanvItemClass*)klass; + + parent_class = GANV_ITEM_CLASS(g_type_class_peek_parent(klass)); + + g_type_class_add_private(klass, sizeof(GanvTextImpl)); + + gobject_class->set_property = ganv_text_set_property; + gobject_class->get_property = ganv_text_get_property; + + g_object_class_install_property( + gobject_class, PROP_TEXT, g_param_spec_string( + "text", + _("Text"), + _("The string to display."), + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_X, g_param_spec_double( + "x", + _("x"), + _("Top left x coordinate."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_Y, g_param_spec_double( + "y", + _("y"), + _("Top left y coordinate."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_WIDTH, g_param_spec_double( + "width", + _("Width"), + _("The current width of the text."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 1.0, + G_PARAM_READABLE)); + + g_object_class_install_property( + gobject_class, PROP_HEIGHT, g_param_spec_double( + "height", + _("Height"), + _("The current height of the text."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 1.0, + G_PARAM_READABLE)); + + g_object_class_install_property( + gobject_class, PROP_COLOR, g_param_spec_uint( + "color", + _("Color"), + _("The color of the text."), + 0, G_MAXUINT, + DEFAULT_TEXT_COLOR, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_FONT_SIZE, g_param_spec_double( + "font-size", + _("Font size"), + _("The font size in points."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + + object_class->destroy = ganv_text_destroy; + + item_class->update = ganv_text_update; + item_class->bounds = ganv_text_bounds; + item_class->point = ganv_text_point; + item_class->draw = ganv_text_draw; +} diff --git a/src/widget.c b/src/widget.c new file mode 100644 index 0000000..075896e --- /dev/null +++ b/src/widget.c @@ -0,0 +1,505 @@ +/* This file is part of Ganv. + * Copyright 2007-2016 David Robillard <http://drobilla.net> + * + * Ganv 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 3 of the License, or any later version. + * + * Ganv 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 details. + * + * You should have received a copy of the GNU General Public License along + * with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +/* Based on GnomeCanvasWidget, by Federico Mena <federico@nuclecu.unam.mx> + * Copyright 1997-2000 Free Software Foundation + */ + +#include <math.h> + +#include <gtk/gtksignal.h> + +#include "ganv/canvas.h" +#include "ganv/widget.h" + +#include "./gettext.h" +#include "./ganv-private.h" + +G_DEFINE_TYPE(GanvWidget, ganv_widget, GANV_TYPE_ITEM) + +static GanvItemClass* parent_class; + +enum { + PROP_0, + PROP_WIDGET, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_ANCHOR, + PROP_SIZE_PIXELS +}; + +static void +ganv_widget_init(GanvWidget* witem) +{ + GanvWidgetImpl* impl = G_TYPE_INSTANCE_GET_PRIVATE( + witem, GANV_TYPE_WIDGET, GanvWidgetImpl); + + witem->impl = impl; + witem->impl->x = 0.0; + witem->impl->y = 0.0; + witem->impl->width = 0.0; + witem->impl->height = 0.0; + witem->impl->anchor = GTK_ANCHOR_NW; + witem->impl->size_pixels = FALSE; +} + +static void +ganv_widget_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_WIDGET(object)); + + GanvWidget* witem = GANV_WIDGET(object); + + if (witem->impl->widget && !witem->impl->in_destroy) { + g_signal_handler_disconnect(witem->impl->widget, witem->impl->destroy_id); + gtk_widget_destroy(witem->impl->widget); + witem->impl->widget = NULL; + } + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +static void +recalc_bounds(GanvWidget* witem) +{ + GanvItem* item = GANV_ITEM(witem); + + /* Get world coordinates */ + + double wx = witem->impl->x; + double wy = witem->impl->y; + ganv_item_i2w(item, &wx, &wy); + + /* Get canvas pixel coordinates */ + + ganv_canvas_w2c(item->impl->canvas, wx, wy, &witem->impl->cx, &witem->impl->cy); + + /* Anchor widget item */ + + switch (witem->impl->anchor) { + case GTK_ANCHOR_N: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_S: + witem->impl->cx -= witem->impl->cwidth / 2; + break; + + case GTK_ANCHOR_NE: + case GTK_ANCHOR_E: + case GTK_ANCHOR_SE: + witem->impl->cx -= witem->impl->cwidth; + break; + + default: + break; + } + + switch (witem->impl->anchor) { + case GTK_ANCHOR_W: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_E: + witem->impl->cy -= witem->impl->cheight / 2; + break; + + case GTK_ANCHOR_SW: + case GTK_ANCHOR_S: + case GTK_ANCHOR_SE: + witem->impl->cy -= witem->impl->cheight; + break; + + default: + break; + } + + /* Bounds */ + + item->impl->x1 = witem->impl->cx; + item->impl->y1 = witem->impl->cy; + item->impl->x2 = witem->impl->cx + witem->impl->cwidth; + item->impl->y2 = witem->impl->cy + witem->impl->cheight; + + int zoom_xofs, zoom_yofs; + ganv_canvas_get_zoom_offsets(item->impl->canvas, &zoom_xofs, &zoom_yofs); + if (witem->impl->widget) { + gtk_layout_move(GTK_LAYOUT(item->impl->canvas), witem->impl->widget, + witem->impl->cx + zoom_xofs, + witem->impl->cy + zoom_yofs); + } +} + +static void +do_destroy(GtkObject* object, gpointer data) +{ + GanvWidget* witem = GANV_WIDGET(data); + + witem->impl->in_destroy = TRUE; + gtk_object_destroy(GTK_OBJECT(data)); +} + +static void +ganv_widget_set_property(GObject* object, + guint param_id, + const GValue* value, + GParamSpec* pspec) +{ + GanvItem* item = GANV_ITEM(object); + GanvWidget* witem = GANV_WIDGET(object); + int update = FALSE; + int calc_bounds = FALSE; + GObject* obj; + + switch (param_id) { + case PROP_WIDGET: + if (witem->impl->widget) { + g_signal_handler_disconnect(witem->impl->widget, witem->impl->destroy_id); + gtk_container_remove(GTK_CONTAINER(item->impl->canvas), witem->impl->widget); + } + + obj = (GObject*)g_value_get_object(value); + if (obj) { + witem->impl->widget = GTK_WIDGET(obj); + witem->impl->destroy_id = g_signal_connect(obj, "destroy", + G_CALLBACK(do_destroy), + witem); + int zoom_xofs, zoom_yofs; + ganv_canvas_get_zoom_offsets(item->impl->canvas, &zoom_xofs, &zoom_yofs); + + gtk_layout_put(GTK_LAYOUT(item->impl->canvas), witem->impl->widget, + witem->impl->cx + zoom_xofs, + witem->impl->cy + zoom_yofs); + } + + update = TRUE; + break; + + case PROP_X: + if (witem->impl->x != g_value_get_double(value)) { + witem->impl->x = g_value_get_double(value); + calc_bounds = TRUE; + } + break; + + case PROP_Y: + if (witem->impl->y != g_value_get_double(value)) { + witem->impl->y = g_value_get_double(value); + calc_bounds = TRUE; + } + break; + + case PROP_WIDTH: + if (witem->impl->width != fabs(g_value_get_double(value))) { + witem->impl->width = fabs(g_value_get_double(value)); + update = TRUE; + } + break; + + case PROP_HEIGHT: + if (witem->impl->height != fabs(g_value_get_double(value))) { + witem->impl->height = fabs(g_value_get_double(value)); + update = TRUE; + } + break; + + case PROP_ANCHOR: + if (witem->impl->anchor != (GtkAnchorType)g_value_get_enum(value)) { + witem->impl->anchor = (GtkAnchorType)g_value_get_enum(value); + update = TRUE; + } + break; + + case PROP_SIZE_PIXELS: + if (witem->impl->size_pixels != g_value_get_boolean(value)) { + witem->impl->size_pixels = g_value_get_boolean(value); + update = TRUE; + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec); + break; + } + + if (update) { + (*GANV_ITEM_GET_CLASS(item)->update)(item, 0); + } + + if (calc_bounds) { + recalc_bounds(witem); + } +} + +static void +ganv_widget_get_property(GObject* object, + guint param_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_WIDGET(object)); + + GanvWidget* witem = GANV_WIDGET(object); + + switch (param_id) { + case PROP_WIDGET: + g_value_set_object(value, (GObject*)witem->impl->widget); + break; + + case PROP_X: + g_value_set_double(value, witem->impl->x); + break; + + case PROP_Y: + g_value_set_double(value, witem->impl->y); + break; + + case PROP_WIDTH: + g_value_set_double(value, witem->impl->width); + break; + + case PROP_HEIGHT: + g_value_set_double(value, witem->impl->height); + break; + + case PROP_ANCHOR: + g_value_set_enum(value, witem->impl->anchor); + break; + + case PROP_SIZE_PIXELS: + g_value_set_boolean(value, witem->impl->size_pixels); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec); + break; + } +} + +static void +ganv_widget_update(GanvItem* item, int flags) +{ + GanvWidget* witem = GANV_WIDGET(item); + + if (parent_class->update) { + (*parent_class->update)(item, flags); + } + + if (witem->impl->widget) { + const double pixels_per_unit = ganv_canvas_get_zoom(item->impl->canvas); + if (witem->impl->size_pixels) { + witem->impl->cwidth = (int)(witem->impl->width + 0.5); + witem->impl->cheight = (int)(witem->impl->height + 0.5); + } else { + witem->impl->cwidth = (int)(witem->impl->width * pixels_per_unit + 0.5); + witem->impl->cheight = (int)(witem->impl->height * pixels_per_unit + 0.5); + } + + gtk_widget_set_size_request(witem->impl->widget, witem->impl->cwidth, witem->impl->cheight); + } else { + witem->impl->cwidth = 0.0; + witem->impl->cheight = 0.0; + } + + recalc_bounds(witem); +} + +static void +ganv_widget_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + GanvWidget* witem = GANV_WIDGET(item); + + if (witem->impl->widget) { + gtk_widget_queue_draw(witem->impl->widget); + } +} + +static double +ganv_widget_point(GanvItem* item, double x, double y, GanvItem** actual_item) +{ + GanvWidget* witem = GANV_WIDGET(item); + + *actual_item = item; + + double x1, y1; + ganv_canvas_c2w(item->impl->canvas, witem->impl->cx, witem->impl->cy, &x1, &y1); + + const double pixels_per_unit = ganv_canvas_get_zoom(item->impl->canvas); + + double x2 = x1 + (witem->impl->cwidth - 1) / pixels_per_unit; + double y2 = y1 + (witem->impl->cheight - 1) / pixels_per_unit; + + /* Is point inside widget bounds? */ + + if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) { + return 0.0; + } + + /* Point is outside widget bounds */ + + double dx; + if (x < x1) { + dx = x1 - x; + } else if (x > x2) { + dx = x - x2; + } else { + dx = 0.0; + } + + double dy; + if (y < y1) { + dy = y1 - y; + } else if (y > y2) { + dy = y - y2; + } else { + dy = 0.0; + } + + return sqrt(dx * dx + dy * dy); +} + +static void +ganv_widget_bounds(GanvItem* item, double* x1, double* y1, double* x2, double* y2) +{ + GanvWidget* witem = GANV_WIDGET(item); + + *x1 = witem->impl->x; + *y1 = witem->impl->y; + + switch (witem->impl->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_W: + case GTK_ANCHOR_SW: + break; + + case GTK_ANCHOR_N: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_S: + *x1 -= witem->impl->width / 2.0; + break; + + case GTK_ANCHOR_NE: + case GTK_ANCHOR_E: + case GTK_ANCHOR_SE: + *x1 -= witem->impl->width; + break; + + default: + break; + } + + switch (witem->impl->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_N: + case GTK_ANCHOR_NE: + break; + + case GTK_ANCHOR_W: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_E: + *y1 -= witem->impl->height / 2.0; + break; + + case GTK_ANCHOR_SW: + case GTK_ANCHOR_S: + case GTK_ANCHOR_SE: + *y1 -= witem->impl->height; + break; + + default: + break; + } + + *x2 = *x1 + witem->impl->width; + *y2 = *y1 + witem->impl->height; +} + +static void +ganv_widget_class_init(GanvWidgetClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GanvItemClass* item_class = (GanvItemClass*)klass; + + parent_class = (GanvItemClass*)g_type_class_peek_parent(klass); + + g_type_class_add_private(klass, sizeof(GanvWidgetImpl)); + + gobject_class->set_property = ganv_widget_set_property; + gobject_class->get_property = ganv_widget_get_property; + + g_object_class_install_property( + gobject_class, PROP_WIDGET, g_param_spec_object( + "widget", _("Widget"), + _("The widget to embed in this item."), + GTK_TYPE_WIDGET, + (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_object_class_install_property( + gobject_class, PROP_X, g_param_spec_double( + "x", _("x"), + _("The x coordinate of the anchor"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_Y, g_param_spec_double( + "y", _("y"), + _("The x coordinate of the anchor"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_WIDTH, g_param_spec_double( + "width", _("Width"), + _("The width of the widget."), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_HEIGHT, g_param_spec_double( + "height", _("Height"), + _("The height of the widget."), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_ANCHOR, g_param_spec_enum( + "anchor", _("Anchor"), + _("The anchor point of the widget."), + GTK_TYPE_ANCHOR_TYPE, + GTK_ANCHOR_NW, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_SIZE_PIXELS, g_param_spec_boolean( + "size-pixels", ("Size is in pixels"), + _("Specifies whether the widget size is specified in pixels or" + " canvas units. If it is in pixels, then the widget will not" + " be scaled when the canvas zoom factor changes. Otherwise," + " it will be scaled."), + FALSE, + G_PARAM_READWRITE)); + + object_class->destroy = ganv_widget_destroy; + + item_class->update = ganv_widget_update; + item_class->point = ganv_widget_point; + item_class->bounds = ganv_widget_bounds; + item_class->draw = ganv_widget_draw; +} @@ -1,16 +1,169 @@ #!/usr/bin/env python +# encoding: latin-1 +# Thomas Nagy, 2005-2017 +# +""" +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: -# Minimal waf script for projects that include waflib directly +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. -from waflib import Context, Scripting +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. -import inspect -import os +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. -def main(): - script_path = os.path.abspath(inspect.getfile(inspect.getmodule(main))) - project_path = os.path.dirname(script_path) - Scripting.waf_entry_point(os.getcwd(), Context.WAFVERSION, project_path) +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +""" + +import os, sys, inspect + +VERSION="2.0.4" +REVISION="5e4201f8b4f4a2a744d52154db826b02" +GIT="x" +INSTALL='' +C1='#<' +C2='#4' +C3='#)' +cwd = os.getcwd() +join = os.path.join + + +WAF='waf' +def b(x): + return x +if sys.hexversion>0x300000f: + WAF='waf3' + def b(x): + return x.encode() + +def err(m): + print(('\033[91mError: %s\033[0m' % m)) + sys.exit(1) + +def unpack_wafdir(dir, src): + f = open(src,'rb') + c = 'corrupt archive (%d)' + while 1: + line = f.readline() + if not line: err('run waf-light from a folder containing waflib') + if line == b('#==>\n'): + txt = f.readline() + if not txt: err(c % 1) + if f.readline() != b('#<==\n'): err(c % 2) + break + if not txt: err(c % 3) + txt = txt[1:-1].replace(b(C1), b('\n')).replace(b(C2), b('\r')).replace(b(C3), b('\x00')) + + import shutil, tarfile + try: shutil.rmtree(dir) + except OSError: pass + try: + for x in ('Tools', 'extras'): + os.makedirs(join(dir, 'waflib', x)) + except OSError: + err("Cannot unpack waf lib into %s\nMove waf in a writable directory" % dir) + + os.chdir(dir) + tmp = 't.bz2' + t = open(tmp,'wb') + try: t.write(txt) + finally: t.close() + + try: + t = tarfile.open(tmp) + except: + try: + os.system('bunzip2 t.bz2') + t = tarfile.open('t') + tmp = 't' + except: + os.chdir(cwd) + try: shutil.rmtree(dir) + except OSError: pass + err("Waf cannot be unpacked, check that bzip2 support is present") + + try: + for x in t: t.extract(x) + finally: + t.close() + + for x in ('Tools', 'extras'): + os.chmod(join('waflib',x), 493) + + if sys.hexversion<0x300000f: + sys.path = [join(dir, 'waflib')] + sys.path + import fixpy2 + fixpy2.fixdir(dir) + + os.remove(tmp) + os.chdir(cwd) + + try: dir = unicode(dir, 'mbcs') + except: pass + try: + from ctypes import windll + windll.kernel32.SetFileAttributesW(dir, 2) + except: + pass + +def test(dir): + try: + os.stat(join(dir, 'waflib')) + return os.path.abspath(dir) + except OSError: + pass + +def find_lib(): + src = os.path.abspath(inspect.getfile(inspect.getmodule(err))) + base, name = os.path.split(src) + + #devs use $WAFDIR + w=test(os.environ.get('WAFDIR', '')) + if w: return w + + #waf-light + if name.endswith('waf-light'): + w = test(base) + if w: return w + err('waf-light requires waflib -> export WAFDIR=/folder') + + dirname = '%s-%s-%s' % (WAF, VERSION, REVISION) + for i in (INSTALL,'/usr','/usr/local','/opt'): + w = test(i + '/lib/' + dirname) + if w: return w + + #waf-local + dir = join(base, (sys.platform != 'win32' and '.' or '') + dirname) + w = test(dir) + if w: return w + + #unpack + unpack_wafdir(dir, src) + return dir + +wafdir = find_lib() +sys.path.insert(0, wafdir) if __name__ == '__main__': - main() + + from waflib import Scripting + Scripting.waf_entry_point(cwd, VERSION, wafdir) + +#==> +#BZh91AY&SY¿øGH¢«ÿÿ°ÐÿÿÿÿÿÿÿÿÿÿÿE ‚„ 0Á#)€¨bIwzØ8#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)_mßmêõ¶V3[Y¢T¬ªTª[+îç$é²ßs§a¡¼ÛZ¥Û7Ä»}5öîÛÝ©ÔÙ¥«{n&[>o{íoœ#4¼Í–§m‘‘@õÑ&ö÷vîØ-nÖjîÀr/wtvÎï}Ùß_2Hô#4ï›Ý<2uÔ;ÍsÃÝïízܳ]°dÛs·Þß=¯xn`n™ámöžë»ÕhìWmC%ïgÞ#)#)#)ì#)#)ìhÀ:Wp>Ž€§Ûƃë»;Íæ#)½·°éï6ŸG»Iæ(€Ž¹Æܯ¡§(¦¶#<hH4#4@6Å#)w³Þª©*‰*JzaŠ(‘ET*¤%JvÏN»Û{hÝöÇzu“³+ëîÏŒë»tÛ|÷¼÷ÜàUkPÛ¦¨®Ùw7W}æœÚ…d°#4¼ße÷jº}z˱뤽ӽ7½y_Y$æg·Ö×VöËÞû‰2:eVÞ}ž¼ËTß3µiwkÞú=o7l}=tºÍhÍ*HTJzz:»{›L‡½ì+{Þ»Üîî¼4÷†½iN—zttÃÛ[m *«Þv{ÚqN€Ð¨®Àty@#)":èT›½¹êŒÞå^»¨UÙo¸½ÝÖùœ}¾ûèè:}ó»nœQkh,²Ÿv/Œ@Ý®ûp«f6¾'yáèÓ×9½z»·Üîè6ã«îÍÞöz¾ÇÇ ö×–Ìö:ÓDI–*[ÙÏwo<sv´X–¼.óïí±Öº»£¹®ç7³s7·½›Fìç=÷yÙîÖîÎíݳo¼=å7·ËÞ{×ܬ¯GÞ÷½ª×5ÕÄÙ¢6ힺˆË³s°[¹O ôµË’no‡®nÎíÍW5±>>yÞó8ºï{»zô{æï6z\XîY¥ìyöó‹>ãCÖœoq”àrhfíTêæñf#)»)绽=;—}Í=äy³^:!B€ ¨ŠjR#<FÀ4U·:Ûw@æE6È+fݳît®ÛvÁlOsºÙÑ^½^Ò;³ ]ww+ÍÃÞjª¦ë½Ý§;+&zço#)#)îóÕ®@#)»Ì½]ÎëçµÏ|í]ÎhÕ׬wk¹U£;g³¼ðî7«¤âŽ7:G´yÛ]#)}S—¾ÌKJªåµ¦&¸è‚öày¼Ùï±ñ7ßß^ó§ÀU£&Šão1éíhò6é£m¸t}°÷Ýϼ»“µƒŸmÄo«T&ùµ{¹ËI±qÚOxûÞÛ¸x¹£Û¯zøÞ½é¶ÑaÏwžúç϶° @KM#)>N›Ø÷·{w±÷šÛݱï7ní Ti}x|1çÛ-{¹Þ½STëœö]#)#<ÖzSkvh3´îͽuê¶=;u»Ò\aîôáSßM;ϼôzöໃ]b¶ôÚAw>¾õî}Y½Û¢š=ôØõìë®éׯ:Ó¶o,7zÙêšÒ+;›=yxö÷×ÓГʹ4ôNÝÚRö»X/t¨=ñç[½3¼|æ{Þp»h @ÓgKxï{‰¶õ“‰âfu®.2ªÍ‚ô}ܯ^;¶Ž»uEíœéÊuª“Nî1÷ƒDçºíŪDzWn$š³ÙåÅ>¾÷>µŸ-õðƒœ¾§]`·Û´|Gf É£Ëë36ñÀ¦Î{³¹ÝŽ{»›Ãt6ß'¾û½o¶¡´=ÝÛ†¾.»OSx¼¡–Ÿ|7#44@#)š#) #)£@Ñ¡#4#Pž§¤ò€õ駩â‚S@„@ КjfhMQú=TýHÄÑ‘“Ò#)@#)#)#)#)$!#@L§¦¦Š~&¦hÈÔžS@=OB©å#)Ð#)#)#)#)$õII"i¤ÓSÓ ¡’ˆöOÕÔŒ€#)zš¡ Ñê#)#)#)#)$ˆ&€#)#)€š#)F¦£OS jbdi©é#4#4#4F€#)$ÔDÐLA22& =SÔzŸ¥<§éª~©è@ö¡#)#)#)#)ÿûð?ìÛV»‰¬åÐnVÕÜ—ùVœP¦"1¾ý«]Ò „`¶5²Ù—ÛU¶¹[Z¢Û$}ij B?3ôÿçuùÍfñÒnÌR’ÓyŽDR¹üâÓÍ;ÅbÄâä¦ÅBÅ]ÄÂDgû…àŠDÂªÝ J¿6(9ÅZt¹w®Kˆ\<´ÓÅÄÕ:§NªmæŠx³—ž8––«™¾É6Þo\qMfû)‰#CýþSgÈ…¢)¬U"$F1PcRDÁR -E‚ Ò‚DjQ®S”°„‰T̬ S@* „Ž$QBÀ$PlDP„\A¢#)ÙQRjÕÝ©ÑlV-õwmI[jªßkmjµ33!d£3P)¦&ÃKI!4›™‰’Âj™c ¥(ÒŠm¤Æ š$ &Œ†©h¶5ÑšK&–F#%£EF#"m)FXÔ±¡LXÄÆ*P6LØÒE&ÉhÚˆ¡,²Ò˜ÊÑTD,´†ÍÁ²“5ÖEhÀ*#4&ÈŠ¤Ø¦2P&4Í2Ò1¥cR”l%´Ú¶Uˆ„ÕØ“FfIš"ƒ,›i¶ÓM MIFÊJšØËRÛ2¦ZL¦’ˆÑ¥„5fŠIK#<YXÅ2B$Ê‹4ŠÅ&ÅD‚6)5"QšTb#<”Æ(Ø„!A"2˜À´2#$©0d"Ë,Í DDŠ¤šhÒ²€ÌÑ‘A"ˆ¥“Y(£6!…¤X†K) 5”¤„ÅRd`¥4(†bARhÒ%Òd#4d*1bH°LÊZb)˜ÆÙ!˜¢Æi¦l&hX©6&ÊÂ`D’“b±%£– $M¦(”#<ˆ˜ed”„bÍ ”JJƦj6+ i5$I1„´¥HȲ[E–e‹2MfR#c&ÄÈŠQ›"`™¶iŒ)`²Æ*CdƒM!£•’ÉÊš$RÉ! š"ÓMH4Z„$–e6Q”´S3HÚ#AÒÅÚ˜,Pi,ÆB1d¤²$˜‹Ó0¢”Q£0Á¨¦…&*#4‚$H$0ŠLÆ’Fˆ‘2Âb‚6I•35f4U)TJ"fCR4Ä4ÍŠ”6ÊhÙ#Eb’I6H£Q£%M‘iš,lE4™I™LRÈÊͦD¬D`‰)™#<ji4É!Œ¢4†¦‹M±DlÔ©L˜S#$dÉ)Y4XÉXŒeIÌEcI¢M%MFɨ²I41Á2Ù¤ÄÚ#4*`Ë0šL¢ˆ±¦d6dI)²S#J ›&BjÍ•‹XK‘1&™d‰,[F±¨5ˆ¦[#<D%ؤH0Œ£RX´i†S4Ò˜6JÊiLšÒA¡$Å)1˜B†Y6De0H¦Ê2ÂÒ ¡2&c"HÑ6Ú•[FPÉBZeŒ¦cf”RJf"+A©K`RÍb“Le e•6Y6 ‹S-…³F&¦Äe(–BR×÷úí²!55@ˆ#c6Š1Q‚dš4š‹Y$RÊhĨhÖ‰¥¤*66RMFÖDe£Q–Le"Ê`%IŒm"134¥³5ciP¶6¢´dÓ–2©$ÌŒÖ6CJÍÉ“-&ÐlYV6Å#EM”#k"e‘TƳQL)³c)36Ém6cFÖ‹DkdÓ5’J™e6²kE6ÊRšcf5©HÉHÚ‹¬±&“R&#<TFÑU‚µ´d¨‹X¶“Y(¶£h¨©5bh‰’*‹Q*Ñ°ËF¬Xƒi4”4BkPÂJ)ÚTLD-2LJÓXØ5LKcT–#4ÒAVÕ+mšiU ”ÍMY4I²1!š„QdŠÚ–³lb”Úm–iS¦›TÈ‹m*–I35J‘‹4Ñ ¶„ƒ3$Ëe ¦RK&ÙYRh–QŒ2ÂEE‰3RbÅ…‚Aˆ‘"Aa1ˆl¶™ªf%EšÒVI¥“&Š6D6X¤Se hÙY©D,JfIaIE˜¦`bjZ5™!•Šm&J#È”„f™$„F£&CJ2‰ ”Ô@ÄÂÒcA’†Z,Q4¦E1fY¥l&1(,˜Å0”²L²Bj-"£LµQ³)$£ …Œ…Qe+#L6Š“’Œ›#RE¥„Š2V4‰#4C1“I¤Ù”Ù””³6”¤¬S†‹$j!I+EQ³Z3DV*SI°Ó,k2¨Ð$d# LR#416$©6“(QRšb¤‚Š[%2£h*™Sd²T™Bb¢M$€‰CJ2Vd–Ñ["6"™™"TA„†Ë1DC&ÄÍ#b5šMÌDؘ‰©! ´hÑl%EF¢ŒjÆe°ÒaŠPÈR4²dlb”Al ¨+,²Y…X´fJl¡*Hƒm QXÑ&‚R¨ÑA±A’6MQh4˜J!…„¥(jcL0ÙJ£TF”ÓK!E$ɶdZ$Z‹±JªZhª-&‹%‰"še+(V, Ì #)˜™%M‰DLÒU¨¬b¦j*KEŒ)EšZŒ•‹[IŠJÍ*‘6Ñ´MbS!™MIa@Y,T”lš$ˆÄ•IHk™F-f@ÐCQe*eLE6Ô•’Ù ¤)¤šF#<&Íi6‹e$±RcI#F,$ŠÔ¢4m,´Z6±&ÖŠ´e(ÚJ©BÔm£RQb‘M ’S#0&4Ô‰(53d™µŠ¤±j’¥š²kBV€‹cRTQ$[b™¶imE±hÖ£bÛI™3e–[+mE™QµM•¥©E1©(22“S%cE‚$Ñ¢ŒM&kcS6$‹A€´•±dÑ´¦šËQXÖ3HÚ2Q¢Éckm–µŠDÍ¢TÄ”c ,EÒJÓ#%XÛØ´Í[F5¬´•42ÖÊY-©©¶BÚ5bhÊ(ˆ±š+$¢ƒa¬Ôš±™³eDPj#I”“"2(ŒT–Ò`,dÿ‡ýoûÿÍ@ÿÏ?ºxåk3HHÿ‡û1µ¦pš¦ÜtKG,#4“÷‚á„P)«I³åÕ×yo9¿Çþ>ßøÊnã•Qÿ+k™÷z#<œ0(!¯þªÊëQh0\:8À¥‚á‰I äˆm½µð8|{'ýlÄêÖÿ—çµ£þ²IB–¹@%m#<ÊH–á+„Oájq•™ÆÐd ¡§}Ó,uË$ɬ ÈÏu‹Ã4ëZvÄäðó"28”caªN׋’Æ#ükfØíRRWü6S;qC-“P #´¼2&Ö8cDOL#nŽ#¢¢5uB*35S4™³(·\°ww,¼ëß±Ög5r#&ŠéP¦(MÑ™N¸ÒæX”…0€Ø$ÄÓb\ÚìY#)ïcFq#)Ž¯=ݨe}íÍ‘›ãsI`²"ÄAL^.LUAì.9_ü6X± nᦠ¢Á`îÖõ@S#<-«‰~åÍ$?'VähÅ'ô«ß`v¼^˜D˳b(¦¦Ð¤Y;øä¬èI³7>#4´¡±;ž"J<våÅþÈÃÕ…wÓ#<ÐA¢#4#4e†íŸíÿ‡^½©êuQYÊ„ìÚmû»ÃÀöø°#4kÞÞîŠõç¯d£—.‹¸¦W6éd4QW6éîè±±ByFâIi‚ˆÈ¤÷Õ}HZ~¶¹Í}îEmåÍçHŸê·`+Ž5g=â aãÆa#4›O,¡´Ã”¶ ªÅaåtFaÑ#))‚4†ÓŒ®4Úï9äË FÔ£+ÜßesF âk®W{»£¥ræJ)uÝ$Žs!_±¶¬ºŸøõ–j˜“T¤R…",0%+¡‡vØ˦N#-X,úè¤OUM?6¦#4+j~/LHeàÁ¿ã©HlÉÏLª #Š‡û“TÄC Ã1|êbæ_Å- ŒE±õôÄ9 Gášå®oýó„бÅT¶[DûjºïrûDá÷Ô½ëçÏù¹blù^ýN·˜Wêcv\â|²~_Bå,rpÕ{q¦rugbhÀà&É/z®¸3,Ìk·–·‡¹^Mm^öÅ[)•TRŸ·_²ýNyßCµ;7ktä××P¦±´HNBÉF¢óe³¦µ¼”ROÖãܲa2³Ñ…’¡Øäm¬ÓM¨ÑZ+<œTê)AG9åxû_¯:+”‹¢jË*¨6~YjdÒo$~¡GÞ.Í¡uQF¨Í]ÿµS,b“ Cÿ“PV1 œh£ÊŠª‚Ä7ÌçáüY`ãPm±›qí…È<KB«å E|™›ÛJÄóeká]Y)¢©£d*(«ÍË&£yËQVÿ·ý]½1§éu&×Ò¹dÂ[ôÿ#<™¢(5A)AQUyký7!„dLcI©)>ç7+¾Î J†’RtNÜPL ªrºÊREÂPɢ幨¶Þïåß>o4¤–ÒT>×rÅ¢¿¶[¯³±ûzãH ¨ÎTi®%§´JÖª…¢Y£d«ñ~…y‹A¤·ëtÁH»Që½=¸¯-6¹€`|š˜¨R4ÀåIb?¢¡ðçÔ°Ê#]+k6gÞ’|RšÏætþn¶ìÂm“_ι%ú* nÖíL+¯ÃPHÁe!BRkïycÝ\œºas~.Y#«~˜05TàǪ4ÆéE2„R]U¥"½›Õ¬üjç“¢oåYNNYlÓ¾¬ Bj/Âë¢ûÚû¥y¾ç-%»³¸^SÉhTþqF#4¥¶–š·›{ÄS²ŠŸF¯’Ø-ŠŒ:¤àÏ[0"<™% kTt©@'…SuïoÒ‚5Ú¶|åñ‡\i*Ï'üI½øÄ#V.(¢,œ½_Óg«<þÌBׂh—:p ±çTnÅIÑ~pϘ컈 ´ÅõæI['¯Ï5®U%&aFeS?™Áú0ú‰0#ùNã¨j# <0«ÃD?î3ÊùÑ}›€g¬ïSÃ~$–ªÓ#rB‘ä…E8Õ*m]*w;=÷ág#<£õÉYJéã›÷ä f¢Õ€²·2½ýõWSn=|f\ˆ1¹Åšh–w°û²ÓdA³ÁjC?L[»ç&Cë|´Þ¾©œTbtNlžLÑÛå\Sƒ›ágS¹CW™Brç|Pââ‰Í£ìe#<w§çÖúüN¦¢m‘0Ÿx¶ÌµèùÍBùgŠä¾UPµ:ƒÃ:ëƒÝéJ}9Ñ&ˆEÔý8²¬#=é#)à„ña¨âüÚÌ8`³h†÷!dúè{‘š¥¿EvØI£(^ÑýQŒÇ\TŠ%ò¨ÇhÔ˜†t¾©Ò‡Î\_„Mz}ÜMïuXaãHt¡°îŒ"$˜I?5G…«˜& ÜŸj¤btÇdzï†s4çíßz`s¨UYLó¬YI×jµÙPÝ(Ib¨Œ1Gù|ÛýY<@¡&Ì\2wCK´ãÆfÁ‰#<B…!¬;?Ý;|œ–ÅÇ’'OO7WCåýQ8êÐ8…ƒw H”gÉÎSVïpàëîM×5²’ÝÍ•‰»!ŒÊâr(õøå1§ ²´ :+ho*„LüÓa7~·µÕHا.®Aù¬dsñ(>>3h—qÒI–ÇÊ!uïZèîÙ<œ#<Î5DVhœ0VÊ”‹¾û\ð#4ô•½Çj¸7üÑPm½I¦ˆG G}ø;wB1´nn:ýÇP§ùq69û7O›#4sOl#<ߧ•òxì„’HeT×Ƕ–“çÚ”Ò£o¨#4_Ó“F÷0ÈiéAÝ|9Ý#4ú!&u;.xöcVyaŸÞþ½iœa7'àBX£ñïO—QÎ÷T½@[â\|ˆ]20Ó›‘ò#48@âV¶ã[ü(½öcm‡cë#;jÑ1ê-—ðübü¥õoÉæ1ýœÍ_7ï÷búò!ª‘Al¯ª©-öò®ßJáu«××¹€RÔ(sd½ë¡)$©yoyÑ4þ.ä7+¯v™¸Íú:º•d ~¦A³5Ú•òçÉÆ´Ô²7"ËåÿŠ¢Â(O#<©¯ÿU×àþñ:–Ÿ/ÇÆñѧôUAûÚþµæ'ãvßÓ×yAb5+좙òg€ŸnožÏ®•Wsv±GrŸ²ZûßßÎ{bˆûfìd‘?ÒÖھ黄«4jMPk²éPC¢«55Á)™ïÅÌøÅ»?–‡mç-kY¥¬˜Â}ùkw¨i˜ûì«?™ØNÜieT¨0B„]~IvS<f}©Øè«4j=,jé.ì³åV\åRøž§$\œ%ûèF#4Öc;ý9Ñûôº'ò#4[ºÄ8G àDL4wwÄ9½ºÛ9ó¬ñùÀnÀêÀ¡´¥Uü“8÷fãéEEâf¥#46&Òu‡éõ´†Nîc"âSå¸hÆ~=Sïß¿|mŠ?A—Žýñ`ʼnÙýá¿i˜êì齇µÆý"ñç¹mw`aI-§=oƒQñ/r+ÕÎrðaYAx‡k>YàÉqb™1ôô†6ž«¤¾=GØDM×~”¶÷?èšR|¹Ã‹Þð¾ûçÛaïDC,½ïZF™¦"&gËŒ¦_gð¢åT¼™µÉOwž÷0úß2æXS¿j¶ïMwqã3TYL(Xmé(-¶ŽÛºaâ›æ‘Õ=ûÊÑÝ‘ýLxdl[žw—GÔïfHg”J¶Ö~5Py#ϦÐk§«#œg5ο]œ\M¨û/…Öh£šPˆ+Ò¡À@éîÍœ•S¢#ª’‘h½1|ùÙz'+?ËZ<xT4`v-<Rå” ¬M&ê™Ù1§œç·–ÂðÛ(WnN¦I0å³L¤œ_å|ûülŒ Îx‰«ìGúV÷ŸähHÍ(œûVÍ£,W™&äd0P¹bhPžÊ(†Þ<éU–¾ŽÎnôˆLŽr½";{=LáØC!$È´¥Ã“X‹Zê;ÿ÷‹ÓºçwÇÎ$vpâËÊc#¤Î|ÖT\N>TâÎ:L—¶WnÊšbMrÏa™Y¢¶t‰Šjš$(ÍoûË=çÕ¡Ë¢¨@w†ènÈ°[¤á{Vt9šéóL!°°Eb²7‰V¢ÔÑ \Š5/Ÿ#<j¹0JîçWË$¡ûóó(,EYoUeðÍÍs’[ËD8Y…B,U„Ò‘ËñCKû¡åö¡ìLøvëæû&Ý7:}Ñþ*a÷YÇÉTL á!‹®6ñC~4aàèá· áÐ.ÇÒæp‰Jm ²¾iéùáÎ3$«æã—q&”ÈLþ)¡/Öª‹q×ʳ2ÆTl8ÝíÂóÞsÖ‡°·—Ý7廘üÞ–â£Ê{D¢¶pŒñ}}ÓcþXyïzËß×.o«nËú²qáKCWùçö͘^E,QÌm5 Eu6³ë’Œ}9ÚvãÖ5æú÷ótX§ÉÛbA;§Âðà¤yÁ¡÷(1DEç†}Ù”ŸËøk¯§ü8ãg‚G›òì~ÿ»Úèú§è{gñÃÙª2qÅ«-Š°Ù1ª|;o….œøæ1Ý;¬&ÊeŒê[ÕF8EÕ#<UQÃË'Ö:€®•[ð ò>[;÷ã·!ï˜àÒ#4j¤¾>Þ—ÞC(EÙ÷P'”=¬fÇ\iȉUD|XÁŒ\w·%!Ñê‡]s¦˜oT*¥áªéW‚Ÿ‹)QC\U{œ}zÕÖRßÆ©òçX>µž{ µÛ÷ôÁ…ÝŸjs‰Ôs¦÷^º§«ö²mŠu¡V±F{(–‘‡ƒóøQëÍ;;–›ÕÓºYàTò‡C TÄ¿XnÓ@¶v‘-N¤s΄½oã~wJ,î/~¶4á±86ÎmQ3ʲ©ä°vÚï$@ç&sµÅ¥èƒšâ#O% e5¦=–Qý*ÑoˆríÍ'–â=QruW¨.5rÚ€äj{LYÙøÅŒíå«Ç6û_•\¦ÆØ7S·•x¦`‰.»›Mwq® sfF#<öVRöÿ7݉æiGk5‚£gôtͽ‰UQ8™ÊÂHjJâ9sëA•@¾Õ‡‚iæœ#4¨Êx4©Éãß[³SÇZ-ŸjzßðƼ»ùlcÇjÕ<SFg2¢*þŽïvîC,ÕR½™·gU¾IÂùŽÃŠmõ‹ý¾5—Ú-ÈtQ§*Qþú(¼øÆF>"?ݘcù<88ý½þˆ„|“T¶ß=ñÚƒöÆZùõ1”~ÐÇ(´:U/áSe¿¿8õ*š¥ph¡œ'ãKóuþÎc:˜67ÇO3ϳtÆ`ÈëRCRŽTX*ÄQnÈbèžÍ¹b°Ìyµ1ëó‚¶OØÀ¬ZèÐ7—#Œ<^ø}ÔýÒ¿WùÑ .ÎipHþÌ$#òGtÓ-icXñÚ¸s,³TX*#<‘RlÂOÀ£y@Q}û»@?_{Jý¦œÔQ‹ÑƒÉ¥îr±–„Лœ¹Ãÿ?|çÇt[‚#4Ñ)®‘ïzEL‰„(±XÃúÓ1R®¥Ý‹qVë©G·5=™Š¼àYš¨?ƒ]ƒõºvfÏ6u©‚Íêª'0µd+ü»¦D§P‰–…š¨¬øFʲV‰è›æ‹wîçë§n+º8˃ڎ6¡vO"ÏŒú#)Ã]L®zÙß.°úîQIáÒÊÏÖ\uŸ\À¯‡r->¦Ç/†W‘p«°lQ3PÖc‰s»¤Å¬+Ilþ:cŒ(‰~ÐáÙP¸®êÀ*ÇÝŽV™QƒI…°?‹xHóøÕQâk#)µED×÷“çH ÔNjDvq¶zµšÊÆ+b>Ë8`i‰jmÑw[£{ç÷þqóªú»Õ)YSW»·7“µÈ¥ÇUÜœQúH°"ªÅO²½3†ØÇ4·ú¾~l"Àëðw7lå@ýÖ+õ£xv±$âT’K’dùåï«ÚmúŠÚ?·Çû¹°,´}DwÿN!Î+#4góŸµA5('V?[Îó~[~g·´ý<èÎbÕèP@r¨³Ã³íÿwÌ)øã©Ê0SÊ£aòl[|ò-çÇ3ùêN—„:?y°ÊÖˆa|Ð}1cð®i2±¬Pæ*ñ·º¢:øÆT¦6M›|²hA³ì¡œÍf8uŒÁ®[Â4íŒ95jüÛkÅ‹QÆFõH4Í^œí¸¶úblP’s¶y¹òaj¨‰Ñ04Z>Œ’ UWÇÒÉB(q_S"ÁD°QiF!Å2!ä8¥–,âß+œÆì–ûÄ}pó28Å|×£±¢ Å`óðª }ý:£Õ¸Q¡ÖЈ£g¨L˜SëmYÂ.±±qñÑH€òf²4Ш‚7J./bÚâÏ*t½æáÿÜm•J1j¯Ìd^EžÕ.Z¾ï=X<Wš,ï‘™<ÚY-íU#<¶b-U·úe¯úýó;Øà$\»‹hpåݸ¸€ú|`u›½ïÏ4Ötøµ÷³å)ôç9zbƒ$p)wÁ¥Èéê°èš3M«èê"iIU”š!h2oÂP±œjhÞÕ@á^TFÂv4„–^ÿÆ;ÐîíOóáÒ-]ñß’HöY€ßž!ºÛi0Û;ïÙÃ)švˆÇàõ™¦ŠÀúâ2„JGéßÅ*ïåºm‘†›7¯Ÿ,zû«m#4Sµ]RgóݸB¬™£Ô”ßÇ5y{Þj™`ºxÖ>ªŠiSº¹û+²1ü;¹ÎuCþž'b“³ãûC‚èÁñ復Åð®ŽíÎ33…•\óøŠ^žqæ¦=éFdþ•§:#÷Èk.§F{¦œÏ€‘ðƒsQ´Õ({Ö)q—j6ÓÅ.3´ˆ X0`ÔÆYèL™%•á¹ß=Mz[¯"µ[ÌÜÝœ#Cœ§‘RÁL†$9•>’ $! 8ˆ”DT’¤H`Oq´zVð¾1QfL¸j¹ºr½°û/VzÔºþÇO–ÅíòF{.æV’é(“.)2(u;ç!Þ92×UxäÛ4ß:Œb«˜'‘õ òñWÊGèyíÛƶõé¾ÆéRïAñ"/R‚ESQÓÛ‡ŸÛñ¨´½Üã½µ¦ÎAõÝánPòJ΃¯ù*vò;ižÚÞÝçÞÁ=sLrÎ!“üJ_›i·¶SèíÿŽ¾Ý3ÕðåF—Çð(S¤Éï Kÿ®ŽžŒ¬©Î9¤ÿ¦(låK¾B2jšmdHrtqðäÙNNlIW»Oø¦§ÊºäÇ[\\ ×pAoÅ8&µMoÖGg²cÂ#47Ug…ÀoÂ7óQÓXÊŽZ_r Dß8uNfÌ–D³®‹ÿÌŸJò£Œï´¿ï ‚zvN¼óz»gG(û„l°sã2¼_n*ÏÃ`—zó}¸#<Âv-ÓJ#ãY?CÚ]´Œ'È‹¨ð@\”j,,Qw2[GH˜×öôè/Çk_,Âèþhé:À”˜yÚí ÝŠCK6»ÕyµÒ>í®{•#4\÷#ñ#8ÐŽªxÅí³±8¿ÅÜß®]c¸åìnzĺeRñFÿñ6ûa.2.s±©¾Z@òÖzÝM§#<¨·‹àmªˆÝÞ3`íéË£ŸÃšë%n°$Åú,Aäë¹SMdç`õM)C¡#4·!½¹Yo"Ë„Rˆ—#)±Ýö=ptçä»—‚†x;ÚºÀ‡B¼^Ü‚ÃVŒœ—\:H¬îe |å8裡lr’Ï[\âþ#<6ºŠþ¬ä8éõÔï4 7|våõreâDÍ9Q¬Iõj‹zÀó‘åÿÖS–¬9Søßç»ý#/G<ô™Áà„$ª"Ô£ÐÀ_9õfìýwEÇ_¤Æß³»&³×øt›d¡óà:ø<nK…åUþp£#È}ü¬1Ç;‡ÁΈ ³oèPM¾¡&ßòþ›„ˆH’à™ÿ§|Ð$Ÿä¹o{.0yŠíôWY ˜uÜ\’ÉCÒÍË4õaÏ:Hqsâ:ÇxKåëèuYD‹rèaØsø5z¼uT;£Û˜Ø EëþlŽçæUîöY„GÄS¯î]{¢ÃˆvøMÕR4lQ°3·²Æ¿Û5Ï¡Ö ™i`€D0Ùý½¼=?ºÛÅ㧇ßm°ü4>zq^3mTÇÈ8R±“ôb]¢_{…ªõ¹¬Ù…(£Yi«…·Gþûâh=¹²DdÔÁs¾¨LÄDYæ~Pî&#)ý}K z„o «\û#4µÓöA³ÓÐ"/]´¸"#ÅßO6ºü~#4ýtÏç‹wZ:£ME¶³\‚½kúyœ¤÷ò¯/MIýÍùMœkLº(š¦Ý͈×SI~yàüö&›•bôªâô1†¤MªñŒŠ%Ož)a™)Ù˜Œym·£Ž¾ÖåóW3úŠo\zDi ÏäöV¥Dß,¿îù€íÿtÆ£B#)ñ˜!#) „ŒgKñ»íl0yÎâæqŒfÍû _/€ÆÕ2ÅeñO¦KüÖ~EõÃWúWð’¹Âà.’EÇËõ=#)¿ã’5½<ðÈAgÛrKžˆöœ¡ùl|1ÒÙÑïo£xúþ?[Æ\n®û¯áê¸Ø¦våábJ܉Èk]óüÔ’>ë'_è}Ã@ÃCè3×Gl²=cOOËÃ+wxß`Ì?(¿9>nÈÛðtç˜É#äþ>ÈÆ $$#4|Y®ü„E¢³åÌ=Ñ÷õuRœŠˆõE#)ª#)táðgöè=Zþ³Ë7)ð¨42!ó·âëïxÎ<XɺQTéî<N\ú~çnfœ8ò*l¾#÷9÷ãøX:“pËé–‚Ê|!y’©«¾ÙJ2ßî^8 SeMÚá¦ûn °‹°©Ò0g7Í®Áƒi–þ÷™“‹‡fíÈ*ìîX€ìÛökÓ—R¸Ïò?£cœö”pë•TºbWvƃki’EÚÿ'FÌéšÞQUýlÆêMÿy¨å4ÞóñÞ #<P¸_½ÁÄV«xÏ#4¢ñUÞ»Äq#)#<”b‘Wn$íqÙ®n6Ì3èZqù‰9–òÿ."“ L·ŽrJÑ*ÚICx¬Þö$y9´ºxæÍ,e>¸°$‰?7]Eâ,Ë#6ôq.Çøÿ–…ç93O{lŒá'ïïãìt™Diô¢›j¥cœUŒº¸2Ú ¼±†Š®õ˜/‡Î+ûqò!ÀîâCèKNûÑA>´9£$ØmzÅS½4ëìØãj#40Î_¿Æ¼Ð¯ù-Kr|z54°I»3‹ÄôÅ_ÅŸ¡ÿ9³AØw^„1(6œ÷QÍ°2ISŸ;é)<±PÏ8“Ü›I±a<Uyuû²p¶ñÈo[ÁÑÂÔ3øw Ê|‘–R2ühï%…¹"„þFüyí†Ã£¤ïí¶w6wk7½eùÿWŸ¿I>æ5×Í.¸êÓõEªàá¼h¼V?²`fÀ⡈y¤¸&øŸ³m+“P#<”‘‡·Bó=Ø~Š¸W/<쇦2f.V7NéÛ#4naçÈÙ>õòä6Ò·9,#)£oÍZf(Û߯¦´ñ_î:õÿ's÷v‘Ûù3Ýaº"ÁÖ„qTETÍ—Ÿ\Y,'yŒf)VÕûÞíRzNša›4TpmuæÁî1˜!Ôgª¶‘\"ôÀ Öd™‚Lã•Ñ¹|8ÞÉ7Êøü°29>ù#4#4®&‰$QFÎú¨ZÇ!®÷X†ÓÅ}¡Æ;?ÆâÛ˜~¢#4ˆp™Ú7üÚ’KéƒÓ*#)-X*&‹‚‘uñáG»×dK’:–ØßeIL¬P¨D&Q¬Tiò÷5ûUÐRY³d@3Œ1-ìxvŽÓ‹+ü–nàõ¿Y”ÒdsÓ¾Å1•ÑÑd”-Ç#)ÝSŽšI¡}¾<¾à/¤#4Y¿øDLÍewj%J½ßÏß Ú£]°¡«£áNºÇ)¬ÐMP„°5š’ñ?k…×oÕöá[_c`†ù9b| ¢¤É#4BÀ‹§LÉgõ›ôÚ¤ðUºsžrQ BÀèè|šãFm=AbÐG#4ÒªˆÀÇ#4,U²ÇÙë6e‹¹!HbC(¡S‘¥08ÆÐæ„»”ÆB¢e…—w9cƒk"+|ë¦m|½Ÿ[×Ãäç{×$EÊã¦Æƒ>þù¾Vy[õkUtGÚ]WìÌööCÕëáùë qyJŒeE` ÄUô/7¯*·‡y©1º5^;qò1·ÇqÃñþåÌÙ¡5pÜf`—ØHHÏΦ@Ž¯Ã|ÓÉ]¿«¨h>ê¢[”e·œ7eu¬#)ëòÂPWâwwêÊàÆ#)l«ÛS+m˜‚êÀ@†&Ãéý˜0ÄeÎÙ¯¶ûþ¥©Áô‡¤^Ï®Ãj¾ðt¢”ž…Ü™±ž±”Jh>îø:áEС`ƒ÷ªÑHÓ©é¦UW‡Þ©F!kj„|;kÑ;iU"àÎÇ0)1- #)2#<6c=$Ç7jœÜÁšËÚ]¼_ˆååÄ—/æLJn#4Í÷Ïh‚¤táGJvè±ï²ô¸oWá!rÑåÖZ¬sazù?gß•ÀM¸˜Ì0´ õØe5EÐYîÎ,šs¿MnB:,W,(ŒÏ*Ö‘“9²z“¡êUlÁü1åÌX蛎»št»r3Ÿ®Ž†9·Ã–ˆyøO^·Áv^5<"¸7;‹ßBáçÁJèІ¸99ptbÔd*GN©.ï«)c9pÏC¢9Í뙺ÞXv}Cvfú}[±N¹ù¹j]¹P3oàáƱŠé±¯H\¥W¥ÏF8hŠR8¥c—aÑù·ccÌÁÖKÜßÍ£œIºL%® ;>¸òëðÑä½0ÒÛ¥Ý0ezò:ÝTjyÅYdÛêÊ,ž|Ôé¨ÚêúzäÎÛŠ%/)Ì~ÝÍZãúkØÊYEŒùÙ-Tvùq<S8-y°®™ö)êv©×Þ6Ûüñs§v¬…Å´8o‚˜Î=„#<¸s²#<r¤ŸRŽ‚ì{˜kÆbÂØâî‹ =µ¬Ý²]Pˆ†%ó»]Þô»\ZyÙŒh¹ºEÃkšÅPÄɵEñ̘(•ÚƒÃ><µgðÜ/^H<ìŽ w)´ÂBÍD6 ±°C,m#4S˜ˆz,Är‰í×ÃÄÑ0iP¸”ˆ1¸ã@6Â;È™ Ã{†¢ŠEŸ_§žù“ƒŒG‹†XÇqtÑ©;¤ÆðØH¯tiX²'´¢ÓÊ?„ì©Ë#<èøãPY?#<õ†·*Éå.S4#4À¥“cû4’Ô.´®L;;Û}Îâ¹ÏI8ã'#4Âõš¢CõR–]ŽÝÍ9&ÎÀ$¦E ß8Uã&B®¨)à†àwQNFLÆT`€À´<n±§gMa¢Kn9‚ÃS陋N‡µÓ×Â+ b!ÈmóêMòìðÇsJ7øI½ªs½mÒ8üñãéZ-lì.7|‘Êñ¢” …°Æ±A' ?EÛ `,\:Ÿiɳ“Ž¬ÚV –åà³ác±¨"$ÿÞý:¼šQ9#4žÑcZ/‡iݘöHÆKVpNðÁœ-#4Ú˜¦Ðé˜JÞ)”+˜+FÚÝAðÔ×a‹i3†j!¨††¡ÚÓ"Q…iF!†U£Cb'ôB f34V—,XŒdFLÙBë,y#*+Q Áˆ3»>™ü;ƒÍ×èžš’¾Ýo^\9Ž*ál‚†È0X #„ºjøÞîÕ~E|ûuW ‹cLŒQ`š2Ñ·ÒK_fÞhÙ´i ²%Ãf1[<÷#<É› c“—µ¯-ynW˜µðsR›Y &a!±»¶§£ãÜÝ>˜8ÌîFþ~þ-ÕyzLÈQ¾Ñ¼§}»™YÌÁ®«Ñ}³~{:Æð>+}FÛ!•.ã[Ü“¼u{)ÈÆm•—¦Â‚è³–‰#DÇ v ã´g6Dáà2íÝåOå i³+;œ›¬§³ÝX‰ÑÃJq>Ø׈ÄBQPo/#4Çäþ>`+Ñ`“¯Géd³8Rú £Ðÿ?UErdy^Bo{GZ†)Ûº6”ó'T„ÎÛ?IŒQ€ò,Ë‚ã•Ðæó¡y3{¸›w»G»§w·Æ7©ƒ_NÃÒç—§ì©rÚÑ8r&Aö‡l—f4:Y@“B`jGÖZÍÌGKÇj)°¨ûlÎ1ð»’µgŽSâ7}‹fý‹P[a ŸùÍ>ÎFx^<¥^O«ã9Ãß”„ñ;‡ý¬:~‹ßèc¸½‡#<eæœ0º)[”kÅc7C&Þ$òid LþùAÇ•ñ¶õýS>.11°æ?[xís³¶8ïNüèþ›ø³¿>†³>)&Ý<f¼ã}®é<êZÛoSÎì*pã[¡t}œ+L¼†·$Ö©(‚Š‘(00²Z#<Éá8‘¶|T—nÆ8ýs²éƒ#<Ûøß¹0YEà‡†+Wz+V\¡ˆøÜ’ZÓØ– …#)Ä~¾ §Ï“òZ©ªßý ²Ð÷Zqð$. 8'uJäÓ¤ÿyÛ%¤mï†)¥Ê¿Hàû>æ4ù¢.‡Ã“`Àrhm1M¤=YâL²"²#46réLÔÄE…Xa)&‘Aµ’Pȳ"$éÖ‹i'ÖG\Å4Ô)c¤QG¼m mA&ñ=7üBà&¤i¥Ã–Zã!¢„Ö¨o.Ø%v¢ˆ¡£JÅУ ·QÌš"Ø™#<È–œC¸:ö1Äa)•L¨aÊbfU*E $e’ªðI/<V7»M3òx%¥&÷“úÆTS_ÆzI];Q<ys ¯—8\~|óçÏ:ŠcI¼‹‚†•Â>þÐóÔJX¿=(,ø5áz>u8—X_ið³áéT:肸¼ëWÁŽãÚŸ=Ó‘å ŸØåH·CY‹\%õ=A{ÙýÁ¶QèZ>ë•ùAT.‡T@¬n8ÆK-Ó¹ÎØhØ–eb»/žò,¢«{gí®}e˜ã7nØ´C&Ï£û‘]z(Ûó9h#4Ê\† 'n×,Ío‹Ÿ#çÞ/t‚Þæiv6Âs*3*6Ž^%eƒýø#l+2Zd}^ÕeŒ¨w|zGjˆ”×pŸ(uMM®C½”l¾éSä0ÛeF׶ɼMRÌüÚ4<5Ñù#)ú0Z8¨pY–r*£e‡rGÊDÊ‘&{*9Få:$éÂã©}‘ꤢc¸ 4ó$„Á;>)Í™]¸‹,.%’?ßœ)Ç~ööÔ>ƒÞk#e˜”Âû sh0Â*1ÑΔ¯È–5îíéÏñsˆÐZ¬ñssFlªuÕªž•Ý›ß<EíS©"â‰4Ðî1‰xøâ>«Ëo~_†`côðÆíÙÕ㉑F243étimË“*ÒDbŠš¨bŠ½zÕ»k.iý¿2o©“hÊE=N¬ªh’¦:àœ=Ûï´ìß|ç–O™+¾[>†Ê>T÷bŒ{qWó×Ç@©´ÿÛ´D4…ÖRôB™¦vï·ûº¯àcë‘UŒóû~¾ÊNô÷àû#X¨MÃH¦Ç,È€R7ouúÕäœÛ‚q@Žk,Çd؈@zpÿt(kÀØaU´üâ´ld¶Œ7vÆtØ\×$֊ܶÝ5f–î³ð}Qb!c¬]¥²V#0[ì;z#4D‰„J-k?zº5ÛvÚ°·#)B9CŠûIÖ3©3„o©è÷üG³\±pÔ~ïhRFãÜОlâ‚}à‰J ·ÑáÌ¿ßW2AqðÐ(r¬Óñ>Çï‡#4é¥è¶`u•†¯ªm7Fïsç/k#)ÓÏuz»@ÍC†°:okª:fu°5gèÃðÜ?ð¹?;ökêíM$$¹(@F%¼éäˆï*Ø=ÒÁ™~åæèåŽ~ü†nä-Ë@ÄSàÄ#4!Öˆë_Ô¾Ê9\¤šÿIvºùŧé¡ÄE>™9¿gÚGeŸ÷¢R—ÙÛQ{÷°ßÊü`,| É3ÆXÒ÷öı9ÏdÖäÆp£§EŽ#4ÿ-ÖÕ’‘=òßÚÿ7cêxùö‚4÷.‚.Ó:X÷Asðûÿl¹û>ž _6€9TQ¬Í̬ŒS²žñÂ\¾ÝKM››Õ;úº-¥Çé³ ØœŠ‘n7S¯^‚€xù¹}xm–ßI•¶¡<°Ò>l\=>ïC¸Ôþï/鯺ûgɘº-ëòC!ëÕ·x˜âyÙ€ñ>†lx™‹Š€èÉWžoB z¥ÀtYÁ¸Œ´ÝL„¥ª~'$Á.ÈgvVru}Z„E3µs@@üδ¸î$‘eÂƊܶ´Q§j¢¾}º=Çúº0ã’T1d€{¿gñô×èø™f&kÞ<ã8•W”ûûÁ=}Ù}¸ø*ø—·&dt“ɨڡ§@§Kšf=œ§ïþÏ/ð¤â‡Ñÿ Aîz|l°òý;»ž ³Õõ}Ÿ75½€ º—âÈUP2ª %4uùµß„,ÕNç#4#)”|¥G«2yéÍÔ"s’60ÒoÞßÓõ·Íþ-ó¶"¡E±òvÙýî·¦mIdÑ3²¥Œ¢þ‡‰£MŠ¿Ñ_GmÚÞë{κºjæ¼%?G‘,(kû@ú…[“´[Wìü…à~`D&’¶)4¡.=•Ç[0’8ÈöãDêc€ÓbƒMs›ü¯~o¦5y\«ºín¤Œi¤Š¤óm´^©¯?Ó_‡ù¦¼ÿŸcèýòaàtX¡EäøÔGî‘V$1ƒgåþöúk°üjcIú*ÑÑQ—–‘‰ºO¹) ¨"À?Î) J©‡‹à {Ò“õÅrE¹Ñ¢$##<†Ø ÝýÔ@ Úù*âo‰#Vüªô·*Š¢ˆ¡åŸ£þŸ»éUy¬$7Ø®Cš·f¤ii“UÿlšpüÜAÓH,0ʨTž^em/ýxí4Ñý{zŽm“X8/ W2hoâä¾–‡àÊôé(YöЩìE`ÁÑÉ÷ïÊ“roœÉ "’îEÆr×TrCýÐò6ÇÀkWoÒ¨:Ä|¥¥%‹Ð¾c2Ê磓ÓйLäu£Qk/ËçŽ8ôñÆ6þk8\åa‘²žÔ‡Ä2›¤òk²Ähð1±`Ùì“þjZá¡‚°~t冷Ãî®o$ù#âû¥B†#<0Àþû“N5S"‹Å#<Òl&IÝÈ,U)j—Y í«þðÕ3®wKúT†li;þi.l:<Qéˆþ–QØÒE »¥„…Cû?;Q –(«dþt¤“a›ö^î„?u”¾8L¿–}¼³mŸÍÐy¾k;~×~ø;D¾£ézúÉÅ'™õh~~²BÞ ·Þ½d«‡ì[ËíߦÆzÔÿÔ»=?‰L?[Õ,ˆÛòwÂ"Vì5Ál9ó…ØZèGå²?›‚¼>Ü»ì`[oF²š-£tHkîý±pÄ#)œ“$UÙ‹¹—mâþ»-¥9Œ¤9ÌïyR¾:oš^öü £ïŽî¾6›Ô,k¿ä_ŒSûé¸ñýâËÈu¹g{¿Ã‡’<#ñ6ÝU•Ïýú‡oðsõeÜ®JêaËùéW±‘7•Rz§V¯%¥@#<Q?#`Çuéî½KÞ¡ÚpjßuÖÑnv5·ö8aËA¸sýJ“2q…¬ºGÙê†rš$JKÛôÙX»Kœî}T¿²þ5Eœ[´éጾ9y4lõP(õrt_<îU5]¯nWyJ6 ¤ *£ÎÁÂá`Ëa㌺¢îžu‰KHúˆþ6,ŽÂ>ÁÞ=a~µ@£áSðè€àøwô„¤ÿÌž†ÅåMhr,0§ØÃÂÿ×z×àùEF«e0@#<”˜‘°úŸÒ"<5f‡@Þ1¬RY}øáßÓ³Ò¤ØÌÀÛ³,U£›õŠ&Úv?ÕäžîúGÂ,Æ_39åú¤|¸ËOˆ"%I†aå×åæk‹åc“Œ?s#kQêM™Õ²Î®Z/!@W«'J•Ñ!ƒ~Ÿ`Gôx çºÀç(´6PÄ#4#'$y–tÎaËäÌQ¬!`TP˜íegx¨ÃWé`û°úÉQ„•Éa…~B‘F±Â,åã$DF¨ "…àý#<©ë]ˆiçþž÷HŠLFPCx`–vû€öûÓ*ýÅSõQµÒ„õ‰‹*ÌñúgG)«-5aC%Db#4E•«5xP¼Š¢H¯€J¯Ï¹žF*:œñ3ÆˀĎ+ú´0¶jˆbWÖ¬I®˜†ƒ‚Ä…ðj¾qbOO'ÏñíUÑI<YHRÝT'¹v‹²´´÷ky§»?E»?½Ý´ñížkáíãÏùö/ðªÆ»ƒOê5ƒoìåÔ‡M…¯²XÂ’Ò2S ZÀBYäžR0žI#<‚ŠÀ¶MÆÿnßÀÖ-Œˆ?Ú}ëLj–l¦7`þÄØ«ýb/vþÜzwþ]ž?OÛæ移íû\~fîžè* øý Óí¦Z’›>¯ºÛlääõ~«=®Î¬ëµ×>©%-Zè¿:.Zììwµ‘l¿¿•ÝþÓøpÝ…¯Îí=›>îßÔ4'#4ÿ9¹.½åzaèÇ<¢¢[*öþ_ÕøtvµK‡×æ_O‚Ü:þŽzÿ—Ó¢—Ôïpbtp×ð`ä<Ëø¥7ùˆ´y»æÃŒ¿.aïÛåý££XÕë-€úóäÓ³ö~Þï±çŽÆ¬Z—£'»céÝ:ËVΟV·#44´{¾ëºß£pϤzäÃÝÛ$¦ô÷ÿ9|±æG!#)Ë4€Ø|¿w¥øÅsðØöéÇ£L§Èb-Ãk¹GÜ©ËŸÏæþÖþJZ‚›h;^ËÜŒ=X\Û¯¶³äÃ¥xáۮɷÊ7[áÂéÇ™Èîë)³Ý#<1œ-³È?Dý4pt{¾MŸykOFœS¦}ÚvÆ0ÓVÕ½ã«\Ïmy÷n:¡}ϘֱêC¹|°Ì]ˆ[ÈåG:^ĵ»¬òNûL8b¶v´v9Ñ"ÖÕÑûõü€7YfÙ[fØ›l½€ÝϰƱÙɲ˜—¢Þ,>ØØ9;¾}Þk{ÔMS<qÚ-ÛÊ!0X¹›H]«—½ÏW4ýŸœFÌlÓ6É;—ÙÏËŽû|ÜyðóïáðÛ‰ºXïw¤‰|>î®÷Îýwáä¶<8¶{öÜÐó(aÎbǖɺî=#<Ò8ÓßrðømžÇôëu…Hw£¨ž´+ŠX{¹”XUÃ_ûÜów§c 9•–5ÙÑ>ÇòG»“`ÁÙGR»ö¼pûBkú==ÛûLþ/?”{““žËé8ŽóCðoØ4[!g†ù_ðƧ}zò#åÁg%½¡ûŸO϶»²·¯kïÃhºÏ§üª¯ö¯ J»0‹½ypdû'x`W4¼*ðɱä]½ú>± ÙúŽ*+H#4oF6é³ÏöMâý-³—N2ä‡5%ÊÞáØ#¢Ÿ«Oê®>®0÷œ3µGWóQB>š°3NÍ+÷}#4ú7thúñ]Ú¿'q8×™?7·úý%MW~}»ËµèÆ1ø¢°Z?¹¸:¾ï%þÙó$¿ô˜íùŸ!¯2öóŽ_£§òÝœ(=6ŽÿßN”X.b»‚Žì¼>»;²µ2ü›“®fîW}6ÆíÙÇ8õ3¦?SÔFÆŽ…žÿ¥¾ƒŒ£€ó]ô7ùvÆîúšÿ^z°<aM.z90è`GŸÀvâñuý^iÛ³›ßþ]w²ÛOG?>ǧÚ~ãôŸ³î?yoàuøjßÉn<ÑM½ïÓæñlýÖ’£W¬Ãöþ`Š¯§îéÌgå—¡²ivúúþÍ_K«ü»P{Õ#<¢ b¨Z#<#&*ìõ|~ŸatÿòóÉYë¬îìEÊëçU£ýl®¾Øj#<ÏìKj’ëçùÚ%ð·ç 1û®QpH#<./E€wÓQ—n·ô?ðoHÜUŠä“ñýÿhOÇÄu[êîú÷~ŸšþŒyõ³äÏNÙù<M?p¹™¶xöì3õOcŸ°ïüî¿=¡áE,¯NÍš—fß›^þsRf”N_Á~¤ø|x»‡PìñïÆ#òèèÄ|~Jxr÷~<Œ<3ÿ?½Ý#<>1÷Ž¡Þ<啾HyGî#<=Oi•ÇèžÊf¼ÃÆäüì}oöýà 9VZ¬~ÅDp?’ýØa‡æoåÁ.—´of)án½Ýãïï×»U³¿wÍ]n“‡3Vïáò_Ù`Ü,þ$èº<Ÿ§9n‚c#4ºçâ·Ž˜ý¾£Ì~:“Ä~vœsõÊAãçÄ” øˆìöusñçã/æ>Ôµ<“«”ssÁÊòÈ0ú‹5<±xà§ó ÚÏÝÊÔø!P„-ˆžO^MÙ„ÜŽ¢%BExoðN<¸s<i¿YÛú{óï^##4´[×›š¦G£ò°ôJŸ1b¿{r»0|Øws.®w òVdÉ}ÇÂvÓË4Qó‚<§ã„,4Ž#ÍË#<yÕF:pñ˜¾ï¯`7_NN¿“™%®$60IðË=“Ó0tZ¬cÀ¨Nf 0Ï¢ûT(²Ý][Eèd–“2A7:%lÀ #)£Öj¼Cš®ñ`€d>”"r:Jí•Œë ¦¢i^Œy4βÆWt9~‹Ýžt¯MüüŽ¼r¥CªDG[0Ÿ]±ÀAëò(—gÓƉt*™òÑÄYÁx;.Ù™À’Xê%ùo‡(é6!¡7þÎ:ìôÏWÏo´'¢ø{x~±‡ÅÝXqi4ûŸÐá#)[’×O^7A¯h2òºcñuœ“•ª+‡?*ÉÆ}ñuï°TŠt>|«#5hT2ÑTFmA:¬`ÏpÍi3bÏ;–îZåfclT¼Ô¡©õ§µæï=—}k¯iƒ‡@ž|ÆþƒúGDÌ^DvC³£Sd¶[Ʊ.¨èß\C«h¢ #ëçm€+¤ÛÉ?§Øñ«=É*Û¦9Ë‚æR .lƒkЃ1£P¿x¦VÐh•ØhŸ¤UÃO£ñxÕñŠ<}'fKé–÷cƒîùÓ@¨’IIÜE#<ŽÍoˆxï;NÐ4꜋¤ºLBÖöo5îùäÛ#4KOž‹.1óÐ@jâ>OÇ„W‡»÷ÛoÓÉbûÇ5áßïÀXtO°ø\´#Æ6-‘^#4ä²ú˜X1Ì1#)NÄ7$z‚§Ù#<:Ž“=GI.SQEG˜[Ö“‡ÑN÷BC‘Gv@VýviéÑg–ßÌwç{ñ¿û¿8øC¯Ó÷†e§Èí3áQ³o$)Rܧ§Y3f~Eñ•±àzö"BO~?×Ê~“/'À78¦OÑn#žð5ßì“laé<®óèÚ»\hò¿Ãµ¯‡Î£èÉ4ݦýÞ«ý1‰ê©LRI͵ôÊ]òuŒ¤öÆ/ulu³ÜYÌrs~züá'wþYÃW¦£Mÿ#)Ðæò#)Ôԃ’«Õ&=I(×@ѸþÅGõþL•Jsè‘äw«ø‡ÔEì“–U—›éõ¯Á¥€öóËf#4A°¾:ÀÉG~ß—Ç·û±Úi°±~ø±Xÿád?ÔZŒÌ¸ÓÈÚ+nFåe„‚i¸ÁFA„aIåí€V6j”A¶ÅHâæ#<+EJ$Ó¶ #D49ŒŒcIàâUV”nmM¶‡÷nÔò$1¶¸}=3†´Ö¤Olƒ&D#u„c±GMQ5†5i’SjÅCCt!Ij²4±[a™F6ÛŽF#@âŠìõdÓ«ÃÂ,ŠC„dÀ¤ªˆíú´®õ]]·K¡.F¶B·QhJýñÁȲm:ÊãOëþ[ákGŒˆ®pã9#Jþ;ÿ«zŠ2]¸n¬,lÄ|ÀZ¨9-ì˜eþíkžãDîBÂàÝ¢š½6¦ ^%ï_N]›Ãóߦ>ßÓLôø<<‚/F{ýHmi[S Hèê¨)3ŒNY&bæP²`¹,KJŒDL!jÔ&ØâdlÂÒITõ" 9(%]&nøNÏ»bhœnú4|þ\®YÚ¢½?"iôSõcÝ.³†ýzò¥ðì"+¡âᮦÛuË( Îw+V»\Û¬óûÞ:<Õ×æêù*}sûŸ»wããìà%ê*õù±Ò¿£îø#Þ§?dz³«‡7Ü<°Na<ÓÉû¿¦·;>¬?vR?—®AÛxˆëÈXÙr]†Í=K(Jí•_ëx‹©Uƒ_ÛeÖáó×lt•¿íÑÑ“»9|b¢ñ´.¡ã#)á §™Ãf~TÐIAÛƒ¼¥Úìhõø|»ð_·—âÕ¯ÝâQ-B<åxŽž.4ª›}¶^ÏjðígóÊî<>!Ó’óÇgµy´ ÂÂwþ7»ö¹\ç3Ü®µÍ0ÃÀý¾ÙŒz?®Ý#<›”Ëòíòþ?-6ù›wô»o6Éón(›î·÷Ï~žù‹d²a|_h[ûý vzUE#îAÒ+îÔ.8Ùñ8aþw;cF´×™ŠÜÜAL1b¥I¸Äÿš¤xÆ)Nù/³¶ 15¥#4ÒÁ¬!X6b¶‘¶îo{'œÖe*M‘D—iÕK1¶’;0u¬*ðÔš†Êª(׺‘³yk4FÀd€È£°®ÈXF†GUÈ”¬¶v¨´`ŠhyÊ«W9¨c|’Þn/f0¶Pˆ;T+) 1Á¶#<2Anäã8¹Ù˜R1¯„©H#üøhW2p‰¹¡ipb šAZ0<êivÙ.›âpcª$Ú1¤«L*&)Å©Övª-!ñi«Œ®!ñI‚Õ-®kD´8ož) ¢#4”i¯ñáÙ¥ cΣhŒ7³Yhlï‚m&PÍ»‚Üuºs*/;…(¹”TÆtY¥@¯‰ÉGUh"ƒ<ª# Ýh«Å2£€Ù]d¤cÙ=ÁB2tº³@gò>Ua‰ ¤’ATðšP‡‚»YC¥el‘ÙÕ/_l‡†Þ¬wC÷|G'@PV»?@ѶYþöåè…ÂWã‰q¸¬ Õ¸\ø7œ(‹@«%úZ/µ;Ët¾…E'§]‹§»?§ôXžØüK³³I»×¢š"Z0d•O)èÿÏ#4H¶¸|pú/WggÊ=»:¶Å#4þ2ßݨö)ÿoŠòLéÑŽe¾Óû5?~»»sÃÙã;cؾåù‹õKŽ˜ÈÛ!!JóyW®{)ņ[“Ø»m4ìÙyØ~§«áî#o'øµƒ*ÉØo”ƒµºÖÚõþ+•¶6øTþkáت/ˆ^óx!w‘ÔH.FWôî ‚#)Çq郆dàí«í>ÞG²‘ÓÞ‚cO¢>£’ z2èºaE©ÅËîõ2ðP{‰$¹ÊªærèÁ‡\]>³H˜œîìõ1ò#4N·ïßk½zf_ëžÉÍ?OøÎC©ôcú Ú~‡¼H?«òmñÇJç;œîËl愶øŸwn·“î«öÄõª¨Âƒ•Oúvt³«ô®\ã+¹~¹‚^~>§»mš‰Øhd! ÁýùþˆéPÑiTR#4DÔmYwtÍ’XÓÉxr<_hÅ2ÑS[ÜòIƒH^H˜wñ{vv#4"ÿ“¼ÔuË®§çÃO/I™âeZ{½Úoý_ªŠì’5@?)# TXBRŠV›,mªFÆHG’"Ä5þ¶+`ü™tÞm¢^ŠFA»²²ª#"Ùg«Õ/íÎGËæ³ãì_•¤oÄc>îïšwñV'ª·±"€‰,)©ëÛ‰’"¹Žeõ`ǶŠuô Ü12-7Èš©Àt`Ú)™X¸#4MT(Ù¨Ä<2Æaž¤1ãš¹>4U†2j6äŒÁààŽÌ2FÊ£&a‚ÌF@*ù½\Ú^%ïÀˆÄÓ#<÷mºo˜ò8´èë§o·#<ш;ü¹A³`ØÙ7cUPßÙ†hjÁFâ~S,ÄT®íÎ#4UlZ!B8vxÌŽT#š´>5Q\É·í Ö]WYEQ”¬p²(DάL£Rb‰±˜VŽV†i¦Ñˆ0Û(öÇâÁ£bÑà»»4§PÝ‘EÂcm'[C®Ù`Ø2éº%ÈeÌ»a¦(ã´ÿü½4.ßgXoÚº„m´ÛF9ƒ#l„ƒlóv™FۖĪƒm‰=²*ÛÍL<·+gxMŒf`xçƒò0´ dBÃ÷Žùg *R€…*#4½7mãÅñÛEáuüOÏú\xôT~ãýjã‡GŸë£Cˆ’„)Y6Ayjšœïk÷ÙøN&aŒýæˆ=@¨W3¿‡G<Ðt‚3à?7ò›±Ó7I=\¼eþ`'Ö~Q,»z½=wØû=„÷‘÷Þ¯U¥íz¸Á¼¼%âT,²Û1bÂÒª§ì¥U0iÆ‘ú.ðÇ»~¾mHôEÍÆNQ":~SU<‹#©Å HÈ‘#eL!¼Á߲ƫzA:Šƒ}³F3ÚrŠûÜe÷‡CX8XwÚi4³sÌK&KACª‚;B+£oF°4€ÒZ%méhƒDF<ê0uQíѱ¼Ú°œî®©UZ*„B§-¼=ÿwá¡Û¶÷moÃ8Pò–¨´¼9Ë{]q†èÕ¿üGòyýÙß³×îçþ<{:¢<}9dï^¡~zûÆiòä=ÓÉvÑy“QºC‘X?¡EG‡átq&‚EËJÐdn-ЉŽŽÒƒë1Úá2æX&ÈREAWîª05 LO¢F:»S 0dh}äu·}ª£);ºŽËˆQ°ÄÜpî;sä⿲ù^=Ïžš{^¹ºÈ®Ç#T½‘Þ8•‘Cg\ÌÎñqE³‚›˜J©²DaDqaBM¦QÀ0Fµ 3~0Ä,B7Õ2•(¦%éPê¨i–òôd1sd©£„MpKGj£$;#4ʺs|6l]é7w.²I‘QÒCÛVh„àÂZi#)Ôœna‘6bés“*:HT+•ñ`ffŒ´…²GÏFjOå„+kÌ}Ì‚é8¸Ü#<ØݱX„(e’,¶Y#FS!(HöcÊïwÖÒ-Ú•ød©¢¸f"d2ô0ûñÒ]ù¹b(ÇÌo-þQ½^s™¬3 ù3ÂßLnÌ>ež(aFòÏOFh&RcúÕ@&â6轟¶Ã½qm–YÄ+2ʬL£Äób°sc;èòh|™’ΨYßIcú,}–.ßµu°øuiÃF”‹ÄçsYÙz•¤äžIb’tFD7Z6žY›#Ô‹íw¸¹šjºõßðfÛ{p„'œŽRãåÖøfFÜ›’+[]RÜc¬mV±–Ò-àLH›J¨„îû™ðZð€ÖÄCìókà¼[wÑ{ékO\0eX&Ê(‘ظyL“¨NM&mˆS%&ټŠ±AAO•`PwÍÂjŒßò¥WÁå í'l‰£BmXZúMËŒ ‘•Àtx@ÃæQülfœÉmBfm`wI¶v8-Ì-µÐ¡Æ9›âmpלÙôÏl0¨ß:´Áþˆ*¬8qŒJÉ}oèm«$o€¾/oJp$³«Oµ'º3zÖ#4Æpß3,\7¹Áú&,áÅ·ê4°ilÔÚ)ÛüÆÂ#42¸mËêjHpÜLͤÌx[wÅ/»®v¼îÑŽ±o¢ZòŒ4 &ÂﯥÔ6[›ðøÓ›õºÈïj¡$’Ë5$-ßsœ³Ó³9‘ò<rÙð»<(í–8\ø¬ëÍŸ©†ÞoÄ;›@G#-Ô Tm)E¼[ç º¶µÚ¹Q”—VŒÈkq¥¸".óá2X˜êÎh9)fãæӽ»Š6ڎî‰È+&óÉXîÁiÎ6#<9¼¶wM[á©tÅÆB¬'áA¬Xlmow;ˆ÷®‘Z±Ú:1M×W\g3É›*gÈqioÌ\ð¥¡Ó[±ÆθêìÎͧÅNt/96£7ÙÉ3ÎB(K¦f´WgßÃʤêɹ˛îdäO$ÚÔša´ó6f»ì÷¬lLxt¬[ô½™¶m¸ƒíüå,GÍd]<]Ðë<{gù¼ä¯7b“(Ó®ªŠ—ˆxU!I=æ<6=¾;eµ9`sgÕÑÐNL½ÑÙŒaÆ%äÉwùÝ*j¶ÏCI_û7q98Ø¢`ßh•ÎÝ“õ6SxëŽ'7#<ª}Ñ9wþzDÖ9n1‘ND\Ì;íO‹š\ºˆµŒDäÖê0¶SqÐC£4dÄ#4˜ÈeMÌ:ïŽñ¥¬¬¥‘cy§¹"h³»[D¦š8Ì!i̪>;Ü˾iá@Ò‘^ù¶§(Õ ½¨ò}B`„„É0&ïÍÑEŸóú[tèô®œvðÎ#4ã1BiÆùFõþãºp¾ß¾Š·Nîc¾k#<øñzí>£¢#451c‹½Àfòø†1ÔÇeÚtuZ[NÁ ðž`5Bi¹g%l¹Î2Æ%$õ¶Mz3¼¢!ôûÌM*KNó:Á·ˆ„¢-J9!¾ç3ÓLDE`³VlÙ%t_u vØ «„”o“tÒµEãÛô°¼-Ò¤$ÞŠch4XöNYËWE¯DÛ¥yÙÛ6Õ{çõ]¾œ89ÀÜrüêû%Ðnì<§Ñ®uÚåÙìtI¹x]E2£Ql¶iÒ`±7ç¡1žÎX!†¦ûÒn›4Ø”…°¦*”qŒKiš8ºŒF#<êÓL¥vû_TxuŸDn¬Fßšñè%†oŒ—8“Ög†ïÞ]™&-x4IyXàV´Ù—nºáo„Ñëž½ #<ðP<`ϳ#)/j#<:¸½Ù¿ÄÀF¸Ä‹_¯fàj(7§iV®ßËã…(tŠA#ÀvEÈ>8ÈÖ—½•úx˜ÐÝιcÃÙóàW9@¶3im/Óñ¯pòé`ºuu÷¹rÉÁ‚ëO8wÔ/Ÿîš~@ÇS”(×OØhlâõrÒWaoäJ°Ü<9e =†¶ãpÿ r IΚ^)>¯#¹D€¿˜rz~ÜeeÕ`DFz‡í/‚~„¾ƒ¸ÿW#<^£¿7ž/ª¸|›f|§Ç{´/σ-Úà.twúo‡˜ã;l¢ò’i<3€"~›Q wuI$è/jº¤½C{fb˜w(—‚Èë2†™´Ò,ô3 QèKŠˆêèæñaJ¬°ïËù/ˆ®9ø{í±€B‹µbáÚ ,à#<y)†ŸŸöÕº¸%·b§³@Þ?#<(·<‡·=#)éŽKXý8p_=ý½óÇ9¯ÇptjÜÙg³:OÁ/Ñ1IÏñ¹ž¼†|:œÜý#4Õq5Q Ì4Ø?pÁraK؉ ziè+xrßâ)? ïûÈá½Ì›Î½#<+%êwcì^×c§ÐêçñlÀêoÒG¸ŸŽZq+;NLÔÔJnÀíŒMÛ`d*JÔ‹øc*ýïÓû%±™ØT9Ë»ý7#4' -æhß½., E(—€Âû&LÅSå1ºùeÃxç²)^ûˆ~-'4æ$(AFó«âKtnF0ÉŒ0õ$W£c¹“¶›û?ªs ÉÂ$\¸J-õ5MSD{ÅðíÛ™Øó}iöܸid'x=›ÏŒ˜šu…'ÄKÁœbæ#)} ¶8ÎØKQdBLIªÙNæµEªÇÁyjáݯ=d¤!Róˆ"Ù™l‰t õlp)ðëZ—Âa\øœ8,fÿÿÿ5¾àæýöýžTGusÛU±tù‡ÃLú2;¯À£Ü+oTußÏiÖxT±©§Ê2‹6^ˆ4Mß4CÛzã«A1رªðoM¦ªª—¢Û솸ɻËövŸ_G³ç®#ˆDs˜Êd»»[yd-;.N©¨“‘ìGá;ɯ@zd¤žº¯DlQÝœX[vLGWaГ0᜚7×¥7®½'n¾›§Z¹5Y=%ÃúÊÔl…ÓÔ.¬Xñ¸]g˜ð×'Úv¯âÈÍí‘äPÓ™§—ff¤¤m}Dôl„åý’ø®7¥#4Ÿá{ö謯Juiê<4Ÿ¥¶¹=ìy³•ëÑ›7—_‹Z—I2ÛN€ÇÖoæ—·>LfºÎ~B†Cní}Ji“ˆ+ÄØètòç;kr¹;ÝËñd7þ†³fÅ#4áÁbÁ¬."ë‚Ê'ô®T‚zä75ÄËvb8s–M?-´´˜‘Ûl()4£ 5ÅAÕ…é¬9óÀðÍHZaÑhJ÷êJÐxœG9øe½ûeºôØè±±nY,Pʹ®#4U±Ÿõ"Î[#4?V—}ayâ3 ‚1ÀæÑ\÷¡†CVÄÃÝ`²HAÐE¼Þ‡0¶:XO”‚BØmŒK=èµÜñæÃ-ÁÀÛtåµBä`Âéj =ð/†|jààø··ŽÆèà÷¡Bq*‡D'$”(SÓ·¿•ç±/˜æR†1Ê2Iþævé»OuÕ¡ÌìË»LƒÉQÁ×áEWïC{§oyÇSʼvf#4=ÑÞ-Ç/¤Æu)@9¢æ¥+Z’©nâ¦ø•‚`qÉP Jæ@ó+ÏñaÍpnõ†s=”^æxÂ.[7eEÝE”µáà~b5åg³5b¿˜ã}ÙIÓ£»¦#4®PtUO6 Šý±x,¯7‘q_Lævƒ#)e*ˆdƒÛg¤CÖæ\>æO¶_k ÞßèÜw6ìî«ÃÎg^U¯>}öÐ$q[4up¥ÔMíÖ„[´÷¨& j鑦¿…t‹Îw§øú.º3Ö0ÆÕ±Ò¬¼ßÁpù^¸§8ÙF½Q…ñ0¶fÆÞ¾F‰GØ><GU¡ÑÎN^«T)(H$3¹_Ò¦ÏÑ5cá£×ÛÌ_è¾pSÆs© B|ÄË(xÇ[KÛ}g]i`Xa#<S«NÔ§Æ>ÑP;¤ñ^«%ôNÙ‰y¨ÎÜóP^gÕ5[Ã$ßåïcµ·Á3¬&|¿CgÊÚ#)~íg3S½ÁgUš#¢é—Êã úZÝþŽJ#<§e—ÊèBˆ~üm8ùw™çÓçµo|yO]fäåß:„Ò"ºÞºÁ]sit{p#ÓÓk$ùwöíØ@#)f2 ôfinMdf¡[Ã#Ö¸EŽ5·A¤ ðih"àBÙ›^ª²¥¸Ù*¢ðЛ <f›Ê½V%'»C¶”?ŒCNsæõ5óí±m³†ë¢Õ˽C¼Ì;wìdp—Mh¸³'²iÕôFÇž´n'Ü}ûóÐÎOD;„Ëþ~0o¬ùùGZäNwÏŠšC^y>ú?[—…—³¸Œ’8Óab“F¤ñxòrYç1“b63&Ûwß™-Ó>/hââ%¶¶Û>g^øÈ›‡ØÐÛµ®«Üì¼[îF¬wg¢â¶o6Åó72a¶Ù'ØÂ)KïY>{é;t['6Ĥ„&ëJc~Gvaê‹Ë‰ÆYA-§™=¦!ƒB#)oa¹ö@ÂJ.P¼úÙú+vo~8<3bå:K}½ÏB®w(Íä»eUÁ"ËÖÎfcÊÚñV—Çx'>mý“ŒÛp$oGÆ€]ªZ‹{f‰(žÓôÅ@ìR5ûæv66Ìg_®qjŽ#Ç|KÀ6#4=ºG„¢8+Ha¾§&–Âä 7¤`#¡EßðàrÊåýp?¿ïlÕQƒfû£ÉVU#›N'æf>ù œ|¶‘·±C˜Fu,>‹MÎí¦m›NÜNÔ—¹cÑK»±i)²)Ä÷"Í<Lb.*(hàvsºiðoO¡$—¢?Éö9ñÇrVŽ§+irÑ¿Õv®Ý﻾ÐðFœ¿ °¥KW~dKeãåæm;c}¦DÒË0÷ÚÈ—#o,ÓPa°Éü&|3MáUáÇmñÕq³d}¶&”Á€št©aÝÌ–x6·ÆÐâ«Ë#4#<[ÒÇ]¿nü\#a´tr÷(‚y×-»Š ÒÂ& :càbá\aêöë/SÅëÓyŠn·£éW20Åm#<òá‰Ø‚"ÆvvÙ¦ró2ÙœQŽ•#4ÌàÅVkâ,|¶x3S¬èCÛC„C2ÑËÆðÇÁáÊô{ˆy=æøŸ—\èï^ûqßC0#=¦<Z›Jì7yӶˋKŸÖ“ÉgÜaζº¶¹Ïø‹AIøê³>hAÒB^ò‚UЃǗð›EúÖtœ#)ë9ñÙû†Ì^òVá•gO’t¼¹Vç-¥êcëÖiHŽ`°7M奓aéhK+p…–}…„Òèµ®³y#<%÷Rs`Ï·–zo±.k²`V«Ìß°<Z$¶ÙÉG……âxŽ#<ý-få±0°yöCQY‹VN¹µrÜÑUÅDÆÁ:Qcö=[ ÔÅÕƾG¬5.‰íe•EÏßbF»ëvÁtóÙmZD%,ó®epºV?Üby•ÂúÅN*û«¦en7ê«ðÎ-¡·Âß}Þ>7ßÆq¸¾5W}:?E4b©^IÅ%vë,kœÜ³¾Ù|uÒ*6…[íuÖóCµñÎy˜BºòÁ³Uó$ãPäÖ~Ø~ž™æöé|Ö[£.gà&<ZöØ×Ìc†1†*Eñ¦¢çÃVÅ~ŽÔ\ÄB—•{—#<ÏÛ’íÕõ¯‡qÚ©°vàú_o`7•ÁÁçÍ«çbGƒ&Ø:R4²bË ®GÊ'¹ôò:.YG¥ToãdzW_3ÇqüsšÄ]zH•¿…ºSIG‚Úª)"—<I^·þÌ}5øøäÇáÛ™Æf~¢åd‰¾õž/ÃU>VÆ€(^7ª‹/ÎÉ>FÅt«}TùD†¾k'T_Ç+Ÿ†.¯nÌìæ"Ü$æ³WG`x|•-£šÍ—ßm˜ƒ¾|vZ ò嶃ˆŒ?Ç·0'“¨ÄõòàË„óš3o÷x@wSéÇɲ8Öa†W…#45#4úød&$dd¯³û^XÛŠÖGó’k};ú)Žýâ%yò¸{æºWE(–ò¨ó5cØ^ä7®½m#4-"y÷B..yz ~~½M³J{&è•åe]±ßº.ŽbsÓRb)¼‡3/#)ÇLXYc<÷ê²yØê Rû™ üV˜°°Ês(lw[hwlt˜1˜4î"Ì'Áãìˆèl7L¾™<ït“½D[Ùq8\e³Á³lX{]ãô뉜۹Ýæ£\9ì#4†×B‘SGÒÇ<=pv>L£:oº"•®+ÒpïgEÄÙŒ\¬dóž£ á¢c ècVTd³É#)þó¼,ü•D3º¾xãAŒž_öhé±’ƒdxteÇ#)ÇCÅþµÚä´î—{à“¿¶!¢k¸IµrZ]ÚxUöÛ|–ÙÄïXn¾»ãÝÓ:“;íÅIhº wò¾'挥ÍÓÙø»×HápÜLÝÑñsUòƒœ~v@a#46¿yªC¹aþ6x@OAHó®…¶ÍVfk¾Ü‹Ú!cN±²:iW¤´¹8-’².…cÎà$v:=—²'·ÞTM&^(¿—Lñ‰»(ZPñàþýó×CtB˜¨{,°±i2)„y!XÁüVøY|m/sÍ™\o7Iî«bt^²Ñã7KE,-~~ØL¼P!eME”9ƒî¤£¨t)ã'Qú4¸DAëÌW…âtpŽmÁñþpX2-yEÓÓK¬j<Mí8QÛ°”Q¦"Lñ{;´Kî`´t\´±û_U#<"dÌ¡¹ÍÍŽüå'¿¥ÐÅF|³é•xµ`NGÒ@Nl&˜kר¤ÒK1Ø%²Ì¹ã;yà>;èšœ’ÞK"(ƒ’Gàs¬©daŒÍ¤ú¹ë1·?_Ž5÷g;@ŸžLS˜ƒ¦nr{äŒ~J £ùJ±xÁ«BØš#)ý¹Ù[ì„pb*ÛcšAÎó~¯£§8ÖÆ.m<8 Ë!`À0iXÙlšJ¸«¤BèZÖ³A„¹…J7!H‰0Q‚×uÒéÓc€µòY)ZÉÌ;§W\FP£æ´<–²ª‘W~Ùº.[¸4åI9„ˆh-œôÔ9<*˜[mÔ±ƒ«Ã³N€‘šáY5•Â;«]n]Ö+?UÏ¢Â;IS|å‰RòkZ«£Öë%ÆSÜÑïâ;‘›Ñ42fbYøG¥7.çž7Â@*2¾u…ĺ‘#4 gL&G07|W“ÓùF:EŸŠÆœXú)Ù7³Á2s™Eí~UÓü£´WÊ~=þ\ÙžGåaîLZº™ð|rð7q†øƒK¢÷îÂçÙÌ壽Êg)Pw„ÝÒrv6Tn²›'ª*üô9õƒ‰²g\Ưæ9螢Ò]is¥Ã‹–÷‡3…:Z°Ê¢U˜#Ê.Ò·›Œ#4öNÚ=JГ'j»(QÂgW#ܽ\Áâ¡yÔš:üqò²Îëïñ^LŽ.wÂ?EF',Jÿv]¹>áÊX9~`<=BŸÖöÏ—iÀxNÞé˜Çƒ¾pZuà:Ç?EÄî9à/@·~_e•,D$-*âõØÇo¶X¿`skÅfk¿4Gâ³g1#<°æ5ð-c¾ŽT¶Ôù弜ÁôÛé&Ùõ ú/oŸóôæô²îî^]È&ç§^µ8¿Üó&›7,²»$ƒÓc(°óš\Væ[NÚZ`‘ºÎëfH"Ä…ï¿nd×CÏåµÇ±ó_(¡×éþ©Ú’l$ÄQäÓîF¹–U7)Ó^]»>k®m¥P/Xsªs?³È'÷!§›§ä俶Ÿ;ÌŪ>SªŒ{{‰²Gºå¨§«Ð0},‰µØRÈUÊ–Å>¦dGG ÀÍz»¶§é劗׎%8SµLm×ÙæhÕø8à·»Y“nô˜•ú»ËŃ˜4WljÃ-,%³âªf<:LÊ×åÐâ5|ä"_íË.µèØ=ËÆÂË¢ÖáJàáMÍmáÏt£døŒä'5V7v7]TÙi:鹋¿.gA§ðÉ⫵Åë=§{O%¦ ‹†Ô:D&”/·ÝµŸm°Ùõ¢ÃG»äøUÇ„j¥úÅ¥ê&”ªÞ×e…¼)³Ÿ(/7ƒ£Üæ»ùœ?MûâøÎY0nÆLA®1“wc¡K0ìV*„ß/€¾*”óØÎŒTÎþ¨5ïË•æ(H\‰7°Q¥1Q£s“yÖrÝËç ßË)`%ž3L›oÁ;37T#4ølìë8ÁP™z?±Çf©3¶u-IDlÛ7Ñ ršE˜ËKÞ_ÞS>ÊjµžÙô·uy]ÑÝÇ…ÛŽ¶S9Ò®Kà¯åC'‡êìjN#8þ9ˆˆ›Hb284Ù±í€?ÚD6Îøazèi¹Ù௞îhDXLÛ'Vâ¸ïXÛŽ#<¢=¸óŽk¸z©±@¨`ö½G:^#³ßÓ×k§ÑÃû¹…n‡W¯„òÆeCÄgÐRøQ…yv2uëxµÚ‰m-P^SÂÕLÏ;ïúá €íFæ@‚dùuö©Îíî¥CqT÷³ª\œ—r—¹AJ”‡EZjæ9 EÜZÓHÕk̶͸‘`kÞÔÑ‹Âë€û¿q}þÕÚöÑ¢cp•‰Kýï𾪩ྀX§ISD]‚íÛôOèÍÃìÑ]¦@ÕÒáç.UÆQ¦Ç€©®aÑZª½?¯ßŸ_â·ÑÛ;uÊ¢sû½».¯åï×Ò_•õ‘«U§/Ö}Nu„±É¹e¢û^Ž!zU…óºè‰c·sÞÀ8o8Éh«²z•È8ןÂÉ' &ùŒÀ«•êæ|ô–Sep‚óÎÅ r3Óy«ni8ºbĦ—õƒ¡Dé; æiê¹Ó1´b0èé}Ö‹D$ìP)»4²ä)¦Å#<_ÆùçtAd^¸`á‹gl¦ô…Ôš¸‰«IÖ—°z½rQ„Ÿêö¯s˜ÎÚ1·Ai ð!ø:;_|4<c´K’T,;ç7¼Íx•~nÊ'eЋäÃv›%%|#qt!k’°SMU"Ï‘XVLrØð¿{Þó“7£†2÷ùòä¶Á#)w=ì„O¬a‹Ùºô€•S´ÿ}} ºGyF±øÆzGУÇ$r½Í,“ØÍ›”J|9®~™DJµt].x+«íXØÊ`Ô¡f6[žÊ|2ÅÕDµ`«ÖªA¾æŒ3óÑÏ‘ø¢æ\g*y çÌ}fà¤s—iß>žþ¨“·ÃžÊÔ-žq#<OÞ)¶tç²c *ª§ŸhŽßÄk¾fSDÔ«ïlƒ=µüÿ#«æ¶I2…Ê,Ç zÝfa÷õyeáò#4'ÂáD¬V0rX|t77t¹^Ÿ%–ÔP.eAú³>U‘-!#)\ZzѽÍ׃Yf…^¨ã×µÙù+Ûú?)öT…€Cœ;²J§ƒPŒ—[—'_âèîV:9uºãGCËh{Z]ßV]ÖÅÓ T{´BB`SBüµp“]„.{¥“ËVAÂ`ŒÓ‚mÕó>¤6q¡‚WA× ;t0£¦ƒ–;ô/7ŽSË®¯i–åâê8 •Êªfþóù‘Ä‹¦ 0”óMäŽ#(z²Ä_4iC V”ã½]³>‡yù!#J»cŒÔ*Y^ú´øaðÊ.}#Q6謃žÎkOç‡cg÷LKJg7ëšåß¾Æ#<û?cŽÜm]HÒ!&˜5{É5Þ1æªï/ëyûõãœrné¾åçåÝGkÚ;#uÝx ¶×„3U¨Õ)·(âH!j°I‡Ù½ê4“¥ÃUÑ6¤øÝ4³ÚÙP3c‹˜ñ@êи6½£ù}Î_m‰äè*ã<}3ã³V?±¤[1"ÖN/Ô÷Ü+&‰!ÅiÆœpßZêε›âå8:âñXôœ™V8ϾÅ>O– ÙîLÃïCدGm€»CÆÇCÔo©UW¸%0¶`q/¹Ï–N„ Êõ1z”Ю‡&®#4ñÔ–iKÛ)É÷AVŽ¦‚g³ËÍW>¾=WÏcªœ³‘éŽ6(¢Ã«Æò™Ó¹¬j'‹‡ŠÒysëèý\|õ»ç:ÑN»û¿º®¶ý5ýF”Òuu‡ôÚÃ÷‹-ØåøÙÈ9wÙZúÖyhÆ+ÄÄ€°I©±D8ÅÍŒ%WOé”Hž:ä‰E“Jàdùí|0a×|ùv‰#Œ%oél£¿KÖ9—<S5NorèÁÓE·L4áùP«*í)Pöo9-çÖL"%$¾#<ïs¡+œ™’>ÊÕ>o`wÌμÅG«AŠð‚çÓ¾þ×Îùóòrd—´JJ—àÂkõôÌþ'M iqφîq>â¡áÛ„bØÞ¨¡#j£d:VË+db5RÅ@UA.ÕɺHà‡ÏIðë~JO”¨FusÑ9JºIÞåýB^.a@:àëíîœxœçoŽRŽñ¢Ì‡ÓÚbo˜·²7ŒsÒ}Yêµâ£³×mlE²ÈMÉ NÅ& *Œ#4|uCT×tè‚!,~Yãa‘=øir÷8x 7‹oH¶!š1LJ`cZ* çO9Fã·–ï"M.ÉÜòæâ²L[ZáçœôÁ5 Á…qÚx\ƶt:D#~·µGKÄ\ RôÔ^JlJNþ#<&G¯éÚH;¼ŸC6N¸¼Âü±^~»÷ôßnX~ŽÂŠ{ý/ðBÿ^Õ9©L~ý¤iô™jÖe:\©Î½XÎmÛÙ5À챋—ò ¢Ü•q3,ž3Œš¹®´ØÉc×@1¡«#)‘~9£ ¼CÀë¿Žåé·>Zº© QP ƒÏu±áºôMgÏÂ1'›ôõŸ> IÆt#)EÙ+Ú-FzûnªØÓ¹ÿé»_MÀ(ÿg}Ÿ.»‚vóm¾ÛîM=Š‚`ÅÙpÌd){"’úÝi_Çå|¸¯p×ÃÁCÁÒ¨²vÇdÞñ·0OSÍÒ)w©Tó*vPM&Úôð‹Ý¿¤6ëO5®‡7À~1¯´ÜÊgMp#<ðä)Ø{¸|ׄ÷u¯"Õž?½à¦ÀáÛYnz!Ï‹1w(m*~-bÃ䀌eWìS±ù#<Ò wJŽ>“p"j¢ÒéÂçŒ\¦Ka4z!‰,æV ŽÓ‘wrÞm1‡y›4«íf?(¹l¼€£(è2Ž›¶¤=ü#÷?cÚiµ+kiʸâY[½‡EwJK‚ŽáŸ¥Ù!®Äñ'EÃ.Í¡,ÐÓ1JÍ©WÚ';jüñ뺄 &CïîDÓR};å;Vá™°ƒ(Yû=,8¯œlÌÇݯ’è <ȹ|¾{ëû;¼¾'«OéŸKJ÷òñ(#)`SÖ©ôzÔ§÷§êOÙöåìŠ hFà9ìág0ûç U…¶8;qø+ª&n#<R^Ï3ù<5†_z{ƒSø}œìJˆ¾ž»ÁƒHX-Qmó7ã¬%R|ÿ¸úY4 ZQU$×5Óó#4{n®¢Ä¤Z“HŒ‹Pp3Jû8N•v©êbsr%àˆ…˜šô Õ p¨×ø½9DŒÜàž»íÇ>—3óºÇdϾ´M‡Ä™ìäoI$ƒ"±d#$C$=ä2†šw–ÈÝx ß)%(¤**¿®Ôå p)#42T¿M*ÇŒ`Â?TòY¹1ïv6£”•¹0ÏØRïRs2'z€ú%?FY„ÁE,@žûÚ>Š“‰GH‘/®×¼?Bò¿Àsi1b #<¢JÌ«¯ÈŸ*ÒËYŠéP¥Bš`1ª¡¹SCü'»þ…£•÷ï60EPnr·ÖˆÃKšè#)1Ë벨ôÈm«ppxßÞnvù»®ÕS[•¹‚p€¥Ó/—ÕMß9}A†¼ÌD#Ûç/·X¨ÉÖ`¶t;`v|üþ;8hiM—)PH×}Ÿ07ò#9yFãü[÷ùÙ?7Ю…Šz¬nBcžciŽª‘06$»º›kHsz-sÃÊ2¨Ï¼K}—sËõeRîlŽBþsK¦{=›b”¥2&-=e—å!òÎL0âB'êýjhG¥maµ:H9l›æY‹=ª5GR'üƒ‡²cd‘)õNÎB7%§ZÑ^ U˜-â4iÃ_Ò¿v¯oÞJ¹ž‰V„¢Ä°“¡ÉÏÌååúÝ–qèÊ¿pãÿª}¢~¼›ÎœôYŽ‡Ö–¾ï¡<)Fô7Ã@~Ïõ:Ïñš‹ý‘µ٤#)ì0ÝG‡-y!#<ð¿îöOœ3RÎiÞÍ#4Ò‹¢‚™¿Ò§W±Š`~o؇ބä™J`sº!PTP7|ÿËmq#<ˆÀ΀åj«ùûxAUÿ1éDQFK"ÈHžÍô*c;â°!Z;‹9D)ö´#4šh5'Ñf=ê%gÐÔ`tyåIhCw\ºZÄItƒ âx #45ܹ)ÕGUµ%ƒÝ\‹ëˆc#)×úùi¯[•¬§OñÒÚ±Mäôxþünso¯!0ÀfÊ1ù/X~N<žÎÑ”$>=ïh$cwèÝ7ôP¿±Çöš¾à€G»¶ò0ÙÔ;!6^9Ó¯„:*ˆ¤I¯s„·«mŸö?ÛòæZû=ROg‰0ïãGr8ó?næ‹)VkwÜ&&õE˜°C«Ñ.iT1™B)¤[%ï<´¦“v#4Щ}í·{~]ôúá+Òõò¬3v‰–TßΡ)Uøç[·]CO]góu9Ê5”ThT ç“|"ßœöEþËéf¢äO¿u2/’H8)}ß<}g®;iÉ—›¤ˆwؘùæWs#<ï=/‡vBä[DFî{ûóÀJ9uÿN¹Žð„óôë4œæêÀôsDÛ‚úÈ&ž#<©±~¶çá®#4FåÔSÔÞôLDçê[k/wBñÞç2 GW‹Ï·…õX“ã\‰*×UlÇ Ûºe#4f؃‡{gKcm³®ºÌ^òwulôªÆZquuŒHí@hgo«@†ÌÙC@Ü'P¢¸¢£mìI×øê^cÄéûâ*´¥B«ÈÀ:Vëî‹õtuCZïÙ²ˆÍš3¡‚@ë…Ž!Q”ˆMèX³TVPxSŸ»¼q³ ù>N׸Ñp#)_\z/wí€<g‹£ußKºäàÂî.#<oz½Øó‚ ]¾/N:|Ì#<T‰Ëâ$¨$šnkïJH©ç‚%Ac×KnÛ9FK’‹-~v6'êY×?ŸvæØ„Zg<>Î.è°§šãõ”8°`Ø´±³MzÔ¸š â|çµó›ñ£é6„6‡æg©ê3çdÕ/Õ$IªõªTNj’ŽµÏ|YHaßJÿ9Û«Ø»ÞuëéNœRºº©âX[ŒtæŠ;R1 €ˆe#<¯hB=]ý~üV;3qÕ!ÜbX8Î|qÇÆ÷êç?ä“y3zlÑàý¦‚Nâcxü¥NÉÇ£þrqõ&2¾˜!áC?ø3ªMé/Åõ¨Ç#øZnÈ×ø©µ^Ö<]£Ò-@&œ9›PxK«ÂŽâ²'~ËÇ“²Íýçϼ1}™Útá£Íªä òm‚g]%¬$%r±É\4Ià øºoˆôõùFx€ò4Üä–P×ß´;ÊSL9† ´z.æ¡/Ó¥Ó Òèß̬`Û¢‚Ô§¯Âöy7IºU(¹.Pë1ùâhJ´9HÚ埉&xùÞ:fo7"ÍÊh‹øFRH¢ˆ¢ZbüØÃû$þqˆnÂç‡ÕýÂݹ-õ'ª1Í-v7Cåì}•1½\$HCL© åÛ‡hlËö*¹Õ!ý=ñQAÝ¥Žºl‰ØäPÛEÈ m(‰ïCXÀg“Ãnh‰¸#4ï¡0|ÙýX0¢at¥Í³Êaò’ìl™æ‚– ‚‹#)¬›™3-Ï`KHt¨Bà-Gêæs"’ —vøÅ ØQ$Ê<J¡ÕŽç x#_]Î^Ý0¡DðÊ;ŠlD¯8[Â…–÷¹ˆ;˜`œæù¸p;a°Jº´¸7ù>ÂÇçÊëš“7\<fe‘Ý“i‚8”o–:×)#<ãЩF‹*ò½¯Ñ¸FºYç¾»H,èxóñ4ý#ª?f¨þ{(!†z‡—nóNÞqCûà0! 4¿«(X‹kcu¯Ñ[‚ÄøŽÅ@J$™J¬4õ½N{ÊÌNëÎÚ_~+HGoõüuãŽ\¬7Ùhâ#4ìR!#4€š#)¡2׺/ã¡š” paM3›"Ïê[l¼ùHc5°9Û–dœàè}ß?£‚:!}ÙØI-u·<G˜^¨Šp2#H˜#<¥KϬó±ý®ñòƒ/ŠÓO;S&ž‰)-!/¢+ÁËÅ<›Ãû#©‚ãÕÚ;0æ¸ñ¾0ç~û·£¢>i¦XµŽï׎ØHN—»w7E¦)™ ÊèMfze–j~ijUod@ÙR˜@-€ #!C;{y`†ÈŽ.)jv]AÄmua,´ìÚ¹ÃEêUL¬,Ö¢(ÇrO'ßÆO–Ÿœ»ä’Òs=¹ˆäæ_PìáŠøëlb)ÒìÞ¾°&G¦+ò+#4¼wÓ×´ÓÒ²Hí4ù)H'!àç2ÆÍýÇÎ3®=ß=5¤#\T<è±Ö`Rž/#µUÑ^¾MÝBØÁÒMe¿$+Ÿ<ûðBœ*z.öÀã=ä=PsØùµ–M场ÓT)¨%üvŽf¬ï¿>ž 1$¹µ3s/ðšT[¶0B™æàFdÊÃm¢”%rßKhì7lÔ#<k,ؤáâ™Ln^OIÀ{±pí±V{•FpR² „FjúX/#<·f%b¸(Ìa1Iµ´ÏÒ!ýkibïQï—¼Ü#<]ÛÞ¹_¦×õ;Xð}gšÅ$'ÖB”pe]{³qUTAøy„“–4°A¸ê"èÆäG\I# r.èÚ‰•ãÉ'Pt5*p Átƒ¦ÔR^ŠšH>ÂuÁqgǤ®TÝ\˜‹#yŸØ˜ëO…}ãUWM©óÆÙ©¶2t¨-‡§£#4ô2ÿ‰SrS¤¶ç:΢ãº}¼üKø@§Ï~Fþ¸Xì0Ú¥I«s+×ÒƒÈÆéªÛѲ±Bnû—Ûü‚ÚÔú2N’@‡èáuH‡bŒY¦4¥˜:PJÜ1)WÒÌžGn)ýÌ ©}P âi̓Ù0–k\çÓêW»2#)âññïLyON”&6¨‹Ñ`=Ž®Ö½z¡¼ ¨ØÓëÌYêÄ<NÖ§;؆¨Õ1ïÒܶgeêºÕ›Ã¨éÙ‘C†6{#<JÅMšX´‘>5n˜N½ÛMxŽœå0Õyó6â&‰à½ç…ò_aYGílp8,±= ~:H‚O,GfθÍ#+«P•Ø@dÍQ5+$#4À|3R6_’‡Õ±)yOç!\ÈúÞŽ®¸ý¶ºci!TͽÉ^çXyW¹“Ö„ÿ!UAU#^æQ>„þVƒÕ¶ƒàB:¢JÿDÿ¬ÿ•\“÷Ƨ#)Ðõœ##<bÙUÊåÛº¡©D.ì, …2JU`ú¿Çë}Ê©¦{R¤#4¿Ñœ²ë‹úª¡ýÐCüy#<ƒŒD¯Aô=r¤ü¶óV9BeåAUOŽj þè²}$üGÃáØ{ôz÷·×ì!ÿOZ§Ç>¼±QUHIýètBЃûI3ûP4#)qø~ ¾ä÷‰'Åëõ3Ä8úqdÀZS}৮aB ±ð!@{××mÍñN .ø;7r‚„ßK\ã@áæÝŸo.!p¨æjãGV¹NÈ=S$ú¢R«šîö¡d¿#<0Ay²˜(ŒCÇt|âäºaƒðãÍÁŽŽ\u9¯>uê"'^|?&Ì{Úa¢T:Çšrpû:xãˆç¸ÞËÛÙdçtÚ!™ÌT‚€xŒ3ʽörBù ÒÊÔhû7CÀëÙ⨦ٞÝì¸îäÝJÆJ’„þ.RèǬ—ŽS§‹|#úgâ~ÎÝù¼§¬CA9`X°;kÍÞ=}¹l…Ç”í±ï§·¥äé½^6Î…g‰™îa¡““L´¡W~¶;PfgÞ‰V× ¯´rBÅîÛªé?DhúŠôÐQåæòþû 15EźU,6¯TM„³ãpb+ª÷p¯ÆìÑ#4XhÀ¨0UüCë.Váøæ|ÐþĦW,øþy™9|ü}Yüž¡ÌO‘Q(¾ÀzY#)8Ÿå²P7ò~²”?²æžlªö.ôA`Bßdx#)|‡—ñ;BË–Ez¾M˜µpUî(3@»”BÌ=,AMP²#‹}9%#<²)\@‹@é)MÐ9èÀŒ³‰°6环D Bé#4YBd—!$àÃK–GRÛ(XX¢æRü¬ €6¡3[ì/ÑÀ0#)°Ñ²h”g¸]âçc&°ÈÛ€°,8$K(˜`Á‘Ù…Pd„&ÇËDAPC8«üÅ9F¤=$—|FSý÷íÕâ=E,Ë"#4$94è”Ó)Ðï^áP9éï‡F_×Ò¯õàÐãß$ÌVù1A·òBådÙÙ§ørðI,8â ÑAb"É qf' '«áhö†¡Ó[¿ì~¦Uf*µ£e!߃0ݦ¼kZ£Ž,1Øéèô-üuœ×–ÑáI’»ÔŸ›©{AÃßô9¶s¬’´ÖäWä#)“``ÛšÅ{YÒ¹ïÙQ¾•£u“rüÌ »¡¤›Dj‘BéÛµ×uw.™·[»ÒŒ^êßp‡#4뉻ìÚîMY!nSTìßt¨pö'¶«\⧷·o+Ž¸Ê¿Ë'âŠxÀÿC¤,{ϳd®©gÍZˆðœh¦Æ6Æi£ë½Š)ÙÃàq8q<žëQÈÔ•—.Œº©82w³FjVXzê¿ÐãÒ©òiŠ`Eˆ7DùÔƒu~‡¬Ã`ðl<ZvTT¶ïÇjÅŽ—\ÝvÞéê蔉#)žkÅøP[=ÏJ=Zh}¦5?ÞxC‚~´{2^áÌU“÷†³ådÇ&…ïîMf´®žbÄ?LC×.#4ÈÂç¡`z½˜I,ЪTdXˆ¢ØÚ)šT³ZfJbÇé{øΘ8*ª‘¦(-1*¡éÛ¯³Y˜Lªj÷»qʦsi6À£c“¬6˜Ø$]ˆD5fÞÂÝ6væẆD„À\uEPbCTëæ–2‚ŠÁ+ĺv‹= @ZDý'ƒmìykÁHÉ餉N², ¤"¼ÍfÚ#lË¥t‰«AÄ.°ÙÄÙjD´&‚l.‰„I$3§3Uñ+?fðLΉŠ‡ˆ;S´Î•Â¿ÔL< ~ „“L]8ЄˆPvVòhÀu*Q²Î9éc†…Úto¼ò÷熨|“g.õø™à2‡5ìy`™å[Èñ8@˃ŠàqHž‡Œ$<Âñ»‰ìˆe ²GUKPkDyÖd.r&?~ ñKƒã¶×‡~¾ç†JŠ'‘Zvêm JÖsOòñ¢«™Zà‰É¹ŠÉC#)•HæAS6‚°€,ˆ6”1ZôåWe¼Ö¼ÛUʲmW0ibF ¦Ñ!>‡dÌDÆ›@6“è»rˆÝ|íîÝm͵ù¤Ù)4`¯¬è¤ÙYAs\HŠ3\1.jÍ]ÁÖ#)yq ¤¡ºaëtív#)ÿ[YÒ,dseÊ9䇾òÇEVT«¸gÅ“çqŸQÛ»¡x€l‰-C_!>;ä[ø× Ò¾w(C¼¥ñëçY0§ÒV|ªhg”ùy\N’Ni`¨‘„‹"!"ç °lK"Î>ç|•š8;: S×Ç`Éð+Âë1dò=›„.yˆdRüH2®8…˳pz¡Ù\±A°ŒÒ#)¯¶HÅó…ç¡ç&ßî`ì'Ù_†ÅIã ˜Gfl|ó{9¬JŸšrÅhÐFháT@#<€´T‘MŠÛ[ë}¢Z5E¯Ÿè^E$‘@‘!TƒKåmA5~¼}´N9Öþ Ù;z“ÀZªXÕkóadÚ§©Dg]ªLŒ<QM(³8´ØpÖeŽüùó·©îðÛÆ*‡©FPì4&éb†à`CDœ1¿¨Ž™âEgºÕ¼Ž}iµ"éš`@ "¥ö`K#4LYD¡ó¯:²…(Yt¨6Ã`@ÔŸVÌ9mã¨C¼çÈ}»”7\´ÐxF1$ÄkµéC}¥z$W#¥ï.”æ¬Tí×+Ë4ãÊóí¢ƒ¥©´Â‘`²¤J¼}t4mƒ)À¢R©¤R „ØÕ##4œùgpDíãYûöÌ0Ò°,†Ñˆl`ïÞ†œ*5 OIÕ†ÌØS#)£c\üf†?¹Ÿ=—ëÅ<«¯yg¥”"*jªóòH|)dX$(*Ëô(¬1¨IÝ.k©pÏž½y¼÷.ÍWwJl– º1³ÍÚKòdts$ìâv¨¢§lÆü(ÞŸ*Ê3̺8ˆl)ðÅ62fQ1®õÅÛrsñÖ—¹¬õ¹é)˜áʧ*Jˆ{‡q `ÎŽžo©š¤”ÀiDÆtÒ· È2);ž£&Í%|Ól1‘«»™6<ÐÃ;¸œ_$Màéªx±m¼ÒÇ›LŽÐ5ÊwoÕ¬7#4ÍgnÝç»å5Zó³{š¡$˜NÀ?#<{™BlÄÄÅëéóaaǜӜûÛ¦ýΤ߬é,7¿ËΖtúHƒLf™Ïêôðš<ÜÉ$`¹‘°1šQuïÍÉ.÷Ç}G¥ïç†Þï¿cO¶¡Ý¿:vÉ,ã®'ˆ¢½ÎC[òŠQöíWTñ·¤JdÅ-õΞ0œ>»âpzt¸ò6óŽ™/ïà(j˜HÆÌãO`pöþYôoªiÉ]åMìxC¢, tæÏà|´ðÄó‹Í^ÎY:Ý$nG$q’6=^©WÝ•íš*54²Úâ’¼¿O»"…ÐRúšb-æé±c…B^´Qõ÷<kÆp½YëÙE&œ†ã‰ž~e¤“ð»Ø2ÚOŸÕÅͲžuNÈ°<Þšby#á‹£8¥•]“Nëömݦ³LáUš¨‚´.Ñe46ZR,dó8[€làæ&—‚ÆXoz¿pèe–p¾ ¶…òD"C¶sÜíÊJ—wÐïQzèß!jRí¹¿«e9®¤=‰ñ]ö²NÙEo'v.ÙçOQŽ4QÁJ#<Q`¹’Q¬KCƒ%*ˆwΙ+Ž¼¤¸švÝsí,üà¿•_K®ƒ+º9*÷Ç|f<tòûé\ökªÚbÍ‹™÷¾o}å™ùe_ÉÊv>ײ:Ó1O3ÖŜΆÞ8!ŽöPdXfoÃ#)=OŠØÄ•N¤,ío²…÷²ë\ñaìÐÁ‘ÐØÀð~#4¤B9ƒ·ÅþwQð¡ÿJ¢z×0ÎŽˆsd±ùÌÈ Ì˧¾o'žÌWðÏ*·xW˜\8‡z$<´œÙO#)(#4•‹ˆh4ºg©S€™#ßR¨V¥;-À»²„d=šrPQAe“:“…n³ í½¡7›iÌœ ™¦¬w×ÇåõYÓÃ;sÙ¬6Jª"wQØ(,Uƒ÷5!ª|5¡ñ1߉…Šª§9¤€{»^t;/·K£ií½¶ñ6YEш÷ngÖ¢y:‡½O¹ÇD ØÛ|‰¸Gwã&Ë’%¨·8~»±,ÖÆD¼ñË`òx¬RuªgÊÔý¨UV4‚3Hë²g»Ú±Žˆê”$ÃˉúÏ2tÛëŸç™°‡§Coo!Ð3µß5¢ïš¶Îf-ÉPbPErl!›òZ€4ÄÚnsävA=^c M¦¸ákõu´‡HÌ36šÐà@y?CÖqº‚Žä5i#)Âw¤ž,-à“›âw‰`œº3‰ÙÛ{`¶Ðº;‘MAìC2.û\•t0¹$Ú#<Á6ÛEIñº¨ÒkMŒÖÕX¤-vÃIub¥B©FgC[±4³ÃÙ18Ï‘]EHuÁÈâ\Á"¨=í¸”¥€ÙŒ§OÁëµ?u‚å°fžžŒ4Ûk®)á~äïm@ñëÅœ™šð¡ébÐéOI— Ð䇅Í'J¯™5e¨‹¯}%‰ÂXˆ»KѤ)Äà>¿ ê®Ë+·Ë£ÈŒÅÜéÈàë°>é%J“Bn´<Dù•à5‰¡c&q:øŽöû8l6¼àxá®Bˆ‹§Ì9sOÅçÇ´¨dQÜCÓ£Z§¯L<'ÉžŒ5u`´¬%1hワÓw-ÝûpÍ;ˆ&íÔ!×û8\)ò€)Ûú?#4›'ûölñ{|ÿÑÔ;~”{¬ÿ¡È‚V…üŸ<Q#4˜Ö¨V¢ya{*¾rYxéÌr1£y®Iöï×øTõ('_ëË5ÞöÙ{ãª&ؘ:Ûó[«Ø žø¾<¡´ï£.p˜ÆÎÛØQ n{Æÿ¹˜æâ– à·‰‡ŒèÃÅ‚²µ{ÆP²z÷¸Ws‚‚#4}CGÏR–Ílsq%ý×"¤%ÿsO¯n0ÏV2¤t7×Kükº¾#)‰‰ÔGÊ2Z©1Ûë<õ¯Ö“@D˜”FJ5('?oæªþøwBö«\·è/úîap|“8„#)íÿD8&ì#<AQ$ŠC$—‘2ŒŠy~ÔÜÍk…+01MD[ª|Žb¿Ø88âuKè;™×ý¤8vT0V“ò«””õÅiTz´DI•x˜ýE‚(ž—`Bzíõ•Ù†Þžm0¦Èxøgd@õ× €¯éŸèÿ?Ýýßš“Ÿðù?Œ‡â_6þÿôc úÐÍòÜX¾Åf¢ÖLçÂ*+?¾/ü'MS#4#<êå#4Z¹ÏR{GaÙ jFAý þÅŠ*asûæS8_ÛòÃöêL½-›ñšÙ¶'ÛëËJØZŒðѦ•¨Äb¦¶ÂÓÛ‚šáLJèú#<,åMUCT‰] }Ä8?Óüw'Ã5(¢Üa¶Z÷ø¿q¼Þ’4ÔÅL4„ÒçæoÞ¾~=q}õåéU~iÈþæNl) ?ŽmÒÁ±µ•ožíÿ)édsp:¾¶lÁqO¨3›áôÉÃÒP#<€RJ¡>˜9Òïg´Z'ŸÞfI|Ì=ta¿*ŒLþ¸Ö:ØŒ\Ûó½·ˆÞòiHÃTLNûîñAjè'E#)JgÌ>þ‘¬Oj‰%P`T;Ž\z/ËNÞzjyÖGèÿbò´5ÂýÓ3!~Uê¦Þœ¬ŸPӆ텾€ „ð’^Eöâ,rP†é¤\à¸KEÂ7}'µà¡S¥u6èºØV ¸¨®aý’×›%Oà‰Ô<Û“JgÈÁ)÷Lø]šz&‘3çeïv/©!pÏ#<¤b‚‹<&,û}ß_eÔº#õ•J[Œ~ŸÄ0N¼@ûR™qe@PDc#)¨JDÙ‰ãõZߦræç]Eb\®ÔÚÓξ.Žn“.f†£RExý6ÈÐñ‰™óq³WØcþèO§èÿ^?õoAÑ>lâáXÄD‹'úFåÕ:6EUv–ºõ¢%%÷²ƒ2íÛY0)úÄäàë½[¹…ÿ@Ùw§¾ÈKžh£˜"hçÐ5¢|Ÿ›Çj ;T‰ô/3ÇWPºÏg«œõ7oÙät2ôzsÑì$TZÆ"pðßÏÊwÕ⊹r$]î_ÍNÜödå¨>«õ{ÄLVÁu_sä³=ö³)_ÞÌÎ-µÌÛ”õ ocùÀ!;<µ))#<ç[Øø-žÓñ‡"aÞO”1B„‚H!ˆ"ŠÃ#)WÕ‡ƒåú¼ºƒ1ü¬z–×÷{ýSßíj…Ðuí–Ú1ôý;711<ð>U>’.œG®ßº½òˆÃpxüFÁÀk:´'¥#)Ü÷²dDw;C…ÑÞpþ+õ”$t‘ÿÇ•èïç¼CãsE{91§¢¶ÇÊO ¥ÅñO ahrzÃTÅÁ P[(ÓãïPOÆx×æÌ_¦œ¬u}ž+·×è6ì1·Án’_ËÌñÐ9+¤/šuøQa:ˆÚÔgì÷C;½[†Ø Õçxëo@óz<©S4Pû½:>]îÓ¨«Ù‘#)AÈõã#4…L[H˯+j9J™@5=Ú•Puí`þe!êŸÍêÛ»d‡Ä2±<üÊ:µßM㣨pt®!äCèV…ØltIû±`#4 ‹ñþÃhÃÊë#)®´÷?S<í/jfŒ‚NPlŸ£âšxs?x#4âœyztâƒRnOîÔÝkÍÍûžqçJ¥åTTKÃÍNýŒ~kÀ£AT8àIæƒdT)ä!<XD/k3ˆ·ä迾HVŸŸUÌ,¡êÝOš²ÁèÈPu:—¦“¬ûBx}±$þCTá aBÍŒvXXy‹•»+Þ 3Ûãåîóv_2¤$‚ˆ¤AÌ™‡;ì&¾œtzêSMuy¼I>“œý~?»Å™§LâºC¡:ù{®t¨Ûµ[vVVSöÆN ˆh9žÁ¤V7óökAí©µ=cÑíñˆˆr{)4Õ-5Ýð¢rìµþ„ã¿— ç¼9ÝÕÝöt‚ Ý”©8dV7Rɸ÷.»¦!@å@,²Õ[–ÕµCÜZOÞɲ~vÑŸã¹sfÑÂâ´Lþß-‰.óÎqîÆ×IF>#<ö B–ëãÄ#ãElfù&¯¨ñמ¥õí•Ùw¢#)‚#<õ`GŒ~ïç¾?~îŠZ^œ6Ì"ˆÃé,Œí)þ#<ñ©²Ënó¨·<ݥΎÉe¬ƒ€R6`)eØŽOÕ:2&:€P‰¶Ã~Å®ûöær΀€%ÎïÇî)ZŒ]½¿+}Á‡ŸGy€%éí5Ú”¤¢j¹ç᪇¹õp”’L]Ƴìau%<”WzïÅÁæ³Õ/gH[®ÖñÝüþ¥uÈ?†Ì°=FeE+1³yÓ¾A„–UɪgÆÏvÿ®ÇÇÚÈëå“Ý•³ü??tá{òûðç…ír>ó¤qvs»¶íþ{lÉgAø7¨³¥<[Â{ï»ý1¾'+qfx£;ã¾j©ùÍK¨#ò¾2ƒeò(¯.q'EPEøA_¼paNw’ÙuCúrûË¢ÒGš:Þל]R~þç§Eo{{VrýSãxŒÊ¾ÒÑݧz3:6Á´YI*HÈ+į~Ö§Àà5‰I¨¡N#<¦‹PÂêÈòº6™£Wáü8©Çªxz¤<=ˤ¥A˜)7‡†¾@Nç¶,ñZ†S·,Ê<XÍE[Þ Éƒp±òé·:¤1µYʪ„‘Ê'©ºô,µ¥vR•{ùV0›rMs"Tž…WBÁ#)ú(Å«¯+ÔU1($ß‚ŽZ<°Àßu¡cÆ2‡BaB”ßæ9ÇÕ…±Ï<Ž{b~NO^Y`¹Ü©°±WÍà)´Õï劘Œˆ…påa+ƒ} Ó:å?èk™ÞÍ@êñqïFnžnÐÚ?ðU‚Ôj)fe‰°kHcqÐÅD¯,#<®Â÷ã×g-?ëé™aÒÒø)iËå~X¸¤Î$r®g÷ïQÅ‘Û—"»C¶ÂqztvD:çÒÄû¿]ìi XFmd¹\1…ê׉¬#4–5ò¸Ò6ÆL$šA²!Õ뛥ûæÇ*XZ’æ1ܱeGˆ3pªÎÎ9¢•ßÛCWžÓ×.(1ŒSª‡ÑüäªóMãÔw}³£1á‰ÜÛÆsŒŒxÅŸ›“»â¿#<ÌqEø©ÂübŽ½5Çzλ\ꇨ1íé¬Á#4X ¯«ÔW‹¿o,ÀhÅ¿ŒúgíÝö×ôa"Xïrq‹°p`ÄÞË€¦÷mžÉF¸0bII-.ê7›z—Bìï~x™m‡Ñ:‰vúùüÎÅÁn¦vêSa΄ -#)ç(È…–öˆÓ¾¢¯K½‡é•ÁZܺÞáTE¢Ø3Hºr“GÏG#”hŸ9~RRHïd#<ZÙA#4åD{!#<ÈÔ9ÿw³ÓU¥'r‘™}ª#4¥Æ°Ä)‡OÞt¢wyÛ1ïßÖ¶vÜåÎ-×óÍ¢hÿ¹´4Ê)ÅÀ±'¨T3Ape-Dæò«¦ak*ˆRR½ZDìÎg¯¹;C¥[Kâˆvuûàû¢sÅ3‡É!Íúý,ü,t—ëÓ»eü¸àÙß«‰D\͈zw”ø;{S×}ªz‹«RSuH“†~öqÕ|¹o¿g¼è¶:Ó¬;±árž§¤“Ãàðؤc/ðwÛG¿´ÎK½;§tSžèJç#4>&ÙÅ;:*‰‘”£GݧOìÄá¤ôáWt±6Do ´iǤ޺ýtéÌ,:ÿ’’Sùsû¶ˆyÆ^=²Ú»T»¢ C“¿Šë¼åFOçÖj¼ß9Ëö¸ËHô×#<=µì\å,cöâ,zíòò±ÖÉ{žÄrj\nû.XB符:tøœY(ƒ¦olÅ,.èãÎ×לÒÚ‘55„S&ƒ#<4‰‡dƒåûœ‘R’+™#4Ø_jö«Á1 #<ÉEÛïèÚîK#e,,•ìsÍãy7k)£V/Ä{S87×ç›.G“·ŽÅÑ÷cĬó÷CÆmÝó1ãâðXë4²Æ,ÓÈ'l7.IåsÇG‡ÇÄrÎœìþœt5™F<JôªÖíšEq€pÅVxß8:1M¨ÑgÉÀPÉȯ±žâ»1Í}ÖV)§Eµâô‡M,æ#4°ã#<½¢—|ß…háܤW˜9;Kœ›çmvãý.âi‡ñ>×oT¿¦è6|¥OÚ@þ¹*£"¦øu³ná8Ž~Þ/ä¦î±îøØ:¬RhÇÝL#4ò/_Y#<ùø(bsí©Þ3Ä×͘<õ§å¯Û¦öÎsék•õµÎŠðÁœfQ|VðôÉ#<ÛØç(®{jÈ –MEÕ¹™jÍ' w… Oí=ý»x£ì>”¹'Ôv&‡üÄëã.Þ¨iÇúæôò"RØì}ÒOû"Ñ™ƒØõ7²Jqø^‘ù6©¨Ù‘Ù(³•“â0–ÄçíUï}¾¯##<œÔ„#ñç˜ÇÜsFåãòpuÊÇû1ßÛšàH›aá×æ¶ÞTdM±…D4w“Úš:âÎÖ)»a…+äî–÷éV&‡9ŽS¯x†-lò·ú~Òù—#4;;õ!øª:iÛe.â·ty‚·WÖc—m±ÙÀci.GˆøþɺS#<9Øh‹#<¨Q.P¨àÃJÝàº(±ýJ§¹H0êCLG†Q‹ÂvÄ»57!¯«ög×õ]†Ùpy‡úM…¨x¥²þÏ•ÀZñw±ÿˆBe.(xr]æz}8ðÅvÉ®<"åì§ù$Að‚ðåÓzVm6 X'¦c¢t7äîrìùLŸ¤Þ ×±?¦H_^ÜDÿIÕú ç.ÀøÔqÑ4˜9MRì8ñcKÃ̉~Æù³ç¨Hmûغ~Ý¡eÊp…Úyø ÁëˆÔ¸ì“Ä[˜†Í²‚<2ÔÆWr¥,.tñd”GM¼îèæôñÍã¼ç6ʽUuA畉a-ÒÆ:U”7ÁŽêÛØZe…%1O»'á}Ü(~…Hvo½…goºgCW€âÃÆÁƉfŒˆž¶ažl<¦Œ…¤õ¡x¢ŸÉÈ8„”$ q#<Dûï`ÔƒAVïw çC9Åv¨éT°{Ì‹ƒ4ïËG¹Hûüê+€_PÑP´d m)ùÄDlm#4‰L€FS#).cMRK+hF$Âm(L„›Õe„2ùUCÈ€™Q"òj[OXd}x´íPz¼²£ìåòe`ä8ÿ6ïºô&ô‚Ôöï&&°ñùëîƒ3”nó؇Ô;8pËžX©wÃ=ù…}î´¡‹?.æ{Q<‡z?¯;äé/¼i|˜Øèt:³ÿF<¼|LJöÿY÷¿QÃge\ýR_¿§ÌÞOm墄åE§MžÛÚÞb¡Äˆá#)^îgò˜?_ó²0LM;Ä#ìäí#4DŪ0$<#ù.ä,åþ~%"ÇyIüäÉD,}œ<ýUqáó6Ô¢kö“?pÜSøbšÉ@Œëþé_ØÅþÊlíþSýÁæ"mN>©æ÷ÐxÀöÓ{}ÛÆŽI¨ÇyçÅJ%þ?´ý%öÀÙláoø˜Y@ó‘¹Y°ÊrøÙâ«áó.n5û¥ÒŸä5”gõ¬>ßø4½?Ótu>ÿ#)qâ<ÞaüùS˜t˜.Ì1ÛÜ=6|·ßð»’Õææû`ƒ7¤HoÚFrǨoìW>ß³ó|yëÐ…$=!”ŽŽÁqÓ¤ybú§ÕÏœ™Ê%?zž‚74èÐê#<’ 5ŠEF(;=è9ÿGXhÈAzQ2µÌ9#H:Ë@Â%r¤+—ÚzÙó$’ü!V˜ØA{~?ßìþn?ÍûÇiû˜Ðˆ‡Ãbôrx#4¼PŠ0Gº§ÒhÖ(_’Í£×9¹ê÷bu$¢çûr¹ ;žãüLVëÅò>bâX#<SÌÃá³â‚³¾åêoÙªÒÐÕ=/Ô]ý‡îû:[œ\$5hynXÀr•‘m#)ôj͵7&¼’¹ä·¹@1&; p@$AÀcöe/‚½z†Cª}ûœ-lÔdX²Ä½nf°:`#4Á8Õsz“Wïšt‡æ0Ë÷p«{è¿7FñòОnuÒNêó—)ü¨ç¨)”éZl¤7ŽÔçP¥O9#<HÒ;Eï…—`è <‰Ð[¾AÇFÖ†Š—ñø«…æ’ÝAÝð4G#)°‡k.[ ¡ÙÙñÂÅè#)VE lp²±=iÓzXƒzŒ¹$ÏϲU!ñÒ£T#< Œø×,¤0µWؤùžì”y]†ï&*¢ÂM'DÆ ŠÆ&=|ÚY¢Û8Ô’ÂÑÖ‰šm&ÂÁ4gËï#4²†²#ߨP¶‡¾¼ñ‹¯Ï£„¤Û[“Aéd×íw4 ™÷õòºwÝçNXiÿ#ûëïZ09þ¼î#<ds߉èÜéGŒœâý|ÓQEˆ.àtâ%]Þ}¹&VîÍžh ŠÀm3µx8؈ߔ«R’þ<¼A.[åbÞ‘8ruwéÎLöXÉqŧùn[™¦m–W\©<ÎýÙ†zÜpœã²¸JŸ‰ÝŽXž›ô._2›5”š÷ó«Sž²¹¥ŽÀÒ~pèHÜ]íßæ ›¡ãF·Ãæ¶×[¢º `ŒàîNh?n6Ûh0%„èÛîGKò¹•‡%gv"ßLÂ@P§-œ¹9.*#4öÜ¥p[ö>Üz|†Àf¬oÇÈ.ŠoŠ#‡ Ÿ—\ôàÁ×u¦î‹&=‹m¼Go ³-!Ø {$PmËáèŸ"îP]ªŒC¯„[6³H‰„ˆœàAá#<Mõfä3Aç§m4Yçô6¹’eï¯ÓO+$t2õE•*ìã`¯gtþôwÄ„Öî>.5vv=“R•ï{m3Ô²›| ÁƒÂô‘À¥\£ŸÙ#<»rÑWTǃ”6‘šÍ1jëÆ°Ãö_î…þ݉Ô4hx¼¤¸Å´oµ¡ú¿[;—EG [{èôÑw«ãí¼OÊE½Êål˜sy\ðD®gA@LMQ^ìY›ÕGï=0„Ðqœ‡Ó#<#<ÁýØ0äîÓkbÊûúâªq£÷ˆoË]Dz‘=Õ¿ô^¢é ?¥Qô¯Øìì{ë>¤•„ÇuçöÉûç@zc(BŒmp½'{>åyLÃ(ÕA•¶è¥ÞÂ-á²Ý»oÑ;6ÚÙÆ•5ÛouÑÕ¥c*—(¿«¾æÔ›G½9Ói–èز”Ó¦p„ý…j¥ÚKQ‡ª¢÷Ý+Þ‚#ôé‚}fì;cŸ2Ö%láëWˆy7£)®EÔþ „Žfú\œ,¼œ29M¾R±ëRÍ8抪£ÛëˆðZZ-Ï»å_Km߄ꮾšâ^Í»>çHhn<‚i¥-Do©½®.qF}jåÍTb#<q—jË•:þ>,ÍÛÏ¿]_¹.œuõ³/ÝDº×ûG@È3à.ŠõÓ>–¦È555‚‰mgi{9ne×8‚ã³L#4Õ.jhÙ…dQlÂŒå]íÀè^Ã;Wjψ﷢'^[)Õ´f¼ÖëE¡ÒZ‚:q§ìož=ý˜ L“Aªgòøyr¤ªüWù±Cà¡žt~î•&瀶+<Þó¢zë9Fw-¥UTUk²ÖZK Ù°Ñã¯hùis××™*±]z|£š!†ãWgaQ‡ÈÂ2“ãrß<3{J®²i³}k–:n]eûÜt„÷Γ™öê$\Ï$vË=î± i=Ê¡èpQ$¹:0Åh–Ï2“)£@7\¸‡M8ù´n Gâ¨éÐTkK’Ûâ+ë^ÃçmÚ½ÆB°o½¶tyYîvã=™¶ºýüiwèâd HP«—ŽŽŸÕ“mu?Ï1sKø£òÑ™ˆX±¼;#è3lcX81°æo[ÙÚ¬ÀÉîiT^>½Åƒ“zXƒ,è l!¢×¨Ù>Qgôó—?y•oæµuZ û3¯ùÿEËøKrקό/åùÚßQww´Œèq3ppü=bí®î¨Sfy}u£Á#4Øpª†£7zQ‡ëg+£®ÜË?á«”!ã«G;L Ò!ßNcð–"<_=¾Hð{ ¼'ï!1põ“"ôvÏ3CËeË»9BnÐ4’G21ï¯vˆÓ*Áp¯¼ûÌ1tΘö!µjè#4’ñºš†òêí¹Lí׈nìÁ\€M¥j#<f@÷Uí-SôÙ¹¿OÎ-pµ#)ƒbƒ´ç¸W@Õ!hi·¯®·^úœA°Õ;€ø¾?NŽœn¥1P-E´\æÌ40ö9ÀÇL‡(gÌx¼4ý‹rpEËN.æÉfŽøfô÷ÿÔ?Nvþ—Ønâ N<œ£S2yìJúëÞž_>Ëöuµ”K`1¶AÚþ0DÛ—íØR€Þt{<<j<DV‚``#<QÀ*AåôsˆC†³z£ãåÀлûpwT¢ÅUú¹uš¿ÆbB8ÂáßDóuù7oÇ”S±s›Q9UFÑÃ73…*ü5à.rØ6ªJ¬)|/#¾CcÜŽçmV…µ^Eä*ÐP—9¥·|œšÚ¨´té%_¤öº'A(#)綧œSSÏ„Âkðµ©x½á7Hfû!‡#<Qòà'§—k¼<•Îbh#<¨â¨04¿@†¼¦ü¸²y›{#)û{À»šðî:XsOFöÞÉÙ/$’¡¨HG²ïÓvìxÜ"ûÚn¨ñu߹Ä(*f.Þö©"H9r~ÍߊÕç½b‡gA¬¶y¢gÁÇôíƒÍUp/ÆiÓj¶/æÞçä“Ï=ù?ñš‹PIGm“´è)|'*wßvƒ…3wX{0É:·¯ðÑïQ²{‚#<¬ ³lP% Z# ¡˜ÂR‡66^R3×-.ÙjÇeàlî~ãýí¦Wç§{Ib0Ï'Y™”JÉ_LÔRõÌ@^Ùœ£c—A½”¤”ȶlHb¹/¡§»QݪêŽ{‡7ͱW9ç;SVÀ÷@Ú#Èøïsˈ5ïC¤¡A¹Cn¶†Žâ¡Â%4˜Œ¸vŠr€y×_ Ü.Â7‰5ïò¹ÇŠ®>L§\;3·iÚ3x@mã²#4–ÌûRÇo·MòÆüYÐà],Z`°C…±=çJ ÖÉûÉë~ê•×f;í>vÎ&èƒÒ9†:ú¼™Ó¿Ü§©lZíò~ÓÚ¥ºIº=»ñ.á;n]\TXú > AÜrØ+#åÔ##)ȯ/*¯¾æºÄ5û(‰ŒdK}VZXʵØéP¿¹ÛDìž‚9#<ss¿r î+Õ%<¹Äï<7ôzáZ*oa¡QBì¡ï+i¶:ÏT9^Ý@ð´ÒŒÀ\DÒE1êHò_¹´5uF"uÉzô nQ³¥—Lr:;† _£±ÚzE–ε›—INøÁ’†2˪#4åÖc0Sºk N7ZÓºW!ízðg…ôGµœ®iëÑ-U 8»¤A•~An™ÐOo`ñ²ë\ìKÀèp! Q…ÅZª4íÆTÅöŒ%ª®K óçêú-ضsŸ¤ Š¶è‘hCE¨j"äz§U7ß Wèh:„o#4x’s ÍÃ[æÕ““T` ´( |SÚˆhÛNfäCc××1†´Ö·=ÝŽ¨d#<˜Nñj~€B®å!öË¡Ú=±k#4·-½ 1F£"::Ÿb6ůu’ˆ°Ö(§¯º^Ö(âm D|O¢(·–ëã8ÌÏEõ§‘Zuh5µû³Œ×½ïÅû‘çÙÑÏVDî…Ñgᆪ†„=æ7‡$Iü#)ÊeµúãÀëa ÉFNÎÞ^#4>Δ"oî?‡KÖdÛ#4ã°÷Až{õƒ×9æXöðp>äÄý_[Ç#4•Ã@çÑ¿,mÌ\Ém X@)¿Ck@-Úè#<S»lï›QÆÛ9Ú1åÖ•¿Tæ.ÎÓ¬ŽLÒè¤[œ}#4?}3'¶#pc~ÏL}©ÂÆÂë`!t‚ŽAG–yvSÔö_!±Ïž—¿HÄ޸bMâfÙi €C¢£Ò”dp0.£ÇÇrÎuÀ8%à'÷9ƒlò†7"ÞKYr³€É«bgz-áÛ¯*,¡kê›T_·\É~¤aë×ïÔ´.G@hDËYAt†úJÙP,Z[ž—¼»¬‹Ê‹Éíõ—öx'8B‘[W¨pˆaÞƒG$8‡… qÕ:À/£w<×à"&£z•ÉpÉQ1 ÜÐ!@nŽ~hõûáeÂÙ`PfGH7tÈS…/¬c4Y¿¦þ_¯Ú¬mPìTi)¨#+ÐÂZ-t$ðÌ#<º5øÓ ï¿x”ߧg6—|–ݤ>SU#ÿ„ÇDX Ûý?^ÞoÑïö˜{¾"â7 èo¡Ëô‡wSç!ª«J€’?è³Òüî¥_*Š$£ï²Ö{óÐRD**Í|¬>ߧڙMZÄÇ–(Òtíöuû{IÛÈùM#<B*ä`V^þÀƒñÑËKE°¾ÃÀq#)“û>JÚž.sß´rs0ð‡Í>ÎT^ÂÌ#4Õ£N'Ôe:ä®Ïø/W}½ypýƒáYþαâužXb2·(þ _¸<Àýæáú!hoRí¾|ø[ÐØ¿ä:"’páËuñ1Q&½fÂÍßàr÷Zü¶óþ/ØNY[öã»øôõÞßtfºS¬ªB ¡`÷ˆB¡ïCÔ¾Ïì€w?ùáoÒÚíúMDŸåCûB‡ô YÁ#)._ñÔ2e_þŒRáRKUP©ýÅ’1)K¨vÿY¢¾hŽÿñ´4l€G`ÉEp#4*7ûKB3G[BÙþæ?¼¶ÃË=ooeŒœBíÞÜÉt’ÿí›ì”h°?ßVîl?8dø&`w£˜âtÖ‹îêï7Šš… vIÚ܃ÀëYsfÒ—†ÓUC—T!.e£× ÈWh’C®hÞúD+ª/ÀŒ#<†³fË#<Ý„¾#<"îýðÈ ÆqŒ@oX¼²ÄzÇ´ÁÖÖåìÃÛ×L=öÿ{éṙÑ}Ÿi ˆèk†%¾ßj; ·€ŸçþÈܼ<mAwÐNá5Ô-‘Å…î\þ‹ºJhdƒ–th?•ö` >ÓBÒŒ‚v?>¶î)è#)Öz×áê6 ÿ5…DÇñ~ÅíØywÚ×SzSxßÆ@^#?»*¿T¶¾a–ì2@´þôìüjmø„æ‚Œ@&T+Ø{MÍ2ä’ÄÓR«Õì‚ù—ød"9’â¬#)«Î¢W‘Ú®wõ`Ûìú>r¿¢Ûˆ9{5-d#4›€`å@J(Ùͧp#4§í{µ GUúYï>!½i}!ˆþiϘH™Ê²Y*«H,]ø!púàÍÇÞ[S/•-AõÂÚaþ]¤¥_Ö%·—¹Ðê\ià~(pÿÜ<ÿ‘ ÜyMœÐÝ‘ò"X%.3Žx`³]#w>bЯæn%@Ôç`óÞM4Pä$C—=ɃNðµŠ.Â'Lo£T–OÜÜ¡ô„iNî2QÇ´¯¸…DDC¯55[lÚœ¹'¯åÜ kСgê{#<aÐqÓ¹·P&Å 2ü;™SQ¬uìFÕ÷£‘èGU£øèyš_7²ÁhÂéd%‘Œœ°P‚‘.Á:“ž^t#4–{ŒlïZRŒ¸@.=[Ëd¬AîììÄ㨳jjÎâ^£¤CDj«€†-ˆÐZ|¤?JO¯òÍîzð@Ä"²S#<ò½zD]’}ß`^äñÿ?ˆì˜GÔVfÛxjß”3È1J‚<7†_Úä³Å›mÿm‚±oñ#)øçÄyæMÉ-Á¾?mü‡ÂûX` !•R¨‹Ö!Á¼"•ù¨vÖ]dÕƒ*=J2:,sJ¿à³¿|a°Ï‚ßûs]“cÄLD#)’J}ÓåãØG}%+_·Æ€ôʯm½Ð=¸SPÞòѳ]:§Â{OÒxA“,£#4‘¦ë&—}]PÑ!ú±¥†]qñ!ÔÁAÁåcËóöátÒô™M¡Ãð™ŸZÿs;Ë¿nÀÕl¢xŸ‘ÈDDb;M;Åõm(Üí6‚J6ñĶ`RÀÖÞKH2OpÒ{|MüÝG!†~¯ÚURÃñ<¤qü£À§qPITÂ#4DK¡ÆçCc{²u„:ÁÎ&`t)Áÿšg€js‚Ý¥s9wg~“ð¯…ÖVj#7fÁT•{òS²gkS—»¥—"Ø·F8Æ+`n*ìhŽšg5Jª£®›]šÂ³_Ÿ23›ß}àt‹ÀT`S8t-o¬Pþb|¢—¦pj3ݸò˜9n#4o@îpua±å˜^!»#(p7šÇ¨Ì#4Px»½š´fijl\Š,Ü¢—âF”õh{0ã'Á½È€7ëöÈ‚`./(‚@ü?ÓÕõhô}§#¬…HÅý1mãap¼ "2§Ê”'äOA€<‰?s9Áã|¤ñN# 4ç´Íë}RÉgú<â”'§à¶Î;êî Œ¤ lZ( @(`á£ü~7Ùö1-*<#<`PŒWÀmdX,ì6;4†RÄ‚ŒfþI¿!’ñg|Ã-0/ÊÑØ¡i¸X1#)|¿sbQI÷’?¯/SjÓ*Rˆ²0D‰(Åã8º»ç| Mäaºû»Æã‹Ž#<ƒ|hˆ@1#4moÕ¸^ÀþqH˜›œÅ/ Å3"ˆê1@‡Ÿ{$[=_oÛþìÙöº}ÝZH4PÓ"¼ú”ÚnM?@=CS„‡˜‰Ñ“½naŽP²ˆ’Ó¾Xbˆ êxïi1P #4FDñÂÌOY˜q‘N3¥öYP~‰Jž4/.ºà>—zÞjSÜ–Ã>#G©¡¯#Ž%Ü€z‚ÉrŠH符J`ǘ##Z¡‘fî„gwBNîþ±‡eýá¨Í‹Ñ÷ÆaPD)kÏðâ«ë#4]FãÚ‹È.YÓÞsì\ê@#©²Bø”\&Æ\÷:_ »ë:*#Ó@ÍY$ˆÜа`6‚j!øŸÙÕúßÇöƒþ¬~H2ʸ32ì®õ þ9—Ë£%¥¿eÒ˯öÜ8×OŸ:¤ß”¾vë»%Jlc`Ø?^#Ô³wý?ÌFç†KK½Fá2™¢WYY£Y§2'¨6C´k9 Z]`Þ:Ë2¹JD1Y7k¯$’I3Uk˜©šÖµ%¥¶žf®jË^³™Y„uã.¡Ž½aGƒo{ÂkzDhhkµ%Ü7‡±ëXêËŨdÐKT“Ec?æ2W>¿gÙ!QŠ„„„@*< /ÛøòRÈñ#–/í°:îö MX¥ƒ~C«—•¹ÇLâÀbï…>n¥èɨôÍ:BA*ð;"{6ßñC‚¢ÅÙWOd?‚Z( çéÙëOW؇*xp•w–‡ÚC—}¡-ˆÀÛžäúUW$Ü#):ˆ ,„ϱè>5Ĺügñß³ûÆÍÍ®`O¸[î)_KBê™AÈÓd㻂¢Ù¡RWz»5ƒ‘_Ñ$¨m> ÃÂI$¡¶>PlY;>Ûq¬g&…’¸izZªÎÀ«àU¶#AIDF¥#<1#4#–ˆ(êðp4ïZm½CL÷ËãÓCk/À§™áœužˆ¦UJ~Í1íÅÒ/™Xùp1ó½Ø©µÆ††§œ)ýfÅ›ÏHu>ts.œöÐ%ÎÝÛö†ñ³’D‘c#i„÷_¾þÓŒÝiJåŠ ”Êb‚ÁZ ¿M.¾ó½¾Ô½òïAˆÐ+äMb±H¾"¨Rƒ_°Bô…¾;bøý_R€„9>¯â-ïûŒVa¨#<tYJG¢äxØH]~í¹ö·6éüÌ\€ó¹BW#<P÷-ò8žÌ9:îvûŠ¦ŠZ¨HBC^ÜÍîªjeº“K¨Û ÛB÷f‰eBñ@00” |ÒÎt˜Ÿ£X?NEgÍ+ˆni]º‡üj¶›·¶%Š ⪇֨î'kÞÉ=bˆBØ¢Ãd¸€H‡™ÐZH~ö)hÈ'x7 1v,Â1<wËø…ðÉi(^A¨59Ü[móê¸ñ@£P.£÷D0ûH…Ÿ«^ì0ŠkßöÙnæP´#)!_àãïûroqö;ÌJ…òÝÄpÈ}6ì¿#)HAHjO{ØT¢ ﯮÔI'×…àש%½/`(Ùï U¢{¢¢˜m6¢yË[ô¥I‚õû®Ü˜=0}?{ý=Î×-Ò{ÑÜtŸÔ‡&Ï°l e‡O†aß9›qdPˆ0¬#)óI ˱Ìãµñ7‡ß-ðê„XÆœ@ïíý/Ðì=AËak9ÅØ‘ uù€ö;6x§ã7¨sO÷Šv‘R‚$nŽà6>]¢‰0™%â+RêÝË¥wx»Œ0pv´b#4AÞjã°%hè8ï—|îĦÒTþóg#)ó¢ˆZqä7ªýq¦•E¤ÍŒ&£iÂ2}çZ7ô…ý‡0ºp(úÌJ5øê¹Üœ¿3k¯w#)3ÓÂSä¼â-Ò¥O~žÃ’§ÀŸ×V·ÛöhQöq¿Y?yû‹¡_PÿE€_ä†ÓõŸˆ·âsî“oÆ°á0,JÛ0~|nùê>@ø}ð_GÛ^_U¬#)f™¤>’FŸ,±U_µàêÜû5íþ]*åêk#)D€>¯?’¿•"’}½b'\qÖÔjíèBÏõÏwó:|½A‚=Êgã¨Ñጵ;üqÜÁq&`ß.©Ê¾=ã#)1¹èû¥†Ü,¦ßúO½Òz#<#4‡îô(öAdá.Ï°ªþL{AŒUüªO¢#l”N±Ü,‹ì9×”#<npbMA¡ay–¡Çh8÷¿¬îÙ,sËñx3%û“xäÙg«Z´(ÉV-U¨ˆÓñªªLÙÈ£°˜*þt2ƒ:’8ØÎýù8D°X“‚S/÷R¢rÙØÖ¡Q“•_w»¬†d@Äõ*ˆÆA$U¤,ã«×7Ï›¶!Œ£ak¬[‘¶A›ÍææÄïzÊ蚟VÿRlçÅÈŠd#)Þ?qãªe†‰ ²+ |: %¢Ô¨Û×Áò‡QÚnmb%#<m±ƒ?.Ó¼ÄO”F«•Ø£eR°k—~§w4DÍÓÐäzE;Àæž&aóÉëÀÍÛç0Њ‡ˆi°Ói¹ì³ÿÊ–ÑÀÌ$P§`³*¥¹à{{þ'ö>Êù'èŽ~îÀ©†avY3*¡ºWè·óõ*ø4’£¤ÿ,(Ú£ÿeRR±¿ö¸Ør+¸ã`£)$PXuÚðÅɌڕ(¢)TMVn}]}óÔwÉÝ<ö¶ã>5õΰqâ{ 9D[2i}'â„OZ‰oêú$—¤5¶˜`Qó&^çj‹ue†OqÙ¯_¾×= PìXû ö›>Csñ›:‡q 2¡ HaP˜µóSÅ8hýÙ*Ûp:ŽªS¨ø“$óÆü³“™^¯l)ùbvÝàgÒ¿mß»^_,eyÂ'€F>:¼&îœK0Fá>ó#<¥z…#<…£o¡ùLžê¶2UÆá`)ÐÓD{û§—^ŸÏ5@ÅÇ¿ƒ>G`Ñi(ö3ÊÌÊL{•”¶³¼#<Â4õܹ™ °ÜÕ[Ö(ÒGš1+•5XÅX%Z™2iq™y÷Ú¨¼£ºY$Aî'ðr#)õŽ… îˆ~jžKO A±±7¦/`=°Æ„ˆ„EX”U Du.ñDÌôçÅ€$Ò &ŠYÚ‡¥@ñ¼Â]ȉ#)Q„ÏQøh™Ô¢hªšÂÈN§yM)Js *@J#))c#<Ié}¨lØáù"S»q©L¦…¡•Qt èBˆÐXƒ!ÓüǶ~3ôñ´úÎÎ*ÞïÌj¿]R$R+ÕF&³çD†<-_:6’`0†˜~rÙn¹*!F‰LÂ#4+ðïm¿r«ã\Ý#jÝÙP”A¬µ-»¯^˪ÏNÞ‰i$#<nÿÁ:‘õzüÿ~rÿXpNCλîôÏo¤=ϳqtZ#<Ëèºçòfˆ¿q ׫>D3FcÓÌóFÜ’8†`XãúÍoàh¬Ä?Xa7,AoT¤Œb¤ˆˆÐ‘À@†¢N®‘cPñAÁ–9r»H¶ÉO/'” :ë¿‹³wiè¤Ñ„„%Ï6}FûÎÜ©è/Á~ÅB¥æ;†458:dWfÑýSÝE$åZEfþàÈꇔ#)#4g†òŽÂ'0/¯aFú![Ghrlœù±ÌÔuúçmô·ámfXKïCˆ§2€ÜjbK#4Ø[8T#4CŸik·£ÙùlöÜ‚ùwû^Û«÷Ûeþâ|CÃâãë®E(Ê¢”Dääû@þ>ãæÀ_€´©^sÔwÙO¹õÝ>¨ÿ"‰#<Òié§Ñ±£¹½º•À†2ŠŸ×#<Ðú‰’[&\2™´Èåª/Y‚¸ &PÞ…ž…’U’IÚ!0}1²7B^t=bÛÀÐ0ÒÖ©‰ƒ2¥¡ã”Po°9þÏkÎí¦©)‹=õHª]S÷Ìùs¢—èEœ=”ub‘ÚQ±1qBùv?н‘!¬D×HŽ*BCšjÀDbý@tì²ùŽ :º¾_hõ•%¦ÒŠÞï»X¼ƒ°Ü†HZL:¸rêÒ )Ü’Ð{SÕe TFtƒü“ñ#4ü{ÔQWô&óf4&Ì~•õut$yKxŒ+¬xèZ#'PéH¬Œb‚’Ïv!Œ®$‹½}wFÍ#4Æ8<´ˆgÑ¢~?äûd/ö+³ïÕšäéË~ÙVä«À2#úãì5a÷~R°ÏT¦'ë!Oðæ{>ó88Ã×”×",¢ˆ"ƒê)Uhª=ƒ˜Æp ÈKœOVƒd_‰Î‚ÿ&ÐTソ‚ V+#<È"¤£à…”d«Ù8¬2âÊpㄵmÌU!ˆ B#ò„H§åH¨`üˆï%/©Çæ:a—Ð+·±ðëÎØòÐBÏ’!H¥,E‹dÀ(p ü`í½÷9†îíEŠ@Änr裩5"š‡ò}c²á®~¨]9ní5ïGŒ7BnW°>÷’˜Š‘ÀºÉp#<˜#<$³5n³‰’½Ï³Põ)Ð:PnyÀápÛ$…”éúlƒx‰ 4u;ÃÍb—¥Í¶eÂì‘:mÄ`É—»OÏYuEY0daTŠú[äD#<žCÕÉñ¤…8ú#/o<|·/œqg{³ÒƒN#<ªŠ¨6vf^YÏÊ¿CÃN,&îºA;زC2¬‘ºõ›H|¾§ŠØ €t#)BèSÕl«((—½óûø^„‘T4ü,-‡rB„¸™“¶Øœ&®íŠ1ñžÜ•|ìàÌÕ’‹²wlqȇǧj7p|Oðk“ùˆy糈Ú0dã>›»†/@õ’*ªU'2n›š+tî¦)iô楇Jf¥¬(A©|»„o*6cvÙamhz¼§Å#4ñ<{ÁòÊÁàpããÛÚn0Åìø$ŽÖï:WZw¶q%È¢æ’Z¶KQ¸î㬄qs©2:ÉÀ+äPw—^˜óàëé“(1ϯWK^Þ‰=TüûDx Ún#)ê#)`âäO†´›ë‹ÌÐ&½™T=¥CJ°€;Ôw§3ƒq¤bB9’1âA«ÀFÑ,1@)X'#}Ê+ò„ó‡“Å1 ÝßÄ–BÏ ëÅ¡¨¡!‡{&")3CXrÞî¾}‡íÙx`G·YtaHÊÌüF—Ò¨4À)<PØêBŽY׎uÛ¹S-´]£Ýâ>61(ä”NÏ°j%s÷£[ °QK—¶ëÄèÜe¶(êû“ëüÇð휫û@#<“P}‡ÄÇÍ鳯üE%ˆ$wÿAñ½4²£ÖGSÆñýµ…†¦²gDÌ{ƒÃ½Žs8Cʃ-ÞÝÊSœ“ÔWÖ§¡QPuw¹Ja&?©ÊË“(oÛ›+÷¹úœ§?CÎj·ŒQœž_êÇá“wgYÜ~ÍõŒà9f+|Ç«Í}6|ðÓÜvð#4mÊ:1vÞCn•/NLF&0ÓIeñÁŒÃv…Ñâ Ê5YÇBàZÃlèb¯_/`>“»ÛîüÚ¥UsγõœˆYç#<סJ’ }™–ÉÚàh:]Kaä#):d\~øôädUî/s0ÏÅ#4H,yÁdráBA#ùû… ïà€x¦Áâ>)Š„@ è&Z*ö‰#)¯KÐÞìå««6Áhíïý¼á7:!þ£Ûa.¾ÅÃË’G§äÿ5òLÞ"¼¨Á6r#)9¤_õâbOμÛU#<ÔèwÙ[Þ’G–ÐAlÐÖ´ªî°YìÈ~¿0ô2lj®ÀÚ«U ¡ e4#<ö}Þ¯ÓöžÿwÊ&'ôUµzT¼Y#<½À’‘F¯¶—uÛÝ»CËy»uV~ÃñÔzfôŸÖ×#4jqb?Ï#<°…#šç&¸üÕ2ì‹žxàÕrM¼Š4ßšüètŠvFm”Êë‚U¦ñ8˜•WæµÔ°#4èZ´*怘FB*£}ߟ\þ>¹Œ–ç6_µ‚ĉAúD‡ÌT?#<Ïí÷z}‹ì‡®Wm¬>‹Ðú³ø~KB'È03=h1µÀÁ>‰p€ü“ÊX÷™¹ð8#(#)Ö8É’ü»ý×Gyæ8'#)rn!³ˆ7ªŽ}û#<„„›X’ÓÂúÎë Í£M¶jÛhC¨HCiKåúoÈÿ#»ˆ÷ÛV¬µ™ED„B2Çñ;]wweggUsž~îLUŒrØôÿi ZÆáû1JöÆæ§EˆbZ ѣüzÄm§¢%#ˆÂتm±ãjÉûKpfH†4Ó“@@M ¶2O‰Ë¨ÿ¿Ï‹ï;UçÏÍP”ÐvˆSÓú°hUù¥ÇæxdMiKøMÓß=§ SÍ:dÕ¸S_JÒMDàsœÂpOkí3óÎNÿ§.XIõJ#)¥9ÛëðŸ¨sÆñ¿ªjÎn½Qí€}Å᯽Ð%zªÇº¯šù£°1:¿»¦µÉÃø¡#µïT.x˜ØîáïÔq—œjÿÏ¡ÀöHV@!!žEC*Uø{ˆ'¹ÕýZwãŠ#§ç«,ÐÆ–½Ð÷¦Ÿç`Â1&¾Û8ÃNõ°¡TKôÔhÉÒjN5Á>Ý]¼jœ–»{Ší1W´Phf/?‹F¡#4¶#)Ñ€\˜#;Ø`™éÄ#< ™Axzú¢_—ôÃÄØÚ®%ˆ¦^8ntA)#)#¯Üž_+½²åÑE$4b†mÉK×#)ø…lô„(„YlÎLI“Út<€)¼-0e„\¶_mš#)Ú–È*æ·ïü—Ä—?“_SÔœ‡ÞJ>ûÍAø~JãÙÖK´Upæ‹ýNT-Ü€Çè’{Ñ‚‘7”3¾/…¦æŠ˜Ñ#4Ø‚²’Ê&“„#<Ñ3{t̶OSÌïw¼öÂ4¢Óô•x¡&Š–È`dÈ‘F$°a™Üª ÔÜ~¦#<æY`8œIÇf,´±Lª‰‘„he*œÚ´-SEðŸ°dkúüa˜v#4°†þ°ó' pT…™³$¯RÔeœéD ÇqpÄñsn–îÿ.6«=kÍèªoîfF¦Mɼ7$ÛdÙĹ:1WêtˆÐŒM„ØÎRÖ…ÃÚ›˜Gb?Üpˆ`±ˆàÞÊÏ|#4!ﺉf4›AÏ• Ñ¢aýDOû6¡7øÝõ=vfÃO DË #4‹ÑoÔŸ³P#zÔí?VÆa’âÎdT‚§óÏñˆXv×àV&@¢¸¤ÝA“¸¬Wíj\Ìæ}-ý °ß1ííŸÎ!hÆ¢#)`øÝ~<=$?‰ý*ƒB’ÑbÂÐ-CJås8Ç, ~!q<ÍsOSŠsþâA*/p€P¯V`¼9Eu<#4XNÚ½+ 4YÈði0â¨n†.nˤÙØQ°ØØè…]°Îj2/z‡¹žÌC^á{†³Kµ ïP{Ü‹A_c²Õ–2Ô™i!ahª,<’ðÀ`<J#¸2;Û൯xV©ó>~ê#<oL`†p8@¼j-!Ûë‡qI2Í’BZZ±F#<~¾0`#),«ÃÃÌ #)õùäØ¡ó8(<½#40õõ*2…½Óî}l”ŠÏq›?MI¯{þ‹+Óˉ_åœÞ1ûa˜Þs\:ñjØóëôFp”eyxâ™V’'A¿×þ.Àe1T7Ѐ|wpÖñ-õšÍÑŒEÕ>ûèÙ7ªÛ¿_#9Œúš6HÝÚ8*P(3ÓÚÉ=Š!z‚%§¸#4Ò¡_’s\ì)(>RMï‹8ÁmöÏò铆§C:÷b»:£¡žYå5âà¦ôJ&·±ê˜Šf8g:óA%ƒZÔ¢ÍìÝ#4|µÁ¬úˆÍ/ày•ƒxÛ3‡«,ŽÈQfIäõ|ÐsTp²ìôÐÅ ÐXÐŒ¡âWAÐEعìÿaè‹Hß+"îZ½¹¤¬3‹³ˆï¯èk¹;ì} ÆUÖ‹@Ÿ’wòí!y •õ„ |Ãwµ˜rJñù¥ã7õ;Öu°•#)„À‡Ç »ýŸäì1)Í5¿Ïq~¿Z¢$!#)æz 9@gòýÖîû{ÛŸù¼ÕEés¶p•G2!ž?’áÞúï›ðÁñyEðŽtx~J½ºíE8dö||fh‹DÊÔL#<s‘eÿ#)Å¿›¬#<êÅîÖDÛk•B«/hl3èÇ€ÜÊCÖ?³õÿ¡(5‘µB¼+µ‰½è¿Wß”5‚‚wØǤLMï²@?j£ÈOಇïÃ÷&ßÞÎ3 lk´8wé»vú¶ˆè„ǹÁÇŠ:ú$øAGúíçæ~f–xÛ•›±í¸tgCÅ8¾?~,ÍBÀKeÕOåÞ|ŽB=¼ºröÞ=€hŒs>²à9Añøx|Ëj‡üŽõðeèì,3ù®XÕ#4§Ôñ=ΉïŸÒßÆAhèåz‰ Þ¢nûŠr¾ïÚ¾¾QlcáK±rã·‡Z/vóé·©Ú£ùe£/Tìr5]Í‚/„å«%Ž¯î½sunÒÓ¾PÓWߔơ‹¥¥‡TjÃ"ë…Ûv™ÀVÉ~‡Ž±îeÑp¹ìP21!7YF@ã51r#<¦ç(´ÑÔ|‹Ðpln¨çÇo=˜“n—àUƇ3ˆ¤ð»?¾·ÇS½_,—”óˆ$ß™XMÝn€Â0ƒ„Ì:ìíœ?cxÝﲞÃBÝ<¸%K–öÍÊv½"wZ³;.Œ\cv.z@™[66.›X!¸¿ÑlJå'i|^¶›Î%/…Ì#4-ƒðUK¢ à£;U ¾e“ÜG\ãœ6|òþ=9ƒœ8›ÅÀ|éÜÄ\¦•ÕÜ—m–'”ã/¨îtËôÎS,Óf^Z(ÛŽÌm†´3U=hs[¡‚€‚Å(`°U!½‘€ˆ#<NU¬òNXã—gÊ1,Θw<;oÒ%ôq÷v9þ·çaÄV3ÉïýÊt-¤0á£cŠâð3:î&!tµkÔN#§ýPЀ#)‚+Ä|¿»Íïawê—¬ÖätŠ6翸‘G‘ûw¯@a–åpš;{+ש]f!ÿDÞÐËUn²+Â?*ï[ôׄÜlWéµYpÇ‚5'~'RÛkQÂÜ\iEŠ©]b\D>l$ÂK”ÂÄb=΄¼‹¥®¡ÖШÏi£0ô¼¯¶µ»%þÕô÷)×/§IÙp ´_]ö©·w•ç”HfMù$adv"D¨¸#)ÝDHÁs#×uÕ×®>sÊß»U!¢v0¤?RO²Ý¨²çáŽæøQÈÓ›Ó¨½5>}|º‚ý%ˆmmŒjÇô}vŸŒ†¾°ýûÀwËá©€úëû“³Â]â¨ïÃÐ7‹Ä¹ø]xÚ¶„àG5þŽ‹ïDЛ›wBŽ+ÒÁä#)•‰ë)˜ò»¾ÙÈäFc¼d¹d¿v ¹ 8Ôþ#)gýâ2„ƒ+6ö™õqºH™›ôÀç´.Bq¤#<€•,z7~åûœ7ÉÓ†'gŸXl‰éîs®=)ßåÉP–1cÀˆˆ^p¸Aî%áêä#<mÓfm²§ÛG#NibÑéqpÞçîz_êE<cP¼âL¡ª&\ŒÞ&ê¼á¨ƒˆa®Þ||ó|6‚ù4#¯qŠ™4ÒC@qc&Ì”¯z¨x8aø°×Ód½xC´2ùúyÞ<éøK`Ôš)ÑëÒî¼4ÝÐÚ|ÄÀÞ{Q,#4ouõÊÌJ)}W3¿&Ñ,g.ÞH‹vªDÃÈ™˜GÁÂNÞ¬ø¼åÝ-hwª¬Ÿ|á#4Mº²ez@˜Òz+‰*•Ãˆô°¬†îxŽÓBEŠ¤XFÖådº¬‘å犯GDÃ"oÖ±b¸®4Ãçœt²÷>tø´mOÔ’TRëú»hýsÛ†#c#<8–(³ãȈHȧуý~õǽà{ýœ$šä³ÀP‚„ u³ù÷ h‚ŽãïFZóÇ'2á6^ÿ†«È݇碑AßëÀÿäýù{;ÜÜàȤ%0ŸÀÃ÷þŠô™Ñd#4#IÎX£ø´Àý½Ø¿À}ùÿ·úŸ¸‡õ ¨3·¡$»/ôc‘oEUU‡ûÏCº˜ûöf0ÿY½&ÍëlÀãµÞb@9•¿‰2#4}Ü^½ƒÌÜMÍ°éw `oÖÈdÜo‰(á&¢äi¤$¤HÅŒÔòOí²ñ#4ºM掣péÜýŒf’«=“ Ù¶;ªÂ`Àò¦5žË0³²Inl-˜÷3{d–×@Y ¹oºF;#< ¤kès;éDzP}Xá BéÀ:CV°í{Š8‡$:À;‡…øpq#)ðÓzb‚»±|Q£Ì’žŠ!½S"SDk3Ì,̆¦VÌþóc¼ãözDJ*•Œ•JÕR¨ÕØvI¨u;2Üï3ÌÔÂP-c°ß͆¬09»aÀ*Â|žGñoó%}ÕÛüèS€lJ@0€^6„&Àès9tŠŒCË¡å2uuÞ‹|%Ëk˜<<Êh'py¿.ï¡ÆøØk†ìXšÆoŸLß“Ho<¹œ÷ÕR®QQ—™ªm7{vz9„˜`ØÃA¨kA€±4#ˆ#<Q¨3K@E€á‚y#)Ê;FŽÆoª7ahgƒ1(Ì°®*óÈ^g¨ºxv#<Á´±EŠ(ŠéÎX„¼@Ú>ÿ«]ŽÊé«…ë)’ p‡#4¶ÓGBM{¹™Yý³Øøëô#ýõ@:}¨ßO››“e„…;µwsµ“±týþþ»Ý.DþŒ>]±7ÓîîõÔYž‹;7¨T8§¡ÅA$8Ï¥¡oéz#<4.ì$‚1‹ƒ–”ZÉaÿnÆãBÍ«Lm“V¤¨"A¤F0aì+@†\iþ`—E C?À€”帒µ+ý&Ü™^¯ù?êêßSZ[3FcXßàßk¬/kÚT$‰¢FŠÒ•D²0~_å5§¸)_TÔgÁˆµwmÙ“Ì wá Þþ÷ù@!÷iœ0dõ½½í`B-š¤B³—õu5·úQëÔÕV+áÇ“´®vv–<C…ä‚Ù—¬,¹—5ÜC[‘¡¨3°…´V¬„Ž-á{×Â,¶®MBwØ”uq¡ý󗛃TäÖ µ‡B„Ô±¾42½¡Cs|“™„aÀÔ„1lÓñ!|+8ÃÃèM‡³ÈsküäRAj¯1SÝÚ9‡â4#41±¨4EýDTß°ÑÊ°ûš`ÓÕsãå}õÛ öôYÖÝùxxàfäMKÁ „ï°»¶Ý‚çäp!FøOx°ØÛð¢Ãœ’áÔ^®8m¼Œ£@ÀÏê8<mÑ›ˆ¬ì›Ÿ¿†ó^–V¿:·Ô¥ôÚjÉd̆f¨²—!˜º®€p41x¥ø“£½ÆD¨®ÿ$]Y•Ž¾z¬ç1V0)¾\b-÷ó¤ç<K;ÎX£Œ#</4‘×µù»døË!êÍ<õËw'’^}Ò… y2•!©4z›o&îûîœqJ:9ézTš70²#†ìÂmÞ3¬ÂHÝŽ’ºëÔD\·Tlä¾pÆ·°øœ8hBìÑ)¶[‹‘¾ú‚;J7°¢Lœe¡Ú¸âÛÅÈ7wíè*¯wF›÷£4{qQBóQJ{1mÙêzõDæ`[3q¼6b#4Õàämaa3I¬ÅÔ|?ÅTÑ[;½^Ëš‰Ù¹Ñ8— fwÖd–3žX($®dzI#4:pÛ—w?_—o2BïBø)Gµc;’hhÃUÔèã»#<.GGª:ÖÃzÀ#ØáÓ¹[9¾Ø§{Ǻ(5•¨šPc3²œj6ÄQ†´i©x6°¾äŒušøƒPÂmŠóYa¥y[P¶=Ý»¶0ùêl“ C$µàˆ=#)°ãA#4!rqíg#)eŽDñö÷GÄ4$y.—1®…žnp¤×ˆ_-›Ybœpë+Jœ£ÛÈZƒ¨£Dô:bë9 Ö9CUöUÈÔ¿aÚóñf&†£[Ê `"a¬#4°Ž#)wÇ*Â×í«Ëã8Îr©R¦!5;uX(ƒ¬2^ùkaÌ4 ôI0x·s t׺í+—*òñlø9/oX¹/v!˜_og]±íš?ç›9ìx°¥ÔjÀ;GÛ‘q-ÏZd\6°9[Yg5¹n¨QaAUÑÐwîÞ„f6†ÄT×5e#)ÃWòÖ¶Ô*à@©ÔöaŠ”M¦í2&ÂÅŽ*tl–v ЇhÀaÁK°E7,I¿£ã»xµ”’ÖP÷rÀô(EM¡’ƒ,ÛØYŠhfÓ—cÓÜjdÏ2Ça-(رåëà5\¦ÌÙÏ’&„¦Æ§†7–/Û*övB’E@‰ÅBìÒ#4üõÁx¾+ c#<³TÒÛÕ7iwºDò3Š1¥À{—¿ÌMwædä<ˆLÐ) ,‘*‚¥œZ*…Põ#“x¦|‰AIÙ¼ÚS/q9J.šÔh–1ÄÒäʉ°¡îŒ5½X~7Õ»ðÖ75s¹+)¢°DDi9°«U#BÒu(èzMíÃa-:¹À€Lpåíš™‚Qí#<s.<ÌÉ—332ÆÙeY–fI*ÇlÏD?+à«ŽìöAd(Uu¢¦’¹J¨F()@aLïªQdÆW®†beóë}Þ¡áMœ#4À퀽„6O(<òËrLðiõ;Ûw¿mèòÒŒhlÔ„]»ÀmyTòþ¹z§geº8ÄT³:ð\'‰‘F3#4@Ø`ÎJâìeuÕµ$‘3!#4ZD¶´ãç'[«ÔdtÈMÙ˜aÃےΙ:rª°"—g¨/múæM]œ%‘ðÓåØ,žöcF“.륽ÜoG]¤”d· âÚW~Çט´Œ»‚mÓ"†Ý/jy€„ÈÚÜBÍš’[’úpÒlkfßwÎÙwYâÇa·Ôø3Ú¨U$Ôu2ÀîëÁéë#4dÓ’CÀÌG)¢”Aknçð7[¥Ò!D:MûÑQVúæ'-@…TèÉLÛ‚“ðó:dÿ*³pÔAÚ#)wnah®‚79ïàUCȃNdjédЃ–î‚á “Eˆ*x]eD³*ø$ƒFÞRÁ<ðàv|¢^&¤à}|Œ´~,¾'Íß3ºêœ¡ÓVaߺ`s‰ÀMé,˜d!üV(Õ¶ËzÔ5ÖwØ:”„€Ž_‘“´¹Ôc ]…ò:š²hÝ/p6é! .$GnÖ9¶<²åȲŇôs¶6ܹiüƒº/'ÜS-ÛöDƒñ \q^…ÛBhi2s¸Õ]"yøí#4½Èºˆ#)1¤ù(cL&™¥eÎI#)ä>Öý²O sïn"ƒÓc¢Š‹º0é >ZÆKïp¶e‹ÊûÊ$êuU&PF{%‰ÒÊ“!ãÀânWˆ÷¢H‹BŒ2uÈ:u:×Zç™W%°™ðÔ´@°oæÝÆ>q¸HÈâqÚÚìƒ×é!º;áCV`£LÑtˆ¯êèPÄ_‰4¤`ÇEjEQ(×Ó8’F%K8€L£àeeX'ÔÜ6Õ3Þ¢ÝÈr†ÑS X–pÐ^Âö#Ôyü׉žÒ¬7ò‰³'Z\9ýlJ vL6Ak¨oKT!X0‹ÐE(ꌃ÷ëí41"z»fÓ fa&$18N‚¦¬{s5+SV¨Ù˜`ZŽ¥Á"KʈäÁš3I!Ü´Öµ{†—(ÛåõŸGîÅYˆÜsl# my÷û\ÁéðÈù΋©y· k¢1ö®8”îñ¥!%{hB^ºs‰+™A§d÷dÀìRÉchšlµ2ŠMö¯´2(„Zwt<ç!ÌÄsb³s¨€kMg¸¬'Kò)³õú«ZÁ~¼™¬ËÁ:=gì2£¯ÀKšš¶5¯Q ÉÔBÍŽ§xS¹Ô§€C"åEƈç~ÅrU(LE?D¾T#4ÙöµÆ}WF¾ŽÑ”‘èǯ—·di¡×ƒu+ßÃߢñ‹G…‚BÁ#<¼¼{{¼î†ÌÜÓ*#}Íã},@dmjÜqüóô.Êc'Æó{zjåÖžoHcHhc8,LÀÆ<`ÐKv/ßòMÒʲI,[†àJ‚zKkc#)›gîí82aé,^ÿñÐjVõÁ-Ž”HHb{uñ;ÊGɘMÜŒÊL$–Óêÿu`ßLMž¢l™°6'î?I™&0Ž#4MÉùÃãnzs÷Åšï´‹(Mï‹k-êðÌŒ#<€UÃ#)Àr:stƒµÀ —ꦫûùIL´8`ÊKZû_±?†ÔþøqŽM)6ÎßeØiþ7…Ô‘º3ÓáïÆìÁ°!|¨B#<¢‹ç÷øIh;cü°?Qð7™™–½iå`´#<%؅ƶ`eJ—#·SÍH߯byÔD¢%;p!ÂUJ…=Њ°eA >Ÿqñø½ÝºÒ஽È/0sÓýd{¿Ñû=ó,ÃýŸøÎÖ¢²^Ÿ_äy@¡$"F‘>B¢*¢yÏûoéµjïáSí.¤J-™²JD”¡ü"¬£»»Ñ˜÷U!³Ð@‡‡]ŸÊÅÄæžÒ"m~´è‡Ø!€1“TÒ&&·×ukü-·Y™ DN#Nýx„¡j¹ÆPE-A$Q#)ªÄ¾X0‚ÀŠ³¥BÑg®J£lV|¥Àû×HAz%¿fL÷öò9’Bw0Š(Š-@+Ö/Ãð¤£ÊÊŸ´ü¨ÿ|‘nÕ(¦$)ZÈ#)x)¯Psy§ü9Y|hé`M<p“iîÉZ;HMÖÕQ7àuÁž(TJ€nd TG ô+—ü¥RÐÑä\,9bÑ©1#4d¬M¤¨%È"¿LOI #)„!ß?tHRꕇÕ=rå4Áò‹(\4GbE#<C¬ÄÉ=”†Ò0 H u#4„É$ÁöÚ<ƒ7äáøæsœg¬;¦VÛuã¤ÅÏ¿Õ¸Ìî¬]®í+Ε‰‰r*€¤Qc‹Ê‰E4pŨDCX@8þÒÄ"Ù#4;;Íðò•‹8ül~Är5ÄFÚÍ›â/qÌyUC¸|n¤ÀÃâÄlFOÎ4‰UÙæ£h„/AH(zŒÎ°–T~Ê}hºÆŸ¼Jfayô$&à«zk½îÁdìI_ͳ:ð cen]b6åÇŽ·¸õ¢¾yªùpO€ H™³¤ÅäÀ‚¶"…A$#)‚BREHŽÒ8*d@ú∲(aJŠ’$ˆ1 *ØŽ%Ä¡Aº@²¬TÞcÁø†ž~)'ªÜ¬n²YÊea;ebYUBÄpÒ5BIðKVd)Øj°Ö*i4˜Í`$ÄC„$±adÞ³Jõ”y+ ¨ÉWÆ/+án]I$[åݼøªå|›—ɼ×bhÝÎו!´e‚¯úìJ²5#4‘nD£Ü©++»GÏt»]~ONÛ¤ù/h)!DI‘QýXu ·2€Å#þøÌÙ6A®Ûoì×ìý›m[ÐÀöEņ┶#)ȱm§E‡Q cØÇÈ9“Ù…øýsüÙ;~â¹7v[$ÛN¡<YCIuE;üñ°è6oÝ/ÎôyíüªØ±cB1Å,ÂGÀ·ÌñÍÂù¿Y5Ó,°&Î'ê_«¯W>]'Q=Xì¾Ã‘ŠmÚCä•$…ò¹Xš›a†Ÿòí-|åºjÙþš{#4íâ¦LQSTJŒ DVD—^(Ùmz¬#"önMMÖIÍýkÓK–?Â'Aìøa¨Kw¨'¬j¢?ØZƒ]S~-°k4¾çþ û\#%œú¯«ŒÙ0õ4Ö@Ô˜H”àB˜ó®‘%&̆MuÜ> i22@#)˜BP†–ÐÏmînfBšRÉq¸L/ÐÄ@À‰{ kƒtìÒ#4éMÚáào?Ö*Hõ#<ýV[ü>åÏn”ôá[Š\fÐÎ$&|7z<æËu'¸ü<b{ÂÃnÀî6H¾U`¹ÃÞ!%Ñx\ë'^ëI gkkÐ4sTùaD!X=áéÐöÉßÝL‘`Á×D˜aü^ž`„óåÕTÓ&ÈïÿDHVÍÑöt³<5^Kž™,•d†ßÂ#4FhÛ;/Ý»âmŒbYAlÎwCÝB©Ññ>S÷ êùw‚QB5MSEC·³è{“¨ñh¢‚QÖ kOM&ª^¾SÌ⚆Ù6«õ*+nM›V4ÀµXT#)>ÅNaqÜ‘AˆH0‚/ ’<‰d•ñ)9ÆÅÊ©%T•d!´tï vE4-îËÑLB‚ØW$œL•¶&ÁŸ¢þUž[Áf3$}¢h«™ªæ¢#¿Ç~{ë0ðÏãî;c%UJ#<YPÑÓ™{‡]îi;v5—ú=óç[R_}T˱iJŠþH\=Ø!$$f±¶£UIZ²A³V4“ej)6e¦ÛXšU%¦Ö+LÕf4¨¤c#)C#)Z€±ˆ Ðà’pŸ9ÆßT©9C~ÀSÉ#4ðA‰ç‚+QEzZÞ€=°AƒªB”¡*Rú;Ó_A¹ïZòÙ»hƒ\ØhŸº\ÈPÀxhluÅQÍD†(´hÕL.Žx‚=«,Š\Ù`ëA©¶ÛZìڳݢ!§º²Ë"°DÎ0ÕAŽX¶h›x†6#<mÒ¾Wø°âìÙU•PiÆ´™A‰%úN³`±Œ‹ŠÐÇœëÜ·]è×#)A$Q#)$#){)T±$ØŠñóä¡–Æ cÆV¢ˆ…²µ¤H&ëŠdÌuÐ 2b%#2¡bbS_ôÆðöáúõ0góç@gŒMr¤b€•T¦ä^îÁ”B·€Xᆉ|Dd$F$‘dX1ÌÕÍb4›EcZ¹·-¹[šeB–)š-™ƒQRm"€-4ŠŸvgá<šÁ ‡fÉÚ——<J@á $„VDQÅQ;ÐPÖ!Ⱦœ>Ñûï}¾£ñü0·5XC(Z77KHfdáp‰·æªäA=G"ò¼çxªw]€çT5=ñ4#)ð™Òí>2Óòw~OÉöÿ?ßãþß÷ë5êа(yD‘ò›ñ¢Uó1 #),îõYà—ŠÂ1U•R`ném¼-¿wÝÚÙeýÛòU*Ó5ØT#4A¡}2Fp~g‹< ø|=ÔÚ]0$59´¡x ®ÏL©qú ðÜŒ~ìtȨ#<Ÿ[?¶´:« ™×L‰h¯ÒÉÇ#<殟‡’ÔzcÍD—þtG»ÞþÇîLjýT"ì™±šQ#<Š9nõœFƒÓ†Ûrð;“£n¢æ¤¡)CU8{ŸäÉí;WÓhlTfðAG·Ç7†è#<f@C¶‘Û6#<Ë Ü,p!E10MíŒwGšuþ³§×Gh]{r$°ºà;ÁÓ!Ðg$þ‹®g¿žÜï²ïÙ0Ô¸Á¬Â5§“©ë˜¡îÞþH ‡puGø:µ8eûaGÆ퇯yÔUÊ%NÉy×4Ò¸hø|±˜¥U 4I¬n~†Ê ‰(´aö7Âh!î>”ëå2V:'\e&1éκh½=Ù9j¢@Ï‘ÕpÐÙ©Aöló£yŽæÁ†µýDuõA2lj³PÚât"ܸÛnRhš,BÌP¥3tB®e¥pÇMù q)qcïa—ëx²[81ýsûy[ÅnF¨“gWƒxmƒLYT8Õε¢”pªq¯ìÍïkŽxÞ´¹'Q¢<Á‘€ÛO$ ôÀ‰‡“2¸¸æci8¡µ%首¾px€, 1!BzÚMV¥CÖ>`óQ⠚ͧ§Áø(;|á»`Â?Rо|5!nßåýÔCºD„,ÿaûýyCJ Õ”ÑTTQ¨ˆ,jY÷t<Àïæ#<2/ U#<¤R-0h€H/?OêhŽªd〉êÉuu'›¯¿PÂœr ±Û+ao2’ûÐR—f?ÅÂ4ØÜ’“ÙÒÀ€Fr#»0HhŸØ¥n;¿L4Z”’XbêYÇÓîKý#4{9Kl„ŽUlŸcÕ &³^`܃QB€~…¤ìXA3ŽDH@ÓX8}ö&•L5t®òËà¸äæ 0á±Î»Db 6ż4ßñƒÞ¡öÅá™CÇ‘`EÈxÈg«@ÂÙr¾‚àË—qÑÐhT´Þ.RäÃ_ÌáÉ\©#<IõºE LMB‡i~—_˜²4ÒÁÑ0Y6FM#<qf®ÙÊg]¢ü΢°âk×oÂc^¹8˜ïAåÑ„çgvwÔ¤Í^/´Á,»àm»;Y°¶›YeÂp¸@V&L«f¡·U˜U)°.iœíÃí®:G!;í<ÖU6ø3CÑÑr£Ãaºt¾¢ëÛž=Øʉl7<Ï2AËE’r –ó¨FNL^ç[:òk;PhÕÃg#4óØ+ÕÜìØÁ»ôű©·|úÅ©ÚŒLÐ(+«vn™üõîõ3»ÍÒ¼í=äZ¯§#ÇH¢<”v—®Nfm0³8¾ˆô¾lÅ#)…¦,€Ô&7ï‹¥#)ëÓyõã€.ÅÙ š;7É#4‹ãÜütîçs.Ü”ÝÞÓ”ÛÕE„åÇK%£×Û; !ǬuÍD*{Ì/#)„¾”F}"(ReÕø/Ú¡,Ï(¥ˆYBÈð«ôÜýÈÈO;±àg¿XÝÃ"xŸy}zt×Ùc@¡Vœ R‡€„„UèÇwç¶ÏÏ?8*rÑ9_Æp"¢Hˆ“J€Mú‰E¬ñB1D~s=rÚ©Öc@_”"ÍÞ~!‡ Œ„¡Þ1W3Ñj®GMg¿ÓY@4ÌÐÜÙáNá{7lߟX÷†Ü˜à¦(èa®M„K¿#)¶ód½™òKbסÃ_#<ôï@6;Œ[tÔT.h9K°Ö`&7sžÊ[÷9èzŽ$ƒ»8Q£„Ž»Jªi±!#)á¸áÍönÓ†pnkE"Ÿ+¯‘#šh¬–U7T'º±‰ï÷îcÔz"€¥¾Ü™È‚!† qÙ3 #)Ø<êÀÊùGW’C¡$P‰Ý˜*ðáï%‰9M“—ggûu3Âõ³ÃÈ?>¦ZkÈÙö±.Ãì(ñžo?u'ø}:–E„[ŸC°zôÄ÷í¤rf¦¾²I"÷³»‹=‡(‡)a©µ4o55 hýþ>¬ˆò5Ô‘'"wÛ¥ð’§êGN&=¥d+³6;m–NôS5@&³YÁ?¯Õ¯`„FAmêé¢òÙP ¾!Ò=˜µÕ±@å™4N!Û'm+PN`r¬ç)ÓWÀ߇µëÝÛSJÒµ]UÌidhfÉZésdHwWRénä\–5YKk·ì÷žsOyªHDA¶‚¸X*2*ÆÉ"hdˆŠix<xX¶4-%±P’À+‰AçÇBÑ…#)–¢A’!!6˜œ9A`+„PcŠ! ¢HÀØnÛËñaÖŽ!ˆxä9Ã!ÚÌù±z|ÁKòÜá¾ÜüÆÙãÚ}úöÈ›ÂSù?m×”Š˜Ò\®–M®—m¿m×WíW]Ýú½ÏIv#< R„$…ÐêP:B<kßnȼ'æ2*>×2v«¢0(þB HMNˆ¤Lq¡ÑAd¦‹;ëPÒ÷š5ü’@lî«\=–ÖÊc#4fЬ#<ÊB™Ä¸–¥hêY+¿ðñJÍ{0Ü{EZhSf´<ñÎ7k4©‹üù{ )ÐÆQÀé3¦#³á½€w¯&àü3\v[ïB É ôÀèœz>ˆ9!úxϺÑs]gu»][µ®í©Ka#<)vÖíÒTš`Œ©H%Ÿ#ƒIÑ[A!‚fdˆp=´±ÔÝ/~•9iÃb#4×ß_/Ó)¨ÆÔ[6ÕÑC\Cè&Qa'ß“z©/%BÓîɯ·œj¹À\»¸ò£ð#4³[„š#4Yñiýÿ#b"¦g‰×yÜæÀ½ùÂr)Lw<gb€c2΄u~.6f¼ÖäËKL´Ö#·mæmëZÖv>@!Y#)yç¨WMÅÇ6y¶²9¯†yN·rvNÆ[O˜Ç#4ܪØ"ŒÀv•6"Z0<A îcMók¶#<x4I·Z¼¨Á°BEØfku3^@¥¢dûÛQ "´gÉ UF‹û¹¸#4{‚x@¦–ÈT‹G@òl֙à «Ävp%I°"ö#<)O¯u:áFCDÞň±DŠvÑb6ÒSÓ=”4ÛåúK¾UY#^lÙTë~½zHyѶÄ9ÈãmM¾vi[ü÷l¦o¼N"“šrÉsô67Mó 0ZeÝ÷êl/8:£ý—½¯ À´Ý…2#4T)Ö–Rç‰Âöàú›máǙ˹s„p:RÄ?O=’k}™F‡úö”7‚ñ¾²jDïŒü|ºÚ"éÄ2ýZ¶äÌ“¦#.oð KÞI$‹çF–»í›³"GåÙ·#4&Àì<¸Ð¤„„ !°×–¥PÑ0Ð-t>¼éŠFPA¡sÄ1 ŽPàˆ<#4ÂY¨ŒƒÐ€¡¯%iЗ9xT,‘8¿ÅÍîÊõ0øÑYžjÄ‚WX‘þC`†aêY.#<ÈDÞÁlI‚J¦âi‚q¶’!Ì€ÈQÀ#4Lµ7cL#4#<.•ÏR-hŽn%á—!8á7.ì¨nUB_bAãhÆy´ØŽ†h0Jƒ›a¼å,kœ*Ài`Pmbiƒpì@‰+@e!ÐÑ eÔ¥†÷[²<¿g9ÃÏÛú<BØ~¿¥aúô„¶KËRöER§35F”,ç&nG•@3rWŽ!ûŠOŠ´PêÇׄ;$ˉvP¨%³£ÇÒgÎÆ ¨ §´6i·d^¾“·ÝÀíEÄ D5b÷ë©m ìnß°’úY/éM>,ÅÉÏmðr~ltg‚Nvp¸–°lªìSq÷âi¤0fiZ‰]µ=‰Hz%²g3Šéuimž‡¿ÍŽ>Ï}bâ¾]f£cÌüKÄ#»s&k®þÞÂ0ErêlJå¹cúzÙ鬼C…˜-&ÅJª@‘"òùûۊȲ# ©z!Ô*DkYñô÷knw[Z‚)tÇžÏf2V.G“µÂ*âK´n¨MRª«ÒY›KŠRâ£UŽª«hŒç’Ç#<fÊc˜>½MÿÜ~mJ¦…0l¡#)`’#<CÞÖ7Z!ÈÕ#<äq&ÐG?{"Q)m2"ÝJcC0„oðSS×–æóÍmÅ×Õt›{ Áájé3 3'?K<JQU¨)?å#š^‹á—e°À`¸À,Å4’#$Hïyº@수ôjÔ••ZÔ‡½§¹[#4eÜlÞ3TâËöðN± =„b#:$#lh¿!`ÎÓðǸ†˜–òj(wHôÃEB57ºÏ¯Hé!Ü!Iªx™+ÓŒª§Q!LÌÑa~J0FN¼äã6²ŠÖÌÕZR[,È:lˆŒ¿º¡)#4„†I…¸Ön·Xó¯¤p6¶k(ãÓŒ’5‹>x,¹$d!fÍ¢˜Ø«‘%P¥#<w…ô°#š4l€@…Œ_7å=ßÞ•aøÄløÄ»°»¡y¼^]kGÍØêbXÀá¬àžÉ†µ8“£ÆÎ&Ú(,d×KLY¤ÒÂØ¢š4ô>ƒ1hi±NýXŒz5[E"¤# ÕŽ‡á奱õ®*Œá…÷8¯Pžm‡“3XRŽ ¯^Å f £T=ËÍ–ÉFNÔ'PÚÆÔÈÔ„ô佯“J±¨–6B˜ƒ€Æ…›†Üä3~E=‘FYÍ#4Y†SGK*µZqEŒIßvŒ#4È6æò€z°ÆSµT#<54Oë–ùk]õ±ÈzÃO³†GÆûÙ†=| «»t(”Þª*T¼#4>œPQq…†°Ä9¯³q¾¤€Õ×Å*TIqãÈñˤv¾ˆÅ^Ù1ö½aw†vG;‰ S»ððŒf"ÆÕêÏùÉ6#4S#:c»r7s;èYd8‚Œ#4¤PÐFãDUTB"Â&.H@CòpÕó´¦"¦úeÉ]£’ÔW‚Š#4Ào‚pѳ¢ý%‡Þ#lGa¡m£€õ0«o$:b¦ûS`DË)¡)5£sA”“Q)†ì4II&£!²M›gLLb<P£“Iàãm.[¼Rs;hÎå™+I¸B!*¢•a“´2!L’ÌÒUKCt4x04BhÈ‚ ,47j݇GH„tü¢¿ÅÏñî|"ùÆêêT™žT¡×Mb#<K.E勉QŒ1HÆîb÷z¸*$tgM#<^ÑðFÙêFâ#4¸Ï'6Ö#[±p6„ÿàÃC'ÈÐáæ‹’IK¬œpä.Q©‚PHl†Ânl)#4Kœ#4?Z¡›vÄ46¶\6jfNÌRi…7Ç\²Pr;‹p9ÚG0 µ¢!Ò(èD$QÛã@†&ÏÁb/#4¦À²:âq‰xÞ e5úk"#<r½àá´H1°›ÈËg7»±Êêà7ý{ð,pF6‰œBN“a=®ôò“"k•8É•nÎDt1êsØÙ۷ßœ¼3Ma+LÁ) ‘÷6Ö#<ʨÛ0‚VÅUY‚‰·é³b}Å!æ5ùKn.X-ŸN‰>øËœÀÜ1µÃÆ^¤æ´k›«~O¾Ô°æ–õÂlKaÝÁ3L¶É°}í™3»¹AŠ¥9½²b ›òê¡›ïÀ)ÜæÜA6‡duç‰ÝïÜnÏ¡ŽV;9 6@)€‰Ç¨ãö{¼G¬'·s‰Ã.'q*"ÄX#<6`†-„Ì*.VÉ|–#)i2~–,V`;ªÉ¹£k70p)QÄi5…Éd’9‰#TІ" ú¬˜LŠGÔßE±€fŠ¢g—4Ølë€Å¬Dc†*F› ˆ½Í#4Á’&£FNQCv¾N#’}`@ ¼1ÖÏ#øêÈzñ#4¾Íå;‰7ayýé šŒ„\ß?jôÅ“÷#4NÍÍFs€™“#µkËei߆o$÷ÉÃ#4êàÍ“v„¥ðš{ÚíV4`0Ý}ê¡ÎKâyª#‹ñ¸Œ´S3/NÓ!Ž’/Å.he81úŒ–f\VV-¼æôÇ›±øû¢ñPD E¹Û:ì!º3ÏOP‰óŒ’P©k_¹sËnˆ,#4¨ÖÔm¯5\U¶´j¿Öj«ËmæÛZ¢¢¬ëB„¡vOŽf°¼<Öî'¢Œë"!‹XQ%øéFR“Ípv_l#)ÖÖ]”„þHÔ|»Ï Üð©¡ú´YB'¨XÅTLB ”¤ÉLÆj”³2VRšb“j6“-'öº’˜miJ˜6h‰B‹i£_na’6¤Å’Q)JÙ¡L˜™¦d¦4hEQ¦&”M/ÉÝ@FÅ‘¡ALi%%–&A™4j©¨¢ŒL"Œ1ŒdÖ”lª*BS¤¨4ÊlJŒÄh)•E“6D¥#‰ŒÚàå«<Hr¼±¦õæRÂ…@bl¾p |_•¦+©v‰y7}™ìÝ}?Ž²´ŠùJ}c8C8£µG0«½}fÆuv@»yn“%ÈÞér'á°4%¤/b@Æ2ضã0Ø—`FՑ˨Ð;7#w›E#<ÊcûèøÆêrä ÈÎÒÞt8,ä#){ŒpÔ¦µ¶}T—f´Ë#4XÂKߎY}FXia“#<·Ü×±7ðÉÃR`¿ N&Ÿ7µ$ãE}ø‹˜ã²@m&Ûe+x5[#xap`‚.’ß±ú„fÄŠ“X‘6ÐišÉ¶-Œ#1ETDHžÎ:öu®ùƒÀ:‹¸8Qo#<¥Ã@WºSOÁ¦±3í-Ô,ª›ÍÎ0Ã× ³_…Õ¦%ÐS8»#45wlÈÇìù7Ê^Ç`*óz|Ä€ö´Ê‘®Ç”öP#,&Q]ºÏ7R|ö/Öêž9^aŒ"o‚=Ò/¹‚}µC9‘lŸÓï³`ÀŠ›S<Ó¤p£rÅ%ŒD]˜ŸúÓ7J!ºª²Úik#T¤!B€º#<HÅ8iáÒcòñüð#<z9M+Ùnó”Šéç6tŠ—x4ûŸ}UWDÄq¹mÅ>#4ï˼8õ-v¬ËáöZÖ“´®>Çý°ËƒÐÄ#&gL³93T8Lyf[M»S‹÷½ŒÁ‡ÁÚÆ ÍúÁM#<‹jñZ¹\œA‹ÛK±‚îBYi{Y6…æîÛ–8ÎÛìE/>%Èßl°ü›í$,7ÕÆé‡m ‹ “ic³4X¸=7œëo§Áùœ>í‡81j CÂ:þÌïGžS«ëîõ^^¡¹ê\—¶FÉÏP<©Š2‡©ni§q=敹¯º4#4²maw5É5„Ûa? ¡,;b“—O¹èóªrÞR,½¹I×¹Ï>27%¢žÈïŸL#<öeSÐ.g'Âí™Üm;DD©„ó…s.ꂃ3ŒfÎ68ërŠ"•‰JüîŒå4$–½ˆÝ¶ÜÌ‹Þ준‰±÷¼ì@ïã2ÄTý>©¥âé¯IèÛ¿Á³Ç#]$P!ËoÀe[é©Ô'²»»ÁÕ×céê,xKüÞUÁÎùlN„)&Á¤ œ°mT“"2“‚ €qô0Q+9\±ƒ`ºï<¼T t¢-G'–ª+À<4·íKîj`h#<4gJA9jTµ24W¿¤ïCH8c=³qÓΙ¢ÖÏ9R\;;<¯ê‹¨ao]#Sã%£xTeªX]5”={¨ÝC?J“áéÛUMUŒ2ŸÝÕ ,#4ÝÁISGvQŒ%œóÑ2"j3ÉI¸ŽÄêtÉì~÷RlMÌ—]¤9|4èŠé;?x@‹&P$DàVõJÕžtŒ=·ÿ_Ÿ…Ž{ó2Ï_äyªŒ>uXû±Œô« hͲ”“íhŽF(ñHÇÊË™—f£fMTAê0y\(ÈÖ¸Ó£{šÌÓöã3ò¹÷ÆÌ1¶æ£tæcæi5#4$&²;ºE§Ó’ª\„hD‘/iŠÖo¶c'–R<8s1Ú²io5¬DâdZ.Œ7™3#9ÖýÙ&ð¦ÕÕ“P¼ímó§k¼o'†\Â-)oöæ´a.syÑ¡oL3«êœp)º\,@øuCTI¬3>š÷ºD䂉™¨*qi=ªŒ\Ì¡2Ò¨ŒËµR„1)¥!É"âî(ˈÔD£Bâñ;SZÙ8ƒ8â?GIÒ^åîM<ahÉwšÞóµöÙ®NýÌBÐ>Lƒ¦q ¶Ä“±GXƤyÔÇHÕ£µdÏ\Ü×N Ë‘»l#GWv¸»0¤åÞQb¥ø1•¦â6•}]¡¶}s6'HE;mcþ<àqÒÚÁ¸Yׇk¬iÍÔIF$wz`ƒc¡»0#<?ftÕÝ*ÇVI|Ÿ‚CÉ®¯M#²767¶ŒL±±‹!ôC¨iÉQñÇ}æías/úËÁñ¸!9Îvånª¹Œm¨€x¥Ò¦VŸ¦c-nÆ“3J|´ÜlQbÖî^0ÈLºZ97ã2׊—üÜ9‰ˆ!øv;æ#<LŠŠ— Ä;P›e“e)Ô£38j¥Š ÉQ„&ÂMþ¶#<òî óÿhß²1zãPN¼"Å;tFQyÄcÆ2齘z"V®ñ¾5ŠÞLëXÜÎý'ZÏûìî=%$mN"UØÈ»çxøÔ.î¼GÐ\kÎcŽ}s¼ëx‘tèbo£mÄÇ'››pQ ûâ$é>4%&8s5¼©ÎøÞAó°ã‚IߦתÞù®N͵£x Lи—Þꥯ„òã‘[åñ®s©n8Âr³Ô%C¶rNŸaûnng;#4“½±›Ú£ ó<ªÓÞÍQ%aIP¥ÔiÀoÄÀ¸Ó;1¢:Þc¬!X<ÆÜ® y§YM†dÒ!ÁcêuCÆRÈ<-«ÈnvY6#4ˆzgv„ÚfÒ¦Õ"hq±-kŒ“$ñËDF¹iu†ŒAA•?qbEaÓP}"HuÅ&qT¹‡ªHÂIÔ#utþ•NL#4+".íó‰#<ÛWVK™ls¡öw/hmLI¤B&Y•‚æ²FÆÁP£~0L׎¬´'‘öî<'1t²k{ÍiWÖ¨ÑÈò•2æ#4kv¥„ÆØ3¾È%jMt‡CÍRn&#<ÙëZ$"©“&爉[ê«g’ÇH¶I¢MQZ4ða¸D-ÄY‚_hÙn˜¥Á¿X-l´t✖0‡%5ëÝœæ2f€ÖÊ6šïœ_–’÷ètÜAÁôµ:xç¥P9Ðo7j$h„‹4Íkk*’kˆ]Œ×X¦¢mxáA9ÝÎ!ôÙÞ·yÁ›;Aè/Rc/S<˜re•®Ú†Å0²Ù¥D‹-¶‹Éó·#4=aÊËåÍ WLB)U Q´*#4ê›J:ô"¢\…{ÄÊl©‘UÏ#4Muù¾!ÆÞ‡©Iõ¦¹…˜q™Ð0µlåm°í¶/<¸…p<ÊÆ]ÀA„@ W†wÌmfu&ÿ7£|p÷¡Ú^7l&“.œý9.2Ýô™tjFÛpó Ü#5&©_ðñ£rl‹y_¹²\îâ>ÆåœzØkpk”çºO·W5mÖi¾ÜÞFos;:Ö`6qap›2v“Fä}ðËæ°ã5£3É™%3½<SãDí@šéêtTt³Èp4CŽù¥³e³o$¤©æÇuÜc|ì&쎆o[¶fœÙ™YªÞªˆyD¢;D‹z;Ï5#<j°RðŽxŒÞ4¦9Ì¿Rh·:TLÔ]USÈM*‡‰1Å_‡[¢¸ˆÙ òO±1 epSÀ¼±x”Kµâ×*Õª…ˆ´Ò[A£ÑÃ1TLÆÐ9¶jÉ«ˆÉYÄ#ÆàܺÔÏqšÐcÁ“&™»FŽ.:jn¬5䇧‡á&¨6‰½2Íffjti$ÞknmÖfMÛ53—N½Fcelvj˜1<“’‰ÓD¹§MvÖrÈÛ2rÄW^ße5ÚpÄØŽoãeìœ[Nï£9YÙªŸRŒòl6[Y;B7¨û¡‚×i7ŒÚ'Cå6iæ·œ"k*Q'±ËN+XèËV(‚q\p¤éßä$‚1Ã8²˜pr9àâÕÄ#4êÁü Ç8Q£˜!9…RéÞ-KÜÀäLTfÁ#‡"„liŠ6„ÔÍNT…2C":`6+<°«É¤Éq$Š$}íŒÍJüZ–5Z):UãŽg“zhÉ*БãpV1Š‡M‹-UM0öæ„Ç$Ƶeѧ¢NéµkiìÆvÏ~!F¦¨Û×lÊrîÑá#)B òÒøÕ#)ÒKT*Hž#4GTlM›´ãs†Ž‚… ¶’œiI+F5ezÎ~WȲú¶¹à…¡mÎV7-aa–629'€äÑcl™•‘¶Rbi@(R`nƒe׸E45´=‘uL3ÓB‚ç“e"8£ÅìpÄæ(àÑÇØ †ìïÕ6®ÏZcªD-EV†!.ø„y‘wÀéØbØ[$¾•#<AHj“tšiRšfY2Í4cJŒÖ¢9dÏŽ±‹mh9`qç#²L¡¨‡&ðe¦2cºƒFnÎ#)…²h› ”-†RvQRh4ÄÚ¡-’™Â©Hº&{+FgJ,G5)#4Ü0·£ICÂnšºf¦Ì”Èab ñÖ÷g$–‘mZpÒ¦é«Å4‘&¬œÙ·œS &¬¼Ô¦™4@ËF7 \>É´˜¤šCÛ1ÑìãÒlïXŒrý8Dt“JmÚ”¥Ð¥åfŽH#<Fæ\0™·@Æ»°Î˜ßf#4õ"e'PHèÊß:zr¹—/\tŠc1-˜›e#)éKÓ‡”m‘,ðÝÒéÃÔî×’cht'9CÃî»ÎÝm¬FÑÊm¸(ZF™¸Ú¯8Ù˜Ö”næ9–èss‚rl¤!fÈÎh†FquJ—ŠQ±ôru‹²×'2b¼KÛOƒ)“jˆâ¶“g#Œk&/Âã®×&˜bÇ„7#)‰5T^SBgj`E¸cc½«œÖ^SÁ¹Ê”æ2µäˆÆ[ÝŠÀÒb&Í"ƒ$Pkl º5@Ï'6÷¾ü3d×C’òa“[Âtºˆ‰2Ð)¾É!à¦`€Ë`êC–ûtF8%†ÒÜa(Ê2‚4&ÃLŒÁV€Á ,¸`£Œb±#[x¼ã‹b ôhMx<CŒj>»3±ÑO Tlïä2e‘Œmµ$`1³„¼x¼~s†\¶:ñÖ5Œí6ëÉÄÿ¦©Ï"ð·j=Ž´Œ.}°('•éºh¨†Aê61Í`Ðn=K†LÉF—&,Öà9M{Ò¬Š¥„8Æ™’#<ÜÀ™$ÔS=´àFMQn›áa8 @â¸Rˆ‚!È9Ê,#4X‰™T“3pæíÉ€Ø#4÷nHcÉŒ£C¦#Ã<ÂeU"7ìr@"„€‰i¼ØéFwãÕœPQI’0\Ì´†‘»¤9¤Ë”Î2\fðãÆÊÜdA#)I¡,S²l3›#E6ÌLø˦±É7Õ6è.Çc@¸ý[ÑÙŠÐfÜÈt06.&h9¦¼DÅÔ¶À¶¶D±¦†Ëk¶R9kcŒ%Á†,Õä’ˆKºÓm#4ö¢µ³ƒ¹°ÒeKBGFž*J¬aá G¢§d?‚ÐŽgR¦¬Iš2Ym36µÍ™!#)VÒ“÷E!\5½™^@»A4(G¡ä”Š0QÿCˆ/Õ¼=¼œáBG@ííV•$‘$‘ª¨Êª„€$CȪ71þcÈ`“*°Â#<n#)ä9·H§ó#Êo-ZßzŽË{µÖö®Æ$Æf€#4#)`Rp@raGœžS—5æSH&HXäv„±'ÆŽb¡µn¥¢r¡;H>ýí|®H|ý3ƒõNël}IÔ'yÙîeáÅ´În#4fR™c&ïÚ[H,‹JV“.b©mMiÙᤸn-Œå) O˜ƒ–V>NÕ[öBR>/s pòI¸›fõ2Ma¨3.âä´„qÄr´6´àÂ12Åpv èu,:áòˆx„r„8åtì;‹¿_B@Ž½pë}ÏO£üÎKʆt%ÞêB$‚PGh;Ï÷p¹~'ãõjµ¯-U³n“§7“Ôr¹-Ș…óhhHLhÀ1 ¢)h#<¥ÌÕ†ÊȲ‰¿Æ ®Ãu@âo…|òBÚ&T†=\_é!3È6½4jBJµµ‹5 ȬNeñ¡B%ËÃ*ɺÞÏf¯#)3$;Å£®aÓߊõªü‚¨€l÷ìR$@~#þXÈI"Q4Ú$M25.›µ®l•º^Ô½»²5làT´Ðêï ïxu"Y(Y͸ŽÙLv(.ðp&ð8Ÿ#)HC.G<ytÚAy@å¸2ˆZ‘10ÀŠ{Om~µv”g˜ú¬j?dÄ»øÏàd AØâýM•f¿÷{ùœIN[W"Æ ¾z锎ծš´W Ú¥Rb`0(Ô`eŠ#<c´M¤ˆÞ'ÐneÀ¸=¾PǸ-þ›)Ni1Ø#<i‰@ŽGÁW#4‚F«QeFŒ'1R.EÇ1‚Rwæ\1¢Ã¬)*#4+Ø~&!’òå_—à(´Ø)ÞÃ*佂q>³ãFßuCÉ ¨ÊMl[m£cQ‰ChÍ3-‚£[H2%¢fTBƒ2Ô…D÷®ïQ‡†ªÔ#Úy·Z›6_«½i1Wytt;`)¬ÕÚ!èYÔTi£I>WµˆB!¯d”É뮾5UÕtÔk•Ê &iqŠ58A5ô¬C'ñt1;‘¤í‡¬ e–ÆOZe*UÏš.“î>íL‰‹Š\!’GÂëz4óÍyƒÁ‹4}ké…n®ÒMØfæí(9’b¬¦PÉù]ˆË2Ëy'±A:²ÔŠð( #4†ìù#<ÈÌ-®²<ˆ…Â#4d1ZWp‘çQö0÷œT*ÀcKi"È£ÔBn_ÕÍ®–ß‘·É|jø"ÔÈU¤®ÉŒ#4™JY°CM"´qŠ´¸fh„[‘ƒbÓ‘Ï™H™±Žâ»•¬CœßDÚ–ŒûH†Ze“`5}ÝÛ{º×#<”¨ªýÔ·ÇŒÆê>a˜òNþ3]ß-QV#<£ª7´Ñn;Í!ÐÂBac}mV6E ijHTŽR—#ÖˆVº¤nC€Ì%†S„£¾‰5uI“z×d¸"^lE¿©#4o5AtÇ‘Ä\b;Ø"Ù#)XD&‰'MN+ñ¼ç…±ñü~`î@<“ƤcÝäSr‹²H+ÆV‚R#<¦ @ÄE€°Tþ@é¼KèRŸ$ µ•Íš¼~]‘#)2UA‚@C3’Ë1Ç¯/4–N3/Ki¾°ÌfŒ»×#4‡6±ÒßÛ˜ÑFÚ4ÏÛ¸ƒ…AõW##<·7³‚¶ˆÈÂd\}øQœ=vÉŸ‰JiÉa <H¶}-ß`6̤!j¼¥<‰ÈI¨B)»RoT(0Êw×PÙ†€·dSÇKJÚÍEØf¿§¸aœ22õÁ¯UèÌíÙ`Á'‘NÇ6q•ºu™…U‰Q*‰(k·‚Prõ!.âpZß Ý—s•€[b!Óï¾ ÒØ·Â ¨þ'Óìëdg)&9»(˜wg9Î&3- Ó9œaa"ô¬Mu¦—‚†ö#§³P6¼¥}°‰R)”mtXQ¦Ô:sŒ¸,»W ¼ÓMõ|ÖøѺ‘Æg>qââ¬{!Sæ‘ggjޘЄ³Ç;#4ƒ›–z!¡Ò¨aRi Šk«†A˜ÀÜÑK-#Àn„"dL‘fÈ K’ÉÁ LLEߤŒ#<¡$PRä Öƒ²›Lƒ`c€#4ðÒe¶š#42ЋŒ°?Ð#) "!r;@HŽý{BÀkôÊ}m}„€¯êÌ™ø+”ÓŠE'²ƒ#4Gø«t¢`×à0üe¬>Ñÿ_TŸªa—:Úúlk#4¶¡Ï÷¶(&¢$?òc5×jbi%öG¢û‘}t %çNîæîvi¿=kÍÎ[AmFÆÔZÉ´jÖ-¶T–Ù!$BD„‡¡ðS$>Š}W’¥RÙV¨ƒ?3.ße”ûH}Äï±8£øÀèÒ»Ñ[SÇטš¨ß=,¥¥÷ï•þ#)Ý&°VâÚ;+jâ:†«I+‘CdÞúéÃYÞ%)ž„Q¥…s0ä>“©qK-µ)ÖÙ{ͶɌ¡´‚™õ7cRײ”bŠºRˆšI¶a¦†Æ!mlªñ4á‘pñRmá%…ÌÞI†qƒÃ#2™¦'wl{²#4VIQŠÊÆÛÞ¯’7•Šiȉ3Ìð͙ۜTÉËëdA_MoD‰œxœómˆöèŶo,ò–Æe(g ê2•¬ÓGŒ¬MW>fÎxe§xÓð9¾K#<åpôܺ6דr1¨/ò¨BÕRc7VÍPàZÛ¤¶v*ÕÚvÕšÍNûÌwHñ“|áéc߇hË3³ê3+5‰Â®I&öG.¹D¨ž'z·‘i’MEZ€Êé[¬àí|‡Ó%ÚVI6XòH;Š|ªè}©‘†É±e†¡Ì0_©ŸÉœ¦#4HPeŒS3Hk$F,7äc‰VÍÒ”a*S#) Z‹LÁGˆlYF¡òÛçq‰iVRÓe%0âE‚7ŠÕC²L,Á˜ð0i#uîŠ5óÕym£zËjÌŠØÔr…(†!$#)/ (ÙP[d·¥TOÊ©{Zd/¹Ô°s]uvIUkßmÎMÝÖºmMwh»-Ò£bîêø“Ÿ=yxådŒ‘¸AvAÑÙUmƒLÄì;[½Æ¦šón†¯{[ëf77dßw|î±]ÚæfÓ´ö뽚½‘¹\”+u+r{×±¼®Ä$RDŒ6e?ßž*DŽÑM£mÉ_[qþ[ko’Ñ”3lÉ#<R‰²±¶-eI”¥—«tÕŠm2kKÙ¶Ôüúüvü¿>hµA‚Ʀ%ÚKjµBTj iI³\·³Ñç+7æ#©GæFæ˜J4‰q/a¶*ÉlâƒÔ@‰h3yÁ§)´Q[|Ít×Um«õµZÄìNá #4ð_<GrD='yì°z²ÂyÈF3ý,(vü¸b]LÃ3Nãg×’'Žµ’È„‘$ât ±JŸX'ÖlÂi33—„âÿ=30 gÌM(.„„HÒ6þºzŒZJ.#<4|œ›ç}õrC冧dn¡æ©5˜R§û_ÂÊÔŽéRª„h‚HPl¤BÏ㤴Ø"¬Ê#)¨ÊÄ‚4—nLjNýÈôñåceð×´¡ÕF9·CVV-÷dæ@±¶Ÿ¢#4ôµn²$MÓ#4ß#4ñù9Œl…H=+bÖ¦S1ñÞ‰‰«ÑÖH(Dˆ6Û¿OWíxÛMܵ¦Ç{ ÜVò±È2àqÕ‘|ÂÁV,ºötÍÐØ~F2s>’|po}xu(<d\ˆ«7.8ÞªV¿–›)äôÁcyªƒV$Í[Lgh[)C±h"dý̲!+íŒîϹB\Ðþ#D#4ÝrNW$3™È—+‚ˆXĆ¨Ú¿ÙêÁDÂpsT' ;áýš’×–§a?º%ZuЩ6uÉÇ·Ø@ýpëX#)(@ì|Þ¯¨…užãîèkoàobŒJÉöæYüQ0×;|ÿÂlÀƒðMµNý?6Öß|vMhÖÿuEY$Þ릚3i>º×DHJMæ涙a5$ÖI‹Yêmk’Íe±”¶“a¤ÒO9M3H÷n–i“M´Å,¤D¿k]aHÕe3#4–Í#LK6Š-jj©¢|qFÒ5R—.›P—æÚæºãY«ë®ÆŠ˜„ ™š«15_'R&¶"-,ceW÷6Û¶¿dM_;¶I²(µ‹"kjªD“M³k[—m-L¶úWI©¶ß>wŠh›M¦Ê)…KXTZ–ͤßmªV£¨ónŠÍ›{«¯w&RÖ™#4#4_ãy¯m5I¾#<íš¼ÚëJ%V5^&¹1¥¦øøA\þõ/íŽr‡ùV‡‹Ù7^{(?Óþ^?‘\b#®"óÔWËåÛ^Ž½\4ƒ3ª£UU¯Ë?…Å.I0Š}óLZˆÑÈÈgçðé~,La}¹´Ó#4Z#<‚îJI”]“¬´#4.î´bŠ#„Õhï΂„&èm¾õ-´%>îéš–›J™¤#)A#)… îã¾f z 0$(¶i[4Õ¾ÍZºZØÀåÄКTD`Ò#¼‚(4"MFÛvÊ®îØØ¢ÒX¶2¡P7Å0ŠÉ„ŠÈ:”S:v(é´¥ÍH*k+Q@ m–͘–b†1‘“Úý*è¡(Ô¤Õ)m6Í™cm&Ú†”ÑJ”Ûñ[”0À@™j+*‰hĤ¥iI#<,6›e)dMLÉ1°ÌÆXØ*##<Q)šŠRbM&D´%F²Šh²–ScT¥±)¤Ê”¦DÙ±‹DcL–M%I)ŠJ¤1d‹R‰µY´Ò¤(II‹&Ra4É4ÉRËTÛÕ«"b´RÄÍ©2H²¶Ô³Y2hÒ™-)¶Ë5µ&µkïk¸ÛT›JÍZk%–Èi-÷A´U±,@¨#)QaD¨T…ÖG†%”*`ÅF-~V·5J[µ¶‘4$X#<jŠ€TDÄ’éÞO8dÜ/†Eèr²S!×@-gfJHùñ.ùÏ: ËŽÃqÓ3¨¯Ž˜oïg()»¥××q—üz‰µ˜G¾ÎƒýI‘#)#4-ÛžòÕC€V]:X<ðµ…åzª$j†Ê›j¯Q$!ðÄ1ñ¿x*ÌvŠî‚wùa‚yC|uǪYêÜAÉdb¿gƒwc][:ë»Í›k(&cÎc=WJ¦üŽJQ»ICÃEJWi‰v_[5z7¦ø?ÞÀZcåQÚv]ºÐƒ ëô2$ fl¸Óha!¼†/@)Ìæk #M1Ø×)P¢¢¨‚(ÁW^°Ø°âéI_ÄP[ÎRܨKƒÞ¨ùõ\ÛG9 k‚qâ¨H#<•ú»»gÕqDðÏ`œ¦2бڅ¤UÓçPä’Øþ|ýFþdêDÆ&ÌŠâ€|Y¦a!"ñ×ÕXÅÃ2üHæ‡û˜¤Ò¨l6Š{AÙS‘„ŠPLE°Ë€ˆf¡lzsm‚JÇC†Y;`”–,Å@ «x»œ¥œ˜‘q#<zyT|ðAÏHç*^“9#<“Lª#<‹Cè©r0xU+C¤ëö6Ñ+![8›öêræY$gfõ[ëšLÖGT5^UU5…¡¢z7`UyØÛ×Z8™)®aõq{¶RAÆÎíCVÔu¤†èmõ„AÑgÞq¾#4™ÚŠ†FECèw”•ÚA#)îH!úy~gÛæÛë´à"ƤÀ@õÜ#)#)-¶-ª1Y'TT”Šài\´˜Bd:j‹wÑ!#)ª(HRE±']ƒ™²F=”<1Ž#/ÇÀ´îwëY(¸!“ß~N´,ŠÀ€².ýF¡ ¹(Œ’ˆDº´pìÞbÂù¥ËÛZÞü>£‰¯o.¦ Õð37AÔ%?Zöaí‘@O>aÀÁ7x½Ïbèoêó±¼åëlyWVÎúS=ƒ j^„Tåcio¯•ï¶'IÛMØe²è¶ÓÿƒGªïn÷jþ7´ÁŠ9£sÖâê#üËQ¦C#)‘dA‹ØP%¢Û!c?ƒN¬¶ÍZ]Ô¤U¬²$FCœEDÐ4Öž0¤-[`V#C¨fI3U¸LQ0#4IöïžÛãyh’ôŒêœhþî!‰cŠ´ª‘MœUV6pãPbm9]ªB3›†D6&iVW™J<¦ØÕ{a˜â$ #4‰´®Û¤“KV‡šEÓteS8¨¥!ÆF:¾q-°cCž 'ßÆ=–°÷ä7höÉ#)Y÷@¾ó5«ë–n|u È!²>nÛT‘h†µj¢fÊ´kf§¯'©raud &þ¤<ò?¯ßÉŒ”»Ty´ž#)õ‘ Gª#4£œ¨úlk«‰å<ç<IÄÍú²•f´,ˆÚ-AXÜÙNÜwf»Emë)Ýê½’$=àìú§Ì0öÀ( c±Tn6.nìÛµÂûùüþÐ>EdcP‚j‰ô¾ŸÏ§âýui‹L\^)§·à’s¦°¯%mÆ¥ˆª†–³%äqɹ‘¡a!3¯Š„¤¹¡²n”h@|ÑÇRgHRfe€Ú.¸…’Ú’Í‘P••2 B0Vý~47‘þ\›ãCóû^¸}=pïd<AÝÓæå"{og®+ÚV¥ïëÅÀûÞ»N 'ë©4W`Ðä>˴žßÍR8`<€ƒã†#4aã°û÷ŸRiÆ»Xj#<¶ÚΣŠü¦IÅ>íýíÝþ:Ïe3 0±€å%l;QØ€¶´i…À0c!pUe€()©IIèÑôj"²„Ô…Ž@£Hy€1êó€·6}ÌÙ×¹öfl°í¤#4´¦?uJƃ4bC!”‰žià=G"×ãa‘Pi»R¦Fè3ÛßÖ©Y(î§g΋£#<Iøe׸ÆÕÃQ\•zõÄs¥x©Øw_¾* ‰Úzq;2¡UKÑ” ÅâÔÄ©ÑIS#4v·¦þ…”† P‚i™ÁEèQŽâ…X¦Ò‰¡ÇIŠ!èÐfbÊ»‘¿_q“ÕéÝ܇¼ðû»3”÷-Ñ¡ËõOW%ø'q¡XÍOÌZP„0PdÇòOºT¦Úñe.\‹éõ;Î翈°m˜þpœâïµ40´:ñÕf¢15øSsDÁ¶úÑ!´GN#)ˆÙ󈪥‰¬6#4;}Ù[qFçQt±“€Xlëb–Ö9-ÃITl˜– 錋]†cÕáp6„l6åVl‘[)«qÍÓ,ÖÌÜM¡¸2|Ðêþ5$ãÑUˆ«éE*ò$õ—¨°#Ó¼áÝfò¶gr¡q§yÔ“>çÒŒ‡§Pê (¤NàX¥‚*”Y 6HQD*øžó’˜SèSΕ4>㹉q"{µXÁTp#)qæ¦QÍ8Cyp¿žaŒz€]ÛY‘›Ô´7 Ó™°¤,E$`HA‘æÀ¥Uâ‚+»Yb È##bÌ”$²»Æ‚ ¶&ÅÖZYN>Zwk¹—Îìg8ûWs1½Ýsêë0e¬à¦7š§×¾æ¤Æó`¡U`‚}Ìv_Ëö½yQoÃ[¹~>xL0ci¡¶»B+å®*&8ç]Ý{Ý…+Ís›ŒÃ&ë»—G·NÖé,Òç½ÖƒÉãÞöwð(™aMÀa 6 ”’#4‘œ5¬†"8˜›mzÈß2‘±I£¤À¡ ‚K`^Ç·»‘wñ!*äK¬ïÓ]áÊKQ€y9€²B*C·E].á–Ñ;{m²z²å»>ºÕ°(1ÞØ”l B£®8ìÈØKã}ÃÆVÙ‘ÉÂ90¤6ÈE¯ƒf\Ø°‘ÑÚ%Ï׬;;©ÓÑ€ðG þXæ8ðôêíªïˆzï:½Xá=AØ•âöŠl·{NxÈCh=ø"ova“4¥ûÓê½Ò` ,É=Ý´‹–£Ò¦ÎÑ™EÃô¢‘-¼3‹&á‘T—TÁVFSµ…ofNo#AbÈÀ§ºªQ˜™”ÙÆ$ˆ‹¼‰$Ü[i/6×ÙË×.òêó³›~–õ"e2C Þõ{å©«¤•â©•RJ#)È¡°#)‘UµÉiEß]Õç\ë;·2\±ruØëé+Ý55oi»^·-ïT±b€‹#)šôçZ˸$C£º w—Sonx#)k\5æñ–³gÍx ƒqèwhirO§ó’eK[ZNd8ÊãBÄŽ,FÄL%±V.O°¬Š«1’z%¤ßl”0®c«F¬›Õdfö!AÖŽwLšÀÜ*¼Ú+}•4Ʊ2ßݪÕæ€Í³M&šÒºè¹¿"½/)‰,‹F¨Ö¿™µsdQ@‹9¤ ‚ÈS)‘$…À¨#/îýõfˆ©€ª^”wÿ6,p-QÏá«€A3ROÕŠŽhY”ýXÒä2ëµ™ÀL¤-’,% RTB„–õ10mDŒ‡Þ…é vÃu© ,go2ËHDŒ/šÞŠ¬ì`†,C±IŠ†‘˜¡h~>ìvFWm#4ÂôÜÉ`€Ø<#4¦fç]ë‚{d¢¡©ÿLt@¥(o8CWh®I[ãs[~ký:‘A³—ï…þǸ,̪§²Š…],#)Ýì•l2Èãj!³c€çãè¦ÓLþ=•#<D*]ÒhÚdj4›F¨Ú;[,PMa·ÛºWœvèb0×Lݳci®J¼Qö.a˜Ð˜øIiUµ‰ŒŽ™÷‰ „±½ìŠ2" ‡bA&!…*?ËúïñŸW'Øäáam×¢‹ Ühh0‹ #4„ÌÝM¥îõIìÛÅ(Q<¼×û¿êÁÁEÚçdÖ´0Ps÷|˜` ш#)˜t‰eA.z¢X;-’¸›û,øÛ¾Ysº‰~µÕT#Õ#4aÌ„ŠåLÈ$€/ÕÌW³Mj‰ß#<•=ú®135Èn½ÌMn5û²<á÷霶{ûKýÿß…Õó•¯ ¡Y¡ß}Š)À3Ý’›˜ýnva&ƒ‚ŽÐC«ïeyÁU¯¨Èµ?Ãçù¡ì()¤ÛŽÚÛ,WŽ…È\´+Vç‹Þ õ"mØB. ñŒQô`žÆE¿G8_‰\—#)êDÌ„#ô ÒÅÐÚ\!MQMˆ>'Æ!ôô¤"þó½píô"yÈ›ý:ÛkÃX«_Ë*¬\ÚåU®•U¶JæµÍª±kKï¶Þ-‹0/¨ "‚Ád%#)*Rgd=wÜToA˜Æ/óA^êÒé´©]+ÚK#<p2»>ã^g ‰‰ÚsNgÂǃ֧æü6`ÅÛ'Ö<’M‰æOZQÐ8;ÌÅág°Oíw'hÃŒU) R0•R†¨$:߉d@ß•NãD}Ì–©-…"º#4/2ƒÜXíøîù^»Å*ŒLV~®~·ÏnÒN'Ë!ApÒ³ú\o5`´JOCÔuh+gë߬:'†ÍßH‘=çBÖÓ®òß9‡:${ÃN8öã¼éë£ã݇¨d€dóV,D °Dà’Ϩáøs@ƒDý¡"Ž±yª•ýTe‡µ™I¦ƒüf5CG1ƒU ±QT*‚™ÿf-Å4T2”2,… ^Ij¶€b£:`²þ½@F0ÛXB‡, ¤v£Oø*cì¹nÊ… ‘‰1¡gV%ã k2½Â(ëW[¡Ã$Ê*Ńï€ÂÚHhÀÆ%‰˜B6†É#<ÎYQ¸1 Rw`a„v 0 ”Y(¦¥V"zèš½ïo/vß'Åñ$¾yw•¹Wsußnøå‹Nê®m¢‚†YLVJÖ¤”„bÂD\æÁVpÒ20ÄÐߦˆ\ÀÛ™ÍsV-¸Q¯¦ò¤Öå¼³im¤Ã‘¨ÑÎW,{`…ƒ`MYi4r¢M+S-`¶RËRÁÕ.‰©czxa¶EXVÖŠÑboUYVÝÖAÈhïÍÁקwNèæ¹k—6Ñ´®èÑQhƹn[˜“îï.4=uÕ_zÜÉo>}Ý·Øi7ÂݧR†cV ÌbŽ0Æ‘ZNŒìEßþ#4fe„7ÉpÁê<e0òƒ!\hPÙÕUL%ÓI@ 8PÚÏíÈ`Ãɪnž™ZM§!Ô@L£r`ãY^·e®Éü>ÓЛ Ô9‘P1qüfÊ >t¶×'š¤ÀBæi¢~(puÀûCÈò‹DgÙ”# –©+1¼ê«½×i²=vÍD¡0K£ãžÛIL G:CÄm¿£¦6Þpáìyüû75N;PèäKÍaTÖ÷È?I÷A¢aS´ÁÈAŒq”²km-+Rm5·ãÿM_&ÚÓ¸Ôn^ÛymÙ©i²˜±¦ÕêÔcê¹9Ù.[¤¶0جî§uÒ®¥cZUË6mË*¤´Úm6úÚˆ)ÁZÂ#¤n ,‚±jTT!?dÞýh0b8–€š‚#)ŠBÉ„HA ìÜo>çŸ"Àj×Xø¸ÕØãáöxöû¼q´õŽ078ý¾s5ÅÏ>¤ÂP¤€@´´ÿÊ©al@ÆÉAb H£'J,P……S ‰H®º¾JÉñ”jxsnyD‚?Ž.>¹µ‰õÇ(·zªˆH’àÅç®·mòmrÜ¿#z¼Ýdµ’6“nt¦TŠm\Ûš¯+~vµë/Žˆ¢Ù5¬¤5QÓX±·UÝQkãkêï—¢_:Ýš¹µ~#4VókÙÊ4bÌJI*Ñ«RM6’úûuªû#@hi!ê$QAøDdyÇAôîãæÆ!ÔÑô‘kopГ0YÜ©rÿ h$R0ŠËY#*QÁ /¦d#)ÂAÉL f#4ˆ@†P±‡ahH_DYD*ªª½ò®Q8ªÐŽ„€»*}Ê=„cƒL-ÚÚäÒð8Ì®Âüƒ³#4àWd%ŽK‘I#4¤qؾ٠ŽàØ©qólÛæÔy·oÇÈàT#<å´"i¨—œz(„#)¯Bl*ªEH,÷l·f´ 9˜ùë„$˸ŽKëP´î„L!´Ûg,.Ä.Xr,ãË*ëOª/dT¨‚g_ÂâŽQG]±“|³ŒMÎW1¥ÆKXnãÓZ®°;:ï2exKNuvË°ÂFšÈ´¥1F¢Ú$Û5ö•ÕF•¬Ól¿~—}Jê6‚13Ea¡ 0/ì¹Àë7´íåßdÂ]®Ø±½¶æù5J*ÎDB÷9âŸ):õº«¾í¨¤/ØG•ÈýhhÁv$Å9–ù•»ú°5NÉ™n]ÝéËe‘‡È†rÌãuÏâµyL¼óÆ®ù ›ŠpÆÁ°ëæÓdÃ9I11¡”¢Ja G{Š Åchi¼”dÙÓ;«Tî408ZHÊFÛGw©dr4Yb$!˜Ž#ãé`ïòDO$ó½ý °CÌUU‚.ðž ýÁ4šOxà¹mÆPèõ:x GR† *=â–8Š"ÙàˆÅ‡1š0-TÝ–ÂÔZj–À¦oö?.8ÑÂI³£ Ïâ½ðu%\›9Ò/ñl€hдŽìI´}^¸q®r1„‘#<ä:Äb5)¢&ó®äÒ×MµÈÑ#<2ˆýì¨ÅM’l†‚,É’€b( „#4±’’åMtß(Ê©Nõ^à’¿“œ¿[>åWíî8?VÄÈÂœGa±à‡#<}2ÆÝ#TiÿX&@~½tÁFu¹QþŽßå¹¢»MŠ@å6Œ<c‹LŽ:²Ìöec*“ꆦN¾¡>¤ô*¥ö&¬×oÐ1Pù|TÞ‘¨ïÚÙpòHq£F²dUÖR#4©†,Íšf*¨Ú[lµ4Ø“X(Ìb”×óÊìBAc3i#4¥ŸØÚܪ2V-™•M"ÑBÊj’kRŠ¦F,UM¶Ê²Ñµš™m•*¦„µŠ”ER´Ê&ÍVhšj–‹[Qii³í[ò_¡CåAŸµú?2ÉÇu6$vU²Ô‰€«€!á’©×ãü‰D"#4AYmÔåm´klmu5rÛ³=Å^£¿ÛêkãmæÞ´±!8°zŽþ®¢p@²â5#)©Èµ¹±c'Rþk_.«ê~³A?×ü…èq_VÊMÔT\†‘HÆša‚¨ö!H–}R`÷Ì“ã‰2BÖ 0ŠB{üü—GlŽÍÅÛDokêCÇM<SûÃ>)@J 5#)OgÊ€;”#4G±9+°?‘ˆßß¾/ó&é´‰WN%¿Sœ-'•¶¢ÆÛyvˆmétÕòî‹Ì#)¨2 ^Ô¶H`A©ëBbùmx‹ÆQä;§·†¬÷i¸·5㉦4b@†>Ìdíu Mˆù °òô¨ZN(6Ò™ËèKà„66‰cÒæ“;ÖFF‘¾8þž°ALãVa`f]B×"¼ùÞÞ³ãy¼#<¦$Fkäñ_§•½îWÇœTŸ\& 8CÖJŽAÜqº»»Q]Q1ÜŸ‚sDC ví;•†oò#)1l¨þ¦t%æzɤvaÒo©A3§ç£Hó8«rE-d`Æ‘É@ð~´tÀáŸÔÑ« éZÆÔE`„ÐÅ$bDè!ËùÓVjqjŠ(³ˆr0cjó<??‰°õ^CÕ±â‡áù‹®1aÇ:Ëé«*ÐÈÔ"铳ý]þš"ƒ£ŸRb!‹é[¸œq”N£ùbKõmIÁÓÆ1ùò:*²#<"* ÀŸpþ;qåžWoäù¯Fž•$!=àGœÖVüŽíù:>yžÅʳ‹i·í9zØX¹¾42jªhL^õ¯Ra¹våÒòºívÅ_/á||M1×mØ>´Ù‹R%PeEŒA"Ecb‘aà”^j{õÆ0$ûö³Z;½{Ò¬D¶ýýû¡¥›5ˆ>-ÉÝë¯U²’¥,Í¿W]E¯»®å²B4Ó#< Õ$m¦ã>__çP³Œxà01Z”ƒ–ˆ!wDY$RFãÓÆ‘YZ@44 ÆÂñ û-@Ò»Š1´&µiñ¸·“±ï¹Ä0ѳ&?P*Йt ¤ xûUE#)[ÿU¨Y‘H®9R^ˆC®ÔÏÚPƒ¤p€˜mµŒÙ•4H‘0¡õA\ð(@ñä#·:ÐE#²^’£ÏÝœþ:<s/T–á4JqÛ:ÄÙÖóxÐÚœ™‚mQ¢80ŒÃ¢æ ñLÕzŽ^1ÜöèfÇ–ó–˜Òg0¦%§ì´÷‰êé D#)H¦,w…ʲ¬hŠÆë¥ÚêôªM¯jj^µvjªA§ƒ`³HR§Šef‡Ïæð¥ã. ×QÔ:À0?‰J]}ô;{|õTHtBå1IEÉE³«½f“×aõ§æL;µÙRŒÍ6j#40FL³ÏÏF[©NAõ*=ÈÃ6;6Í;MH§<ÍЛq¡|‡p´ÂEJ’ÇÛ®ŠÉ²Uñ®^»µ™^w²à4†ÙS='–'ć1„èÈS˜7TÎØѱWl„€$‹"2)Õ`³.âB—<é+×)’»Þö~„ÆÄòDÛe`„=Áªw(²(|šâDC ª‚€È‡YÐÆ]Új m3˜y '/KGš»”VA¨_á0Áèˆâš(mÒ6Å8Ѥçk™ÍxʨöysLìe v6û…ȱËT'ð¯ƒLAñÅß²*ãÐ@ÃÇlÐÀ‰o‹«Ë°p#)™aÓÞ–N3zÕÊÒOšA"GÒn†ÛhÏÎÏñ3јû‚Õ6Ø>aÅlOÓ[f)…V‰xѸ¾Æ[®EUkŠàtÉ a"°J×T¼E#<B’ÔÆŠ¡ÒÊê1ï#¤y–Qûä¬eaF|Ù55åêÏ×'Õ¢$»3ÊD´9ïöüyƒB<õåÔª(™YÁÑ#4Sª6‡Ì**ì^OF"6[ÄHùJ<;Úæ€hŽ.´‚cŠÕ©kç}›Z€14¹Ãº¦Š£7ýe]ÑAI¢á°¡5rËIì¹£5\l^¥…Q[! ìš—Ç/“J°œö•E¸Ã Ö³¦¿ÛüiæÙ‡ÏãíÛ#:Jœ#4p}(ªÆF%U<ÌÝÝZ”Š6ªù›™u4»^me‚¦ä¹d²)#)ˆFa[åÞPuê¦AOY#)/wq¸‚!·‡lëúÄ6s.æ¦'w€ñ ñx˜þíîîÉ#<#»Bœ3 JÓ«W¯,æNÞG3îlT18ï4ìô˜‡8€²²0ˆ Å8æÑÖ‡ŸXH&Ì'M{Ï>¦Å`(®¬l8L]ê@\8ƒªhl~1M]}~}jå¯iÝžDհð>ü¢k B/¡özZM¬#<6– xq÷lÖž@P#yJ"wYÔÅ@¤¢^:øedKi™1W•ïtÝ{Ûy{µÖ%eRªKU¤Õe¤w:‹iÖÛzíué¢é)P#4‚Ÿ‡Ëi´ÜQAlˆV_ßqÔ<z´1{1-¤#<¤€‚€ü‰I„ ¿˜/ªÿ·Î`-FA&%šAŸ¶ÃI)˜e"€ˆ3T1>™2O¡8ßÓÚÜ—{o;MdÊ\-2á˜Ö0⸣f‚41…L©•¢Š®mëÎÕÒßzù]ÚûeËÊ#<`L¹dÖál3Te#4Y)Â|ïˆÙÛ’Ìû#©uu|믒YÕX%10léçSj“{¦,#<Î’g#Z"9 VSÍ>i´ÚÛ#·x°ÓÄ6ñã<fÙYB@’6j^„8aˆ_ß›N"DDj#4!o,6Ӏȱn 61 Òìˆ>f!“dlÅ0D"Y¹F«ëˆõp6U"vróº–Ba5ÙJ#°ªF@\2#)ËðSṴ̈̀Û* ·ùƒˆåîôRÒÅÖ(õzDr†¿låãÓ/@ÜDÙ?ѽßÝfø‚ƒ4àêq‰]¤:ÁÕBRP$€Èæ·ëm{+4–Tk%lL´lZMmF©K[÷~æ6¤¥~½ëÕ©ý¯6‹y\Û•ÃS"@‚ú>ÌG½"0ºpÚŸ“xÆ[GH<×,¥7u6©Ç趢Š#)CÑÛk5d*>é¨8§³_.ŽGLpÄ#)1(<`W¨Ÿª|“Óýi Ã{’liƒº•Œ‰ó©¶µÚÝו╬ӶګØLS᡿ʺ4»[4<f8e…œ¶ÍØ{Öøìf·L$›{±¸oÙ“À&»ê·Ç€®xùΛå¾öd=†SÀè¶#<DMDÈN¬^Ra{l.5·.×Õ¬d&’BdÉ·ÎgÓ®Àæ1`X£;æ&½œLsÕI©Š@XpË9´7m¹as‰%îcaÃñÉkžÔßÁç°[E2Ü–‚<oÌ¥ò—rß]0Ùz|O"£z…úùkÀã{m»¹ÚŽ<¼ j»"íäq¿ãK&fG£Á“š$éß4w_*NÚÇÒçγDûõسëawœ=˜ãkJ(ÆÔÖ{Žÿ/câ)¯Xæ<TEöàQžìbͧ¯ÛÏìÁ=ÑyA£T|AÔͱb4Ús44°†+Ò€MÅ×ÇcQï3#4;kî‹õ&£ÀóˆÄÓ_TœÙDß,Íí±MFÂêèx2³‘4tϱ"Éò¡YM]·x¼=ý”Z1LGO»„¾›žW%³œf‚8ž=ÌŸhÈ=‡À(5ü44A˜„û ¥µÑͯüÚg Îé^îÝ/pÏ{æ¦z4ƒôº»Æ˜}Žaá§;Ô‚ÖAT{Þñ¬j+®íñVæ‰6½øýÕÕŠapDXI ÊI-ç)h rC8nñÎ÷ê(ÂÝ[µ¡¸1 Kx;$ž²'øAºÊôð¹hª’Ä ,cì6ï"™Q“ 12¼²Œ± ƒël '`8).’˜ŠB#)-1¼(Š)ñHÄS™0–3hw#4*Bga¡ó½r.x}|NXó=)Ä|8:³yˆ;¿Gîº^wU3Xµ¶+\åÓ‚/ø1÷pL|ÀêùïÛÓ¦@Ó¸Ù¹ðÃÄõÑÛé×í[CN7u&mE… Œä#É#<Lµ0dN•Ñ³×ƒt0ôàZÇ¢š:iÂ7qr<…U¬ÚÔ–òüð7ÄÔG¥>he,…$zÑÃÚ‡qÚÓ‘¬#)–/ë†À‘%ëÕ€£´HäfkæMÏ®piˆQMCTj© ÉÌ¡Z’M´FâAÑ"]ºÍ³MiM;v«©6Uªfµ]VÆŠæeåË×v•½oÂl£diRÕR¶ÕŠÖ»~x7;“€=õO/"²À̳ÕðJVšö ³9Z±šnÅ¡ãÁ¶ â#<mÏÕÐD›ãÛ|FRn<s©ôi>ŒÜAµ×évSRê–ß;„¡1úrr“ljÅ1ÄåµI¿7Ø9süرÑZ5ÛüU§Þ#£+HÖ¢F†p“d˜FYÀ{r#4Îè¹}Êf ‡r!2ÐVy õÿ7A¾ð| ¨Þ(e1ÖðåmÄÙ‘F&ÌÁÆôèh4#47¾&ÔYX W¸AÞr›½õØlÐ|#)‹üUçp>{Uõ¸z}ˆXÈ»!ÏÙbãBµÕ¤ò¼@@ G²XÅÜ»#4Eص[Ù»NwáÿÛšªÄH¬Ë@<åNÔ)?:κÆÌ\ÉÛø²¶Ž@„V¸fpv¼xÖõ”@fEÄŸl7|߇ïÿ/óþMümáý)jsª5î¿4ÅöÝ'zkÛã›Á7ÆÖ°Ø@ôd>ïJ…Ò”¦å2ÙWVî#4$¡!©ª÷’ÎAR¬ÖV#4¬}˜ó1ë?UÁëP¸B¼‘¶ôÛIE`¡I±>ïo"â‘Ó#4ê.ˆ.þÌ8‹ÔÏÑÂâ”áÔm)Üõ^f–ÆÓ`û@nx¡’ÇíYt'¡;”Ó¦Øá²'Ý“69lÔ@~=ÓþmÈ`ñŽöÜÞÇe?§îïU»•I$—“6BÀWƒÃ4f[o½FSˆeyoÎX2&.Úá5³#[ªEv㔾A‡°S§‚#)ØFBçRÄ:x¼lã´Ø½¶†ñ?#Œ;þî^-¶û:c(˜Ò¡æ] ˆôÀ²R`QŸÍ•Yt’^ˆ[ ~¸äÛ¿y5¾søç4³ÓfMèQ€ ñLŒ²<Ÿé¬ÝYŒu®Ó~™L38#<èjwÈ]CF8—z±HjˆH=~Ëhˆ‰!)‘B½Ð>cE_ð=o@[å~Ùé±,#‚ŒÆAŽ‘ÆîjzDETî{À[]Ú0HmAÂ+Å`Z4}°à‚‰ŽÏL[źa[0™38W^t䫉WÃeç€u‡ˆP,¿†ãƒ•ùý5¾\3ôa#4üDˆÄH!#<E4ƒÉìÞ¦(_..œb„Œ Œ=Öêkc[§èT^m¬ËEj"±ªÅ¨ÚŠÛQ£k2¢ÖKlm“Q[ÛV’’ÉPœ"hØ€g¹ê|ÿŽû³ ì#44ª¦F"Dƒù’0U] ‰À R¤dÓÙáÄèyËæB}©–z°r!€j%¯ÙC´ýÅÇ«Çà`Á€)5£<Ú€‡Øq0Ø`‰«L_nhV:ÿ<öúÜ ú©x]5t<‚#!»xí‚Óm‚CEli î°2Ú=±íi›ÄqƧ˜}‡”!³Èôe}gò#<ƒhy•¶^lMy‡¡6)#)¥° ¶£Ëˇ:ŽÞwN°#<Óß×b4˜wñ›³_ÝRj–‡R§:ckƦ‘¢‡àp9w碗Èó:‡¦‡±ÊÜT@;â†y˜¬Í Æ¢7@ °Œ'Êt’Ó1Œ\Œâ$Êl·@>g'Á˜)…‰¨äÂO©Ãƒ!Y%Wô-U-Ù¢z,{»jõÞÍ÷r/H™eBQªš‰œ Z<`¿T--’¤.¶(žp`ipƒHŠ1]ND” ¨'ú"ÊÖh” Ÿô$ÃÑW¾C‚0½*HP: Ç‘œCxbFH!³*¤EV¨TÙùý}ìú²|¾žÇöØÇ5äaˆk·WSŠîw]Ûå¾ú#<c&Öû’H¡=ié^ÌãòÅØâ–ä£ó=43Øæ*Èî,'+äjˆØV·s•0–HI™#<™T¹rœ™ä€Ù¡¨P¹½we×ÞšO#41,Z d/ƒõXkŽˆÂ‚ªŽx;Uc¢ÅÓ?“OBê ßåé@ž^Àê„Ú#)˜q~FÍüŒ!Dôžµ¸:ý:Ë©ËÎu9âz2/ð•-]±´eÕ#<¨¬Ö,Õ!®+WDµd)¤…U#<#@‡Ú›¹°´ÙÁéL#4f\h1éF÷! ¤Æð¤#<šla›Òu†¢¢„r41¦!HD0IVQ›ECE£\]ÔkM#)ècW?!ñ™l¬BÊoÀZhA&2L`Â(ÆDJ0'“éW}tíºãÔ¥.qÓZ¶‰Ç¨P„”kE©¡T‘º.ê5!V’™ó¢†ˆEw„qßÇpÉꙢ~ç™jƒbþL›IuwfIh[ðšòŠ¹_–WL‹6Mµ&×:IŒöîÒnR¤Úk¾{ÓØù*ï·_<Ñ*˜+æk³A뮥õÕ½Mj"±&˜4ÛfdÆ„+1ªfWíw»]æ»iii™l–©MCB™FÆ5I!¥+,„–U3"¶SRùÕÛ×jä·s¹ÃnL›ñnÞºæ±sGeö›–ö÷z&#<–¡‘¹oËó¾Ï¦l353P¦6REµt•M¤40%#lFh 66jµº\DJ‹<&ÍZ°Ð""±T0`7Žâš±Œšcy‘S‹ªkoRlÖ¢60qý\À«9ˆi†ÃºªÖ0‘`·&[ jm2åÄ“áÇ„ƒm„Ï•G6›D8âÎ)~\4°È1œe6†ûéFÚ†4Ürgé‹×Ï4®Ö£Z jùµÓQ¢F¾ææuÚ»Çzjû›ËôŒœz¤ºD6ÚoÖ7IEŒ¤qY²‰0Á@ÃÄZÏ÷`™.>Õ4 `æŠé¤TÒ±4Â1¶›hÙÉkdNÔ†cªÂ#<«Ñ²Ò|ã›^÷¯rëª_Þž²2°Q š"J´‘ã"s[iŒm&ÀnÈ&41™’[Zcßl¹ÇŒ#O#ňjÙ”’ƈˆƒÇâ4šÀÜÁ×Rµ,!14¤bndÁ¼øÚ66µ!’ÚÈôÏÆÄi’l(A‹§‹àâZu›¡MÙ74¥‡œyS5ºš±·üknosÍQS嵤±¬açé1‹'¥3¼ãO•‰dƒÃT››[F8fc/hF—#4$nÁ¦#<á´ƒYŒ[f7·ªbU6žzB¹ABi„‰Z¹˜¥âÆØoàoî5C?Ì}Œ¤ú½K.Å:#)ÜGŒýËä˜aØý{›6žÎ8Ø$¥Àåa~†<Éñ{tóøc¥¤ÞŽq&òEFcmÙË#<îB¾˜«ŒÇ Qu“CÌAm)“„Ù%Sj³|eÒì=ÓŽ.BbS|^7©Ä˜vθE£fÖUk1‘U¶'Jò§£ HF[†â#ÏÛÅòŠköXa¡#<aCd7Zéü¸yX@ÅßTê0@Ü#4œÀÑäHÄØ›Iî#)â Ùn2ƒ2ªÝ¶Á½ mMÅó”~zopÃhShA…(Š‚g1†`QQ¶›}´÷sµsh«¦ÖUë«…ƒ-…Ä°Èì(!x’”lF¢00„ňc a#<6¢Ú](„’ÂSŠaý-ÍÕÈëì=OgŸ\p<,û6–eœRtNô<ÏóD–ýðQ‹¿Ëmî7ìü¢¨Lï¼û`¨ÐR9„ü>rôÎŒ³^úðÝbk]ZQ‚›'ìK¥Ést¢¯éýWiæëå^¬MHŸâúô‡£zÅŸ G‹^ýXÚoÄÖæ¥%+Ý‘îèË.C—§2ãçŽ9š9ͼãF->Jòf¶Ùší ÂYVd¼ë›ñXhŒÂ¼tvŸÌ¾gÄe Ráe÷ˆ«_ç\!€J-—¤ˆI!ÉÌQÍèSóÔW¢¿#4o=Mós)„Eï;Ž4òDˆÑ‘‡0"Œ'¬æèúèmUPÅ„0ózH³6"'€ˆ„“¦tÛÆpÜ'M°’1YÏSµË´VB¯Ê#<“¨¬ÄÝ^!™C[râÇFÇz÷¾Ì(„ä^ï¾t%Òheþù¼™Œ>óˆig#ÆVÞä1·Î¦såÉbrá‚à=Ù¨BÆåË~n@y2cÛÑûµÄ*c6Âi•ª÷%„CÜøˆøË~Û熙Hë«ßdþß¿¹á€Þkt¦ÔJ;C3J4i%bZN ØÊtL!òˆ2"Œ"ŸÍÏÂ;ï¡>:“àñ ‘H"#),ªjüµýJÅnj× FFÂЗ²ùD5¥… üédFYG|LQ.UU–ÝéÜ]»¦žöôõµçJåWeªé^tµyîè±[½×7M]ªºmݺd7.ÕæÔSÆi,EH¬ŒT©nqÝ·wm¢Ôš¥©¯½µ^my“[I¹µ»n½íæ5Š-Ql€¤˜!ïpá±õ²êŠõêi~H-¢££hŽH‰/Ëê}Md[BQ#4 óVšÖ]jÊÛe6ÕÍ2]¤)ˆ!±eFÅyØH$!J7B I:²äMˆQ#<4 ¬ED¡Š×këkÄËÙ_«åjö½ByæÝX¸DÉ’P#<!B1(L" EEjRIF²‹þ¯t¶#4Ù¢µ ™¥+ƨ¨Öe5¢Ø¶ÄšÈhÑ3ZM’)š“+M"´Ò³@lÆjÔÕîÕtoÑÅ®Ù'öõ«ø‡n %þ™,ØIÂB6JÖ¢¥ìA¤Ú£hMHh¶½·õߧòüþ§ù¾k÷y<ÞzózìXþûÛÆågi`Ì"ÑnôÖg m–«ZÛ"^&P3·Îç4Tƒ"ž16M6´”Ê4mö9¿ó¯Ý+‹4kWJ¯Ø·#4V,Ÿ¿ŠìAj4ß2»&Ýn–»´W]b5¶d4š£[Íï[©´¥6Öš¶WÎÝšÊѨÑm®ÚîÙºþïnoú´ÍváN$bLr#)ùxIä “ÄC š!0db©$#ÑM‹€ˆê]›qCŠa$ †¦2#4ôªÚuòÌ<íõ¥„q ÚWI„5"$„50,ócÜXaŠp’Äv~ç#)û™;Š{BU9rEæßi âð„¼u=‚íR¢0u¢C*1B}3MÀûϺL‰ÿ]¸†mÁƒ{TɆ27?;,q±ŒetÌ“MB`ƒ#<a!rÚ•º8$ÓÉ2†\›};aÆ©mwŒ8¶ëXdS%ÏÝÆù>ï]r1®ÝJÑØ&5lc&+'MŸ9ýÉŽ0J#<wuRyhÙdüJé.ÆWÑÞñ¼hÀðšÅ’óa™1%äO28Ùß%µ$çÅ0ãÌBvs™ª-6݈!Bsº´@„ó¸kLL¢‘âQFòS.L4‰w{½ïÐX*ÿ);¨V1"“ÌX#<N°ÁÛÛŽíÊÌË÷;Gça;w©»ÆËCù±´þ<¼¬åÕgŠ)“›µ¤0lTÊTŒd|ÜÑç¯UyÑû:BSØxÁ>q„D„DY5¢Ô–߆ô¶µÒ²b™oùyŸâ04Å@FBKP$±4$H(² 騩hŠZÛål0VÎÛÕÊÕø+þÎ!ý(¸Ï®‘$#)C$ñÝs ¤F~„Åüœc¹„2"À,M“m5×Yöï·u‹Vå\̪½ }èX`@ zwÖîÌ2 [XÈ[T‘.!§SbÐÚŠE ÀÑ?‚lÒðÂà¤\€[²¹DwÁY!U™&1²jÖ½¬›ßY*½ÝWlSéw•u‘$šËšõë®múò<d£E#)M4„Áˆ]†-k7p¬xi:²dB™ƒl mkV¥)V‘õaV™•Ã#<!DQ#<#<ŠÆ,@Á|ö!ˆé€4™¤+ÀêÃûä"Í ! rÖÌIѤçH×Ü¿.$'¸„XÄA#y7•$<ßçâë앪‰JBô³àè‡ýÇï’}ì;[A…‚©ÏÀúµÎš»xÈÉÅ—råÀäýFdæ 4cçBZ›3ønËnhŽ³TxDD e¨ÅUQD„ÁÄ„È$6þŽ³Ã<¾ÓæF0ªôã£+Pby³ÿoë^†5Ì£t?;©ªQùF\CcJ™þ2bò9ÌŸ¹Óz*”ªaéUt•¿VœOÆ^–5#„äùñ<]k>NÍû³’ñž$ÛgnM°ÝÀÿ]ÂÙrñ€ÅElëG)IÑ{¸Ø!¡»íS´8Ôuž³p#4÷<Rš"C]!#<‰%¡ó:šÜQ=Födäo» A9{‰5s<êÎ8u¸`Ý–„<©#<Ö6ÒLCZ¢«x`ÒPmg©ÞQaÀ;{D2e!‘ªD–#<ðªF’”Å0]MƒÁ¨ñøˆ¡€€YÆY̯7=}³¿·ÛØ] Bb?™òzÄ@õp[}ô=SáË{5Ø÷+‹•&õ<2»E½À(ÇÐþæßœ3¾MòþL ‡\Ö®Ô£©Íe¨*™‡4¡‚%6QÏ÷]嵐#<‹PC‚dÀáúìø\MÀ†¹xJ€wUÇml&VvV»#üšöõüWóÙÄŸ,‹ Ã.’®î#4]RÁ›WJ:îòô6%(ËÕÉbË"S3P›)Z=*楥Rm”›/7Á„¹«•ËnS‡tÞÞ»q×]©ŽtK•ÝÛ¤WvÛ¯-ÏM±ÊE-één®]ÍfXÐë£ÕY£#ÜËTÔ¬îÜÚé·M¶ºm²–6ºÅ6îÛ´•%o.é7Njs3¥¹"šÛ 殲çl¬k9ZºîµÕ¥j—m¼¢Ö—ø»oX¡UÜÐ#<XJz ð#4˜¦°R*fàÒ¸Dõ‡aèˆX‡”#)äpwÂñ`Äj‚ ”#<'k,ˆŠ.ÿµÅQÉϯ5ÇöÎتƒ×ÐYC¾ŠAb¯ßL{¼!=A ió«eŽ(|:š`ÉVÁ>ÎñI÷PAùýÜôwͤ:û¸Šî$LÌV£33Møëëš«êZ”D¨Á"‰’–°]ÿiOº „6|~}–Ê€°‘B $bĉàz£}#<YA–¾þ]£+›]¹Õñjï;—9K6‹2åoK^[ÒoÕíäÚi ÓBˆgÄ!»’ÉV€=?µ¶¾ù|[nmÒžëo¸bA;¥%$͈¨Šû6¶úV®ôPƒH(ÌŒ Tq#IãÐÆ!F¼ßzcV·]ÛfÏÁ·ª}K¦®–Û³"E¶€AÆ‚’á¨é?£jà&!J|R²¯-ÔPc?ߧâ×"#)´¢›˜Jı…Ûî?›ù&£×X¥Û"‡g@Ø™ÅÞˆíWìVØÙU5lÙ¡”ªÈ©#)@ ê®yý¤&ç¢ æ#¬!+ )FH¨²AûªêóL‚(#~kü¯ÑößS\Ñté©-37øš1H±ˆ¬B88î8"á#<€TZ¶f—¦wQÇ]HíCÓ$(3–€;.P©\Ï@™~'ˆ\jíU*«hk,l÷p¾€ëz=7;á—ýS×ò_ÄÈÁ8© Sȵ^¯Ïü·èNX@@еn6‹£dÉžE¬¢¤iU€‘»Mu ÈfÊ+åw')#4Ý‘E‰IHÜ“ƒq8zqÑÉ®º¢Y¡7üõg$üôÌ*é…»ú„=$¡‚vÔríV°"_F‡ö8ÈþL+°pØ ™ ‘,aY$¬QŒN< EnìnÕ_nêŠõvc®I[G×Qœ²ÈH‰ím.!)B"B06ë#)lO[¨Tnµ© Q@1¤*Â3ŒªÝ²ƒyŽ…U…¶ò-o*ç?‘ªóÆÜ©-ËB!$-’Xɳ.#)´~Çg¤’Ú ‡©rq+U|\Iíö¼PîÎÊ«0þ`Òh(#<b@&©(2jp¦ ‰é˜Y%zþ!Z5‚ÃF£}uAk#))Ê)…X39«LÜÁb lAi@BDVADn Ôh †f€‡ðþ`Ö°&ç–°Û@‡”#)ÖRnݸ5…ÂhÍ3&Ë€ô‡æMÝ#<ˆ ÆR {ÊÇv§ _„´£vàÀŸ¦ ¡X¦#)Å4„5¡H$b%äËÜ®ï=ËU0ùƒ¬Sùa›#<‹î° ð$c l œ•:öÁ¢¢²¨#)€@‰‰Pª’*œB+¯ö‹Vû*ˆÒ¿TišW_-;b¥D[0lA~#4¬N’î¶$b+CÍB‰s¨xOTMÁ’y“£‚$‘O‘OøŠ"mxó€}?zoXLѵÕ#4YJÄÅ´BX;¥—Œýðãa¿²¾æ¸]?ñŸ»¹¬nŸ~f/çà–CCš´H75Ì–ÔÉ®û®íõ9¥Ø=×{»P˜à¡ $jmWGQ—+mÙ!+¤ª±‹ý¹8fÙµ°"ì¼¼£’5,¼8#4T*Ë«m°¯v—(µO¸Ús^o#<Yç‚=ašHÁížD<ú7†Ã3¸<È9`TõæÙhÝ)PC^Ê?<£¨ˆ#4ÁI´å‚›d4P$TS5Ezèi€ »=’Ò…ˆ…Á#<Š‡@J@ó(|ŃÜ`Ø›ÜÀsŸµ_ì0‚tazònéßõÛ÷ÎX›7÷!þŽä}¯Ý¿n¿ÙÒ’hÁ¤6#<™¡'Ååcg®9(‹¨>`§ê„ pþ Ï«³½Gàþzx·e˜õônœ®â?¤iDÆÛ×Ò¼~íÆ·¸K.BmA¹æ¨¡5!!9B4Ùûêôaƒ€O¦+Ž—WØ}¸4™¶'Þ’ÏèPé-¼Ø”j_5;DLÓDñ#)N±8G-V$Çö&qŽ9qÜбws]<#»2„éƒÃ|LXÎ.òÍ5>«.OC2Í\$DtÐÍÄ#<0#)×AQ®”64VË|²Sóvˆ÷§zˆo,åÞqü\\‹««ÑUT¿k…Œ#)Ø)p|èÍ\Y%Ž`aïhšÊ’B,ýn^^&®ô)×I“ÓÞ(÷+õ¼H‘÷Ýñçw™|÷šÁ‹øã‹N[†ŠªX>û-#<åû¸#…U7c™òw)ð[¡§g¥„JýOJÐ[×öGŠòdënR¶„%º-2=Rÿdп»Á¡1§.ŠX¦ŠSK¦•ß’ø®Àw§È¤rêѲ<`Æ6Ùèñ#)‘d#<å˜#30]Ì•¦!…(æˆi4b¦ƒ¹øÏNieÏÊç¶K½ß§têæøŠ2¶éx§kÏ<uo+„Ò[E(¹ˆ=g–¬éO4½âõ¤ó``òtç¤5ë<ØwÙŠÇ©‚ÁŵÎ f¼»l"þ¨[Ý×5ëÒÂœ hâžÕzâ8ïÞWržMü+0›¼¼¨Ç»ê5A5c6#<qØ#<4´»5‰t’{©#4îÚÞùMcATê'Ä ¿GÒ$Ù‹ÊCgš¹~²þ#<›Çú샬‘ÞÙ;3âüÂÕÁ×éÖˆÑÓpÓ#<À„C1¡Ð p‡à©a‹Hý+€ë§%2 %A2íäxYì< [á„ëµ—±ï飾Rö“V„×ÑËô#<&TPnÏF‹sÈTüepŠè…Ë8<畸ú³pȾñ¯’v˜t¹ämLÛ#)ÃU%„ÚèFóÞihØĨ5ïÈj8‡È;gú÷fX’øÔ’Ð!3¹¡òn˜Iç;ªx+Qº%D¨Âx5$3q°èQ¿¯u716 ³¡1[ÎM—“¶H…Þa?ö(ùÝ,ŠcÅâðCušÕù ~Öoå[Œª©jUUf×omi3¤°gÜ„¤£l\0JÀY$ô²m²\ÕÎùwò/»Ü·¸Á/%q;š»{Z»ÄebÒ|«&ÝmÒ%™‡†¯¨n¿|áóÔ㉥®òÞ©ëÆQÿl^÷¦±—âX/~˜ná¯8mLÇ(èb*¶Ù¡AؾEó½wõ¼y3€Bhdž#<ÇßèGqg<†ÂgŠ°XÐ VŦ‚1|(%R&†1œÃ‡<ýs¶ÛÅT´ÏLU`¸@”áÌøK îŠQ ðÏsÆïN…‡b‘ð}Þ/;™Ä‰¨$ta+Ï^ÑÊpUBfMÌt¢Ø½¦1®#444‰Ï»F´Œúå¦îžVÍoHXËòz·Û,L(©$šqíߨðã…“¹lÁ!éö#4¯ÙÒvÚ£D˜]¶¬qÆDÂý=J/ö<4ïWó¡ÌÇ×û¾›0¬&ºžÙ@Cpo~@Ý€DŠHÈŠ%â## ƒßÞB!¥:aï‡ÈPþÁX8>Y‘$A$H‹(wn/WØ©ã¿–HöVÓñWœó¦#26ÐMýq ÙFö1ä½ã°ë£DãÆz|¬Ø1×õžôî—Ö@Wäj90¸[˜xÄÄ·nE¹‚(æzNÞ±på‡Z;ý»Î¨û—Cù›lëíà꽂ӹ¹¾!À6Ý#)#½I_'ÎÀ SðÕµmó€h;4¶Ósšh‚"¼Šdœƒ”=àòU”;؈\NtHH¹áù«Ý¼…W\çL:qÕÈ(ã°â÷Qè²>ò*dhú¹ÑÊmfŠ>'o c‘0>]&8ÖR³œZ®§ÆycÞ(¦DaÄ (‰á#)¡¤`šƒË™ªÍMµi¯,‚üùq$p8…ƒƒô€çÖFw$ωÞ]‘ohÍ(4À°¬bqCåµ£ºœ-qú<þ<þo¸·¨¡">ªltƉa;D€˜FÀ"CúÙl·{¥CÈŸo><¦ŒXÈB‘J©3„H‚–PÔ<‘wB›Ï}!®‰îõ0=ÄaÛýGC<NW›*š<û¯Û3?#43'1Aî§õIK–è¡ ÷8‘#<”Oû3lhj%à(ÅYýñ8eà’¬=ñnÜ.ðâ‡]âìó+f^È_d|K0ƒ1µý6·êŸ:Ó΋Qÿ¢ÚuV²½‹@¨%sKõt¼uÈMßcëÁÁ1Åçc&ÔU^͆(<âÞCž4±’Neˆ“V™s†×*ÓR94P÷Ÿ+ÿW׶¶¡ éòÓXŽûãVÝv¼66 Ú£ßüyy_Ýèaz*‰Œ‹ùb§(ò.PRøeÖ%1Ütê7•ÎåÆš˜:p@/¿Nã‡#)îuÖuE5Y›Ÿš`'Õ¸¹|{Ív7šÿ#< $žæ|lƧ”nŠG Ÿ†BÍÓ²µ§{º‹º6êš”ûºÕü›úð<yCi HaÌ'kQFIdM lé˜|2¤ØÙüx®±Ò¸Ãµ8tÕ—æñ«b>´²a•+Ðîðrßb,–#<jzs®UÝ?Á®µF5Š€‚ÅXB *$RAB3·v˹åZ’HÀ0܇5ÉbãÛ•ÌþBîºJ)¿sS9lTX¨Úoíÿ/ñÞøoôxöB£2LÑ’"¦ÒÅÒÐLB£BbcFQˆ)²2lM#Ad©²l@’‘$èºôž“Ëy–ÐDÇyò²ëŒ±T>"nÉòC^ú8ütÏ#X0pœfØC‰Ç.:3SaÂ!Lj#8XÖ.oò#`!` {:Å•þR¬ñúOÜg“ó!@†4*TÚ|—^Y¶cœL T¶¤R5*³‡l½©[Ïœäþov^ú=Âi”N·´˜‚hbcm¼3‰˜Þ—óÃ`-®šMUNB7TRzk6ž^÷\qKPÍÒí®«X,LýÈìoá²[hM[¥Ù<ó¸~Ãò÷ôj‡*”G#Kó¯Ì1„û\kF\àÍ”#QÎÖ<j’Aÿy2eÿwŸôkÆŽ÷‡Ì…AÄS–#3ùQ@Ñk-‚©Œ”ª¶Ónsnuo+å›Óao†Z5k¯ž™7¤có*M;¸Ð¤ä#+š«µÝ77T—UÒ;í{½HÆI#<Q£6‚3|Øc7˜P`µL)†ƒ@2 9 ) ”1€§“D@†b†&"øTmÑ#Œ(±õJÔK}*ñIIHFÓ6ón\º\³ºÜ¾–ªªpåúÞÄù1y5•iˆã>O#)SDHE²l*GSCDÈŠjÁ§ºš.×"~mDÔÕeÄÑs4Ô°‹þNWðö8YâUl3I±XM`t™˜Ä« AŠ(ÒšL3Ày1ùD¯è8Îq#4£¨R¸©£4¬*ÕÅQH•‡hf(<‰V€ökfÍÎ#Q‰)"E„I6 ÆF°A4ÛÀ¦¤#4.½ÎæDˆyw¶ÝËH‰ÐU‚õmãDH#<S×aƒ`Áf‰&j•„Œ>E¦0\ºŠnŠ‚cfÓŒ‚qÉB1–[jiµ4âi˜ÓÄ@Ylc`Û†‰hV¨Èà²2¡±aØ:UªŠÄܪ´:R\£³Û+â„Í°Š¸´&®¶ç·vã»[z¼îyw½$õ#<ã"e(l¨Þa)FGpn(”5Á¥êË4#4ìéåŽnãÀoÇnà»**æê®Iv´ ‘3ˆiR #<žžó¶²¼‹k7¨þLc#ÌÓ·˜_Trô#4±ŒÑ»ËƒrWGFè'bÌ¥+H;#UˆË HVI†DJL£‹Œn—‚¾°ÃÓÒÂH-÷>²Ôšð)¶ZÞWlÄÅI°• Æ81[|¡k€ìD#4ê¦mÒÌY’#mU©çpfØàBѱàŒfXVƒÄ™¨J#4ÁŽB•|G1- `žiÂ>²#4*ñ4§š¬Žá*\`V!¸§daYFùNcZN0£C"N‹p."€È®Ë2¡‚h2Vj’RdgH™ÖŒ¡¤tRPM>ˆž«&ºjb%¤›FRÆS+j´¤,bÁi½QœU RS,JW:Ô&c&‘C#4ÈÃLæä «0)…$c]B0ŒMX«A r ƒF4opAãEQQϾiè8-Š¾Pƒ6ðÁ6ù“Nâf©„[1#<X-Š˜A€c±Ë7ŠQ¶ôÅÔ>ÆŒçQ&ÆO‹ÑÓQ7¸Z¢é8ÃóhÍÅŒÓ÷Ó_ZÍš5€Âµ†rb&™¢‘AÕǹ”VG§•¸5ºC£d|Ië#[hZ¼%|Ïc#4¬åF&ÉwÈEöºÓ{‰aR‡Á:¦$gÏÖŠ•€™B#4¥TEXëR GÕ:×\›%µEù÷HÎƵɬbˆŠÙlBá2"iŽ?F¡8±yºxÜF#4UD@!£DLìRš" ‘¤´ Q%Ì:êUeêûjöÞkd¢2k13X(#EIƒBFÄÅŒ®YhŠH†(.MjP®¤¥Okƒó×éýK¸ÜÑ®ó¹Ãwuá?Ï÷zéQ“k¾ut·ù½q>\Š¨5HÈ-njNðD@‚À#)¡jlÚfË&M[ð¹k½Îñ.¢€ö¡J~´?ӊ𲃧Z:¡SÊ~ôaÖ‰QE¨” ¥¿(”€as»Qýaoºô•}øP8GHOEYÇôVÆI'"{ÕÈ#)ˆE„HÄ`$‚¥€hDK–×윤aô¦¡LÃÙ_#)a4'7]>ë8ÀÃJ‚Â@—"´Œ‹íøëÆÞcrÿ'”5g£v´™#4¦†UWèÅ>IÂÙbƒƒ¯OóÖùó¸årî툋k««”’Ò!†Ù$#<3£CEPl2˜Ø<„ƒ7 Äq"‹––Sy$‡íU²äØ575Ýš9`¦4rît#ˆ8K¶Œ4.|ßïû–ÂÞÃá°“6Pcìï%p—9Ëí³]y[Ã[îÌ"ΈÜ0|^{©šnNcËŒ›<ÍX'¼5#)ÕÕpnáÁµ’÷©™á|ãÀYómÈàûµxf•éDJØ>•W×û` ¬BÁÒrŸŽ¼L•Ã.ÁC#4QBC¸¹’V[Îàp“ÑÛù:®›ÜÞø6ŒúŠÉÐð‹ 1¬‰A.Õø~qO‘íSéÕû{#4Oç>£ê,ÇêÉM½¸<Cô$‰GÏzwçâ#<9Ñü'0õ»X xê’O6¿'d¯|NZ‹#@¡¾ßœ#4òbvš)Õ^Îþ¿?;¹'ç½HXµúv]Þ¥ügÏÖ~CM";neÍ\:I¢‚T ¡[þÄìS,Úš'„…Á]˜Spª&øimHª°4°—"a=s©Õ,I§iKÎ*¬mkØV;,o(@ü~¢Àöá*2¶’1Bä´X< °âX2h¸Ò=Ä+PÂLR‹dùZ¿©jù¯»y~Ž.âaÌ‹\þ¶U¤Eõ¹ØÞœgCFÿ<Xà³)#4\ÕR@—mE„ýmg‘¾#4Α9`8™¹±jIÁ#)¤„9 ª¯õPÀÇda¨‘„Zl‘m6©(¢×œ’Ö-æ*݉ncZ¹¬U£G+šÝ6¶é«!¬TZémr²k›šjåNï¦ähÚ“Zþ¾â5L$j¡!ýKpÃD8‡swIDµƒD¾ºEÊ+ a„GaF%g¿=øbÌ„sf#"—c¶‡>lc9ú‡ÀÐ×îƒ ˜:‘q0¤qA"¦#)Wë #)d Ü(GÒühÁ’EÈ€T¡’˜†Íû¸U{¸Ä?o‹¥'yUO®Í=#<ŸW/Ä Q519šY}ÓöÒ6}Ò’$ÒsAw´4É.ë#cãë—ˆÑÒMD¤¡$n"`Ê*¨ïá_:E-.©#‰¢‚`Á$T!0RKm-vª&«t•¥´"tÖäK·F#)”˜‰v÷G‘Q#4Óf¶Û~”mif£hÒk$j@@!D#ùÍþ'ÌBá©Pt#ÿZ‚z”OÂ!ÕÒƒªÜÊ0µCC@¾ñ¼‰B?D"ãï¬-õ”>>‚â#4Òg#)@Ü™ã“öÉ·ùŒÍ¤Ô)°–Ƶ¯Ýü#4ÉX†â28 ÔubWÒ¤ª£× dæPž¶”ª,‚²#<gŸ2 B‡óITÊm$„³CŒZY ËF“bÍ#<Ó4Z”¤É)&Ñ¡Mµ¢Ú‹kÚM¥i•J4jeMµ‹ci6£S[ßÓ|þ_:AÈ9%a™H‚4›rcAÊäHsŒ0¸m6Ûò¬¤##q†0ƒ/âñѽ1Dî ÒTëFØ(„‚` 4@Q³ c,d&Tݯ,Y¥É.×Ûç¹ño2_;ÅMŒ$ƃFlA°)ˆQ@Ä#DE¡•JA*¨B"Zí0_PTj‰ÕýØ`„&}•!jZD¡MÓSwtîÌ\;Sü…v›ç[«çvk^jº•RÛ4–Þ›éæÔÞ‡hn* Kˆ»”e„dùYûÇ?EaéOÇÆ_ëõçÙZ\òÑE*ŠW°¤‚¤ƒCØ·êØÙÜÐ÷5OСäØf(ã¶ñÔãgÒÒ?;C@K.0ˆË%œÈwhÄPÅŽ•`‚0‚ó×X… CTàýÞ~5—u˜ö4“HÆÁŒÙ¢içXB¤ê+ÌtQ@=?Á ³í‡d=P=#)”w¨(Õ0 cU½ë½×nèí-ë.Ê›Jf@ôõÃám\Ôm¬Z-ÖѶ×-lº#<·Guu/•»`lA¿'±Ð1ƒœü!þI"¶/Ouê£Ò[¨†R°¤¯£ú´Ú²b³†uÅ#³”úË„¶BFõ¨û"ÛôÍi§NÍ›¨5µ·¶}û¥×u å h9ÌâÈ¥/¢Áß5š5‘Ä"ºi*lý¼êÚ]¥u}ó%¬öxM³òABß~·;ÚÛÈZŸ<í±Ë%ž*£Ì&ëôê×ÜׯB~:ñëo¹¬ôBßD¬¿Zš×)(IÙ´/ÒÇ߇ã-‰ãÛ'¦qaÚøâ}¦anTïŒÂûÃyøD $ñ— lAÂ$òDPê^:©Päùõž–ågÕÚ‘;ws;Žú åwåõ‡ðÝêÙírñPâ@:¯|t‹ÊkF@Ñ·¦¼,fµ†™¶ÛÚ:ÉV`Š¥›1/ˆ] uƶݙšN˹ß4g«ãYõÕ[ã¹…ÒÐæcÉ?\ŒÏÝçüœÜà–P4’8ŽŽÚDñ§°@m³óû½Çm‹ƒÂ©³J·r½3ÎŽ’Í"®”¬"x¦± 6UíJaA;Jƒjž¯é#4k–ËP„˜ðò©ö\]tÆóÎÁ,ƒ-ç–áZ#)õC;uô÷4ÒÃÒ´öUØ2NC‰××½-“k©IB–T$‡ºè¤¦C˜†© ²”ȦäÉ9@J%ÅÝ?§ûqë'!Hå7ׇ%u<µÂg§Ÿ~æ×uî¹—P-«RÔ|"¹Yþ]ƒœ-öo¾N%UÝ5`B›yÛ¢ƒj¼X°t=7ˆJ›’]J DãVkËJA†1qš^8È5Ý*P…l«×®Á¦)ihJz% ´êV—:d9…&K\ß„Sf¤~u™=ª3K6S<¹ÐEç#<C”Z/ŒÉ¨IàÐb3°–ü0¬.œ¬Á”‘Eh|¦t£"P ™jš¹ã$¶:‹Uµœ5&56±Á¼©he$³²ù÷Û<+;Ë›kiÈMÅsR¸¿¦ûCÀm‹f±“ 虘·â®8kÃÓ[é‘þ»ˆž tý™ty[æâ6¥´®qj#Y«EU*¨M9Yžgrª³o:,æ¼7ÂŒ#4вc: |;ˆ/Â96 jb_Ceƒ±¶æô6lÊù-:™œ3ÇÓ<i6!ƒZ9›•–bìRѤnÞ97Í`ggý\z׆—69…{2ìKí…¯ìZÆÖÐTÛiã‡3Îÿ#4¨õÛyã¯yNQO$“ÑàÌÓGÍq§Ä½%{f¼q‹‘%Ö<`hP²ÖÖ“œQ{îŠÖ3ä-²;ŽŠÌçseSÍ{e9jÜÅj»ÅhÄP¤ô^Ä"n¨í±Ú2˜Ú–ÒÓ—ý9¥éˆñëßÇzèûúŠ°YvæñpbÌs%.ýœÙßjË´.šü#")cÊfGwû?xñïé‹Z¾ªEãJ¢g ûטÝÐÄÀ¼ n³àßãxÉàŸKÂ81Ål«¦uÚŽœ©Û“5Ô½|pÍI5=ù‹“ÖtòÉ#{5é1ê»âëÉñ¶tܧY\óÑèäÅ&;·0¸ð1²!ù;\ÂÏrï·úA¡“n»¦“/v]šòô¿b•#4«¢Ñ‚–A#<˜ƒBÐ~§4rhj×z¤f'–@?8 ÌPA"/ƒ5ç9»”r»BŠXŠ6Æ¥-©¬JF]5&#z^.W™Ž™å‡½`6Θ,hŒ/YÈÚ–hL¸1G}ØëKèMzw—$Ò&½Þf|öªaáÃC˜ÒŶvæ±~ûßBs¤ 3%·R\»Õ2OŽ›í±¬æã뢣p|ç{Ë)˜'Ç“ösÖ/Y˸¤@^§ !Ðã(°Å!¡cÏv)´#40ÛkwFã1œ¹¡¼XðÇrŒy`“(Êð^XF\ñÄš*†„“bäjЃíÚ:(à74¡V…„uÃ^8#Rg„Òp3â†)‡q¼.Êl¾ã´#<Hmm®ÚÌ…^fŽ¸€vCƒã³WÛשA° [Ã#4dBšƒ†î[¨Û+•k•zØ}mäëÇrQ”“Å<Irò`ÜrFbHu ;q`5?[<ûq{ÃÞyiO®b-ùa@ÝU#<3»’[¢Ôn`™¯)®C3´ ©<u%‚Í*%0cq²#)B#)),"¦°½6›áÏZ®A~{k³vxã·;Y¼È!iféåaZ6#žó¼%J+¦i~³E33yâ‰q×vÊ) a²yLp5kÃ~N6Q…fçtæX*½©…&[q2lòžeywÑGïÈrw«¸xëR’q@yK#)·mShÔoî&í·¬!D’Tݹìá&Q¤`ºD¬»O|[_ ààŒA0pŠŸS ¦J…¥ÅΉMÆbzwR@Ò$AE9,8:À.¶·Ñ)}®PI‘¼¢¦—ku°W 5Íy1[mů0zмmr…‡í*(ESiDUMM9qφn']¦@Í 1YÙl\oŠjš‡ƒi¸Úì›3œ”\å½dÖøâ·¡ž·—dç>Ãe5#ËÇ@†$vn«K'•Í5qÓé†Ë¹&éœCR¥/L#4Ñ1-†"x,Í?‰Ã‘#<,ÇÌÎ:ã}/§…6#£àvئ«3ÅZ}¡tËh¦ïxwu®¤xt¸Ãìa?‡.pn1YVAHÇ_JR›+ç|zã¿ðüI“c©Ió¿ŠÉŸ‚7ZUu¨’ds|>òí‹sN€ƒŽ+b¬FB•}]°E=]”¹Ó-5ëAwëˆUÙ¹gi;zu=38å#4–+ž6§ßÀžiòçjzñpîššËÁ×0vÂht.뮲Þô¦§ÜÛDà]zÍÓSqæsåáƒÃ¡Ñs×SVqٗٻ0›¿¹ç/¿ƒ‰~™¨Ú¦ôw6æ¼¥Òâ²Å5é];Ï™~/¨4wBߢު|œ4AÒâñ’MvãÝƆ8pÈ|Ñá©_8Ùóó_–éîçßäoø}¯"ð˜yù?XÂUYî2%Mš>_YëÉ¡‘)¨!tÒÕ€i‰(~ƒ:Òt ñ‚¨†–òÌY°½Säy~|ú†ÃÂÌ,:¸ÊÊ°6wÖU1mrD@f¡¡5…ƒ«`^†å™#4î›ÒŒ·§˜“xt4|äý‡|#)Éx×€ºOaãgJ»À[Wv-Œ3–7Õ¸Õ²üñ-™! U‡ž‡ hGŽaB«ÜC=åË#)Â\³båÏYwqXÌ7·{&›åsV¨Š,rblm¸:ôç$Ç”,,¸Œg ¢=hªz{»–ʤj®¡PÔÛŠBÚvæ5…änT÷Z%9èù÷Ž qCLAR6Ÿ‘…¢ ÁfrœQ ¯X)â#1dœª=‘‚+¡ºI¥ÞaÀé€Î‹NA#)`Ä-s#)ose =ßA~Å]±á¨ÔïÌ0Ù†n‚·5žtò¿‰N¾ÀCw¢³0ñdRâŸÜD ð±aßÉÛdØÌyååÀK{~ǬŽDêÁžOÁx#4Þ#4þÑÑÅmýéRª¡§ÓùN¼$Ñ"^Ðïö±{`qC.“ÐmNÛUªI*këßÓ™Xš¹ô«èZ<àrãéZsTóHt§sípqÚ|ŠÚ£²*“J„lÒÁ±‘ J1~3uÞkuƒ´©ìÉ¡é·tœC¨rH&5¡Æ©²Ø”CÐbj„$"Œ"›CISçW{|»ç[›|ksFÑ©)6‹skÍé;®I“iš¢óuóÊSv%Jˆ\¼@@#<"ÒaKAEFñRDV ‹QG‚²,PíL±µ‘@éX,m5€Ôà° ªQFB¥´ÍìÌ<Îg¬æþóš~wnøa#4RòL#)nmÁ¿¯&0-—m%ðN²AQŒé6_Ë8·Åo+yW#’Æ´Úµ¯à¶‹U[W6µª¯—Šm›ÖŸÔÜk?µ#4&©coƒñàÌϺ怜Ë-N‚•_ßëcÆÐÒÅ#<ÝD2†º}p£Ä ÓmÛ\ãÜ#4hÆèÍ?Ö:›1h‘]…2¸™øÍO‹7“lTë[q†¬Ä3Ë+«†#<RñÞ×ÐK£!#<HRUÐe!Ã)TE|¸ÓâopÇjê›Æ³‘, ,ÊŤÍÐÔq͉¨Àm6–5$RÅ.ê+M1¢@%rÑQîJ¬ä2mÇvî™Hì’B¬—Û-÷p´Úa#<Ͷ‹Í²Ù0¨)d¹2m špb#<eŽñ.S5—¬2&àiØÆÝ{S¢#<C¦3ŒÍ…Kܺ‰ÍMD‰“ÜU=6µS Ü!µ—IÚÃZº¼âÙη–Vñ“m¤†6X*#<;FÍÈ‹¦”SJ†íM§£4FL16Ô³‚m°É3©kôßÔëœÛµ¦Â²œ§x•¹ Ž6 cÝZ‰Q»%q1£ìx«¬bÕÈ kU¡¢–ÖÚdP‰`M,Å7—2¾ªÇ&GJÖfõ±ã„Ë©DÆê›°Ý!¶:ÎøsiH“°N°Ó†Ž;j8õÝñÃ#)Ê’j”Ž6Q½Œ)28F0›áÆ"ŒœA•'#4[ªˆkHÐÓ9:C%i¹Œ!Ѹe)¥#<°È4n#<hpÒM615Z6óqmäÉc®»zça›[ ¹tøÐC :Ô6EJŠ×gƒCW Wki5a”j¬æ˶›¹Œc1 ìs§?Øyo·n!1Üa:åñÕaB#i¢:š§ù2²Ä7$~’^U*|ÜtëS#<˜¬Ócgi‡LÇvO#7Ûö}sj´2I Èø³qÉ æ´Ü…¦Ô°Œ!‚ÇZ1šKZ&Æc\˜O&g6Õ3s0táÃPQ(XSÚâZ¥EÍ0 Ùé¶Ûu¤AÌ"Q[Yý¹J<AZŠóaÃ*Qðˆb,`cJ½È©„QÖÝtÁE×xiõ`nÀ|’WŽZhxÒÉÍDáé·‹fR˜q²@iX3;¦Þ˜2âO³°!ƒ,„c±¦xw }Éf·ºjÔàÜ#42j“#)Š…™83V#<æebkwZ!Û ÃÓ[±vJ†#kS(i¥§ŒÄ˜ÈH7γÖYÀ^D(@`“dYr •&º½Í²Ù·ó`yÏPiï>Iƒðj¥20DˆÁJ;K.ê&ÚñÉžŸ^¿é¯uJˆ1Ë&éûí_Ûe&þ7‘Úë:ÄJFU#á&ßÁ(æ‡kÔ#)óì}wLH~f44[’™ßýTwlfƒ`Û ·7ž£`oËóWòþÛ±Gó¶çç¯ÂäýïúîooTÝQMí³“”“gC©1í»$žã´+Ü)×>@*§ÙÜVêž’@ÙY^– H%Èéëéú¼à驇 5ù¸'šÆKdáÝßåU‡ÉÝ‚‘‰!RðÉ·Cl«—Ø. àöz_ð•Ç«CUDTÍË-¡Åå0ϯy½Ï]¸Þ1´5 Ç$Œi·9;·fÍÝϯƾ·îdXî_¢ºmxñcè”z@âv„pi8›mFrFö”Tª3HRCíº§ØtðëÄÃ*:‡¦ÙåK! Ô{B.O#)T0Š/$j:òÕͽ•yY+®ºÝ*ö©”Ûo6ɪ.ºêåb©Ýª‚Ò@〘´!*@€‘#<ø‹«#):%#4dž"<LÒ‘ýúçw][yø‰4F5RM°¤¶¶Ò’1%D¨šJ©–ØMcl›ÛX¤¢¨’²FÍ•Å#-#<Y²M)6JQL¢*#CZ#<‰LKFh¥4RšE*”a¬“#4°ýk˜DÒFATª(’DDþC‡Qç+Í0A˜Ã‘ÐÚ†"û"qíç;YËiø¦Ñ}šîf·pó8º?(q$„ƒ7÷ˆp ¼¯_}´Ó Äåõä¨>÷–jGÙŠ€*µ>ƒõª8e’à«,Fœ`ÃÒÍfÛò#)ä9F×µ› ´Ò´â@-Bj{(D™®'ש÷Þ›nÛý©wäwŠ$ °U„ÓfÕð1#4æÒ»NŠàæÜ‚#ŒaK1<üeä’Bèœ-n£ÓèòÈ}Úº‡kÏÔÄ1TÊ©J©Ð¦¿Vl[zm“,dò+2ô1zºçk ’ÿpð„òd€#<&Ûb©DmIkEµk•—öw[•u#<MFŒm±¬g麮c+Óku©|ºµª¨O”,µddæd2CÕ†L°¤PaT¡’ÉgßíÐ[4±E”žL*Ë®7õ™±´5±gÔÑXÌDy#<–Ô#4³‡òË£U¶4¡Ë‹§CªŠ82V”Ô¥VÆÊ<DP6`äƒ`ÆØÂØôÈVjl"…‚#) e’¤ hâ8ªai0çÉ–Å‹C4KÄ™ÙÊú÷k×;F2•Ñšú•êKÍË$Úë·j鎦“çËöàrÕ7ät\co(ûÁ§œEi¤¤Ek”AÈȬHÙÔuQ¯2™dô<ô’¡ê—™D¨†Øú%â¡»T™m¢8D6^f$BƒæÊm~#4nÂ1+–£c}#ƒFuçDeá©ÁûfÄg›ªÞj’UʨØÓSyYRw|w©0ÈEÖïu‚#„Ob“bÀ‡Q2JAB]U4BêU&Bà&-@Ò†‚Ò˜¶ aP¶-¬<Æ>vII$‰Êà Š‡«÷I‚œÔ<Ó#))\2n‰i¢Â@#)¯Oke=âÃfø¤+#)û>ø»»ˆó”gò&–¤ó4LGÃO‚1L~ÜãòȦbîÒ3ÊkßÞ%™ÜMòà¶ì¤ôör~'0G`:¨‘ZªjÒåÂëF¨‘O¤’RÒ\2†yPBîDÛÚëïD÷¨‹¿v²¬šŒˆ¬] ßU¯FXýÚƒíb…4z]ã1Yøøyc*Š7ßš™*¨©!žX…t:¥¨*o}Aa4ÇmE‚60H·# ¼N6Ú$OPØy|“÷ /]#4.§Øt[øèýWF‰ ¾†¬Únµ1Öíq±‘·7BhcÚdʆÁ¢‚‚š$„¦™(.ÅGÝ-Ëšé[ÑåiÈ1ÕhEõzO]ƒTM¦£*ꇉ»eß÷Kê}Y’‡hp±êN‰®k«¼ç¾ª$Š¬½XåÆŠ–àøŒÛBH-)Q ©bùõ¹ðØOn®ù3>¯_ÒMË°#4‚š²ê9;½Jbb½¡(Æ“ÞuRÚnYG¼áÚøÌا¬ñ5憦âÅKK&^eAðkú°[#4"[@¨ï÷ü¼@“_ºibN¬yâ4p€Â-_»é1#4݃²©5™Ÿw?Êû4aìï2O¿îA„T ˆˆ‚¬L|ãнþQgy3/µûþð`TCBÀó<®P˜.ëÙóüÜhk ÷çle$Øû3ƒñûãf¸îuÍ!UÓEˆÀ†H–cku`ÎOXÚv§F³Þ“¸Ö ‘Ô½7î`\Êx2rïÜ#)Alj)ÍØÀøÈoæq{Ó ¹˜•ü¼Z\úëu4î÷´L¾]EçŠ+ù¬*ôñ‡ƒc¯ÉÓ!`ÙÖ¥G9)Ûd³Áí#4½³Äˆ¾"Êòôü#<¨Ð‰JwcL]ÖÒ4¨¦g…§ÖuUÂÚÍ•UÚ„!l4ÁAh#)†È!HŠC]Æ÷¦¥¡#)‰M}lÚ+rKþ(QQhdI†ù)£Áw&³FúÀÔÌ`Æã €…2¡¦Ó<I‘E¤€i.âwp+X=0銰l|(C<ª/x…¡®6oœÐÒs¨NfÚæ fŒìíž7Ô€ô³r#4£ÂÆcdJq°@j2m%ßÓΞ‡uzÌ'|#=s±LaZÇR}IǧŒ0ŒðÄtÃäÊãeYŠÂH™ÓK Pfw€\ bA)‰š¡o±JÐÈE¡W”‰õÅàÚÙ6g#)®@݆1ÂFZyå{µ…Š˜D*‘Tòzaík÷Õ0ñ &6+ÑçLö×~N(]v©VÓ#4Bls/, =C¨²™©˜W¡Æ÷3¶°Ò@Ž;S&õ{zÞÎÌﺆmÇL0Üc“µúÕô™l)[ÅÁ„[!20ɨ ©Ô3ÒEDÊä˜x]–vÅãðñÿ ^Ë{æ%µµLO\q2™œè9ÕÇFÙaÁ&\knÊLp²f¼û†ü’1Ë)¼@.Õ"T³[•”CX÷É<#4½ùp"±Ž Ñ#4Ê#<´††’õØk5bÂá„Ñ P1BV X°Ir #<ÕJ8AÅÜd;ÝÑ”#<ô³2•±žû¬3RÈcÊF][¦ ‹W$««š;b"“ ñ2”¢Èvu*9i#¾µQÀšG¡RÆFûf#œêÙSRÛ´XãE©Ãð"œuìô°eA«Ã-s»å‡™D‰Œ ã Úm”m2€t 5’æÈ8-§2a;HøP°Rô‰-§I†Ò¥†VÎèz¤´·¹Ó±QšLÄš“#)A»m¶ÒÁL\•#Ð×:y1–”–_;´²}NQ[`ØÉ/¨ÈœÒ䘆n#4±‡#4å´†„Òžøq Èö«¦Ä6È&ôð-Ûm¬†!›áC³±vy¨óŠl‰‘dÙ.žõeÖψ[m$KYq(tS¼¬I.›ûµ$[!uvÇyˆÎÏpBsö«ç´H©`‡ç‡Î14ª#wŒˆyˆ†I-•â>£pz8°çÇWfÑÉdíp»–͈’_ ùdÒ-ŸlÉpteûúQà°±zâZ…ªê¦P󉮽œ5Ûx'×Xªñs&âiÒtîJ¥kŽµ#<µ‘eP#(k—„+r¹Ä5k¶øà×L5¹í7«H\•*8Ò8r"Ÿá—¥ä[ò-láy’³LÛMFUÁE>ÀKKK#<KGEÝܸðŠëœ¢÷è"2pk“*Ð"ª}*eÇÄÂfÁÐ7§mÌtàÏÀaq³¦•..g|̶ÇXw0dÖ:¸mÍ“táòÁ@ᾄA¹%qÄHFÌü¥Ü"8×ÚWn•4i²£¾ËXÂÚ#4ŠpI±H&Y@£8ÈÓM ©#)Óilb,(B£#)F£áóÑ2Ø8`ƒòbÄVÚ\ƒœI “—+ࣣYVHk¯¸lÄofMha’Œ/žøPç)Õc$:råî›väšsgr0šZÈç¬Ñ]lï(vi©¡µSlk—L“k‰m‚ÆøÓéC¤;ìŽSJ:R™7NIƒîëû>xMëY½ÐS7Q#4œ?–jÚOÏÏG=«}Õ‡ÔId0$ÓbS?›žl¼:Ç’ÃÇ]<ˆ)±¨!ÜrŸùÀÇ¥¥Õ#ãqrÌȹêaŠoS³åÈF·$ÕMü†pòL'ìø}4´žžÁ“´xhÞL&GD$ÂKXrŠ„ÅÃM¶v+ÓJ.®jQë\¼…,PÐö.&ÇåƯÏÑ l:j'F…ZE6ƒ}QcÈltÑŠ°K¾cÂ./“™#¦ðÓ4&Þ>9ÎT¢L†äÞEézMºÃg#4ðõ¿d:ÓõÂvhœ›ó͆™gæh˜8œ )2)åCB£Bh;xñ¾Ž=F¯gM 3Ð9i‰·dTÓCYC–ôÆòØÔ¾2™!ÃìùãQ¬ÜÌÕ„Rï;¦m¦8q¢â—&GND®ˆž^na¹€Œêš@„Ôí״㶯¨&Á´Ô¶¥‡O]nN2AtŸ¤Sw·{Û¶›òÔ,1ÄsÖPÍ°ZíÐ@±Z4½ªîüµÅ7ݳ½¾0o9WÖÂg•‹¢Q1ÕJVæãºät±±0l(]ñriî%É«³jhÄiƒS¸ÖšH©‰™ùÉZ|;`A¶¢…Hy!—«lEáA©f¤\Õ(c.â@dŸ>ZL_0àëÅ5£HÀœ!F¤01-·@C#<i¾„Öä Yˆâ\ÐnRØŠ4!F#V‰3•P¢p0\`·LA¡ Ž!ŒC>ñ’¤Òp5PykaHŲ”R¢J²Ðj…jí#4 F#4VJ‡pÑ€ŽÌ±U€LtÉXP¸âÛ35¯ªÓ6”ZíÛ‹ŒÊ5-áÖ/r#<iknˆgt’â#4`ŽQCÂ#4“ÃG`|ŒØ¸#hå&TîlÖ$M7² 0gA‰Pß%Xá jÂБ•rÛg&l9AÜ 0HÃØ#<š6v4#<0eÒ#¸CFD#+#)¡rMÚ‚ #4ÌkbȘ’MxÂœóÇoŽƒ`r8à#4x6¢éˆ;w‰”£tbDU l#åͪŒ#<°ˆ[bf4ˆ¨k¤m¶6“µi‰T0§èƒ»åç{ üG>Ç#<ˆ¨2Š*"(kÕ«úøm$8IÏMñðò“Ô7Ç/w%Š ÂШšÕ! ¢È#)‚Z#) ̈vþ³Í®Ø("òäOò…‡Üþw6|àŠª‰õù¾²öíb#4ü_R˜k¤¶—âvÆúÈR¨äˆguû жi)e«dED2÷#4«¼ ÌøæfyÓE–›áÀÕ4˜ê<‚¦ŒÇú,#< ƒ`^:1Î5赬oKŽbMp†¹ç¼Œ› \¨C)ÄÓæ–kåÛÑUtËJ¹*©õZˆ¿ž ¶2Ræ1B½”ªÈ‹Ä›ªC‰4õÖ z¦J#<)g) ¾“pp§^üìc|ù÷³z;³ƒw)S÷UJ>•Q&vñ@Óq#HªÕO¾Ë$ÓÐgfJyU²ë"i“'ŽN÷l~G5i£Ï§{èþW7=Àˆœãh©_¬. ²¥Å/³6‹tò7¸Ãnhûž÷=,ïf³–˜Ù"É…Ž‹-άa¨A}jz«÷ÇÒ)ÛF;÷¿æ§>2îqäU³-m6DíÝÈ϶ì÷]#4Ù9Dæ|5bÌ"†þ©µ}íø'vØ¿RöñÖRvuÁÈœaÛày(Óñ8Ófædk·W#)©ÚJуIFÓbBˆÁØã$Jðd™ ’S5ä´ï7&½»´f¤W{·Ïp•ñÓåÒ¹q&8å ÔrK0Ä$(,ƒM4¬´[Õ5ò{æë™%.ͶÝoÀ#4¶¡ #)ï†}qgMÞ¨^•¥8Ö\†¸ï„òàifní5aÅÓLŒ‘Ô«28Bˆ0dø”Ž#c|ôvvÎçÛßæ|a‡‰á«M¢…#ô@zAS’sœS¾d|ÆøQPª¦õ™c'Õµ¶ŠªŒ@ˆ4:HñÜÂÛ‚#4‰]’&ÉL+Ãè·ÎÔGRÍu‘3[8 A¸ =<Âk‘Ç @ˆ;˜Ž´x2Ó½íŽÒ)ác·ã#4ù{’d#=›c9ùÅn®ïº¥Ç·Ò“Ñ™ÑÒEý™7ùŽÆ§Àk6M#<ë#<@Ôá6¸˜KvXaTN+ÎMN8ÐÕB04ïîÂÀ<8мRÐ;ùu#NO#4"¦‚3OLì™o±5?TÌ›uŒ›¹a¸x…ÇÍ#)ñB60›h@EAÙe|-ëlë£E5ua«Åw¤„ ¹à.Hl9LÜ_¶ó–¨²å¤Af1ùøù1†Ð^HÆ?Žß_•›ÍÅb©¬DõÐR?3YÇéש¥3¡FM.á¡À“ÔâÊDìBQ#_½Æµý‹mÕL¶‰(Ö6Ͷ´¦2mdÖ™_Ç5’Úú)#HB™ 4Æ9D¹´¨©{U¨²"Ù Ò#ƒDÍUꈎåžÉŽ˜ÑBÒ¤,8ÿ›#)#)=Š‚fköi\½#4âXwmƒ™Å„R‘@X‰!ª÷ñðãñ|‚Ÿbš”-Jﻆ@¨X0dTdL>0ìòZîW§}%;ñQ„kÈú…¶°ÐÁH¡å#4¡CÖ;×qÖ•ÀêůuBN•s\Ã815p‰5 ~‰ 4a£eŤÑÂ)#4…j\¤j´D#)Q‘²¸› áQ"d²:šbt-•cUe¦\¨A3Li‰„µ¹{v»Q¶{qØRm±Ÿväv½Þ½6â€<Ž]oˆ(o*6ÀUÉUॅ 1,Àr3µ§Ü =ÁÔð=<)a 0«Þ^ßu3'#Ao!ÓÊŒ;QzËd’¿,R†*öfô@á–&îÀæöFÉÜ;]¤TnBX_2I%›uã¨tôÔ%ß2Á¢ÏŠ¢Íô0„àZYBeJÔ‚„&QÄ×»´&#<#½¨¢#4þ"nïiá6X€.#)[ÝP4³&t—ðþïM«I,x{>´ál|tè7ÃbÀLtKž3³E]s¸(µ$”#<¡XÖÅF¢*Wˆ®ÛÑ#)±Ä¡ˆ€R@\ëc“€öòƶ¬¼s³‰ç)¶Läk×æðhô?‡Þ‡Õd$Qy¾BÃH,0Š"&’$˳9sÛØ)O_1ï=80$±„7È\ú9ú“ày…<ëÞóÝ,ß5Vßoçù|#45›mͤԔªYTɱ)C6µ±[0ÖB4l¬Æ«IW÷4[WÍtÆfÍ®þKóïã7Á6Ï2zc…Š®x™¯ËZ6ê!Àëé7yæ§$3@;ÎgÄj6ãÏ«1êv5€ç³ŽÜUd`A$AC>êsgÌŽ)õæ)oú{6‰RN\Cà‰Ú'¬÷ÝA0*§0#<Tv1~iÒHs>ì|zvLÓ;|=ˆû?Ù'{ÀTüjdpËÁFKÔÔ´)¾c_gŠõÔä5„‹Dí:)éq„$láÓ¹0ïÂÐ57¥n(6äÈՒ˹,: 6Pjàe(liÚtá‰g|6ÆÈÅÕX˜s$š¹Œ¦s«yàó³—.ä“vN#)˜t´áI¡%-[â¬qnD`Ô5åb,ËÈKVZŒ±+…í+mäÆÛS«'àmC‚À€ ³,̘(˜h…`°‡43Й&ˆ@Ám‰à~ŽßDTÞw d`"ú‚î¦L•Ø0¢í(Åå(Ö‘¿Öh`}/F´·XaÉÂ_ÀXþdÎ…a.ªnÊBÍÀ³#)¸äEÈuöú¥,–{Šæ³»š0¥W¾ÝÿEÁGõ1"(Æ$`„&Z‰ì !áÈÅ:ƒ¸“§N˜á‰Ñ‰‚exQ mCÈ"HÀ‚À‡PÄQÈŠ‰µßwKÃÈÔŸ9H#)W›‘¿¼ä%áhÔ#<Àî&Fª'‰ FqY¢™D¯W·ÌûÝD¥1Èe è:¦$·¿c’•íÒ6/,ÖlŒÕPª½I!’0€d;Òïêçò“Ÿs¦¬ØÑ»lvs#ÎLÈkÑqË ýçàRo÷íúx{vÕÃbËËw§éÉ¥æ'¼xs·ußØw˜ª‘œüû´¸Z§1˜#)Ž@0#<m%›5ï?¾†ºŒr^ï0\é÷’-ƒ€Ò§Ê#6Ñk#)#4C²(«½G¥6²Â#)ÄëÛ¢À,ld $Hd£3=×Q&ÄhÒnuÝrF&E‘¦~n½„Vò×7ù2ç½{o|î-§ )(ÚRÊ0ÒF(‰znÚ,èÙ6- ZM¤C&\Ý$–l™®–™óƒB¨ö"šÄ‚!„¡°¶ÍOißá( ¢«âWÃÙcÄÀ½™êòJÈóüïœ#<z#<ºƒŸHÈH0‚ˆ2) È‚#4Á{Í£ãÖ6¯»õ‰SbB£R‘±´i#<Y’©·óv¿‡ó"£~Wæ¿»#4"(щ±°ªfØÚ#< ê€|}<1˜1C`Æ'Ѐ #)Z"iöBR×#4ÓÖ$LŒ@‹È†*y˜-Å2‚<*HÅÄœ`•}Ò¾i»®lƛȕ±E±³#Æ[M(¶h1>7ž[i6Ô‹5¥-[öþhKÀB.ñ#<øàã~Š©¬ÿñôÑ×ÔŸì.éÜI<@<‚ ¦Á£½}'Ÿé®¦VEMW!aWÙ®’b|áX7‡b!á"F=¡«#)Þ×™\…ó˜õæ%Eñ=H>|ÐÁ÷SUãoÏ{Ø,AR ¬"ð•ÚOãµ#<"(ó+(OD˜ò#4ì…Å„A€Ü?b!Ç¿‡gÏ!l–÷#4oZ¦°H£¬‰Š Ñ=ËP@P^h‰¥„E¶ ,Ý*@ˆ„#\ÝÕÖ’Ê-Lêl¬Zl‘´PdIš7Ï_”ßlßœ‚†°ŠÐ¬Æ„Ê>4ˆ„ˆÃ…¢2ËÛ¡Vœýt!î•#<ñ×"hOåÃ^Ö?q†°Êôæ–ÜLf‡`=gvq-)AÏ/7©úvÏñî#¥ûXÑ3(”:ex¨áª-ò2@þ\ùÞôÝÖ^¼ccßIsb>²`Ëï#4„Àa¿™¿0})ýê Gœ‡“¤<ªF˜@’MRÍ4©!)lb¢ƒQT›T)h×ó¶ßÐÕæV©4ÍÄ©„Fxw=}¶vË4#4QdD‹#<Ã#<"]‹¹È‘+O¸0ra#/*)bbÂ)¢fkØQ YA(#4Ú#)yß õÇ-˳]èvÜu"‘þ#)BÆÚÕí´V£ÐV”5E„«6X¬mµM¬Øø’3†:zu7úÙݽ´ªË%$äkíÇó4Ÿb"€k54þÙYcDcxã1¸ªbÕX¦QÂ.<ðªÈ?((4¾¸‘ø›ÇõdU¤V”¶þ—‡šY»fX†ÒŽ#§DÆ&ynÖ6`äŠhªÿ¯da±¹Uùt¯/X¯u®›Î·à·Y¤ÔÑJ–ù›#<í§vmwvWšöõkªmKo\•öî³MÙ™U×7mE\íC-“,µzîÆšdÚîêîíl›*J™›Þîí5î®Þë»UénJE„†" Û0(ØÕ’xEñ¾zâ*¥4¶MI•*øv·ºº÷»V¼Ö6ÊL̀ȂP° —ª¨4•M‹#< ƈŠ=°ØðgdÀ2@£DhÜ@U¡FéÁþ‘ÏŒ“PocpX²‡Ð¾d-‘QŠ ™’1À-±#)ÀD-Ãü{Úó7pÜ#kö±mŒAV/†LÓmuÈâ@œáË}ZéÚìt!Ò=W3.F=ª»?¡Çù.#)wà\Ä~‹âTõüxÃëÌ=ž4.(XÙÆ0$6ÌÞóXn*SnêccѸÜK‰‘o/#íý…¦F$Ç©CUR§WÊtÎÒiMfši€Ê1…¾Ï#3\…;>Ý8‚H¢Ž+”R1‰x\ëÔ˜ãÂS£\È~Ì5‘EÖÀ’$fL°dZ©.† Fîé1=>[Ì]Yw~%‡øe$¿ÀŽ#)D¹_©Õô:}ÒV]×J“Ó[Ø7æš°íã¸6Ä#<ÑŽhÞDš}ÿ@q3ÚM^-òOs x…¡l…!’c2øU>3Ön£™qÝ8Ýd²Ù#<™û“؆.Ò†Ošò¯É_ª´’`oÒŠÃÆœƒÍÔÝ”‰ê€ýÇÑ(œY`j¦årÍ”'S-‚„&[Hpíª‹Ü4Lbu‰3u0ˆ@ëÙ£0(~Äâ¶0íòíM{ :UŠ^´Ê“h«ËqJSJ1(@¢ë!Úºÿì7ø#4uÓü!V»Lhèä‘تÖ6Îpë —w8Úÿ£äædW”2¦KD”èj2äfæ@¡]ΉÊhËþú¢\’äƒÙçrÈ£ºËdËU›xMv×e0ƒš¤hr~ô”ÄDVgº NÙìÛú%²AùºØXÄvŠ#4/íƒk¿¢ª¼€fÔDdZ=JÛ+xƒ€ÜUvò§ŽúТcpkcœÀÁ„åGÙ“¹)ŸžN‹¥Ç:D#DF»gŽáØ2ŽüDÇ"†zY”Ê-3\NbBC¡âÖ»ñ“pù™²À’˜ccñ4mÙÁm¡ø€…c;Òq°HÈ÷‹ì#)Üt8/B ˆE0=°³‘Úy{s1 nr1ÊÖÜVÃÙ#4ˆÎÞ«~Nò߶ڰô´¤ûlí<b”¹¨‰>Ü „Ð?#4_Êx@ÄA6MÛðˆ,¤Aì#)kãÀ|Ïâ,ž™/ˆc*(÷žô|êýìÀÇáwÛ}ÀY‡]ùó4#)˜¨M_®Ê¢ÏòÿêÿçÿÇÿýº¿ðÿ»ÿ/þoæÿøøÿ§þm–]ÿóéÿÛÿ/ùÛþŸÇ»ô}^ߟù|ÿÿŸ†ŸçðGþŸúCÿß“ÿåãþòÿûÿïÿ?þßîÿŸý~.Sÿ_FËáÿútÿ×þÜþ#ÙÊßöÿ>ç}gÕ?@#êQû!öÏõÿ°üˆ˜ˆø„pLS"®Ãû¤U!Ä*Ò%GTÀñ¬Â%jĸŸæi Oõ¤‚¨¨CCtº!ŒþGûþz§wQ$&f~moËTÛ_ÞUHˆå²ök¼ »´Ú!®=HVÖBcûìïá{+€°"uú·nؘŽ{#<š¡ü·7€m·¯à¦/t_õ£&ˆAãx<¹˜í®Þ÷¸ÿÜÝ„‚ ‚ÿ»9aÎF3à#4Ëï´Æ³ß™NÐ:ïÔCäSOóp‰Hþ#`«¨ûäR®ÐŽúÛ¦q¾77]ÒœcÞ'¡¾|øF]•ÊÒGMå)AE‚ãL¢¢@ÂB">#4}eýP'ƒCŽ oúš‚EÏkÿ¥ð°Œ'OŒ¨1ÌtªÈÏ"¶fÇâí,{RÆUTÞ”Ï=.&QFiuª|oj·5¢¡çè•Ãp£“úphÙÞ±aåv.š¸uíÌÀÇÀ4íž#4 5*““-Ó††—ƒ,±-–Æ]Q,¡òièå›ÑQGjÕ6uiÑsYe€š4Í®TLÔ±´[Ôƃx…ºM2ÁE¡ïÉìĘY.ä‹0eÕ»&BIŽÈʦâØkÝŠ±¿xK¦w’/>ŠU¶qß1²<d1ðÍåïk%£·Pø! Óë®c(ôLØMž§#)Çwv(WJbÉ‘ˆ—$HÉœZåçl^FÈÌ1—`Ðçí¾H6âf~9k‰eYúµcÈ ß]›Åt³:FÆp¶—pð#¹qKó˜¾Xͼ޳ýjÀNßUA”<ñw‘¤1'‘ÇwÔÂÇnfªb»ÒY0NŒX¤GÆ$ðDL=>XK¸QÆp,†#<¹^>]ŸÇÉËö|6á†5â`ª¢Ò@òºñýþ~>íwë%w\Å_>{Œì3DEATLPÅJ…óÙË}Å2횀ˆ9ÂFA4Øí—#4nJgÿHÒ–#<€ø H!.wwz<ǯ°L]Öli$ 0‰#45¤:<‚†ü²Zбk¨†p‘3ÅÚ@Û™‰bfpÅÿùžÙY 'ÎÆÛnLw–ç„™€ˆÄX0DåËe…ÍYšËwôèÁ¦æ’ÛiH‰Š›‰¨¦†ÔcmK&m´[%c$i!A˜*caJª¹eB8€žüäÿlù‘J·ÌÝ$Àÿ]€iãTÝoH("‚–Å]ç‰@a.Ô ¬@O`ÓX|Ôï>žFT7·³’†oÕOÆ?8oÕ·}ÉucäÿÁ83“âm_/*Õõñ*©"(‹ñLYž½”Ÿ÷dåòÉZ.‰ž”L!Ù¶FÎKºƒ}¦•û§ú$2‰ñþÿhxqÖÛ”ñT¸±#4¯±úP=’ZóòóÀNÐxùGm: ÙÒÕÆ0Ðò‰AŸŽÚŸ!ž#4 Œ9Ñðê—r_³/m:¹¨9bÄÑëù<MÐf£ˆ¬ÂýÔZ5?…6ï#<lú³tî3;†Ç¦~Öj»³×¡Èå*žl °Rj›~Vì±å¤#<ߥ®jJÇäjºj-F7–ÛãF¯¥«|š6Ô›ƒZ¾ÙrÕ}+×uUuîÓAL¶•ÆT¥gJÓ!Ñ®±Fe¡5EaMŒ¾ZS£pkþÒ€¯¢') 8¬ÉÖ@Úw¹µbçˆà]ìÁv¤Â&0M‹ñéÐ\IPé®ð¼Ìr¿Y|Ö,~T´p#<Nïûè¿tEÔ#4$‹ 3rnÚŠÿÙ;ü>ÉGMOEw(j}¡È_(]OàÀ(·:Ÿ)©ýÑUŒI#) #)H@„,5ºÑÄM‡v‚ö;msfµd¶Ó3\-»j»-_ƒb›5&¾¯ªÖ¿¦EFBPqG¼öv}#0óY÷O˜š¢‚>={ýéïEõû>ß“¦Æeò²Óø=“ˆ aØã&„‘OTOŒ±Aðû¿~OûÅD?.ÉÛÿßÉÓJkik¾¨*H„‹‡ÿ>R ¯ûÄ©¬˜{ÿ€U#þxÿñ€$OX!ˆ —ggÿ©ÙËÿÑÿ#)¹òäË á°Yâ ÿÏÊÿ'þŸím~•ú<þÿØiæÐìŽ\Ä<éwtzñã‡mø}n )ì=%…ÞÎÿOÀñïkÿ#4‡×æ:ÿï1ð¤ý1ûˆ—Ž1H@üŸò¥Ýï—ôÄ3‹!¤Lãù±aðìÍËÿ=g52Œsé{KÑXá=~_þi¯§õTÏê»_áæ%ôi¡ý‚poøT£ÿ³Ò»xœULà›éªáq–s¦ò8‡Ç×iÓ¶ˆÐeecú9ÈÓxFà±7–ÂQ¿Ñ8¨–½-§0aw<?ÿ^ŒÁÔÆ™&HG;¸ñ÷.ðþ@‘fĸ@ûü~>¤—þWïr7èiÀæ>Xž|}Ÿ÷VÖTÝG\[Õ&g¼ÃÅ´„” N@`š«^Šüßðµ=ƒéùĽßKÐDÿÅÜ‘N$/þÒ#) +#<== diff --git a/.gitignore b/waflib/.gitignore index 8d35cb3..8d35cb3 100644 --- a/.gitignore +++ b/waflib/.gitignore diff --git a/Build.py b/waflib/Build.py index 1afcba6..1afcba6 100644 --- a/Build.py +++ b/waflib/Build.py diff --git a/waflib/COPYING b/waflib/COPYING new file mode 100644 index 0000000..a4147d2 --- /dev/null +++ b/waflib/COPYING @@ -0,0 +1,25 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/ConfigSet.py b/waflib/ConfigSet.py index b300bb5..b300bb5 100644 --- a/ConfigSet.py +++ b/waflib/ConfigSet.py diff --git a/Configure.py b/waflib/Configure.py index d0a4793..d0a4793 100644 --- a/Configure.py +++ b/waflib/Configure.py diff --git a/Context.py b/waflib/Context.py index bb47c92..bb47c92 100644 --- a/Context.py +++ b/waflib/Context.py diff --git a/Errors.py b/waflib/Errors.py index bf75c1b..bf75c1b 100644 --- a/Errors.py +++ b/waflib/Errors.py diff --git a/Options.py b/waflib/Options.py index ad802d4..ad802d4 100644 --- a/Options.py +++ b/waflib/Options.py diff --git a/README.md b/waflib/README.md index c5361b9..c5361b9 100644 --- a/README.md +++ b/waflib/README.md diff --git a/Runner.py b/waflib/Runner.py index 261084d..261084d 100644 --- a/Runner.py +++ b/waflib/Runner.py diff --git a/Scripting.py b/waflib/Scripting.py index 749d4f2..749d4f2 100644 --- a/Scripting.py +++ b/waflib/Scripting.py diff --git a/TaskGen.py b/waflib/TaskGen.py index a74e643..a74e643 100644 --- a/TaskGen.py +++ b/waflib/TaskGen.py diff --git a/Tools/__init__.py b/waflib/Tools/__init__.py index 079df35..079df35 100644 --- a/Tools/__init__.py +++ b/waflib/Tools/__init__.py diff --git a/Tools/ar.py b/waflib/Tools/ar.py index b39b645..b39b645 100644 --- a/Tools/ar.py +++ b/waflib/Tools/ar.py diff --git a/Tools/asm.py b/waflib/Tools/asm.py index b6f26fb..b6f26fb 100644 --- a/Tools/asm.py +++ b/waflib/Tools/asm.py diff --git a/Tools/bison.py b/waflib/Tools/bison.py index eef56dc..eef56dc 100644 --- a/Tools/bison.py +++ b/waflib/Tools/bison.py diff --git a/Tools/c.py b/waflib/Tools/c.py index effd6b6..effd6b6 100644 --- a/Tools/c.py +++ b/waflib/Tools/c.py diff --git a/Tools/c_aliases.py b/waflib/Tools/c_aliases.py index c9d5369..c9d5369 100644 --- a/Tools/c_aliases.py +++ b/waflib/Tools/c_aliases.py diff --git a/Tools/c_config.py b/waflib/Tools/c_config.py index d2b3c0d..d2b3c0d 100644 --- a/Tools/c_config.py +++ b/waflib/Tools/c_config.py diff --git a/Tools/c_osx.py b/waflib/Tools/c_osx.py index f70b128..f70b128 100644 --- a/Tools/c_osx.py +++ b/waflib/Tools/c_osx.py diff --git a/Tools/c_preproc.py b/waflib/Tools/c_preproc.py index 7e04b4a..7e04b4a 100644 --- a/Tools/c_preproc.py +++ b/waflib/Tools/c_preproc.py diff --git a/Tools/c_tests.py b/waflib/Tools/c_tests.py index f858df5..f858df5 100644 --- a/Tools/c_tests.py +++ b/waflib/Tools/c_tests.py diff --git a/Tools/ccroot.py b/waflib/Tools/ccroot.py index cfef8bf..cfef8bf 100644 --- a/Tools/ccroot.py +++ b/waflib/Tools/ccroot.py diff --git a/Tools/clang.py b/waflib/Tools/clang.py index 3828e39..3828e39 100644 --- a/Tools/clang.py +++ b/waflib/Tools/clang.py diff --git a/Tools/clangxx.py b/waflib/Tools/clangxx.py index 152013c..152013c 100644 --- a/Tools/clangxx.py +++ b/waflib/Tools/clangxx.py diff --git a/Tools/compiler_c.py b/waflib/Tools/compiler_c.py index 2dba3f8..2dba3f8 100644 --- a/Tools/compiler_c.py +++ b/waflib/Tools/compiler_c.py diff --git a/Tools/compiler_cxx.py b/waflib/Tools/compiler_cxx.py index 1af65a2..1af65a2 100644 --- a/Tools/compiler_cxx.py +++ b/waflib/Tools/compiler_cxx.py diff --git a/Tools/compiler_d.py b/waflib/Tools/compiler_d.py index 43bb1f6..43bb1f6 100644 --- a/Tools/compiler_d.py +++ b/waflib/Tools/compiler_d.py diff --git a/Tools/compiler_fc.py b/waflib/Tools/compiler_fc.py index 96b58e7..96b58e7 100644 --- a/Tools/compiler_fc.py +++ b/waflib/Tools/compiler_fc.py diff --git a/Tools/cs.py b/waflib/Tools/cs.py index aecca6d..aecca6d 100644 --- a/Tools/cs.py +++ b/waflib/Tools/cs.py diff --git a/Tools/cxx.py b/waflib/Tools/cxx.py index 194fad7..194fad7 100644 --- a/Tools/cxx.py +++ b/waflib/Tools/cxx.py diff --git a/Tools/d.py b/waflib/Tools/d.py index e4cf73b..e4cf73b 100644 --- a/Tools/d.py +++ b/waflib/Tools/d.py diff --git a/Tools/d_config.py b/waflib/Tools/d_config.py index 6637556..6637556 100644 --- a/Tools/d_config.py +++ b/waflib/Tools/d_config.py diff --git a/Tools/d_scan.py b/waflib/Tools/d_scan.py index 14c6c31..14c6c31 100644 --- a/Tools/d_scan.py +++ b/waflib/Tools/d_scan.py diff --git a/Tools/dbus.py b/waflib/Tools/dbus.py index d520f1c..d520f1c 100644 --- a/Tools/dbus.py +++ b/waflib/Tools/dbus.py diff --git a/Tools/dmd.py b/waflib/Tools/dmd.py index 8917ca1..8917ca1 100644 --- a/Tools/dmd.py +++ b/waflib/Tools/dmd.py diff --git a/Tools/errcheck.py b/waflib/Tools/errcheck.py index de8d75a..de8d75a 100644 --- a/Tools/errcheck.py +++ b/waflib/Tools/errcheck.py diff --git a/Tools/fc.py b/waflib/Tools/fc.py index d9e8d8c..d9e8d8c 100644 --- a/Tools/fc.py +++ b/waflib/Tools/fc.py diff --git a/Tools/fc_config.py b/waflib/Tools/fc_config.py index 222f3a5..222f3a5 100644 --- a/Tools/fc_config.py +++ b/waflib/Tools/fc_config.py diff --git a/Tools/fc_scan.py b/waflib/Tools/fc_scan.py index 12cb0fc..12cb0fc 100644 --- a/Tools/fc_scan.py +++ b/waflib/Tools/fc_scan.py diff --git a/Tools/flex.py b/waflib/Tools/flex.py index 2256657..2256657 100644 --- a/Tools/flex.py +++ b/waflib/Tools/flex.py diff --git a/Tools/g95.py b/waflib/Tools/g95.py index f69ba4f..f69ba4f 100644 --- a/Tools/g95.py +++ b/waflib/Tools/g95.py diff --git a/Tools/gas.py b/waflib/Tools/gas.py index 77afed7..77afed7 100644 --- a/Tools/gas.py +++ b/waflib/Tools/gas.py diff --git a/Tools/gcc.py b/waflib/Tools/gcc.py index acdd473..acdd473 100644 --- a/Tools/gcc.py +++ b/waflib/Tools/gcc.py diff --git a/Tools/gdc.py b/waflib/Tools/gdc.py index d89a66d..d89a66d 100644 --- a/Tools/gdc.py +++ b/waflib/Tools/gdc.py diff --git a/Tools/gfortran.py b/waflib/Tools/gfortran.py index 1050667..1050667 100644 --- a/Tools/gfortran.py +++ b/waflib/Tools/gfortran.py diff --git a/Tools/glib2.py b/waflib/Tools/glib2.py index 949fe37..949fe37 100644 --- a/Tools/glib2.py +++ b/waflib/Tools/glib2.py diff --git a/Tools/gnu_dirs.py b/waflib/Tools/gnu_dirs.py index 2847071..2847071 100644 --- a/Tools/gnu_dirs.py +++ b/waflib/Tools/gnu_dirs.py diff --git a/Tools/gxx.py b/waflib/Tools/gxx.py index 22c5d26..22c5d26 100644 --- a/Tools/gxx.py +++ b/waflib/Tools/gxx.py diff --git a/Tools/icc.py b/waflib/Tools/icc.py index b6492c8..b6492c8 100644 --- a/Tools/icc.py +++ b/waflib/Tools/icc.py diff --git a/Tools/icpc.py b/waflib/Tools/icpc.py index 8a6cc6c..8a6cc6c 100644 --- a/Tools/icpc.py +++ b/waflib/Tools/icpc.py diff --git a/Tools/ifort.py b/waflib/Tools/ifort.py index 74934f3..74934f3 100644 --- a/Tools/ifort.py +++ b/waflib/Tools/ifort.py diff --git a/Tools/intltool.py b/waflib/Tools/intltool.py index af95ba8..af95ba8 100644 --- a/Tools/intltool.py +++ b/waflib/Tools/intltool.py diff --git a/Tools/irixcc.py b/waflib/Tools/irixcc.py index c3ae1ac..c3ae1ac 100644 --- a/Tools/irixcc.py +++ b/waflib/Tools/irixcc.py diff --git a/Tools/javaw.py b/waflib/Tools/javaw.py index f6fd20c..f6fd20c 100644 --- a/Tools/javaw.py +++ b/waflib/Tools/javaw.py diff --git a/Tools/ldc2.py b/waflib/Tools/ldc2.py index a51c344..a51c344 100644 --- a/Tools/ldc2.py +++ b/waflib/Tools/ldc2.py diff --git a/Tools/lua.py b/waflib/Tools/lua.py index 15a333a..15a333a 100644 --- a/Tools/lua.py +++ b/waflib/Tools/lua.py diff --git a/Tools/md5_tstamp.py b/waflib/Tools/md5_tstamp.py index 6428e46..6428e46 100644 --- a/Tools/md5_tstamp.py +++ b/waflib/Tools/md5_tstamp.py diff --git a/Tools/msvc.py b/waflib/Tools/msvc.py index 17b347d..17b347d 100644 --- a/Tools/msvc.py +++ b/waflib/Tools/msvc.py diff --git a/Tools/nasm.py b/waflib/Tools/nasm.py index 411d582..411d582 100644 --- a/Tools/nasm.py +++ b/waflib/Tools/nasm.py diff --git a/Tools/nobuild.py b/waflib/Tools/nobuild.py index 2e4b055..2e4b055 100644 --- a/Tools/nobuild.py +++ b/waflib/Tools/nobuild.py diff --git a/Tools/perl.py b/waflib/Tools/perl.py index 32b03fb..32b03fb 100644 --- a/Tools/perl.py +++ b/waflib/Tools/perl.py diff --git a/Tools/python.py b/waflib/Tools/python.py index 25841d0..25841d0 100644 --- a/Tools/python.py +++ b/waflib/Tools/python.py diff --git a/Tools/qt5.py b/waflib/Tools/qt5.py index 4f9c690..4f9c690 100644 --- a/Tools/qt5.py +++ b/waflib/Tools/qt5.py diff --git a/Tools/ruby.py b/waflib/Tools/ruby.py index 8d92a79..8d92a79 100644 --- a/Tools/ruby.py +++ b/waflib/Tools/ruby.py diff --git a/Tools/suncc.py b/waflib/Tools/suncc.py index 33d34fc..33d34fc 100644 --- a/Tools/suncc.py +++ b/waflib/Tools/suncc.py diff --git a/Tools/suncxx.py b/waflib/Tools/suncxx.py index 3b384f6..3b384f6 100644 --- a/Tools/suncxx.py +++ b/waflib/Tools/suncxx.py diff --git a/Tools/tex.py b/waflib/Tools/tex.py index eaf9fdb..eaf9fdb 100644 --- a/Tools/tex.py +++ b/waflib/Tools/tex.py diff --git a/Tools/vala.py b/waflib/Tools/vala.py index 822ec50..822ec50 100644 --- a/Tools/vala.py +++ b/waflib/Tools/vala.py diff --git a/Tools/waf_unit_test.py b/waflib/Tools/waf_unit_test.py index a71ed1c..a71ed1c 100644 --- a/Tools/waf_unit_test.py +++ b/waflib/Tools/waf_unit_test.py diff --git a/Tools/winres.py b/waflib/Tools/winres.py index 586c596..586c596 100644 --- a/Tools/winres.py +++ b/waflib/Tools/winres.py diff --git a/Tools/xlc.py b/waflib/Tools/xlc.py index 134dd41..134dd41 100644 --- a/Tools/xlc.py +++ b/waflib/Tools/xlc.py diff --git a/Tools/xlcxx.py b/waflib/Tools/xlcxx.py index 76aa59b..76aa59b 100644 --- a/Tools/xlcxx.py +++ b/waflib/Tools/xlcxx.py diff --git a/Utils.py b/waflib/Utils.py index b4665c4..b4665c4 100644 --- a/Utils.py +++ b/waflib/Utils.py diff --git a/__init__.py b/waflib/__init__.py index 079df35..079df35 100644 --- a/__init__.py +++ b/waflib/__init__.py diff --git a/ansiterm.py b/waflib/ansiterm.py index 0d20c63..0d20c63 100644 --- a/ansiterm.py +++ b/waflib/ansiterm.py diff --git a/extras/__init__.py b/waflib/extras/__init__.py index c8a3c34..c8a3c34 100644 --- a/extras/__init__.py +++ b/waflib/extras/__init__.py diff --git a/extras/autowaf.py b/waflib/extras/autowaf.py index a4a06ba..a4a06ba 100644 --- a/extras/autowaf.py +++ b/waflib/extras/autowaf.py diff --git a/extras/batched_cc.py b/waflib/extras/batched_cc.py index aad2872..aad2872 100644 --- a/extras/batched_cc.py +++ b/waflib/extras/batched_cc.py diff --git a/extras/biber.py b/waflib/extras/biber.py index fd9db4e..fd9db4e 100644 --- a/extras/biber.py +++ b/waflib/extras/biber.py diff --git a/extras/bjam.py b/waflib/extras/bjam.py index 8e04d3a..8e04d3a 100644 --- a/extras/bjam.py +++ b/waflib/extras/bjam.py diff --git a/extras/blender.py b/waflib/extras/blender.py index e5efc28..e5efc28 100644 --- a/extras/blender.py +++ b/waflib/extras/blender.py diff --git a/extras/boo.py b/waflib/extras/boo.py index 06623d4..06623d4 100644 --- a/extras/boo.py +++ b/waflib/extras/boo.py diff --git a/extras/boost.py b/waflib/extras/boost.py index c2aaaa9..c2aaaa9 100644 --- a/extras/boost.py +++ b/waflib/extras/boost.py diff --git a/extras/build_file_tracker.py b/waflib/extras/build_file_tracker.py index c4f26fd..c4f26fd 100644 --- a/extras/build_file_tracker.py +++ b/waflib/extras/build_file_tracker.py diff --git a/extras/build_logs.py b/waflib/extras/build_logs.py index cdf8ed0..cdf8ed0 100644 --- a/extras/build_logs.py +++ b/waflib/extras/build_logs.py diff --git a/extras/buildcopy.py b/waflib/extras/buildcopy.py index a6d9ac8..a6d9ac8 100644 --- a/extras/buildcopy.py +++ b/waflib/extras/buildcopy.py diff --git a/extras/c_bgxlc.py b/waflib/extras/c_bgxlc.py index 6e3eaf7..6e3eaf7 100644 --- a/extras/c_bgxlc.py +++ b/waflib/extras/c_bgxlc.py diff --git a/extras/c_dumbpreproc.py b/waflib/extras/c_dumbpreproc.py index ce9e1a4..ce9e1a4 100644 --- a/extras/c_dumbpreproc.py +++ b/waflib/extras/c_dumbpreproc.py diff --git a/extras/c_emscripten.py b/waflib/extras/c_emscripten.py index e1ac494..e1ac494 100644 --- a/extras/c_emscripten.py +++ b/waflib/extras/c_emscripten.py diff --git a/extras/c_nec.py b/waflib/extras/c_nec.py index 96bfae4..96bfae4 100644 --- a/extras/c_nec.py +++ b/waflib/extras/c_nec.py diff --git a/extras/cabal.py b/waflib/extras/cabal.py index e10a0d1..e10a0d1 100644 --- a/extras/cabal.py +++ b/waflib/extras/cabal.py diff --git a/extras/cfg_altoptions.py b/waflib/extras/cfg_altoptions.py index 47b1189..47b1189 100644 --- a/extras/cfg_altoptions.py +++ b/waflib/extras/cfg_altoptions.py diff --git a/extras/clang_compilation_database.py b/waflib/extras/clang_compilation_database.py index 4d9b5e2..4d9b5e2 100644 --- a/extras/clang_compilation_database.py +++ b/waflib/extras/clang_compilation_database.py diff --git a/extras/codelite.py b/waflib/extras/codelite.py index 523302c..523302c 100644 --- a/extras/codelite.py +++ b/waflib/extras/codelite.py diff --git a/extras/color_gcc.py b/waflib/extras/color_gcc.py index b68c5eb..b68c5eb 100644 --- a/extras/color_gcc.py +++ b/waflib/extras/color_gcc.py diff --git a/extras/color_rvct.py b/waflib/extras/color_rvct.py index f89ccbd..f89ccbd 100644 --- a/extras/color_rvct.py +++ b/waflib/extras/color_rvct.py diff --git a/extras/compat15.py b/waflib/extras/compat15.py index 0e74df8..0e74df8 100644 --- a/extras/compat15.py +++ b/waflib/extras/compat15.py diff --git a/extras/cppcheck.py b/waflib/extras/cppcheck.py index 13ff424..13ff424 100644 --- a/extras/cppcheck.py +++ b/waflib/extras/cppcheck.py diff --git a/extras/cpplint.py b/waflib/extras/cpplint.py index e3302e5..e3302e5 100644 --- a/extras/cpplint.py +++ b/waflib/extras/cpplint.py diff --git a/extras/cross_gnu.py b/waflib/extras/cross_gnu.py index 309f53b..309f53b 100644 --- a/extras/cross_gnu.py +++ b/waflib/extras/cross_gnu.py diff --git a/extras/cython.py b/waflib/extras/cython.py index 481d6f4..481d6f4 100644 --- a/extras/cython.py +++ b/waflib/extras/cython.py diff --git a/extras/dcc.py b/waflib/extras/dcc.py index c1a57c0..c1a57c0 100644 --- a/extras/dcc.py +++ b/waflib/extras/dcc.py diff --git a/extras/distnet.py b/waflib/extras/distnet.py index 09a31a6..09a31a6 100644 --- a/extras/distnet.py +++ b/waflib/extras/distnet.py diff --git a/extras/doxygen.py b/waflib/extras/doxygen.py index 28f56e9..28f56e9 100644 --- a/extras/doxygen.py +++ b/waflib/extras/doxygen.py diff --git a/extras/dpapi.py b/waflib/extras/dpapi.py index b94d482..b94d482 100644 --- a/extras/dpapi.py +++ b/waflib/extras/dpapi.py diff --git a/extras/eclipse.py b/waflib/extras/eclipse.py index bb78741..bb78741 100644 --- a/extras/eclipse.py +++ b/waflib/extras/eclipse.py diff --git a/extras/erlang.py b/waflib/extras/erlang.py index 49f6d5b..49f6d5b 100644 --- a/extras/erlang.py +++ b/waflib/extras/erlang.py diff --git a/extras/fast_partial.py b/waflib/extras/fast_partial.py index b3af513..b3af513 100644 --- a/extras/fast_partial.py +++ b/waflib/extras/fast_partial.py diff --git a/extras/fc_bgxlf.py b/waflib/extras/fc_bgxlf.py index cca1810..cca1810 100644 --- a/extras/fc_bgxlf.py +++ b/waflib/extras/fc_bgxlf.py diff --git a/extras/fc_cray.py b/waflib/extras/fc_cray.py index da733fa..da733fa 100644 --- a/extras/fc_cray.py +++ b/waflib/extras/fc_cray.py diff --git a/extras/fc_nag.py b/waflib/extras/fc_nag.py index edcb218..edcb218 100644 --- a/extras/fc_nag.py +++ b/waflib/extras/fc_nag.py diff --git a/extras/fc_nec.py b/waflib/extras/fc_nec.py index 67c8680..67c8680 100644 --- a/extras/fc_nec.py +++ b/waflib/extras/fc_nec.py diff --git a/extras/fc_open64.py b/waflib/extras/fc_open64.py index 413719f..413719f 100644 --- a/extras/fc_open64.py +++ b/waflib/extras/fc_open64.py diff --git a/extras/fc_pgfortran.py b/waflib/extras/fc_pgfortran.py index afb2817..afb2817 100644 --- a/extras/fc_pgfortran.py +++ b/waflib/extras/fc_pgfortran.py diff --git a/extras/fc_solstudio.py b/waflib/extras/fc_solstudio.py index 53766df..53766df 100644 --- a/extras/fc_solstudio.py +++ b/waflib/extras/fc_solstudio.py diff --git a/extras/fc_xlf.py b/waflib/extras/fc_xlf.py index 5a3da03..5a3da03 100644 --- a/extras/fc_xlf.py +++ b/waflib/extras/fc_xlf.py diff --git a/extras/file_to_object.py b/waflib/extras/file_to_object.py index 1393b51..1393b51 100644 --- a/extras/file_to_object.py +++ b/waflib/extras/file_to_object.py diff --git a/extras/fluid.py b/waflib/extras/fluid.py index 4814a35..4814a35 100644 --- a/extras/fluid.py +++ b/waflib/extras/fluid.py diff --git a/extras/freeimage.py b/waflib/extras/freeimage.py index f27e525..f27e525 100644 --- a/extras/freeimage.py +++ b/waflib/extras/freeimage.py diff --git a/extras/fsb.py b/waflib/extras/fsb.py index 1b8f398..1b8f398 100644 --- a/extras/fsb.py +++ b/waflib/extras/fsb.py diff --git a/extras/fsc.py b/waflib/extras/fsc.py index c67e70b..c67e70b 100644 --- a/extras/fsc.py +++ b/waflib/extras/fsc.py diff --git a/extras/gccdeps.py b/waflib/extras/gccdeps.py index d9758ab..d9758ab 100644 --- a/extras/gccdeps.py +++ b/waflib/extras/gccdeps.py diff --git a/extras/gdbus.py b/waflib/extras/gdbus.py index 0e0476e..0e0476e 100644 --- a/extras/gdbus.py +++ b/waflib/extras/gdbus.py diff --git a/extras/gob2.py b/waflib/extras/gob2.py index b4fa3b9..b4fa3b9 100644 --- a/extras/gob2.py +++ b/waflib/extras/gob2.py diff --git a/extras/halide.py b/waflib/extras/halide.py index 6078e38..6078e38 100644 --- a/extras/halide.py +++ b/waflib/extras/halide.py diff --git a/extras/javatest.py b/waflib/extras/javatest.py index 979b8d8..979b8d8 100755 --- a/extras/javatest.py +++ b/waflib/extras/javatest.py diff --git a/extras/kde4.py b/waflib/extras/kde4.py index e49a9ec..e49a9ec 100644 --- a/extras/kde4.py +++ b/waflib/extras/kde4.py diff --git a/extras/local_rpath.py b/waflib/extras/local_rpath.py index b2507e1..b2507e1 100644 --- a/extras/local_rpath.py +++ b/waflib/extras/local_rpath.py diff --git a/extras/lv2.py b/waflib/extras/lv2.py index 815987f..815987f 100644 --- a/extras/lv2.py +++ b/waflib/extras/lv2.py diff --git a/extras/make.py b/waflib/extras/make.py index 933d9ca..933d9ca 100644 --- a/extras/make.py +++ b/waflib/extras/make.py diff --git a/extras/midl.py b/waflib/extras/midl.py index 43e6cf9..43e6cf9 100644 --- a/extras/midl.py +++ b/waflib/extras/midl.py diff --git a/extras/msvcdeps.py b/waflib/extras/msvcdeps.py index fc1ecd4..fc1ecd4 100644 --- a/extras/msvcdeps.py +++ b/waflib/extras/msvcdeps.py diff --git a/extras/msvs.py b/waflib/extras/msvs.py index 8aa2db0..8aa2db0 100644 --- a/extras/msvs.py +++ b/waflib/extras/msvs.py diff --git a/extras/netcache_client.py b/waflib/extras/netcache_client.py index dc49048..dc49048 100644 --- a/extras/netcache_client.py +++ b/waflib/extras/netcache_client.py diff --git a/extras/objcopy.py b/waflib/extras/objcopy.py index 82d8359..82d8359 100644 --- a/extras/objcopy.py +++ b/waflib/extras/objcopy.py diff --git a/extras/ocaml.py b/waflib/extras/ocaml.py index afe73c0..afe73c0 100644 --- a/extras/ocaml.py +++ b/waflib/extras/ocaml.py diff --git a/extras/package.py b/waflib/extras/package.py index c06498e..c06498e 100644 --- a/extras/package.py +++ b/waflib/extras/package.py diff --git a/extras/parallel_debug.py b/waflib/extras/parallel_debug.py index 35883a3..35883a3 100644 --- a/extras/parallel_debug.py +++ b/waflib/extras/parallel_debug.py diff --git a/extras/pch.py b/waflib/extras/pch.py index 103e752..103e752 100644 --- a/extras/pch.py +++ b/waflib/extras/pch.py diff --git a/extras/pep8.py b/waflib/extras/pep8.py index 676beed..676beed 100644 --- a/extras/pep8.py +++ b/waflib/extras/pep8.py diff --git a/extras/pgicc.py b/waflib/extras/pgicc.py index 9790b9c..9790b9c 100644 --- a/extras/pgicc.py +++ b/waflib/extras/pgicc.py diff --git a/extras/pgicxx.py b/waflib/extras/pgicxx.py index eae121c..eae121c 100644 --- a/extras/pgicxx.py +++ b/waflib/extras/pgicxx.py diff --git a/extras/proc.py b/waflib/extras/proc.py index 764abec..764abec 100644 --- a/extras/proc.py +++ b/waflib/extras/proc.py diff --git a/extras/protoc.py b/waflib/extras/protoc.py index f3cb4d8..f3cb4d8 100644 --- a/extras/protoc.py +++ b/waflib/extras/protoc.py diff --git a/extras/pyqt5.py b/waflib/extras/pyqt5.py index c21dfa7..c21dfa7 100644 --- a/extras/pyqt5.py +++ b/waflib/extras/pyqt5.py diff --git a/extras/pytest.py b/waflib/extras/pytest.py index 7dd5a1a..7dd5a1a 100644 --- a/extras/pytest.py +++ b/waflib/extras/pytest.py diff --git a/extras/qnxnto.py b/waflib/extras/qnxnto.py index 1158124..1158124 100644 --- a/extras/qnxnto.py +++ b/waflib/extras/qnxnto.py diff --git a/extras/qt4.py b/waflib/extras/qt4.py index 90cae7e..90cae7e 100644 --- a/extras/qt4.py +++ b/waflib/extras/qt4.py diff --git a/extras/relocation.py b/waflib/extras/relocation.py index 7e821f4..7e821f4 100644 --- a/extras/relocation.py +++ b/waflib/extras/relocation.py diff --git a/extras/remote.py b/waflib/extras/remote.py index 3b038f7..3b038f7 100644 --- a/extras/remote.py +++ b/waflib/extras/remote.py diff --git a/extras/resx.py b/waflib/extras/resx.py index caf4d31..caf4d31 100644 --- a/extras/resx.py +++ b/waflib/extras/resx.py diff --git a/extras/review.py b/waflib/extras/review.py index 561e062..561e062 100644 --- a/extras/review.py +++ b/waflib/extras/review.py diff --git a/extras/rst.py b/waflib/extras/rst.py index f3c3a5e..f3c3a5e 100644 --- a/extras/rst.py +++ b/waflib/extras/rst.py diff --git a/extras/run_do_script.py b/waflib/extras/run_do_script.py index f3c5812..f3c5812 100644 --- a/extras/run_do_script.py +++ b/waflib/extras/run_do_script.py diff --git a/extras/run_m_script.py b/waflib/extras/run_m_script.py index b5f27eb..b5f27eb 100644 --- a/extras/run_m_script.py +++ b/waflib/extras/run_m_script.py diff --git a/extras/run_py_script.py b/waflib/extras/run_py_script.py index 3670381..3670381 100644 --- a/extras/run_py_script.py +++ b/waflib/extras/run_py_script.py diff --git a/extras/run_r_script.py b/waflib/extras/run_r_script.py index b0d8f2b..b0d8f2b 100644 --- a/extras/run_r_script.py +++ b/waflib/extras/run_r_script.py diff --git a/extras/sas.py b/waflib/extras/sas.py index 754c614..754c614 100644 --- a/extras/sas.py +++ b/waflib/extras/sas.py diff --git a/extras/satellite_assembly.py b/waflib/extras/satellite_assembly.py index 005eb07..005eb07 100644 --- a/extras/satellite_assembly.py +++ b/waflib/extras/satellite_assembly.py diff --git a/extras/scala.py b/waflib/extras/scala.py index a9880f0..a9880f0 100644 --- a/extras/scala.py +++ b/waflib/extras/scala.py diff --git a/extras/slow_qt4.py b/waflib/extras/slow_qt4.py index ec7880b..ec7880b 100644 --- a/extras/slow_qt4.py +++ b/waflib/extras/slow_qt4.py diff --git a/extras/softlink_libs.py b/waflib/extras/softlink_libs.py index 50c777f..50c777f 100644 --- a/extras/softlink_libs.py +++ b/waflib/extras/softlink_libs.py diff --git a/extras/stale.py b/waflib/extras/stale.py index cac3f46..cac3f46 100644 --- a/extras/stale.py +++ b/waflib/extras/stale.py diff --git a/extras/stracedeps.py b/waflib/extras/stracedeps.py index 37d82cb..37d82cb 100644 --- a/extras/stracedeps.py +++ b/waflib/extras/stracedeps.py diff --git a/extras/swig.py b/waflib/extras/swig.py index fd3d6d2..fd3d6d2 100644 --- a/extras/swig.py +++ b/waflib/extras/swig.py diff --git a/extras/syms.py b/waflib/extras/syms.py index dfa0059..dfa0059 100644 --- a/extras/syms.py +++ b/waflib/extras/syms.py diff --git a/extras/ticgt.py b/waflib/extras/ticgt.py index f43a7ea..f43a7ea 100644 --- a/extras/ticgt.py +++ b/waflib/extras/ticgt.py diff --git a/extras/unity.py b/waflib/extras/unity.py index 78128ed..78128ed 100644 --- a/extras/unity.py +++ b/waflib/extras/unity.py diff --git a/extras/use_config.py b/waflib/extras/use_config.py index ef5129f..ef5129f 100644 --- a/extras/use_config.py +++ b/waflib/extras/use_config.py diff --git a/extras/valadoc.py b/waflib/extras/valadoc.py index c50f69e..c50f69e 100644 --- a/extras/valadoc.py +++ b/waflib/extras/valadoc.py diff --git a/extras/waf_xattr.py b/waflib/extras/waf_xattr.py index 351dd63..351dd63 100644 --- a/extras/waf_xattr.py +++ b/waflib/extras/waf_xattr.py diff --git a/extras/why.py b/waflib/extras/why.py index 1bb941f..1bb941f 100644 --- a/extras/why.py +++ b/waflib/extras/why.py diff --git a/extras/win32_opts.py b/waflib/extras/win32_opts.py index 9f7443c..9f7443c 100644 --- a/extras/win32_opts.py +++ b/waflib/extras/win32_opts.py diff --git a/extras/wix.py b/waflib/extras/wix.py index d87bfbb..d87bfbb 100644 --- a/extras/wix.py +++ b/waflib/extras/wix.py diff --git a/extras/xcode6.py b/waflib/extras/xcode6.py index 91bbff1..91bbff1 100644 --- a/extras/xcode6.py +++ b/waflib/extras/xcode6.py diff --git a/fixpy2.py b/waflib/fixpy2.py index 24176e0..24176e0 100644 --- a/fixpy2.py +++ b/waflib/fixpy2.py diff --git a/processor.py b/waflib/processor.py index 2eecf3b..2eecf3b 100755 --- a/processor.py +++ b/waflib/processor.py diff --git a/waflib/waf b/waflib/waf new file mode 100755 index 0000000..e22930a --- /dev/null +++ b/waflib/waf @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +# Minimal waf script for projects that include waflib directly + +from waflib import Context, Scripting + +import inspect +import os + +def main(): + script_path = os.path.abspath(inspect.getfile(inspect.getmodule(main))) + project_path = os.path.dirname(script_path) + Scripting.waf_entry_point(os.getcwd(), Context.WAFVERSION, project_path) + +if __name__ == '__main__': + main() @@ -0,0 +1,256 @@ +#!/usr/bin/env python +import os +import waflib.Options as Options +import waflib.Utils as Utils +import waflib.extras.autowaf as autowaf + +# Library and package version (UNIX style major, minor, micro) +# major increment <=> incompatible changes +# minor increment <=> compatible changes (additions) +# micro increment <=> no interface changes +GANV_VERSION = '1.5.4' +GANV_MAJOR_VERSION = '1' + +# Mandatory waf variables +APPNAME = 'ganv' # Package name for waf dist +VERSION = GANV_VERSION # Package version for waf dist +top = '.' # Source directory +out = 'build' # Build directory + +def options(ctx): + ctx.load('compiler_c') + ctx.load('compiler_cxx') + autowaf.set_options(ctx, test=True) + opt = ctx.get_option_group('Configuration options') + opt.add_option('--no-graphviz', action='store_true', dest='no_graphviz', + help='do not compile with graphviz support') + opt.add_option('--light-theme', action='store_true', dest='light_theme', + help='use light coloured theme') + opt.add_option('--no-fdgl', action='store_true', dest='no_fdgl', + help='use experimental force-directed graph layout') + opt.add_option('--no-nls', action='store_true', dest='no_nls', + help='disable i18n (native language support)') + opt.add_option('--gir', action='store_true', dest='gir', + help='build GObject introspection data') + +def configure(conf): + autowaf.display_header('Ganv Configuration') + conf.load('compiler_c', cache=True) + conf.load('compiler_cxx', cache=True) + conf.load('autowaf', cache=True) + autowaf.set_c_lang(conf, 'c99') + + autowaf.check_pkg(conf, 'gtk+-2.0', uselib_store='GTK', + atleast_version='2.0.0', mandatory=True) + autowaf.check_pkg(conf, 'gtkmm-2.4', uselib_store='GTKMM', + atleast_version='2.10.0', mandatory=True) + + if Options.options.gir: + autowaf.check_pkg(conf, 'gobject-introspection-1.0', + uselib_store='GIR', mandatory=False) + conf.find_program('g-ir-doc-tool', var='G_IR_DOC_TOOL', mandatory=False) + conf.find_program('yelp-build', var='YELP_BUILD', mandatory=False) + + if not Options.options.no_graphviz: + autowaf.check_pkg(conf, 'libgvc', uselib_store='AGRAPH_2_30', + atleast_version='2.30', mandatory=False) + if not conf.env.HAVE_AGRAPH_2_30: + autowaf.check_pkg(conf, 'libgvc', uselib_store='AGRAPH_2_20', + atleast_version='2.20', mandatory=False) + + if not Options.options.no_fdgl: + autowaf.define(conf, 'GANV_FDGL', 1) + + if Options.options.light_theme: + autowaf.define(conf, 'GANV_USE_LIGHT_THEME', 1) + + if not Options.options.no_nls: + autowaf.check_function(conf, 'cxx', 'dgettext', + header_name = 'libintl.h', + lib = 'intl', + define_name = 'ENABLE_NLS', + mandatory = False) + + conf.env.LIB_GANV = ['ganv-%s' % GANV_MAJOR_VERSION] + + conf.write_config_header('ganv_config.h', remove=False) + + autowaf.display_summary(conf) + autowaf.display_msg(conf, "Static (Graphviz) arrange", + bool(conf.env.HAVE_AGRAPH_2_20 or + conf.env.HAVE_AGRAPH_2_30)) + autowaf.display_msg(conf, "Interactive force-directed arrange", + bool(conf.env.GANV_FDGL)) + autowaf.display_msg(conf, "Native language support", bool(conf.env.ENABLE_NLS)) + autowaf.display_msg(conf, "GObject introspection", bool(conf.env.HAVE_GIR)) + autowaf.display_msg(conf, "Unit tests", bool(conf.env.BUILD_TESTS)) + print('') + +ganv_source = [ + 'src/Canvas.cpp', + 'src/Port.cpp', + 'src/box.c', + 'src/circle.c', + 'src/edge.c', + 'src/ganv-marshal.c', + 'src/group.c', + 'src/item.c', + 'src/module.c', + 'src/node.c', + 'src/port.c', + 'src/text.c', + 'src/widget.c' +] + +def declare_doc_files(task): + bld = task.generator.bld + path = bld.path.get_bld().find_or_declare('doc-html') + for i in path.ant_glob('*', remove=False): + i.sig = Utils.h_file(i.abspath()) + +def build(bld): + # Headers + includedir = '${INCLUDEDIR}/ganv-%s/ganv' % GANV_MAJOR_VERSION + bld.install_files(includedir, bld.path.ant_glob('ganv/*.h*')) + + # Pkgconfig file + autowaf.build_pc(bld, 'GANV', GANV_VERSION, GANV_MAJOR_VERSION, + 'GTKMM AGRAPH_2_20 AGRAPH_2_30', + {'GANV_MAJOR_VERSION' : GANV_MAJOR_VERSION}) + + bld(rule = 'glib-genmarshal --prefix=ganv_marshal --header ${SRC} > ${TGT}', + source = 'src/ganv-marshal.list', + target = 'src/ganv-marshal.h') + + bld(rule = 'glib-genmarshal --prefix=ganv_marshal --body ${SRC} > ${TGT}', + source = 'src/ganv-marshal.list', + target = 'src/ganv-marshal.c.in') + + bld(rule = 'cat ${SRC} > ${TGT}', + source = ['src/ganv-marshal.h', 'src/ganv-marshal.c.in'], + target = 'src/ganv-marshal.c') + + # Library + lib = bld(features = 'c cshlib cxx cxxshlib', + export_includes = ['.'], + source = ganv_source, + includes = ['.', './src'], + name = 'libganv', + target = 'ganv-%s' % GANV_MAJOR_VERSION, + uselib = 'GTKMM AGRAPH_2_20 AGRAPH_2_30', + vnum = GANV_VERSION, + install_path = '${LIBDIR}') + if bld.is_defined('ENABLE_NLS'): + lib.lib = ['intl'] + + # Benchmark program (C++) + bld(features = 'cxx cxxprogram', + source = 'src/ganv_bench.cpp', + includes = ['.', './src'], + use = 'libganv', + uselib = 'GTKMM AGRAPH_2_20 AGRAPH_2_30', + target = 'src/ganv_bench') + + if bld.env.BUILD_TESTS: + test_libs = [] + test_cflags = [''] + test_linkflags = [''] + if not bld.env.NO_COVERAGE: + test_cflags += ['--coverage'] + test_linkflags += ['--coverage'] + + # Static library for test program + bld(features = 'c cstlib cxx cxxshlib', + source = ganv_source, + includes = ['.', './src'], + name = 'libganv_profiled', + target = 'ganv_profiled', + uselib = 'GTKMM AGRAPH_2_20 AGRAPH_2_30', + install_path = '', + cflags = test_cflags, + linkflags = test_linkflags) + + # Test program (C) + bld(features = 'cxx cxxprogram', + source = 'src/ganv_test.c', + includes = ['.', './src'], + use = 'libganv_profiled', + lib = test_libs, + uselib = 'GTKMM AGRAPH_2_20 AGRAPH_2_30', + target = 'src/ganv_test', + cflags = test_cflags, + linkflags = test_linkflags) + + # Documentation + #autowaf.build_dox(bld, 'GANV', GANV_VERSION, top, out) + + if bld.env.HAVE_GIR: + bld.add_group() + + bld_dir = os.path.join(out, APPNAME) + if not (len(bld.stack_path) > 1): # not top-level + bld_dir = out + + pc_path = os.path.abspath(os.path.join(bld_dir, 'ganv-1.pc')) + + bld(name = 'ganv-gir', + source = ganv_source + bld.path.ant_glob('ganv/*.h'), + target = 'Ganv-1.0.gir', + install_path = '${LIBDIR}/girepository-1.0', + rule = 'g-ir-scanner --warn-all -n Ganv --nsversion=1.0' + ' --no-libtool ' + + ('--pkg=%s' % pc_path) + + (' -I%s' % bld.path.bldpath()) + + ''.join([' -I' + path for path in bld.env.INCLUDES_GTK]) + + (' -L%s' % bld_dir) + + ' --library=ganv-1' + ' --include=GObject-2.0 --include=Gdk-2.0 --include Gtk-2.0' + ' -o ${TGT} ${SRC}') + + bld(name = 'ganv-typelib', + after = 'ganv-gir', + source = 'Ganv-1.0.gir', + target = 'Ganv-1.0.typelib', + install_path = '${LIBDIR}/girepository-1.0', + rule = 'g-ir-compiler ${SRC} -o ${TGT}') + + if bld.env.DOCS and bld.env['G_IR_DOC_TOOL'] and bld.env['YELP_BUILD']: + # The source and target files used here aren't exclusive, + # but are declared so waf can track dependencies + bld(rule = '${G_IR_DOC_TOOL} --language C -o doc-xml ${SRC}', + source = 'Ganv-1.0.gir', + target = 'doc-xml/index.page') + bld(name = 'yelp-build', + rule = '${YELP_BUILD} html -o doc-html doc-xml', + source = 'doc-xml/index.page', + target = 'doc-html/index.html') + bld(name = 'find-docs', + always = True, + rule = declare_doc_files, + after = 'yelp-build') + + bld.install_files( + os.path.join('${DOCDIR}', 'ganv-0', 'html'), + bld.path.get_bld().ant_glob('doc-html/*')) + + bld.add_post_fun(autowaf.run_ldconfig) + +def test(ctx): + autowaf.pre_test(ctx, APPNAME) + autowaf.run_tests(ctx, APPNAME, ['src/ganv_test'], dirs=['./src']) + autowaf.post_test(ctx, APPNAME) + +def i18n(bld): + autowaf.build_i18n(bld, '..', 'ganv', APPNAME, ganv_source, + 'David Robillard') + +def posts(ctx): + path = str(ctx.path.abspath()) + autowaf.news_to_posts( + os.path.join(path, 'NEWS'), + {'title' : 'Ganv', + 'description' : autowaf.get_blurb(os.path.join(path, 'README')), + 'dist_pattern' : 'http://download.drobilla.net/ganv-%s.tar.bz2'}, + { 'Author' : 'drobilla', + 'Tags' : 'Hacking, LAD, LV2, Ganv' }, + os.path.join(out, 'posts')) |