Compare commits

..

217 Commits

Author SHA1 Message Date
Torsten Dreyer
5818366d92 Bump version number to 2.12.0 2013-06-28 12:09:30 +02:00
James Turner
e5bca74b9d std::string namespace fixes. 2013-06-27 10:14:08 +01:00
Thomas Geymayer
82dd1e4be9 Set binding after color array (required by OSG 3.1.8) 2013-06-27 00:38:44 +02:00
Alex Romosan
d0b77639c2 Ensure compatibility with OSG 3.1.8. 2013-06-26 23:54:09 +02:00
James Turner
6edb0f66d5 Fix crash when telnet or other protocols close.
http://code.google.com/p/flightgear-bugs/issues/detail?id=1144
2013-06-26 21:50:27 +01:00
James Turner
c458c13af7 XCode warning fixes. 2013-06-22 16:15:41 +01:00
James Turner
bb7875edd4 Fix a warning spotted by Emilian. 2013-06-22 16:15:30 +01:00
James Turner
6f54fb90eb Object names for STG groups / nodes. 2013-06-22 14:27:53 +01:00
James Turner
4d1e7c536f Fix preview exclusion of sliders. 2013-06-22 14:27:18 +01:00
James Turner
59cc200f92 Fix pick callbacks on scenery.
Ensure pick callbacks work over the whole pick group, otherwise
the visible render group is hit first, and resolves to the ground
pick callback instead.

This fixes the hangar doors at KSFO and Moffett.
2013-06-22 11:59:42 +01:00
Thomas Geymayer
c6093430ae Canvas: Fix creating/forwarding of mouseenter/mouseleave events. 2013-06-20 23:13:47 +02:00
James Turner
23172bcdd0 Windows SGPath::desktop() impl
Basic SHGetSpecialFolderPath wrapper, with dynamic
finding of the function since linking to shell32.dll
is painful. Obviously could be generalised to other
CSIDLs in the future.
2013-06-19 23:58:57 +01:00
James Turner
aea2dab0d7 Fix preprocessor boolean ops for MSVC 2013-06-19 09:04:15 +01:00
James Turner
4d26e144ab SVN client - prefix error constants.
Avoid Windows clashes on common names (NO_ERROR, etc)
2013-06-19 08:42:47 +01:00
Thomas Geymayer
c391fcd345 Canvas: fix updating placements after non matching placements 2013-06-18 23:07:20 +02:00
Thomas Geymayer
ece743342a canvas::Text: Make font resolution user configurable 2013-06-16 10:49:46 +02:00
Thomas Geymayer
02babbe340 canvas::Text: set font resolution to actual texel size. 2013-06-14 23:33:26 +02:00
Thomas Geymayer
e27b6d050c CanvasGroup: Do not try to remove already removed children. 2013-06-14 19:47:09 +02:00
Thomas Geymayer
86e2de8d10 Fix #1139
Always set TEST_LIBS as applications/tools also need to link
them, even if tests are disabled. Also allow disabling building
of sg_pkgutil.
2013-06-12 23:56:04 +02:00
James Turner
b4526f926d Use CoreServices, not Cocoa, for FSFindFolder 2013-06-12 22:11:43 +02:00
Thomas Geymayer
14f04878d1 CanvasGroup: allow derived classes to provide more/other child factories 2013-06-11 22:09:57 +02:00
Thomas Geymayer
830fb3b752 Use TEST_LIBS to include all libraries needed (on Mac) 2013-06-11 20:26:07 +02:00
Thomas Geymayer
9a336da359 Use global TEST_LIBS... 2013-06-11 19:41:29 +02:00
Thomas Geymayer
6cec49ce7e Missing library for Apple 2013-06-11 18:04:06 +02:00
Thomas Geymayer
6c28edea55 SGPath: new helpers and static members for home and desktop path 2013-06-10 21:37:00 +02:00
Thomas Geymayer
d7870d672e strutils: move unescape and simplify starts_with/ends_with 2013-06-10 21:36:13 +02:00
James Turner
5bd9228b4a Fix HTTP tests build. 2013-06-09 23:45:43 +01:00
James Turner
6392cf5a2f Fix Linux build of SVN code. 2013-06-09 23:14:25 +01:00
James Turner
6a2e2b704e Optionally, use internal code for SVN syncs. 2013-06-09 22:55:15 +01:00
James Turner
ecec803388 SVN read-only client code using our HTTP engine. 2013-06-09 19:19:03 +01:00
James Turner
848965e7f0 EasyXML: expose attributes easier. 2013-06-09 19:19:03 +01:00
James Turner
478af5f01e Base64 and hex helpers for strings 2013-06-09 19:19:03 +01:00
James Turner
14decb7387 simgear::Dir helpers
- remove all children of the dir
- check is the dir has children / is empty
2013-06-09 19:19:02 +01:00
James Turner
323d0d5ba0 HTTP engine tweaks for SVN support. 2013-06-09 19:19:02 +01:00
Thomas Geymayer
9bfa6ac1a1 Restructure Canvas/PropertyBasedElement
Nodes inside the osg scenegraph now hold a strong reference to
the according canvas element. Canvas elements in turn now only
hold a weak reference to the according node. This simplifies
for example the canvas::Group as it does not need to keep a
list of children on its own anymore. This is now stored inside
the scenegraph (as it was already before).

The restructuring will also allow to use a canvas::Group for
the canvas gui inside FlightGear and share for example the
handling of stacking based on z-index.
2013-06-08 11:28:49 +02:00
Thomas Geymayer
c8af817eeb CSSBorder: Do not return unitialized data. 2013-06-07 18:54:41 +02:00
Thomas Geymayer
d24d3ce487 Canvas: More helper functions and cleanup. 2013-06-07 16:45:34 +02:00
Thomas Geymayer
60a81dfbd8 CanvasGroup: Fix handling z-index while moving child to front. 2013-06-07 16:23:44 +02:00
Thomas Geymayer
8896a59dff Helper functions for SGRect and canvas::Element 2013-06-06 22:28:00 +02:00
Thomas Geymayer
7fe16d99be Canvas: clear event listeners on destroy
Removing all event listeneres on destroying a canvas
prevents circular references due to Nasal event listeners
keeping a reference to the canvas in their closure.

Also fix event handling with direct children of the root
group and add some more helpers to the Canvas.
2013-06-03 23:39:27 +02:00
Thomas Geymayer
c3af88dfc1 Canvas: set blend function for elements and prevent autoresize 2013-06-02 23:32:53 +02:00
Thomas Geymayer
5c8f0cc966 Make nasal::Ghost usable with weak_ptr 2013-06-02 21:20:47 +02:00
Thomas Geymayer
2a6c50c893 More helper methods for Canvas and PropertyBasedElement. 2013-06-02 21:19:37 +02:00
Thomas Geymayer
7a8d796ac1 Move BlendFunc parsing to separate file for easier reuse. 2013-06-02 20:41:03 +02:00
Thomas Geymayer
8b3b71bce3 Support for parsing basic CSS color keywords. 2013-05-31 19:18:36 +02:00
Thomas Geymayer
83da4e9248 Canvas Image: fix updating while showing nested canvas. 2013-05-31 19:17:30 +02:00
Thomas Geymayer
2c879095d9 Canvas: add chainable helpers to Path for adding segments. 2013-05-31 19:16:42 +02:00
Thomas Geymayer
13344fbb62 Canvas: separate CSSBorder parsing from image. 2013-05-31 19:14:39 +02:00
Thomas Geymayer
2fe9ad595f Canvas: bug fixing and add some helpers. 2013-05-31 19:07:55 +02:00
Stuart Buchanan
0f798289f0 Use square textures for trees, with shrunk UV coordinates. 2013-05-19 21:29:58 +01:00
Thomas Geymayer
01104cc1d3 Canvas: Add local_pos to mouse event and fix transformation of event positions with multi-level nested canvases. 2013-05-13 00:34:09 +02:00
Thomas Geymayer
d61b5827fd Canvas: proper forwarding of dirty and visible flags within nested canvases. 2013-05-13 00:26:13 +02:00
Stuart Buchanan
d18cc81531 Avoid UV bleeding issues on trees by using a strip of textures. 2013-05-10 20:03:48 +01:00
Thomas Geymayer
c96d7ae226 Use simulation time for spin and timed animations 2013-05-10 01:11:46 +02:00
Thomas Geymayer
9535aef59b Extend SGPickCallback to allow passing more information to callbacks 2013-05-09 21:31:27 +02:00
Thomas Geymayer
82f6fca06f Add (optional) uv coordinates to SGPickCallback 2013-05-09 12:40:59 +02:00
Thomas Geymayer
627b36a53b Prevent timer endless loops 2013-05-06 23:26:45 +02:00
Thomas Geymayer
2af78a3874 Allow adding a contition to locked-track animation 2013-05-02 00:47:51 +02:00
Stuart Buchanan
7c7109edf4 Reduce the height of tree UV coordinates to work around mipmap issues. 2013-05-01 22:01:45 +01:00
Thomas Geymayer
0fa23b83e6 Extend locked-track animation to support a slave element.
This allows tracking elements while at the same time changing the
rotation of both animated elements to fill the available distance
to the target element. This allows for example animating gear
scissors or other connected objects like gear doors and their
actuators/arms (possibly connected to the gear strut).
2013-04-28 23:46:25 +02:00
Thomas Geymayer
e53c71543a Move and rework pow to math/SGMisc 2013-04-28 15:43:02 +02:00
Thomas Geymayer
fa64f7b6aa Work around MSVC linker bug with classes derived from i/ofstream 2013-04-27 23:44:32 +02:00
Thomas Geymayer
eaac3a64f8 Fix linker error for test 2013-04-26 23:48:25 +02:00
Thomas Geymayer
4eabc45dfc Fix animation tests for low precision floating point 2013-04-26 00:19:00 +02:00
Thomas Geymayer
5c5e2a0f2f Remove debug output (and shorten test names) 2013-04-26 00:13:35 +02:00
Thomas Geymayer
559a5d146a Add trackTo (locked-track) animation 2013-04-25 23:23:16 +02:00
Stuart Buchanan
df46c58cb8 Reduce the texture coords for trees.
Support winter, summer and snow textures on single texture sheet.
2013-04-20 21:05:55 +01:00
Thomas Geymayer
1f43132363 Canvas: rework style setter system.
Style setters are now only setup once for each element type and
not for each element instance as before. A static map holds the
setters for each element type. Also an animation type is stored
which will later allow to animate properties of Canvas elements
without specifying and animation type.
2013-04-20 00:22:37 +02:00
Thomas Geymayer
17369a20de CanvaImage: Enable texture repeat for images 2013-04-15 21:09:41 +02:00
Thomas Geymayer
c83dae4c70 Comparison operators for SGSharedPtr 2013-04-13 16:21:04 +02:00
James Turner
c2f44a51c4 Restructure to avoid ordering issues.
Create a pick group for each object, instead of a single one. This
can be optimised in the future but this structure is backwards-
compatible with existing usage, which is more important.
2013-04-13 14:40:55 +01:00
Thomas Geymayer
3da44d1215 Nasal: add naVec_removelast. (Thanks to Philosopher) 2013-04-12 12:32:03 +02:00
James Turner
f6709e357f Support for proxy pick objects.
Pick, knob and slider animations support additional object-names,
which are always invisible and untransformed (in the case of knobs/
sliders). These objects act to extend the hover/pick area, making small
switches and knobs more usable. (Especially for users with smaller
screens / windows).
2013-04-05 17:12:58 +01:00
James Turner
45d5511fd7 Remove stray debug. 2013-04-02 18:10:09 +01:00
Thomas Geymayer
198c5d23fd nasal::Ghost tweaking to allow using with osg::oberser_ptr. 2013-04-01 13:35:41 +02:00
Thomas Geymayer
c41caeaf64 Ensure every scenery model has own SGModelData.
This makes Nasal unload hooks of scenery objects working again.
Previously the same SGModelData instance was used for all objects
which never got destroyed and therefore was not able to call any
unload callback.
2013-04-01 13:22:28 +02:00
Thomas Geymayer
b6c542b0e7 nasal::Ghost tweaking and to_nasal_helper for SGGeod. 2013-03-23 12:47:06 +01:00
Thomas Geymayer
2d62275a08 Fix uninitialized member.
Thanks to Chris for the catch.
2013-03-20 23:44:26 +01:00
Thomas Geymayer
971ea81861 cppbind: add from_nasal_helper to convert Nasal ghosts to C++ shared pointer 2013-03-19 18:36:55 +01:00
Thomas Geymayer
9e9cc7859c Interpolation system tweaking and add helpers to SGPropertyNode to interpolate its value 2013-03-17 23:48:01 +01:00
Thomas Geymayer
8898f5fe52 Tweak interpolator and allow passing list of interpolation steps 2013-03-16 16:36:20 +01:00
Thomas Geymayer
40be69ae8e Remove unecessary dependency from libSimGearCore on libSimGearScene. 2013-03-16 12:19:23 +01:00
Thomas Geymayer
17eec81071 Fix headless build 2013-03-16 10:35:30 +01:00
Thomas Geymayer
c9bbbd18ec New interpolation/animation system.
Inspired by jQuery.animate() properties can be interpolated using
different easing functions and specifying an animation duration.
Additionally animations can be chained to get table-based
animations like with the current SGInterpolator, or also create
looped animations or other more complicated curves.

Currently this system is not used yet, but it is intended to
replace SGInterpolator and allow more advanced animations of
eg. also colors, for example, for the canvas.
2013-03-15 23:37:17 +01:00
Thomas Geymayer
e08eda18d5 CMake: Create list of libs for testing only once 2013-03-15 23:21:38 +01:00
James Turner
d2c03a6651 Fix pick-callbacks with no action.
Ensure pick animations work only to show a tooltip. Thanks to Emilian
for noticing I broke this
2013-03-12 21:14:31 +00:00
Thomas Geymayer
0a1e920659 Ensure canvas is updated before displaying image containing a canvas 2013-03-11 21:22:43 +01:00
James Turner
cd58df820e Standardise location (in XML) of hovered elements.
Hovered bindings are always direction children of the pick animation, since having different hovered behaviours per-action makes no sense.
2013-03-11 18:07:27 +00:00
James Turner
1a5467aec8 Collect properties from expression/condition trees.
Initial ground-work to support efficient updating of condition/expression results; allow collecting all the dependent property values from the hierarchy, so they can be observed. Also add a very small test-case for this.
2013-03-11 16:53:52 +00:00
Thomas Geymayer
0dcb64dca3 slider - fix shifted check while dragging 2013-03-10 23:10:26 +01:00
James Turner
b703102d9b Part of fixing bug 1055.
Add machinery to convert hateful legacy Windows encodings to UTF-8.
2013-03-10 13:38:29 +00:00
James Turner
3c2ef75b50 Tweaks for pick callback cursors. 2013-03-10 12:03:09 +00:00
James Turner
4fa530354d Knob/slider - clean up internals.
Add support for a global sensitivity parameter to scale drag response.
2013-03-08 16:31:02 +00:00
James Turner
eeeb30dc3f Work on knob/slider animations.
Rename some values, and support mouse-dragging.
2013-03-07 18:40:37 +00:00
PlutoniumHeart
870fc2e53e Replacing strdup with _strdup in MSVC 2013-03-06 23:58:38 -07:00
Thomas Geymayer
79ae0da927 Fix some warnings 2013-03-07 00:17:45 +01:00
Thomas Geymayer
e179bda57a add static_pointer_cast for SGSharedPtr 2013-03-06 23:17:02 +01:00
Thomas Geymayer
8e0c15e32e cppbind: allow adding free functions accepting nasal::CallContext and do some cleanup 2013-03-06 17:00:38 +01:00
Thomas Geymayer
ff844f6760 May MSVC likes this more... 2013-03-05 18:12:17 +01:00
Thomas Geymayer
bc96ac85f4 cppbind: Add some methods to nasal::CallContext 2013-03-05 17:27:49 +01:00
James Turner
33b328e3d5 Fix priority of pick animations. 2013-03-05 08:39:44 +00:00
James Turner
dcda8d1c7a Add a pick priority lower than the default. 2013-03-04 23:33:21 +00:00
James Turner
5fd2bd225f Support motion-tracking in pick callbacks. 2013-03-04 23:11:40 +00:00
Thomas Geymayer
2db8859076 cppbind: Tweak from_nasal error reporting 2013-03-04 23:15:13 +01:00
Thomas Geymayer
6eff167a28 Silence MSVC 2013-03-04 18:42:32 +01:00
Thomas Geymayer
9fecb69b84 cppbind: tweaking from_nasal/to_nasal. 2013-03-04 16:26:28 +01:00
Thomas Geymayer
0539aa38e5 cppbind: faster from_nasal for bool and fix VS warning. 2013-03-04 00:30:30 +01:00
James Turner
fe9caad391 Max catalog age configurable per-catalog.
Allows for short-validity catalogs for development use.
2013-03-03 23:03:09 +00:00
Thomas Geymayer
ceae2928aa cppbind: refactor to_nasal for better name lookup.
Using template parameter dependent name lookup it is
now possible to create to_nasal_helper overloads
for custom types and having them used for automatic
type conversion with binding methods/member on
nasal::Ghost objects.
2013-03-03 23:42:40 +01:00
Christian Schmitt
0e6934abe5 Add a missing define for minizip, needed on some installations of zlib 2013-03-03 23:35:43 +01:00
Thomas Geymayer
a2e7c92f99 cppbind: Allow getter without from_nasal defined and setter without to_nasal. 2013-03-03 20:55:57 +01:00
Thomas Geymayer
643a8717b4 Make old gcc happy 2013-03-03 20:16:21 +01:00
Thomas Geymayer
f21127fd4a cppbind: rework to allow binding nearly everything.
It is now possible to register all types of member function
and free functions as methods for nasal::Ghost objects.
The return value and arguments are converte automatically
to the required types.

Also usage is simplified by removing replacing the old
method and method_func with a single method function
which only needs a name for the method and something
callable.
2013-03-03 19:28:39 +01:00
Peter Sadrozinski
0bc8005100 Add new api to retreive a list of SGBuckets within a bounding rectangle 2013-03-03 19:21:05 +01:00
James Turner
67efc1f68f Look for MSVC 3rdparty deps differently.
Check for the 3rdparty dir in the parent of build dir, not the parent of the source dir. For the recommended build layout, this is the same location, but for super-builds using fgmeta it's not (and the source tree should not be touched).

If this causes anyone issues, please let me know, since it's possible more flexibility is needed to set the path explicitly.
2013-03-03 16:06:51 +00:00
James Turner
dfebf60e68 Simplify CMakeLists now package code is enabled. 2013-03-03 16:01:06 +00:00
James Turner
26e7d134ce Package management tweaks. 2013-03-03 15:03:25 +00:00
Thomas Geymayer
c414242f13 sg_netChat: let getTerminator return const char* for compatiblity with FlightGear 2013-03-03 01:19:35 +01:00
Thomas Geymayer
33e60725b1 sg_netChat: Use std::string to prevent crash with old strdup.
Thanks to Godspeed for noticing this code crashing with MSVC.
Using std::string should fix this.
2013-03-03 01:12:01 +01:00
James Turner
f21b0985cc pkgutil can show detailed info about a package. 2013-03-02 17:02:08 +00:00
James Turner
71e9548c20 Lots more work on package support.
Delegate is hooked up in the demo util, and Install forwards control to the delegate too.
2013-03-02 16:30:39 +00:00
James Turner
fc64abea5c LGPL license on package files. 2013-03-02 15:15:52 +00:00
James Turner
413e89c955 Package dependencies. 2013-03-02 15:14:09 +00:00
James Turner
439041c2f4 Enable package code by default. 2013-03-02 14:43:19 +00:00
Thomas Geymayer
530de4d809 cppbind: Prepare for improved bindings.
- Improved Nasal/C++ bindings will follow. For now just test if
   all compilers are happy with intended approach.
 - Add to_nasal overload for std::map<std::string, T>.
2013-03-02 00:13:48 +01:00
Thomas Geymayer
5e45bdeeda Add optional user_data to Nasal C functions.
A user_data pointer and another pointer to an optional
deleter function is stored in unused parts of the naPtr
union. The previous behavior of extension functions does
not change. Only one additional boolean comparison is
required upon each function call to check whether user
data is available.
2013-03-01 12:22:51 +01:00
Thomas Geymayer
081eba903f Visual Studio fix 2013-03-01 00:33:47 +01:00
Thomas Geymayer
0def045611 cppbind: Fix SGPath from_nasal_helper 2013-02-28 23:46:55 +01:00
Thomas Geymayer
e20cc6a90f Nasal cppbind: support more member function types 2013-02-28 23:43:42 +01:00
Thomas Geymayer
7dd83dd2d1 Fix boolean conversion 2013-02-28 22:23:30 +01:00
Thomas Geymayer
1240807b9f ODGauge/Canvas: add option "additive-blend" to use additive alpha blending 2013-02-28 22:04:16 +01:00
James Turner
ad7d6624de Fix some signed/unsigned comparisoms. 2013-02-28 19:45:19 +00:00
James Turner
06b089ba13 Updated package-management code.
Use mini-unzip code to extract, since we don't need all the features of libArchive, and the deployment issues become simpler.
2013-02-28 18:31:28 +00:00
Thomas Geymayer
e1302bcf17 CanvasImage: Fix border abs/rel calculations; add slice-width property 2013-02-28 01:09:27 +01:00
Thomas Geymayer
cfdc7db79a sgstream_test needs linking with zlib 2013-02-26 12:53:56 +01:00
Thomas Geymayer
50de873453 Canvas: ensure z-index is updated on adding new child. 2013-02-26 12:49:04 +01:00
Christian Schmitt
a4ae7b2059 sgstream: detect two directly following CR/LF EOL correctly in skipeol,
supply a testcase for this.
2013-02-26 11:56:14 +01:00
Mathias Froehlich
d90abdcb44 scenery: Make static scenery static. 2013-02-25 06:55:35 +01:00
Mathias Froehlich
4a31045fd9 spt: Make the range multiplier configurable. 2013-02-25 06:42:55 +01:00
Mathias Froehlich
249155987d spt: Normalize the bucket box meta filename lengths. 2013-02-25 06:42:55 +01:00
Mathias Froehlich
2ae7fc244b spt: Expose the paging levels to osg options.
The levels at which page nodes are built is exposed by an
osg database pager option SimGear::SPT_PAGE_LEVELS which could
contain a blank separated list of levels where paging occurs.
Also the database pagers paths are searched for static lower
level of detail tiles in case they are available.
2013-02-25 06:42:55 +01:00
Mathias Froehlich
fafa70c6bc spt: Use vbos for geometry. 2013-02-25 06:42:40 +01:00
Mathias Froehlich
49162a7a64 stg: Defer loading models into a paged lod node.
This defers loading the models into a paged lod node
that is evaluated at a later point in time. That should
help for memory problems with higher visibilities.
2013-02-25 06:42:40 +01:00
Thomas Geymayer
328e6b3e28 Update bounding box if calling update() on canvas elements 2013-02-24 20:44:02 +01:00
Thomas Geymayer
5441db9b18 Support image slice/scale-9 for CanvasImage. 2013-02-24 17:57:19 +01:00
Thomas Geymayer
06bfa4f1a1 Fix package management compiling with linux 2013-02-21 14:03:08 +01:00
James Turner
a530712491 Initial work on package management.
Basic library infrastructure, catalog download/refresh, and package install,
uninstall and update. Disabled at cmake time by default, and not yet hooked
into FlightGear.
2013-02-20 16:17:22 +00:00
James Turner
3c8cfe9b76 Fix MSVC compilation, thanks to Fred. 2013-02-18 13:56:41 +00:00
Eric van den Berg
57b4ce96e4 added physical constant cp 2013-02-18 08:58:52 +00:00
Hooray
63c7d64143 fix mingw 2013-02-18 08:58:52 +00:00
Thomas Geymayer
1784327c47 CanvasImage: Use image/canvas size rather than texture size 2013-02-15 12:32:31 +01:00
Thomas Geymayer
a63ec83d5f CanvasImage: Use normalized coordinates by default.
Using absolute sizes by default lead to problems,
as while creating the image the size of the later
texture is not available.
2013-02-14 17:32:19 +01:00
Thomas Geymayer
d661516b02 Expose whether CanvasElement is visible 2013-02-13 12:09:15 +01:00
James Turner
ba020245f9 Fix jpeg-httpd compilation. 2013-02-11 12:51:20 +00:00
James Turner
79fefc1ff9 Support arbitrary parameters to bindings.
Use this to supply window X/Y to hover bindings.
2013-02-10 15:07:55 +00:00
James Turner
eac169b6da Grrrr. 2013-02-09 15:45:36 +00:00
James Turner
a60d293759 Tweak APi for GCC happiness. 2013-02-09 14:11:48 +00:00
James Turner
b0063f8db6 Command-manager supports functors.
Will be used to support native Nasal commands, which was tricky with pure function pointers before. Can also be used to avoid some trampoline functions in the code, but this can happen gradually.
2013-02-09 12:40:44 +00:00
Thomas Geymayer
632abdd87b Fix CanvasText horizontal alignment.
Don't reduce width of bounding box below character width
even if outline of rendered character is smaller. This
prevents trailing spaces from being ignored and also
fixes continuously chaging alignment (especially with
monospace fonts).
2013-02-09 12:17:45 +01:00
James Turner
d9b01ca5de Foundations of hover support in pick-callbacks. 2013-02-09 11:11:53 +00:00
James Turner
adb7db9229 Ensure individual log-level setting works. 2013-02-08 20:17:27 +00:00
James Turner
e4f7aec965 Linux needs explicit <algorithm> include. 2013-02-08 20:07:11 +00:00
James Turner
768a8c1062 Give terrasync a buffered log. 2013-02-08 19:37:47 +00:00
James Turner
318c5000ce Logging tweaks.
More pieces to support logging to buffers in the GUI.
2013-02-08 19:37:29 +00:00
James Turner
70f334e1a3 Fix GCC warnings about signed/unsigned compare. 2013-02-08 12:58:02 +00:00
James Turner
279b53a705 Move BufferedLogCallback to its own class.
Reduces includes in logstream.hxx, which is included by everyone and hence needs to be lightweight.
2013-02-08 12:58:02 +00:00
James Turner
a72a3ce5f3 Fix stderr log filtering.
Thanks to Stuart for pointing this out!
2013-02-08 12:58:02 +00:00
Thomas Geymayer
979aea5212 Add SGMisc::seek helper 2013-02-08 12:12:15 +01:00
James Turner
34d3c63384 Fix bucketBoxTest linkage. 2013-02-07 17:53:34 +00:00
James Turner
cf4af2692b Fix property tests static linkage. 2013-02-07 17:51:42 +00:00
James Turner
d335ca5a8d A couple more tests fixes. 2013-02-07 17:48:58 +00:00
James Turner
9cac2c1457 Fix math test linkage. 2013-02-07 17:42:00 +00:00
James Turner
523b992b4c Windows logging tweaks.
Restore the freopen calls, though I am unsure exactly what they achieve.
2013-02-07 17:23:31 +00:00
Christian Schmitt
02be490466 Missing headers 2013-02-07 18:20:57 +01:00
James Turner
45ae3978f6 Fix handling of ZSTREAM_END result from zlib.
Avoid getting stuck when ZSTREAM_END occurs with no more bytes written out.
2013-02-07 13:01:56 +01:00
James Turner
f983194d7e Buffered log-callback.
Allow a particular set of log messages to be retained indefinitely, for (presumably) later display somehow, such as in the GUI.
2013-02-07 13:01:56 +01:00
James Turner
ab8373b989 Rewrite logging code.
Support multiple backends, and make it thread-safe. Use an internal thread to push log entries to backends, to avoid blocking the submitting thread for any time.
2013-02-07 13:01:56 +01:00
Thomas Geymayer
4bef2c48eb Remove debug output 2013-02-07 10:10:38 +01:00
Thomas Geymayer
21e2a769eb Fix PropertyObject bug and interface improvements.
- Fix overwriting of parent node if PropertyObject::node() is
   called for a non-existing node.
 - Prevent implicit conversion from const char* and
   SGPropertyNode*
2013-02-07 01:25:55 +01:00
Stuart Buchanan
fd8369142a Building performance improvements.
Use sensible LoD range of 10km and gradual fade-out to 20km rather
than fixed 20km+ LoD range.  Also make deeper quad-tree to make
culling easier.
2013-02-06 21:53:57 +00:00
Thomas Geymayer
5dea221ad5 Expose reloading Canvas placements 2013-02-06 01:19:24 +01:00
James Turner
235c29913a Make the knob mouse-wheel direction configurable.
Add a global setting to control the mouse-wheel direction of the knob; a future commit will expose this in the FG GUI.
2013-02-03 17:21:50 +00:00
James Turner
0870407f65 Add 'release' bindings to knob animation.
Currently this fires on button release, but also after any mouse-wheel movement (since they are sent as buttons). This feels slightly wrong, but can't decide how a spring-loaded know would work, when interacting via the mouse-wheel. Comments welcome.
2013-02-03 17:11:05 +00:00
James Turner
e8156c6bd9 Knob animation: shifted mode binding.
As part of this, pass the osgGA GUIEventAdapter down into the pick callbacks, which seems reasonable to me. If anyone can foresee issues with this, please let me know and I can adjust the API.
2013-02-03 16:33:30 +00:00
Thomas Geymayer
e7f9486aa1 Nasal String wrapper and allow adding methods to string objects.
- Add nasal::String for wrapping Nasal string data and accessing
   string methods (which eg. could be exposed to Nasal)
 - Allow adding functions from C/C++ which are callable on
   Nasal strings.
2013-01-31 19:07:33 +01:00
James Turner
53b9fd2110 Support preview mode in the model loader.
This makes fgviewer / fgfs --viewer mode useful with models containing Rembrandt light volumes.
2013-01-31 16:46:02 +00:00
James Turner
4d4e474464 Initial prototype of knob animation. 2013-01-31 00:15:09 +00:00
James Turner
a58b41e7d2 Pick animation moved to its own file.
Preparatory work for adding more pick animations which will share some
internals with this code.
2013-01-30 21:51:41 +00:00
Mathias Froehlich
4db05e97c5 spt: The bucket size case is already handled above. 2013-01-29 07:01:58 +01:00
Mathias Froehlich
3a668232c8 spt: Introduce a third layer of paged nodes. 2013-01-29 07:01:50 +01:00
Eric van den Berg
d2622a5d86 added some physical constants to constants.h
goes with fgfs merge request #38
2013-01-28 16:56:07 +00:00
James Turner
d11db8a67a Prepare for optionally embedded SimGear. 2013-01-26 15:31:17 +01:00
James Turner
98b6102e56 State-machine correctness tweaks. 2013-01-25 12:05:16 +01:00
James Turner
0db8613a21 StateMachine tweaks. 2013-01-25 09:29:31 +01:00
James Turner
94a6cb2cff Tolerate lack of C99 in MSVC. 2013-01-23 16:01:46 +01:00
James Turner
5b92575ed3 Fix state-machine test linkage with static libs.
Thanks to Thomas for the catch.
2013-01-22 20:56:57 +01:00
Thomas Geymayer
47b02c0480 Missing include 2013-01-22 19:20:41 +01:00
James Turner
691be54ca2 Keep Linux happy. 2013-01-22 18:53:51 +01:00
James Turner
22ea8ebe25 State-machine structure, initial work. 2013-01-22 18:08:44 +01:00
James Turner
bd71635c49 Add left/right Nasal library functions. 2013-01-22 18:06:50 +01:00
James Turner
830bc3eac3 More native math functions in Nasal. 2013-01-22 18:06:50 +01:00
Mathias Froehlich
2d72bf4308 spt: Put the sea level textured tiles also under a transform node. 2013-01-20 15:33:25 +01:00
Mathias Froehlich
f4e694afa7 spt: Consistently return references in the loader. 2013-01-20 15:33:25 +01:00
Mathias Froehlich
55610bae70 spt: Use SGBox to build up the bounding spheres for the tiles. 2013-01-20 15:33:25 +01:00
Mathias Froehlich
08351e0df2 bucket: Make no bucket cross the 0 and 180 deg longitude border.
Change the bucket tiling at the poles not to cross the
0deg longitude and not to cross the 180deg longitude line.
This lets some special cases disappear for the buckets as well
as for the hierarchical level of detail spt loader.
Also change the last degree tiles from spanning 360 degrees to
12 degrees. Before we had 8 nested rings which neither helps
scenery paging nor culling. With that change the chunks are
about the same order of size than any other tile. Also the
tile shapes are much more friendly for culling and paging.
Also in presence of the current scenery, this polar tiles contain
as of today just totally broken geometry which tells me that
also the scenery generation process did not like these rings
for the tile shapes.

The impact on the current scenery is low. With this change the
polar regions do no longer load the tiles that are available
in the current scenery builds. The last degree tiles are broken
in this scenery version anyway. The next degree that is changed
will really loose some tiles that were sensible as of today.
All these areas are replaced with sea tiles by the old paging
structure. So, you will have at least ground below you when you
fly there. A hopefully comming soon scenery rebuild will
use this new tiling structure and thus will be the first one
providing closed polar caps.
2013-01-20 15:33:25 +01:00
Torsten Dreyer
7489771ad2 Bump version to 2.11.0 2013-01-17 19:22:07 +01:00
Thomas Geymayer
ea8023e51f Canvas: Provide sane default bounding box (For Image & Text) 2013-01-09 12:11:19 +01:00
Thomas Geymayer
8a9693a28e Fix canvas mouse intersection test
Bounding box of drawables use other coordinate frame
then bounding sphere of MatrixTransform...
2013-01-05 00:44:19 +01:00
Thomas Geymayer
724fba4af9 canvas::Element: parse full 3x3 matrix
This doesn't change any existing behaviour but allows specifying
full 3x3 matrix to eg. perform a perspective transform needed
for some HUDs.
2013-01-01 14:18:39 +01:00
Thomas Geymayer
beca1cbf96 Allow nasal::Ghost to only wrap classes by their shared pointers
This removes support for exposing and managing classes by just
raw pointers as this made lifetime management of instances
from C++ to Nasal and especially the other way round nearly
impossible. Always using smart pointers saves us from a lot
of headaches.
2012-12-29 17:49:22 +01:00
James Turner
b74d2ae9fa Notify all requests on name lookup failures.
Since there's no active request during a name lookup failure, explicitly fail all sent and queued requests, so the Request subclasses can take regular failure action.
2012-12-29 14:36:38 +00:00
James Turner
ec82a0154b Expose the current members of a subsystem group. 2012-12-28 14:54:00 +00:00
James Turner
0ea8dbea10 Add SGPath to the Nasal conversion helpers. 2012-12-28 14:53:31 +00:00
James Turner
a131f44247 OS-X specific sleep helper, more stable. 2012-12-23 23:30:40 +00:00
Thomas Geymayer
0423aedffc Revert to old way of including everything from SGMath.hxx 2012-12-23 01:30:41 +01:00
James Turner
afc89cdd95 Tweak libSvn usage: fixes a crash on Mac.
Work-around a crash on certain Mac builds (I can't tell why other work fine) where the called parameters to apr_pool_create_ex are problematic. Explicitly pass the default values for the function.
2012-12-22 23:55:37 +00:00
Mathias Froehlich
541606e00d hla: Lower the log level of two alerts.
Trying to interpret the object template in a yet
unimplemented format is not worth an alert.
2012-12-22 10:12:45 +01:00
208 changed files with 17826 additions and 2254 deletions

View File

@@ -21,31 +21,33 @@ if(InSourceBuild)
message(WARNING " mkdir ../sgbuild && cd ../sgbuild && cmake ${CMAKE_SOURCE_DIR}")
endif(InSourceBuild)
#packaging
SET(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/COPYING")
SET(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Simulation support libraries for FlightGear and related projects")
SET(CPACK_PACKAGE_VENDOR "The FlightGear project")
SET(CPACK_GENERATOR "TBZ2")
SET(CPACK_INSTALL_CMAKE_PROJECTS ${CMAKE_CURRENT_BINARY_DIR};SimGear;ALL;/)
if (NOT EMBEDDED_SIMGEAR)
#packaging
SET(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/COPYING")
SET(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Simulation support libraries for FlightGear and related projects")
SET(CPACK_PACKAGE_VENDOR "The FlightGear project")
SET(CPACK_GENERATOR "TBZ2")
SET(CPACK_INSTALL_CMAKE_PROJECTS ${CMAKE_CURRENT_BINARY_DIR};SimGear;ALL;/)
# split version string into components, note CMAKE_MATCH_0 is the entire regexp match
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" CPACK_PACKAGE_VERSION ${SIMGEAR_VERSION} )
set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
# split version string into components, note CMAKE_MATCH_0 is the entire regexp match
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" CPACK_PACKAGE_VERSION ${SIMGEAR_VERSION} )
set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
message(STATUS "version is ${CPACK_PACKAGE_VERSION_MAJOR} dot ${CPACK_PACKAGE_VERSION_MINOR} dot ${CPACK_PACKAGE_VERSION_PATCH}")
message(STATUS "version is ${CPACK_PACKAGE_VERSION_MAJOR} dot ${CPACK_PACKAGE_VERSION_MINOR} dot ${CPACK_PACKAGE_VERSION_PATCH}")
set(CPACK_SOURCE_GENERATOR TBZ2)
set(CPACK_SOURCE_PACKAGE_FILE_NAME "simgear-${SIMGEAR_VERSION}" CACHE INTERNAL "tarball basename")
set(CPACK_SOURCE_IGNORE_FILES
"^${PROJECT_SOURCE_DIR}/.git;\\\\.gitignore;Makefile.am;~$;${CPACK_SOURCE_IGNORE_FILES}")
set(CPACK_SOURCE_GENERATOR TBZ2)
set(CPACK_SOURCE_PACKAGE_FILE_NAME "simgear-${SIMGEAR_VERSION}" CACHE INTERNAL "tarball basename")
set(CPACK_SOURCE_IGNORE_FILES
"^${PROJECT_SOURCE_DIR}/.git;\\\\.gitignore;Makefile.am;~$;${CPACK_SOURCE_IGNORE_FILES}")
message(STATUS "ignoring: ${CPACK_SOURCE_IGNORE_FILES}")
include (CPack)
message(STATUS "ignoring: ${CPACK_SOURCE_IGNORE_FILES}")
include (CPack)
endif()
# We have some custom .cmake scripts not in the official distribution.
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules;${CMAKE_MODULE_PATH}")
@@ -106,13 +108,15 @@ endif()
option(SIMGEAR_HEADLESS "Set to ON to build SimGear without GUI/graphics support" OFF)
option(JPEG_FACTORY "Enable JPEG-factory support" OFF)
option(SG_SVN_CLIENT "Set to ON to build SimGear with built-in SVN support" OFF)
option(ENABLE_LIBSVN "Set to ON to build SimGear with libsvnclient support" ON)
option(ENABLE_RTI "Set to ON to build SimGear with RTI support" OFF)
option(ENABLE_TESTS "Set to OFF to disable building SimGear's test applications" ON)
option(ENABLE_SOUND "Set to OFF to disable building SimGear's sound support" ON)
option(ENABLE_PKGUTIL "Set to ON to build the sg_pkgutil application (default)" ON)
if (MSVC)
GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_SOURCE_DIR} PATH)
GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_BINARY_DIR} PATH)
if (CMAKE_CL_64)
SET(TEST_3RDPARTY_DIR "${PARENT_DIR}/3rdparty.x64")
else (CMAKE_CL_64)
@@ -165,6 +169,10 @@ if (MSVC AND MSVC_3RDPARTY_ROOT)
set (OPENAL_LIBRARY_DIR ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib)
endif (MSVC AND MSVC_3RDPARTY_ROOT)
if(APPLE)
find_library(CORE_SERVICES_LIBRARY CoreServices)
endif()
find_package(Boost REQUIRED)
set (BOOST_CXX_FLAGS "-DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION -DBOOST_BIMAP_DISABLE_SERIALIZATION")
@@ -191,7 +199,9 @@ else()
message(STATUS "JPEG-factory: DISABLED")
endif(JPEG_FACTORY)
if(ENABLE_LIBSVN)
if (SG_SVN_CLIENT)
message(STATUS "Using built-in subversion client code")
elseif(ENABLE_LIBSVN)
find_package(SvnClient)
if(LIBSVN_FOUND)
@@ -206,7 +216,7 @@ if(ENABLE_LIBSVN)
endif(LIBSVN_FOUND)
else()
message(STATUS "Subversion client support: DISABLED")
endif(ENABLE_LIBSVN)
endif(SG_SVN_CLIENT)
find_package(ZLIB REQUIRED)
find_package(Threads REQUIRED)
@@ -314,7 +324,10 @@ if(WIN32)
# SET(WARNING_FLAGS "${WARNING_FLAGS} /wd${warning}")
# endforeach(warning)
set(MSVC_FLAGS "-DWIN32 -DNOMINMAX -D_USE_MATH_DEFINES -D_CRT_SECURE_NO_WARNINGS -D__CRT_NONSTDC_NO_WARNINGS /wd4996 /wd4250")
set(MSVC_FLAGS "-DWIN32 -DNOMINMAX -D_USE_MATH_DEFINES -D_CRT_SECURE_NO_WARNINGS -D__CRT_NONSTDC_NO_WARNINGS /wd4996 /wd4250 -Dstrdup=_strdup")
if (${MSVC_VERSION} GREATER 1599)
set( MSVC_LD_FLAGS "/FORCE:MULTIPLE" )
endif (${MSVC_VERSION} GREATER 1599)
endif(MSVC)
# assumed on Windows
@@ -326,6 +339,7 @@ endif(WIN32)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS_C} ${MSVC_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS_CXX} ${MSVC_FLAGS} ${BOOST_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MSVC_LD_FLAGS}")
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/simgear/canvas/ShivaVG/include)
@@ -333,8 +347,11 @@ include_directories(${PROJECT_BINARY_DIR}/simgear)
include_directories(${PROJECT_BINARY_DIR}/simgear/xml)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR}
${OPENAL_INCLUDE_DIR} )
${Boost_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIR}
${OPENAL_INCLUDE_DIR}
${LibArchive_INCLUDE_DIRS}
)
add_definitions(-DHAVE_CONFIG_H)
@@ -355,14 +372,37 @@ if(ENABLE_TESTS)
message(STATUS "Tests: ENABLED")
include (Dart)
enable_testing()
enable_testing()
else()
message(STATUS "Tests: DISABLED")
endif(ENABLE_TESTS)
# always set TEST_LIBS as it is also used by other tools/applications
# TODO maybe better rename?
if(SIMGEAR_SHARED)
set( TEST_LIBS
SimGearCore)
else()
set( TEST_LIBS
SimGearCore
${CMAKE_THREAD_LIBS_INIT}
${ZLIB_LIBRARY}
${WINSOCK_LIBRARY}
${RT_LIBRARY}
${CORE_SERVICES_LIBRARY})
endif()
if(NOT SIMGEAR_HEADLESS)
set( TEST_LIBS
SimGearScene
${TEST_LIBS}
${OPENGL_LIBRARIES})
endif()
install (FILES ${PROJECT_BINARY_DIR}/simgear/simgear_config.h DESTINATION include/simgear/)
add_subdirectory(simgear)
if (NOT EMBEDDED_SIMGEAR)
#-----------------------------------------------------------------------------
### uninstall target
#-----------------------------------------------------------------------------
@@ -372,3 +412,6 @@ CONFIGURE_FILE(
IMMEDIATE @ONLY)
ADD_CUSTOM_TARGET(uninstall
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
endif()

View File

@@ -22,7 +22,7 @@ PROJECT_NAME = SimGear
# This could be handy for archiving the generated documentation or
# if some version control system is used.
PROJECT_NUMBER = 2.2.0
PROJECT_NUMBER = 2.11.0
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put.
@@ -56,7 +56,7 @@ EXTRACT_PRIVATE = NO
# If the EXTRACT_STATIC tag is set to YES all static members of a file
# will be included in the documentation.
EXTRACT_STATIC = NO
EXTRACT_STATIC = YES
# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
# undocumented members of documented classes, files or namespaces.

View File

@@ -18,6 +18,7 @@ foreach( mylibfolder
threads
timing
xml
package
)
add_subdirectory(${mylibfolder})
@@ -63,8 +64,10 @@ if(SIMGEAR_SHARED)
set_property(TARGET SimGearCore PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
target_link_libraries(SimGearCore ${ZLIB_LIBRARY} ${RT_LIBRARY}
${LibArchive_LIBRARIES}
${EXPAT_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT})
${CMAKE_THREAD_LIBS_INIT}
${CORE_SERVICES_LIBRARY})
if(LIBSVN_FOUND)
target_link_libraries(SimGearCore ${LIBSVN_LIBRARIES})

View File

@@ -224,17 +224,6 @@ std::string SGBucket::gen_base_path() const {
// return width of the tile in degrees
double SGBucket::get_width() const {
if (lon==-180 && (lat==-89 || lat==88) ) {
/* Normally the tile at 180W in 88N and 89S
* would cover 184W to 176W and the next
* on the east side starts at 176W.
* To correct, make this a special tile
* from 180W to 176W with 4 degrees width
* instead of the normal 8 degrees at
* that latitude.
*/
return 4.0;
}
return sg_bucket_span( get_center_lat() );
}
@@ -356,4 +345,13 @@ void sgBucketDiff( const SGBucket& b1, const SGBucket& b2, int *dx, int *dy ) {
#endif
}
void sgGetBuckets( const SGGeod& min, const SGGeod& max, std::vector<SGBucket>& list ) {
double lon, lat, span;
for (lat = min.getLatitudeDeg(); lat <= max.getLatitudeDeg(); lat += SG_BUCKET_SPAN) {
span = sg_bucket_span( lat );
for (lon = min.getLongitudeDeg(); lon <= max.getLongitudeDeg(); lon += span) {
list.push_back( SGBucket(lon , lat) );
}
}
}

View File

@@ -42,6 +42,7 @@
#include <cstdio> // sprintf()
#include <ostream>
#include <string>
#include <vector>
/**
* standard size of a bucket in degrees (1/8 of a degree)
@@ -57,9 +58,7 @@
// return the horizontal tile span factor based on latitude
static double sg_bucket_span( double l ) {
if ( l >= 89.0 ) {
return 360.0;
} else if ( l >= 88.0 ) {
return 8.0;
return 12.0;
} else if ( l >= 86.0 ) {
return 4.0;
} else if ( l >= 83.0 ) {
@@ -80,12 +79,10 @@ static double sg_bucket_span( double l ) {
return 1.0;
} else if ( l >= -86.0 ) {
return 2.0;
} else if ( l >= -88.0 ) {
return 4.0;
} else if ( l >= -89.0 ) {
return 8.0;
return 4.0;
} else {
return 360.0;
return 12.0;
}
}
@@ -326,6 +323,15 @@ SGBucket sgBucketOffset( double dlon, double dlat, int x, int y );
void sgBucketDiff( const SGBucket& b1, const SGBucket& b2, int *dx, int *dy );
/**
* \relates SGBucket
* retrieve a list of buckets in the given bounding box
* @param min min lon,lat of bounding box in degrees
* @param max max lon,lat of bounding box in degrees
* @param list standard vector of buckets within the bounding box
*/
void sgGetBuckets( const SGGeod& min, const SGGeod& max, std::vector<SGBucket>& list );
/**
* Write the bucket lon, lat, x, and y to the output stream.
* @param out output stream

View File

@@ -9,6 +9,7 @@ set(HEADERS
CanvasEventTypes.hxx
CanvasEventVisitor.hxx
CanvasMgr.hxx
CanvasObjectPlacement.hxx
CanvasPlacement.hxx
CanvasSystemAdapter.hxx
MouseEvent.hxx
@@ -23,6 +24,7 @@ set(SOURCES
CanvasEventManager.cxx
CanvasEventVisitor.cxx
CanvasMgr.cxx
CanvasObjectPlacement.cxx
CanvasPlacement.cxx
ODGauge.cxx
VGInitOperation.cxx

View File

@@ -31,7 +31,6 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/foreach.hpp>
#include <iostream>
namespace simgear
{
@@ -76,9 +75,13 @@ namespace canvas
}
//----------------------------------------------------------------------------
Canvas::~Canvas()
void Canvas::onDestroy()
{
if( _root_group )
{
_root_group->clearEventListener();
_root_group->onDestroy();
}
}
//----------------------------------------------------------------------------
@@ -107,7 +110,13 @@ namespace canvas
}
//----------------------------------------------------------------------------
void Canvas::addDependentCanvas(const CanvasWeakPtr& canvas)
bool Canvas::isInit() const
{
return _texture.serviceable();
}
//----------------------------------------------------------------------------
void Canvas::addParentCanvas(const CanvasWeakPtr& canvas)
{
if( canvas.expired() )
{
@@ -115,28 +124,67 @@ namespace canvas
(
SG_GENERAL,
SG_WARN,
"Canvas::addDependentCanvas: got an expired Canvas dependent on "
<< _node->getPath()
"Canvas::addParentCanvas(" << _node->getPath(true) << "): "
"got an expired parent!"
);
return;
}
_dependent_canvases.insert(canvas);
_parent_canvases.insert(canvas);
}
//----------------------------------------------------------------------------
void Canvas::removeDependentCanvas(const CanvasWeakPtr& canvas)
void Canvas::addChildCanvas(const CanvasWeakPtr& canvas)
{
_dependent_canvases.erase(canvas);
if( canvas.expired() )
{
SG_LOG
(
SG_GENERAL,
SG_WARN,
"Canvas::addChildCanvas(" << _node->getPath(true) << "): "
" got an expired child!"
);
return;
}
_child_canvases.insert(canvas);
}
//----------------------------------------------------------------------------
void Canvas::removeParentCanvas(const CanvasWeakPtr& canvas)
{
_parent_canvases.erase(canvas);
}
//----------------------------------------------------------------------------
void Canvas::removeChildCanvas(const CanvasWeakPtr& canvas)
{
_child_canvases.erase(canvas);
}
//----------------------------------------------------------------------------
GroupPtr Canvas::createGroup(const std::string& name)
{
return boost::dynamic_pointer_cast<Group>
(
_root_group->createChild("group", name)
);
return _root_group->createChild<Group>(name);
}
//----------------------------------------------------------------------------
GroupPtr Canvas::getGroup(const std::string& name)
{
return _root_group->getChild<Group>(name);
}
//----------------------------------------------------------------------------
GroupPtr Canvas::getOrCreateGroup(const std::string& name)
{
return _root_group->getOrCreateChild<Group>(name);
}
//----------------------------------------------------------------------------
GroupPtr Canvas::getRootGroup()
{
return _root_group;
}
//----------------------------------------------------------------------------
@@ -168,11 +216,7 @@ namespace canvas
{
// Resizing causes a new texture to be created so we need to reapply all
// existing placements
for(size_t i = 0; i < _placements.size(); ++i)
{
if( !_placements[i].empty() )
_dirty_placements.push_back( _placements[i].front()->getProps() );
}
reloadPlacements();
}
osg::Camera* camera = _texture.getCamera();
@@ -201,10 +245,18 @@ namespace canvas
if( _visible || _render_always )
{
BOOST_FOREACH(CanvasWeakPtr canvas, _child_canvases)
{
// TODO should we check if the image the child canvas is displayed
// within is really visible?
if( !canvas.expired() )
canvas.lock()->_visible = true;
}
if( _render_dirty )
{
// Also mark all dependent (eg. recursively used) canvases as dirty
BOOST_FOREACH(CanvasWeakPtr canvas, _dependent_canvases)
// Also mark all canvases this canvas is displayed within as dirty
BOOST_FOREACH(CanvasWeakPtr canvas, _parent_canvases)
{
if( !canvas.expired() )
canvas.lock()->_render_dirty = true;
@@ -344,13 +396,19 @@ namespace canvas
//----------------------------------------------------------------------------
int Canvas::getViewWidth() const
{
return _view_width;
return _texture.getViewSize().x();
}
//----------------------------------------------------------------------------
int Canvas::getViewHeight() const
{
return _view_height;
return _texture.getViewSize().y();
}
//----------------------------------------------------------------------------
SGRect<int> Canvas::getViewport() const
{
return SGRect<int>(0, 0, getViewWidth(), getViewHeight());
}
//----------------------------------------------------------------------------
@@ -408,21 +466,26 @@ namespace canvas
if( node->getParent()->getParent() == _node
&& node->getParent()->getNameString() == "placement" )
{
bool placement_dirty = false;
BOOST_FOREACH(Placements& placements, _placements)
size_t index = node->getIndex();
if( index < _placements.size() )
{
BOOST_FOREACH(PlacementPtr& placement, placements)
Placements& placements = _placements[index];
if( !placements.empty() )
{
// check if change can be directly handled by placement
if( placement->getProps() == node->getParent()
&& !placement->childChanged(node) )
placement_dirty = true;
bool placement_dirty = false;
BOOST_FOREACH(PlacementPtr& placement, placements)
{
// check if change can be directly handled by placement
if( placement->getProps() == node->getParent()
&& !placement->childChanged(node) )
placement_dirty = true;
}
if( !placement_dirty )
return;
}
}
if( !placement_dirty )
return;
// prevent double updates...
for( size_t i = 0; i < _dirty_placements.size(); ++i )
{
@@ -449,6 +512,10 @@ namespace canvas
{
_sampling_dirty = true;
}
else if( node->getNameString() == "additive-blend" )
{
_texture.useAdditiveBlend( node->getBoolValue() );
}
else if( node->getNameString() == "render-always" )
{
_render_always = node->getBoolValue();
@@ -491,6 +558,24 @@ namespace canvas
return _cull_callback;
}
//----------------------------------------------------------------------------
void Canvas::reloadPlacements(const std::string& type)
{
for(size_t i = 0; i < _placements.size(); ++i)
{
if( _placements[i].empty() )
continue;
SGPropertyNode* child = _placements[i].front()->getProps();
if( type.empty()
// reload if type matches or no type specified
|| child->getStringValue("type", type.c_str()) == type )
{
_dirty_placements.push_back(child);
}
}
}
//----------------------------------------------------------------------------
void Canvas::addPlacementFactory( const std::string& type,
PlacementFactory factory )

View File

@@ -23,6 +23,7 @@
#include "ODGauge.hxx"
#include <simgear/canvas/elements/CanvasGroup.hxx>
#include <simgear/math/SGRect.hxx>
#include <simgear/props/PropertyBasedElement.hxx>
#include <simgear/props/propertyObject.hxx>
#include <osg/NodeCallback>
@@ -70,7 +71,7 @@ namespace canvas
typedef osg::ref_ptr<CullCallback> CullCallbackPtr;
Canvas(SGPropertyNode* node);
virtual ~Canvas();
virtual void onDestroy();
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
SystemAdapterPtr getSystemAdapter() const;
@@ -78,22 +79,49 @@ namespace canvas
void setCanvasMgr(CanvasMgr* canvas_mgr);
CanvasMgr* getCanvasMgr() const;
bool isInit() const;
/**
* Add a canvas which should be mared as dirty upon any change to this
* Add a canvas which should be marked as dirty upon any change to this
* canvas.
*
* This mechanism is used to eg. redraw a canvas if it's displaying
* another canvas (recursive canvases)
*/
void addDependentCanvas(const CanvasWeakPtr& canvas);
void addParentCanvas(const CanvasWeakPtr& canvas);
/**
* Add a canvas which should be marked visible if this canvas is visible.
*/
void addChildCanvas(const CanvasWeakPtr& canvas);
/**
* Stop notifying the given canvas upon changes
*/
void removeDependentCanvas(const CanvasWeakPtr& canvas);
void removeParentCanvas(const CanvasWeakPtr& canvas);
void removeChildCanvas(const CanvasWeakPtr& canvas);
/**
* Create a new group
*/
GroupPtr createGroup(const std::string& name = "");
/**
* Get an existing group with the given name
*/
GroupPtr getGroup(const std::string& name);
/**
* Get an existing group with the given name or otherwise create a new
* group
*/
GroupPtr getOrCreateGroup(const std::string& name);
/**
* Get the root group of the canvas
*/
GroupPtr getRootGroup();
/**
* Enable rendering for the next frame
*
@@ -117,6 +145,7 @@ namespace canvas
int getViewWidth() const;
int getViewHeight() const;
SGRect<int> getViewport() const;
bool handleMouseEvent(const MouseEventPtr& event);
@@ -130,6 +159,7 @@ namespace canvas
CullCallbackPtr getCullCallback() const;
void reloadPlacements( const std::string& type = std::string() );
static void addPlacementFactory( const std::string& type,
PlacementFactory factory );
@@ -160,9 +190,9 @@ namespace canvas
std::vector<SGPropertyNode*> _dirty_placements;
std::vector<Placements> _placements;
std::set<CanvasWeakPtr> _dependent_canvases; //<! Canvases which use this
// canvas and should be
// notified about changes
std::set<CanvasWeakPtr> _parent_canvases, //<! Canvases showing this canvas
_child_canvases; //<! Canvases displayed within
// this canvas
typedef std::map<std::string, PlacementFactory> PlacementFactoryMap;
static PlacementFactoryMap _placement_factories;

View File

@@ -157,21 +157,55 @@ namespace canvas
void EventManager::handleMove( const MouseEventPtr& event,
const EventPropagationPath& path )
{
if( _last_mouse_over.path == path )
EventPropagationPath& last_path = _last_mouse_over.path;
if( last_path == path )
return;
if( !_last_mouse_over.path.empty() )
// Leave old element
if( !last_path.empty() )
{
MouseEventPtr mouseout(new MouseEvent(*event));
mouseout->type = Event::MOUSE_OUT;
propagateEvent(mouseout, _last_mouse_over.path);
propagateEvent(mouseout, last_path);
// Send a mouseleave event to all ancestors of the currently left element
// which are not ancestor of the new element currently entered
EventPropagationPath path_leave = last_path;
for(size_t i = path_leave.size() - 1; i > 0; --i)
{
if( i < path.size() && path[i] == path_leave[i] )
break;
MouseEventPtr mouseleave(new MouseEvent(*event));
mouseleave->type = Event::MOUSE_LEAVE;
propagateEvent(mouseleave, path_leave);
path_leave.pop_back();
}
}
// Enter new element
if( !path.empty() )
{
MouseEventPtr mouseover(new MouseEvent(*event));
mouseover->type = Event::MOUSE_OVER;
propagateEvent(mouseover, path);
// Send a mouseenter event to all ancestors of the currently entered
// element which are not ancestor of the old element currently being
// left
EventPropagationPath path_enter;
for(size_t i = 0; i < path.size(); ++i)
{
path_enter.push_back(path[i]);
if( i < last_path.size() && path[i] == last_path[i] )
continue;
MouseEventPtr mouseenter(new MouseEvent(*event));
mouseenter->type = Event::MOUSE_ENTER;
propagateEvent(mouseenter, path_enter);
}
}
_last_mouse_over.path = path;
@@ -188,13 +222,12 @@ namespace canvas
// http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
// Capturing phase
// for( EventTargets::iterator it = _target_path.begin();
// it != _target_path.end();
// ++it )
// for( EventPropagationPath::const_iterator it = path.begin();
// it != path.end();
// ++it )
// {
// if( it->element )
// std::cout << it->element->getProps()->getPath() << " "
// << "(" << it->local_pos.x() << "|" << it->local_pos.y() << ")\n";
// if( !it->element.expired() )
// std::cout << it->element.lock()->getProps()->getPath() << std::endl;
// }
// Bubbling phase
@@ -210,23 +243,22 @@ namespace canvas
// (eg. removed by another event handler)
continue;
// TODO provide functions to convert position and delta to local
// coordinates on demand. Events shouldn't contain informations in
// local coordinates as they might differe between different elements
// receiving the same event.
// if( mouse_event && event->type != Event::DRAG )
// {
// // TODO transform pos and delta for drag events. Maybe we should just
// // store the global coordinates and convert to local coordinates
// // on demand.
//
// // Position and delta are specified in local coordinate system of
// // current element
// mouse_event->pos = it->local_pos;
// mouse_event->delta = it->local_delta;
// }
// TODO provide functions to convert delta to local coordinates on demand.
// Maybe also provide a clone method for events as local coordinates
// might differ between different elements receiving the same event.
if( mouse_event ) //&& event->type != Event::DRAG )
{
// TODO transform pos and delta for drag events. Maybe we should just
// store the global coordinates and convert to local coordinates
// on demand.
el->callListeners(event);
// Position and delta are specified in local coordinate system of
// current element
mouse_event->local_pos = it->local_pos;
//mouse_event->delta = it->local_delta;
}
el->handleEvent(event);
if( event->propagation_stopped )
return true;
@@ -250,6 +282,9 @@ namespace canvas
EventManager::getCommonAncestor( const EventPropagationPath& path1,
const EventPropagationPath& path2 ) const
{
if( path1.empty() || path2.empty() )
return EventPropagationPath();
if( path1.back().element.lock() == path2.back().element.lock() )
return path2;

View File

@@ -73,7 +73,7 @@ namespace canvas
// Don't check collision with root element (2nd element in _target_path)
// do event listeners attached to the canvas itself (its root group)
// always get called even if no element has been hit.
if( _target_path.size() > 2 && !el.hitBound(local_pos) )
if( _target_path.size() > 1 && !el.hitBound(pos, local_pos) )
return false;
const osg::Vec2f& delta = _target_path.back().local_delta;

View File

@@ -0,0 +1,244 @@
// Canvas placement for placing a canvas texture onto osg objects.
//
// It also provides a SGPickCallback for passing mouse events to the canvas and
// manages emissive lighting of the placed canvas.
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "Canvas.hxx"
#include "CanvasObjectPlacement.hxx"
#include "MouseEvent.hxx"
#include <simgear/props/props.hxx>
#include <simgear/scene/util/SGPickCallback.hxx>
#include <osgGA/GUIEventAdapter>
namespace simgear
{
namespace canvas
{
/**
* Handle picking events on object with a canvas placed onto
*/
class ObjectPickCallback:
public SGPickCallback
{
public:
ObjectPickCallback(const CanvasWeakPtr& canvas):
_canvas(canvas)
{}
virtual bool needsUV() const { return true; }
virtual bool buttonPressed( int,
const osgGA::GUIEventAdapter& ea,
const Info& info )
{
MouseEventPtr event(new MouseEvent(ea));
updatePosFromUV(event, info.uv);
if( ea.getEventType() == osgGA::GUIEventAdapter::SCROLL )
{
event->type = Event::WHEEL;
event->delta.set(0,0);
switch( ea.getScrollingMotion() )
{
case osgGA::GUIEventAdapter::SCROLL_UP:
event->delta.y() = 1;
break;
case osgGA::GUIEventAdapter::SCROLL_DOWN:
event->delta.y() = -1;
break;
default:
return false;
}
}
else
{
event->type = Event::MOUSE_DOWN;
}
return handleEvent(event);
}
virtual void buttonReleased( int,
const osgGA::GUIEventAdapter& ea,
const Info* info )
{
if( ea.getEventType() != osgGA::GUIEventAdapter::RELEASE )
return;
MouseEventPtr event(new MouseEvent(ea));
event->type = Event::MOUSE_UP;
updatePosFromUV(event, info ? info->uv : SGVec2d(-1,-1));
handleEvent(event);
}
virtual void mouseMoved( const osgGA::GUIEventAdapter& ea,
const Info* info )
{
// drag (currently only with LMB)
if( ea.getEventType() != osgGA::GUIEventAdapter::DRAG )
return;
MouseEventPtr event(new MouseEvent(ea));
event->type = Event::DRAG;
updatePosFromUV(event, info ? info->uv : SGVec2d(-1,-1));
handleEvent(event);
}
virtual bool hover( const osg::Vec2d& windowPos,
const Info& info )
{
// TODO somehow get more info about event (time, modifiers, pressed
// buttons, ...)
MouseEventPtr event(new MouseEvent);
event->type = Event::MOUSE_MOVE;
event->screen_pos = windowPos;
updatePosFromUV(event, info.uv);
return handleEvent(event);
}
virtual void mouseLeave( const osg::Vec2d& windowPos )
{
MouseEventPtr event(new MouseEvent);
event->type = Event::MOUSE_LEAVE;
event->screen_pos = windowPos;
handleEvent(event);
}
protected:
CanvasWeakPtr _canvas;
osg::Vec2f _last_pos,
_last_delta;
void updatePosFromUV(const MouseEventPtr& event, const SGVec2d& uv)
{
CanvasPtr canvas = _canvas.lock();
if( !canvas )
return;
osg::Vec2d pos( uv.x() * canvas->getViewWidth(),
(1 - uv.y()) * canvas->getViewHeight() );
_last_delta = pos - _last_pos;
_last_pos = pos;
event->client_pos = pos;
event->delta = _last_delta;
}
bool handleEvent(const MouseEventPtr& event)
{
CanvasPtr canvas = _canvas.lock();
if( !canvas )
return false;
return canvas->handleMouseEvent(event);
}
};
//----------------------------------------------------------------------------
ObjectPlacement::ObjectPlacement( SGPropertyNode* node,
const GroupPtr& group,
const CanvasWeakPtr& canvas ):
Placement(node),
_group(group),
_canvas(canvas)
{
// TODO make more generic and extendable for more properties
if( node->hasValue("emission") )
setEmission( node->getFloatValue("emission") );
if( node->hasValue("capture-events") )
setCaptureEvents( node->getBoolValue("capture-events") );
}
//----------------------------------------------------------------------------
ObjectPlacement::~ObjectPlacement()
{
assert( _group->getNumChildren() == 1 );
osg::Node *child = _group->getChild(0);
if( _group->getNumParents() )
{
osg::Group *parent = _group->getParent(0);
parent->addChild(child);
parent->removeChild(_group);
}
_group->removeChild(child);
}
//----------------------------------------------------------------------------
void ObjectPlacement::setEmission(float emit)
{
emit = SGMiscf::clip(emit, 0, 1);
if( !_material )
{
_material = new osg::Material;
_material->setColorMode(osg::Material::OFF);
_material->setDataVariance(osg::Object::DYNAMIC);
_group->getOrCreateStateSet()
->setAttribute(_material, ( osg::StateAttribute::ON
| osg::StateAttribute::OVERRIDE ) );
}
_material->setEmission(
osg::Material::FRONT_AND_BACK,
osg::Vec4(emit, emit, emit, emit)
);
}
//----------------------------------------------------------------------------
void ObjectPlacement::setCaptureEvents(bool enable)
{
if( !enable && _scene_user_data )
return;
if( enable && !_pick_cb )
_pick_cb = new ObjectPickCallback(_canvas);
_scene_user_data = SGSceneUserData::getOrCreateSceneUserData(_group);
_scene_user_data->setPickCallback(enable ? _pick_cb.get() : 0);
}
//----------------------------------------------------------------------------
bool ObjectPlacement::childChanged(SGPropertyNode* node)
{
if( node->getParent() != _node )
return false;
if( node->getNameString() == "emission" )
setEmission( node->getFloatValue() );
else if( node->getNameString() == "capture-events" )
setCaptureEvents( node->getBoolValue() );
else
return false;
return true;
}
} // namespace canvas
} // namespace simgear

View File

@@ -0,0 +1,75 @@
// Canvas placement for placing a canvas texture onto osg objects.
//
// It also provides a SGPickCallback for passing mouse events to the canvas and
// manages emissive lighting of the placed canvas.
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef CANVAS_PICK_PLACEMENT_HXX_
#define CANVAS_OBJECT_PLACEMENT_HXX_
#include "CanvasPlacement.hxx"
#include "canvas_fwd.hxx"
#include <simgear/scene/util/SGSceneUserData.hxx>
#include <osg/Material>
namespace simgear
{
namespace canvas
{
class ObjectPlacement:
public Placement
{
public:
typedef osg::ref_ptr<osg::Group> GroupPtr;
typedef osg::ref_ptr<osg::Material> MaterialPtr;
ObjectPlacement( SGPropertyNode* node,
const GroupPtr& group,
const CanvasWeakPtr& canvas );
virtual ~ObjectPlacement();
/**
* Set emissive lighting of the object the canvas is placed on.
*/
void setEmission(float emit);
/**
* Set whether pick events should be captured.
*/
void setCaptureEvents(bool enable);
virtual bool childChanged(SGPropertyNode* child);
protected:
typedef SGSharedPtr<SGPickCallback> PickCallbackPtr;
typedef osg::ref_ptr<SGSceneUserData> SGSceneUserDataPtr;
GroupPtr _group;
MaterialPtr _material;
CanvasWeakPtr _canvas;
PickCallbackPtr _pick_cb;
SGSceneUserDataPtr _scene_user_data;
};
} // namespace canvas
} // namespace simgear
#endif /* CANVAS_PICK_PLACEMENT_HXX_ */

View File

@@ -38,8 +38,18 @@ namespace canvas
click_count(0)
{}
MouseEvent(const osgGA::GUIEventAdapter& ea):
button(ea.getButton()),
state(ea.getButtonMask()),
mod(ea.getModKeyMask()),
click_count(0)
{
time = ea.getTime();
}
osg::Vec2f getScreenPos() const { return screen_pos; }
osg::Vec2f getClientPos() const { return client_pos; }
osg::Vec2f getLocalPos() const { return local_pos; }
osg::Vec2f getDelta() const { return delta; }
float getScreenX() const { return screen_pos.x(); }
@@ -48,6 +58,9 @@ namespace canvas
float getClientX() const { return client_pos.x(); }
float getClientY() const { return client_pos.y(); }
float getLocalX() const { return local_pos.x(); }
float getLocalY() const { return local_pos.y(); }
float getDeltaX() const { return delta.x(); }
float getDeltaY() const { return delta.y(); }
@@ -55,6 +68,7 @@ namespace canvas
osg::Vec2f screen_pos, //<! Position in screen coordinates
client_pos, //<! Position in window/canvas coordinates
local_pos, //<! Position in local/element coordinates
delta;
int button, //<! Button for this event
state, //<! Current button state

View File

@@ -57,12 +57,9 @@ namespace canvas
_size_y( -1 ),
_view_width( -1 ),
_view_height( -1 ),
_use_image_coords( false ),
_use_stencil( false ),
_use_mipmapping( false ),
_flags(0),
_coverage_samples( 0 ),
_color_samples( 0 ),
rtAvailable( false )
_color_samples( 0 )
{
}
@@ -101,42 +98,43 @@ namespace canvas
updateCoordinateFrame();
}
//----------------------------------------------------------------------------
osg::Vec2s ODGauge::getViewSize() const
{
return osg::Vec2s(_view_width, _view_height);
}
//----------------------------------------------------------------------------
void ODGauge::useImageCoords(bool use)
{
if( use == _use_image_coords )
return;
_use_image_coords = use;
if( texture )
if( updateFlag(USE_IMAGE_COORDS, use) && texture )
updateCoordinateFrame();
}
//----------------------------------------------------------------------------
void ODGauge::useStencil(bool use)
{
if( use == _use_stencil )
return;
_use_stencil = use;
if( texture )
if( updateFlag(USE_STENCIL, use) && texture )
updateStencil();
}
//----------------------------------------------------------------------------
void ODGauge::setSampling( bool mipmapping,
int coverage_samples,
int color_samples )
void ODGauge::useAdditiveBlend(bool use)
{
if( _use_mipmapping == mipmapping
if( updateFlag(USE_ADDITIVE_BLEND, use) && camera )
updateBlendMode();
}
//----------------------------------------------------------------------------
void ODGauge::setSampling( bool mipmapping,
int coverage_samples,
int color_samples )
{
if( !updateFlag(USE_MIPMAPPING, mipmapping)
&& _coverage_samples == coverage_samples
&& _color_samples == color_samples )
return;
_use_mipmapping = mipmapping;
if( color_samples > coverage_samples )
{
SG_LOG
@@ -162,9 +160,9 @@ namespace canvas
}
//----------------------------------------------------------------------------
bool ODGauge::serviceable(void)
bool ODGauge::serviceable() const
{
return rtAvailable;
return _flags & AVAILABLE;
}
//----------------------------------------------------------------------------
@@ -196,26 +194,25 @@ namespace canvas
osg::PolygonMode::FILL ),
osg::StateAttribute::ON );
stateSet->setAttributeAndModes(
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.0f),
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.001f),
osg::StateAttribute::ON );
stateSet->setAttribute(new osg::ShadeModel(osg::ShadeModel::FLAT));
stateSet->setAttributeAndModes(
new osg::BlendFunc( osg::BlendFunc::SRC_ALPHA,
osg::BlendFunc::ONE_MINUS_SRC_ALPHA),
osg::StateAttribute::ON );
if( !texture )
{
texture = new osg::Texture2D;
texture->setResizeNonPowerOfTwoHint(false);
texture->setTextureSize(_size_x, _size_y);
texture->setInternalFormat(GL_RGBA);
}
updateSampling();
updateBlendMode();
if( _system_adapter )
_system_adapter->addCamera(camera.get());
rtAvailable = true;
_flags |= AVAILABLE;
}
//----------------------------------------------------------------------------
@@ -234,7 +231,17 @@ namespace canvas
camera.release();
texture.release();
rtAvailable = false;
_flags &= ~AVAILABLE;
}
//----------------------------------------------------------------------------
bool ODGauge::updateFlag(Flags flag, bool enable)
{
if( bool(_flags & flag) == enable )
return false;
_flags ^= flag;
return true;
}
//----------------------------------------------------------------------------
@@ -249,7 +256,7 @@ namespace canvas
camera->setViewport(0, 0, _size_x, _size_y);
if( _use_image_coords )
if( _flags & USE_IMAGE_COORDS )
camera->setProjectionMatrix(
osg::Matrix::ortho2D(0, _view_width, _view_height, 0)
);
@@ -267,7 +274,7 @@ namespace canvas
GLbitfield mask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;
if( _use_stencil)
if( _flags & USE_STENCIL )
{
camera->attach( osg::Camera::PACKED_DEPTH_STENCIL_BUFFER,
GL_DEPTH_STENCIL_EXT );
@@ -289,18 +296,36 @@ namespace canvas
texture->setFilter(
osg::Texture2D::MIN_FILTER,
_use_mipmapping ? osg::Texture2D::LINEAR_MIPMAP_LINEAR
: osg::Texture2D::LINEAR
(_flags & USE_MIPMAPPING) ? osg::Texture2D::LINEAR_MIPMAP_LINEAR
: osg::Texture2D::LINEAR
);
camera->attach(
osg::Camera::COLOR_BUFFER,
texture.get(),
0, 0,
_use_mipmapping,
_flags & USE_MIPMAPPING,
_coverage_samples,
_color_samples
);
}
//----------------------------------------------------------------------------
void ODGauge::updateBlendMode()
{
assert( camera );
camera->getOrCreateStateSet()
->setAttributeAndModes
(
(_flags & USE_ADDITIVE_BLEND)
? new osg::BlendFunc( osg::BlendFunc::SRC_ALPHA,
osg::BlendFunc::ONE_MINUS_SRC_ALPHA,
osg::BlendFunc::ONE,
osg::BlendFunc::ONE )
: new osg::BlendFunc( osg::BlendFunc::SRC_ALPHA,
osg::BlendFunc::ONE_MINUS_SRC_ALPHA )
);
}
} // namespace canvas
} // namespace simgear

View File

@@ -71,6 +71,8 @@ namespace canvas
*/
void setViewSize(int width, int height = -1);
osg::Vec2s getViewSize() const;
/**
* DEPRECATED
*
@@ -91,6 +93,12 @@ namespace canvas
*/
void useStencil(bool use = true);
/**
* Enable/Disable additive alpha blending (Can improve results with
* transparent background)
*/
void useAdditiveBlend(bool use = true);
/**
* Set sampling parameters for mipmapping and coverage sampling
* antialiasing.
@@ -112,7 +120,7 @@ namespace canvas
* Say if we can render to a texture.
* @return true if rtt is available
*/
bool serviceable(void);
bool serviceable() const;
/**
* Get the OSG camera for drawing this gauge.
@@ -134,21 +142,31 @@ namespace canvas
_size_y,
_view_width,
_view_height;
bool _use_image_coords,
_use_stencil,
_use_mipmapping;
enum Flags
{
AVAILABLE = 1,
USE_IMAGE_COORDS = AVAILABLE << 1,
USE_STENCIL = USE_IMAGE_COORDS << 1,
USE_MIPMAPPING = USE_STENCIL << 1,
USE_ADDITIVE_BLEND = USE_MIPMAPPING << 1
};
uint32_t _flags;
// Multisampling parameters
int _coverage_samples,
_color_samples;
bool rtAvailable;
osg::ref_ptr<osg::Camera> camera;
osg::ref_ptr<osg::Texture2D> texture;
bool updateFlag(Flags flag, bool enable);
void updateCoordinateFrame();
void updateStencil();
void updateSampling();
void updateBlendMode();
};

View File

@@ -627,6 +627,7 @@ VG_API_CALL const VGubyte * vgGetString(VGStringID name);
#define OVG_SH_blend_dst_atop 1
VG_API_CALL VGboolean vgCreateContextSH(VGint width, VGint height);
VG_API_CALL VGboolean vgHasContextSH();
VG_API_CALL void vgResizeSurfaceSH(VGint width, VGint height);
VG_API_CALL void vgDestroyContextSH(void);

View File

@@ -61,6 +61,11 @@ VG_API_CALL VGboolean vgCreateContextSH(VGint width, VGint height)
return VG_TRUE;
}
VG_API_CALL VGboolean vgHasContextSH()
{
return g_context != NULL;
}
VG_API_CALL void vgResizeSurfaceSH(VGint width, VGint height)
{
VG_GETCONTEXT(VG_NO_RETVAL);

View File

@@ -6,7 +6,11 @@ set(HEADERS
CanvasImage.hxx
CanvasMap.hxx
CanvasPath.hxx
CanvasText.hxx
CanvasText.hxx
)
set(DETAIL_HEADERS
detail/add_segment_variadic.hxx
)
set(SOURCES
@@ -18,4 +22,5 @@ set(SOURCES
CanvasText.cxx
)
simgear_scene_component(canvas-elements canvas/elements "${SOURCES}" "${HEADERS}")
simgear_scene_component(canvas-elements canvas/elements "${SOURCES}" "${HEADERS}")
simgear_component(canvas-elements/detail canvas/elements/detail "" "${DETAIL_HEADERS}")

View File

@@ -21,6 +21,7 @@
#include <simgear/canvas/CanvasEventListener.hxx>
#include <simgear/canvas/CanvasEventVisitor.hxx>
#include <simgear/canvas/MouseEvent.hxx>
#include <simgear/scene/material/parseBlendFunc.hxx>
#include <osg/Drawable>
#include <osg/Geode>
@@ -42,19 +43,40 @@ namespace canvas
const std::string NAME_TRANSFORM = "tf";
//----------------------------------------------------------------------------
void Element::removeListener()
Element::OSGUserData::OSGUserData(ElementPtr element):
element(element)
{
_node->removeChangeListener(this);
}
//----------------------------------------------------------------------------
Element::~Element()
{
removeListener();
}
//----------------------------------------------------------------------------
void Element::setSelf(const PropertyBasedElementPtr& self)
{
PropertyBasedElement::setSelf(self);
_transform->setUserData
(
new OSGUserData(boost::static_pointer_cast<Element>(self))
);
}
//----------------------------------------------------------------------------
void Element::onDestroy()
{
if( !_transform.valid() )
return;
// The transform node keeps a reference on this element, so ensure it is
// deleted.
BOOST_FOREACH(osg::Group* parent, _transform->getParents())
{
parent->removeChild(_transform);
parent->removeChild(_transform.get());
}
}
@@ -88,14 +110,24 @@ namespace canvas
{
case TT_MATRIX:
tf = osg::Matrix( tf_node->getDoubleValue("m[0]", 1),
tf_node->getDoubleValue("m[1]", 0), 0, 0,
tf_node->getDoubleValue("m[1]", 0),
0,
tf_node->getDoubleValue("m[6]", 0),
tf_node->getDoubleValue("m[2]", 0),
tf_node->getDoubleValue("m[3]", 1), 0, 0,
tf_node->getDoubleValue("m[3]", 1),
0,
tf_node->getDoubleValue("m[7]", 0),
0,
0,
1,
0,
0, 0, 1, 0,
tf_node->getDoubleValue("m[4]", 0),
tf_node->getDoubleValue("m[5]", 0), 0, 1 );
tf_node->getDoubleValue("m[5]", 0),
0,
tf_node->getDoubleValue("m[8]", 1) );
break;
case TT_TRANSLATE:
tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("t[0]", 0),
@@ -120,6 +152,24 @@ namespace canvas
_transform->setMatrix(m);
_transform_dirty = false;
}
// Update bounding box on manual update (manual updates pass zero dt)
if( dt == 0 && _drawable )
_drawable->getBound();
if( _attributes_dirty & BLEND_FUNC )
{
parseBlendFunc(
_transform->getOrCreateStateSet(),
_node->getChild("blend-source"),
_node->getChild("blend-destination"),
_node->getChild("blend-source-rgb"),
_node->getChild("blend-destination-rgb"),
_node->getChild("blend-source-alpha"),
_node->getChild("blend-destination-alpha")
);
_attributes_dirty &= ~BLEND_FUNC;
}
}
//----------------------------------------------------------------------------
@@ -150,9 +200,18 @@ namespace canvas
return naNil();
}
//----------------------------------------------------------------------------
void Element::clearEventListener()
{
_listener.clear();
}
//----------------------------------------------------------------------------
bool Element::accept(EventVisitor& visitor)
{
if( !_transform.valid() )
return false;
return visitor.apply(*this);
}
@@ -171,38 +230,48 @@ namespace canvas
}
//----------------------------------------------------------------------------
void Element::callListeners(const canvas::EventPtr& event)
bool Element::handleEvent(canvas::EventPtr event)
{
ListenerMap::iterator listeners = _listener.find(event->getType());
if( listeners == _listener.end() )
return;
return false;
BOOST_FOREACH(EventListenerPtr listener, listeners->second)
listener->call(event);
}
//----------------------------------------------------------------------------
bool Element::hitBound(const osg::Vec2f& pos) const
{
const osg::Vec3f pos3(pos, 0);
// Drawables have a bounding box...
if( _drawable )
{
if( !_drawable->getBound().contains(pos3) )
return false;
}
// ... for other elements, i.e. groups only a bounding sphere is available
else if( !_transform->getBound().contains(pos3) )
return false;
return true;
}
//----------------------------------------------------------------------------
osg::ref_ptr<osg::MatrixTransform> Element::getMatrixTransform()
bool Element::hitBound( const osg::Vec2f& pos,
const osg::Vec2f& local_pos ) const
{
return _transform;
const osg::Vec3f pos3(pos, 0);
// Drawables have a bounding box...
if( _drawable )
return _drawable->getBound().contains(osg::Vec3f(local_pos, 0));
// ... for other elements, i.e. groups only a bounding sphere is available
else
return _transform->getBound().contains(osg::Vec3f(pos, 0));
}
//----------------------------------------------------------------------------
bool Element::isVisible() const
{
return _transform->getNodeMask() != 0;
}
//----------------------------------------------------------------------------
osg::MatrixTransform* Element::getMatrixTransform()
{
return _transform.get();
}
//----------------------------------------------------------------------------
osg::MatrixTransform const* Element::getMatrixTransform() const
{
return _transform.get();
}
//----------------------------------------------------------------------------
@@ -248,6 +317,9 @@ namespace canvas
{
if( parent == _node && child->getNameString() == NAME_TRANSFORM )
{
if( !_transform.valid() )
return;
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
{
SG_LOG
@@ -277,13 +349,16 @@ namespace canvas
SGPropertyNode *parent = child->getParent();
if( parent == _node )
{
const std::string& name = child->getNameString();
if( setStyle(child) )
return;
else if( child->getNameString() == "update" )
else if( name == "update" )
return update(0);
else if( child->getNameString() == "visible" )
else if( name == "visible" )
// TODO check if we need another nodemask
return _transform->setNodeMask( child->getBoolValue() ? 0xffffffff : 0 );
else if( boost::starts_with(name, "blend-") )
return (void)(_attributes_dirty |= BLEND_FUNC);
}
else if( parent->getParent() == _node
&& parent->getNameString() == NAME_TRANSFORM )
@@ -303,8 +378,14 @@ namespace canvas
if( setter == _style_setters.end() )
return false;
setter->second(child);
return true;
const StyleSetter* style_setter = &setter->second.setter;
while( style_setter )
{
if( style_setter->func(*this, child) )
return true;
style_setter = style_setter->next;
}
return false;
}
//----------------------------------------------------------------------------
@@ -394,9 +475,20 @@ namespace canvas
//----------------------------------------------------------------------------
osg::BoundingBox Element::getTransformedBounds(const osg::Matrix& m) const
{
return osg::BoundingBox();
if( !_drawable )
return osg::BoundingBox();
osg::BoundingBox transformed;
const osg::BoundingBox& bb = _drawable->getBound();
for(int i = 0; i < 4; ++i)
transformed.expandBy( m * bb.corner(i) );
return transformed;
}
//----------------------------------------------------------------------------
Element::StyleSetters Element::_style_setters;
//----------------------------------------------------------------------------
Element::Element( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
@@ -405,6 +497,7 @@ namespace canvas
PropertyBasedElement(node),
_canvas( canvas ),
_parent( parent ),
_attributes_dirty( 0 ),
_transform_dirty( false ),
_transform( new osg::MatrixTransform ),
_style( parent_style ),
@@ -417,7 +510,10 @@ namespace canvas
"New canvas element " << node->getPath()
);
addStyle("clip", &Element::setClip, this);
if( !isInit<Element>() )
{
addStyle("clip", "", &Element::setClip);
}
}
//----------------------------------------------------------------------------

View File

@@ -45,23 +45,42 @@ namespace canvas
public PropertyBasedElement
{
public:
typedef boost::function<void(const SGPropertyNode*)> StyleSetter;
typedef std::map<std::string, StyleSetter> StyleSetters;
/**
* Remove the property listener of the element.
*
* You will need to call the appropriate methods (#childAdded,
* #childRemoved, #valueChanged) yourself to ensure the element still
* receives the needed events.
* Store pointer to window as user data
*/
void removeListener();
class OSGUserData:
public osg::Referenced
{
public:
ElementPtr element;
OSGUserData(ElementPtr element);
};
typedef boost::function<bool(Element&, const SGPropertyNode*)>
StyleSetterFunc;
typedef boost::function<void(Element&, const SGPropertyNode*)>
StyleSetterFuncUnchecked;
struct StyleSetter:
public SGReferenced
{
StyleSetterFunc func;
SGSharedPtr<StyleSetter> next;
};
struct StyleInfo
{
StyleSetter setter; ///!< Function(s) for setting this style
std::string type; ///!< Interpolation type
};
/**
*
*/
virtual ~Element() = 0;
virtual void setSelf(const PropertyBasedElementPtr& self);
virtual void onDestroy();
ElementWeakPtr getWeakPtr() const;
/**
@@ -72,17 +91,25 @@ namespace canvas
virtual void update(double dt);
naRef addEventListener(const nasal::CallContext& ctx);
virtual void clearEventListener();
virtual bool accept(EventVisitor& visitor);
virtual bool ascend(EventVisitor& visitor);
virtual bool traverse(EventVisitor& visitor);
void callListeners(const canvas::EventPtr& event);
virtual bool handleEvent(canvas::EventPtr event);
virtual bool hitBound(const osg::Vec2f& pos) const;
virtual bool hitBound( const osg::Vec2f& pos,
const osg::Vec2f& local_pos ) const;
/**
* Get whether the element is visible or hidden (Can be changed with
* setting property "visible" accordingly).
*/
bool isVisible() const;
osg::ref_ptr<osg::MatrixTransform> getMatrixTransform();
osg::MatrixTransform* getMatrixTransform();
osg::MatrixTransform const* getMatrixTransform() const;
virtual void childAdded( SGPropertyNode * parent,
SGPropertyNode * child );
@@ -110,11 +137,32 @@ namespace canvas
*/
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
/**
* Create an canvas Element
*
* @tparam Derived Type of element (needs to derive from Element)
*/
template<typename Derived>
static
typename boost::enable_if<
boost::is_base_of<Element, Derived>,
ElementPtr
>::type create( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& style,
Element* parent )
{
ElementPtr el( new Derived(canvas, node, style, parent) );
el->setSelf(el);
return el;
}
protected:
enum Attributes
{
LAST_ATTRIBUTE = 0x0001
BLEND_FUNC = 0x0001,
LAST_ATTRIBUTE = BLEND_FUNC << 1
};
enum TransformType
@@ -132,11 +180,10 @@ namespace canvas
uint32_t _attributes_dirty;
bool _transform_dirty;
osg::ref_ptr<osg::MatrixTransform> _transform;
std::vector<TransformType> _transform_types;
osg::observer_ptr<osg::MatrixTransform> _transform;
std::vector<TransformType> _transform_types;
Style _style;
StyleSetters _style_setters;
std::vector<SGPropertyNode_ptr> _bounding_box;
typedef std::vector<EventListenerPtr> Listener;
@@ -144,44 +191,254 @@ namespace canvas
ListenerMap _listener;
typedef std::map<std::string, StyleInfo> StyleSetters;
static StyleSetters _style_setters;
Element( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,
Element* parent );
template<typename T, class C1, class C2>
Element::StyleSetter
addStyle(const std::string& name, void (C1::*setter)(T), C2 instance)
/**
* Returns false on first call and true on any successive call. Use to
* perform initialization tasks which are only required once per element
* type.
*
* @tparam Derived (Derived) class type
*/
template<class Derived>
bool isInit() const
{
return _style_setters[ name ] =
bindStyleSetter<T>(name, setter, instance);
static bool is_init = false;
if( is_init )
return true;
is_init = true;
return false;
}
template<typename T1, typename T2, class C1, class C2>
Element::StyleSetter
addStyle(const std::string& name, void (C1::*setter)(T2), C2 instance)
{
return _style_setters[ name ] =
bindStyleSetter<T1>(name, setter, instance);
}
template<class C1, class C2>
Element::StyleSetter
/**
* Register a function for setting a style specified by the given property
*
* @param name Property name
* @param type Interpolation type
* @param setter Setter function
*
* @tparam T1 Type of value used to retrieve value from property
* node
* @tparam T2 Type of value the setter function expects
* @tparam Derived Type of class the setter can be applied to
*
* @note T1 needs to be convertible to T2
*/
template<
typename T1,
typename T2,
class Derived
>
StyleSetter
addStyle( const std::string& name,
void (C1::*setter)(const std::string&),
C2 instance )
const std::string& type,
const boost::function<void (Derived&, T2)>& setter )
{
return _style_setters[ name ] =
bindStyleSetter<const char*>(name, setter, instance);
StyleInfo& style_info = _style_setters[ name ];
if( !type.empty() )
{
if( !style_info.type.empty() && type != style_info.type )
SG_LOG
(
SG_GENERAL,
SG_WARN,
"changing animation type for '" << name << "': "
<< style_info.type << " -> " << type
);
style_info.type = type;
}
StyleSetter* style = &style_info.setter;
while( style->next )
style = style->next;
if( style->func )
style = style->next = new StyleSetter;
style->func = boost::bind
(
&type_match<Derived>::call,
_1,
_2,
bindStyleSetter<T1>(name, setter)
);
return *style;
}
template<typename T1, typename T2, class C1, class C2>
Element::StyleSetter
bindStyleSetter( const std::string& name,
void (C1::*setter)(T2),
C2 instance )
template<
typename T,
class Derived
>
StyleSetter
addStyle( const std::string& name,
const std::string& type,
const boost::function<void (Derived&, T)>& setter )
{
return boost::bind(setter, instance, boost::bind(&getValue<T1>, _1));
return addStyle<T, T>(name, type, setter);
}
template<
typename T,
class Derived
>
StyleSetter
addStyle( const std::string& name,
const std::string& type,
void (Derived::*setter)(T) )
{
return addStyle<T, T>
(
name,
type,
boost::function<void (Derived&, T)>(setter)
);
}
template<
typename T1,
typename T2,
class Derived
>
StyleSetterFunc
addStyle( const std::string& name,
const std::string& type,
void (Derived::*setter)(T2) )
{
return addStyle<T1>
(
name,
type,
boost::function<void (Derived&, T2)>(setter)
);
}
template<
class Derived
>
StyleSetter
addStyle( const std::string& name,
const std::string& type,
void (Derived::*setter)(const std::string&) )
{
return addStyle<const char*, const std::string&>
(
name,
type,
boost::function<void (Derived&, const std::string&)>(setter)
);
}
template<
typename T,
class Derived,
class Other,
class OtherRef
>
StyleSetter
addStyle( const std::string& name,
const std::string& type,
void (Other::*setter)(T),
OtherRef Derived::*instance_ref )
{
return addStyle<T, T>(name, type, bindOther(setter, instance_ref));
}
template<
typename T1,
typename T2,
class Derived,
class Other,
class OtherRef
>
StyleSetter
addStyle( const std::string& name,
const std::string& type,
void (Other::*setter)(T2),
OtherRef Derived::*instance_ref )
{
return addStyle<T1>(name, type, bindOther(setter, instance_ref));
}
template<
typename T1,
typename T2,
class Derived,
class Other,
class OtherRef
>
StyleSetter
addStyle( const std::string& name,
const std::string& type,
const boost::function<void (Other&, T2)>& setter,
OtherRef Derived::*instance_ref )
{
return addStyle<T1>(name, type, bindOther(setter, instance_ref));
}
template<
class Derived,
class Other,
class OtherRef
>
StyleSetter
addStyle( const std::string& name,
const std::string& type,
void (Other::*setter)(const std::string&),
OtherRef Derived::*instance_ref )
{
return addStyle<const char*, const std::string&>
(
name,
type,
boost::function<void (Other&, const std::string&)>(setter),
instance_ref
);
}
template<typename T, class Derived, class Other, class OtherRef>
boost::function<void (Derived&, T)>
bindOther( void (Other::*setter)(T), OtherRef Derived::*instance_ref )
{
return boost::bind(setter, boost::bind(instance_ref, _1), _2);
}
template<typename T, class Derived, class Other, class OtherRef>
boost::function<void (Derived&, T)>
bindOther( const boost::function<void (Other&, T)>& setter,
OtherRef Derived::*instance_ref )
{
return boost::bind
(
setter,
boost::bind
(
&reference_from_pointer<Other, OtherRef>,
boost::bind(instance_ref, _1)
),
_2
);
}
template<typename T1, typename T2, class Derived>
StyleSetterFuncUnchecked
bindStyleSetter( const std::string& name,
const boost::function<void (Derived&, T2)>& setter )
{
return boost::bind
(
setter,
// We will only call setters with Derived instances, so we can safely
// cast here.
boost::bind(&derived_cast<Derived>, _1),
boost::bind(&getValue<T1>, _2)
);
}
virtual void childAdded(SGPropertyNode * child) {}
@@ -202,6 +459,39 @@ namespace canvas
osg::ref_ptr<osg::Drawable> _drawable;
Element(const Element&);// = delete
template<class Derived>
static Derived& derived_cast(Element& el)
{
return static_cast<Derived&>(el);
}
template<class T, class SharedPtr>
static T& reference_from_pointer(const SharedPtr& p)
{
return *get_pointer(p);
}
/**
* Helper to call a function only of the element type can be converted to
* the required type.
*
* @return Whether the function has been called
*/
template<class Derived>
struct type_match
{
static bool call( Element& el,
const SGPropertyNode* prop,
const StyleSetterFuncUnchecked& func )
{
Derived* d = dynamic_cast<Derived*>(&el);
if( !d )
return false;
func(*d, prop);
return true;
}
};
};
} // namespace canvas

View File

@@ -33,19 +33,18 @@ namespace simgear
namespace canvas
{
/**
* Create an canvas Element of type T
* Add canvas Element type to factory map
*/
template<typename T>
ElementPtr createElement( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& style,
Element* parent )
template<typename ElementType>
void add(ElementFactories& factories)
{
ElementPtr el( new T(canvas, node, style, parent) );
el->setSelf(el);
return el;
factories[ElementType::TYPE_NAME] = &Element::create<ElementType>;
}
//----------------------------------------------------------------------------
ElementFactories Group::_child_factories;
const std::string Group::TYPE_NAME = "group";
//----------------------------------------------------------------------------
Group::Group( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
@@ -53,11 +52,14 @@ namespace canvas
Element* parent ):
Element(canvas, node, parent_style, parent)
{
_child_factories["group"] = &createElement<Group>;
_child_factories["image"] = &createElement<Image>;
_child_factories["map" ] = &createElement<Map >;
_child_factories["path" ] = &createElement<Path >;
_child_factories["text" ] = &createElement<Text >;
if( !isInit<Group>() )
{
add<Group>(_child_factories);
add<Image>(_child_factories);
add<Map >(_child_factories);
add<Path >(_child_factories);
add<Text >(_child_factories);
}
}
//----------------------------------------------------------------------------
@@ -80,11 +82,38 @@ namespace canvas
//----------------------------------------------------------------------------
ElementPtr Group::getChild(const SGPropertyNode* node)
{
ChildList::iterator child = findChild(node);
if( child == _children.end() )
return ElementPtr();
return findChild(node, "");
}
return child->second;
//----------------------------------------------------------------------------
ElementPtr Group::getChild(const std::string& id)
{
return findChild(0, id);
}
//----------------------------------------------------------------------------
ElementPtr Group::getOrCreateChild( const std::string& type,
const std::string& id )
{
ElementPtr child = getChild(id);
if( child )
{
if( child->getProps()->getNameString() == type )
return child;
SG_LOG
(
SG_GENERAL,
SG_WARN,
"Group::getOrCreateChild: type missmatch! "
"('" << type << "' != '" << child->getProps()->getName() << "', "
"id = '" << id << "')"
);
return ElementPtr();
}
return createChild(type, id);
}
//----------------------------------------------------------------------------
@@ -92,10 +121,10 @@ namespace canvas
{
std::vector<GroupPtr> groups;
BOOST_FOREACH( ChildList::value_type child, _children )
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
{
const ElementPtr& el = child.second;
if( el->getProps()->getStringValue("id") == id )
const ElementPtr& el = getChildByIndex(i);
if( el->get<std::string>("id") == id )
return el;
GroupPtr group = boost::dynamic_pointer_cast<Group>(el);
@@ -113,11 +142,21 @@ namespace canvas
return ElementPtr();
}
//----------------------------------------------------------------------------
void Group::clearEventListener()
{
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
getChildByIndex(i)->clearEventListener();
Element::clearEventListener();
}
//----------------------------------------------------------------------------
void Group::update(double dt)
{
BOOST_FOREACH( ChildList::value_type child, _children )
child.second->update(dt);
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
getChildByIndex(i)->update(dt);
Element::update(dt);
}
@@ -126,9 +165,9 @@ namespace canvas
bool Group::traverse(EventVisitor& visitor)
{
// Iterate in reverse order as last child is displayed on top
BOOST_REVERSE_FOREACH( ChildList::value_type child, _children )
for(size_t i = _transform->getNumChildren(); i --> 0;)
{
if( child.second->accept(visitor) )
if( getChildByIndex(i)->accept(visitor) )
return true;
}
return false;
@@ -146,9 +185,9 @@ namespace canvas
return false;
bool handled = false;
BOOST_FOREACH( ChildList::value_type child, _children )
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
{
if( child.second->setStyle(style) )
if( getChildByIndex(i)->setStyle(style) )
handled = true;
}
@@ -160,16 +199,17 @@ namespace canvas
{
osg::BoundingBox bb;
BOOST_FOREACH( ChildList::value_type child, _children )
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
{
if( !child.second->getMatrixTransform()->getNodeMask() )
const ElementPtr& child = getChildByIndex(i);
if( !child->getMatrixTransform()->getNodeMask() )
continue;
bb.expandBy
(
child.second->getTransformedBounds
child->getTransformedBounds
(
child.second->getMatrixTransform()->getMatrix() * m
child->getMatrixTransform()->getMatrix() * m
)
);
}
@@ -177,21 +217,32 @@ namespace canvas
return bb;
}
//----------------------------------------------------------------------------
ElementFactory Group::getChildFactory(const std::string& type) const
{
ElementFactories::iterator child_factory = _child_factories.find(type);
if( child_factory != _child_factories.end() )
return child_factory->second;
return ElementFactory();
}
//----------------------------------------------------------------------------
void Group::childAdded(SGPropertyNode* child)
{
if( child->getParent() != _node )
return;
ChildFactories::iterator child_factory =
_child_factories.find( child->getNameString() );
if( child_factory != _child_factories.end() )
ElementFactory child_factory = getChildFactory( child->getNameString() );
if( child_factory )
{
ElementPtr element = child_factory->second(_canvas, child, _style, this);
ElementPtr element = child_factory(_canvas, child, _style, this);
// Add to osg scene graph...
_transform->addChild( element->getMatrixTransform() );
_children.push_back( ChildList::value_type(child, element) );
// ...and ensure correct ordering
handleZIndexChanged(element);
return;
}
@@ -210,10 +261,15 @@ namespace canvas
if( node->getParent() != _node )
return;
if( _child_factories.find(node->getNameString()) != _child_factories.end() )
if( getChildFactory(node->getNameString()) )
{
ChildList::iterator child = findChild(node);
if( child == _children.end() )
if( !_transform.valid() )
// If transform is destroyed also all children are destroyed, so we can
// not do anything here.
return;
ElementPtr child = getChild(node);
if( !child )
SG_LOG
(
SG_GL,
@@ -222,8 +278,9 @@ namespace canvas
);
else
{
_transform->removeChild( child->second->getMatrixTransform() );
_children.erase(child);
// Remove child from the scenegraph (this automatically invalidates the
// reference on the element hold by the MatrixTransform)
child->onDestroy();
}
}
else
@@ -239,54 +296,52 @@ namespace canvas
{
if( node->getParent()->getParent() == _node
&& node->getNameString() == "z-index" )
return handleZIndexChanged(node->getParent(), node->getIntValue());
return handleZIndexChanged( getChild(node->getParent()),
node->getIntValue() );
}
//----------------------------------------------------------------------------
void Group::handleZIndexChanged(SGPropertyNode* node, int z_index)
void Group::handleZIndexChanged(ElementPtr child, int z_index)
{
ChildList::iterator child = findChild(node);
if( child == _children.end() )
if( !child )
return;
osg::Node* tf = child->second->getMatrixTransform();
int index = _transform->getChildIndex(tf),
index_new = index;
osg::ref_ptr<osg::MatrixTransform> tf = child->getMatrixTransform();
size_t index = _transform->getChildIndex(tf),
index_new = index;
ChildList::iterator next = child;
++next;
while( next != _children.end()
&& next->first->getIntValue("z-index", 0) <= z_index )
for(;; ++index_new)
{
++index_new;
++next;
if( index_new + 1 == _transform->getNumChildren() )
break;
// Move to end of block with same index (= move upwards until the next
// element has a higher index)
if( getChildByIndex(index_new + 1)->get<int>("z-index", 0) > z_index )
break;
}
if( index_new != index )
if( index_new == index )
{
_children.insert(next, *child);
}
else
{
ChildList::iterator prev = child;
while( prev != _children.begin()
&& (--prev)->first->getIntValue("z-index", 0) > z_index)
// We were not able to move upwards so now try downwards
for(;; --index_new)
{
--index_new;
if( index_new == 0 )
break;
// Move to end of block with same index (= move downwards until the
// previous element has the same or a lower index)
if( getChildByIndex(index_new - 1)->get<int>("z-index", 0) <= z_index )
break;
}
if( index == index_new )
return;
_children.insert(prev, *child);
}
_transform->removeChild(index);
_transform->insertChild(index_new, tf);
_children.erase(child);
SG_LOG
(
SG_GENERAL,
@@ -296,14 +351,36 @@ namespace canvas
}
//----------------------------------------------------------------------------
Group::ChildList::iterator Group::findChild(const SGPropertyNode* node)
ElementPtr Group::getChildByIndex(size_t index) const
{
return std::find_if
(
_children.begin(),
_children.end(),
boost::bind(&ChildList::value_type::first, _1) == node
);
assert(_transform.valid());
OSGUserData* ud =
static_cast<OSGUserData*>(_transform->getChild(index)->getUserData());
assert(ud);
return ud->element;
}
//----------------------------------------------------------------------------
ElementPtr Group::findChild( const SGPropertyNode* node,
const std::string& id ) const
{
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
{
ElementPtr el = getChildByIndex(i);
if( node )
{
if( el->getProps() == node )
return el;
}
else
{
if( el->get<std::string>("id") == id )
return el;
}
}
return ElementPtr();
}
} // namespace canvas

View File

@@ -28,10 +28,14 @@ namespace simgear
namespace canvas
{
typedef std::map<std::string, ElementFactory> ElementFactories;
class Group:
public Element
{
public:
static const std::string TYPE_NAME;
typedef std::list< std::pair< const SGPropertyNode*,
ElementPtr
>
@@ -46,6 +50,34 @@ namespace canvas
ElementPtr createChild( const std::string& type,
const std::string& id = "" );
ElementPtr getChild(const SGPropertyNode* node);
ElementPtr getChild(const std::string& id);
ElementPtr getOrCreateChild( const std::string& type,
const std::string& id );
template<class T>
boost::shared_ptr<T> createChild(const std::string& id = "")
{
return boost::dynamic_pointer_cast<T>( createChild(T::TYPE_NAME, id) );
}
template<class T>
boost::shared_ptr<T> getChild(const SGPropertyNode* node)
{
return boost::dynamic_pointer_cast<T>( getChild(node) );
}
template<class T>
boost::shared_ptr<T> getChild(const std::string& id)
{
return boost::dynamic_pointer_cast<T>( getChild(id) );
}
template<class T>
boost::shared_ptr<T> getOrCreateChild(const std::string& id)
{
return
boost::dynamic_pointer_cast<T>( getOrCreateChild(T::TYPE_NAME, id) );
}
/**
* Get first child with given id (breadth-first search)
@@ -54,6 +86,8 @@ namespace canvas
*/
ElementPtr getElementById(const std::string& id);
virtual void clearEventListener();
virtual void update(double dt);
virtual bool traverse(EventVisitor& visitor);
@@ -64,18 +98,23 @@ namespace canvas
protected:
typedef std::map<std::string, ElementFactory> ChildFactories;
static ElementFactories _child_factories;
ChildFactories _child_factories;
ChildList _children;
/**
* Overload in derived classes to allow for more/other types of elements
* to be managed.
*/
virtual ElementFactory getChildFactory(const std::string& type) const;
virtual void childAdded(SGPropertyNode * child);
virtual void childRemoved(SGPropertyNode * child);
virtual void childChanged(SGPropertyNode * child);
void handleZIndexChanged(SGPropertyNode* node, int z_index);
void handleZIndexChanged(ElementPtr child, int z_index = 0);
ChildList::iterator findChild(const SGPropertyNode* node);
ElementPtr getChildByIndex(size_t index) const;
ElementPtr findChild( const SGPropertyNode* node,
const std::string& id ) const;
};
} // namespace canvas

View File

@@ -21,6 +21,8 @@
#include <simgear/canvas/Canvas.hxx>
#include <simgear/canvas/CanvasMgr.hxx>
#include <simgear/canvas/CanvasSystemAdapter.hxx>
#include <simgear/canvas/MouseEvent.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/util/parse_color.hxx>
#include <simgear/misc/sg_path.hxx>
@@ -43,9 +45,11 @@ namespace canvas
{
public:
CullCallback(const CanvasWeakPtr& canvas);
void cullNextFrame();
private:
CanvasWeakPtr _canvas;
mutable bool _cull_next_frame;
virtual bool cull( osg::NodeVisitor* nv,
osg::Drawable* drawable,
@@ -54,11 +58,18 @@ namespace canvas
//----------------------------------------------------------------------------
CullCallback::CullCallback(const CanvasWeakPtr& canvas):
_canvas( canvas )
_canvas( canvas ),
_cull_next_frame( false )
{
}
//----------------------------------------------------------------------------
void CullCallback::cullNextFrame()
{
_cull_next_frame = true;
}
//----------------------------------------------------------------------------
bool CullCallback::cull( osg::NodeVisitor* nv,
osg::Drawable* drawable,
@@ -67,10 +78,17 @@ namespace canvas
if( !_canvas.expired() )
_canvas.lock()->enableRendering();
// TODO check if window/image should be culled
return false;
if( !_cull_next_frame )
// TODO check if window/image should be culled
return false;
_cull_next_frame = false;
return true;
}
//----------------------------------------------------------------------------
const std::string Image::TYPE_NAME = "image";
//----------------------------------------------------------------------------
Image::Image( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
@@ -91,26 +109,33 @@ namespace canvas
// allocate arrays for the image
_vertices = new osg::Vec3Array(4);
_vertices->setDataVariance(osg::Object::STATIC);
_vertices->setDataVariance(osg::Object::DYNAMIC);
_geom->setVertexArray(_vertices);
_texCoords = new osg::Vec2Array(4);
_texCoords->setDataVariance(osg::Object::STATIC);
_texCoords->setDataVariance(osg::Object::DYNAMIC);
_geom->setTexCoordArray(0, _texCoords);
_colors = new osg::Vec4Array(4);
_colors->setDataVariance(osg::Object::STATIC);
_geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
_colors = new osg::Vec4Array(1);
_colors->setDataVariance(osg::Object::DYNAMIC);
_geom->setColorArray(_colors);
_geom->setColorBinding(osg::Geometry::BIND_OVERALL);
osg::DrawArrays* prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
prim->set(osg::PrimitiveSet::QUADS, 0, 4);
prim->setDataVariance(osg::Object::STATIC);
_geom->addPrimitiveSet(prim);
_prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
_prim->set(osg::PrimitiveSet::QUADS, 0, 4);
_prim->setDataVariance(osg::Object::DYNAMIC);
_geom->addPrimitiveSet(_prim);
setDrawable(_geom);
addStyle("fill", &Image::setFill, this);
if( !isInit<Image>() )
{
addStyle("fill", "color", &Image::setFill);
addStyle("slice", "", &Image::setSlice);
addStyle("slice-width", "", &Image::setSliceWidth);
addStyle("outset", "", &Image::setOutset);
}
setFill("#ffffff"); // TODO how should we handle default values?
setupStyle();
@@ -127,14 +152,119 @@ namespace canvas
{
Element::update(dt);
osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
(
_geom->getOrCreateStateSet()
->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
);
simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
if( (_attributes_dirty & SRC_CANVAS)
// check if texture has changed (eg. due to resizing)
|| (canvas && texture != canvas->getTexture()) )
{
_geom->getOrCreateStateSet()
->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
if( !canvas || canvas->isInit() )
_attributes_dirty &= ~SRC_CANVAS;
}
if( !_attributes_dirty )
return;
const SGRect<int>& tex_dim = getTextureDimensions();
// http://www.w3.org/TR/css3-background/#border-image-slice
// The fill keyword, if present, causes the middle part of the image to be
// preserved. (By default it is discarded, i.e., treated as empty.)
bool fill = (_slice.getKeyword() == "fill");
if( _attributes_dirty & DEST_SIZE )
{
(*_vertices)[0].set(_region.l(), _region.t(), 0);
(*_vertices)[1].set(_region.r(), _region.t(), 0);
(*_vertices)[2].set(_region.r(), _region.b(), 0);
(*_vertices)[3].set(_region.l(), _region.b(), 0);
_vertices->dirty();
size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
if( num_vertices != _prim->getNumPrimitives() )
{
_vertices->resize(num_vertices);
_texCoords->resize(num_vertices);
_prim->setCount(num_vertices);
_prim->dirty();
_attributes_dirty |= SRC_RECT;
}
// http://www.w3.org/TR/css3-background/#border-image-outset
SGRect<float> region = _region;
if( _outset.isValid() )
{
const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
region.t() -= outset.t;
region.r() += outset.r;
region.b() += outset.b;
region.l() -= outset.l;
}
if( !_slice.isValid() )
{
setQuad(0, region.getMin(), region.getMax());
}
else
{
/*
Image slice, 9-scale, whatever it is called. The four corner images
stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
fill the remaining space up to the specified size.
x[0] x[1] x[2] x[3]
| | | |
-------------------- - y[0]
| tl | top | tr |
-------------------- - y[1]
| | | |
| l | | r |
| e | center | i |
| f | | g |
| t | | h |
| | | t |
-------------------- - y[2]
| bl | bottom | br |
-------------------- - y[3]
*/
const CSSBorder::Offsets& slice =
(_slice_width.isValid() ? _slice_width : _slice)
.getAbsOffsets(tex_dim);
float x[4] = {
region.l(),
region.l() + slice.l,
region.r() - slice.r,
region.r()
};
float y[4] = {
region.t(),
region.t() + slice.t,
region.b() - slice.b,
region.b()
};
int i = 0;
for(int ix = 0; ix < 3; ++ix)
for(int iy = 0; iy < 3; ++iy)
{
if( ix == 1 && iy == 1 && !fill )
// The fill keyword, if present, causes the middle part of the
// image to be filled.
continue;
setQuad( i++,
SGVec2f(x[ix ], y[iy ]),
SGVec2f(x[ix + 1], y[iy + 1]) );
}
}
_vertices->dirty();
_attributes_dirty &= ~DEST_SIZE;
_geom->dirtyBound();
setBoundingBox(_geom->getBound());
@@ -142,46 +272,105 @@ namespace canvas
if( _attributes_dirty & SRC_RECT )
{
double u0 = _src_rect.l(),
u1 = _src_rect.r(),
v0 = _src_rect.b(),
v1 = _src_rect.t();
SGRect<float> src_rect = _src_rect;
if( !_node_src_rect->getBoolValue("normalized", true) )
{
const SGRect<int>& tex_dim = getTextureDimensions();
u0 /= tex_dim.width();
u1 /= tex_dim.width();
v0 /= tex_dim.height();
v1 /= tex_dim.height();
src_rect.t() /= tex_dim.height();
src_rect.r() /= tex_dim.width();
src_rect.b() /= tex_dim.height();
src_rect.l() /= tex_dim.width();
}
(*_texCoords)[0].set(u0, v0);
(*_texCoords)[1].set(u1, v0);
(*_texCoords)[2].set(u1, v1);
(*_texCoords)[3].set(u0, v1);
_texCoords->dirty();
// Image coordinate systems y-axis is flipped
std::swap(src_rect.t(), src_rect.b());
if( !_slice.isValid() )
{
setQuadUV(0, src_rect.getMin(), src_rect.getMax());
}
else
{
const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
float x[4] = {
src_rect.l(),
src_rect.l() + slice.l,
src_rect.r() - slice.r,
src_rect.r()
};
float y[4] = {
src_rect.t(),
src_rect.t() - slice.t,
src_rect.b() + slice.b,
src_rect.b()
};
int i = 0;
for(int ix = 0; ix < 3; ++ix)
for(int iy = 0; iy < 3; ++iy)
{
if( ix == 1 && iy == 1 && !fill )
// The fill keyword, if present, causes the middle part of the
// image to be filled.
continue;
setQuadUV( i++,
SGVec2f(x[ix ], y[iy ]),
SGVec2f(x[ix + 1], y[iy + 1]) );
}
}
_texCoords->dirty();
_attributes_dirty &= ~SRC_RECT;
}
}
//----------------------------------------------------------------------------
void Image::valueChanged(SGPropertyNode* child)
{
// If the image is switched from invisible to visible, and it shows a
// canvas, we need to delay showing it by one frame to ensure the canvas is
// updated before the image is displayed.
//
// As canvas::Element handles and filters changes to the "visible" property
// we can not check this in Image::childChanged but instead have to override
// Element::valueChanged.
if( !isVisible()
&& child->getParent() == _node
&& child->getNameString() == "visible"
&& child->getBoolValue() )
{
CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
if( cb )
cb->cullNextFrame();
}
Element::valueChanged(child);
}
//----------------------------------------------------------------------------
void Image::setSrcCanvas(CanvasPtr canvas)
{
if( !_src_canvas.expired() )
_src_canvas.lock()->removeDependentCanvas(_canvas);
CanvasPtr src_canvas = _src_canvas.lock(),
self_canvas = _canvas.lock();
_src_canvas = canvas;
_geom->getOrCreateStateSet()
->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
if( src_canvas )
src_canvas->removeParentCanvas(self_canvas);
if( self_canvas )
self_canvas->removeChildCanvas(src_canvas);
_src_canvas = src_canvas = canvas;
_attributes_dirty |= SRC_CANVAS;
_geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
if( !_src_canvas.expired() )
if( src_canvas )
{
setupDefaultDimensions();
_src_canvas.lock()->addDependentCanvas(_canvas);
if( self_canvas )
{
self_canvas->addChildCanvas(src_canvas);
src_canvas->addParentCanvas(self_canvas);
}
}
}
@@ -197,7 +386,10 @@ namespace canvas
// remove canvas...
setSrcCanvas( CanvasPtr() );
_texture->setResizeNonPowerOfTwoHint(false);
_texture->setImage(img);
_texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
_texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
_geom->getOrCreateStateSet()
->setTextureAttributeAndModes(0, _texture);
@@ -212,17 +404,75 @@ namespace canvas
if( !parseColor(fill, color) )
return;
for( int i = 0; i < 4; ++i )
(*_colors)[i] = color;
_colors->front() = color;
_colors->dirty();
}
//----------------------------------------------------------------------------
void Image::setSlice(const std::string& slice)
{
_slice = CSSBorder::parse(slice);
_attributes_dirty |= SRC_RECT | DEST_SIZE;
}
//----------------------------------------------------------------------------
void Image::setSliceWidth(const std::string& width)
{
_slice_width = CSSBorder::parse(width);
_attributes_dirty |= DEST_SIZE;
}
//----------------------------------------------------------------------------
void Image::setOutset(const std::string& outset)
{
_outset = CSSBorder::parse(outset);
_attributes_dirty |= DEST_SIZE;
}
//----------------------------------------------------------------------------
const SGRect<float>& Image::getRegion() const
{
return _region;
}
//----------------------------------------------------------------------------
bool Image::handleEvent(EventPtr event)
{
bool handled = Element::handleEvent(event);
CanvasPtr src_canvas = _src_canvas.lock();
if( !src_canvas )
return handled;
MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
if( mouse_event )
{
mouse_event.reset( new MouseEvent(*mouse_event) );
event = mouse_event;
mouse_event->client_pos = mouse_event->local_pos
- toOsg(_region.getMin());
osg::Vec2f size(_region.width(), _region.height());
if( _outset.isValid() )
{
CSSBorder::Offsets outset =
_outset.getAbsOffsets(getTextureDimensions());
mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
size.x() += outset.l + outset.r;
size.y() += outset.t + outset.b;
}
// Scale event pos according to canvas view size vs. displayed/screen size
mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
mouse_event->local_pos = mouse_event->client_pos;
}
return handled | src_canvas->handleMouseEvent(mouse_event);
}
//----------------------------------------------------------------------------
void Image::childChanged(SGPropertyNode* child)
{
@@ -319,36 +569,67 @@ namespace canvas
{
if( !_src_rect.width() || !_src_rect.height() )
{
const SGRect<int>& tex_dim = getTextureDimensions();
_node_src_rect->setBoolValue("normalized", false);
_node_src_rect->setFloatValue("right", tex_dim.width());
_node_src_rect->setFloatValue("bottom", tex_dim.height());
// Show whole image by default
_node_src_rect->setBoolValue("normalized", true);
_node_src_rect->setFloatValue("right", 1);
_node_src_rect->setFloatValue("bottom", 1);
}
if( !_region.width() || !_region.height() )
{
_node->setFloatValue("size[0]", _src_rect.width());
_node->setFloatValue("size[1]", _src_rect.height());
// Default to image size.
// TODO handle showing only part of image?
const SGRect<int>& dim = getTextureDimensions();
_node->setFloatValue("size[0]", dim.width());
_node->setFloatValue("size[1]", dim.height());
}
}
//----------------------------------------------------------------------------
SGRect<int> Image::getTextureDimensions() const
{
osg::Texture2D *texture = !_src_canvas.expired()
? _src_canvas.lock()->getTexture()
: _texture.get();
CanvasPtr canvas = _src_canvas.lock();
SGRect<int> dim(0,0);
if( !texture )
return SGRect<int>();
// Use canvas/image dimensions rather than texture dimensions, as they could
// be resized internally by OpenSceneGraph (eg. to nearest power of two).
if( canvas )
{
dim.setRight( canvas->getViewWidth() );
dim.setBottom( canvas->getViewHeight() );
}
else if( _texture )
{
osg::Image* img = _texture->getImage();
return SGRect<int>
(
0,0,
texture->getTextureWidth(),
texture->getTextureHeight()
);
if( img )
{
dim.setRight( img->s() );
dim.setBottom( img->t() );
}
}
return dim;
}
//----------------------------------------------------------------------------
void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
{
int i = index * 4;
(*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
(*_vertices)[i + 1].set(br.x(), tl.y(), 0);
(*_vertices)[i + 2].set(br.x(), br.y(), 0);
(*_vertices)[i + 3].set(tl.x(), br.y(), 0);
}
//----------------------------------------------------------------------------
void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
{
int i = index * 4;
(*_texCoords)[i + 0].set(tl.x(), tl.y());
(*_texCoords)[i + 1].set(br.x(), tl.y());
(*_texCoords)[i + 2].set(br.x(), br.y());
(*_texCoords)[i + 3].set(tl.x(), br.y());
}
} // namespace canvas

View File

@@ -22,7 +22,7 @@
#include "CanvasElement.hxx"
#include <simgear/canvas/canvas_fwd.hxx>
#include <simgear/math/SGRect.hxx>
#include <simgear/misc/CSSBorder.hxx>
#include <osg/Texture2D>
namespace simgear
@@ -34,6 +34,8 @@ namespace canvas
public Element
{
public:
static const std::string TYPE_NAME;
/**
* @param node Property node containing settings for this image:
* rect/[left/right/top/bottom] Dimensions of source
@@ -43,11 +45,12 @@ namespace canvas
*/
Image( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,
const Style& parent_style = Style(),
Element* parent = 0 );
virtual ~Image();
virtual void update(double dt);
virtual void valueChanged(SGPropertyNode* child);
void setSrcCanvas(CanvasPtr canvas);
CanvasWeakPtr getSrcCanvas() const;
@@ -55,14 +58,40 @@ namespace canvas
void setImage(osg::Image *img);
void setFill(const std::string& fill);
/**
* Set image slice (aka. 9-scale)
*
* @see http://www.w3.org/TR/css3-background/#border-image-slice
*/
void setSlice(const std::string& slice);
/**
* Set image slice width.
*
* By default the size of the 9-scale grid is the same as specified
* with setSlice/"slice". Using this method allows setting values
* different to the size in the source image.
*
* @see http://www.w3.org/TR/css3-background/#border-image-width
*/
void setSliceWidth(const std::string& width);
/**
* http://www.w3.org/TR/css3-background/#border-image-outset
*/
void setOutset(const std::string& outset);
const SGRect<float>& getRegion() const;
bool handleEvent(EventPtr event);
protected:
enum ImageAttributes
{
SRC_RECT = LAST_ATTRIBUTE << 1, // Source image rectangle
DEST_SIZE = SRC_RECT << 1 // Element size
DEST_SIZE = SRC_RECT << 1, // Element size
SRC_CANVAS = DEST_SIZE << 1
};
virtual void childChanged(SGPropertyNode * child);
@@ -70,11 +99,15 @@ namespace canvas
void setupDefaultDimensions();
SGRect<int> getTextureDimensions() const;
void setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br);
void setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br);
osg::ref_ptr<osg::Texture2D> _texture;
// TODO optionally forward events to canvas
CanvasWeakPtr _src_canvas;
osg::ref_ptr<osg::Geometry> _geom;
osg::ref_ptr<osg::DrawArrays>_prim;
osg::ref_ptr<osg::Vec3Array> _vertices;
osg::ref_ptr<osg::Vec2Array> _texCoords;
osg::ref_ptr<osg::Vec4Array> _colors;
@@ -82,6 +115,10 @@ namespace canvas
SGPropertyNode *_node_src_rect;
SGRect<float> _src_rect,
_region;
CSSBorder _slice,
_slice_width,
_outset;
};
} // namespace canvas

View File

@@ -42,7 +42,9 @@ namespace simgear
namespace canvas
{
//----------------------------------------------------------------------------
const std::string GEO = "-geo";
const std::string Map::TYPE_NAME = "map";
//----------------------------------------------------------------------------
Map::Map( const CanvasWeakPtr& canvas,

View File

@@ -35,6 +35,8 @@ namespace canvas
public Group
{
public:
static const std::string TYPE_NAME;
Map( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,

View File

@@ -20,7 +20,6 @@
#include <simgear/scene/util/parse_color.hxx>
#include <osg/Drawable>
#include <osg/BlendFunc>
#include <vg/openvg.h>
#include <cassert>
@@ -429,6 +428,9 @@ namespace canvas
*/
void update()
{
if( !vgHasContextSH() )
return;
if( _attributes_dirty & PATH )
{
const VGbitfield caps = VG_PATH_CAPABILITY_APPEND_TO
@@ -467,6 +469,9 @@ namespace canvas
};
};
//----------------------------------------------------------------------------
const std::string Path::TYPE_NAME = "path";
//----------------------------------------------------------------------------
Path::Path( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
@@ -476,14 +481,18 @@ namespace canvas
_path( new PathDrawable(this) )
{
setDrawable(_path);
PathDrawable *path = _path.get();
addStyle("fill", &PathDrawable::setFill, path);
addStyle("fill-rule", &PathDrawable::setFillRule, path);
addStyle("stroke", &PathDrawable::setStroke, path);
addStyle("stroke-width", &PathDrawable::setStrokeWidth, path);
addStyle("stroke-dasharray", &PathDrawable::setStrokeDashArray, path);
addStyle("stroke-linecap", &PathDrawable::setStrokeLinecap, path);
if( !isInit<Path>() )
{
PathDrawableRef Path::*path = &Path::_path;
addStyle("fill", "color", &PathDrawable::setFill, path);
addStyle("fill-rule", "", &PathDrawable::setFillRule, path);
addStyle("stroke", "color", &PathDrawable::setStroke, path);
addStyle("stroke-width", "numeric", &PathDrawable::setStrokeWidth, path);
addStyle("stroke-dasharray", "", &PathDrawable::setStrokeDashArray, path);
addStyle("stroke-linecap", "", &PathDrawable::setStrokeLinecap, path);
}
setupStyle();
}
@@ -517,6 +526,60 @@ namespace canvas
return _path->getTransformedBounds(m);
}
//----------------------------------------------------------------------------
Path& Path::moveTo(float x_abs, float y_abs)
{
return addSegment(VG_MOVE_TO_ABS, x_abs, y_abs);
}
//----------------------------------------------------------------------------
Path& Path::move(float x_rel, float y_rel)
{
return addSegment(VG_MOVE_TO_REL, x_rel, y_rel);
}
//----------------------------------------------------------------------------
Path& Path::lineTo(float x_abs, float y_abs)
{
return addSegment(VG_LINE_TO_ABS, x_abs, y_abs);
}
//----------------------------------------------------------------------------
Path& Path::line(float x_rel, float y_rel)
{
return addSegment(VG_LINE_TO_REL, x_rel, y_rel);
}
//----------------------------------------------------------------------------
Path& Path::horizTo(float x_abs)
{
return addSegment(VG_HLINE_TO_ABS, x_abs);
}
//----------------------------------------------------------------------------
Path& Path::horiz(float x_rel)
{
return addSegment(VG_HLINE_TO_REL, x_rel);
}
//----------------------------------------------------------------------------
Path& Path::vertTo(float y_abs)
{
return addSegment(VG_VLINE_TO_ABS, y_abs);
}
//----------------------------------------------------------------------------
Path& Path::vert(float y_rel)
{
return addSegment(VG_VLINE_TO_REL, y_rel);
}
//----------------------------------------------------------------------------
Path& Path::close()
{
return addSegment(VG_CLOSE_PATH);
}
//----------------------------------------------------------------------------
void Path::childRemoved(SGPropertyNode* child)
{

View File

@@ -20,6 +20,7 @@
#define CANVAS_PATH_HXX_
#include "CanvasElement.hxx"
#include <boost/preprocessor/iteration/iterate.hpp>
namespace simgear
{
@@ -29,6 +30,8 @@ namespace canvas
public Element
{
public:
static const std::string TYPE_NAME;
Path( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,
@@ -39,6 +42,30 @@ namespace canvas
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
#define BOOST_PP_ITERATION_LIMITS (0, 6)
#define BOOST_PP_FILENAME_1 \
<simgear/canvas/elements/detail/add_segment_variadic.hxx>
#include BOOST_PP_ITERATE()
/** Move path cursor */
Path& moveTo(float x_abs, float y_abs);
Path& move(float x_rel, float y_rel);
/** Add a line */
Path& lineTo(float x_abs, float y_abs);
Path& line(float x_rel, float y_rel);
/** Add a horizontal line */
Path& horizTo(float x_abs);
Path& horiz(float x_rel);
/** Add a vertical line */
Path& vertTo(float y_abs);
Path& vert(float y_rel);
/** Close the path (implicit lineTo to first point of path) */
Path& close();
protected:
enum PathAttributes
@@ -48,7 +75,8 @@ namespace canvas
};
class PathDrawable;
osg::ref_ptr<PathDrawable> _path;
typedef osg::ref_ptr<PathDrawable> PathDrawableRef;
PathDrawableRef _path;
virtual void childRemoved(SGPropertyNode * child);
virtual void childChanged(SGPropertyNode * child);

View File

@@ -20,6 +20,7 @@
#include <simgear/canvas/Canvas.hxx>
#include <simgear/canvas/CanvasSystemAdapter.hxx>
#include <simgear/scene/util/parse_color.hxx>
#include <simgear/structure/OSGVersion.hxx>
#include <osgText/Text>
namespace simgear
@@ -33,6 +34,7 @@ namespace canvas
TextOSG(canvas::Text* text);
void setFontResolution(int res);
void setCharacterAspect(float aspect);
void setFill(const std::string& fill);
void setBackgroundColor(const std::string& fill);
@@ -44,6 +46,8 @@ namespace canvas
protected:
canvas::Text *_text_element;
virtual void computePositions(unsigned int contextID) const;
};
//----------------------------------------------------------------------------
@@ -53,6 +57,12 @@ namespace canvas
}
//----------------------------------------------------------------------------
void Text::TextOSG::setFontResolution(int res)
{
TextBase::setFontResolution(res, res);
}
//----------------------------------------------------------------------------
void Text::TextOSG::setCharacterAspect(float aspect)
{
@@ -165,16 +175,85 @@ namespace canvas
if( !bb.valid() )
return bb;
#if SG_OSG_VERSION_LESS_THAN(3,1,0)
// TODO bounding box still doesn't seem always right (eg. with center
// horizontal alignment not completely accurate)
bb._min.y() += _offset.y();
bb._max.y() += _offset.y();
#endif
_text_element->setBoundingBox(bb);
return bb;
}
//----------------------------------------------------------------------------
void Text::TextOSG::computePositions(unsigned int contextID) const
{
if( _textureGlyphQuadMap.empty() || _layout == VERTICAL )
return osgText::Text::computePositions(contextID);
// TODO check when it can be larger
assert( _textureGlyphQuadMap.size() == 1 );
const GlyphQuads& quads = _textureGlyphQuadMap.begin()->second;
const GlyphQuads::Glyphs& glyphs = quads._glyphs;
const GlyphQuads::Coords2& coords = quads._coords;
const GlyphQuads::LineNumbers& line_numbers = quads._lineNumbers;
float wr = _characterHeight / getCharacterAspectRatio();
size_t cur_line = static_cast<size_t>(-1);
for(size_t i = 0; i < glyphs.size(); ++i)
{
// Check horizontal offsets
bool first_char = cur_line != line_numbers[i];
cur_line = line_numbers[i];
bool last_char = (i + 1 == glyphs.size())
|| (cur_line != line_numbers[i + 1]);
if( first_char || last_char )
{
// From osg/src/osgText/Text.cpp:
//
// osg::Vec2 upLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
// osg::Vec2 lowLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
// osg::Vec2 lowRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
// osg::Vec2 upRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
float left = coords[i * 4].x(),
right = coords[i * 4 + 2].x(),
width = glyphs[i]->getWidth() * wr;
// (local + width + fHoriz) - (local - fHoriz) = width + 2*fHoriz | -width
float margin = 0.5f * (right - left - width),
cursor_x = left + margin
- glyphs[i]->getHorizontalBearing().x() * wr;
if( first_char )
{
if( cur_line == 0 || cursor_x < _textBB._min.x() )
_textBB._min.x() = cursor_x;
}
if( last_char )
{
float cursor_w = cursor_x + glyphs[i]->getHorizontalAdvance() * wr;
if( cur_line == 0 || cursor_w > _textBB._max.x() )
_textBB._max.x() = cursor_w;
}
}
}
return osgText::Text::computePositions(contextID);
}
//----------------------------------------------------------------------------
const std::string Text::TYPE_NAME = "text";
//----------------------------------------------------------------------------
Text::Text( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
@@ -188,22 +267,33 @@ namespace canvas
_text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
_text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
addStyle("fill", &TextOSG::setFill, _text);
addStyle("background", &TextOSG::setBackgroundColor, _text);
addStyle("character-size",
static_cast<void (TextOSG::*)(float)>(&TextOSG::setCharacterSize),
_text);
addStyle("character-aspect-ratio", &TextOSG::setCharacterAspect, _text);
addStyle("padding", &TextOSG::setBoundingBoxMargin, _text);
// TEXT = 1 default
// BOUNDINGBOX = 2
// FILLEDBOUNDINGBOX = 4
// ALIGNMENT = 8
addStyle<int>("draw-mode", &TextOSG::setDrawMode, _text);
addStyle("max-width", &TextOSG::setMaximumWidth, _text);
addStyle("font", &Text::setFont, this);
addStyle("alignment", &Text::setAlignment, this);
addStyle("text", &Text::setText, this);
if( !isInit<Text>() )
{
osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
addStyle("fill", "color", &TextOSG::setFill, text);
addStyle("background", "color", &TextOSG::setBackgroundColor, text);
addStyle("character-size",
"numeric",
static_cast<
void (TextOSG::*)(float)
> (&TextOSG::setCharacterSize),
text);
addStyle("character-aspect-ratio",
"numeric",
&TextOSG::setCharacterAspect, text);
addStyle("font-resolution", "numeric", &TextOSG::setFontResolution, text);
addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
// TEXT = 1 default
// BOUNDINGBOX = 2
// FILLEDBOUNDINGBOX = 4
// ALIGNMENT = 8
addStyle<int>("draw-mode", "", &TextOSG::setDrawMode, text);
addStyle("max-width", "numeric", &TextOSG::setMaximumWidth, text);
addStyle("font", "", &Text::setFont);
addStyle("alignment", "", &Text::setAlignment);
addStyle("text", "", &Text::setText);
}
setupStyle();
}

View File

@@ -33,6 +33,8 @@ namespace canvas
public Element
{
public:
static const std::string TYPE_NAME;
Text( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,

View File

@@ -0,0 +1,21 @@
#ifndef CANVAS_PATH_HXX_
# error Canvas - do not include this file!
#endif
#define n BOOST_PP_ITERATION()
Path& addSegment( uint8_t cmd
BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_PARAMS(n, float coord) )
{
_node->addChild("cmd")->setIntValue(cmd);
#define SG_CANVAS_PATH_SET_COORD(z, n, dummy)\
_node->addChild("coord")->setFloatValue(coord##n);
BOOST_PP_REPEAT(n, SG_CANVAS_PATH_SET_COORD, 0)
#undef SG_CANVAS_PATH_SET_COORD
return *this;
}
#undef n

View File

@@ -1,6 +1,7 @@
// constants.h -- various constant definitions
//
// Written by Curtis Olson, started February 2000.
// Last change by Eric van den Berg, Feb 2013
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt/
//
@@ -103,6 +104,37 @@ const float SG_RADIANS_TO_DEGREES = 180.0f / SG_PI;
/** Radius squared (meter) */
#define SG_EQ_RAD_SQUARE_M 40680645877797.1344
// Physical Constants, SI
/**mean gravity on earth */
#define SG_g0_m_p_s2 9.80665 // m/s2
/**standard pressure at SL */
#define SG_p0_Pa 101325.0 // Pa
/**standard density at SL */
#define SG_rho0_kg_p_m3 1.225 // kg/m3
/**standard temperature at SL */
#define SG_T0_K 288.15 // K (=15degC)
/**specific gas constant of air*/
#define SG_R_m2_p_s2_p_K 287.05 // m2/s2/K
/**specific heat constant at constant pressure*/
#define SG_cp_m2_p_s2_p_K 1004.68 // m2/s2/K
/**ratio of specific heats of air*/
#define SG_gamma 1.4 // =cp/cv (cp = 1004.68 m2/s2 K , cv = 717.63 m2/s2 K)
/**constant beta used to calculate dynamic viscosity */
#define SG_beta_kg_p_sm_sqrK 1.458e-06 // kg/s/m/SQRT(K)
/** Sutherland constant */
#define SG_S_K 110.4 // K
// Conversions
/** Arc seconds to radians. (arcsec*pi)/(3600*180) = rad */
@@ -165,6 +197,9 @@ const float SG_RADIANS_TO_DEGREES = 180.0f / SG_PI;
/** Inch Mercury to Pascal */
#define SG_INHG_TO_PA 3386.388640341
/** slug/ft3 to kg/m3 */
#define SG_SLUGFT3_TO_KGPM3 515.379
/** For divide by zero avoidance, this will be close enough to zero */
#define SG_EPSILON 0.0000001

View File

@@ -0,0 +1,101 @@
/** \file BufferedLogCallback.cxx
* Buffer certain log messages permanently for later retrieval and display
*/
// Copyright (C) 2013 James Turner zakalawe@mac.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include <simgear/debug/BufferedLogCallback.hxx>
#include <boost/foreach.hpp>
#include <simgear/sg_inlines.h>
#include <simgear/threads/SGThread.hxx>
#include <simgear/threads/SGGuard.hxx>
namespace simgear
{
class BufferedLogCallback::BufferedLogCallbackPrivate
{
public:
SGMutex m_mutex;
sgDebugClass m_class;
sgDebugPriority m_priority;
vector_cstring m_buffer;
unsigned int m_stamp;
unsigned int m_maxLength;
};
BufferedLogCallback::BufferedLogCallback(sgDebugClass c, sgDebugPriority p) :
d(new BufferedLogCallbackPrivate)
{
d->m_class = c;
d->m_priority = p;
d->m_stamp = 0;
d->m_maxLength = 0xffff;
}
BufferedLogCallback::~BufferedLogCallback()
{
BOOST_FOREACH(unsigned char* msg, d->m_buffer) {
free(msg);
}
}
void BufferedLogCallback::operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage)
{
SG_UNUSED(file);
SG_UNUSED(line);
if ((c & d->m_class) == 0 || p < d->m_priority) return;
vector_cstring::value_type msg;
if (aMessage.size() >= d->m_maxLength) {
msg = (vector_cstring::value_type) malloc(d->m_maxLength);
strncpy((char*) msg, aMessage.c_str(), d->m_maxLength - 1);
msg[d->m_maxLength - 1] = 0; // add final NULL byte
} else {
msg = (vector_cstring::value_type) strdup(aMessage.c_str());
}
SGGuard<SGMutex> g(d->m_mutex);
d->m_buffer.push_back(msg);
d->m_stamp++;
}
unsigned int BufferedLogCallback::stamp() const
{
return d->m_stamp;
}
unsigned int BufferedLogCallback::threadsafeCopy(vector_cstring& aOutput)
{
SGGuard<SGMutex> g(d->m_mutex);
size_t sz = d->m_buffer.size();
aOutput.resize(sz);
memcpy(aOutput.data(), d->m_buffer.data(), sz * sizeof(vector_cstring::value_type));
return d->m_stamp;
}
void BufferedLogCallback::truncateAt(unsigned int t)
{
d->m_maxLength = t;
}
} // of namespace simgear

View File

@@ -0,0 +1,79 @@
/** \file BufferedLogCallback.hxx
* Buffer certain log messages permanently for later retrieval and display
*/
// Copyright (C) 2013 James Turner zakalawe@mac.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef SG_DEBUG_BUFFEREDLOGCALLBACK_HXX
#define SG_DEBUG_BUFFEREDLOGCALLBACK_HXX
#include <vector>
#include <memory> // for std::auto_ptr
#include <simgear/debug/logstream.hxx>
namespace simgear
{
class BufferedLogCallback : public LogCallback
{
public:
BufferedLogCallback(sgDebugClass c, sgDebugPriority p);
virtual ~BufferedLogCallback();
/// truncate messages longer than a certain length. This is to work-around
/// for broken PUI behaviour, it can be removed once PUI is gone.
void truncateAt(unsigned int);
virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage);
/**
* read the stamp value associated with the log buffer. This is
* incremented whenever the log contents change, so can be used
* to poll for changes.
*/
unsigned int stamp() const;
/**
* copying a (large) vector of std::string would be very expensive.
* once logged, this call retains storage of the underlying string data,
* so when copying, it's sufficient to pass around the strings as raw
* char arrays. This means we're only copying a vector of pointers,
* which is very efficient.
*/
typedef std::vector<unsigned char*> vector_cstring;
/**
* copy the buffered log data into the provided output list
* (which will be cleared first). This method is safe to call from
* any thread.
*
* returns the stamp value of the copied data
*/
unsigned int threadsafeCopy(vector_cstring& aOutput);
private:
class BufferedLogCallbackPrivate;
std::auto_ptr<BufferedLogCallbackPrivate> d;
};
} // of namespace simgear
#endif // of SG_DEBUG_BUFFEREDLOGCALLBACK_HXX

View File

@@ -1,7 +1,7 @@
include (SimGearComponent)
set(HEADERS debug_types.h logstream.hxx)
set(SOURCES logstream.cxx)
set(HEADERS debug_types.h logstream.hxx BufferedLogCallback.hxx)
set(SOURCES logstream.cxx BufferedLogCallback.cxx)
simgear_component(debug debug "${SOURCES}" "${HEADERS}")

View File

@@ -20,95 +20,342 @@
//
// $Id$
#include <iostream>
#include "logstream.hxx"
logstream *logstream::global_logstream = 0;
#include <iostream>
#include <fstream>
#include <algorithm>
#include <boost/foreach.hpp>
#include <simgear/sg_inlines.h>
#include <simgear/threads/SGThread.hxx>
#include <simgear/threads/SGQueue.hxx>
#include <simgear/threads/SGGuard.hxx>
#include <simgear/misc/sg_path.hxx>
bool logbuf::logging_enabled = true;
#ifdef _WIN32
bool logbuf::has_console = true;
// for AllocConsole
#include "windows.h"
#endif
sgDebugClass logbuf::logClass = SG_NONE;
sgDebugPriority logbuf::logPriority = SG_INFO;
streambuf* logbuf::sbuf = NULL;
namespace {
struct ignore_me
const char* debugClassToString(sgDebugClass c)
{
ignore_me()
switch (c) {
case SG_NONE: return "none";
case SG_TERRAIN: return "terrain";
case SG_ASTRO: return "astro";
case SG_FLIGHT: return "flight";
case SG_INPUT: return "input";
case SG_GL: return "opengl";
case SG_VIEW: return "view";
case SG_COCKPIT: return "cockpit";
case SG_GENERAL: return "general";
case SG_MATH: return "math";
case SG_EVENT: return "event";
case SG_AIRCRAFT: return "aircraft";
case SG_AUTOPILOT: return "autopilot";
case SG_IO: return "io";
case SG_CLIPPER: return "clipper";
case SG_NETWORK: return "network";
case SG_ATC: return "atc";
case SG_NASAL: return "nasal";
case SG_INSTR: return "instruments";
case SG_SYSTEMS: return "systems";
case SG_AI: return "ai";
case SG_ENVIRONMENT:return "environment";
case SG_SOUND: return "sound";
case SG_NAVAID: return "navaid";
default: return "unknown";
}
}
//////////////////////////////////////////////////////////////////////////////
class FileLogCallback : public simgear::LogCallback
{
public:
FileLogCallback(const std::string& aPath, sgDebugClass c, sgDebugPriority p) :
m_file(aPath.c_str(), std::ios_base::out | std::ios_base::trunc),
m_class(c),
m_priority(p)
{
logstream::initGlobalLogstream();
}
virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& message)
{
if ((c & m_class) == 0 || p < m_priority) return;
m_file << debugClassToString(c) << ":" << (int) p
<< ":" << file << ":" << line << ":" << message << std::endl;
}
private:
std::ofstream m_file;
sgDebugClass m_class;
sgDebugPriority m_priority;
};
class StderrLogCallback : public simgear::LogCallback
{
public:
StderrLogCallback(sgDebugClass c, sgDebugPriority p) :
m_class(c),
m_priority(p)
{
#ifdef _WIN32
AllocConsole(); // but only if we want a console
freopen("conin$", "r", stdin);
freopen("conout$", "w", stdout);
freopen("conout$", "w", stderr);
#endif
}
void setLogLevels( sgDebugClass c, sgDebugPriority p )
{
m_priority = p;
m_class = c;
}
virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage)
{
if ((c & m_class) == 0 || p < m_priority) return;
// if running under MSVC, we could use OutputDebugString here
fprintf(stderr, "%s\n", aMessage.c_str());
//fprintf(stderr, "%s:%d:%s:%d:%s\n", debugClassToString(c), p,
// file, line, aMessage.c_str());
fflush(stderr);
}
private:
sgDebugClass m_class;
sgDebugPriority m_priority;
};
class LogStreamPrivate : public SGThread
{
private:
/**
* storage of a single log entry. Note this is not used for a persistent
* store, but rather for short term buffering between the submitting
* and output threads.
*/
class LogEntry
{
public:
LogEntry(sgDebugClass c, sgDebugPriority p,
const char* f, int l, const std::string& msg) :
debugClass(c), debugPriority(p), file(f), line(l),
message(msg)
{
}
sgDebugClass debugClass;
sgDebugPriority debugPriority;
const char* file;
int line;
std::string message;
};
class PauseThread
{
public:
PauseThread(LogStreamPrivate* parent) : m_parent(parent)
{
m_wasRunning = m_parent->stop();
}
~PauseThread()
{
if (m_wasRunning) {
m_parent->startLog();
}
}
private:
LogStreamPrivate* m_parent;
bool m_wasRunning;
};
public:
LogStreamPrivate() :
m_logClass(SG_ALL),
m_logPriority(SG_ALERT),
m_isRunning(false)
{
m_stderrCallback = new StderrLogCallback(m_logClass, m_logPriority);
addCallback(m_stderrCallback);
}
SGMutex m_lock;
SGBlockingQueue<LogEntry> m_entries;
typedef std::vector<simgear::LogCallback*> CallbackVec;
CallbackVec m_callbacks;
sgDebugClass m_logClass;
sgDebugPriority m_logPriority;
bool m_isRunning;
StderrLogCallback* m_stderrCallback;
void startLog()
{
SGGuard<SGMutex> g(m_lock);
if (m_isRunning) return;
m_isRunning = true;
start();
}
virtual void run()
{
while (1) {
LogEntry entry(m_entries.pop());
// special marker entry detected, terminate the thread since we are
// making a configuration change or quitting the app
if ((entry.debugClass == SG_NONE) && !strcmp(entry.file, "done")) {
return;
}
// submit to each installed callback in turn
BOOST_FOREACH(simgear::LogCallback* cb, m_callbacks) {
(*cb)(entry.debugClass, entry.debugPriority,
entry.file, entry.line, entry.message);
}
} // of main thread loop
}
bool stop()
{
SGGuard<SGMutex> g(m_lock);
if (!m_isRunning) {
return false;
}
// log a special marker value, which will cause the thread to wakeup,
// and then exit
log(SG_NONE, SG_ALERT, "done", -1, "");
join();
m_isRunning = false;
return true;
}
void addCallback(simgear::LogCallback* cb)
{
PauseThread pause(this);
m_callbacks.push_back(cb);
}
void removeCallback(simgear::LogCallback* cb)
{
PauseThread pause(this);
CallbackVec::iterator it = std::find(m_callbacks.begin(), m_callbacks.end(), cb);
if (it != m_callbacks.end()) {
m_callbacks.erase(it);
}
}
void setLogLevels( sgDebugClass c, sgDebugPriority p )
{
PauseThread pause(this);
m_logPriority = p;
m_logClass = c;
m_stderrCallback->setLogLevels(c, p);
}
bool would_log( sgDebugClass c, sgDebugPriority p ) const
{
if (p >= SG_INFO) return true;
return ((c & m_logClass) != 0 && p >= m_logPriority);
}
void log( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg)
{
LogEntry entry(c, p, fileName, line, msg);
m_entries.push(entry);
}
};
static ignore_me im;
}
logbuf::logbuf()
/////////////////////////////////////////////////////////////////////////////
static logstream* global_logstream = NULL;
static LogStreamPrivate* global_privateLogstream = NULL;
logstream::logstream()
{
// if ( sbuf == NULL )
// sbuf = cerr.rdbuf();
}
logbuf::~logbuf()
{
if ( sbuf )
sync();
}
void
logbuf::set_sb( streambuf* sb )
{
if ( sbuf )
sync();
sbuf = sb;
}
void
logbuf::set_log_level( sgDebugClass c, sgDebugPriority p )
{
logClass = c;
logPriority = p;
}
void
logbuf::set_log_classes (sgDebugClass c)
{
logClass = c;
}
sgDebugClass
logbuf::get_log_classes ()
{
return logClass;
}
void
logbuf::set_log_priority (sgDebugPriority p)
{
logPriority = p;
}
sgDebugPriority
logbuf::get_log_priority ()
{
return logPriority;
global_privateLogstream = new LogStreamPrivate;
global_privateLogstream->startLog();
}
void
logstream::setLogLevels( sgDebugClass c, sgDebugPriority p )
{
logbuf::set_log_level( c, p );
global_privateLogstream->setLogLevels(c, p);
}
logstream *
logstream::initGlobalLogstream()
void
logstream::addCallback(simgear::LogCallback* cb)
{
global_privateLogstream->addCallback(cb);
}
void
logstream::removeCallback(simgear::LogCallback* cb)
{
global_privateLogstream->removeCallback(cb);
}
void
logstream::log( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg)
{
global_privateLogstream->log(c, p, fileName, line, msg);
}
bool
logstream::would_log( sgDebugClass c, sgDebugPriority p ) const
{
return global_privateLogstream->would_log(c,p);
}
sgDebugClass
logstream::get_log_classes() const
{
return global_privateLogstream->m_logClass;
}
sgDebugPriority
logstream::get_log_priority() const
{
return global_privateLogstream->m_logPriority;
}
void
logstream::set_log_priority( sgDebugPriority p)
{
global_privateLogstream->setLogLevels(global_privateLogstream->m_logClass, p);
}
void
logstream::set_log_classes( sgDebugClass c)
{
global_privateLogstream->setLogLevels(c, global_privateLogstream->m_logPriority);
}
logstream&
sglog()
{
// Force initialization of cerr.
static std::ios_base::Init initializer;
if( !global_logstream )
global_logstream = new logstream(std::cerr);
return global_logstream;
global_logstream = new logstream();
return *global_logstream;
}
void
logstream::logToFile( const SGPath& aPath, sgDebugClass c, sgDebugPriority p )
{
global_privateLogstream->addCallback(new FileLogCallback(aPath.str(), c, p));
}

View File

@@ -26,222 +26,33 @@
#define _LOGSTREAM_H
#include <simgear/compiler.h>
#ifdef _WIN32
# include <windows.h>
#endif
#include <streambuf>
#include <ostream>
#include <cstdio>
#include <simgear/debug/debug_types.h>
using std::streambuf;
using std::ostream;
//
// TODO:
//
// 1. Change output destination. Done.
// 2. Make logbuf thread safe.
// 3. Read environment for default debugClass and debugPriority.
//
/**
* logbuf is an output-only streambuf with the ability to disable sets of
* messages at runtime. Only messages with priority >= logbuf::logPriority
* and debugClass == logbuf::logClass are output.
*/
#ifdef SG_NEED_STREAMBUF_HACK
class logbuf : public __streambuf
#else
class logbuf : public std::streambuf
#endif
#include <sstream>
// forward decls
class SGPath;
namespace simgear
{
class LogCallback
{
public:
// logbuf( streambuf* sb ) : sbuf(sb) {}
/** Constructor */
logbuf();
/** Destructor */
~logbuf();
/**
* Is logging enabled?
* @return true or false*/
bool enabled() { return logging_enabled; }
/**
* Set the logging level of subsequent messages.
* @param c debug class
* @param p priority
*/
void set_log_state( sgDebugClass c, sgDebugPriority p );
bool would_log( sgDebugClass c, sgDebugPriority p ) const;
/**
* Set the global logging level.
* @param c debug class
* @param p priority
*/
static void set_log_level( sgDebugClass c, sgDebugPriority p );
/**
* Set the allowed logging classes.
* @param c All enabled logging classes anded together.
*/
static void set_log_classes (sgDebugClass c);
/**
* Get the logging classes currently enabled.
* @return All enabled debug logging anded together.
*/
static sgDebugClass get_log_classes ();
/**
* Set the logging priority.
* @param c The priority cutoff for logging messages.
*/
static void set_log_priority (sgDebugPriority p);
/**
* Get the current logging priority.
* @return The priority cutoff for logging messages.
*/
static sgDebugPriority get_log_priority ();
/**
* Set the stream buffer
* @param sb stream buffer
*/
void set_sb( std::streambuf* sb );
#ifdef _WIN32
static void has_no_console() { has_console = false; }
#endif
protected:
/** sync/flush */
inline virtual int sync();
/** overflow */
int_type overflow( int ch );
// int xsputn( const char* s, istreamsize n );
private:
// The streambuf used for actual output. Defaults to cerr.rdbuf().
static std::streambuf* sbuf;
static bool logging_enabled;
#ifdef _WIN32
static bool has_console;
#endif
static sgDebugClass logClass;
static sgDebugPriority logPriority;
private:
// Not defined.
logbuf( const logbuf& );
void operator= ( const logbuf& );
};
inline int
logbuf::sync()
{
return sbuf->pubsync();
}
inline void
logbuf::set_log_state( sgDebugClass c, sgDebugPriority p )
{
logging_enabled = ((c & logClass) != 0 && p >= logPriority);
}
inline bool
logbuf::would_log( sgDebugClass c, sgDebugPriority p ) const
{
return ((c & logClass) != 0 && p >= logPriority);
}
inline logbuf::int_type
logbuf::overflow( int c )
{
#ifdef _WIN32
if ( logging_enabled ) {
if ( !has_console ) {
AllocConsole();
freopen("conin$", "r", stdin);
freopen("conout$", "w", stdout);
freopen("conout$", "w", stderr);
has_console = true;
}
return sbuf->sputc(c);
}
else
return EOF == 0 ? 1: 0;
#else
return logging_enabled ? sbuf->sputc(c) : (EOF == 0 ? 1: 0);
#endif
}
/**
* logstream manipulator for setting the log level of a message.
*/
struct loglevel
{
loglevel( sgDebugClass c, sgDebugPriority p )
: logClass(c), logPriority(p) {}
sgDebugClass logClass;
sgDebugPriority logPriority;
};
/**
* A helper class that ensures a streambuf and ostream are constructed and
* destroyed in the correct order. The streambuf must be created before the
* ostream but bases are constructed before members. Thus, making this class
* a private base of logstream, declared to the left of ostream, we ensure the
* correct order of construction and destruction.
*/
struct logstream_base
{
// logstream_base( streambuf* sb ) : lbuf(sb) {}
logstream_base() {}
logbuf lbuf;
virtual ~LogCallback() {}
virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage) = 0;
};
} // of namespace simgear
/**
* Class to manage the debug logging stream.
*/
class logstream : private logstream_base, public std::ostream
class logstream
{
public:
/**
* The default is to send messages to cerr.
* @param out output stream
*/
logstream( std::ostream& out )
// : logstream_base(out.rdbuf()),
: logstream_base(),
std::ostream(&lbuf) { lbuf.set_sb(out.rdbuf());}
/**
* Set the output stream
* @param out output stream
*/
void set_output( std::ostream& out ) { lbuf.set_sb( out.rdbuf() ); }
static void initGlobalLogstream();
/**
* Set the global log class and priority level.
* @param c debug class
@@ -249,42 +60,48 @@ public:
*/
void setLogLevels( sgDebugClass c, sgDebugPriority p );
bool would_log( sgDebugClass c, sgDebugPriority p ) const
{
return lbuf.would_log( c, p );
};
bool would_log( sgDebugClass c, sgDebugPriority p ) const;
void logToFile( const SGPath& aPath, sgDebugClass c, sgDebugPriority p );
void set_log_priority( sgDebugPriority p);
void set_log_classes( sgDebugClass c);
sgDebugClass get_log_classes() const;
sgDebugPriority get_log_priority() const;
/**
* Output operator to capture the debug level and priority of a message.
* @param l log level
* the core logging method
*/
inline std::ostream& operator<< ( const loglevel& l );
void log( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg);
/**
* \relates logstream
* Return the one and only logstream instance.
* We use a function instead of a global object so we are assured that cerr
* has been initialised.
* @return current logstream
*/
friend logstream& sglog();
static logstream *initGlobalLogstream();
protected:
static logstream *global_logstream;
/**
* register a logging callback. Note callbacks are run in a
* dedicated thread, so callbacks which pass data to other threads
* must use appropriate locking.
*/
void addCallback(simgear::LogCallback* cb);
void removeCallback(simgear::LogCallback* cb);
private:
// constructor
logstream();
};
inline std::ostream&
logstream::operator<< ( const loglevel& l )
{
lbuf.set_log_state( l.logClass, l.logPriority );
return *this;
}
/**
* \relates logstream
* Return the one and only logstream instance.
* We use a function instead of a global object so we are assured that cerr
* has been initialised.
* @return current logstream
*/
inline logstream&
sglog()
{
return *logstream::initGlobalLogstream();
}
logstream& sglog();
/** \def SG_LOG(C,P,M)
* Log a message.
@@ -296,10 +113,12 @@ sglog()
# define SG_LOG(C,P,M)
#else
# define SG_LOG(C,P,M) do { \
logstream& __tmplogstreamref(sglog()); \
if(__tmplogstreamref.would_log(C,P)) { \
__tmplogstreamref << loglevel(C,P) << M << std::endl; } \
} while(0)
if(sglog().would_log(C,P)) { \
std::ostringstream os; \
os << M; \
sglog().log(C, P, __FILE__, __LINE__, os.str()); \
} \
} while(0)
#endif
#define SG_ORIGIN __FILE__ ":" SG_STRINGIZE(__LINE__)

View File

@@ -787,7 +787,7 @@ HLAFederate::processMessages()
bool
HLAFederate::readRTI13ObjectModelTemplate(const std::string& objectModel)
{
SG_LOG(SG_IO, SG_ALERT, "HLA version RTI13 not yet(!?) supported.");
SG_LOG(SG_IO, SG_WARN, "HLA version RTI13 not yet(!?) supported.");
return false;
}
@@ -816,7 +816,7 @@ HLAFederate::readRTI1516ObjectModelTemplate(const std::string& objectModel)
bool
HLAFederate::readRTI1516EObjectModelTemplate(const std::string& objectModel)
{
SG_LOG(SG_IO, SG_ALERT, "HLA version RTI1516E not yet(!?) supported.");
SG_LOG(SG_IO, SG_WARN, "HLA version RTI1516E not yet(!?) supported.");
return false;
}

View File

@@ -16,6 +16,10 @@ set(HEADERS
sg_socket_udp.hxx
HTTPClient.hxx
HTTPRequest.hxx
DAVMultiStatus.hxx
SVNRepository.hxx
SVNDirectory.hxx
SVNReportParser.hxx
)
set(SOURCES
@@ -32,21 +36,18 @@ set(SOURCES
sg_socket_udp.cxx
HTTPClient.cxx
HTTPRequest.cxx
DAVMultiStatus.cxx
SVNRepository.cxx
SVNDirectory.cxx
SVNReportParser.cxx
)
simgear_component(io io "${SOURCES}" "${HEADERS}")
if(ENABLE_TESTS)
if (SIMGEAR_SHARED)
set(TEST_LIBS SimGearCore)
else()
set(TEST_LIBS SimGearCore
${CMAKE_THREAD_LIBS_INIT}
${WINSOCK_LIBRARY}
${ZLIB_LIBRARY}
${RT_LIBRARY})
endif()
add_executable(http_svn http_svn.cxx)
target_link_libraries(http_svn ${TEST_LIBS})
add_executable(test_sock socktest.cxx)
target_link_libraries(test_sock ${TEST_LIBS})
@@ -66,4 +67,5 @@ add_executable(test_binobj test_binobj.cxx)
target_link_libraries(test_binobj ${TEST_LIBS})
add_test(binobj ${EXECUTABLE_OUTPUT_PATH}/test_binobj)
endif(ENABLE_TESTS)

View File

@@ -0,0 +1,375 @@
// DAVMultiStatus.cxx -- parser for WebDAV MultiStatus XML data
//
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "DAVMultiStatus.hxx"
#include <iostream>
#include <cstring>
#include <cassert>
#include <algorithm>
#include <sstream>
#include <boost/foreach.hpp>
#include "simgear/debug/logstream.hxx"
#include "simgear/xml/xmlparse.h"
#include "simgear/misc/strutils.hxx"
#include "simgear/structure/exception.hxx"
using std::string;
using namespace simgear;
#define DAV_NS "DAV::"
#define SUBVERSION_DAV_NS "http://subversion.tigris.org/xmlns/dav/"
const char* DAV_MULTISTATUS_TAG = DAV_NS "multistatus";
const char* DAV_RESPONSE_TAG = DAV_NS "response";
const char* DAV_PROPSTAT_TAG = DAV_NS "propstat";
const char* DAV_PROP_TAG = DAV_NS "prop";
const char* DAV_HREF_TAG = DAV_NS "href";
const char* DAV_RESOURCE_TYPE_TAG = DAV_NS "resourcetype";
const char* DAV_CONTENT_TYPE_TAG = DAV_NS "getcontenttype";
const char* DAV_CONTENT_LENGTH_TAG = DAV_NS "getcontentlength";
const char* DAV_VERSIONNAME_TAG = DAV_NS "version-name";
const char* DAV_COLLECTION_TAG = DAV_NS "collection";
const char* DAV_VCC_TAG = DAV_NS "version-controlled-configuration";
const char* SUBVERSION_MD5_CHECKSUM_TAG = SUBVERSION_DAV_NS ":md5-checksum";
DAVResource::DAVResource(const string& href) :
_type(Unknown),
_url(href),
_container(NULL)
{
assert(!href.empty());
}
void DAVResource::setVersionName(const std::string& aVersion)
{
_versionName = aVersion;
}
void DAVResource::setVersionControlledConfiguration(const std::string& vcc)
{
_vcc = vcc;
}
void DAVResource::setMD5(const std::string& md5Hex)
{
_md5 = md5Hex;
}
std::string DAVResource::name() const
{
string::size_type index = _url.rfind('/');
if (index != string::npos) {
return _url.substr(index + 1);
}
throw sg_exception("bad DAV resource HREF:" + _url);
}
////////////////////////////////////////////////////////////////////////////
DAVCollection::DAVCollection(const string& href) :
DAVResource(href)
{
_type = DAVResource::Collection;
}
DAVCollection::~DAVCollection()
{
BOOST_FOREACH(DAVResource* c, _contents) {
delete c;
}
}
void DAVCollection::addChild(DAVResource *res)
{
assert(res);
if (res->container() == this) {
return;
}
assert(res->container() == NULL);
assert(std::find(_contents.begin(), _contents.end(), res) == _contents.end());
assert(strutils::starts_with(res->url(), _url));
assert(childWithUrl(res->url()) == NULL);
res->_container = this;
_contents.push_back(res);
}
void DAVCollection::removeChild(DAVResource* res)
{
assert(res);
assert(res->container() == this);
res->_container = NULL;
DAVResourceList::iterator it = std::find(_contents.begin(), _contents.end(), res);
assert(it != _contents.end());
_contents.erase(it);
}
DAVCollection*
DAVCollection::createChildCollection(const std::string& name)
{
DAVCollection* child = new DAVCollection(urlForChildWithName(name));
addChild(child);
return child;
}
DAVResourceList DAVCollection::contents() const
{
return _contents;
}
DAVResource* DAVCollection::childWithUrl(const string& url) const
{
if (url.empty())
return NULL;
BOOST_FOREACH(DAVResource* c, _contents) {
if (c->url() == url) {
return c;
}
}
return NULL;
}
DAVResource* DAVCollection::childWithName(const string& name) const
{
return childWithUrl(urlForChildWithName(name));
}
std::string DAVCollection::urlForChildWithName(const std::string& name) const
{
return url() + "/" + name;
}
///////////////////////////////////////////////////////////////////////////////
class DAVMultiStatus::DAVMultiStatusPrivate
{
public:
DAVMultiStatusPrivate() :
parserInited(false)
{
rootResource = NULL;
}
void startElement (const char * name)
{
if (tagStack.empty()) {
if (strcmp(name, DAV_MULTISTATUS_TAG)) {
SG_LOG(SG_IO, SG_WARN, "root element is not " <<
DAV_MULTISTATUS_TAG << ", got:" << name);
} else {
}
} else {
// not at the root element
if (tagStack.back() == DAV_MULTISTATUS_TAG) {
if (strcmp(name, DAV_RESPONSE_TAG)) {
SG_LOG(SG_IO, SG_WARN, "multistatus child is not response: saw:"
<< name);
}
}
if (tagStack.back() == DAV_RESOURCE_TYPE_TAG) {
if (!strcmp(name, DAV_COLLECTION_TAG)) {
currentElementType = DAVResource::Collection;
} else {
currentElementType = DAVResource::Unknown;
}
}
}
tagStack.push_back(name);
if (!strcmp(name, DAV_RESPONSE_TAG)) {
currentElementType = DAVResource::Unknown;
currentElementUrl.clear();
currentElementMD5.clear();
currentVersionName.clear();
currentVCC.clear();
}
}
void endElement (const char * name)
{
assert(tagStack.back() == name);
tagStack.pop_back();
if (!strcmp(name, DAV_RESPONSE_TAG)) {
// finish complete response
currentElementUrl = strutils::strip(currentElementUrl);
DAVResource* res = NULL;
if (currentElementType == DAVResource::Collection) {
DAVCollection* col = new DAVCollection(currentElementUrl);
res = col;
} else {
res = new DAVResource(currentElementUrl);
}
res->setVersionName(strutils::strip(currentVersionName));
res->setVersionControlledConfiguration(currentVCC);
if (rootResource &&
strutils::starts_with(currentElementUrl, rootResource->url()))
{
static_cast<DAVCollection*>(rootResource)->addChild(res);
}
if (!rootResource) {
rootResource = res;
}
}
}
void data (const char * s, int length)
{
if (tagStack.back() == DAV_HREF_TAG) {
if (tagN(1) == DAV_RESPONSE_TAG) {
currentElementUrl += string(s, length);
} else if (tagN(1) == DAV_VCC_TAG) {
currentVCC += string(s, length);
}
} else if (tagStack.back() == SUBVERSION_MD5_CHECKSUM_TAG) {
currentElementMD5 = string(s, length);
} else if (tagStack.back() == DAV_VERSIONNAME_TAG) {
currentVersionName = string(s, length);
} else if (tagStack.back() == DAV_CONTENT_LENGTH_TAG) {
std::istringstream is(string(s, length));
is >> currentElementLength;
}
}
void pi (const char * target, const char * data) {}
string tagN(const unsigned int n) const
{
size_t sz = tagStack.size();
if (n >= sz) {
return string();
}
return tagStack[sz - (1 + n)];
}
bool parserInited;
XML_Parser xmlParser;
DAVResource* rootResource;
// in-flight data
string_list tagStack;
DAVResource::Type currentElementType;
string currentElementUrl,
currentVersionName,
currentVCC;
int currentElementLength;
string currentElementMD5;
};
////////////////////////////////////////////////////////////////////////
// Static callback functions for Expat.
////////////////////////////////////////////////////////////////////////
#define VISITOR static_cast<DAVMultiStatus::DAVMultiStatusPrivate *>(userData)
static void
start_element (void * userData, const char * name, const char ** atts)
{
VISITOR->startElement(name);
}
static void
end_element (void * userData, const char * name)
{
VISITOR->endElement(name);
}
static void
character_data (void * userData, const char * s, int len)
{
VISITOR->data(s, len);
}
static void
processing_instruction (void * userData,
const char * target,
const char * data)
{
VISITOR->pi(target, data);
}
#undef VISITOR
///////////////////////////////////////////////////////////////////////////////
DAVMultiStatus::DAVMultiStatus() :
_d(new DAVMultiStatusPrivate)
{
}
DAVMultiStatus::~DAVMultiStatus()
{
}
void DAVMultiStatus::parseXML(const char* data, int size)
{
if (!_d->parserInited) {
_d->xmlParser = XML_ParserCreateNS(0, ':');
XML_SetUserData(_d->xmlParser, _d.get());
XML_SetElementHandler(_d->xmlParser, start_element, end_element);
XML_SetCharacterDataHandler(_d->xmlParser, character_data);
XML_SetProcessingInstructionHandler(_d->xmlParser, processing_instruction);
_d->parserInited = true;
}
if (!XML_Parse(_d->xmlParser, data, size, false)) {
SG_LOG(SG_IO, SG_WARN, "DAV parse error:" << XML_ErrorString(XML_GetErrorCode(_d->xmlParser))
<< " at line:" << XML_GetCurrentLineNumber(_d->xmlParser)
<< " column " << XML_GetCurrentColumnNumber(_d->xmlParser));
XML_ParserFree(_d->xmlParser);
_d->parserInited = false;
}
}
void DAVMultiStatus::finishParse()
{
if (_d->parserInited) {
XML_Parse(_d->xmlParser, NULL, 0, true);
XML_ParserFree(_d->xmlParser);
}
_d->parserInited = false;
}
DAVResource* DAVMultiStatus::resource()
{
return _d->rootResource;
}

View File

@@ -0,0 +1,141 @@
// DAVMultiStatus.hxx -- parser for WebDAV MultiStatus XML data
//
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SG_IO_DAVMULTISTATUS_HXX
#define SG_IO_DAVMULTISTATUS_HXX
#include <string>
#include <vector>
#include <memory> // for auto_ptr
namespace simgear
{
class DAVCollection;
class DAVResource
{
public:
DAVResource(const std::string& url);
virtual ~DAVResource() { }
typedef enum {
Unknown = 0,
Collection = 1
} Type;
const Type type() const
{ return _type; }
const std::string& url() const
{ return _url; }
std::string name() const;
/**
* SVN servers use this field to expose the head revision
* of the resource, which is useful
*/
const std::string& versionName() const
{ return _versionName; }
void setVersionName(const std::string& aVersion);
DAVCollection* container() const
{ return _container; }
virtual bool isCollection() const
{ return false; }
void setVersionControlledConfiguration(const std::string& vcc);
const std::string& versionControlledConfiguration() const
{ return _vcc; }
void setMD5(const std::string& md5Hex);
const std::string& md5() const
{ return _md5; }
protected:
friend class DAVCollection;
Type _type;
std::string _url;
std::string _versionName;
std::string _vcc;
std::string _md5;
DAVCollection* _container;
};
typedef std::vector<DAVResource*> DAVResourceList;
class DAVCollection : public DAVResource
{
public:
DAVCollection(const std::string& url);
virtual ~DAVCollection();
DAVResourceList contents() const;
void addChild(DAVResource* res);
void removeChild(DAVResource* res);
DAVCollection* createChildCollection(const std::string& name);
/**
* find the collection member with the specified URL, or return NULL
* if no such member of this collection exists.
*/
DAVResource* childWithUrl(const std::string& url) const;
/**
* find the collection member with the specified name, or return NULL
*/
DAVResource* childWithName(const std::string& name) const;
/**
* wrapper around URL manipulation
*/
std::string urlForChildWithName(const std::string& name) const;
virtual bool isCollection() const
{ return true; }
private:
DAVResourceList _contents;
};
class DAVMultiStatus
{
public:
DAVMultiStatus();
~DAVMultiStatus();
// incremental XML parsing
void parseXML(const char* data, int size);
void finishParse();
DAVResource* resource();
class DAVMultiStatusPrivate;
private:
std::auto_ptr<DAVMultiStatusPrivate> _d;
};
} // of namespace simgear
#endif // of SG_IO_DAVMULTISTATUS_HXX

View File

@@ -47,12 +47,14 @@ const int ZLIB_INFLATE_WINDOW_BITS = -MAX_WBITS;
const int GZIP_HEADER_ID1 = 31;
const int GZIP_HEADER_ID2 = 139;
const int GZIP_HEADER_METHOD_DEFLATE = 8;
const int GZIP_HEADER_SIZE = 10;
const unsigned int GZIP_HEADER_SIZE = 10;
const int GZIP_HEADER_FEXTRA = 1 << 2;
const int GZIP_HEADER_FNAME = 1 << 3;
const int GZIP_HEADER_COMMENT = 1 << 4;
const int GZIP_HEADER_CRC = 1 << 1;
typedef std::list<Request_ptr> RequestList;
class Connection : public NetChat
{
public:
@@ -88,13 +90,24 @@ public:
virtual void handleError(int error)
{
if (error == ENOENT) {
// name lookup failure, abandon all requests on this connection
// name lookup failure
// we won't have an active request yet, so the logic below won't
// fire to actually call setFailure. Let's fail all of the requests
BOOST_FOREACH(Request_ptr req, sentRequests) {
req->setFailure(error, "hostname lookup failure");
}
BOOST_FOREACH(Request_ptr req, queuedRequests) {
req->setFailure(error, "hostname lookup failure");
}
// name lookup failure, abandon all requests on this connection
sentRequests.clear();
queuedRequests.clear();
}
NetChat::handleError(error);
if (activeRequest) {
if (activeRequest) {
SG_LOG(SG_IO, SG_INFO, "HTTP socket error");
activeRequest->setFailure(error, "socket error");
activeRequest = NULL;
@@ -106,13 +119,27 @@ public:
virtual void handleClose()
{
NetChat::handleClose();
if ((state == STATE_GETTING_BODY) && activeRequest) {
// closing of the connection from the server side when getting the body,
bool canCloseState = (state == STATE_GETTING_BODY);
if (canCloseState && activeRequest) {
// force state here, so responseComplete can avoid closing the
// socket again
state = STATE_CLOSED;
responseComplete();
} else {
if (activeRequest) {
activeRequest->setFailure(500, "server closed connection");
// remove the failed request from sentRequests, so it does
// not get restored
RequestList::iterator it = std::find(sentRequests.begin(),
sentRequests.end(), activeRequest);
if (it != sentRequests.end()) {
sentRequests.erase(it);
}
activeRequest = NULL;
}
state = STATE_CLOSED;
}
@@ -175,6 +202,7 @@ public:
}
Request_ptr r = queuedRequests.front();
r->requestStart();
requestBodyBytesToSend = r->requestBodyLength();
stringstream headerData;
@@ -218,6 +246,7 @@ public:
bool ok = push(headerData.str().c_str());
if (!ok) {
SG_LOG(SG_IO, SG_WARN, "HTTPClient: over-stuffed the socket");
// we've over-stuffed the socket, give up for now, let things
// drain down before trying to start any more requests.
return;
@@ -225,8 +254,7 @@ public:
while (requestBodyBytesToSend > 0) {
char buf[4096];
int len = 4096;
r->getBodyData(buf, len);
int len = r->getBodyData(buf, 4096);
if (len > 0) {
requestBodyBytesToSend -= len;
if (!bufferSend(buf, len)) {
@@ -234,13 +262,16 @@ public:
state = STATE_SOCKET_ERROR;
return;
}
// SG_LOG(SG_IO, SG_INFO, "sent body:\n" << string(buf, len) << "\n%%%%%%%%%");
} else {
SG_LOG(SG_IO, SG_WARN, "asynchronous request body generation is unsupported");
SG_LOG(SG_IO, SG_WARN, "HTTP asynchronous request body generation is unsupported");
break;
}
}
//std::cout << "did send request:" << r->url() << std::endl;
SG_LOG(SG_IO, SG_DEBUG, "did start request:" << r->url() <<
"\n\t @ " << reinterpret_cast<void*>(r.ptr()) <<
"\n\t on connection " << this);
// successfully sent, remove from queue, and maybe send the next
queuedRequests.pop_front();
sentRequests.push_back(r);
@@ -287,25 +318,51 @@ public:
zlib.next_out = zlibOutputBuffer;
zlib.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
if (contentGZip) {
if (contentGZip && !handleGZipHeader()) {
return;
}
int writtenSize = 0;
do {
int result = inflate(&zlib, Z_NO_FLUSH);
if (result == Z_OK || result == Z_STREAM_END) {
// nothing to do
} else {
SG_LOG(SG_IO, SG_WARN, "HTTP: got Zlib error:" << result);
return;
}
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlib.avail_out;
if (result == Z_STREAM_END) {
break;
}
} while ((writtenSize == 0) && (zlib.avail_in > 0));
if (writtenSize > 0) {
activeRequest->processBodyBytes((const char*) zlibOutputBuffer, writtenSize);
}
}
bool handleGZipHeader()
{
// we clear this down to contentDeflate once the GZip header has been seen
if (reqSize < GZIP_HEADER_SIZE) {
return; // need more header bytes
if (zlib.avail_in < GZIP_HEADER_SIZE) {
return false; // need more header bytes
}
if ((zlibInflateBuffer[0] != GZIP_HEADER_ID1) ||
(zlibInflateBuffer[1] != GZIP_HEADER_ID2) ||
(zlibInflateBuffer[2] != GZIP_HEADER_METHOD_DEFLATE))
{
return; // invalid GZip header
return false; // invalid GZip header
}
char flags = zlibInflateBuffer[3];
int gzipHeaderSize = GZIP_HEADER_SIZE;
unsigned int gzipHeaderSize = GZIP_HEADER_SIZE;
if (flags & GZIP_HEADER_FEXTRA) {
gzipHeaderSize += 2;
if (reqSize < gzipHeaderSize) {
return; // need more header bytes
if (zlib.avail_in < gzipHeaderSize) {
return false; // need more header bytes
}
unsigned short extraHeaderBytes = *(reinterpret_cast<unsigned short*>(zlibInflateBuffer + GZIP_HEADER_FEXTRA));
@@ -314,14 +371,14 @@ public:
}
gzipHeaderSize += extraHeaderBytes;
if (reqSize < gzipHeaderSize) {
return; // need more header bytes
if (zlib.avail_in < gzipHeaderSize) {
return false; // need more header bytes
}
}
if (flags & GZIP_HEADER_FNAME) {
gzipHeaderSize++;
while (gzipHeaderSize <= reqSize) {
while (gzipHeaderSize <= zlib.avail_in) {
if (zlibInflateBuffer[gzipHeaderSize-1] == 0) {
break; // found terminating NULL character
}
@@ -330,7 +387,7 @@ public:
if (flags & GZIP_HEADER_COMMENT) {
gzipHeaderSize++;
while (gzipHeaderSize <= reqSize) {
while (gzipHeaderSize <= zlib.avail_in) {
if (zlibInflateBuffer[gzipHeaderSize-1] == 0) {
break; // found terminating NULL character
}
@@ -341,33 +398,16 @@ public:
gzipHeaderSize += 2;
}
if (reqSize < gzipHeaderSize) {
return; // need more header bytes
if (zlib.avail_in < gzipHeaderSize) {
return false; // need more header bytes
}
zlib.next_in += gzipHeaderSize;
zlib.avail_in = reqSize - gzipHeaderSize;
zlib.avail_in -= gzipHeaderSize;
// now we've processed the GZip header, can decode as deflate
contentGZip = false;
contentDeflate = true;
}
int writtenSize = 0;
do {
int result = inflate(&zlib, Z_NO_FLUSH);
if (result == Z_OK || result == Z_STREAM_END) {
} else {
SG_LOG(SG_IO, SG_WARN, "got Zlib error:" << result);
return;
}
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlib.avail_out;
} while ((writtenSize == 0) && (zlib.avail_in > 0));
if (writtenSize > 0) {
activeRequest->processBodyBytes((const char*) zlibOutputBuffer, writtenSize);
}
return true;
}
virtual void foundTerminator(void)
@@ -394,6 +434,7 @@ public:
case STATE_GETTING_CHUNKED_BYTES:
setTerminator("\r\n");
state = STATE_GETTING_CHUNKED;
buffer.clear();
break;
@@ -434,6 +475,11 @@ public:
{
return !queuedRequests.empty() && (sentRequests.size() < MAX_INFLIGHT_REQUESTS);
}
bool isActive() const
{
return !queuedRequests.empty() || !sentRequests.empty();
}
private:
bool connectToHost()
{
@@ -542,7 +588,7 @@ private:
// blank line after chunk data
return;
}
int chunkSize = 0;
int semiPos = buffer.find(';');
if (semiPos >= 0) {
@@ -581,7 +627,7 @@ private:
void responseComplete()
{
//std::cout << "responseComplete:" << activeRequest->url() << std::endl;
// SG_LOG(SG_IO, SG_INFO, "*** responseComplete:" << activeRequest->url());
activeRequest->responseComplete();
client->requestFinished(this);
@@ -640,8 +686,8 @@ private:
unsigned char* zlibOutputBuffer;
bool contentGZip, contentDeflate;
std::list<Request_ptr> queuedRequests;
std::list<Request_ptr> sentRequests;
RequestList queuedRequests;
RequestList sentRequests;
};
Client::Client()
@@ -651,7 +697,7 @@ Client::Client()
void Client::update(int waitTimeout)
{
NetChannel::poll(waitTimeout);
_poller.poll(waitTimeout);
ConnectionDict::iterator it = _connections.begin();
for (; it != _connections.end(); ) {
@@ -689,6 +735,7 @@ void Client::makeRequest(const Request_ptr& r)
if (_connections.find(connectionId) == _connections.end()) {
Connection* con = new Connection(this);
con->setServer(host, port);
_poller.addChannel(con);
_connections[connectionId] = con;
}
@@ -712,6 +759,16 @@ void Client::setProxy(const string& proxy, int port, const string& auth)
_proxyAuth = auth;
}
bool Client::hasActiveRequests() const
{
ConnectionDict::const_iterator it = _connections.begin();
for (; it != _connections.end(); ++it) {
if (it->second->isActive()) return true;
}
return false;
}
} // of namespace HTTP
} // of namespace simgear

View File

@@ -4,6 +4,7 @@
#include <map>
#include <simgear/io/HTTPRequest.hxx>
#include <simgear/io/sg_netChannel.hxx>
namespace simgear
{
@@ -33,6 +34,12 @@ public:
const std::string& proxyAuth() const
{ return _proxyAuth; }
/**
* predicate, check if at least one connection is active, with at
* least one request active or queued.
*/
bool hasActiveRequests() const;
private:
void requestFinished(Connection* con);
@@ -43,6 +50,7 @@ private:
std::string _proxy;
int _proxyPort;
std::string _proxyAuth;
NetChannelPoller _poller;
// connections by host
typedef std::map<std::string, Connection*> ConnectionDict;

View File

@@ -48,6 +48,11 @@ string Request::header(const std::string& name) const
return string();
}
void Request::requestStart()
{
}
void Request::responseStart(const string& r)
{
const int maxSplit = 2; // HTTP/1.1 nnn reason-string
@@ -232,10 +237,9 @@ std::string Request::requestBodyType() const
return "text/plain";
}
void Request::getBodyData(char*, int& count) const
int Request::getBodyData(char*, int maxCount) const
{
count = 0;
return;
return 0;
}
} // of namespace HTTP

View File

@@ -52,10 +52,10 @@ public:
/**
* Retrieve the body data bytes. Will be passed the maximum body bytes
* to return in the buffer, and should update count with the actual number
* to return in the buffer, and must return the actual number
* of bytes written.
*/
virtual void getBodyData(char* s, int& count) const;
virtual int getBodyData(char* s, int count) const;
/**
* retrieve the request body content type. Default is text/plain
@@ -86,6 +86,7 @@ public:
protected:
Request(const std::string& url, const std::string method = "GET");
virtual void requestStart();
virtual void responseStart(const std::string& r);
virtual void responseHeader(const std::string& key, const std::string& value);
virtual void responseHeadersComplete();

368
simgear/io/SVNDirectory.cxx Normal file
View File

@@ -0,0 +1,368 @@
#include "SVNDirectory.hxx"
#include <cassert>
#include <fstream>
#include <iostream>
#include <boost/foreach.hpp>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/io/DAVMultiStatus.hxx>
#include <simgear/io/SVNRepository.hxx>
#include <simgear/io/sg_file.hxx>
#include <simgear/io/SVNReportParser.hxx>
#include <simgear/package/md5.h>
#include <simgear/structure/exception.hxx>
using std::string;
using std::cout;
using std::endl;
using namespace simgear;
typedef std::vector<HTTP::Request_ptr> RequestVector;
typedef std::map<std::string, DAVResource*> DAVResourceMap;
const char* DAV_CACHE_NAME = ".terrasync_cache";
const char* CACHE_VERSION_4_TOKEN = "terrasync-cache-4";
const unsigned int MAX_UPDATE_REPORT_DEPTH = 3;
enum LineState
{
LINESTATE_HREF = 0,
LINESTATE_VERSIONNAME
};
SVNDirectory::SVNDirectory(SVNRepository *r, const SGPath& path) :
localPath(path),
dav(NULL),
repo(r),
_doingUpdateReport(false),
_parent(NULL)
{
if (path.exists()) {
parseCache();
}
// don't create dir here, repo might not exist at all
}
SVNDirectory::SVNDirectory(SVNDirectory* pr, DAVCollection* col) :
dav(col),
repo(pr->repository()),
_doingUpdateReport(false),
_parent(pr)
{
assert(col->container());
assert(!col->url().empty());
assert(_parent);
localPath = pr->fsDir().file(col->name());
if (!localPath.exists()) {
Dir d(localPath);
d.create(0755);
writeCache();
} else {
parseCache();
}
}
SVNDirectory::~SVNDirectory()
{
// recursive delete our child directories
BOOST_FOREACH(SVNDirectory* d, _children) {
delete d;
}
}
void SVNDirectory::parseCache()
{
SGPath p(localPath);
p.append(DAV_CACHE_NAME);
if (!p.exists()) {
return;
}
char href[1024];
char versionName[128];
LineState lineState = LINESTATE_HREF;
std::ifstream file(p.c_str());
bool doneSelf = false;
file.getline(href, 1024);
if (strcmp(CACHE_VERSION_4_TOKEN, href)) {
SG_LOG(SG_IO, SG_WARN, "invalid cache file:" << p.str());
return;
}
std::string vccUrl;
file.getline(href, 1024);
vccUrl = href;
while (!file.eof()) {
if (lineState == LINESTATE_HREF) {
file.getline(href, 1024);
lineState = LINESTATE_VERSIONNAME;
} else {
assert(lineState == LINESTATE_VERSIONNAME);
file.getline(versionName, 1024);
lineState = LINESTATE_HREF;
char* hrefPtr = href;
if (!doneSelf) {
if (!dav) {
dav = new DAVCollection(hrefPtr);
dav->setVersionName(versionName);
} else {
assert(string(hrefPtr) == dav->url());
}
if (!vccUrl.empty()) {
dav->setVersionControlledConfiguration(vccUrl);
}
_cachedRevision = versionName;
doneSelf = true;
} else {
DAVResource* child = addChildDirectory(hrefPtr)->collection();
child->setVersionName(versionName);
}
} // of line-state switching
} // of file get-line loop
}
void SVNDirectory::writeCache()
{
SGPath p(localPath);
if (!p.exists()) {
Dir d(localPath);
d.create(0755);
}
p.append(DAV_CACHE_NAME);
std::ofstream file(p.c_str(), std::ios::trunc);
// first, cache file version header
file << CACHE_VERSION_4_TOKEN << '\n';
// second, the repository VCC url
file << dav->versionControlledConfiguration() << '\n';
// third, our own URL, and version
file << dav->url() << '\n' << _cachedRevision << '\n';
BOOST_FOREACH(DAVResource* child, dav->contents()) {
if (child->isCollection()) {
file << child->name() << '\n' << child->versionName() << "\n";
}
} // of child iteration
}
void SVNDirectory::setBaseUrl(const string& url)
{
if (_parent) {
SG_LOG(SG_IO, SG_ALERT, "setting base URL on non-root directory " << url);
return;
}
if (dav && (url == dav->url())) {
return;
}
dav = new DAVCollection(url);
}
std::string SVNDirectory::url() const
{
if (!_parent) {
return repo->baseUrl();
}
return _parent->url() + "/" + name();
}
std::string SVNDirectory::name() const
{
return dav->name();
}
DAVResource*
SVNDirectory::addChildFile(const std::string& fileName)
{
DAVResource* child = NULL;
child = new DAVResource(dav->urlForChildWithName(fileName));
dav->addChild(child);
writeCache();
return child;
}
SVNDirectory*
SVNDirectory::addChildDirectory(const std::string& dirName)
{
if (dav->childWithName(dirName)) {
// existing child, let's remove it
deleteChildByName(dirName);
}
DAVCollection* childCol = dav->createChildCollection(dirName);
SVNDirectory* child = new SVNDirectory(this, childCol);
_children.push_back(child);
writeCache();
return child;
}
void SVNDirectory::deleteChildByName(const std::string& nm)
{
DAVResource* child = dav->childWithName(nm);
if (!child) {
// std::cerr << "ZZZ: deleteChildByName: unknown:" << nm << std::endl;
return;
}
SGPath path = fsDir().file(nm);
dav->removeChild(child);
delete child;
if (child->isCollection()) {
Dir d(path);
d.remove(true);
DirectoryList::iterator it = findChildDir(nm);
if (it != _children.end()) {
SVNDirectory* c = *it;
// std::cout << "YYY: deleting SVNDirectory for:" << nm << std::endl;
delete c;
_children.erase(it);
}
} else {
path.remove();
}
writeCache();
}
void SVNDirectory::requestFailed(HTTP::Request *req)
{
SG_LOG(SG_IO, SG_WARN, "Request failed for:" << req->url());
}
bool SVNDirectory::isDoingSync() const
{
if (_doingUpdateReport) {
return true;
}
BOOST_FOREACH(SVNDirectory* child, _children) {
if (child->isDoingSync()) {
return true;
} // of children
}
return false;
}
void SVNDirectory::beginUpdateReport()
{
_doingUpdateReport = true;
_cachedRevision.clear();
writeCache();
}
void SVNDirectory::updateReportComplete()
{
_cachedRevision = dav->versionName();
_doingUpdateReport = false;
writeCache();
SVNDirectory* pr = parent();
if (pr) {
pr->writeCache();
}
}
SVNRepository* SVNDirectory::repository() const
{
return repo;
}
void SVNDirectory::mergeUpdateReportDetails(unsigned int depth,
string_list& items)
{
// normal, easy case: we are fully in-sync at a revision
if (!_cachedRevision.empty()) {
std::ostringstream os;
os << "<S:entry rev=\"" << _cachedRevision << "\" depth=\"infinity\">"
<< repoPath() << "</S:entry>";
items.push_back(os.str());
return;
}
Dir d(localPath);
if (depth >= MAX_UPDATE_REPORT_DEPTH) {
std::cerr << localPath << "exceeded MAX_UPDATE_REPORT_DEPTH, cleaning" << std::endl;
d.removeChildren();
return;
}
PathList cs = d.children(Dir::NO_DOT_OR_DOTDOT | Dir::INCLUDE_HIDDEN | Dir::TYPE_DIR);
BOOST_FOREACH(SGPath path, cs) {
SVNDirectory* c = child(path.file());
if (!c) {
// ignore this child, if it's an incomplete download,
// it will be over-written on the update anyway
//std::cerr << "unknown SVN child" << path << std::endl;
} else {
// recurse down into children
c->mergeUpdateReportDetails(depth+1, items);
}
} // of child dir iteration
}
std::string SVNDirectory::repoPath() const
{
if (!_parent) {
return "/";
}
// find the length of the repository base URL, then
// trim that off our repo URL - job done!
size_t baseUrlLen = repo->baseUrl().size();
return dav->url().substr(baseUrlLen + 1);
}
SVNDirectory* SVNDirectory::parent() const
{
return _parent;
}
SVNDirectory* SVNDirectory::child(const std::string& dirName) const
{
BOOST_FOREACH(SVNDirectory* d, _children) {
if (d->name() == dirName) {
return d;
}
}
return NULL;
}
DirectoryList::iterator
SVNDirectory::findChildDir(const std::string& dirName)
{
DirectoryList::iterator it;
for (it=_children.begin(); it != _children.end(); ++it) {
if ((*it)->name() == dirName) {
return it;
}
}
return it;
}
simgear::Dir SVNDirectory::fsDir() const
{
return Dir(localPath);
}

112
simgear/io/SVNDirectory.hxx Normal file
View File

@@ -0,0 +1,112 @@
// DAVCollectionMirror.hxx - mirror a DAV collection to the local filesystem
//
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SG_IO_DAVCOLLECTIONMIRROR_HXX
#define SG_IO_DAVCOLLECTIONMIRROR_HXX
#include <string>
#include <vector>
#include <memory>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/io/DAVMultiStatus.hxx>
namespace simgear {
class Dir;
namespace HTTP { class Request; }
// forward decls
class DAVMirror;
class SVNRepository;
class SVNDirectory;
typedef std::vector<SVNDirectory*> DirectoryList;
class SVNDirectory
{
public:
// init from local
SVNDirectory(SVNRepository *repo, const SGPath& path);
~SVNDirectory();
void setBaseUrl(const std::string& url);
// init from a collection
SVNDirectory(SVNDirectory* pr, DAVCollection* col);
// void update();
// void gotResource(HTTP::Request* get, const std::string& etag);
void requestFailed(HTTP::Request* req);
void beginUpdateReport();
void updateReportComplete();
bool isDoingSync() const;
std::string url() const;
std::string name() const;
DAVResource* addChildFile(const std::string& fileName);
SVNDirectory* addChildDirectory(const std::string& dirName);
// void updateChild(DAVResource* child);
void deleteChildByName(const std::string& name);
SGPath fsPath() const
{ return localPath; }
simgear::Dir fsDir() const;
std::string repoPath() const;
SVNRepository* repository() const;
DAVCollection* collection() const
{ return dav; }
std::string cachedRevision() const
{ return _cachedRevision; }
void mergeUpdateReportDetails(unsigned int depth, string_list& items);
SVNDirectory* parent() const;
SVNDirectory* child(const std::string& dirName) const;
private:
void parseCache();
void writeCache();
DirectoryList::iterator findChildDir(const std::string& dirName);
SGPath localPath;
DAVCollection* dav;
SVNRepository* repo;
std::string _cachedRevision;
bool _doingUpdateReport;
SVNDirectory* _parent;
DirectoryList _children;
};
} // of namespace simgear
#endif // of SG_IO_DAVCOLLECTIONMIRROR_HXX

View File

@@ -0,0 +1,582 @@
// SVNReportParser -- parser for SVN report XML data
//
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "SVNReportParser.hxx"
#include <iostream>
#include <cstring>
#include <cassert>
#include <algorithm>
#include <sstream>
#include <fstream>
#include <boost/foreach.hpp>
#include "simgear/misc/sg_path.hxx"
#include "simgear/misc/sg_dir.hxx"
#include "simgear/debug/logstream.hxx"
#include "simgear/xml/xmlparse.h"
#include "simgear/xml/easyxml.hxx"
#include "simgear/misc/strutils.hxx"
#include "simgear/package/md5.h"
#include "SVNDirectory.hxx"
#include "SVNRepository.hxx"
#include "DAVMultiStatus.hxx"
using std::cout;
using std::cerr;
using std::endl;
using std::string;
using namespace simgear;
#define DAV_NS "DAV::"
#define SVN_NS "svn::"
#define SUBVERSION_DAV_NS "http://subversion.tigris.org/xmlns/dav/"
namespace {
#define MAX_ENCODED_INT_LEN 10
static size_t
decode_size(unsigned char* &p,
const unsigned char *end)
{
if (p + MAX_ENCODED_INT_LEN < end)
end = p + MAX_ENCODED_INT_LEN;
/* Decode bytes until we're done. */
size_t result = 0;
while (p < end) {
result = (result << 7) | (*p & 0x7f);
if (((*p++ >> 7) & 0x1) == 0) {
break;
}
}
return result;
}
static bool
try_decode_size(unsigned char* &p,
const unsigned char *end)
{
if (p + MAX_ENCODED_INT_LEN < end)
end = p + MAX_ENCODED_INT_LEN;
while (p < end) {
if (((*p++ >> 7) & 0x1) == 0) {
return true;
}
}
return false;
}
// const char* SVN_UPDATE_REPORT_TAG = SVN_NS "update-report";
// const char* SVN_TARGET_REVISION_TAG = SVN_NS "target-revision";
const char* SVN_OPEN_DIRECTORY_TAG = SVN_NS "open-directory";
const char* SVN_OPEN_FILE_TAG = SVN_NS "open-file";
const char* SVN_ADD_DIRECTORY_TAG = SVN_NS "add-directory";
const char* SVN_ADD_FILE_TAG = SVN_NS "add-file";
const char* SVN_TXDELTA_TAG = SVN_NS "txdelta";
const char* SVN_SET_PROP_TAG = SVN_NS "set-prop";
const char* SVN_DELETE_ENTRY_TAG = SVN_NS "delete-entry";
const char* SVN_DAV_MD5_CHECKSUM = SUBVERSION_DAV_NS ":md5-checksum";
const char* DAV_HREF_TAG = DAV_NS "href";
const char* DAV_CHECKED_IN_TAG = SVN_NS "checked-in";
const int svn_txdelta_source = 0;
const int svn_txdelta_target = 1;
const int svn_txdelta_new = 2;
const size_t DELTA_HEADER_SIZE = 4;
/**
* helper struct to decode and store the SVN delta header
* values
*/
struct SVNDeltaWindow
{
public:
static bool isWindowComplete(unsigned char* buffer, size_t bytes)
{
unsigned char* p = buffer;
unsigned char* pEnd = p + bytes;
// if we can't decode five sizes, certainly incomplete
for (int i=0; i<5; i++) {
if (!try_decode_size(p, pEnd)) {
return false;
}
}
p = buffer;
// ignore these three
decode_size(p, pEnd);
decode_size(p, pEnd);
decode_size(p, pEnd);
size_t instructionLen = decode_size(p, pEnd);
size_t newLength = decode_size(p, pEnd);
size_t headerLength = p - buffer;
return (bytes >= (instructionLen + newLength + headerLength));
}
SVNDeltaWindow(unsigned char* p) :
headerLength(0),
_ptr(p)
{
sourceViewOffset = decode_size(p, p+20);
sourceViewLength = decode_size(p, p+20);
targetViewLength = decode_size(p, p+20);
instructionLength = decode_size(p, p+20);
newLength = decode_size(p, p+20);
headerLength = p - _ptr;
_ptr = p;
}
bool apply(std::vector<char>& output, std::istream& source)
{
unsigned char* pEnd = _ptr + instructionLength;
unsigned char* newData = pEnd;
while (_ptr < pEnd) {
int op = ((*_ptr >> 6) & 0x3);
if (op >= 3) {
SG_LOG(SG_IO, SG_INFO, "SVNDeltaWindow: bad opcode:" << op);
return false;
}
int length = *_ptr++ & 0x3f;
int offset = 0;
if (length == 0) {
length = decode_size(_ptr, pEnd);
}
if (length == 0) {
SG_LOG(SG_IO, SG_INFO, "SVNDeltaWindow: malformed stream, 0 length" << op);
return false;
}
// if op != new, decode another size value
if (op != svn_txdelta_new) {
offset = decode_size(_ptr, pEnd);
}
if (op == svn_txdelta_target) {
while (length > 0) {
output.push_back(output[offset++]);
--length;
}
} else if (op == svn_txdelta_new) {
output.insert(output.end(), newData, newData + length);
} else if (op == svn_txdelta_source) {
source.seekg(offset);
char* sourceBuf = (char*) malloc(length);
assert(sourceBuf);
source.read(sourceBuf, length);
output.insert(output.end(), sourceBuf, sourceBuf + length);
free(sourceBuf);
}
} // of instruction loop
return true;
}
size_t size() const
{
return headerLength + instructionLength + newLength;
}
unsigned int sourceViewOffset;
size_t sourceViewLength,
targetViewLength;
size_t headerLength,
instructionLength,
newLength;
private:
unsigned char* _ptr;
};
} // of anonymous namespace
class SVNReportParser::SVNReportParserPrivate
{
public:
SVNReportParserPrivate(SVNRepository* repo) :
tree(repo),
status(SVNRepository::SVN_NO_ERROR),
parserInited(false),
currentPath(repo->fsBase())
{
inFile = false;
currentDir = repo->rootDir();
}
~SVNReportParserPrivate()
{
}
void startElement (const char * name, const char** attributes)
{
if (status != SVNRepository::SVN_NO_ERROR) {
return;
}
ExpatAtts attrs(attributes);
tagStack.push_back(name);
if (!strcmp(name, SVN_TXDELTA_TAG)) {
txDeltaData.clear();
} else if (!strcmp(name, SVN_ADD_FILE_TAG)) {
string fileName(attrs.getValue("name"));
SGPath filePath(currentDir->fsDir().file(fileName));
currentPath = filePath;
inFile = true;
} else if (!strcmp(name, SVN_OPEN_FILE_TAG)) {
string fileName(attrs.getValue("name"));
SGPath filePath(Dir(currentPath).file(fileName));
currentPath = filePath;
DAVResource* res = currentDir->collection()->childWithName(fileName);
if (!res || !filePath.exists()) {
// set error condition
}
inFile = true;
} else if (!strcmp(name, SVN_ADD_DIRECTORY_TAG)) {
string dirName(attrs.getValue("name"));
Dir d(currentDir->fsDir().file(dirName));
if (d.exists()) {
// policy decision : if we're doing an add, wipe the existing
d.remove(true);
}
currentDir = currentDir->addChildDirectory(dirName);
currentPath = currentDir->fsPath();
currentDir->beginUpdateReport();
//cout << "addDir:" << currentPath << endl;
} else if (!strcmp(name, SVN_SET_PROP_TAG)) {
setPropName = attrs.getValue("name");
setPropValue.clear();
} else if (!strcmp(name, SVN_DAV_MD5_CHECKSUM)) {
md5Sum.clear();
} else if (!strcmp(name, SVN_OPEN_DIRECTORY_TAG)) {
string dirName;
if (attrs.getValue("name")) {
dirName = string(attrs.getValue("name"));
}
openDirectory(dirName);
} else if (!strcmp(name, SVN_DELETE_ENTRY_TAG)) {
string entryName(attrs.getValue("name"));
deleteEntry(entryName);
} else if (!strcmp(name, DAV_CHECKED_IN_TAG) || !strcmp(name, DAV_HREF_TAG)) {
// don't warn on these ones
} else {
//std::cerr << "unhandled element:" << name << std::endl;
}
} // of startElement
void openDirectory(const std::string& dirName)
{
if (dirName.empty()) {
// root directory, we shall assume
currentDir = tree->rootDir();
} else {
assert(currentDir);
currentDir = currentDir->child(dirName);
}
assert(currentDir);
currentPath = currentDir->fsPath();
currentDir->beginUpdateReport();
}
void deleteEntry(const std::string& entryName)
{
currentDir->deleteChildByName(entryName);
}
bool decodeTextDelta(const SGPath& outputPath)
{
string decoded = strutils::decodeBase64(txDeltaData);
size_t bytesToDecode = decoded.size();
std::vector<char> output;
unsigned char* p = (unsigned char*) decoded.data();
if (memcmp(decoded.data(), "SVN\0", DELTA_HEADER_SIZE) != 0) {
return false; // bad header
}
bytesToDecode -= DELTA_HEADER_SIZE;
p += DELTA_HEADER_SIZE;
std::ifstream source;
source.open(outputPath.c_str(), std::ios::in | std::ios::binary);
while (bytesToDecode > 0) {
SVNDeltaWindow window(p);
assert(bytesToDecode >= window.size());
window.apply(output, source);
bytesToDecode -= window.size();
p += window.size();
}
source.close();
std::ofstream f;
f.open(outputPath.c_str(),
std::ios::out | std::ios::trunc | std::ios::binary);
f.write(output.data(), output.size());
// compute MD5 while we have the file in memory
MD5_CTX md5;
memset(&md5, 0, sizeof(MD5_CTX));
MD5Init(&md5);
MD5Update(&md5, (unsigned char*) output.data(), output.size());
MD5Final(&md5);
decodedFileMd5 = strutils::encodeHex(md5.digest, 16);
return true;
}
void endElement (const char * name)
{
if (status != SVNRepository::SVN_NO_ERROR) {
return;
}
assert(tagStack.back() == name);
tagStack.pop_back();
if (!strcmp(name, SVN_TXDELTA_TAG)) {
if (!decodeTextDelta(currentPath)) {
fail(SVNRepository::SVN_ERROR_TXDELTA);
}
} else if (!strcmp(name, SVN_ADD_FILE_TAG)) {
finishFile(currentDir->addChildFile(currentPath.file()));
} else if (!strcmp(name, SVN_OPEN_FILE_TAG)) {
DAVResource* res = currentDir->collection()->childWithName(currentPath.file());
assert(res);
finishFile(res);
} else if (!strcmp(name, SVN_ADD_DIRECTORY_TAG)) {
// pop directory
currentPath = currentPath.dir();
currentDir->updateReportComplete();
currentDir = currentDir->parent();
} else if (!strcmp(name, SVN_SET_PROP_TAG)) {
if (setPropName == "svn:entry:committed-rev") {
revision = strutils::to_int(setPropValue);
currentVersionName = setPropValue;
if (!inFile) {
// for directories we have the resource already
// for adding files, we might not; we set the version name
// above when ending the add/open-file element
currentDir->collection()->setVersionName(currentVersionName);
}
}
} else if (!strcmp(name, SVN_DAV_MD5_CHECKSUM)) {
// validate against (presumably) just written file
if (decodedFileMd5 != md5Sum) {
fail(SVNRepository::SVN_ERROR_CHECKSUM);
}
} else if (!strcmp(name, SVN_OPEN_DIRECTORY_TAG)) {
if (currentDir->parent()) {
// pop the collection stack
currentDir = currentDir->parent();
}
currentDir->updateReportComplete();
currentPath = currentDir->fsPath();
} else {
// std::cout << "element:" << name;
}
}
void finishFile(DAVResource* res)
{
res->setVersionName(currentVersionName);
res->setMD5(md5Sum);
currentPath = currentPath.dir();
inFile = false;
}
void data (const char * s, int length)
{
if (status != SVNRepository::SVN_NO_ERROR) {
return;
}
if (tagStack.back() == SVN_SET_PROP_TAG) {
setPropValue += string(s, length);
} else if (tagStack.back() == SVN_TXDELTA_TAG) {
txDeltaData += string(s, length);
} else if (tagStack.back() == SVN_DAV_MD5_CHECKSUM) {
md5Sum += string(s, length);
}
}
void pi (const char * target, const char * data) {}
string tagN(const unsigned int n) const
{
size_t sz = tagStack.size();
if (n >= sz) {
return string();
}
return tagStack[sz - (1 + n)];
}
void fail(SVNRepository::ResultCode err)
{
status = err;
}
SVNRepository* tree;
DAVCollection* rootCollection;
SVNDirectory* currentDir;
SVNRepository::ResultCode status;
bool parserInited;
XML_Parser xmlParser;
// in-flight data
string_list tagStack;
string currentVersionName;
string txDeltaData;
SGPath currentPath;
bool inFile;
unsigned int revision;
string md5Sum, decodedFileMd5;
std::string setPropName, setPropValue;
};
////////////////////////////////////////////////////////////////////////
// Static callback functions for Expat.
////////////////////////////////////////////////////////////////////////
#define VISITOR static_cast<SVNReportParser::SVNReportParserPrivate *>(userData)
static void
start_element (void * userData, const char * name, const char ** atts)
{
VISITOR->startElement(name, atts);
}
static void
end_element (void * userData, const char * name)
{
VISITOR->endElement(name);
}
static void
character_data (void * userData, const char * s, int len)
{
VISITOR->data(s, len);
}
static void
processing_instruction (void * userData,
const char * target,
const char * data)
{
VISITOR->pi(target, data);
}
#undef VISITOR
///////////////////////////////////////////////////////////////////////////////
SVNReportParser::SVNReportParser(SVNRepository* repo) :
_d(new SVNReportParserPrivate(repo))
{
}
SVNReportParser::~SVNReportParser()
{
}
SVNRepository::ResultCode
SVNReportParser::innerParseXML(const char* data, int size)
{
if (_d->status != SVNRepository::SVN_NO_ERROR) {
return _d->status;
}
bool isEnd = (data == NULL);
if (!XML_Parse(_d->xmlParser, data, size, isEnd)) {
SG_LOG(SG_IO, SG_INFO, "SVN parse error:" << XML_ErrorString(XML_GetErrorCode(_d->xmlParser))
<< " at line:" << XML_GetCurrentLineNumber(_d->xmlParser)
<< " column " << XML_GetCurrentColumnNumber(_d->xmlParser));
XML_ParserFree(_d->xmlParser);
_d->parserInited = false;
return SVNRepository::SVN_ERROR_XML;
} else if (isEnd) {
XML_ParserFree(_d->xmlParser);
_d->parserInited = false;
}
return _d->status;
}
SVNRepository::ResultCode
SVNReportParser::parseXML(const char* data, int size)
{
if (_d->status != SVNRepository::SVN_NO_ERROR) {
return _d->status;
}
if (!_d->parserInited) {
_d->xmlParser = XML_ParserCreateNS(0, ':');
XML_SetUserData(_d->xmlParser, _d.get());
XML_SetElementHandler(_d->xmlParser, start_element, end_element);
XML_SetCharacterDataHandler(_d->xmlParser, character_data);
XML_SetProcessingInstructionHandler(_d->xmlParser, processing_instruction);
_d->parserInited = true;
}
return innerParseXML(data, size);
}
SVNRepository::ResultCode SVNReportParser::finishParse()
{
if (_d->status != SVNRepository::SVN_NO_ERROR) {
return _d->status;
}
return innerParseXML(NULL, 0);
}
std::string SVNReportParser::etagFromRevision(unsigned int revision)
{
// etags look like W/"7//", hopefully this is stable
// across different servers and similar
std::ostringstream os;
os << "W/\"" << revision << "//";
return os.str();
}

View File

@@ -0,0 +1,57 @@
// SVNReportParser -- parser for SVN report XML data
//
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SG_IO_SVNREPORTPARSER_HXX
#define SG_IO_SVNREPORTPARSER_HXX
#include <string>
#include <memory> // for auto_ptr
#include "SVNRepository.hxx"
class SGPath;
namespace simgear
{
class SVNRepository;
class SVNReportParser
{
public:
SVNReportParser(SVNRepository* repo);
~SVNReportParser();
// incremental XML parsing
SVNRepository::ResultCode parseXML(const char* data, int size);
SVNRepository::ResultCode finishParse();
static std::string etagFromRevision(unsigned int revision);
class SVNReportParserPrivate;
private:
SVNRepository::ResultCode innerParseXML(const char* data, int size);
std::auto_ptr<SVNReportParserPrivate> _d;
};
} // of namespace simgear
#endif // of SG_IO_SVNREPORTPARSER_HXX

View File

@@ -0,0 +1,414 @@
// DAVMirrorTree -- mirror a DAV tree to the local file-system
//
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "SVNRepository.hxx"
#include <iostream>
#include <cstring>
#include <cassert>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <fstream>
#include <boost/foreach.hpp>
#include "simgear/debug/logstream.hxx"
#include "simgear/misc/strutils.hxx"
#include <simgear/misc/sg_dir.hxx>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/io/DAVMultiStatus.hxx>
#include <simgear/io/SVNDirectory.hxx>
#include <simgear/io/sg_file.hxx>
#include <simgear/io/SVNReportParser.hxx>
using std::cout;
using std::cerr;
using std::endl;
using std::string;
namespace simgear
{
typedef std::vector<HTTP::Request_ptr> RequestVector;
class SVNRepoPrivate
{
public:
SVNRepoPrivate(SVNRepository* parent) :
p(parent),
isUpdating(false),
status(SVNRepository::SVN_NO_ERROR)
{ ; }
SVNRepository* p; // link back to outer
SVNDirectory* rootCollection;
HTTP::Client* http;
std::string baseUrl;
std::string vccUrl;
std::string targetRevision;
bool isUpdating;
SVNRepository::ResultCode status;
void svnUpdateDone()
{
isUpdating = false;
}
void updateFailed(HTTP::Request* req, SVNRepository::ResultCode err)
{
SG_LOG(SG_IO, SG_WARN, "SVN: failed to update from:" << req->url());
isUpdating = false;
status = err;
}
void propFindComplete(HTTP::Request* req, DAVCollection* col);
void propFindFailed(HTTP::Request* req, SVNRepository::ResultCode err);
};
namespace { // anonmouse
string makeAbsoluteUrl(const string& url, const string& base)
{
if (strutils::starts_with(url, "http://"))
return url; // already absolute
assert(strutils::starts_with(base, "http://"));
int schemeEnd = base.find("://");
int hostEnd = base.find('/', schemeEnd + 3);
if (hostEnd < 0) {
return url;
}
return base.substr(0, hostEnd) + url;
}
// keep the responses small by only requesting the properties we actually
// care about; the ETag, length and MD5-sum
const char* PROPFIND_REQUEST_BODY =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
"<D:propfind xmlns:D=\"DAV:\">"
"<D:prop xmlns:R=\"http://subversion.tigris.org/xmlns/dav/\">"
"<D:resourcetype/>"
"<D:version-name/>"
"<D:version-controlled-configuration/>"
"</D:prop>"
"</D:propfind>";
class PropFindRequest : public HTTP::Request
{
public:
PropFindRequest(SVNRepoPrivate* repo) :
Request(repo->baseUrl, "PROPFIND"),
_repo(repo)
{
}
virtual string_list requestHeaders() const
{
string_list r;
r.push_back("Depth");
return r;
}
virtual string header(const string& name) const
{
if (name == "Depth") {
return "0";
}
return string();
}
virtual string requestBodyType() const
{
return "application/xml; charset=\"utf-8\"";
}
virtual int requestBodyLength() const
{
return strlen(PROPFIND_REQUEST_BODY);
}
virtual int getBodyData(char* buf, int count) const
{
int bodyLen = strlen(PROPFIND_REQUEST_BODY);
assert(count >= bodyLen);
memcpy(buf, PROPFIND_REQUEST_BODY, bodyLen);
return bodyLen;
}
protected:
virtual void responseHeadersComplete()
{
if (responseCode() == 207) {
// fine
} else if (responseCode() == 404) {
_repo->propFindFailed(this, SVNRepository::SVN_ERROR_NOT_FOUND);
} else {
SG_LOG(SG_IO, SG_WARN, "request for:" << url() <<
" return code " << responseCode());
_repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
}
}
virtual void responseComplete()
{
if (responseCode() == 207) {
_davStatus.finishParse();
_repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
}
}
virtual void gotBodyData(const char* s, int n)
{
if (responseCode() != 207) {
return;
}
_davStatus.parseXML(s, n);
}
private:
SVNRepoPrivate* _repo;
DAVMultiStatus _davStatus;
};
class UpdateReportRequest : public HTTP::Request
{
public:
UpdateReportRequest(SVNRepoPrivate* repo,
const std::string& aVersionName,
bool startEmpty) :
HTTP::Request("", "REPORT"),
_requestSent(0),
_parser(repo->p),
_repo(repo),
_failed(false)
{
setUrl(repo->vccUrl);
_request =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<S:update-report send-all=\"true\" xmlns:S=\"svn:\">\n"
"<S:src-path>" + repo->baseUrl + "</S:src-path>\n"
"<S:depth>unknown</S:depth>\n";
_request += "<S:entry rev=\"" + aVersionName + "\" depth=\"infinity\" start-empty=\"true\"/>\n";
if (!startEmpty) {
string_list entries;
_repo->rootCollection->mergeUpdateReportDetails(0, entries);
BOOST_FOREACH(string e, entries) {
_request += e + "\n";
}
}
_request += "</S:update-report>";
}
virtual string requestBodyType() const
{
return "application/xml; charset=\"utf-8\"";
}
virtual int requestBodyLength() const
{
return _request.size();
}
virtual int getBodyData(char* buf, int count) const
{
int len = std::min(count, requestBodyLength() - _requestSent);
memcpy(buf, _request.c_str() + _requestSent, len);
_requestSent += len;
return len;
}
protected:
virtual void responseHeadersComplete()
{
}
virtual void responseComplete()
{
if (_failed) {
return;
}
if (responseCode() == 200) {
SVNRepository::ResultCode err = _parser.finishParse();
if (err) {
_repo->updateFailed(this, err);
_failed = true;
} else {
_repo->svnUpdateDone();
}
} else if (responseCode() == 404) {
_repo->updateFailed(this, SVNRepository::SVN_ERROR_NOT_FOUND);
_failed = true;
} else {
SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() <<
" return code " << responseCode());
_repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET);
_failed = true;
}
}
virtual void gotBodyData(const char* s, int n)
{
if (_failed) {
return;
}
if (responseCode() != 200) {
return;
}
//cout << "body data:" << string(s, n) << endl;
SVNRepository::ResultCode err = _parser.parseXML(s, n);
if (err) {
_failed = true;
SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() <<
" XML parse failed");
_repo->updateFailed(this, err);
}
}
private:
string _request;
mutable int _requestSent;
SVNReportParser _parser;
SVNRepoPrivate* _repo;
bool _failed;
};
} // anonymous
SVNRepository::SVNRepository(const SGPath& base, HTTP::Client *cl) :
_d(new SVNRepoPrivate(this))
{
_d->http = cl;
_d->rootCollection = new SVNDirectory(this, base);
_d->baseUrl = _d->rootCollection->url();
}
SVNRepository::~SVNRepository()
{
delete _d->rootCollection;
}
void SVNRepository::setBaseUrl(const std::string &url)
{
_d->baseUrl = url;
_d->rootCollection->setBaseUrl(url);
}
std::string SVNRepository::baseUrl() const
{
return _d->baseUrl;
}
HTTP::Client* SVNRepository::http() const
{
return _d->http;
}
SGPath SVNRepository::fsBase() const
{
return _d->rootCollection->fsPath();
}
bool SVNRepository::isBare() const
{
if (!fsBase().exists() || Dir(fsBase()).isEmpty()) {
return true;
}
if (_d->vccUrl.empty()) {
return true;
}
return false;
}
void SVNRepository::update()
{
_d->status = SVN_NO_ERROR;
if (_d->targetRevision.empty() || _d->vccUrl.empty()) {
_d->isUpdating = true;
PropFindRequest* pfr = new PropFindRequest(_d.get());
http()->makeRequest(pfr);
return;
}
if (_d->targetRevision == rootDir()->cachedRevision()) {
SG_LOG(SG_IO, SG_DEBUG, baseUrl() << " in sync at version " << _d->targetRevision);
_d->isUpdating = false;
return;
}
_d->isUpdating = true;
UpdateReportRequest* urr = new UpdateReportRequest(_d.get(),
_d->targetRevision, isBare());
http()->makeRequest(urr);
}
bool SVNRepository::isDoingSync() const
{
if (_d->status != SVN_NO_ERROR) {
return false;
}
return _d->isUpdating || _d->rootCollection->isDoingSync();
}
SVNDirectory* SVNRepository::rootDir() const
{
return _d->rootCollection;
}
SVNRepository::ResultCode
SVNRepository::failure() const
{
return _d->status;
}
///////////////////////////////////////////////////////////////////////////
void SVNRepoPrivate::propFindComplete(HTTP::Request* req, DAVCollection* c)
{
targetRevision = c->versionName();
vccUrl = makeAbsoluteUrl(c->versionControlledConfiguration(), baseUrl);
rootCollection->collection()->setVersionControlledConfiguration(vccUrl);
p->update();
}
void SVNRepoPrivate::propFindFailed(HTTP::Request *req, SVNRepository::ResultCode err)
{
if (err != SVNRepository::SVN_ERROR_NOT_FOUND) {
SG_LOG(SG_IO, SG_WARN, "PropFind failed for:" << req->url());
}
isUpdating = false;
status = err;
}
} // of namespace simgear

View File

@@ -0,0 +1,76 @@
// DAVMirrorTree.hxx - mirror a DAV tree to the local file system
//
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SG_IO_DAVMIRRORTREE_HXX
#define SG_IO_DAVMIRRORTREE_HXX
#include <string>
#include <vector>
#include <memory>
#include <simgear/misc/sg_path.hxx>
namespace simgear {
namespace HTTP {
class Client;
}
class SVNDirectory;
class SVNRepoPrivate;
class SVNRepository
{
public:
SVNRepository(const SGPath& root, HTTP::Client* cl);
~SVNRepository();
SVNDirectory* rootDir() const;
SGPath fsBase() const;
void setBaseUrl(const std::string& url);
std::string baseUrl() const;
HTTP::Client* http() const;
void update();
bool isDoingSync() const;
enum ResultCode {
SVN_NO_ERROR = 0,
SVN_ERROR_NOT_FOUND,
SVN_ERROR_SOCKET,
SVN_ERROR_XML,
SVN_ERROR_TXDELTA,
SVN_ERROR_IO,
SVN_ERROR_CHECKSUM
};
ResultCode failure() const;
private:
bool isBare() const;
std::auto_ptr<SVNRepoPrivate> _d;
};
} // of namespace simgear
#endif // of SG_IO_DAVMIRRORTREE_HXX

49
simgear/io/http_svn.cxx Normal file
View File

@@ -0,0 +1,49 @@
#include <cstdio>
#include <cstring>
#include <signal.h>
#include <iostream>
#include <boost/foreach.hpp>
#include <simgear/io/sg_file.hxx>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/io/HTTPRequest.hxx>
#include <simgear/io/sg_netChannel.hxx>
#include <simgear/io/DAVMultiStatus.hxx>
#include <simgear/io/SVNRepository.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/timing/timestamp.hxx>
using namespace simgear;
using std::cout;
using std::endl;
using std::cerr;
using std::string;
HTTP::Client* httpClient;
int main(int argc, char* argv[])
{
sglog().setLogLevels( SG_ALL, SG_INFO );
HTTP::Client cl;
httpClient = &cl;
SGPath p("/Users/jmt/Desktop/scenemodels");
SVNRepository airports(p, &cl);
// airports.setBaseUrl("http://svn.goneabitbursar.com/testproject1");
airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Models");
// airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Airports");
airports.update();
while (airports.isDoingSync()) {
cl.update(100);
}
cout << "all done!" << endl;
return EXIT_SUCCESS;
}

View File

@@ -41,8 +41,6 @@
namespace simgear {
static NetChannel* channels = 0 ;
NetChannel::NetChannel ()
{
closed = true ;
@@ -51,31 +49,14 @@ NetChannel::NetChannel ()
accepting = false ;
write_blocked = false ;
should_delete = false ;
next_channel = channels ;
channels = this ;
poller = NULL;
}
NetChannel::~NetChannel ()
{
close();
NetChannel* prev = NULL ;
for ( NetChannel* ch = channels; ch != NULL;
ch = ch -> next_channel )
{
if (ch == this)
{
ch = ch -> next_channel ;
if ( prev != NULL )
prev -> next_channel = ch ;
else
channels = ch ;
next_channel = 0 ;
break;
}
prev = ch ;
if (poller) {
poller->removeChannel(this);
}
}
@@ -232,89 +213,6 @@ NetChannel::handleResolve()
}
}
bool
NetChannel::poll (unsigned int timeout)
{
if (!channels)
return false ;
enum { MAX_SOCKETS = 256 } ;
Socket* reads [ MAX_SOCKETS+1 ] ;
Socket* writes [ MAX_SOCKETS+1 ] ;
Socket* deletes [ MAX_SOCKETS+1 ] ;
int nreads = 0 ;
int nwrites = 0 ;
int ndeletes = 0 ;
int nopen = 0 ;
NetChannel* ch;
for ( ch = channels; ch != NULL; ch = ch -> next_channel )
{
if ( ch -> should_delete )
{
assert(ndeletes<MAX_SOCKETS);
deletes[ndeletes++] = ch ;
}
else if ( ! ch -> closed )
{
if (ch -> resolving_host )
{
ch -> handleResolve();
continue;
}
nopen++ ;
if (ch -> readable()) {
assert(nreads<MAX_SOCKETS);
reads[nreads++] = ch ;
}
if (ch -> writable()) {
assert(nwrites<MAX_SOCKETS);
writes[nwrites++] = ch ;
}
}
}
reads[nreads] = NULL ;
writes[nwrites] = NULL ;
deletes[ndeletes] = NULL ;
int i ;
for ( i=0; deletes[i]; i++ )
{
ch = (NetChannel*)deletes[i];
delete ch ;
}
if (!nopen)
return false ;
if (!nreads && !nwrites)
return true ; //hmmm- should we shutdown?
Socket::select (reads, writes, timeout) ;
for ( i=0; reads[i]; i++ )
{
ch = (NetChannel*)reads[i];
if ( ! ch -> closed )
ch -> handleReadEvent();
}
for ( i=0; writes[i]; i++ )
{
ch = (NetChannel*)writes[i];
if ( ! ch -> closed )
ch -> handleWriteEvent();
}
return true ;
}
void
NetChannel::loop (unsigned int timeout)
{
while ( poll (timeout) ) ;
}
void NetChannel::handleRead (void) {
SG_LOG(SG_IO, SG_WARN, "Network:" << getHandle() << ": unhandled read");
}
@@ -336,4 +234,114 @@ void NetChannel::handleError (int error)
}
}
void
NetChannelPoller::addChannel(NetChannel* channel)
{
assert(channel);
assert(channel->poller == NULL);
channel->poller = this;
channels.push_back(channel);
}
void
NetChannelPoller::removeChannel(NetChannel* channel)
{
assert(channel);
assert(channel->poller == this);
channel->poller = NULL;
ChannelList::iterator it = channels.begin();
for (; it != channels.end(); ++it) {
if (*it == channel) {
channels.erase(it);
return;
}
}
}
bool
NetChannelPoller::poll(unsigned int timeout)
{
if (channels.empty()) {
return false;
}
enum { MAX_SOCKETS = 256 } ;
Socket* reads [ MAX_SOCKETS+1 ] ;
Socket* writes [ MAX_SOCKETS+1 ] ;
int nreads = 0 ;
int nwrites = 0 ;
int nopen = 0 ;
ChannelList::iterator it = channels.begin();
while( it != channels.end() )
{
NetChannel* ch = *it;
if ( ch -> should_delete )
{
// avoid the channel trying to remove itself from us, or we get
// bug http://code.google.com/p/flightgear-bugs/issues/detail?id=1144
ch->poller = NULL;
delete ch;
it = channels.erase(it);
continue;
}
++it; // we've copied the pointer into ch
if ( ch->closed ) {
continue;
}
if (ch -> resolving_host )
{
ch -> handleResolve();
continue;
}
nopen++ ;
if (ch -> readable()) {
assert(nreads<MAX_SOCKETS);
reads[nreads++] = ch ;
}
if (ch -> writable()) {
assert(nwrites<MAX_SOCKETS);
writes[nwrites++] = ch ;
}
} // of array-filling pass
reads[nreads] = NULL ;
writes[nwrites] = NULL ;
if (!nopen)
return false ;
if (!nreads && !nwrites)
return true ; //hmmm- should we shutdown?
Socket::select (reads, writes, timeout) ;
for ( int i=0; reads[i]; i++ )
{
NetChannel* ch = (NetChannel*)reads[i];
if ( ! ch -> closed )
ch -> handleReadEvent();
}
for ( int i=0; writes[i]; i++ )
{
NetChannel* ch = (NetChannel*)writes[i];
if ( ! ch -> closed )
ch -> handleWriteEvent();
}
return true ;
}
void
NetChannelPoller::loop (unsigned int timeout)
{
while ( poll (timeout) ) ;
}
} // of namespace simgear

View File

@@ -54,20 +54,23 @@
#define SG_NET_CHANNEL_H
#include <simgear/io/raw_socket.hxx>
#include <string>
#include <vector>
namespace simgear
{
class NetChannelPoller;
class NetChannel : public Socket
{
bool closed, connected, accepting, write_blocked, should_delete, resolving_host ;
NetChannel* next_channel ;
std::string host;
int port;
friend bool netPoll (unsigned int timeout);
friend class NetChannelPoller;
NetChannelPoller* poller;
public:
NetChannel () ;
@@ -109,9 +112,19 @@ public:
virtual void handleWrite (void);
virtual void handleAccept (void);
virtual void handleError (int error);
static bool poll (unsigned int timeout = 0 ) ;
static void loop (unsigned int timeout = 0 ) ;
};
class NetChannelPoller
{
typedef std::vector<NetChannel*> ChannelList;
ChannelList channels;
public:
void addChannel(NetChannel* channel);
void removeChannel(NetChannel* channel);
bool poll(unsigned int timeout = 0);
void loop(unsigned int timeout = 0);
};
} // of namespace simgear

View File

@@ -25,31 +25,29 @@
#include <simgear/io/sg_netChat.hxx>
#include <cstring> // for strdup
#include <cstring>
#include <cstdlib>
namespace simgear {
void
NetChat::setTerminator (const char* t)
NetChat::setTerminator(const std::string& t)
{
if (terminator) free(terminator);
terminator = strdup(t);
terminator = t;
bytesToCollect = -1;
}
const char*
NetChat::getTerminator (void)
NetChat::getTerminator() const
{
return terminator;
return terminator.c_str();
}
void
NetChat::setByteCount(int count)
{
if (terminator) free(terminator);
terminator = NULL;
terminator.clear();
bytesToCollect = count;
}
@@ -59,15 +57,15 @@ NetChat::setByteCount(int count)
#define MAX(a,b) (((a)>(b))?(a):(b))
static int
find_prefix_at_end (const NetBuffer& haystack, const char* needle)
find_prefix_at_end(const NetBuffer& haystack, const std::string& needle)
{
const char* hd = haystack.getData();
int hl = haystack.getLength();
int nl = strlen(needle);
int nl = needle.length();
for (int i = MAX (nl-hl, 0); i < nl; i++) {
//if (haystack.compare (needle, hl-(nl-i), nl-i) == 0) {
if (memcmp(needle, &hd[hl-(nl-i)], nl-i) == 0) {
if (memcmp(needle.c_str(), &hd[hl-(nl-i)], nl-i) == 0) {
return (nl-i);
}
}
@@ -75,12 +73,12 @@ find_prefix_at_end (const NetBuffer& haystack, const char* needle)
}
static int
find_terminator (const NetBuffer& haystack, const char* needle)
find_terminator(const NetBuffer& haystack, const std::string& needle)
{
if (needle && *needle)
if( !needle.empty() )
{
const char* data = haystack.getData();
const char* ptr = strstr(data,needle);
const char* ptr = strstr(data,needle.c_str());
if (ptr != NULL)
return(ptr-data);
}
@@ -102,7 +100,7 @@ NetChat::handleBufferRead (NetBuffer& in_buffer)
while (in_buffer.getLength()) {
// special case where we're not using a terminator
if (terminator == 0 || *terminator == 0) {
if ( terminator.empty() ) {
if ( bytesToCollect > 0) {
const int toRead = std::min(in_buffer.getLength(), bytesToCollect);
collectIncomingData(in_buffer.getData(), toRead);
@@ -119,8 +117,6 @@ NetChat::handleBufferRead (NetBuffer& in_buffer)
continue;
}
int terminator_len = strlen(terminator);
int index = find_terminator ( in_buffer, terminator ) ;
// 3 cases:
@@ -134,7 +130,7 @@ NetChat::handleBufferRead (NetBuffer& in_buffer)
if (index != -1) {
// we found the terminator
collectIncomingData ( in_buffer.getData(), index ) ;
in_buffer.remove (0, index + terminator_len);
in_buffer.remove (0, index + terminator.length());
foundTerminator();
} else {
// check for a prefix of the terminator

View File

@@ -61,8 +61,6 @@
#ifndef SG_NET_CHAT_H
#define SG_NET_CHAT_H
#include <memory>
#include <cstdlib>
#include <simgear/io/sg_netBuffer.hxx>
namespace simgear
@@ -70,19 +68,18 @@ namespace simgear
class NetChat : public NetBufferChannel
{
char* terminator;
std::string terminator;
int bytesToCollect;
virtual void handleBufferRead (NetBuffer& buffer) ;
public:
NetChat () :
terminator (NULL),
NetChat () :
bytesToCollect(-1)
{}
void setTerminator (const char* t);
const char* getTerminator (void);
void setTerminator(const std::string& t);
const char* getTerminator() const;
/**
* set byte count to collect - 'foundTerminator' will be called once

View File

@@ -40,15 +40,13 @@
#include "iochannel.hxx"
using std::string;
/**
* A serial I/O class based on SGIOChannel.
*/
class SGSerial : public SGIOChannel {
string device;
string baud;
std::string device;
std::string baud;
SGSerialPort port;
char save_buf[ 2 * SG_IO_MAX_MSG_SIZE ];
@@ -68,7 +66,7 @@ public:
* @param device_name name of serial device
* @param baud_rate speed of communication
*/
SGSerial( const string& device_name, const string& baud_rate );
SGSerial( const std::string& device_name, const std::string& baud_rate );
/** Destructor */
~SGSerial();

View File

@@ -405,12 +405,15 @@ public:
class TestServer : public NetChannel
{
simgear::NetChannelPoller _poller;
public:
TestServer()
{
open();
bind(NULL, 2000); // localhost, any port
listen(5);
_poller.addChannel(this);
}
virtual ~TestServer()
@@ -426,14 +429,25 @@ public:
//cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
TestServerChannel* chan = new TestServerChannel();
chan->setHandle(handle);
_poller.addChannel(chan);
}
void poll()
{
_poller.poll();
}
};
TestServer testServer;
void waitForComplete(HTTP::Client* cl, TestRequest* tr)
{
SGTimeStamp start(SGTimeStamp::now());
while (start.elapsedMSec() < 1000) {
cl->update();
testServer.poll();
if (tr->complete) {
return;
}
@@ -448,6 +462,8 @@ void waitForFailed(HTTP::Client* cl, TestRequest* tr)
SGTimeStamp start(SGTimeStamp::now());
while (start.elapsedMSec() < 1000) {
cl->update();
testServer.poll();
if (tr->failed) {
return;
}
@@ -459,7 +475,6 @@ void waitForFailed(HTTP::Client* cl, TestRequest* tr)
int main(int argc, char* argv[])
{
TestServer s;
HTTP::Client cl;

View File

@@ -44,11 +44,13 @@ set(SOURCES
simgear_component(math math "${SOURCES}" "${HEADERS}")
if(ENABLE_TESTS)
add_executable(math_test SGMathTest.cxx)
target_link_libraries(math_test SimGearCore)
target_link_libraries(math_test ${TEST_LIBS})
add_test(math ${EXECUTABLE_OUTPUT_PATH}/math_test)
add_executable(geometry_test SGGeometryTest.cxx)
target_link_libraries(geometry_test SimGearCore)
target_link_libraries(geometry_test ${TEST_LIBS})
add_test(geometry ${EXECUTABLE_OUTPUT_PATH}/geometry_test)
endif(ENABLE_TESTS)

View File

@@ -41,6 +41,39 @@ public:
static T clip(const T& a, const T& _min, const T& _max)
{ return max(_min, min(_max, a)); }
/**
* Seek a variable towards a target value with given rate and timestep
*
* @param var Variable or eg. SGPropObj
* @param target Target value
* @param rate Max. change rate/sec
* @param dt Time step (sec)
*/
template<class Var>
static T seek(Var& var, T target, T rate, T dt)
{
if( var < target )
return var = min(var + rate * dt, target);
else
return var = max(var - rate * dt, target);
}
/**
* Get @c base raised to the power of @c N
*
* @tparam N Exponent
* @param base Base
*/
template<int N>
static T pow(T base)
{
return (N < 0)
? (1. / pow<-N>(base))
: ( ((N & 1) ? base : 1)
* ((N > 1) ? pow<N / 2>(base * base) : 1)
);
}
static int sign(const T& a)
{
if (a < -SGLimits<T>::min())

View File

@@ -99,11 +99,36 @@ class SGRect
T t() const { return _min.y(); }
T b() const { return _max.y(); }
T& l() { return _min.x(); }
T& r() { return _max.x(); }
T& t() { return _min.y(); }
T& b() { return _max.y(); }
void setLeft(T l) { _min.x() = l; }
void setRight(T r) { _max.x() = r; }
void setTop(T t) { _min.y() = t; }
void setBottom(T b) { _max.y() = b; }
/**
* Move rect by vector
*/
SGRect& operator+=(const SGVec2<T>& offset)
{
_min += offset;
_max += offset;
return *this;
}
/**
* Move rect by vector in inverse direction
*/
SGRect& operator-=(const SGVec2<T>& offset)
{
_min -= offset;
_max -= offset;
return *this;
}
bool contains(T x, T y) const
{
return _min.x() <= x && x <= _max.x()
@@ -121,6 +146,30 @@ class SGRect
_max;
};
template<typename T>
inline SGRect<T> operator+(SGRect<T> rect, const SGVec2<T>& offset)
{
return rect += offset;
}
template<typename T>
inline SGRect<T> operator+(const SGVec2<T>& offset, SGRect<T> rect)
{
return rect += offset;
}
template<typename T>
inline SGRect<T> operator-(SGRect<T> rect, const SGVec2<T>& offset)
{
return rect -= offset;
}
template<typename T>
inline SGRect<T> operator-(const SGVec2<T>& offset, SGRect<T> rect)
{
return rect -= offset;
}
template<typename char_type, typename traits_type, typename T>
inline
std::basic_ostream<char_type, traits_type>&

View File

@@ -18,10 +18,6 @@
#ifndef SGVec2_H
#define SGVec2_H
#include "SGLimits.hxx"
#include "SGMathFwd.hxx"
#include "SGMisc.hxx"
#include <iosfwd>
/// 2D Vector Class

View File

@@ -38,9 +38,7 @@
#include <simgear/structure/SGReferenced.hxx>
#include <map>
#include <string>
using std::string;
class SGPropertyNode;
@@ -69,7 +67,7 @@ public:
* Constructor. Loads the interpolation table from the specified file.
* @param file name of interpolation file
*/
SGInterpTable( const string& file );
SGInterpTable( const std::string& file );
/**

View File

@@ -2,8 +2,10 @@
include (SimGearComponent)
set(HEADERS
CSSBorder.hxx
ResourceManager.hxx
interpolator.hxx
make_new.hxx
sg_dir.hxx
sg_path.hxx
sgstream.hxx
@@ -16,7 +18,8 @@ set(HEADERS
gzcontainerfile.hxx
)
set(SOURCES
set(SOURCES
CSSBorder.cxx
ResourceManager.cxx
interpolator.cxx
sg_dir.cxx
@@ -32,15 +35,25 @@ set(SOURCES
simgear_component(misc misc "${SOURCES}" "${HEADERS}")
if(ENABLE_TESTS)
add_executable(test_CSSBorder CSSBorder_test.cxx)
add_test(CSSBorder ${EXECUTABLE_OUTPUT_PATH}/test_CSSBorder)
target_link_libraries(test_CSSBorder ${TEST_LIBS})
add_executable(test_tabbed_values tabbed_values_test.cxx)
add_test(tabbed_values ${EXECUTABLE_OUTPUT_PATH}/test_tabbed_values)
target_link_libraries(test_tabbed_values SimGearCore)
target_link_libraries(test_tabbed_values ${TEST_LIBS})
add_executable(test_strings strutils_test.cxx )
add_test(test_strings ${EXECUTABLE_OUTPUT_PATH}/test_strings)
target_link_libraries(test_strings SimGearCore)
add_test(strings ${EXECUTABLE_OUTPUT_PATH}/test_strings)
target_link_libraries(test_strings ${TEST_LIBS})
add_executable(test_streams sgstream_test.cxx )
add_test(streams ${EXECUTABLE_OUTPUT_PATH}/test_streams)
target_link_libraries(test_streams ${TEST_LIBS})
add_executable(test_path path_test.cxx )
add_test(test_path ${EXECUTABLE_OUTPUT_PATH}/test_path)
target_link_libraries(test_path SimGearCore)
add_test(path ${EXECUTABLE_OUTPUT_PATH}/test_path)
target_link_libraries(test_path ${TEST_LIBS})
endif(ENABLE_TESTS)

166
simgear/misc/CSSBorder.cxx Normal file
View File

@@ -0,0 +1,166 @@
// Parse and represent CSS border definitions (eg. margin, border-image-width)
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "CSSBorder.hxx"
#include <boost/lexical_cast.hpp>
#include <boost/range.hpp>
#include <boost/tokenizer.hpp>
namespace simgear
{
//----------------------------------------------------------------------------
bool CSSBorder::isValid() const
{
return valid;
}
//----------------------------------------------------------------------------
bool CSSBorder::isNone() const
{
return !valid
|| ( offsets.t == 0
&& offsets.r == 0
&& offsets.b == 0
&& offsets.l == 0 );
}
//----------------------------------------------------------------------------
const std::string& CSSBorder::getKeyword() const
{
return keyword;
}
//----------------------------------------------------------------------------
CSSBorder::Offsets CSSBorder::getRelOffsets(const SGRect<int>& dim) const
{
Offsets ret = {{0}};
if( !valid )
return ret;
for(int i = 0; i < 4; ++i)
{
ret.val[i] = offsets.val[i];
if( !types.rel[i] )
ret.val[i] /= (i & 1) ? dim.height() : dim.width();
}
return ret;
}
//----------------------------------------------------------------------------
CSSBorder::Offsets CSSBorder::getAbsOffsets(const SGRect<int>& dim) const
{
Offsets ret = {{0}};
if( !valid )
return ret;
for(int i = 0; i < 4; ++i)
{
ret.val[i] = offsets.val[i];
if( types.rel[i] )
ret.val[i] *= (i & 1) ? dim.height() : dim.width();
}
return ret;
}
//----------------------------------------------------------------------------
CSSBorder CSSBorder::parse(const std::string& str)
{
if( str.empty() )
return CSSBorder();
// [<number>'%'?]{1,4} (top[,right[,bottom[,left]]])
//
// Percentages are relative to the size of the image: the width of the
// image for the horizontal offsets, the height for vertical offsets.
// Numbers represent pixels in the image.
int c = 0;
CSSBorder ret;
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
const boost::char_separator<char> del(" \t\n");
tokenizer tokens(str.begin(), str.end(), del);
for( tokenizer::const_iterator tok = tokens.begin();
tok != tokens.end() && c < 4;
++tok )
{
if( isalpha(*tok->begin()) )
ret.keyword = *tok;
else
{
bool rel = ret.types.rel[c] = (*tok->rbegin() == '%');
ret.offsets.val[c] =
// Negative values are not allowed and values bigger than the size of
// the image are interpreted as 100%. TODO check max
std::max
(
0.f,
boost::lexical_cast<float>
(
rel ? boost::make_iterator_range(tok->begin(), tok->end() - 1)
: *tok
)
/
(rel ? 100 : 1)
);
++c;
}
}
// When four values are specified, they set the offsets on the top, right,
// bottom and left sides in that order.
#define CSS_COPY_VAL(dest, src)\
{\
ret.offsets.val[dest] = ret.offsets.val[src];\
ret.types.rel[dest] = ret.types.rel[src];\
}
if( c < 4 )
{
if( c < 3 )
{
if( c < 2 )
// if the right is missing, it is the same as the top.
CSS_COPY_VAL(1, 0);
// if the bottom is missing, it is the same as the top
CSS_COPY_VAL(2, 0);
}
// If the left is missing, it is the same as the right
CSS_COPY_VAL(3, 1);
}
#undef CSS_COPY_VAL
if( ret.keyword == "none" )
{
memset(&ret.offsets, 0, sizeof(Offsets));
ret.keyword.clear();
}
ret.valid = true;
return ret;
}
} // namespace simgear

View File

@@ -0,0 +1,71 @@
// Parse and represent CSS border definitions (eg. margin, border-image-width)
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_CSSBORDER_HXX_
#define SG_CSSBORDER_HXX_
#include <simgear/math/SGMath.hxx>
#include <simgear/math/SGRect.hxx>
#include <string>
namespace simgear
{
class CSSBorder
{
public:
union Offsets
{
float val[4];
struct { float t, r, b, l; };
};
union OffsetsTypes
{
bool rel[4];
struct { bool t_rel, r_rel, b_rel, l_rel; };
};
CSSBorder():
valid(false)
{}
bool isValid() const;
/**
* Get whether a non-zero offset exists
*/
bool isNone() const;
const std::string& getKeyword() const;
Offsets getRelOffsets(const SGRect<int>& dim) const;
Offsets getAbsOffsets(const SGRect<int>& dim) const;
static CSSBorder parse(const std::string& str);
private:
Offsets offsets;
OffsetsTypes types;
std::string keyword;
bool valid;
};
} // namespace simgear
#endif /* SG_CSSBORDER_HXX_ */

View File

@@ -0,0 +1,104 @@
#include <simgear/compiler.h>
#include "CSSBorder.hxx"
#include <cmath>
#include <iostream>
#define COMPARE(a, b) \
if( std::fabs((a) - (b)) > 1e-4 ) \
{ \
std::cerr << "line " << __LINE__ << ": failed: "\
<< #a << " != " << #b << " d = " << ((a) - (b)) << std::endl; \
return 1; \
}
#define VERIFY(a) \
if( !(a) ) \
{ \
std::cerr << "line " << __LINE__ << ": failed: "\
<< #a << std::endl; \
return 1; \
}
using namespace simgear;
int main (int ac, char ** av)
{
CSSBorder b = CSSBorder::parse("5");
VERIFY(b.isValid());
VERIFY(!b.isNone());
CSSBorder::Offsets o = b.getAbsOffsets(SGRect<int>());
COMPARE(o.t, 5);
COMPARE(o.r, 5);
COMPARE(o.b, 5);
COMPARE(o.l, 5);
b = CSSBorder::parse("5 10");
o = b.getAbsOffsets(SGRect<int>());
COMPARE(o.t, 5);
COMPARE(o.r, 10);
COMPARE(o.b, 5);
COMPARE(o.l, 10);
b = CSSBorder::parse("5 10 15");
o = b.getAbsOffsets(SGRect<int>());
COMPARE(o.t, 5);
COMPARE(o.r, 10);
COMPARE(o.b, 15);
COMPARE(o.l, 10);
b = CSSBorder::parse("5 10 15 20");
o = b.getAbsOffsets(SGRect<int>());
COMPARE(o.t, 5);
COMPARE(o.r, 10);
COMPARE(o.b, 15);
COMPARE(o.l, 20);
b = CSSBorder::parse("5% 10% 15% 20%");
o = b.getAbsOffsets(SGRect<int>(0,0,200,200));
COMPARE(o.t, 10);
COMPARE(o.r, 20);
COMPARE(o.b, 30);
COMPARE(o.l, 40);
o = b.getRelOffsets(SGRect<int>(0,0,200,200));
COMPARE(o.t, 0.05);
COMPARE(o.r, 0.1);
COMPARE(o.b, 0.15);
COMPARE(o.l, 0.2);
b = CSSBorder::parse("5% none");
o = b.getAbsOffsets(SGRect<int>(0,0,200,200));
COMPARE(o.t, 0);
COMPARE(o.r, 0);
COMPARE(o.b, 0);
COMPARE(o.l, 0);
VERIFY(b.getKeyword().empty());
VERIFY(b.isNone());
b = CSSBorder::parse("none");
o = b.getRelOffsets(SGRect<int>(0,0,200,200));
COMPARE(o.t, 0);
COMPARE(o.r, 0);
COMPARE(o.b, 0);
COMPARE(o.l, 0);
VERIFY(b.getKeyword().empty());
VERIFY(b.isNone());
CSSBorder b2;
VERIFY(!b2.isValid());
o = b.getAbsOffsets(SGRect<int>(0,0,200,200));
COMPARE(o.t, 0);
COMPARE(o.r, 0);
COMPARE(o.b, 0);
COMPARE(o.l, 0);
o = b.getRelOffsets(SGRect<int>(0,0,200,200));
COMPARE(o.t, 0);
COMPARE(o.r, 0);
COMPARE(o.b, 0);
COMPARE(o.l, 0);
std::cout << "all tests passed successfully!" << std::endl;
return 0;
}

52
simgear/misc/make_new.hxx Normal file
View File

@@ -0,0 +1,52 @@
// Helper functions which created objects with new.
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_MAKE_NEW_HXX_
#define SG_MAKE_NEW_HXX_
namespace simgear
{
template<class T>
T* make_new()
{ return new T; }
template<class T, class A1>
T* make_new(const A1& a1)
{ return new T(a1); }
template<class T, class A1, class A2>
T* make_new(const A1& a1, const A2& a2)
{ return new T(a1, a2); }
template<class Base, class Derived>
Base* make_new_derived()
{ return new Derived; }
template<class Base, class Derived, class A1>
Base* make_new_derived(const A1& a1)
{ return new Derived(a1); }
template<class Base, class Derived, class A1, class A2>
Base* make_new_derived(const A1& a1, const A2& a2)
{ return new Derived(a1, a2); }
// Add more if needed (Variadic templates would be really nice!)
} // namespace simgear
#endif /* SG_MAKE_NEW_HXX_ */

View File

@@ -25,6 +25,7 @@
#include <simgear/misc/sg_dir.hxx>
#include <math.h>
#include <stdlib.h>
#include <cstdio>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
@@ -255,6 +256,55 @@ PathList Dir::children(int types, const std::string& nameFilter) const
return result;
}
bool Dir::isEmpty() const
{
bool empty= true;
#ifdef _WIN32
WIN32_FIND_DATA fData;
HANDLE find = FindFirstFile("\\*", &fData);
if (find == INVALID_HANDLE_VALUE) {
return true;
}
// since an empty dir will still have . and .. children, we need
// watch for those - anything else means the dir is really non-empty
bool done = false;
for (; !done; done = (FindNextFile(find, &fData) == 0)) {
if (fData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (!strcmp(fData.cFileName,".") || !strcmp(fData.cFileName,"..")) {
continue;
}
}
empty = false;
break;
}
FindClose(find);
#else
DIR* dp = opendir(_path.c_str());
if (!dp) {
return true;
}
while (true) {
struct dirent* entry = readdir(dp);
if (!entry) {
break; // done iteration
}
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
continue;
}
empty = false;
break;
}
closedir(dp);
#endif
return empty;
}
bool Dir::exists() const
{
return _path.isDir();
@@ -298,28 +348,38 @@ bool Dir::create(mode_t mode)
return (err == 0);
}
bool Dir::removeChildren() const
{
bool ok;
PathList cs = children(NO_DOT_OR_DOTDOT | INCLUDE_HIDDEN | TYPE_FILE | TYPE_DIR);
BOOST_FOREACH(SGPath path, cs) {
if (path.isDir()) {
Dir childDir(path);
ok = childDir.remove(true);
} else {
ok = path.remove();
}
if (!ok) {
SG_LOG(SG_IO, SG_WARN, "failed to remove:" << path);
return false;
}
} // of child iteration
return true;
}
bool Dir::remove(bool recursive)
{
if (!exists()) {
SG_LOG(SG_IO, SG_WARN, "attempt to remove non-existant dir:" << _path.str());
SG_LOG(SG_IO, SG_WARN, "attempt to remove non-existant dir:" << _path);
return false;
}
if (recursive) {
bool ok;
PathList cs = children(NO_DOT_OR_DOTDOT | INCLUDE_HIDDEN | TYPE_FILE | TYPE_DIR);
BOOST_FOREACH(SGPath path, cs) {
if (path.isDir()) {
Dir childDir(path);
ok = childDir.remove(true);
} else {
ok = path.remove();
}
if (!ok) {
return false;
}
} // of child iteration
if (!removeChildren()) {
return false;
}
} // of recursive deletion
#ifdef _WIN32

View File

@@ -72,6 +72,11 @@ namespace simgear
PathList children(int types = 0, const std::string& nameGlob = "") const;
/**
* test if the directory contains no children (except '.' and '..')
*/
bool isEmpty() const;
SGPath file(const std::string& name) const;
SGPath path() const
@@ -90,6 +95,12 @@ namespace simgear
*/
bool remove(bool recursive = false);
/**
* remove our children but not us
*/
bool removeChildren() const;
/**
* Check that the directory at the path exists (and is a directory!)
*/

View File

@@ -26,9 +26,11 @@
#include <simgear_config.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
#include <fstream>
#ifdef _WIN32
# include <direct.h>
@@ -38,6 +40,7 @@
#include <boost/algorithm/string/case_conv.hpp>
using std::string;
using simgear::strutils::starts_with;
/**
* define directory path separators
@@ -153,6 +156,14 @@ void SGPath::append( const string& p ) {
_cached = false;
}
//------------------------------------------------------------------------------
SGPath SGPath::operator/( const std::string& p ) const
{
SGPath ret = *this;
ret.append(p);
return ret;
}
//add a new path component to the existing path string
void SGPath::add( const string& p ) {
append( sgSearchPathSep+p );
@@ -425,7 +436,7 @@ bool SGPath::isAbsolute() const
bool SGPath::isNull() const
{
return path.empty() || (path == "");
return path.empty();
}
std::string SGPath::str_native() const
@@ -483,6 +494,115 @@ bool SGPath::rename(const SGPath& newName)
return true;
}
//------------------------------------------------------------------------------
SGPath SGPath::fromEnv(const char* name, const SGPath& def)
{
const char* val = getenv(name);
if( val && val[0] )
return SGPath(val);
return def;
}
#ifdef _WIN32
//------------------------------------------------------------------------------
SGPath SGPath::home()
{
// TODO
return SGPath();
}
#else
//------------------------------------------------------------------------------
SGPath SGPath::home()
{
return fromEnv("HOME");
}
#endif
#ifdef _WIN32
#include <ShlObj.h> // for CSIDL
//------------------------------------------------------------------------------
SGPath SGPath::desktop()
{
typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPSTR, int, BOOL);
static GetSpecialFolderPath SHGetSpecialFolderPath = NULL;
// lazy open+resolve of shell32
if (!SHGetSpecialFolderPath) {
HINSTANCE shellDll = ::LoadLibrary("shell32");
SHGetSpecialFolderPath = (GetSpecialFolderPath) GetProcAddress(shellDll, "SHGetSpecialFolderPathA");
}
if (!SHGetSpecialFolderPath){
return SGPath();
}
char path[MAX_PATH];
if (SHGetSpecialFolderPath(0, path, CSIDL_DESKTOPDIRECTORY, false)) {
return SGPath(path);
}
SG_LOG(SG_GENERAL, SG_ALERT, "SGPath::desktop() failed, bad" );
return SGPath();
}
#elif __APPLE__
#include <CoreServices/CoreServices.h>
//------------------------------------------------------------------------------
SGPath SGPath::desktop()
{
FSRef ref;
OSErr err = FSFindFolder(kUserDomain, kDesktopFolderType, false, &ref);
if (err) {
return SGPath();
}
unsigned char path[1024];
if (FSRefMakePath(&ref, path, 1024) != noErr) {
return SGPath();
}
return SGPath((const char*) path);
}
#else
//------------------------------------------------------------------------------
SGPath SGPath::desktop()
{
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
// $XDG_CONFIG_HOME defines the base directory relative to which user specific
// configuration files should be stored. If $XDG_CONFIG_HOME is either not set
// or empty, a default equal to $HOME/.config should be used.
const SGPath user_dirs = fromEnv("XDG_CONFIG_HOME", home() / ".config")
/ "user-dirs.dirs";
// Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
// homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an absolute
// path. No other format is supported.
const std::string DESKTOP = "XDG_DESKTOP_DIR=\"";
std::ifstream user_dirs_file( user_dirs.c_str() );
std::string line;
while( std::getline(user_dirs_file, line).good() )
{
if( !starts_with(line, DESKTOP) || *line.rbegin() != '"' )
continue;
// Extract dir from XDG_DESKTOP_DIR="<dir>"
line = line.substr(DESKTOP.length(), line.length() - DESKTOP.length() - 1 );
const std::string HOME = "$HOME";
if( starts_with(line, HOME) )
return home() / simgear::strutils::unescape(line.substr(HOME.length()));
return SGPath(line);
}
return home() / "Desktop";
}
#endif
std::string SGPath::realpath() const
{
#if (defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED <= 1050)
@@ -490,7 +610,7 @@ std::string SGPath::realpath() const
// This means relative paths cannot be used on Mac OS <= 10.5
return path;
#else
#if defined(_MSC_VER)
#if defined(_MSC_VER) /*for MS compilers */ || defined(_WIN32) /*needed for non MS windows compilers like MingW*/
// with absPath NULL, will allocate, and ignore length
char *buf = _fullpath( NULL, path.c_str(), _MAX_PATH );
#else

View File

@@ -98,6 +98,13 @@ public:
* @param p additional path component */
void append( const std::string& p );
/**
* Get a copy of this path with another piece appended.
*
* @param p additional path component
*/
SGPath operator/( const std::string& p ) const;
/**
* Append a new piece to the existing path. Inserts a search path
* separator to the existing path and the new patch component.
@@ -226,6 +233,26 @@ public:
* or if the destination already exists, or is not writeable
*/
bool rename(const SGPath& newName);
/**
* Get a path stored in the environment variable with the given \a name.
*
* @param name Name of the environment variable
* @param def Default path to return if the environment variable does not
* exist or is empty.
*/
static SGPath fromEnv(const char* name, const SGPath& def = SGPath());
/**
* Get path to user's home directory
*/
static SGPath home();
/**
* Get path to the user's desktop directory
*/
static SGPath desktop();
private:
void fix();

View File

@@ -102,14 +102,17 @@ istream&
skipeol( istream& in )
{
char c = '\0';
// skip to end of line.
// make sure we detect LF, CR and CR/LF
while ( in.get(c) ) {
if ( (c == '\n') || (c == '\r') ) {
break;
}
if (c == '\n')
break;
else if (c == '\r') {
if (in.peek() == '\n')
in.get(c);
break;
}
}
return in;
}

View File

@@ -0,0 +1,51 @@
#include <iostream>
#include <fstream>
#include <cstdlib> // for EXIT_FAILURE
using std::ofstream;
using std::cout;
using std::endl;
#include <simgear/misc/sgstream.hxx>
int main()
{
const char* fileName = "testfile";
{
ofstream f;
f.open(fileName, std::ios::binary | std::ios::trunc | std::ios::out);
f.write("first line ends with line-feed\n"
"second line ends with just a cr\r"
"third line ends with both\r\n"
"fourth line as well\r\n"
"fifth line is another CR/LF line\r\n"
"end of test\r\n", 1024);
f.close();
}
sg_gzifstream sg(fileName);
std::string stuff;
sg >> skipeol;
sg >> stuff;
if (stuff != "second") return EXIT_FAILURE;
cout << "Detection of LF works." << endl;
sg >> skipeol;
sg >> stuff;
if (stuff != "third") return EXIT_FAILURE;
cout << "Detection of CR works." << endl;
sg >> skipeol;
sg >> stuff;
if (stuff != "fourth") return EXIT_FAILURE;
cout << "Detection of CR/LF works." << endl;
sg >> skipeol;
sg >> skipeol;
sg >> stuff;
if (stuff != "end") return EXIT_FAILURE;
cout << "Detection of 2 following CR/LF lines works." << endl;
return EXIT_SUCCESS;
}

View File

@@ -204,15 +204,18 @@ namespace simgear {
bool
starts_with( const string & s, const string & substr )
{
return s.find( substr ) == 0;
{
return s.compare(0, substr.length(), substr) == 0;
}
bool
ends_with( const string & s, const string & substr )
{
size_t n = s.rfind( substr );
return (n != string::npos) && (n == s.length() - substr.length());
{
if( substr.length() > s.length() )
return false;
return s.compare( s.length() - substr.length(),
substr.length(),
substr ) == 0;
}
string simplify(const string& s)
@@ -300,6 +303,187 @@ namespace simgear {
return rslt;
}
} // end namespace strutils
#ifdef SG_WINDOWS
#include <windows.h>
#endif
std::string convertWindowsLocal8BitToUtf8(const std::string& a)
{
#ifdef SG_WINDOWS
DWORD flags = 0;
std::vector<wchar_t> wideString;
// call to query transform size
int requiredWideChars = MultiByteToWideChar(CP_ACP, flags, a.c_str(), a.size(),
NULL, 0);
// allocate storage and call for real
wideString.resize(requiredWideChars);
MultiByteToWideChar(CP_ACP, flags, a.c_str(), a.size(),
wideString.data(), wideString.size());
// now convert back down to UTF-8
std::vector<char> result;
int requiredUTF8Chars = WideCharToMultiByte(CP_UTF8, flags,
wideString.data(), wideString.size(),
NULL, 0, NULL, NULL);
result.resize(requiredUTF8Chars);
WideCharToMultiByte(CP_UTF8, flags,
wideString.data(), wideString.size(),
result.data(), result.size(), NULL, NULL);
return std::string(result.data(), result.size());
#else
return a;
#endif
}
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
}
static bool is_whitespace(unsigned char c) {
return ((c == ' ') || (c == '\r') || (c == '\n'));
}
std::string decodeBase64(const std::string& encoded_string)
{
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && ( encoded_string[in_] != '=')) {
if (is_whitespace( encoded_string[in_])) {
in_++;
continue;
}
if (!is_base64(encoded_string[in_])) {
break;
}
char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4) {
for (i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if (i) {
for (j = i; j <4; j++)
char_array_4[j] = 0;
for (j = 0; j <4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}
const char hexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
std::string encodeHex(const std::string& bytes)
{
std::string hex;
size_t count = bytes.size();
for (unsigned int i=0; i<count;++i) {
unsigned char c = bytes[i];
hex.push_back(hexChar[c >> 4]);
hex.push_back(hexChar[c & 0x0f]);
}
return hex;
}
std::string encodeHex(const unsigned char* rawBytes, unsigned int length)
{
std::string hex;
for (unsigned int i=0; i<length;++i) {
unsigned char c = *rawBytes++;
hex.push_back(hexChar[c >> 4]);
hex.push_back(hexChar[c & 0x0f]);
}
return hex;
}
//------------------------------------------------------------------------------
std::string unescape(const char* s)
{
std::string r;
while( *s )
{
if( *s != '\\' )
{
r += *s++;
continue;
}
if( !*++s )
break;
if (*s == '\\') {
r += '\\';
} else if (*s == 'n') {
r += '\n';
} else if (*s == 'r') {
r += '\r';
} else if (*s == 't') {
r += '\t';
} else if (*s == 'v') {
r += '\v';
} else if (*s == 'f') {
r += '\f';
} else if (*s == 'a') {
r += '\a';
} else if (*s == 'b') {
r += '\b';
} else if (*s == 'x') {
if (!*++s)
break;
int v = 0;
for (int i = 0; i < 2 && isxdigit(*s); i++, s++)
v = v * 16 + (isdigit(*s) ? *s - '0' : 10 + tolower(*s) - 'a');
r += v;
continue;
} else if (*s >= '0' && *s <= '7') {
int v = *s++ - '0';
for (int i = 0; i < 3 && *s >= '0' && *s <= '7'; i++, s++)
v = v * 8 + *s - '0';
r += v;
continue;
} else {
r += *s;
}
s++;
}
return r;
}
} // end namespace strutils
} // end namespace simgear

View File

@@ -149,6 +149,38 @@ namespace simgear {
*/
std::string uppercase(const std::string &s);
/**
* convert a string in the local Windows 8-bit encoding to UTF-8
* (no-op on other platforms)
*/
std::string convertWindowsLocal8BitToUtf8(const std::string& a);
/**
* convert base-64 encoded data to raw bytes (possibly with embedded
* NULs). Throws an exception if input data is not base64, or is
* malformed
*/
std::string decodeBase64(const std::string& a);
/**
* convert bytes to hexadecimal equivalent
*/
std::string encodeHex(const std::string& bytes);
std::string encodeHex(const unsigned char* rawBytes, unsigned int length);
/**
* Unescape string.
*
* @param str String possibly containing escaped characters.
* @return string with escaped characters replaced by single character
* values.
*/
std::string unescape(const char* str);
inline std::string unescape(const std::string& str)
{ return unescape(str.c_str()); }
} // end namespace strutils
} // end namespace simgear

View File

@@ -76,7 +76,9 @@ int main (int ac, char ** av)
std::string j = join(la, "&");
COMPARE(j, "zero&one&two&three&four&five");
COMPARE(unescape("\\ \\n\\t\\x41\\117a"), " \n\tAOa");
cout << "all tests passed successfully!" << endl;
return 0;
}

View File

@@ -26,8 +26,7 @@
#include "tabbed_values.hxx"
SGTabbedValues::SGTabbedValues(const char *line) :
_line(line)
SGTabbedValues::SGTabbedValues(const char *line)
{
assert(line);
_fields.push_back(const_cast<char*>(line));

View File

@@ -45,8 +45,6 @@ public:
long getLongAt(const unsigned int) const;
private:
const char* fieldAt(const unsigned int offset) const;
const char* _line;
/** this is first character of each field, if the field is empty
it will be the tab character. It is lazily built as needed, so

View File

@@ -0,0 +1,19 @@
#ifndef SG_MISC_TEST_MACROS_HXX
#define SG_MISC_TEST_MACROS_HXX
#define COMPARE(a, b) \
if ((a) != (b)) { \
std::cerr << "failed:" << #a << " != " << #b << std::endl; \
std::cerr << "\tgot:'" << a << "'" << std::endl; \
exit(1); \
}
#define VERIFY(a) \
if (!(a)) { \
std::cerr << "failed:" << #a << std::endl; \
exit(1); \
}
#endif // of SG_MISC_TEST_MACROS_HXX

View File

@@ -313,7 +313,10 @@ static struct Frame* setupFuncall(naContext ctx, int nargs, int mcall, int named
ctx->opFrame = opf;
if(IS_CCODE(code)) {
naRef result = (*PTR(code).ccode->fptr)(ctx, obj, nargs, args);
struct naCCode *ccode = PTR(code).ccode;
naRef result = ccode->fptru
? (*ccode->fptru)(ctx, obj, nargs, args, ccode->user_data)
: (*ccode->fptr)(ctx, obj, nargs, args);
if(named) ERR(ctx, "native functions have no named arguments");
ctx->opTop = ctx->opFrame;
PUSH(result);
@@ -441,14 +444,17 @@ static const char* getMember_r(naContext ctx, naRef obj, naRef field, naRef* out
naRef p;
struct VecRec* pv;
if(--count < 0) return "too many parents";
if(!IS_HASH(obj) && !IS_GHOST(obj)) return "non-objects have no members";
if (IS_GHOST(obj)) {
if (ghostGetMember(ctx, obj, field, out)) return "";
if(!ghostGetMember(ctx, obj, globals->parentsRef, &p)) return 0;
} else {
} else if (IS_HASH(obj)) {
if(naHash_get(obj, field, out)) return "";
if(!naHash_get(obj, globals->parentsRef, &p)) return 0;
} else if (IS_STR(obj) ) {
return getMember_r(ctx, getStringMethods(ctx), field, out, count);
} else {
return "non-objects have no members";
}
if(!IS_VEC(p)) return "object \"parents\" field not vector";

View File

@@ -2,23 +2,32 @@ include (SimGearComponent)
set(HEADERS
Ghost.hxx
NasalCallContext.hxx
NasalHash.hxx
from_nasal_detail.hxx
NasalString.hxx
from_nasal.hxx
nasal_traits.hxx
to_nasal.hxx
)
set(DETAIL_HEADERS
detail/from_nasal_helper.hxx
detail/functor_templates.hxx
detail/nasal_traits.hxx
detail/to_nasal_helper.hxx
)
set(SOURCES
NasalHash.cxx
from_nasal.cxx
to_nasal.cxx
NasalString.cxx
detail/from_nasal_helper.cxx
detail/to_nasal_helper.cxx
)
simgear_component(nasal/cppbind nasal/cppbind "${SOURCES}" "${HEADERS}")
simgear_component(nasal/cppbind/detail nasal/cppbind/detail "" "${DETAIL_HEADERS}")
if(ENABLE_TESTS)
add_executable(test_cppbind cppbind_test.cxx)
add_test(test_cppbind ${EXECUTABLE_OUTPUT_PATH}/test_cppbind)
target_link_libraries(test_cppbind SimGearCore)
add_test(cppbind ${EXECUTABLE_OUTPUT_PATH}/test_cppbind)
target_link_libraries(test_cppbind ${TEST_LIBS})
endif(ENABLE_TESTS)

View File

@@ -20,8 +20,7 @@
#ifndef SG_NASAL_GHOST_HXX_
#define SG_NASAL_GHOST_HXX_
#include "from_nasal.hxx"
#include "to_nasal.hxx"
#include "NasalCallContext.hxx"
#include <simgear/debug/logstream.hxx>
@@ -29,127 +28,25 @@
#include <boost/call_traits.hpp>
#include <boost/function.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/mpl/has_xxx.hpp>
#include <boost/preprocessor/iteration/iterate.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/utility/enable_if.hpp>
#include <map>
template<class T>
inline T* get_pointer(boost::weak_ptr<T> const& p)
{
return p.lock().get();
}
/**
* Bindings between C++ and the Nasal scripting language
*/
namespace nasal
{
/**
* Traits for C++ classes exposed as Ghost. This is the basic template for
* raw types.
*/
template<class T>
struct GhostTypeTraits
{
/** Whether the class is passed by shared pointer or raw pointer */
typedef boost::false_type::type is_shared;
/** The raw class type (Without any shared pointer) */
typedef T raw_type;
};
template<class T>
struct GhostTypeTraits<boost::shared_ptr<T> >
{
typedef boost::true_type::type is_shared;
typedef T raw_type;
};
#ifdef OSG_REF_PTR
template<class T>
struct GhostTypeTraits<osg::ref_ptr<T> >
{
typedef boost::true_type::type is_shared;
typedef T raw_type;
};
#endif
#ifdef SGSharedPtr_HXX
template<class T>
struct GhostTypeTraits<SGSharedPtr<T> >
{
typedef boost::true_type::type is_shared;
typedef T raw_type;
};
#endif
/**
* Policy for creating ghost instances from shared pointer objects.
*/
template<class T>
struct SharedPointerPolicy
{
typedef typename GhostTypeTraits<T>::raw_type raw_type;
typedef T pointer;
typedef boost::false_type returns_dynamic_type;
/**
* Create a shared pointer on the heap to handle the reference counting for
* the passed shared pointer while it is used in Nasal space.
*/
static T* createInstance(const T& ptr)
{
return ptr ? new T(ptr) : 0;
}
static pointer getPtr(void* ptr)
{
if( ptr )
return *static_cast<T*>(ptr);
else
return pointer();
}
static raw_type* getRawPtr(void* ptr)
{
if( ptr )
return static_cast<T*>(ptr)->get();
else
return 0;
}
static raw_type* getRawPtr(const T& ptr)
{
return ptr.get();
}
};
/**
* Policy for creating ghost instances as raw objects on the heap.
*/
template<class T>
struct RawPointerPolicy
{
typedef typename GhostTypeTraits<T>::raw_type raw_type;
typedef raw_type* pointer;
typedef boost::true_type returns_dynamic_type;
/**
* Create a new object instance on the heap
*/
static T* createInstance()
{
return new T();
}
static pointer getPtr(void* ptr)
{
BOOST_STATIC_ASSERT((boost::is_same<pointer, T*>::value));
return static_cast<T*>(ptr);
}
static raw_type* getRawPtr(void* ptr)
{
BOOST_STATIC_ASSERT((boost::is_same<raw_type, T>::value));
return static_cast<T*>(ptr);
}
};
namespace internal
{
/**
@@ -217,54 +114,33 @@ namespace nasal
return nasal::to_nasal(c, _parents);
}
};
/**
* Hold callable method and convert to Nasal function if required.
*/
class MethodHolder
{
public:
virtual ~MethodHolder() {}
virtual naRef get_naRef(naContext c) = 0;
};
BOOST_MPL_HAS_XXX_TRAIT_DEF(element_type)
template<class T>
struct reduced_type
{
typedef typename boost::remove_cv<
typename boost::remove_reference<T>::type
>::type type;
};
template<class T1, class T2>
struct reduced_is_same:
public boost::is_same<typename reduced_type<T1>::type, T2>
{};
}
/**
* Context passed to a function/method being called from Nasal
*/
struct CallContext
{
CallContext(naContext c, size_t argc, naRef* args):
c(c),
argc(argc),
args(args)
{}
/**
* Get the argument with given index if it exists. Otherwise returns the
* passed default value.
*
* @tparam T Type of argument (converted using ::from_nasal)
* @param index Index of requested argument
* @param def Default value returned if too few arguments available
*/
template<class T>
T getArg(size_t index, const T& def = T()) const
{
if( index >= argc )
return def;
return from_nasal<T>(c, args[index]);
}
/**
* Get the argument with given index. Raises a Nasal runtime error if there
* are to few arguments available.
*/
template<class T>
T requireArg(size_t index) const
{
if( index >= argc )
naRuntimeError(c, "Missing required arg #%d", index);
return from_nasal<T>(c, args[index]);
}
naContext c;
size_t argc;
naRef *args;
};
/**
* Class for exposing C++ objects to Nasal
*
@@ -276,41 +152,94 @@ namespace nasal
* void setX(int x);
* int getX() const;
*
* naRef myMember(naContext c, int argc, naRef* args);
* int myMember();
* void doSomethingElse(const nasal::CallContext& ctx);
* }
* typedef boost::shared_ptr<MyClass> MyClassPtr;
*
* std::string myOtherFreeMember(int num);
*
* void exposeClasses()
* {
* // Register a nasal ghost type for MyClass. This needs to be done only
* // once before creating the first ghost instance.
* Ghost<MyClass>::init("MyClass")
* // once before creating the first ghost instance. The exposed class needs
* // to be wrapped inside a shared pointer, eg. boost::shared_ptr.
* Ghost<MyClassPtr>::init("MyClass")
* // Members can be exposed by getters and setters
* .member("x", &MyClass::getX, &MyClass::setX)
* // For readonly variables only pass a getter
* .member("x_readonly", &MyClass::getX)
* // It is also possible to expose writeonly members
* .member("x_writeonly", &MyClass::setX)
* // Methods use a slightly different syntax - The pointer to the member
* // function has to be passed as template argument
* .method<&MyClass::myMember>("myMember");
* // Methods can be nearly anything callable and accepting a reference
* // to an instance of the class type. (member functions, free functions
* // and anything else bindable using boost::function and boost::bind)
* .method("myMember", &MyClass::myMember)
* .method("doSomething", &MyClass::doSomethingElse)
* .method("other", &myOtherFreeMember);
* }
* @endcode
*/
template<class T>
class Ghost:
public internal::GhostMetadata,
protected boost::mpl::if_< typename GhostTypeTraits<T>::is_shared,
SharedPointerPolicy<T>,
RawPointerPolicy<T> >::type
public internal::GhostMetadata
{
BOOST_STATIC_ASSERT( internal::has_element_type<T>::value );
public:
typedef T value_type;
typedef typename GhostTypeTraits<T>::raw_type raw_type;
typedef typename Ghost::pointer pointer;
typedef typename T::element_type raw_type;
typedef T pointer;
typedef naRef (raw_type::*member_func_t)(const CallContext&);
typedef naRef (*free_func_t)(raw_type&, const CallContext&);
typedef boost::function<naRef(naContext, raw_type&)> getter_t;
typedef boost::function<void(naContext, raw_type&, naRef)> setter_t;
typedef boost::function<naRef(naContext, raw_type&)> getter_t;
typedef boost::function<void(naContext, raw_type&, naRef)> setter_t;
typedef boost::function<naRef(raw_type&, const CallContext&)> method_t;
typedef boost::shared_ptr<internal::MethodHolder> MethodHolderPtr;
class MethodHolder:
public internal::MethodHolder
{
public:
MethodHolder():
_naRef(naNil())
{}
explicit MethodHolder(const method_t& method):
_method(method),
_naRef(naNil())
{}
virtual naRef get_naRef(naContext c)
{
if( naIsNil(_naRef) )
{
_naRef = naNewFunc(c, naNewCCodeU(c, &MethodHolder::call, this));
naSave(c, _naRef);
}
return _naRef;
}
protected:
method_t _method;
naRef _naRef;
static naRef call( naContext c,
naRef me,
int argc,
naRef* args,
void* user_data )
{
MethodHolder* holder = static_cast<MethodHolder*>(user_data);
if( !holder )
naRuntimeError(c, "invalid method holder!");
return holder->_method
(
requireObject(c, me),
CallContext(c, argc, args)
);
}
};
/**
* A ghost member. Can consist either of getter and/or setter functions
@@ -318,25 +247,24 @@ namespace nasal
*/
struct member_t
{
member_t():
func(0)
member_t()
{}
member_t( const getter_t& getter,
const setter_t& setter,
naCFunction func = 0 ):
const MethodHolderPtr& func = MethodHolderPtr() ):
getter( getter ),
setter( setter ),
func( func )
{}
member_t(naCFunction func):
explicit member_t(const MethodHolderPtr& func):
func( func )
{}
getter_t getter;
setter_t setter;
naCFunction func;
getter_t getter;
setter_t setter;
MethodHolderPtr func;
};
typedef std::map<std::string, member_t> MemberMap;
@@ -356,27 +284,40 @@ namespace nasal
return *getSingletonPtr();
}
/**
* Check whether ghost type has already been initialized.
*/
static bool isInit()
{
return getSingletonPtr();
}
/**
* Register a base class for this ghost. The base class needs to be
* registers on its own before it can be used as base class.
*
* @tparam BaseGhost Type of base class already wrapped into Ghost class
* (Ghost<Base>)
* (Ghost<BasePtr>)
*
* @code{cpp}
* Ghost<MyBase>::init("MyBase");
* Ghost<MyClass>::init("MyClass")
* .bases<Ghost<MyBase> >();
* Ghost<MyBasePtr>::init("MyBase");
* Ghost<MyClassPtr>::init("MyClass")
* .bases<Ghost<MyBasePtr> >();
* @endcode
*/
template<class BaseGhost>
typename boost::enable_if_c
< boost::is_base_of<GhostMetadata, BaseGhost>::value
&& boost::is_base_of<typename BaseGhost::raw_type, raw_type>::value,
typename boost::enable_if
<
boost::is_base_of<GhostMetadata, BaseGhost>,
Ghost
>::type&
bases()
{
BOOST_STATIC_ASSERT
((
boost::is_base_of<typename BaseGhost::raw_type, raw_type>::value
));
BaseGhost* base = BaseGhost::getSingletonPtr();
base->addDerived
(
@@ -417,22 +358,27 @@ namespace nasal
* Register a base class for this ghost. The base class needs to be
* registers on its own before it can be used as base class.
*
* @tparam Base Type of base class (Base as used in Ghost<Base>)
* @tparam Base Type of base class (Base as used in Ghost<BasePtr>)
*
* @code{cpp}
* Ghost<MyBase>::init("MyBase");
* Ghost<MyClass>::init("MyClass")
* .bases<MyBase>();
* Ghost<MyBasePtr>::init("MyBase");
* Ghost<MyClassPtr>::init("MyClass")
* .bases<MyBasePtr>();
* @endcode
*/
template<class Base>
typename boost::enable_if_c
< !boost::is_base_of<GhostMetadata, Base>::value
&& boost::is_base_of<typename Ghost<Base>::raw_type, raw_type>::value,
typename boost::disable_if
<
boost::is_base_of<GhostMetadata, Base>,
Ghost
>::type&
bases()
{
BOOST_STATIC_ASSERT
((
boost::is_base_of<typename Ghost<Base>::raw_type, raw_type>::value
));
return bases< Ghost<Base> >();
}
@@ -455,30 +401,25 @@ namespace nasal
* @param setter Setter for variable (Pass 0 to prevent write access)
*
*/
template<class Var>
template<class Ret, class Param>
Ghost& member( const std::string& field,
Var (raw_type::*getter)() const,
void (raw_type::*setter)(typename boost::call_traits<Var>::param_type) = 0 )
Ret (raw_type::*getter)() const,
void (raw_type::*setter)(Param) )
{
member_t m;
if( getter )
{
typedef typename boost::call_traits<Var>::param_type param_type;
naRef (*to_nasal_)(naContext, param_type) = &nasal::to_nasal;
return member(field, to_getter(getter), to_setter(setter));
}
// Getter signature: naRef(naContext, raw_type&)
m.getter = boost::bind(to_nasal_, _1, boost::bind(getter, _2));
}
if( setter )
{
Var (*from_nasal_)(naContext, naRef) = &nasal::from_nasal;
// Setter signature: void(naContext, raw_type&, naRef)
m.setter = boost::bind(setter, _2, boost::bind(from_nasal_, _1, _3));
}
return member(field, m.getter, m.setter);
/**
* Register a read only member variable.
*
* @param field Name of member
* @param getter Getter for variable
*/
template<class Ret>
Ghost& member( const std::string& field,
Ret (raw_type::*getter)() const )
{
return member(field, to_getter(getter), setter_t());
}
/**
@@ -491,7 +432,7 @@ namespace nasal
Ghost& member( const std::string& field,
void (raw_type::*setter)(Var) )
{
return member<Var>(field, 0, setter);
return member(field, getter_t(), to_setter(setter));
}
/**
@@ -519,71 +460,59 @@ namespace nasal
}
/**
* Register a member function.
*
* @note Because only function pointers can be registered as Nasal
* functions it is needed to pass the function pointer as template
* argument. This allows us to create a separate instance of the
* MemberFunctionWrapper for each registered function and therefore
* provides us with the needed static functions to be passed on to
* Nasal.
*
* @tparam func Pointer to member function being registered.
* Register anything that accepts an object instance and a
* nasal::CallContext and returns naRef as method.
*
* @code{cpp}
* class MyClass
* {
* public:
* naRef myMethod(naContext c, int argc, naRef* args);
* naRef myMethod(const nasal::CallContext& ctx);
* }
*
* Ghost<MyClass>::init("Test")
* .method<&MyClass::myMethod>("myMethod");
* Ghost<MyClassPtr>::init("Test")
* .method("myMethod", &MyClass::myMethod);
* @endcode
*/
template<member_func_t func>
Ghost& method(const std::string& name)
Ghost& method(const std::string& name, const method_t& func)
{
_members[name].func = &MemberFunctionWrapper<func>::call;
_members[name].func.reset( new MethodHolder(func) );
return *this;
}
/**
* Register a free function as member function. The object instance is
* passed as additional first argument.
*
* @tparam func Pointer to free function being registered.
*
* @note Due to a severe bug in Visual Studio it is not possible to create
* a specialization of #method for free function pointers and
* member function pointers at the same time. Do overcome this
* limitation we had to use a different name for this function.
* Register anything that accepts an object instance and a
* nasal::CallContext whith automatic conversion of the return type to
* Nasal.
*
* @code{cpp}
* class MyClass;
* naRef myMethod(MyClass& obj, naContext c, int argc, naRef* args);
* void doIt(const MyClass& c, const nasal::CallContext& ctx);
*
* Ghost<MyClass>::init("Test")
* .method_func<&myMethod>("myMethod");
* Ghost<MyClassPtr>::init("Test")
* .method("doIt", &doIt);
* @endcode
*/
template<free_func_t func>
Ghost& method_func(const std::string& name)
template<class Ret>
Ghost& method
(
const std::string& name,
const boost::function<Ret (raw_type&, const CallContext&)>& func
)
{
_members[name].func = &FreeFunctionWrapper<func>::call;
return *this;
return method(name, boost::bind(method_invoker<Ret>, func, _1, _2));
}
#define BOOST_PP_ITERATION_LIMITS (0, 9)
#define BOOST_PP_FILENAME_1 <simgear/nasal/cppbind/detail/functor_templates.hxx>
#include BOOST_PP_ITERATE()
// TODO use variadic template when supporting C++11
/**
* Create a Nasal instance of this ghost.
*
* @param c Active Nasal context
*/
static naRef create( naContext c )
{
return makeGhost(c, Ghost::createInstance());
}
// TODO check if default constructor exists
// static naRef create( naContext c )
// {
// return makeGhost(c, createInstance());
// }
/**
* Create a Nasal instance of this ghost.
@@ -594,7 +523,7 @@ namespace nasal
template<class A1>
static naRef create( naContext c, const A1& a1 )
{
return makeGhost(c, Ghost::createInstance(a1));
return makeGhost(c, createInstance(a1));
}
/**
@@ -627,7 +556,7 @@ namespace nasal
{
// Check if it's a ghost and if it can be converted
if( isBaseOf( naGhost_type(me) ) )
return Ghost::getPtr( naGhost_ptr(me) );
return getPtr( naGhost_ptr(me) );
// Now if it is derived from a ghost (hash with ghost in parent vector)
else if( naIsHash(me) )
@@ -646,7 +575,7 @@ namespace nasal
++parent )
{
pointer ptr = fromNasal(c, *parent);
if( ptr )
if( get_pointer(ptr) )
return ptr;
}
}
@@ -663,6 +592,36 @@ namespace nasal
typedef std::vector<type_checker_t> DerivedList;
DerivedList _derived_types;
/**
* Create a shared pointer on the heap to handle the reference counting
* for the passed shared pointer while it is used in Nasal space.
*/
static pointer* createInstance(const pointer& ptr)
{
return get_pointer(ptr) ? new pointer(ptr) : 0;
}
static pointer getPtr(void* ptr)
{
if( ptr )
return *static_cast<pointer*>(ptr);
else
return pointer();
}
static raw_type* getRawPtr(void* ptr)
{
if( ptr )
return get_pointer(*static_cast<pointer*>(ptr));
else
return 0;
}
static raw_type* getRawPtr(const pointer& ptr)
{
return get_pointer(ptr);
}
void addDerived( const internal::GhostMetadata* derived_meta,
const type_checker_t& derived_info )
{
@@ -724,7 +683,7 @@ namespace nasal
static raw_type& requireObject(naContext c, naRef me)
{
raw_type* obj = Ghost::getRawPtr( fromNasal(c, me) );
raw_type* obj = getRawPtr( fromNasal(c, me) );
naGhostType* ghost_type = naGhost_type(me);
if( !obj )
@@ -739,45 +698,97 @@ namespace nasal
return *obj;
}
/**
* Wrapper class to enable registering pointers to member functions as
* Nasal function callbacks. We need to use the function pointer as
* template parameter to ensure every registered function gets a static
* function which can be passed to Nasal.
*/
template<member_func_t func>
struct MemberFunctionWrapper
template<class Ret>
getter_t to_getter(Ret (raw_type::*getter)() const)
{
/**
* Called from Nasal upon invocation of the according registered
* function. Forwards the call to the passed object instance.
*/
static naRef call(naContext c, naRef me, int argc, naRef* args)
{
return (requireObject(c, me).*func)(CallContext(c, argc, args));
}
typedef typename boost::call_traits<Ret>::param_type param_type;
naRef(*to_nasal_)(naContext, param_type) = &to_nasal;
// Getter signature: naRef(naContext, raw_type&)
return boost::bind
(
to_nasal_,
_1,
boost::bind(getter, _2)
);
}
template<class Param>
setter_t to_setter(void (raw_type::*setter)(Param))
{
// Setter signature: void(naContext, raw_type&, naRef)
return boost::bind
(
setter,
_2,
boost::bind(from_nasal_ptr<Param>::get(), _1, _3)
);
}
/**
* Invoke a method which returns a value and convert it to Nasal.
*/
template<class Ret>
static
typename boost::disable_if<boost::is_void<Ret>, naRef>::type
method_invoker
(
const boost::function<Ret (raw_type&, const CallContext&)>& func,
raw_type& obj,
const CallContext& ctx
)
{
return (*to_nasal_ptr<Ret>::get())(ctx.c, func(obj, ctx));
};
/**
* Wrapper class to enable registering pointers to free functions (only
* external linkage). We need to use the function pointer as template
* parameter to ensure every registered function gets a static function
* which can be passed to Nasal. Even though we just wrap another simple
* function pointer this intermediate step is need to be able to retrieve
* the object the function call belongs to and pass it along as argument.
* Invoke a method which returns void and "convert" it to nil.
*/
template<free_func_t func>
struct FreeFunctionWrapper
template<class Ret>
static
typename boost::enable_if<boost::is_void<Ret>, naRef>::type
method_invoker
(
const boost::function<Ret (raw_type&, const CallContext&)>& func,
raw_type& obj,
const CallContext& ctx
)
{
/**
* Called from Nasal upon invocation of the according registered
* function. Forwards the call to the passed function pointer and passes
* the required parameters.
*/
static naRef call(naContext c, naRef me, int argc, naRef* args)
{
return func(requireObject(c, me), CallContext(c, argc, args));
}
func(obj, ctx);
return naNil();
};
/**
* Extract argument by index from nasal::CallContext and convert to given
* type.
*/
template<class Arg>
static
typename boost::disable_if<
internal::reduced_is_same<Arg, CallContext>,
typename from_nasal_ptr<Arg>::return_type
>::type
arg_from_nasal(const CallContext& ctx, size_t index)
{
return ctx.requireArg<Arg>(index);
};
/**
* Specialization to pass through nasal::CallContext.
*/
template<class Arg>
static
typename boost::enable_if<
internal::reduced_is_same<Arg, CallContext>,
typename from_nasal_ptr<Arg>::return_type
>::type
arg_from_nasal(const CallContext& ctx, size_t)
{
// Either const CallContext& or CallContext, non-const reference
// does not make sense.
BOOST_STATIC_ASSERT( (!boost::is_same<Arg, CallContext&>::value) );
return ctx;
};
typedef std::auto_ptr<Ghost> GhostPtr;
@@ -800,20 +811,13 @@ namespace nasal
static naRef makeGhost(naContext c, void *ptr)
{
if( Ghost::getRawPtr(ptr) )
if( getRawPtr(ptr) )
{
naGhostType* ghost_type = 0;
if( Ghost::returns_dynamic_type::value )
// For pointer policies already returning instances of an object
// with the dynamic type of this Ghost's raw_type the type is always
// the same.
ghost_type = &getSingletonPtr()->_ghost_type;
else
// If wrapping eg. shared pointers the users passes an already
// existing instance of an object which will then be hold be a new
// shared pointer. We therefore have to check for the dynamic type
// of the object as it might differ from the passed static type.
ghost_type = getTypeFor<Ghost>( Ghost::getRawPtr(ptr) );
// We are wrapping shared pointers to already existing objects which
// will then be hold be a new shared pointer. We therefore have to
// check for the dynamic type of the object as it might differ from
// the passed static type.
naGhostType* ghost_type = getTypeFor<Ghost>( getRawPtr(ptr) );
if( ghost_type )
return naNewGhost2(c, ghost_type, ptr);
@@ -825,7 +829,7 @@ namespace nasal
static void destroyGhost(void *ptr)
{
delete static_cast<T*>(ptr);
delete static_cast<pointer*>(ptr);
}
/**
@@ -850,9 +854,9 @@ namespace nasal
return 0;
if( member->second.func )
*out = nasal::to_nasal(c, member->second.func);
*out = member->second.func->get_naRef(c);
else if( !member->second.getter.empty() )
*out = member->second.getter(c, *Ghost::getRawPtr(g));
*out = member->second.getter(c, *getRawPtr(g));
else
return "Read-protected member";
@@ -870,13 +874,46 @@ namespace nasal
if( member == getSingletonPtr()->_members.end() )
naRuntimeError(c, "ghost: No such member: %s", key.c_str());
if( member->second.setter.empty() )
else if( member->second.setter.empty() )
naRuntimeError(c, "ghost: Write protected member: %s", key.c_str());
else if( member->second.func )
naRuntimeError(c, "ghost: Write to function: %s", key.c_str());
member->second.setter(c, *Ghost::getRawPtr(g), val);
member->second.setter(c, *getRawPtr(g), val);
}
};
} // namespace nasal
/**
* Convert every shared pointer to a ghost.
*/
// Needs to be outside any namespace to mark ADL work
template<class T>
typename boost::enable_if<
nasal::internal::has_element_type<
typename nasal::internal::reduced_type<T>::type
>,
naRef
>::type
to_nasal_helper(naContext c, T ptr)
{
return nasal::Ghost<T>::create(c, ptr);
}
/**
* Convert nasal ghosts/hashes to shared pointer (of a ghost)
*/
template<class T>
typename boost::enable_if<
nasal::internal::has_element_type<
typename nasal::internal::reduced_type<T>::type
>,
T
>::type
from_nasal_helper(naContext c, naRef ref, const T*)
{
return nasal::Ghost<T>::fromNasal(c, ref);
}
#endif /* SG_NASAL_GHOST_HXX_ */

View File

@@ -0,0 +1,136 @@
///@file
/// Call context for Nasal extension functions
///
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_NASAL_CALL_CONTEXT_HXX_
#define SG_NASAL_CALL_CONTEXT_HXX_
#include "from_nasal.hxx"
#include "to_nasal.hxx"
namespace nasal
{
/**
* Context passed to a function/method being called from Nasal
*/
class CallContext
{
public:
CallContext(naContext c, size_t argc, naRef* args):
c(c),
argc(argc),
args(args)
{}
bool isNumeric(size_t index) const
{
return (index < argc && naIsNum(args[index]));
}
bool isString(size_t index) const
{
return (index < argc && naIsString(args[index]));
}
bool isHash(size_t index) const
{
return (index < argc && naIsHash(args[index]));
}
bool isVector(size_t index) const
{
return (index < argc && naIsVector(args[index]));
}
bool isGhost(size_t index) const
{
return (index < argc && naIsGhost(args[index]));
}
void popFront(size_t num = 1)
{
if( argc < num )
return;
args += num;
argc -= num;
}
void popBack(size_t num = 1)
{
if( argc < num )
return;
argc -= num;
}
/**
* Get the argument with given index if it exists. Otherwise returns the
* passed default value.
*
* @tparam T Type of argument (converted using ::from_nasal)
* @param index Index of requested argument
* @param def Default value returned if too few arguments available
*/
template<class T>
typename from_nasal_ptr<T>::return_type
getArg(size_t index, const T& def = T()) const
{
if( index >= argc )
return def;
return from_nasal<T>(args[index]);
}
/**
* Get the argument with given index. Raises a Nasal runtime error if
* there are to few arguments available.
*/
template<class T>
typename from_nasal_ptr<T>::return_type
requireArg(size_t index) const
{
if( index >= argc )
naRuntimeError(c, "Missing required arg #%d", index);
return from_nasal<T>(args[index]);
}
template<class T>
naRef to_nasal(T arg) const
{
return nasal::to_nasal(c, arg);
}
template<class T>
typename from_nasal_ptr<T>::return_type
from_nasal(naRef ref) const
{
return (*from_nasal_ptr<T>::get())(c, ref);
}
naContext c;
size_t argc;
naRef *args;
};
} // namespace nasal
#endif /* SG_NASAL_CALL_CONTEXT_HXX_ */

View File

@@ -0,0 +1,180 @@
// Wrapper class for Nasal strings
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "NasalString.hxx"
#include "to_nasal.hxx"
#include <algorithm>
#include <cassert>
#include <cstring>
#include <functional>
#include <stdexcept>
namespace nasal
{
/**
* Predicate (eg. for std::find_if) returning true if no character of the
* stored string given by the range [begin, end) matches.
*/
struct no_match:
public std::unary_function<const char, bool>
{
no_match(const char* begin, const char* end):
_begin(begin),
_end(end)
{}
bool operator()(const char c) const
{
return std::find(_begin, _end, c) == _end;
}
private:
const char* _begin;
const char* _end;
};
//template<typename Iterator>
//Iterator
//rfind_first_of( Iterator rbegin_src, Iterator rend_src,
// Iterator begin_find, Iterator end_find )
//{
// for(; rbegin_src != rend_src; --rbegin_src)
// {
// for(Iterator chr = begin_find; chr != end_find; ++chr)
// {
// if( *rbegin_src == *chr )
// return rbegin_src;
// }
// }
// return rend_src;
//}
const size_t String::npos = static_cast<size_t>(-1);
//----------------------------------------------------------------------------
String::String(naContext c, const char* str):
_str( to_nasal(c, str) )
{
assert( naIsString(_str) );
}
//----------------------------------------------------------------------------
String::String(naRef str):
_str(str)
{
assert( naIsString(_str) );
}
//----------------------------------------------------------------------------
const char* String::c_str() const
{
return naStr_data(_str);
}
//----------------------------------------------------------------------------
const char* String::begin() const
{
return c_str();
}
//----------------------------------------------------------------------------
const char* String::end() const
{
return c_str() + size();
}
//----------------------------------------------------------------------------
size_t String::size() const
{
return naStr_len(_str);
}
//----------------------------------------------------------------------------
size_t String::length() const
{
return size();
}
//----------------------------------------------------------------------------
bool String::empty() const
{
return size() == 0;
}
//----------------------------------------------------------------------------
int String::compare(size_t pos, size_t len, const String& rhs) const
{
if( pos >= size() )
throw std::out_of_range("nasal::String::compare: pos");
return memcmp( begin() + pos,
rhs.begin(),
std::min(rhs.size(), std::min(size() - pos, len)) );
}
//----------------------------------------------------------------------------
bool String::starts_with(const String& rhs) const
{
return rhs.size() <= size() && compare(0, npos, rhs) == 0;
}
//----------------------------------------------------------------------------
size_t String::find(const char c, size_t pos) const
{
if( pos >= size() )
return npos;
const char* result = std::find(begin() + pos, end(), c);
return result != end() ? result - begin() : npos;
}
//----------------------------------------------------------------------------
size_t String::find_first_of(const String& chr, size_t pos) const
{
if( pos >= size() )
return npos;
const char* result = std::find_first_of( begin() + pos, end(),
chr.begin(), chr.end() );
return result != end() ? result - begin() : npos;
}
//----------------------------------------------------------------------------
size_t String::find_first_not_of(const String& chr, size_t pos) const
{
if( pos >= size() )
return npos;
const char* result = std::find_if( begin() + pos, end(),
no_match(chr.begin(), chr.end()) );
return result != end() ? result - begin() : npos;
}
//----------------------------------------------------------------------------
const naRef String::get_naRef() const
{
return _str;
}
} // namespace nasal

View File

@@ -0,0 +1,80 @@
///@file Wrapper class for Nasal strings
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_NASAL_STRING_HXX_
#define SG_NASAL_STRING_HXX_
#include "from_nasal.hxx"
#include "to_nasal.hxx"
namespace nasal
{
/**
* A Nasal String
*/
class String
{
public:
static const size_t npos;
/**
* Create a new Nasal String
*
* @param c Nasal context for creating the string
* @param str String data
*/
String(naContext c, const char* str);
/**
* Initialize from an existing Nasal String
*
* @param str Existing Nasal String
*/
String(naRef string);
const char* c_str() const;
const char* begin() const;
const char* end() const;
size_t size() const;
size_t length() const;
bool empty() const;
int compare(size_t pos, size_t len, const String& rhs) const;
bool starts_with(const String& rhs) const;
size_t find(const char c, size_t pos = 0) const;
size_t find_first_of(const String& chr, size_t pos = 0) const;
size_t find_first_not_of(const String& chr, size_t pos = 0) const;
/**
* Get Nasal representation of String
*/
const naRef get_naRef() const;
protected:
naRef _str;
};
} // namespace nasal
#endif /* SG_NASAL_STRING_HXX_ */

View File

@@ -1,9 +1,11 @@
#include <simgear/math/SGVec2.hxx>
#include <simgear/math/SGMath.hxx>
#include "Ghost.hxx"
#include "NasalHash.hxx"
#include "NasalString.hxx"
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <cstring>
#include <iostream>
@@ -22,7 +24,21 @@ struct Base
std::string getString() const { return ""; }
void setString(const std::string&) {}
void constVoidFunc() const {}
size_t test1Arg(const std::string& str) const { return str.length(); }
bool test2Args(const std::string& s, bool c) { return c && s.empty(); }
std::string var;
const std::string& getVar() const { return var; }
void setVar(const std::string v) { var = v; }
};
void baseVoidFunc(Base& b) {}
void baseConstVoidFunc(const Base& b) {}
size_t baseFunc2Args(Base& b, int x, const std::string& s) { return x + s.size(); }
std::string testPtr(Base& b) { return b.getString(); }
void baseFuncCallContext(const Base&, const nasal::CallContext&) {}
struct Derived:
public Base
{
@@ -35,22 +51,30 @@ struct DoubleDerived:
{
};
typedef boost::shared_ptr<Base> BasePtr;
typedef std::vector<BasePtr> BaseVec;
struct DoubleDerived2:
public Derived
{
const BasePtr& getBase() const{return _base;}
BasePtr _base;
BaseVec doSomeBaseWork(const BaseVec& v) { return v; }
};
typedef boost::shared_ptr<Base> BasePtr;
typedef boost::shared_ptr<Derived> DerivedPtr;
typedef boost::shared_ptr<DoubleDerived> DoubleDerivedPtr;
typedef boost::shared_ptr<DoubleDerived2> DoubleDerived2Ptr;
naRef member(Derived&, const nasal::CallContext&) { return naNil(); }
typedef boost::weak_ptr<Derived> DerivedWeakPtr;
naRef derivedFreeMember(Derived&, const nasal::CallContext&) { return naNil(); }
naRef f_derivedGetX(naContext c, const Derived& d)
{
return nasal::to_nasal(c, d.getX());
}
naRef f_freeFunction(nasal::CallContext) { return naNil(); }
int main(int argc, char* argv[])
{
@@ -101,6 +125,7 @@ int main(int argc, char* argv[])
hash.set("vec2", vec);
hash.set("name", "my-name");
hash.set("string", std::string("blub"));
hash.set("func", &f_freeFunction);
r = to_nasal(c, hash);
VERIFY( naIsHash(r) );
@@ -111,31 +136,45 @@ int main(int argc, char* argv[])
Hash mod = hash.createHash("mod");
mod.set("parent", hash);
Ghost<Base>::init("Base")
.method<&Base::member>("member")
.member("str", &Base::getString, &Base::setString);
Ghost<Derived>::init("Derived")
.bases<Base>()
.member("x", &Derived::getX, &Derived::setX)
.member("x_alternate", &f_derivedGetX)
.method_func<&member>("free_member");
//----------------------------------------------------------------------------
// Test exposing classes to Nasal
//----------------------------------------------------------------------------
naRef derived = Ghost<Derived>::create(c);
VERIFY( naIsGhost(derived) );
VERIFY( std::string("Derived") == naGhost_type(derived)->name );
Ghost<BasePtr>::init("BasePtr");
Ghost<BasePtr>::init("BasePtr")
.method("member", &Base::member)
.method("strlen", &Base::test1Arg)
.member("str", &Base::getString, &Base::setString)
.method("str_m", &Base::getString)
.method("void", &Base::constVoidFunc)
.member("var_r", &Base::getVar)
.member("var_w", &Base::setVar)
.member("var", &Base::getVar, &Base::setVar)
.method("void", &baseVoidFunc)
.method("void_c", &baseConstVoidFunc)
.method("int2args", &baseFunc2Args)
.method("bool2args", &Base::test2Args)
.method("str_ptr", &testPtr);
Ghost<DerivedPtr>::init("DerivedPtr")
.bases<BasePtr>()
.member("x", &Derived::getX, &Derived::setX)
.method_func<&member>("free_member");
.member("x_alternate", &f_derivedGetX)
.method("free_fn", &derivedFreeMember)
.method("free_member", &derivedFreeMember)
.method("baseDoIt", &baseFuncCallContext);
Ghost<DoubleDerivedPtr>::init("DoubleDerivedPtr")
.bases<DerivedPtr>();
Ghost<DoubleDerived2Ptr>::init("DoubleDerived2Ptr")
.bases<DerivedPtr>();
.bases< Ghost<DerivedPtr> >()
.member("base", &DoubleDerived2::getBase)
.method("doIt", &DoubleDerived2::doSomeBaseWork);
Ghost<DerivedWeakPtr>::init("DerivedWeakPtr");
VERIFY( Ghost<BasePtr>::isInit() );
nasal::to_nasal(c, DoubleDerived2Ptr());
BasePtr d( new Derived );
derived = Ghost<BasePtr>::create(c, d);
naRef derived = Ghost<BasePtr>::create(c, d);
VERIFY( naIsGhost(derived) );
VERIFY( std::string("DerivedPtr") == naGhost_type(derived)->name );
@@ -161,6 +200,18 @@ int main(int argc, char* argv[])
== boost::dynamic_pointer_cast<DoubleDerived2>(d3) );
VERIFY( !Ghost<DoubleDerivedPtr>::fromNasal(c, derived) );
std::map<std::string, BasePtr> instances;
VERIFY( naIsHash(to_nasal(c, instances)) );
std::map<std::string, DerivedPtr> instances_d;
VERIFY( naIsHash(to_nasal(c, instances_d)) );
std::map<std::string, int> int_map;
VERIFY( naIsHash(to_nasal(c, int_map)) );
std::map<std::string, std::vector<int> > int_vector_map;
VERIFY( naIsHash(to_nasal(c, int_vector_map)) );
// Check converting to Ghost if using Nasal hashes with actual ghost inside
// the hashes parents vector
std::vector<naRef> parents;
@@ -178,18 +229,72 @@ int main(int argc, char* argv[])
derived_obj.set("parents", parents2);
VERIFY( Ghost<BasePtr>::fromNasal(c, derived_obj.get_naRef()) == d3 );
std::vector<naRef> nasal_objects;
nasal_objects.push_back( Ghost<BasePtr>::create(c, d) );
nasal_objects.push_back( Ghost<BasePtr>::create(c, d2) );
nasal_objects.push_back( Ghost<BasePtr>::create(c, d3) );
naRef obj_vec = to_nasal(c, nasal_objects);
std::vector<BasePtr> objects = from_nasal<std::vector<BasePtr> >(c, obj_vec);
VERIFY( objects[0] == d );
VERIFY( objects[1] == d2 );
VERIFY( objects[2] == d3 );
// TODO actually do something with the ghosts...
//----------------------------------------------------------------------------
// Test nasal::CallContext
//----------------------------------------------------------------------------
int int_vec[] = {1,2,3};
std::map<std::string, std::string> map;
naRef args[] = {
to_nasal(c, std::string("test-arg"))
to_nasal(c, std::string("test-arg")),
to_nasal(c, 4),
to_nasal(c, int_vec),
to_nasal(c, map)
};
CallContext cc(c, sizeof(args)/sizeof(args[0]), args);
VERIFY( cc.requireArg<std::string>(0) == "test-arg" );
VERIFY( cc.getArg<std::string>(0) == "test-arg" );
VERIFY( cc.getArg<std::string>(1) == "" );
VERIFY( cc.getArg<std::string>(10) == "" );
VERIFY( cc.isString(0) );
VERIFY( !cc.isNumeric(0) );
VERIFY( !cc.isVector(0) );
VERIFY( !cc.isHash(0) );
VERIFY( !cc.isGhost(0) );
VERIFY( cc.isNumeric(1) );
VERIFY( cc.isVector(2) );
VERIFY( cc.isHash(3) );
naRef args_vec = nasal::to_nasal(c, args);
VERIFY( naIsVector(args_vec) );
// TODO actually do something with the ghosts...
//----------------------------------------------------------------------------
// Test nasal::String
//----------------------------------------------------------------------------
String string( to_nasal(c, "Test") );
VERIFY( from_nasal<std::string>(c, string.get_naRef()) == "Test" );
VERIFY( string.c_str() == std::string("Test") );
VERIFY( string.starts_with(string) );
VERIFY( string.starts_with(String(c, "T")) );
VERIFY( string.starts_with(String(c, "Te")) );
VERIFY( string.starts_with(String(c, "Tes")) );
VERIFY( string.starts_with(String(c, "Test")) );
VERIFY( !string.starts_with(String(c, "Test1")) );
VERIFY( !string.starts_with(String(c, "bb")) );
VERIFY( !string.starts_with(String(c, "bbasdasdafasd")) );
VERIFY( string.find('e') == 1 );
VERIFY( string.find('9') == String::npos );
VERIFY( string.find_first_of(String(c, "st")) == 2 );
VERIFY( string.find_first_of(String(c, "st"), 3) == 3 );
VERIFY( string.find_first_of(String(c, "xyz")) == String::npos );
VERIFY( string.find_first_not_of(String(c, "Tst")) == 1 );
VERIFY( string.find_first_not_of(String(c, "Tse"), 2) == 3 );
VERIFY( string.find_first_not_of(String(c, "abc")) == 0 );
VERIFY( string.find_first_not_of(String(c, "abc"), 20) == String::npos );
naFreeContext(c);

View File

@@ -16,20 +16,24 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "from_nasal_detail.hxx"
#include "NasalHash.hxx"
#include "from_nasal_helper.hxx"
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/NasalString.hxx>
#include <simgear/misc/sg_path.hxx>
namespace nasal
{
//----------------------------------------------------------------------------
bad_nasal_cast::bad_nasal_cast()
bad_nasal_cast::bad_nasal_cast():
sg_exception("conversion failed", "bad_nasal_cast")
{
}
//----------------------------------------------------------------------------
bad_nasal_cast::bad_nasal_cast(const std::string& msg):
_msg( msg )
sg_exception(msg, "bad_nasal_cast")
{
}
@@ -41,20 +45,27 @@ namespace nasal
}
//----------------------------------------------------------------------------
const char* bad_nasal_cast::what() const throw()
{
return _msg.empty() ? bad_cast::what() : _msg.c_str();
}
//----------------------------------------------------------------------------
std::string from_nasal_helper(naContext c, naRef ref, std::string*)
std::string from_nasal_helper(naContext c, naRef ref, const std::string*)
{
naRef na_str = naStringValue(c, ref);
if( naIsNil(na_str) )
return std::string();
else if( !naIsString(na_str) )
throw bad_nasal_cast("Not convertible to string");
return std::string(naStr_data(na_str), naStr_len(na_str));
}
//----------------------------------------------------------------------------
Hash from_nasal_helper(naContext c, naRef ref, Hash*)
SGPath from_nasal_helper(naContext c, naRef ref, const SGPath*)
{
naRef na_str = naStringValue(c, ref);
return SGPath(std::string(naStr_data(na_str), naStr_len(na_str)));
}
//----------------------------------------------------------------------------
Hash from_nasal_helper(naContext c, naRef ref, const Hash*)
{
if( !naIsHash(ref) )
throw bad_nasal_cast("Not a hash");
@@ -62,4 +73,19 @@ namespace nasal
return Hash(ref, c);
}
//----------------------------------------------------------------------------
String from_nasal_helper(naContext c, naRef ref, const String*)
{
if( !naIsString(ref) )
throw bad_nasal_cast("Not a string");
return String(ref);
}
//----------------------------------------------------------------------------
bool from_nasal_helper(naContext c, naRef ref, const bool*)
{
return naTrue(ref) == 1;
}
} // namespace nasal

View File

@@ -17,12 +17,13 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_FROM_NASAL_DETAIL_HXX_
#define SG_FROM_NASAL_DETAIL_HXX_
#ifndef SG_FROM_NASAL_HELPER_HXX_
#define SG_FROM_NASAL_HELPER_HXX_
#include "nasal_traits.hxx"
#include <simgear/nasal/nasal.h>
#include <simgear/structure/exception.hxx>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>
@@ -31,15 +32,19 @@
#include <typeinfo> // std::bad_cast
#include <vector>
class SGPath;
namespace nasal
{
class Hash;
class String;
/**
* Thrown when converting a type from/to Nasal has failed
*/
class bad_nasal_cast:
public std::bad_cast
public std::bad_cast,
public sg_exception
{
public:
/**
@@ -56,11 +61,6 @@ namespace nasal
virtual ~bad_nasal_cast() throw();
/**
* Get a description of the cause of the failed cast.
*/
virtual const char* what() const throw();
protected:
std::string _msg;
};
@@ -68,17 +68,36 @@ namespace nasal
/**
* Simple pass through for unified handling also of naRef.
*/
inline naRef from_nasal_helper(naContext, naRef ref, naRef*) { return ref; }
inline naRef from_nasal_helper(naContext, naRef ref, const naRef*)
{ return ref; }
/**
* Convert Nasal string to std::string
*/
std::string from_nasal_helper(naContext c, naRef ref, std::string*);
std::string from_nasal_helper(naContext c, naRef ref, const std::string*);
/**
* Convert a Nasal string to an SGPath
*/
SGPath from_nasal_helper(naContext c, naRef ref, const SGPath*);
/**
* Convert a Nasal hash to a nasal::Hash
*/
Hash from_nasal_helper(naContext c, naRef ref, Hash*);
Hash from_nasal_helper(naContext c, naRef ref, const Hash*);
/**
* Convert a Nasal string to a nasal::String
*/
String from_nasal_helper(naContext c, naRef ref, const String*);
/**
* Convert a Nasal object to bool.
*
* @return true, if ref is string or ref is number != 0
* false, else
*/
bool from_nasal_helper(naContext c, naRef ref, const bool*);
/**
* Convert a Nasal number to a C++ numeric type
@@ -87,7 +106,7 @@ namespace nasal
typename boost::enable_if< boost::is_arithmetic<T>,
T
>::type
from_nasal_helper(naContext c, naRef ref, T*)
from_nasal_helper(naContext c, naRef ref, const T*)
{
naRef num = naNumValue(ref);
if( !naIsNum(num) )
@@ -100,7 +119,8 @@ namespace nasal
* Convert a Nasal vector to a std::vector
*/
template<class T>
std::vector<T> from_nasal_helper(naContext c, naRef ref, std::vector<T>*)
std::vector<T>
from_nasal_helper(naContext c, naRef ref, const std::vector<T>*)
{
if( !naIsVector(ref) )
throw bad_nasal_cast("Not a vector");
@@ -119,7 +139,7 @@ namespace nasal
*/
template<class Vec2>
typename boost::enable_if<is_vec2<Vec2>, Vec2>::type
from_nasal_helper(naContext c, naRef ref, Vec2*)
from_nasal_helper(naContext c, naRef ref, const Vec2*)
{
std::vector<double> vec =
from_nasal_helper(c, ref, static_cast<std::vector<double>*>(0));
@@ -130,4 +150,4 @@ namespace nasal
} // namespace nasal
#endif /* SG_FROM_NASAL_DETAIL_HXX_ */
#endif /* SG_FROM_NASAL_HELPER_HXX_ */

View File

@@ -0,0 +1,104 @@
#ifndef SG_NASAL_GHOST_HXX_
# error Nasal cppbind - do not include this file!
#endif
#define n BOOST_PP_ITERATION()
#define SG_GHOST_FUNC_TYPE\
boost::function<Ret (raw_type& BOOST_PP_COMMA_IF(n)BOOST_PP_ENUM_PARAMS(n,A))>
/**
* Bind any callable entity accepting an instance of raw_type and an arbitrary
* number of arguments as method.
*/
template<
class Ret
BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_PARAMS(n, class A)
>
Ghost& method(const std::string& name, const SG_GHOST_FUNC_TYPE& func)
{
#if defined(GCC_VERSION) && GCC_VERSION < 40407
// The old version of g++ used on Jenkins (16.11.2012) only compiles this
// version.
# define SG_GHOST_REQUIRE_ARG(z, n, dummy)\
boost::bind(&arg_from_nasal<A##n>, _2, n)
#else
// VS (2008, 2010, ... ?) only allow this version.
# define SG_GHOST_REQUIRE_ARG(z, n, dummy)\
boost::bind(&Ghost::arg_from_nasal<A##n>, _2, n)
#endif
return method<Ret>
(
name,
typename boost::function<Ret (raw_type&, const CallContext&)>
( boost::bind(
func,
_1
BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM(n, SG_GHOST_REQUIRE_ARG, 0)
))
);
#undef SG_GHOST_REQUIRE_ARG
}
#define SG_GHOST_MEM_FN(cv)\
/**\
* Bind a member function with an arbitrary number of arguments as method.\
*/\
template<\
class Ret\
BOOST_PP_COMMA_IF(n)\
BOOST_PP_ENUM_PARAMS(n, class A)\
>\
Ghost& method\
(\
const std::string& name,\
Ret (raw_type::*fn)(BOOST_PP_ENUM_PARAMS(n,A)) cv\
)\
{\
return method<\
Ret\
BOOST_PP_COMMA_IF(n)\
BOOST_PP_ENUM_PARAMS(n,A)\
>(name, SG_GHOST_FUNC_TYPE(fn));\
}
// Work around MSVC warning C4003: not enough actual parameters for macro
// We really do not want to pass a parameter, even if MSVC can not believe it.
#define SG_GHOST_NO_CV
SG_GHOST_MEM_FN(const)
SG_GHOST_MEM_FN(SG_GHOST_NO_CV)
#undef SG_GHOST_MEM_FN
#undef SG_GHOST_NO_CV
/**
* Bind free function accepting an instance of raw_type and an arbitrary
* number of arguments as method.
*/
template<
class Ret,
class Type
BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_PARAMS(n, class A)
>
Ghost& method
(
const std::string& name,
Ret (*fn)(Type BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n,A))
)
{
BOOST_STATIC_ASSERT
(( boost::is_convertible<raw_type&, Type>::value
//|| boost::is_convertible<raw_type*, Type>::value
// TODO check how to do it with pointer...
));
return method<Ret>(name, SG_GHOST_FUNC_TYPE(fn));
}
#undef n
#undef SG_GHOST_TYPEDEF_FUNC_TYPE

View File

@@ -0,0 +1,112 @@
// Conversion functions to convert C++ types to Nasal types
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "to_nasal_helper.hxx"
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/math/SGMath.hxx>
#include <simgear/misc/sg_path.hxx>
#include <boost/function.hpp>
namespace nasal
{
//----------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, const std::string& str)
{
naRef ret = naNewString(c);
naStr_fromdata(ret, str.c_str(), static_cast<int>(str.size()));
return ret;
}
//----------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, const char* str)
{
return to_nasal_helper(c, std::string(str));
}
//----------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, const Hash& hash)
{
return hash.get_naRef();
}
//----------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, const naRef& ref)
{
return ref;
}
//------------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, const SGGeod& pos)
{
nasal::Hash hash(c);
hash.set("lat", pos.getLatitudeDeg());
hash.set("lon", pos.getLongitudeDeg());
hash.set("elevation", pos.getElevationM());
return hash.get_naRef();
}
//----------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, const SGPath& path)
{
return to_nasal_helper(c, path.str());
}
//----------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, naCFunction func)
{
return naNewFunc(c, naNewCCode(c, func));
}
//----------------------------------------------------------------------------
static naRef free_function_invoker( naContext c,
naRef me,
int argc,
naRef* args,
void* user_data )
{
free_function_t* func = static_cast<free_function_t*>(user_data);
assert(func);
return (*func)(nasal::CallContext(c, argc, args));
}
//----------------------------------------------------------------------------
static void free_function_destroy(void* func)
{
delete static_cast<free_function_t*>(func);
}
//----------------------------------------------------------------------------
naRef to_nasal_helper(naContext c, const free_function_t& func)
{
return naNewFunc
(
c,
naNewCCodeUD
(
c,
&free_function_invoker,
new free_function_t(func),
&free_function_destroy
)
);
}
} // namespace nasal

View File

@@ -0,0 +1,172 @@
///@file
/// Conversion helpers used by to_nasal<T>(naContext, T)
///
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_TO_NASAL_HELPER_HXX_
#define SG_TO_NASAL_HELPER_HXX_
#include "nasal_traits.hxx"
#include <simgear/nasal/nasal.h>
#include <boost/function/function_fwd.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/call_traits.hpp>
#include <boost/type_traits.hpp>
#include <map>
#include <string>
#include <vector>
class SGGeod;
class SGPath;
namespace nasal
{
class CallContext;
class Hash;
typedef boost::function<naRef (CallContext)> free_function_t;
/**
* Convert std::string to Nasal string
*/
naRef to_nasal_helper(naContext c, const std::string& str);
/**
* Convert C-string to Nasal string
*/
// We need this to prevent the array overload of to_nasal being called
naRef to_nasal_helper(naContext c, const char* str);
/**
* Convert a nasal::Hash to a Nasal hash
*/
naRef to_nasal_helper(naContext c, const Hash& hash);
/**
* Simple pass-through of naRef types to allow generic usage of to_nasal
*/
naRef to_nasal_helper(naContext c, const naRef& ref);
naRef to_nasal_helper(naContext c, const SGGeod& pos);
naRef to_nasal_helper(naContext c, const SGPath& path);
/**
* Convert function pointer to Nasal function
*/
naRef to_nasal_helper(naContext c, naCFunction func);
naRef to_nasal_helper(naContext c, const free_function_t& func);
/**
* Convert a numeric type to Nasal number
*/
template<class T>
typename boost::enable_if< boost::is_arithmetic<T>, naRef >::type
to_nasal_helper(naContext c, T num)
{
return naNum(num);
}
/**
* Convert a 2d vector to Nasal vector with 2 elements
*/
template<class Vec2>
typename boost::enable_if<is_vec2<Vec2>, naRef>::type
to_nasal_helper(naContext c, const Vec2& vec);
/**
* Convert a std::map to a Nasal Hash
*/
template<class Value>
naRef to_nasal_helper(naContext c, const std::map<std::string, Value>& map);
/**
* Convert a fixed size array to a Nasal vector
*/
template<class T, size_t N>
naRef to_nasal_helper(naContext c, const T(&array)[N]);
/**
* Convert std::vector to Nasal vector
*/
template< template<class T, class Alloc> class Vector,
class T,
class Alloc
>
typename boost::enable_if< boost::is_same< Vector<T,Alloc>,
std::vector<T,Alloc>
>,
naRef
>::type
to_nasal_helper(naContext c, const Vector<T, Alloc>& vec)
{
naRef ret = naNewVector(c);
naVec_setsize(c, ret, static_cast<int>(vec.size()));
for(int i = 0; i < static_cast<int>(vec.size()); ++i)
naVec_set(ret, i, to_nasal_helper(c, vec[i]));
return ret;
}
//----------------------------------------------------------------------------
template<class Vec2>
typename boost::enable_if<is_vec2<Vec2>, naRef>::type
to_nasal_helper(naContext c, const Vec2& vec)
{
// We take just double because in Nasal every number is represented as
// double
double nasal_vec[2] = {vec[0], vec[1]};
return to_nasal_helper(c, nasal_vec);
}
//----------------------------------------------------------------------------
template<class Value>
naRef to_nasal_helper(naContext c, const std::map<std::string, Value>& map)
{
naRef hash = naNewHash(c);
typedef typename boost::call_traits<Value>::param_type param_type;
typedef typename std::map<std::string, Value>::const_iterator map_iterator;
for( map_iterator it = map.begin(); it != map.end(); ++it )
naHash_set
(
hash,
to_nasal_helper(c, it->first),
to_nasal_helper(c, static_cast<param_type>(it->second))
);
return hash;
}
//----------------------------------------------------------------------------
template<class T, size_t N>
naRef to_nasal_helper(naContext c, const T(&array)[N])
{
naRef ret = naNewVector(c);
naVec_setsize(c, ret, static_cast<int>(N));
for(int i = 0; i < static_cast<int>(N); ++i)
naVec_set(ret, i, to_nasal_helper(c, array[i]));
return ret;
}
} // namespace nasal
#endif /* SG_TO_NASAL_HELPER_HXX_ */

View File

@@ -20,7 +20,7 @@
#ifndef SG_FROM_NASAL_HXX_
#define SG_FROM_NASAL_HXX_
#include "from_nasal_detail.hxx"
#include <simgear/nasal/cppbind/detail/from_nasal_helper.hxx>
namespace nasal
{
@@ -45,6 +45,24 @@ namespace nasal
return from_nasal_helper(c, ref, static_cast<T*>(0));
}
/**
* Get pointer to specific version of from_nasal, converting to a type
* compatible to Var.
*/
template<class Var>
struct from_nasal_ptr
{
typedef typename boost::remove_const
< typename boost::remove_reference<Var>::type
>::type return_type;
typedef return_type(*type)(naContext, naRef);
static type get()
{
return &from_nasal<return_type>;
}
};
} // namespace nasal
#endif /* SG_FROM_NASAL_HXX_ */

View File

@@ -1,56 +0,0 @@
// Conversion functions to convert C++ types to Nasal types
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "to_nasal.hxx"
#include "NasalHash.hxx"
namespace nasal
{
//----------------------------------------------------------------------------
naRef to_nasal(naContext c, const std::string& str)
{
naRef ret = naNewString(c);
naStr_fromdata(ret, str.c_str(), str.size());
return ret;
}
//----------------------------------------------------------------------------
naRef to_nasal(naContext c, const char* str)
{
return to_nasal(c, std::string(str));
}
//----------------------------------------------------------------------------
naRef to_nasal(naContext c, naCFunction func)
{
return naNewFunc(c, naNewCCode(c, func));
}
//----------------------------------------------------------------------------
naRef to_nasal(naContext c, const Hash& hash)
{
return hash.get_naRef();
}
//----------------------------------------------------------------------------
naRef to_nasal(naContext c, naRef ref)
{
return ref;
}
} // namespace nasal

View File

@@ -20,103 +20,52 @@
#ifndef SG_TO_NASAL_HXX_
#define SG_TO_NASAL_HXX_
#include "nasal_traits.hxx"
#include <simgear/nasal/nasal.h>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>
#include <string>
#include <vector>
#include <simgear/nasal/cppbind/detail/to_nasal_helper.hxx>
namespace nasal
{
class Hash;
/**
* Convert std::string to Nasal string
*/
naRef to_nasal(naContext c, const std::string& str);
/**
* Convert C-string to Nasal string
*/
// We need this to prevent the array overload of to_nasal being called
naRef to_nasal(naContext c, const char* str);
/**
* Convert function pointer to Nasal function
*/
naRef to_nasal(naContext c, naCFunction func);
/**
* Convert a nasal::Hash to a Nasal hash
*/
naRef to_nasal(naContext c, const Hash& hash);
/**
* Simple pass-through of naRef types to allow generic usage of to_nasal
*/
naRef to_nasal(naContext c, naRef ref);
/**
* Convert a numeric type to Nasal number
* Convert any supported C++ type to Nasal.
*
* @param c Active Nasal context
* @param arg C++ Object to be converted
* @tparam T Type of converted object
*
* @throws bad_nasal_cast if conversion is not possible
*
* @note Every type which should be supported needs a function with the
* following signature declared (Type needs to be a const reference
* for non-integral types):
*
* naRef to_nasal_helper(naContext, Type)
*/
template<class T>
typename boost::enable_if< boost::is_arithmetic<T>, naRef >::type
to_nasal(naContext c, T num)
naRef to_nasal(naContext c, T arg)
{
return naNum(num);
return to_nasal_helper(c, arg);
}
/**
* Convert a fixed size array to a Nasal vector
*/
template<class T, size_t N>
naRef to_nasal(naContext c, const T(&array)[N])
{
naRef ret = naNewVector(c);
naVec_setsize(c, ret, N);
for(size_t i = 0; i < N; ++i)
naVec_set(ret, i, to_nasal(c, array[i]));
return ret;
return to_nasal_helper(c, array);
}
/**
* Convert std::vector to Nasal vector
* Wrapper to get pointer to specific version of to_nasal applicable to given
* type.
*/
template< template<class T, class Alloc> class Vector,
class T,
class Alloc
>
typename boost::enable_if< boost::is_same< Vector<T,Alloc>,
std::vector<T,Alloc>
>,
naRef
>::type
to_nasal(naContext c, const Vector<T, Alloc>& vec)
template<class Var>
struct to_nasal_ptr
{
naRef ret = naNewVector(c);
naVec_setsize(c, ret, vec.size());
for(size_t i = 0; i < vec.size(); ++i)
naVec_set(ret, i, to_nasal(c, vec[i]));
return ret;
}
/**
* Convert a 2d vector to Nasal vector with 2 elements
*/
template<class Vec2>
typename boost::enable_if<is_vec2<Vec2>, naRef>::type
to_nasal(naContext c, const Vec2& vec)
{
// We take just double because in Nasal every number is represented as
// double
double nasal_vec[2] = {vec[0], vec[1]};
return to_nasal(c, nasal_vec);
}
typedef typename boost::call_traits<Var>::param_type param_type;
typedef naRef(*type)(naContext, param_type);
static type get()
{
return &to_nasal<param_type>;
}
};
} // namespace nasal
#endif /* SG_TO_NASAL_HXX_ */

View File

@@ -160,7 +160,15 @@ struct naFunc {
struct naCCode {
GC_HEADER;
naCFunction fptr;
union {
naCFunction fptr; //<! pointer to simple callback function. Invalid if
// fptru is not NULL.
struct {
void* user_data;
void(*destroy)(void*);
naCFunctionU fptru;
};
};
};
struct naGhost {

View File

@@ -128,6 +128,12 @@ static void naCode_gcclean(struct naCode* o)
naFree(o->constants); o->constants = 0;
}
static void naCCode_gcclean(struct naCCode* c)
{
if(c->fptru && c->user_data && c->destroy) c->destroy(c->user_data);
c->user_data = 0;
}
static void naGhost_gcclean(struct naGhost* g)
{
if(g->ptr && g->gtype->destroy) g->gtype->destroy(g->ptr);
@@ -142,6 +148,7 @@ static void freeelem(struct naPool* p, struct naObj* o)
case T_VEC: naVec_gcclean ((struct naVec*) o); break;
case T_HASH: naiGCHashClean ((struct naHash*) o); break;
case T_CODE: naCode_gcclean ((struct naCode*) o); break;
case T_CCODE: naCCode_gcclean((struct naCCode*)o); break;
case T_GHOST: naGhost_gcclean((struct naGhost*)o); break;
}
p->free[p->nfree++] = o; // ...and add it to the free list

View File

@@ -146,6 +146,32 @@ static naRef f_substr(naContext c, naRef me, int argc, naRef* args)
return naStr_substr(naNewString(c), src, start, len);
}
static naRef f_left(naContext c, naRef me, int argc, naRef* args)
{
int len;
naRef src = argc > 0 ? args[0] : naNil();
naRef lenr = argc > 1 ? naNumValue(args[1]) : naNil();
if(!naIsString(src)) ARGERR();
if(!naIsNum(lenr)) ARGERR();
len = (int)lenr.num;
if(len < 0) len = 0;
return naStr_substr(naNewString(c), src, 0, len);
}
static naRef f_right(naContext c, naRef me, int argc, naRef* args)
{
int len, srclen;
naRef src = argc > 0 ? args[0] : naNil();
naRef lenr = argc > 1 ? naNumValue(args[1]) : naNil();
if(!naIsString(src)) ARGERR();
if(!naIsNum(lenr)) ARGERR();
srclen = naStr_len(src);
len = (int)lenr.num;
if (len > srclen) len = srclen;
if(len < 0) len = 0;
return naStr_substr(naNewString(c), src, srclen - len, len);
}
static naRef f_chr(naContext c, naRef me, int argc, naRef* args)
{
char chr[1];
@@ -573,6 +599,8 @@ static naCFuncItem funcs[] = {
{ "streq", f_streq },
{ "cmp", f_cmp },
{ "substr", f_substr },
{ "left", f_left },
{ "right", f_right },
{ "chr", f_chr },
{ "contains", f_contains },
{ "typeof", f_typeof },

View File

@@ -75,6 +75,72 @@ static naRef f_atan2(naContext c, naRef me, int argc, naRef* args)
return VALIDATE(a);
}
static naRef f_floor(naContext c, naRef me, int argc, naRef* args)
{
naRef a = naNumValue(argc > 0 ? args[0] : naNil());
if(naIsNil(a))
naRuntimeError(c, "non numeric argument to floor()");
a.num = floor(a.num);
return VALIDATE(a);
}
static naRef f_ceil(naContext c, naRef me, int argc, naRef* args)
{
naRef a = naNumValue(argc > 0 ? args[0] : naNil());
if(naIsNil(a))
naRuntimeError(c, "non numeric argument to ceil()");
a.num = ceil(a.num);
return VALIDATE(a);
}
static naRef f_mod(naContext c, naRef me, int argc, naRef* args)
{
naRef a = naNumValue(argc > 0 ? args[0] : naNil());
naRef b = naNumValue(argc > 1 ? args[1] : naNil());
if(naIsNil(a) || naIsNil(b))
naRuntimeError(c, "non numeric arguments to mod()");
a.num = fmod(a.num, b.num);
return VALIDATE(a);
}
static naRef f_clamp(naContext c, naRef me, int argc, naRef* args)
{
naRef a = naNumValue(argc > 0 ? args[0] : naNil());
naRef b = naNumValue(argc > 1 ? args[1] : naNil());
naRef x = naNumValue(argc > 2 ? args[2] : naNil());
if(naIsNil(a) || naIsNil(b) || naIsNil(x))
naRuntimeError(c, "non numeric arguments to clamp()");
if (a.num < b.num) b.num = a.num;
if (b.num > x.num) b.num = x.num;
return VALIDATE(b);
}
static naRef f_periodic(naContext c, naRef me, int argc, naRef* args)
{
double range;
naRef a = naNumValue(argc > 0 ? args[0] : naNil());
naRef b = naNumValue(argc > 1 ? args[1] : naNil());
naRef x = naNumValue(argc > 2 ? args[2] : naNil());
if(naIsNil(a) || naIsNil(b) || naIsNil(x))
naRuntimeError(c, "non numeric arguments to periodic()");
range = b.num - a.num;
x.num = x.num - range*floor((x.num - a.num)/range);
// two security checks that can only happen due to roundoff
if (x.num <= a.num)
x.num = a.num;
if (b.num <= x.num)
x.num = b.num;
return VALIDATE(x);
// x.num = SGMiscd::normalizePeriodic(a, b, x);
return VALIDATE(x);
}
static naCFuncItem funcs[] = {
{ "sin", f_sin },
{ "cos", f_cos },
@@ -82,9 +148,16 @@ static naCFuncItem funcs[] = {
{ "ln", f_ln },
{ "sqrt", f_sqrt },
{ "atan2", f_atan2 },
{ "floor", f_floor },
{ "ceil", f_ceil },
{ "mod", f_mod },
{ "clamp", f_clamp },
{ "periodic", f_periodic },
{ 0 }
};
naRef naInit_math(naContext c)
{
naRef ns = naGenLib(c, funcs);

Some files were not shown because too many files have changed in this diff Show More