Compare commits
217 Commits
version/2.
...
version/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5818366d92 | ||
|
|
e5bca74b9d | ||
|
|
82dd1e4be9 | ||
|
|
d0b77639c2 | ||
|
|
6edb0f66d5 | ||
|
|
c458c13af7 | ||
|
|
bb7875edd4 | ||
|
|
6f54fb90eb | ||
|
|
4d1e7c536f | ||
|
|
59cc200f92 | ||
|
|
c6093430ae | ||
|
|
23172bcdd0 | ||
|
|
aea2dab0d7 | ||
|
|
4d26e144ab | ||
|
|
c391fcd345 | ||
|
|
ece743342a | ||
|
|
02babbe340 | ||
|
|
e27b6d050c | ||
|
|
86e2de8d10 | ||
|
|
b4526f926d | ||
|
|
14f04878d1 | ||
|
|
830fb3b752 | ||
|
|
9a336da359 | ||
|
|
6cec49ce7e | ||
|
|
6c28edea55 | ||
|
|
d7870d672e | ||
|
|
5bd9228b4a | ||
|
|
6392cf5a2f | ||
|
|
6a2e2b704e | ||
|
|
ecec803388 | ||
|
|
848965e7f0 | ||
|
|
478af5f01e | ||
|
|
14decb7387 | ||
|
|
323d0d5ba0 | ||
|
|
9bfa6ac1a1 | ||
|
|
c8af817eeb | ||
|
|
d24d3ce487 | ||
|
|
60a81dfbd8 | ||
|
|
8896a59dff | ||
|
|
7fe16d99be | ||
|
|
c3af88dfc1 | ||
|
|
5c8f0cc966 | ||
|
|
2a6c50c893 | ||
|
|
7a8d796ac1 | ||
|
|
8b3b71bce3 | ||
|
|
83da4e9248 | ||
|
|
2c879095d9 | ||
|
|
13344fbb62 | ||
|
|
2fe9ad595f | ||
|
|
0f798289f0 | ||
|
|
01104cc1d3 | ||
|
|
d61b5827fd | ||
|
|
d18cc81531 | ||
|
|
c96d7ae226 | ||
|
|
9535aef59b | ||
|
|
82f6fca06f | ||
|
|
627b36a53b | ||
|
|
2af78a3874 | ||
|
|
7c7109edf4 | ||
|
|
0fa23b83e6 | ||
|
|
e53c71543a | ||
|
|
fa64f7b6aa | ||
|
|
eaac3a64f8 | ||
|
|
4eabc45dfc | ||
|
|
5c5e2a0f2f | ||
|
|
559a5d146a | ||
|
|
df46c58cb8 | ||
|
|
1f43132363 | ||
|
|
17369a20de | ||
|
|
c83dae4c70 | ||
|
|
c2f44a51c4 | ||
|
|
3da44d1215 | ||
|
|
f6709e357f | ||
|
|
45d5511fd7 | ||
|
|
198c5d23fd | ||
|
|
c41caeaf64 | ||
|
|
b6c542b0e7 | ||
|
|
2d62275a08 | ||
|
|
971ea81861 | ||
|
|
9e9cc7859c | ||
|
|
8898f5fe52 | ||
|
|
40be69ae8e | ||
|
|
17eec81071 | ||
|
|
c9bbbd18ec | ||
|
|
e08eda18d5 | ||
|
|
d2c03a6651 | ||
|
|
0a1e920659 | ||
|
|
cd58df820e | ||
|
|
1a5467aec8 | ||
|
|
0dcb64dca3 | ||
|
|
b703102d9b | ||
|
|
3c2ef75b50 | ||
|
|
4fa530354d | ||
|
|
eeeb30dc3f | ||
|
|
870fc2e53e | ||
|
|
79ae0da927 | ||
|
|
e179bda57a | ||
|
|
8e0c15e32e | ||
|
|
ff844f6760 | ||
|
|
bc96ac85f4 | ||
|
|
33b328e3d5 | ||
|
|
dcda8d1c7a | ||
|
|
5fd2bd225f | ||
|
|
2db8859076 | ||
|
|
6eff167a28 | ||
|
|
9fecb69b84 | ||
|
|
0539aa38e5 | ||
|
|
fe9caad391 | ||
|
|
ceae2928aa | ||
|
|
0e6934abe5 | ||
|
|
a2e7c92f99 | ||
|
|
643a8717b4 | ||
|
|
f21127fd4a | ||
|
|
0bc8005100 | ||
|
|
67efc1f68f | ||
|
|
dfebf60e68 | ||
|
|
26e7d134ce | ||
|
|
c414242f13 | ||
|
|
33e60725b1 | ||
|
|
f21b0985cc | ||
|
|
71e9548c20 | ||
|
|
fc64abea5c | ||
|
|
413e89c955 | ||
|
|
439041c2f4 | ||
|
|
530de4d809 | ||
|
|
5e45bdeeda | ||
|
|
081eba903f | ||
|
|
0def045611 | ||
|
|
e20cc6a90f | ||
|
|
7dd83dd2d1 | ||
|
|
1240807b9f | ||
|
|
ad7d6624de | ||
|
|
06b089ba13 | ||
|
|
e1302bcf17 | ||
|
|
cfdc7db79a | ||
|
|
50de873453 | ||
|
|
a4ae7b2059 | ||
|
|
d90abdcb44 | ||
|
|
4a31045fd9 | ||
|
|
249155987d | ||
|
|
2ae7fc244b | ||
|
|
fafa70c6bc | ||
|
|
49162a7a64 | ||
|
|
328e6b3e28 | ||
|
|
5441db9b18 | ||
|
|
06bfa4f1a1 | ||
|
|
a530712491 | ||
|
|
3c8cfe9b76 | ||
|
|
57b4ce96e4 | ||
|
|
63c7d64143 | ||
|
|
1784327c47 | ||
|
|
a63ec83d5f | ||
|
|
d661516b02 | ||
|
|
ba020245f9 | ||
|
|
79fefc1ff9 | ||
|
|
eac169b6da | ||
|
|
a60d293759 | ||
|
|
b0063f8db6 | ||
|
|
632abdd87b | ||
|
|
d9b01ca5de | ||
|
|
adb7db9229 | ||
|
|
e4f7aec965 | ||
|
|
768a8c1062 | ||
|
|
318c5000ce | ||
|
|
70f334e1a3 | ||
|
|
279b53a705 | ||
|
|
a72a3ce5f3 | ||
|
|
979aea5212 | ||
|
|
34d3c63384 | ||
|
|
cf4af2692b | ||
|
|
d335ca5a8d | ||
|
|
9cac2c1457 | ||
|
|
523b992b4c | ||
|
|
02be490466 | ||
|
|
45ae3978f6 | ||
|
|
f983194d7e | ||
|
|
ab8373b989 | ||
|
|
4bef2c48eb | ||
|
|
21e2a769eb | ||
|
|
fd8369142a | ||
|
|
5dea221ad5 | ||
|
|
235c29913a | ||
|
|
0870407f65 | ||
|
|
e8156c6bd9 | ||
|
|
e7f9486aa1 | ||
|
|
53b9fd2110 | ||
|
|
4d4e474464 | ||
|
|
a58b41e7d2 | ||
|
|
4db05e97c5 | ||
|
|
3a668232c8 | ||
|
|
d2622a5d86 | ||
|
|
d11db8a67a | ||
|
|
98b6102e56 | ||
|
|
0db8613a21 | ||
|
|
94a6cb2cff | ||
|
|
5b92575ed3 | ||
|
|
47b02c0480 | ||
|
|
691be54ca2 | ||
|
|
22ea8ebe25 | ||
|
|
bd71635c49 | ||
|
|
830bc3eac3 | ||
|
|
2d72bf4308 | ||
|
|
f4e694afa7 | ||
|
|
55610bae70 | ||
|
|
08351e0df2 | ||
|
|
7489771ad2 | ||
|
|
ea8023e51f | ||
|
|
8a9693a28e | ||
|
|
724fba4af9 | ||
|
|
beca1cbf96 | ||
|
|
b74d2ae9fa | ||
|
|
ec82a0154b | ||
|
|
0ea8dbea10 | ||
|
|
a131f44247 | ||
|
|
0423aedffc | ||
|
|
afc89cdd95 | ||
|
|
541606e00d |
@@ -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()
|
||||
|
||||
|
||||
4
Doxyfile
4
Doxyfile
@@ -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.
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
244
simgear/canvas/CanvasObjectPlacement.cxx
Normal file
244
simgear/canvas/CanvasObjectPlacement.cxx
Normal 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
|
||||
75
simgear/canvas/CanvasObjectPlacement.hxx
Normal file
75
simgear/canvas/CanvasObjectPlacement.hxx
Normal 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_ */
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}")
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
21
simgear/canvas/elements/detail/add_segment_variadic.hxx
Normal file
21
simgear/canvas/elements/detail/add_segment_variadic.hxx
Normal 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
|
||||
@@ -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
|
||||
|
||||
101
simgear/debug/BufferedLogCallback.cxx
Normal file
101
simgear/debug/BufferedLogCallback.cxx
Normal 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
|
||||
79
simgear/debug/BufferedLogCallback.hxx
Normal file
79
simgear/debug/BufferedLogCallback.hxx
Normal 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
|
||||
@@ -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}")
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
375
simgear/io/DAVMultiStatus.cxx
Normal file
375
simgear/io/DAVMultiStatus.cxx
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
141
simgear/io/DAVMultiStatus.hxx
Normal file
141
simgear/io/DAVMultiStatus.hxx
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
368
simgear/io/SVNDirectory.cxx
Normal 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
112
simgear/io/SVNDirectory.hxx
Normal 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
|
||||
582
simgear/io/SVNReportParser.cxx
Normal file
582
simgear/io/SVNReportParser.cxx
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
57
simgear/io/SVNReportParser.hxx
Normal file
57
simgear/io/SVNReportParser.hxx
Normal 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
|
||||
414
simgear/io/SVNRepository.cxx
Normal file
414
simgear/io/SVNRepository.cxx
Normal 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
|
||||
76
simgear/io/SVNRepository.hxx
Normal file
76
simgear/io/SVNRepository.hxx
Normal 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
49
simgear/io/http_svn.cxx
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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>&
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
#ifndef SGVec2_H
|
||||
#define SGVec2_H
|
||||
|
||||
#include "SGLimits.hxx"
|
||||
#include "SGMathFwd.hxx"
|
||||
#include "SGMisc.hxx"
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
/// 2D Vector Class
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
166
simgear/misc/CSSBorder.cxx
Normal 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
|
||||
71
simgear/misc/CSSBorder.hxx
Normal file
71
simgear/misc/CSSBorder.hxx
Normal 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_ */
|
||||
104
simgear/misc/CSSBorder_test.cxx
Normal file
104
simgear/misc/CSSBorder_test.cxx
Normal 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
52
simgear/misc/make_new.hxx
Normal 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_ */
|
||||
@@ -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
|
||||
|
||||
@@ -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!)
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
51
simgear/misc/sgstream_test.cxx
Normal file
51
simgear/misc/sgstream_test.cxx
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
19
simgear/misc/test_macros.hxx
Normal file
19
simgear/misc/test_macros.hxx
Normal 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
|
||||
@@ -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";
|
||||
|
||||
@@ -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)
|
||||
@@ -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_ */
|
||||
|
||||
136
simgear/nasal/cppbind/NasalCallContext.hxx
Normal file
136
simgear/nasal/cppbind/NasalCallContext.hxx
Normal 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_ */
|
||||
180
simgear/nasal/cppbind/NasalString.cxx
Normal file
180
simgear/nasal/cppbind/NasalString.cxx
Normal 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
|
||||
80
simgear/nasal/cppbind/NasalString.hxx
Normal file
80
simgear/nasal/cppbind/NasalString.hxx
Normal 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_ */
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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_ */
|
||||
104
simgear/nasal/cppbind/detail/functor_templates.hxx
Normal file
104
simgear/nasal/cppbind/detail/functor_templates.hxx
Normal 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
|
||||
112
simgear/nasal/cppbind/detail/to_nasal_helper.cxx
Normal file
112
simgear/nasal/cppbind/detail/to_nasal_helper.cxx
Normal 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
|
||||
172
simgear/nasal/cppbind/detail/to_nasal_helper.hxx
Normal file
172
simgear/nasal/cppbind/detail/to_nasal_helper.hxx
Normal 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_ */
|
||||
@@ -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_ */
|
||||
|
||||
@@ -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
|
||||
@@ -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_ */
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user