Compare commits
396 Commits
version/2.
...
version/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1061580118 | ||
|
|
7184828a58 | ||
|
|
0428adff03 | ||
|
|
04246f0a63 | ||
|
|
61df58d651 | ||
|
|
1bd9a440e8 | ||
|
|
d82c8eb945 | ||
|
|
becbad7ea4 | ||
|
|
2335796a28 | ||
|
|
c6330b64f6 | ||
|
|
df57a23512 | ||
|
|
aea71cf0af | ||
|
|
fb54386f8d | ||
|
|
4d931b6109 | ||
|
|
407c7a10f5 | ||
|
|
16fa4d1d17 | ||
|
|
1ecccd43d4 | ||
|
|
2edbb1cf8b | ||
|
|
8dccbe8029 | ||
|
|
031c833e4d | ||
|
|
890cc46825 | ||
|
|
6b0f7e8512 | ||
|
|
7a220921f0 | ||
|
|
7ef9acda5d | ||
|
|
2a6d62e6f7 | ||
|
|
42b50bd7f7 | ||
|
|
2c3f44ae11 | ||
|
|
b7662f16e5 | ||
|
|
5bc535a8d2 | ||
|
|
3ec9c7ae6e | ||
|
|
e326d0c756 | ||
|
|
7c5434a99f | ||
|
|
26d9256ee9 | ||
|
|
053e761d8f | ||
|
|
5861b52cff | ||
|
|
66193d7283 | ||
|
|
dfb07f359e | ||
|
|
6fe1433497 | ||
|
|
01ace109ba | ||
|
|
8e01acaa12 | ||
|
|
7bbec8dbcb | ||
|
|
10375086ed | ||
|
|
dd367daac9 | ||
|
|
27063d0c32 | ||
|
|
80ff0282c3 | ||
|
|
e973e406a3 | ||
|
|
c64776029e | ||
|
|
5d7d0a922d | ||
|
|
c2c96c4219 | ||
|
|
3e9ed08c53 | ||
|
|
b7df9026c0 | ||
|
|
6527c6357b | ||
|
|
fad333256c | ||
|
|
8753811fe1 | ||
|
|
237752ff62 | ||
|
|
743244a69c | ||
|
|
9f89737986 | ||
|
|
508a5c5f66 | ||
|
|
d84394efa0 | ||
|
|
dd613d48bc | ||
|
|
2e8e500d4f | ||
|
|
48f866dcc6 | ||
|
|
ec4d3e4f9b | ||
|
|
38ddfab1e0 | ||
|
|
353b7e4438 | ||
|
|
7076c9a0ff | ||
|
|
2f42a8714c | ||
|
|
fdf6fc32ff | ||
|
|
f2188b33c6 | ||
|
|
f93fead8f2 | ||
|
|
050f3791cc | ||
|
|
94326fd964 | ||
|
|
f26f3b606d | ||
|
|
7cbfa76be4 | ||
|
|
d896e71ae9 | ||
|
|
ca79d99ec4 | ||
|
|
95f77ef81d | ||
|
|
c4e7d26b70 | ||
|
|
ea49a1a07b | ||
|
|
784223c67f | ||
|
|
77aa2c9a9d | ||
|
|
48145fb234 | ||
|
|
49730cadee | ||
|
|
0b197501e1 | ||
|
|
8b0c246a7b | ||
|
|
9c4face1ae | ||
|
|
952e2e6631 | ||
|
|
4766910413 | ||
|
|
3c2f97a8d2 | ||
|
|
f75730f165 | ||
|
|
b596e62a61 | ||
|
|
1349d48339 | ||
|
|
d69ea5fda6 | ||
|
|
9b68062ba7 | ||
|
|
b217019723 | ||
|
|
a37a254a68 | ||
|
|
5258699f20 | ||
|
|
483bebb003 | ||
|
|
8ca8052a8d | ||
|
|
d68b1144b8 | ||
|
|
42c39b6be3 | ||
|
|
bb82b9d168 | ||
|
|
b1f865d461 | ||
|
|
8e75c6be50 | ||
|
|
06a5f9188d | ||
|
|
370a991208 | ||
|
|
6deb77dd4d | ||
|
|
9e3172cb04 | ||
|
|
426c6b9a72 | ||
|
|
d148bdedcd | ||
|
|
23140e3bf7 | ||
|
|
2f023803e7 | ||
|
|
d658b5fc38 | ||
|
|
68cd84c330 | ||
|
|
0bd82a43d3 | ||
|
|
7fdf42b699 | ||
|
|
aeb0e9aac3 | ||
|
|
1099a3bdf0 | ||
|
|
aa3458f69c | ||
|
|
0186cbb7b7 | ||
|
|
fa36e94c4b | ||
|
|
ad83e70cf5 | ||
|
|
4a0377c0a1 | ||
|
|
83a3241830 | ||
|
|
f299b351e3 | ||
|
|
3880e4ef47 | ||
|
|
f205e918d9 | ||
|
|
42b0b9306b | ||
|
|
483659c319 | ||
|
|
add14dd27c | ||
|
|
61516a5e97 | ||
|
|
17418039e1 | ||
|
|
25cae61211 | ||
|
|
db98c7440e | ||
|
|
3783ae2234 | ||
|
|
3e8732b230 | ||
|
|
b39bca9458 | ||
|
|
d263334030 | ||
|
|
b2cea62189 | ||
|
|
d6b886c69b | ||
|
|
38fb9ea41e | ||
|
|
a922aaa68e | ||
|
|
4f2e36ca46 | ||
|
|
f367627cac | ||
|
|
7a7fcf10ad | ||
|
|
b9bd2734eb | ||
|
|
9c7bd4f5d5 | ||
|
|
362d47f91f | ||
|
|
a18792c397 | ||
|
|
0e2ddb2f16 | ||
|
|
bcfadd6657 | ||
|
|
021cf1a2b4 | ||
|
|
acf86be516 | ||
|
|
dd93eb88f2 | ||
|
|
e06f9e35b3 | ||
|
|
c0424aba7e | ||
|
|
b468254d73 | ||
|
|
15e3e92ec2 | ||
|
|
ee403fd83a | ||
|
|
05f266ac10 | ||
|
|
f52c0026dd | ||
|
|
a284107249 | ||
|
|
a2e25e7940 | ||
|
|
d45a0161cf | ||
|
|
c7a5d15e7a | ||
|
|
3cb2241b1d | ||
|
|
b236821661 | ||
|
|
fc75b0bd21 | ||
|
|
a9fc84d568 | ||
|
|
b15c6d887d | ||
|
|
c2f89978ff | ||
|
|
a004153223 | ||
|
|
dde2bbcbe7 | ||
|
|
7bd572f3ec | ||
|
|
486011dceb | ||
|
|
01c45633b7 | ||
|
|
5320d0ecaa | ||
|
|
757ba03918 | ||
|
|
948db69cc9 | ||
|
|
92f363926e | ||
|
|
d0ff144753 | ||
|
|
943efbb3ed | ||
|
|
6962de4b1f | ||
|
|
6f7c0c23d1 | ||
|
|
14eccc70da | ||
|
|
d4b48cec5d | ||
|
|
2d23c5351f | ||
|
|
b5dfaf170a | ||
|
|
c372591f36 | ||
|
|
36d1308aa6 | ||
|
|
b0d6ed3844 | ||
|
|
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 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.*
|
||||
*~
|
||||
Makefile
|
||||
*.o
|
||||
lib*.a
|
||||
|
||||
4
3rdparty/CMakeLists.txt
vendored
Normal file
4
3rdparty/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
if (NOT SYSTEM_EXPAT)
|
||||
add_subdirectory(expat)
|
||||
endif()
|
||||
|
||||
33
3rdparty/expat/CMakeLists.txt
vendored
Normal file
33
3rdparty/expat/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
configure_file (
|
||||
"${PROJECT_SOURCE_DIR}/3rdparty/expat/expat_config_cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/3rdparty/expat/expat_config.h"
|
||||
)
|
||||
|
||||
set(expat_sources
|
||||
asciitab.h
|
||||
hashtable.h
|
||||
iasciitab.h
|
||||
latin1tab.h
|
||||
nametab.h
|
||||
utf8tab.h
|
||||
xmldef.h
|
||||
xmlparse.h
|
||||
xmlrole.h
|
||||
xmltok.h
|
||||
xmltok_impl.h
|
||||
hashtable.c
|
||||
xmlparse.c
|
||||
xmlrole.c
|
||||
xmltok.c
|
||||
internal.h
|
||||
ascii.h
|
||||
sg_expat.h
|
||||
sg_expat_external.h
|
||||
)
|
||||
|
||||
foreach(s ${expat_sources})
|
||||
set_property(GLOBAL
|
||||
APPEND PROPERTY LOCAL_EXPAT_SOURCES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${s}")
|
||||
endforeach()
|
||||
@@ -15,7 +15,7 @@
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "expat_external.h"
|
||||
#include "sg_expat_external.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -23,7 +23,7 @@
|
||||
#endif /* ndef COMPILED_FROM_DSP */
|
||||
|
||||
#include "ascii.h"
|
||||
#include "expat.h"
|
||||
#include "sg_expat.h"
|
||||
|
||||
#ifdef XML_UNICODE
|
||||
#define XML_ENCODE_MAX XML_UTF16_ENCODE_MAX
|
||||
@@ -18,7 +18,7 @@
|
||||
#endif
|
||||
#endif /* ndef COMPILED_FROM_DSP */
|
||||
|
||||
#include "expat_external.h"
|
||||
#include "sg_expat_external.h"
|
||||
#include "internal.h"
|
||||
#include "xmlrole.h"
|
||||
#include "ascii.h"
|
||||
@@ -18,7 +18,7 @@
|
||||
#endif
|
||||
#endif /* ndef COMPILED_FROM_DSP */
|
||||
|
||||
#include "expat_external.h"
|
||||
#include "sg_expat_external.h"
|
||||
#include "internal.h"
|
||||
#include "xmltok.h"
|
||||
#include "nametab.h"
|
||||
107
CMakeLists.txt
107
CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 2.6.4)
|
||||
include (CheckFunctionExists)
|
||||
include (CheckIncludeFile)
|
||||
include (CheckCXXSourceCompiles)
|
||||
|
||||
include (CheckCXXCompilerFlag)
|
||||
|
||||
project(SimGear)
|
||||
|
||||
@@ -10,6 +10,8 @@ project(SimGear)
|
||||
file(READ version versionFile)
|
||||
string(STRIP ${versionFile} SIMGEAR_VERSION)
|
||||
|
||||
set(FIND_LIBRARY_USE_LIB64_PATHS ON)
|
||||
|
||||
# use simgear version also as the SO version (if building SOs)
|
||||
SET(SIMGEAR_SOVERSION ${SIMGEAR_VERSION})
|
||||
|
||||
@@ -85,13 +87,6 @@ if(NOT "${CMAKE_LIBRARY_ARCHITECTURE}" STREQUAL "")
|
||||
message(STATUS "additional library directories: ${ADDITIONAL_LIBRARY_PATHS}")
|
||||
endif()
|
||||
|
||||
if(NOT MSVC)
|
||||
# TBD: are these really necessary? Aren't they considered by cmake automatically?
|
||||
list(APPEND ADDITIONAL_LIBRARY_PATHS
|
||||
/opt/local
|
||||
/usr/local
|
||||
/usr)
|
||||
endif()
|
||||
#####################################################################################
|
||||
|
||||
if (NOT MSVC)
|
||||
@@ -106,13 +101,13 @@ endif()
|
||||
|
||||
option(SIMGEAR_HEADLESS "Set to ON to build SimGear without GUI/graphics support" OFF)
|
||||
option(JPEG_FACTORY "Enable JPEG-factory 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 +160,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,23 +190,6 @@ else()
|
||||
message(STATUS "JPEG-factory: DISABLED")
|
||||
endif(JPEG_FACTORY)
|
||||
|
||||
if(ENABLE_LIBSVN)
|
||||
find_package(SvnClient)
|
||||
|
||||
if(LIBSVN_FOUND)
|
||||
message(STATUS "Subversion client support: ENABLED")
|
||||
set(HAVE_SVN_CLIENT_H 1)
|
||||
set(HAVE_LIBSVN_CLIENT_1 1)
|
||||
else()
|
||||
# Oops. ENABLE_LIBSVN is ON, but svn is still missing.
|
||||
# Provide clearly visible warning/hint, so builders know what else they should install (or disable).
|
||||
message(WARNING "Failed to enable subversion client support. Unable to find required subversion client library. Some features may not be available (scenery download).")
|
||||
message(WARNING "Install 'libsvn' library/DLL (libsvn-devel/libsvnclient/...). Otherwise disable subversion support (set 'ENABLE_LIBSVN' to 'OFF').")
|
||||
endif(LIBSVN_FOUND)
|
||||
else()
|
||||
message(STATUS "Subversion client support: DISABLED")
|
||||
endif(ENABLE_LIBSVN)
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
@@ -215,12 +197,19 @@ if (SYSTEM_EXPAT)
|
||||
message(STATUS "Requested to use system Expat library, forcing SIMGEAR_SHARED to true")
|
||||
set(SIMGEAR_SHARED ON)
|
||||
find_package(EXPAT REQUIRED)
|
||||
include_directories(${EXPAT_INCLUDE_DIRS})
|
||||
|
||||
else()
|
||||
message(STATUS "Using built-in expat code")
|
||||
add_definitions(-DHAVE_EXPAT_CONFIG_H)
|
||||
# XML_STATIC is important to avoid sg_expat_external.h
|
||||
# declaring symbols as declspec(import)
|
||||
add_definitions(-DHAVE_EXPAT_CONFIG_H -DXML_STATIC)
|
||||
set(EXPAT_INCLUDE_DIRS
|
||||
${PROJECT_SOURCE_DIR}/3rdparty/expat
|
||||
${PROJECT_BINARY_DIR}/3rdparty/expat)
|
||||
endif(SYSTEM_EXPAT)
|
||||
|
||||
include_directories(${EXPAT_INCLUDE_DIRS})
|
||||
|
||||
check_include_file(inttypes.h HAVE_INTTYPES_H)
|
||||
check_include_file(sys/time.h HAVE_SYS_TIME_H)
|
||||
check_include_file(sys/timeb.h HAVE_SYS_TIMEB_H)
|
||||
@@ -269,6 +258,20 @@ if(HAVE_CLOCK_GETTIME)
|
||||
endif(HAVE_RT)
|
||||
endif(HAVE_CLOCK_GETTIME)
|
||||
|
||||
set(DL_LIBRARY "")
|
||||
check_cxx_source_compiles(
|
||||
"#include <dlfcn.h>
|
||||
int main(void) {
|
||||
return 0;
|
||||
}
|
||||
"
|
||||
HAVE_DLFCN_H)
|
||||
|
||||
if(HAVE_DLFCN_H)
|
||||
check_library_exists(dl dlerror "" HAVE_DL)
|
||||
set(DL_LIBRARY "dl")
|
||||
endif()
|
||||
|
||||
SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "add a postfix, usually 'd' on windows")
|
||||
SET(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
|
||||
SET(CMAKE_RELWITHDEBINFO_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
|
||||
@@ -302,6 +305,14 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(WARNING_FLAGS_C "-Wall")
|
||||
endif()
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
# boost goes haywire wrt static asserts
|
||||
check_cxx_compiler_flag(-Wno-unused-local-typedefs HAS_NOWARN_UNUSED_TYPEDEFS)
|
||||
if(HAS_NOWARN_UNUSED_TYPEDEFS)
|
||||
set(WARNING_FLAGS_CXX " ${WARNING_FLAGS_CXX} -Wno-unused-local-typedefs")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
|
||||
if(MINGW)
|
||||
@@ -314,7 +325,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,15 +340,17 @@ 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)
|
||||
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}
|
||||
)
|
||||
|
||||
add_definitions(-DHAVE_CONFIG_H)
|
||||
|
||||
@@ -345,22 +361,33 @@ configure_file (
|
||||
"${PROJECT_BINARY_DIR}/simgear/simgear_config.h"
|
||||
)
|
||||
|
||||
configure_file (
|
||||
"${PROJECT_SOURCE_DIR}/simgear/xml/expat_config_cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/simgear/xml/expat_config.h"
|
||||
)
|
||||
|
||||
if(ENABLE_TESTS)
|
||||
# enable CTest / make test target
|
||||
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
|
||||
set(TEST_LIBS_INTERNAL_CORE
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${ZLIB_LIBRARY}
|
||||
${WINSOCK_LIBRARY}
|
||||
${RT_LIBRARY}
|
||||
${DL_LIBRARY}
|
||||
${CORE_SERVICES_LIBRARY})
|
||||
set(TEST_LIBS SimGearCore ${TEST_LIBS_INTERNAL_CORE})
|
||||
|
||||
if(NOT SIMGEAR_HEADLESS)
|
||||
set(TEST_LIBS SimGearScene ${OPENGL_LIBRARIES} ${TEST_LIBS})
|
||||
endif()
|
||||
|
||||
install (FILES ${PROJECT_BINARY_DIR}/simgear/simgear_config.h DESTINATION include/simgear/)
|
||||
|
||||
add_subdirectory(3rdparty)
|
||||
add_subdirectory(simgear)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
@@ -372,3 +399,5 @@ CONFIGURE_FILE(
|
||||
IMMEDIATE @ONLY)
|
||||
ADD_CUSTOM_TARGET(uninstall
|
||||
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
|
||||
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
# Find Subversion client libraries, and dependencies
|
||||
# including APR (Apache Portable Runtime)
|
||||
|
||||
include (CheckFunctionExists)
|
||||
include (CheckIncludeFile)
|
||||
include (CheckLibraryExists)
|
||||
|
||||
macro(find_static_component comp libs)
|
||||
# account for alternative Windows svn distribution naming
|
||||
if(MSVC)
|
||||
set(compLib "lib${comp}")
|
||||
else(MSVC)
|
||||
set(compLib "${comp}")
|
||||
endif(MSVC)
|
||||
|
||||
string(TOUPPER "${comp}" compLibBase)
|
||||
set( compLibName ${compLibBase}_LIBRARY )
|
||||
|
||||
# NO_DEFAULT_PATH is important on Mac - we need to ensure subversion
|
||||
# libraires in dist/ or Macports are picked over the Apple version
|
||||
# in /usr, since that's what we will ship.
|
||||
# On other platforms we do need default paths though, i.e. since Linux
|
||||
# distros may use architecture-specific directories (like
|
||||
# /usr/lib/x86_64-linux-gnu) which we cannot hardcode/guess here.
|
||||
FIND_LIBRARY(${compLibName}
|
||||
if(APPLE)
|
||||
NO_DEFAULT_PATH
|
||||
endif(APPLE)
|
||||
NAMES ${compLib}
|
||||
HINTS $ENV{LIBSVN_DIR} ${CMAKE_INSTALL_PREFIX} ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib
|
||||
PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64
|
||||
PATHS ${ADDITIONAL_LIBRARY_PATHS}
|
||||
)
|
||||
|
||||
list(APPEND ${libs} ${${compLibName}})
|
||||
endmacro()
|
||||
|
||||
find_program(HAVE_APR_CONFIG apr-1-config)
|
||||
if(HAVE_APR_CONFIG)
|
||||
|
||||
execute_process(COMMAND apr-1-config --cppflags --includes
|
||||
OUTPUT_VARIABLE APR_CFLAGS
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(COMMAND apr-1-config --link-ld
|
||||
OUTPUT_VARIABLE RAW_APR_LIBS
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
# clean up some vars, or other CMake pieces complain
|
||||
string(STRIP "${RAW_APR_LIBS}" APR_LIBS)
|
||||
|
||||
else(HAVE_APR_CONFIG)
|
||||
message(STATUS "apr-1-config not found, implement manual search for APR")
|
||||
endif(HAVE_APR_CONFIG)
|
||||
|
||||
if(HAVE_APR_CONFIG OR MSVC)
|
||||
find_path(LIBSVN_INCLUDE_DIR svn_client.h
|
||||
NO_DEFAULT_PATH
|
||||
HINTS
|
||||
$ENV{LIBSVN_DIR} ${CMAKE_INSTALL_PREFIX} ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/include
|
||||
PATH_SUFFIXES include/subversion-1
|
||||
PATHS
|
||||
/opt/local
|
||||
/usr/local
|
||||
/usr
|
||||
)
|
||||
|
||||
set(LIBSVN_LIBRARIES "")
|
||||
if (MSVC)
|
||||
find_static_component("apr-1" LIBSVN_LIBRARIES)
|
||||
else (MSVC)
|
||||
list(APPEND LIBSVN_LIBRARIES ${APR_LIBS})
|
||||
endif (MSVC)
|
||||
find_static_component("svn_client-1" LIBSVN_LIBRARIES)
|
||||
find_static_component("svn_subr-1" LIBSVN_LIBRARIES)
|
||||
find_static_component("svn_ra-1" LIBSVN_LIBRARIES)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBSVN DEFAULT_MSG LIBSVN_LIBRARIES LIBSVN_INCLUDE_DIR)
|
||||
if(NOT LIBSVN_FOUND)
|
||||
set(LIBSVN_LIBRARIES "")
|
||||
endif(NOT LIBSVN_FOUND)
|
||||
endif(HAVE_APR_CONFIG OR MSVC)
|
||||
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})
|
||||
@@ -44,52 +45,29 @@ install (FILES ${HEADERS} DESTINATION include/simgear/)
|
||||
get_property(coreSources GLOBAL PROPERTY CORE_SOURCES)
|
||||
get_property(sceneSources GLOBAL PROPERTY SCENE_SOURCES)
|
||||
get_property(publicHeaders GLOBAL PROPERTY PUBLIC_HEADERS)
|
||||
|
||||
if(LIBSVN_FOUND)
|
||||
add_definitions(${APR_CFLAGS})
|
||||
include_directories(${LIBSVN_INCLUDE_DIR})
|
||||
endif()
|
||||
get_property(localExpatSources GLOBAL PROPERTY LOCAL_EXPAT_SOURCES)
|
||||
|
||||
if(SIMGEAR_SHARED)
|
||||
message(STATUS "Library building mode: SHARED LIBRARIES")
|
||||
add_library(SimGearCore SHARED ${coreSources})
|
||||
add_library(SimGearCore SHARED ${coreSources} ${localExpatSources})
|
||||
|
||||
# set_property(TARGET SimGearCore PROPERTY FRAMEWORK 1)
|
||||
# message(STATUS "public header: ${publicHeaders}")
|
||||
# set_property(TARGET SimGearCore PROPERTY PUBLIC_HEADER "${publicHeaders}")
|
||||
set_property(TARGET SimGearCore PROPERTY LINKER_LANGUAGE CXX)
|
||||
|
||||
set_property(TARGET SimGearCore PROPERTY VERSION ${SIMGEAR_VERSION})
|
||||
set_property(TARGET SimGearCore PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
|
||||
|
||||
target_link_libraries(SimGearCore ${ZLIB_LIBRARY} ${RT_LIBRARY}
|
||||
${EXPAT_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
if(LIBSVN_FOUND)
|
||||
target_link_libraries(SimGearCore ${LIBSVN_LIBRARIES})
|
||||
endif(LIBSVN_FOUND)
|
||||
|
||||
install(TARGETS SimGearCore EXPORT SimGearCoreConfig LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(EXPORT SimGearCoreConfig DESTINATION share/SimGearCore)
|
||||
|
||||
if(NOT SIMGEAR_HEADLESS)
|
||||
add_library(SimGearScene SHARED ${sceneSources})
|
||||
# set_property(TARGET SimGearScene PROPERTY FRAMEWORK 1)
|
||||
# set_property(TARGET SimGearScene PROPERTY PUBLIC_HEADER "${publicHeaders}")
|
||||
set_property(TARGET SimGearScene PROPERTY LINKER_LANGUAGE CXX)
|
||||
set_property(TARGET SimGearScene PROPERTY VERSION ${SIMGEAR_VERSION})
|
||||
set_property(TARGET SimGearScene PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
|
||||
|
||||
target_link_libraries(SimGearScene
|
||||
SimGearCore
|
||||
${ZLIB_LIBRARY}
|
||||
${OPENSCENEGRAPH_LIBRARIES}
|
||||
${OPENAL_LIBRARY}
|
||||
${OPENGL_LIBRARY}
|
||||
${JPEG_LIBRARY})
|
||||
|
||||
install(TARGETS SimGearScene LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
# EXPORT SimGearSceneConfig
|
||||
install(TARGETS SimGearScene LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} )
|
||||
# install(EXPORT SimGearSceneConfig DESTINATION share/SimGearScene)
|
||||
endif()
|
||||
|
||||
install(TARGETS SimGearCore LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
else()
|
||||
message(STATUS "Library building mode: STATIC LIBRARIES")
|
||||
|
||||
@@ -111,7 +89,7 @@ else()
|
||||
source_group("${name}\\Headers" FILES ${g2})
|
||||
endforeach()
|
||||
|
||||
add_library(SimGearCore STATIC ${coreSources})
|
||||
add_library(SimGearCore STATIC ${coreSources} ${localExpatSources})
|
||||
install(TARGETS SimGearCore ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
if(NOT SIMGEAR_HEADLESS)
|
||||
@@ -138,6 +116,24 @@ else()
|
||||
endif(NOT SIMGEAR_HEADLESS)
|
||||
endif(SIMGEAR_SHARED)
|
||||
|
||||
target_link_libraries(SimGearCore
|
||||
${ZLIB_LIBRARY}
|
||||
${RT_LIBRARY}
|
||||
${DL_LIBRARY}
|
||||
${EXPAT_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${CORE_SERVICES_LIBRARY})
|
||||
|
||||
if(NOT SIMGEAR_HEADLESS)
|
||||
target_link_libraries(SimGearScene
|
||||
SimGearCore
|
||||
${ZLIB_LIBRARY}
|
||||
${OPENSCENEGRAPH_LIBRARIES}
|
||||
${OPENAL_LIBRARY}
|
||||
${OPENGL_LIBRARY}
|
||||
${JPEG_LIBRARY})
|
||||
endif()
|
||||
|
||||
if(ENABLE_RTI)
|
||||
# Ugly first aid to make hla compile agian
|
||||
set_property(SOURCE hla/RTI13InteractionClass.cxx hla/RTI13ObjectClass.cxx
|
||||
|
||||
@@ -212,7 +212,7 @@ std::string SGBucket::gen_base_path() const {
|
||||
main_lat *= -1;
|
||||
}
|
||||
|
||||
sprintf(raw_path, "%c%03d%c%02d/%c%03d%c%02d",
|
||||
snprintf(raw_path, 256, "%c%03d%c%02d/%c%03d%c%02d",
|
||||
hem, top_lon, pole, top_lat,
|
||||
hem, main_lon, pole, main_lat);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,11 +4,11 @@ set(HEADERS
|
||||
canvas_fwd.hxx
|
||||
Canvas.hxx
|
||||
CanvasEvent.hxx
|
||||
CanvasEventListener.hxx
|
||||
CanvasEventManager.hxx
|
||||
CanvasEventTypes.hxx
|
||||
CanvasEventVisitor.hxx
|
||||
CanvasMgr.hxx
|
||||
CanvasObjectPlacement.hxx
|
||||
CanvasPlacement.hxx
|
||||
CanvasSystemAdapter.hxx
|
||||
MouseEvent.hxx
|
||||
@@ -19,10 +19,10 @@ set(HEADERS
|
||||
set(SOURCES
|
||||
Canvas.cxx
|
||||
CanvasEvent.cxx
|
||||
CanvasEventListener.cxx
|
||||
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
|
||||
{
|
||||
@@ -82,16 +81,13 @@ namespace canvas
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Canvas::setSystemAdapter(const SystemAdapterPtr& system_adapter)
|
||||
void Canvas::onDestroy()
|
||||
{
|
||||
_system_adapter = system_adapter;
|
||||
_texture.setSystemAdapter(system_adapter);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
SystemAdapterPtr Canvas::getSystemAdapter() const
|
||||
{
|
||||
return _system_adapter;
|
||||
if( _root_group )
|
||||
{
|
||||
_root_group->clearEventListener();
|
||||
_root_group->onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -107,7 +103,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 +117,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,24 +209,21 @@ 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();
|
||||
|
||||
// TODO Allow custom render order? For now just keep in order with
|
||||
// property tree.
|
||||
camera->setRenderOrder(osg::Camera::PRE_RENDER, _node->getIndex());
|
||||
|
||||
osg::Vec4 clear_color(0.0f, 0.0f, 0.0f , 1.0f);
|
||||
parseColor(_node->getStringValue("background"), clear_color);
|
||||
camera->setClearColor(clear_color);
|
||||
|
||||
camera->addChild(_root_group->getMatrixTransform());
|
||||
|
||||
// Ensure objects are drawn in order of traversal
|
||||
camera->getOrCreateStateSet()->setBinName("TraversalOrderBin");
|
||||
|
||||
if( _texture.serviceable() )
|
||||
{
|
||||
setStatusFlags(STATUS_OK);
|
||||
@@ -201,10 +239,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;
|
||||
@@ -267,12 +313,13 @@ namespace canvas
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
naRef Canvas::addEventListener(const nasal::CallContext& ctx)
|
||||
bool Canvas::addEventListener( const std::string& type,
|
||||
const EventListener& cb )
|
||||
{
|
||||
if( !_root_group.get() )
|
||||
naRuntimeError(ctx.c, "Canvas: No root group!");
|
||||
throw std::runtime_error("Canvas::AddEventListener: no root group!");
|
||||
|
||||
return _root_group->addEventListener(ctx);
|
||||
return _root_group->addEventListener(type, cb);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -344,13 +391,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());
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -361,7 +414,8 @@ namespace canvas
|
||||
|
||||
EventVisitor visitor( EventVisitor::TRAVERSE_DOWN,
|
||||
event->getClientPos(),
|
||||
event->getDelta() );
|
||||
event->getDelta(),
|
||||
_root_group );
|
||||
if( !_root_group->accept(visitor) )
|
||||
return false;
|
||||
|
||||
@@ -408,21 +462,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 +508,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 +554,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 )
|
||||
@@ -500,12 +581,40 @@ namespace canvas
|
||||
(
|
||||
SG_GENERAL,
|
||||
SG_WARN,
|
||||
"Canvas::addPlacementFactory: replace existing factor for type " << type
|
||||
"Canvas::addPlacementFactory: replace existing factory '" << type << "'"
|
||||
);
|
||||
|
||||
_placement_factories[type] = factory;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Canvas::removePlacementFactory(const std::string& type)
|
||||
{
|
||||
PlacementFactoryMap::iterator it = _placement_factories.find(type);
|
||||
if( it == _placement_factories.end() )
|
||||
SG_LOG
|
||||
(
|
||||
SG_GENERAL,
|
||||
SG_WARN,
|
||||
"Canvas::removePlacementFactory: no such factory '" << type << "'"
|
||||
);
|
||||
else
|
||||
_placement_factories.erase(it);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Canvas::setSystemAdapter(const SystemAdapterPtr& system_adapter)
|
||||
{
|
||||
_system_adapter = system_adapter;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
SystemAdapterPtr Canvas::getSystemAdapter()
|
||||
{
|
||||
return _system_adapter;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Canvas::setSelf(const PropertyBasedElementPtr& self)
|
||||
{
|
||||
@@ -547,6 +656,7 @@ namespace canvas
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Canvas::PlacementFactoryMap Canvas::_placement_factories;
|
||||
SystemAdapterPtr Canvas::_system_adapter;
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace simgear
|
||||
|
||||
@@ -23,12 +23,13 @@
|
||||
#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>
|
||||
#include <osg/observer_ptr>
|
||||
|
||||
#include <memory>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace simgear
|
||||
@@ -71,29 +72,54 @@ namespace canvas
|
||||
|
||||
Canvas(SGPropertyNode* node);
|
||||
virtual ~Canvas();
|
||||
|
||||
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
|
||||
SystemAdapterPtr getSystemAdapter() const;
|
||||
virtual void onDestroy();
|
||||
|
||||
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
|
||||
*
|
||||
@@ -104,7 +130,7 @@ namespace canvas
|
||||
|
||||
void update(double delta_time_sec);
|
||||
|
||||
naRef addEventListener(const nasal::CallContext& ctx);
|
||||
bool addEventListener(const std::string& type, const EventListener& cb);
|
||||
|
||||
void setSizeX(int sx);
|
||||
void setSizeY(int sy);
|
||||
@@ -117,6 +143,7 @@ namespace canvas
|
||||
|
||||
int getViewWidth() const;
|
||||
int getViewHeight() const;
|
||||
SGRect<int> getViewport() const;
|
||||
|
||||
bool handleMouseEvent(const MouseEventPtr& event);
|
||||
|
||||
@@ -130,15 +157,26 @@ namespace canvas
|
||||
|
||||
CullCallbackPtr getCullCallback() const;
|
||||
|
||||
void reloadPlacements( const std::string& type = std::string() );
|
||||
static void addPlacementFactory( const std::string& type,
|
||||
PlacementFactory factory );
|
||||
static void removePlacementFactory(const std::string& type);
|
||||
|
||||
/**
|
||||
* Set global SystemAdapter for all Canvas/ODGauge instances.
|
||||
*
|
||||
* The SystemAdapter is responsible for application specific operations
|
||||
* like loading images/fonts and adding/removing cameras to the scene
|
||||
* graph.
|
||||
*/
|
||||
static void setSystemAdapter(const SystemAdapterPtr& system_adapter);
|
||||
static SystemAdapterPtr getSystemAdapter();
|
||||
|
||||
protected:
|
||||
|
||||
SystemAdapterPtr _system_adapter;
|
||||
CanvasMgr *_canvas_mgr;
|
||||
|
||||
std::auto_ptr<EventManager> _event_manager;
|
||||
boost::scoped_ptr<EventManager> _event_manager;
|
||||
|
||||
int _size_x,
|
||||
_size_y,
|
||||
@@ -160,9 +198,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;
|
||||
@@ -172,6 +210,8 @@ namespace canvas
|
||||
|
||||
private:
|
||||
|
||||
static SystemAdapterPtr _system_adapter;
|
||||
|
||||
Canvas(const Canvas&); // = delete;
|
||||
Canvas& operator=(const Canvas&); // = delete;
|
||||
};
|
||||
|
||||
@@ -62,6 +62,12 @@ namespace canvas
|
||||
return target;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
ElementWeakPtr Event::getCurrentTarget() const
|
||||
{
|
||||
return current_target;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
double Event::getTime() const
|
||||
{
|
||||
|
||||
@@ -41,7 +41,8 @@ namespace canvas
|
||||
};
|
||||
|
||||
Type type;
|
||||
ElementWeakPtr target;
|
||||
ElementWeakPtr target,
|
||||
current_target;
|
||||
double time;
|
||||
bool propagation_stopped;
|
||||
|
||||
@@ -55,6 +56,7 @@ namespace canvas
|
||||
std::string getTypeString() const;
|
||||
|
||||
ElementWeakPtr getTarget() const;
|
||||
ElementWeakPtr getCurrentTarget() const;
|
||||
|
||||
double getTime() const;
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// Listener for canvas (GUI) events being passed to a Nasal function/code
|
||||
//
|
||||
// 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 "CanvasEvent.hxx"
|
||||
#include "CanvasEventListener.hxx"
|
||||
#include "CanvasSystemAdapter.hxx"
|
||||
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
namespace canvas
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
EventListener::EventListener(naRef code, const SystemAdapterPtr& sys_adapter):
|
||||
_code(code),
|
||||
_gc_key(-1),
|
||||
_sys(sys_adapter)
|
||||
{
|
||||
assert( sys_adapter );
|
||||
if( !naIsCode(code)
|
||||
&& !naIsCCode(code)
|
||||
&& !naIsFunc(code) )
|
||||
throw std::runtime_error
|
||||
(
|
||||
"canvas::EventListener: invalid function argument"
|
||||
);
|
||||
|
||||
_gc_key = sys_adapter->gcSave(_code);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
EventListener::~EventListener()
|
||||
{
|
||||
assert( !_sys.expired() );
|
||||
_sys.lock()->gcRelease(_gc_key);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void EventListener::call(const canvas::EventPtr& event)
|
||||
{
|
||||
SystemAdapterPtr sys = _sys.lock();
|
||||
|
||||
naRef args[] = {
|
||||
nasal::Ghost<EventPtr>::create(sys->getNasalContext(), event)
|
||||
};
|
||||
const int num_args = sizeof(args)/sizeof(args[0]);
|
||||
|
||||
sys->callMethod(_code, naNil(), num_args, args, naNil());
|
||||
}
|
||||
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace simgear
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "CanvasEventManager.hxx"
|
||||
#include "MouseEvent.hxx"
|
||||
#include <simgear/canvas/elements/CanvasElement.hxx>
|
||||
#include <cmath>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
@@ -71,6 +72,7 @@ namespace canvas
|
||||
bool EventManager::handleEvent( const MouseEventPtr& event,
|
||||
const EventPropagationPath& path )
|
||||
{
|
||||
bool handled = false;
|
||||
switch( event->type )
|
||||
{
|
||||
case Event::MOUSE_DOWN:
|
||||
@@ -78,20 +80,27 @@ namespace canvas
|
||||
break;
|
||||
case Event::MOUSE_UP:
|
||||
{
|
||||
if( _last_mouse_down.path.empty() )
|
||||
// Ignore mouse up without any previous mouse down
|
||||
return false;
|
||||
// If the mouse has moved while a button was down (aka. dragging) we
|
||||
// need to notify the original element that the mouse has left it, and
|
||||
// the new element that it has been entered
|
||||
if( _last_mouse_down.path != path )
|
||||
handled |= handleMove(event, path);
|
||||
|
||||
// normal mouseup
|
||||
propagateEvent(event, path);
|
||||
handled |= propagateEvent(event, path);
|
||||
|
||||
if( _last_mouse_down.path.empty() )
|
||||
// Ignore mouse up without any previous mouse down
|
||||
return handled;
|
||||
|
||||
// now handle click/dblclick
|
||||
if( checkClickDistance(path, _last_mouse_down.path) )
|
||||
handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
|
||||
handled |=
|
||||
handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
|
||||
|
||||
_last_mouse_down.clear();
|
||||
|
||||
return true;
|
||||
return handled;
|
||||
}
|
||||
case Event::DRAG:
|
||||
if( !_last_mouse_down.valid() )
|
||||
@@ -99,11 +108,18 @@ namespace canvas
|
||||
else
|
||||
return propagateEvent(event, _last_mouse_down.path);
|
||||
case Event::MOUSE_MOVE:
|
||||
handleMove(event, path);
|
||||
handled |= handleMove(event, path);
|
||||
break;
|
||||
case Event::MOUSE_LEAVE:
|
||||
// Mouse leaves window and therefore also current mouseover element
|
||||
handleMove(event, EventPropagationPath());
|
||||
|
||||
// Event is only send if mouse is moved outside the window or dragging
|
||||
// has ended somewhere outside the window. In both cases a mouse button
|
||||
// has been released, so no more mouse down or click...
|
||||
_last_mouse_down.clear();
|
||||
_last_click.clear();
|
||||
|
||||
return true;
|
||||
case Event::WHEEL:
|
||||
break;
|
||||
@@ -111,11 +127,11 @@ namespace canvas
|
||||
return false;
|
||||
}
|
||||
|
||||
return propagateEvent(event, path);
|
||||
return handled | propagateEvent(event, path);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void EventManager::handleClick( const MouseEventPtr& event,
|
||||
bool EventManager::handleClick( const MouseEventPtr& event,
|
||||
const EventPropagationPath& path )
|
||||
{
|
||||
MouseEventPtr click(new MouseEvent(*event));
|
||||
@@ -145,36 +161,76 @@ namespace canvas
|
||||
dbl_click->type = Event::DBL_CLICK;
|
||||
}
|
||||
|
||||
propagateEvent(click, path);
|
||||
bool handled = propagateEvent(click, path);
|
||||
|
||||
if( dbl_click )
|
||||
propagateEvent(dbl_click, getCommonAncestor(_last_click.path, path));
|
||||
handled |= propagateEvent( dbl_click,
|
||||
getCommonAncestor(_last_click.path, path) );
|
||||
|
||||
_last_click = StampedPropagationPath(path, event->getTime());
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void EventManager::handleMove( const MouseEventPtr& event,
|
||||
bool EventManager::handleMove( const MouseEventPtr& event,
|
||||
const EventPropagationPath& path )
|
||||
{
|
||||
if( _last_mouse_over.path == path )
|
||||
return;
|
||||
EventPropagationPath& last_path = _last_mouse_over.path;
|
||||
if( last_path == path )
|
||||
return false;
|
||||
|
||||
if( !_last_mouse_over.path.empty() )
|
||||
bool handled = false;
|
||||
|
||||
// Leave old element
|
||||
if( !last_path.empty() )
|
||||
{
|
||||
MouseEventPtr mouseout(new MouseEvent(*event));
|
||||
mouseout->type = Event::MOUSE_OUT;
|
||||
propagateEvent(mouseout, _last_mouse_over.path);
|
||||
handled |= 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;
|
||||
handled |= 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);
|
||||
handled |= 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;
|
||||
handled |= propagateEvent(mouseenter, path_enter);
|
||||
}
|
||||
}
|
||||
|
||||
_last_mouse_over.path = path;
|
||||
return handled;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -188,15 +244,29 @@ 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;
|
||||
// }
|
||||
|
||||
// Check if event supports bubbling
|
||||
const Event::Type types_no_bubbling[] = {
|
||||
Event::MOUSE_ENTER,
|
||||
Event::MOUSE_LEAVE,
|
||||
};
|
||||
const size_t num_types_no_bubbling = sizeof(types_no_bubbling)
|
||||
/ sizeof(types_no_bubbling[0]);
|
||||
bool do_bubble = true;
|
||||
for( size_t i = 0; i < num_types_no_bubbling; ++i )
|
||||
if( event->type == types_no_bubbling[i] )
|
||||
{
|
||||
do_bubble = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Bubbling phase
|
||||
for( EventPropagationPath::const_reverse_iterator
|
||||
it = path.rbegin();
|
||||
@@ -206,29 +276,34 @@ namespace canvas
|
||||
ElementPtr el = it->element.lock();
|
||||
|
||||
if( !el )
|
||||
{
|
||||
// Ignore element if it has been destroyed while traversing the event
|
||||
// (eg. removed by another event handler)
|
||||
continue;
|
||||
if( do_bubble )
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if( event->propagation_stopped )
|
||||
event->current_target = el;
|
||||
el->handleEvent(event);
|
||||
|
||||
if( event->propagation_stopped || !do_bubble )
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -241,8 +316,8 @@ namespace canvas
|
||||
const EventPropagationPath& path2 ) const
|
||||
{
|
||||
osg::Vec2 delta = path1.front().local_pos - path2.front().local_pos;
|
||||
return delta.x() < drag_threshold
|
||||
&& delta.y() < drag_threshold;
|
||||
return std::fabs(delta.x()) < drag_threshold
|
||||
&& std::fabs(delta.y()) < drag_threshold;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -250,6 +325,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;
|
||||
|
||||
|
||||
@@ -70,13 +70,13 @@ namespace canvas
|
||||
/**
|
||||
* Propagate click event and handle multi-click (eg. create dblclick)
|
||||
*/
|
||||
void handleClick( const MouseEventPtr& event,
|
||||
bool handleClick( const MouseEventPtr& event,
|
||||
const EventPropagationPath& path );
|
||||
|
||||
/**
|
||||
* Handle mouseover/enter/out/leave
|
||||
*/
|
||||
void handleMove( const MouseEventPtr& event,
|
||||
bool handleMove( const MouseEventPtr& event,
|
||||
const EventPropagationPath& path );
|
||||
|
||||
bool propagateEvent( const EventPtr& event,
|
||||
|
||||
@@ -30,8 +30,10 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
EventVisitor::EventVisitor( TraverseMode mode,
|
||||
const osg::Vec2f& pos,
|
||||
const osg::Vec2f& delta ):
|
||||
_traverse_mode( mode )
|
||||
const osg::Vec2f& delta,
|
||||
const ElementPtr& root ):
|
||||
_traverse_mode( mode ),
|
||||
_root(root)
|
||||
{
|
||||
if( mode == TRAVERSE_DOWN )
|
||||
{
|
||||
@@ -70,10 +72,11 @@ namespace canvas
|
||||
m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1)
|
||||
);
|
||||
|
||||
// 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(pos, local_pos) )
|
||||
// Don't check specified root element for collision, as its purpose is to
|
||||
// catch all events which have no target. This allows for example calling
|
||||
// event listeners attached to the canvas itself (its root group) even if
|
||||
// no element has been hit.
|
||||
if( _root.get() != &el && !el.hitBound(pos, local_pos) )
|
||||
return false;
|
||||
|
||||
const osg::Vec2f& delta = _target_path.back().local_delta;
|
||||
@@ -86,7 +89,7 @@ namespace canvas
|
||||
EventTarget target = {el.getWeakPtr(), local_pos, local_delta};
|
||||
_target_path.push_back(target);
|
||||
|
||||
if( el.traverse(*this) || _target_path.size() <= 2 )
|
||||
if( el.traverse(*this) || &el == _root.get() )
|
||||
return true;
|
||||
|
||||
_target_path.pop_back();
|
||||
|
||||
@@ -38,9 +38,17 @@ namespace canvas
|
||||
TRAVERSE_DOWN
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mode
|
||||
* @param pos Mouse position
|
||||
* @param delta Mouse movement since last mouse move event
|
||||
* @param root Element to dispatch events to if no element is hit
|
||||
*/
|
||||
EventVisitor( TraverseMode mode,
|
||||
const osg::Vec2f& pos,
|
||||
const osg::Vec2f& delta );
|
||||
const osg::Vec2f& delta,
|
||||
const ElementPtr& root = ElementPtr() );
|
||||
virtual ~EventVisitor();
|
||||
virtual bool traverse(Element& el);
|
||||
virtual bool apply(Element& el);
|
||||
@@ -51,6 +59,7 @@ namespace canvas
|
||||
|
||||
TraverseMode _traverse_mode;
|
||||
EventPropagationPath _target_path;
|
||||
ElementPtr _root;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "CanvasMgr.hxx"
|
||||
#include "Canvas.hxx"
|
||||
#include "CanvasEventManager.hxx"
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
@@ -35,10 +36,8 @@ namespace canvas
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
CanvasMgr::CanvasMgr( SGPropertyNode_ptr node,
|
||||
SystemAdapterPtr system_adapter ):
|
||||
PropertyBasedMgr(node, "texture", &canvasFactory),
|
||||
_system_adapter(system_adapter)
|
||||
CanvasMgr::CanvasMgr(SGPropertyNode_ptr node):
|
||||
PropertyBasedMgr(node, "texture", &canvasFactory)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -65,7 +64,6 @@ namespace canvas
|
||||
void CanvasMgr::elementCreated(PropertyBasedElementPtr element)
|
||||
{
|
||||
CanvasPtr canvas = boost::static_pointer_cast<Canvas>(element);
|
||||
canvas->setSystemAdapter(_system_adapter);
|
||||
canvas->setCanvasMgr(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,8 @@ namespace canvas
|
||||
|
||||
/**
|
||||
* @param node Root node of branch used to control canvasses
|
||||
* @param system_adapter Adapter for connecting between canvas and
|
||||
* application framework
|
||||
*
|
||||
*/
|
||||
CanvasMgr( SGPropertyNode_ptr node,
|
||||
SystemAdapterPtr system_adapter );
|
||||
CanvasMgr(SGPropertyNode_ptr node);
|
||||
|
||||
/**
|
||||
* Create a new canvas
|
||||
@@ -65,8 +61,6 @@ namespace canvas
|
||||
|
||||
protected:
|
||||
|
||||
SystemAdapterPtr _system_adapter;
|
||||
|
||||
virtual void elementCreated(PropertyBasedElementPtr element);
|
||||
};
|
||||
|
||||
|
||||
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_ */
|
||||
@@ -20,7 +20,6 @@
|
||||
#define SG_CANVAS_SYSTEM_ADAPTER_HXX_
|
||||
|
||||
#include "canvas_fwd.hxx"
|
||||
#include <simgear/nasal/nasal.h>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
@@ -36,29 +35,6 @@ namespace canvas
|
||||
virtual void addCamera(osg::Camera* camera) const = 0;
|
||||
virtual void removeCamera(osg::Camera* camera) const = 0;
|
||||
virtual osg::Image* getImage(const std::string& path) const = 0;
|
||||
|
||||
virtual naContext getNasalContext() const = 0;
|
||||
|
||||
/**
|
||||
* Save passed reference to Nasal object from being deleted by the
|
||||
* garbage collector.
|
||||
*/
|
||||
virtual int gcSave(naRef r) = 0;
|
||||
|
||||
/**
|
||||
* Release an object previously passed to ::gcSave to allow it being
|
||||
* cleaned up by the garbage collector.
|
||||
*/
|
||||
virtual void gcRelease(int key) = 0;
|
||||
|
||||
/**
|
||||
* Call a Nasal function with the given environment and arguments.
|
||||
*/
|
||||
virtual naRef callMethod( naRef code,
|
||||
naRef self,
|
||||
int argc,
|
||||
naRef* args,
|
||||
naRef locals ) = 0;
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#endif
|
||||
|
||||
#include "ODGauge.hxx"
|
||||
#include "Canvas.hxx"
|
||||
#include "CanvasSystemAdapter.hxx"
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
@@ -43,6 +44,7 @@
|
||||
#include <osg/ShadeModel>
|
||||
#include <osg/StateSet>
|
||||
#include <osg/FrameBufferObject> // for GL_DEPTH_STENCIL_EXT on Windows
|
||||
#include <osgUtil/RenderBin>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -51,18 +53,67 @@ namespace simgear
|
||||
namespace canvas
|
||||
{
|
||||
|
||||
class PreOrderBin:
|
||||
public osgUtil::RenderBin
|
||||
{
|
||||
public:
|
||||
|
||||
PreOrderBin()
|
||||
{}
|
||||
PreOrderBin( const RenderBin& rhs,
|
||||
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY ):
|
||||
RenderBin(rhs, copyop)
|
||||
{}
|
||||
|
||||
virtual osg::Object* cloneType() const
|
||||
{
|
||||
return new PreOrderBin();
|
||||
}
|
||||
virtual osg::Object* clone(const osg::CopyOp& copyop) const
|
||||
{
|
||||
return new PreOrderBin(*this,copyop);
|
||||
}
|
||||
virtual bool isSameKindAs(const osg::Object* obj) const
|
||||
{
|
||||
return dynamic_cast<const PreOrderBin*>(obj) != 0L;
|
||||
}
|
||||
virtual const char* className() const
|
||||
{
|
||||
return "PreOrderBin";
|
||||
}
|
||||
|
||||
virtual void sort()
|
||||
{
|
||||
// Do not sort to keep traversal order...
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef OSG_INIT_SINGLETON_PROXY
|
||||
/**
|
||||
* http://svn.openscenegraph.org/osg/OpenSceneGraph/trunk/include/osg/Object
|
||||
*
|
||||
* Helper macro that creates a static proxy object to call singleton function
|
||||
* on it's construction, ensuring that the singleton gets initialized at
|
||||
* startup.
|
||||
*/
|
||||
# define OSG_INIT_SINGLETON_PROXY(ProxyName, Func)\
|
||||
static struct ProxyName{ ProxyName() { Func; } } s_##ProxyName;
|
||||
#endif
|
||||
|
||||
OSG_INIT_SINGLETON_PROXY(
|
||||
PreOrderBinProxy,
|
||||
(osgUtil::RenderBin::addRenderBinPrototype("PreOrderBin", new PreOrderBin))
|
||||
);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
ODGauge::ODGauge():
|
||||
_size_x( -1 ),
|
||||
_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 )
|
||||
{
|
||||
|
||||
}
|
||||
@@ -73,12 +124,6 @@ namespace canvas
|
||||
clear();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void ODGauge::setSystemAdapter(const SystemAdapterPtr& system_adapter)
|
||||
{
|
||||
_system_adapter = system_adapter;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void ODGauge::setSize(int size_x, int size_y)
|
||||
{
|
||||
@@ -101,42 +146,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 +208,9 @@ namespace canvas
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool ODGauge::serviceable(void)
|
||||
bool ODGauge::serviceable() const
|
||||
{
|
||||
return rtAvailable;
|
||||
return _flags & AVAILABLE;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -196,26 +242,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());
|
||||
if( Canvas::getSystemAdapter() )
|
||||
Canvas::getSystemAdapter()->addCamera(camera.get());
|
||||
|
||||
rtAvailable = true;
|
||||
_flags |= AVAILABLE;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -229,12 +274,22 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
void ODGauge::clear()
|
||||
{
|
||||
if( camera.valid() && _system_adapter )
|
||||
_system_adapter->removeCamera(camera.get());
|
||||
if( camera.valid() && Canvas::getSystemAdapter() )
|
||||
Canvas::getSystemAdapter()->removeCamera(camera.get());
|
||||
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 +304,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 +322,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 +344,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
|
||||
|
||||
@@ -53,8 +53,6 @@ namespace canvas
|
||||
ODGauge();
|
||||
virtual ~ODGauge();
|
||||
|
||||
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
|
||||
|
||||
/**
|
||||
* Set the size of the render target.
|
||||
*
|
||||
@@ -71,6 +69,8 @@ namespace canvas
|
||||
*/
|
||||
void setViewSize(int width, int height = -1);
|
||||
|
||||
osg::Vec2s getViewSize() const;
|
||||
|
||||
/**
|
||||
* DEPRECATED
|
||||
*
|
||||
@@ -91,6 +91,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 +118,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.
|
||||
@@ -128,27 +134,35 @@ namespace canvas
|
||||
|
||||
protected:
|
||||
|
||||
SystemAdapterPtr _system_adapter;
|
||||
|
||||
int _size_x,
|
||||
_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);
|
||||
|
||||
@@ -51,7 +51,6 @@ namespace canvas
|
||||
SG_FWD_DECL(Text)
|
||||
|
||||
SG_FWD_DECL(Event)
|
||||
SG_FWD_DECL(EventListener)
|
||||
SG_FWD_DECL(MouseEvent)
|
||||
SG_FWD_DECL(Placement)
|
||||
SG_FWD_DECL(SystemAdapter)
|
||||
@@ -73,6 +72,8 @@ namespace canvas
|
||||
typedef boost::function<Placements( SGPropertyNode*,
|
||||
CanvasPtr )> PlacementFactory;
|
||||
|
||||
typedef boost::function<void(const EventPtr&)> EventListener;
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace simgear
|
||||
|
||||
|
||||
@@ -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}")
|
||||
@@ -17,10 +17,11 @@
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#include "CanvasElement.hxx"
|
||||
#include <simgear/canvas/Canvas.hxx>
|
||||
#include <simgear/canvas/CanvasEventListener.hxx>
|
||||
#include <simgear/canvas/CanvasEventVisitor.hxx>
|
||||
#include <simgear/canvas/MouseEvent.hxx>
|
||||
#include <simgear/math/SGMisc.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/scene/material/parseBlendFunc.hxx>
|
||||
|
||||
#include <osg/Drawable>
|
||||
#include <osg/Geode>
|
||||
@@ -28,11 +29,10 @@
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
namespace simgear
|
||||
@@ -41,20 +41,110 @@ namespace canvas
|
||||
{
|
||||
const std::string NAME_TRANSFORM = "tf";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Element::removeListener()
|
||||
/**
|
||||
* glScissor with coordinates relative to different reference frames.
|
||||
*/
|
||||
class Element::RelativeScissor:
|
||||
public osg::Scissor
|
||||
{
|
||||
_node->removeChangeListener(this);
|
||||
public:
|
||||
|
||||
ReferenceFrame _coord_reference;
|
||||
osg::Matrix _parent_inverse;
|
||||
|
||||
RelativeScissor():
|
||||
_coord_reference(GLOBAL)
|
||||
{}
|
||||
|
||||
virtual void apply(osg::State& state) const
|
||||
{
|
||||
const osg::Viewport* vp = state.getCurrentViewport();
|
||||
float w2 = 0.5 * vp->width(),
|
||||
h2 = 0.5 * vp->height();
|
||||
|
||||
osg::Matrix model_view
|
||||
(
|
||||
w2, 0, 0, 0,
|
||||
0, h2, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
w2, h2, 0, 1
|
||||
);
|
||||
model_view.preMult(state.getProjectionMatrix());
|
||||
|
||||
if( _coord_reference != GLOBAL )
|
||||
{
|
||||
model_view.preMult(state.getModelViewMatrix());
|
||||
|
||||
if( _coord_reference == PARENT )
|
||||
model_view.preMult(_parent_inverse);
|
||||
}
|
||||
|
||||
const osg::Vec2 scale( model_view(0,0), model_view(1,1)),
|
||||
offset(model_view(3,0), model_view(3,1));
|
||||
|
||||
// TODO check/warn for rotation?
|
||||
|
||||
GLint x = SGMiscf::roundToInt(scale.x() * _x + offset.x()),
|
||||
y = SGMiscf::roundToInt(scale.y() * _y + offset.y()),
|
||||
w = SGMiscf::roundToInt(std::fabs(scale.x()) * _width),
|
||||
h = SGMiscf::roundToInt(std::fabs(scale.y()) * _height);
|
||||
|
||||
if( scale.x() < 0 )
|
||||
x -= w;
|
||||
if( scale.y() < 0 )
|
||||
y -= h;
|
||||
|
||||
glScissor(x, y, w, h);
|
||||
}
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Element::OSGUserData::OSGUserData(ElementPtr element):
|
||||
element(element)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Element::~Element()
|
||||
{
|
||||
removeListener();
|
||||
if( !_transform.valid() )
|
||||
return;
|
||||
|
||||
for(unsigned int i = 0; i < _transform->getNumChildren(); ++i)
|
||||
{
|
||||
OSGUserData* ud =
|
||||
static_cast<OSGUserData*>(_transform->getChild(i)->getUserData());
|
||||
|
||||
if( ud )
|
||||
// Ensure parent is cleared to prevent accessing released memory if an
|
||||
// element somehow survives longer than his parent.
|
||||
ud->element->_parent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +154,12 @@ namespace canvas
|
||||
return boost::static_pointer_cast<Element>(_self.lock());
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
ElementPtr Element::getParent()
|
||||
{
|
||||
return _parent ? _parent->getWeakPtr().lock() : ElementPtr();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Element::update(double dt)
|
||||
{
|
||||
@@ -129,40 +225,73 @@ namespace canvas
|
||||
}
|
||||
_transform->setMatrix(m);
|
||||
_transform_dirty = false;
|
||||
_attributes_dirty |= SCISSOR_COORDS;
|
||||
}
|
||||
|
||||
if( _attributes_dirty & SCISSOR_COORDS )
|
||||
{
|
||||
if( _scissor && _scissor->_coord_reference != GLOBAL )
|
||||
_scissor->_parent_inverse = _transform->getInverseMatrix();
|
||||
|
||||
_attributes_dirty &= ~SCISSOR_COORDS;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
naRef Element::addEventListener(const nasal::CallContext& ctx)
|
||||
bool Element::addEventListener( const std::string& type_str,
|
||||
const EventListener& cb )
|
||||
{
|
||||
const std::string type_str = ctx.requireArg<std::string>(0);
|
||||
naRef code = ctx.requireArg<naRef>(1);
|
||||
|
||||
SG_LOG
|
||||
(
|
||||
SG_NASAL,
|
||||
SG_GENERAL,
|
||||
SG_INFO,
|
||||
"addEventListener(" << _node->getPath() << ", " << type_str << ")"
|
||||
);
|
||||
|
||||
Event::Type type = Event::strToType(type_str);
|
||||
if( type == Event::UNKNOWN )
|
||||
naRuntimeError( ctx.c,
|
||||
"addEventListener: Unknown event type %s",
|
||||
type_str.c_str() );
|
||||
{
|
||||
SG_LOG( SG_GENERAL,
|
||||
SG_WARN,
|
||||
"addEventListener: Unknown event type " << type_str );
|
||||
return false;
|
||||
}
|
||||
|
||||
_listener[ type ].push_back
|
||||
(
|
||||
boost::make_shared<EventListener>( code,
|
||||
_canvas.lock()->getSystemAdapter() )
|
||||
);
|
||||
_listener[ type ].push_back(cb);
|
||||
|
||||
return naNil();
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Element::clearEventListener()
|
||||
{
|
||||
_listener.clear();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::accept(EventVisitor& visitor)
|
||||
{
|
||||
if( !isVisible() )
|
||||
return false;
|
||||
|
||||
return visitor.apply(*this);
|
||||
}
|
||||
|
||||
@@ -181,14 +310,16 @@ 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);
|
||||
BOOST_FOREACH(EventListener const& listener, listeners->second)
|
||||
listener(event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -199,21 +330,28 @@ namespace canvas
|
||||
|
||||
// Drawables have a bounding box...
|
||||
if( _drawable )
|
||||
{
|
||||
if( !_drawable->getBound().contains(osg::Vec3f(local_pos, 0)) )
|
||||
return false;
|
||||
}
|
||||
return _drawable->getBound().contains(osg::Vec3f(local_pos, 0));
|
||||
// ... for other elements, i.e. groups only a bounding sphere is available
|
||||
else if( !_transform->getBound().contains(osg::Vec3f(pos, 0)) )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
else
|
||||
return _transform->getBound().contains(osg::Vec3f(pos, 0));
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
osg::ref_ptr<osg::MatrixTransform> Element::getMatrixTransform()
|
||||
bool Element::isVisible() const
|
||||
{
|
||||
return _transform;
|
||||
return _transform.valid() && _transform->getNodeMask() != 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
osg::MatrixTransform* Element::getMatrixTransform()
|
||||
{
|
||||
return _transform.get();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
osg::MatrixTransform const* Element::getMatrixTransform() const
|
||||
{
|
||||
return _transform.get();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -257,26 +395,37 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
|
||||
{
|
||||
if( parent == _node && child->getNameString() == NAME_TRANSFORM )
|
||||
if( parent == _node )
|
||||
{
|
||||
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
|
||||
if( child->getNameString() == NAME_TRANSFORM )
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_GENERAL,
|
||||
SG_WARN,
|
||||
"Element::childRemoved: unknown transform: " << child->getPath()
|
||||
);
|
||||
if( !_transform.valid() )
|
||||
return;
|
||||
|
||||
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_GENERAL,
|
||||
SG_WARN,
|
||||
"Element::childRemoved: unknown transform: " << child->getPath()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_transform_types[ child->getIndex() ] = TT_NONE;
|
||||
|
||||
while( !_transform_types.empty() && _transform_types.back() == TT_NONE )
|
||||
_transform_types.pop_back();
|
||||
|
||||
_transform_dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_transform_types[ child->getIndex() ] = TT_NONE;
|
||||
|
||||
while( !_transform_types.empty() && _transform_types.back() == TT_NONE )
|
||||
_transform_types.pop_back();
|
||||
|
||||
_transform_dirty = true;
|
||||
return;
|
||||
else if( StyleInfo const* style = getStyleInfo(child->getNameString()) )
|
||||
{
|
||||
if( setStyle(getParentStyle(child), style) )
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
childRemoved(child);
|
||||
@@ -288,13 +437,25 @@ namespace canvas
|
||||
SGPropertyNode *parent = child->getParent();
|
||||
if( parent == _node )
|
||||
{
|
||||
if( setStyle(child) )
|
||||
const std::string& name = child->getNameString();
|
||||
if( StyleInfo const* style_info = getStyleInfo(name) )
|
||||
{
|
||||
SGPropertyNode const* style = child;
|
||||
if( isStyleEmpty(child) )
|
||||
{
|
||||
child->clearValue();
|
||||
style = getParentStyle(child);
|
||||
}
|
||||
setStyle(style, style_info);
|
||||
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 )
|
||||
@@ -307,15 +468,10 @@ namespace canvas
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::setStyle(const SGPropertyNode* child)
|
||||
bool Element::setStyle( const SGPropertyNode* child,
|
||||
const StyleInfo* style_info )
|
||||
{
|
||||
StyleSetters::const_iterator setter =
|
||||
_style_setters.find(child->getNameString());
|
||||
if( setter == _style_setters.end() )
|
||||
return false;
|
||||
|
||||
setter->second(child);
|
||||
return true;
|
||||
return canApplyStyle(child) && setStyleImpl(child, style_info);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -324,6 +480,7 @@ namespace canvas
|
||||
if( clip.empty() || clip == "auto" )
|
||||
{
|
||||
getOrCreateStateSet()->removeAttribute(osg::StateAttribute::SCISSOR);
|
||||
_scissor = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -336,17 +493,22 @@ namespace canvas
|
||||
return;
|
||||
}
|
||||
|
||||
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
|
||||
const boost::char_separator<char> del(", \t\npx");
|
||||
|
||||
tokenizer tokens(clip.begin() + RECT.size(), clip.end() - 1, del);
|
||||
const std::string sep(", \t\npx");
|
||||
int comp = 0;
|
||||
int values[4];
|
||||
for( tokenizer::const_iterator tok = tokens.begin();
|
||||
tok != tokens.end() && comp < 4;
|
||||
++tok, ++comp )
|
||||
float values[4];
|
||||
|
||||
for(size_t pos = RECT.size(); comp < 4; ++comp)
|
||||
{
|
||||
values[comp] = boost::lexical_cast<int>(*tok);
|
||||
pos = clip.find_first_not_of(sep, pos);
|
||||
if( pos == std::string::npos || pos == clip.size() - 1 )
|
||||
break;
|
||||
|
||||
char *end = 0;
|
||||
values[comp] = strtod(&clip[pos], &end);
|
||||
if( end == &clip[pos] || !end )
|
||||
break;
|
||||
|
||||
pos = end - &clip[0];
|
||||
}
|
||||
|
||||
if( comp < 4 )
|
||||
@@ -355,32 +517,37 @@ namespace canvas
|
||||
return;
|
||||
}
|
||||
|
||||
float scale_x = 1,
|
||||
scale_y = 1;
|
||||
float width = values[1] - values[3],
|
||||
height = values[2] - values[0];
|
||||
|
||||
CanvasPtr canvas = _canvas.lock();
|
||||
if( canvas )
|
||||
if( width < 0 || height < 0 )
|
||||
{
|
||||
// The scissor rectangle isn't affected by any transformation, so we need
|
||||
// to convert to image/canvas coordinates on our selves.
|
||||
scale_x = canvas->getSizeX()
|
||||
/ static_cast<float>(canvas->getViewWidth());
|
||||
scale_y = canvas->getSizeY()
|
||||
/ static_cast<float>(canvas->getViewHeight());
|
||||
SG_LOG(SG_GENERAL, SG_WARN, "Canvas: negative clip size: " << clip);
|
||||
return;
|
||||
}
|
||||
|
||||
osg::Scissor* scissor = new osg::Scissor();
|
||||
_scissor = new RelativeScissor();
|
||||
// <top>, <right>, <bottom>, <left>
|
||||
scissor->x() = scale_x * values[3];
|
||||
scissor->y() = scale_y * values[0];
|
||||
scissor->width() = scale_x * (values[1] - values[3]);
|
||||
scissor->height() = scale_y * (values[2] - values[0]);
|
||||
_scissor->x() = SGMiscf::roundToInt(values[3]);
|
||||
_scissor->y() = SGMiscf::roundToInt(values[0]);
|
||||
_scissor->width() = SGMiscf::roundToInt(width);
|
||||
_scissor->height() = SGMiscf::roundToInt(height);
|
||||
|
||||
if( canvas )
|
||||
// Canvas has y axis upside down
|
||||
scissor->y() = canvas->getSizeY() - scissor->y() - scissor->height();
|
||||
getOrCreateStateSet()->setAttributeAndModes(_scissor);
|
||||
|
||||
getOrCreateStateSet()->setAttributeAndModes(scissor);
|
||||
SGPropertyNode* clip_frame = _node->getChild("clip-frame", 0);
|
||||
if( clip_frame )
|
||||
valueChanged(clip_frame);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Element::setClipFrame(ReferenceFrame rf)
|
||||
{
|
||||
if( _scissor )
|
||||
{
|
||||
_scissor->_coord_reference = rf;
|
||||
_attributes_dirty |= SCISSOR_COORDS;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -411,11 +578,14 @@ namespace canvas
|
||||
osg::BoundingBox transformed;
|
||||
const osg::BoundingBox& bb = _drawable->getBound();
|
||||
for(int i = 0; i < 4; ++i)
|
||||
transformed.expandBy( m * bb.corner(i) );
|
||||
transformed.expandBy( bb.corner(i) * m );
|
||||
|
||||
return transformed;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Element::StyleSetters Element::_style_setters;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Element::Element( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
@@ -424,11 +594,15 @@ namespace canvas
|
||||
PropertyBasedElement(node),
|
||||
_canvas( canvas ),
|
||||
_parent( parent ),
|
||||
_attributes_dirty( 0 ),
|
||||
_transform_dirty( false ),
|
||||
_transform( new osg::MatrixTransform ),
|
||||
_style( parent_style ),
|
||||
_scissor( 0 ),
|
||||
_drawable( 0 )
|
||||
{
|
||||
staticInit();
|
||||
|
||||
SG_LOG
|
||||
(
|
||||
SG_GL,
|
||||
@@ -436,7 +610,93 @@ namespace canvas
|
||||
"New canvas element " << node->getPath()
|
||||
);
|
||||
|
||||
addStyle("clip", &Element::setClip, this);
|
||||
// Ensure elements are drawn in order they appear in the element tree
|
||||
_transform->getOrCreateStateSet()
|
||||
->setRenderBinDetails
|
||||
(
|
||||
0,
|
||||
"PreOrderBin",
|
||||
osg::StateSet::OVERRIDE_RENDERBIN_DETAILS
|
||||
);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Element::staticInit()
|
||||
{
|
||||
if( isInit<Element>() )
|
||||
return;
|
||||
|
||||
addStyle("clip", "", &Element::setClip, false);
|
||||
addStyle("clip-frame", "", &Element::setClipFrame, false);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::isStyleEmpty(const SGPropertyNode* child) const
|
||||
{
|
||||
return !child
|
||||
|| simgear::strutils::strip(child->getStringValue()).empty();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::canApplyStyle(const SGPropertyNode* child) const
|
||||
{
|
||||
if( _node == child->getParent() )
|
||||
return true;
|
||||
|
||||
// Parent values do not override if element has own value
|
||||
return isStyleEmpty( _node->getChild(child->getName()) );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::setStyleImpl( const SGPropertyNode* child,
|
||||
const StyleInfo* style_info )
|
||||
{
|
||||
const StyleSetter* style_setter = style_info
|
||||
? &style_info->setter
|
||||
: getStyleSetter(child->getNameString());
|
||||
while( style_setter )
|
||||
{
|
||||
if( style_setter->func(*this, child) )
|
||||
return true;
|
||||
style_setter = style_setter->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
const Element::StyleInfo*
|
||||
Element::getStyleInfo(const std::string& name) const
|
||||
{
|
||||
StyleSetters::const_iterator setter = _style_setters.find(name);
|
||||
if( setter == _style_setters.end() )
|
||||
return 0;
|
||||
|
||||
return &setter->second;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
const Element::StyleSetter*
|
||||
Element::getStyleSetter(const std::string& name) const
|
||||
{
|
||||
const StyleInfo* info = getStyleInfo(name);
|
||||
return info ? &info->setter : 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
const SGPropertyNode*
|
||||
Element::getParentStyle(const SGPropertyNode* child) const
|
||||
{
|
||||
// Try to get value from parent...
|
||||
if( _parent )
|
||||
{
|
||||
Style::const_iterator style =
|
||||
_parent->_style.find(child->getNameString());
|
||||
if( style != _parent->_style.end() )
|
||||
return style->second;
|
||||
}
|
||||
|
||||
// ...or reset to default if none is available
|
||||
return child; // TODO somehow get default value for each style?
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
#include <simgear/canvas/CanvasEvent.hxx>
|
||||
#include <simgear/props/PropertyBasedElement.hxx>
|
||||
#include <simgear/misc/stdint.hxx> // for uint32_t
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
|
||||
#include <osg/BoundingBox>
|
||||
#include <osg/MatrixTransform>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/type_traits/is_base_of.hpp>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
@@ -45,24 +45,57 @@ 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
|
||||
bool inheritable; ///!< Whether children can inherit this style from
|
||||
/// their parents
|
||||
};
|
||||
|
||||
/**
|
||||
* Coordinate reference frame (eg. "clip" property)
|
||||
*/
|
||||
enum ReferenceFrame
|
||||
{
|
||||
GLOBAL, ///!< Global coordinates
|
||||
PARENT, ///!< Coordinates relative to parent coordinate frame
|
||||
LOCAL ///!< Coordinates relative to local coordinates (parent
|
||||
/// coordinates with local transformations applied)
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
virtual ~Element() = 0;
|
||||
|
||||
virtual void setSelf(const PropertyBasedElementPtr& self);
|
||||
virtual void onDestroy();
|
||||
|
||||
ElementWeakPtr getWeakPtr() const;
|
||||
ElementPtr getParent();
|
||||
|
||||
/**
|
||||
* Called every frame to update internal state
|
||||
@@ -71,19 +104,26 @@ namespace canvas
|
||||
*/
|
||||
virtual void update(double dt);
|
||||
|
||||
naRef addEventListener(const nasal::CallContext& ctx);
|
||||
bool addEventListener(const std::string& type, const EventListener& cb);
|
||||
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 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 );
|
||||
@@ -91,7 +131,8 @@ namespace canvas
|
||||
SGPropertyNode * child );
|
||||
virtual void valueChanged(SGPropertyNode * child);
|
||||
|
||||
virtual bool setStyle(const SGPropertyNode* child);
|
||||
virtual bool setStyle( const SGPropertyNode* child,
|
||||
const StyleInfo* style_info = 0 );
|
||||
|
||||
/**
|
||||
* Set clipping shape
|
||||
@@ -101,6 +142,11 @@ namespace canvas
|
||||
*/
|
||||
void setClip(const std::string& clip);
|
||||
|
||||
/**
|
||||
* Clipping coordinates reference frame
|
||||
*/
|
||||
void setClipFrame(ReferenceFrame rf);
|
||||
|
||||
/**
|
||||
* Write the given bounding box to the property tree
|
||||
*/
|
||||
@@ -111,11 +157,33 @@ 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 = 1,
|
||||
SCISSOR_COORDS = BLEND_FUNC << 1,
|
||||
LAST_ATTRIBUTE = SCISSOR_COORDS << 1
|
||||
};
|
||||
|
||||
enum TransformType
|
||||
@@ -127,64 +195,332 @@ namespace canvas
|
||||
TT_SCALE
|
||||
};
|
||||
|
||||
class RelativeScissor;
|
||||
|
||||
CanvasWeakPtr _canvas;
|
||||
Element *_parent;
|
||||
|
||||
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;
|
||||
RelativeScissor *_scissor;
|
||||
|
||||
typedef std::vector<EventListenerPtr> Listener;
|
||||
typedef std::vector<EventListener> Listener;
|
||||
typedef std::map<Event::Type, Listener> ListenerMap;
|
||||
|
||||
ListenerMap _listener;
|
||||
|
||||
typedef std::map<std::string, StyleInfo> StyleSetters;
|
||||
static StyleSetters _style_setters;
|
||||
|
||||
static void staticInit();
|
||||
|
||||
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>
|
||||
static bool isInit()
|
||||
{
|
||||
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
|
||||
>
|
||||
static
|
||||
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,
|
||||
bool inheritable = true )
|
||||
{
|
||||
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;
|
||||
}
|
||||
// TODO check if changed?
|
||||
style_info.inheritable = inheritable;
|
||||
|
||||
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
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
const boost::function<void (Derived&, T)>& setter,
|
||||
bool inheritable = true )
|
||||
{
|
||||
return boost::bind(setter, instance, boost::bind(&getValue<T1>, _1));
|
||||
return addStyle<T, T>(name, type, setter, inheritable);
|
||||
}
|
||||
|
||||
template<
|
||||
typename T,
|
||||
class Derived
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Derived::*setter)(T),
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T, T>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
boost::function<void (Derived&, T)>(setter),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<
|
||||
typename T1,
|
||||
typename T2,
|
||||
class Derived
|
||||
>
|
||||
static
|
||||
StyleSetterFunc
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Derived::*setter)(T2),
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T1>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
boost::function<void (Derived&, T2)>(setter),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<
|
||||
class Derived
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Derived::*setter)(const std::string&),
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<const char*, const std::string&>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
boost::function<void (Derived&, const std::string&)>(setter),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<
|
||||
typename T,
|
||||
class Derived,
|
||||
class Other,
|
||||
class OtherRef
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Other::*setter)(T),
|
||||
OtherRef Derived::*instance_ref,
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T, T>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
bindOther(setter, instance_ref),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<
|
||||
typename T1,
|
||||
typename T2,
|
||||
class Derived,
|
||||
class Other,
|
||||
class OtherRef
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Other::*setter)(T2),
|
||||
OtherRef Derived::*instance_ref,
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T1>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
bindOther(setter, instance_ref),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<
|
||||
typename T1,
|
||||
typename T2,
|
||||
class Derived,
|
||||
class Other,
|
||||
class OtherRef
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
const boost::function<void (Other&, T2)>& setter,
|
||||
OtherRef Derived::*instance_ref,
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T1>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
bindOther(setter, instance_ref),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<
|
||||
class Derived,
|
||||
class Other,
|
||||
class OtherRef
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Other::*setter)(const std::string&),
|
||||
OtherRef Derived::*instance_ref,
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<const char*, const std::string&>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
boost::function<void (Other&, const std::string&)>(setter),
|
||||
instance_ref,
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<typename T, class Derived, class Other, class OtherRef>
|
||||
static
|
||||
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>
|
||||
static
|
||||
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>
|
||||
static
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
bool isStyleEmpty(const SGPropertyNode* child) const;
|
||||
bool canApplyStyle(const SGPropertyNode* child) const;
|
||||
bool setStyleImpl( const SGPropertyNode* child,
|
||||
const StyleInfo* style_info = 0 );
|
||||
|
||||
const StyleInfo* getStyleInfo(const std::string& name) const;
|
||||
const StyleSetter* getStyleSetter(const std::string& name) const;
|
||||
const SGPropertyNode* getParentStyle(const SGPropertyNode* child) const;
|
||||
|
||||
virtual void childAdded(SGPropertyNode * child) {}
|
||||
virtual void childRemoved(SGPropertyNode * child){}
|
||||
virtual void childChanged(SGPropertyNode * child){}
|
||||
@@ -203,9 +539,63 @@ 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
|
||||
|
||||
template<>
|
||||
struct enum_traits<canvas::Element::ReferenceFrame>
|
||||
{
|
||||
static const char* name()
|
||||
{
|
||||
return "canvas::Element::ReferenceFrame";
|
||||
}
|
||||
|
||||
static canvas::Element::ReferenceFrame defVal()
|
||||
{
|
||||
return canvas::Element::GLOBAL;
|
||||
}
|
||||
|
||||
static bool validate(int frame)
|
||||
{
|
||||
return frame >= canvas::Element::GLOBAL
|
||||
&& frame <= canvas::Element::LOCAL;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* CANVAS_ELEMENT_HXX_ */
|
||||
|
||||
@@ -33,17 +33,37 @@ 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;
|
||||
ElementType::staticInit();
|
||||
factories[ElementType::TYPE_NAME] = &Element::create<ElementType>;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
ElementFactories Group::_child_factories;
|
||||
const std::string Group::TYPE_NAME = "group";
|
||||
|
||||
void warnTransformExpired(const char* member_name)
|
||||
{
|
||||
SG_LOG( SG_GENERAL,
|
||||
SG_WARN,
|
||||
"canvas::Group::" << member_name << ": Group has expired." );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Group::staticInit()
|
||||
{
|
||||
if( isInit<Group>() )
|
||||
return;
|
||||
|
||||
add<Group>(_child_factories);
|
||||
add<Image>(_child_factories);
|
||||
add<Map >(_child_factories);
|
||||
add<Path >(_child_factories);
|
||||
add<Text >(_child_factories);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -53,11 +73,7 @@ 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 >;
|
||||
staticInit();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -80,22 +96,54 @@ 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);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
ElementPtr Group::getElementById(const std::string& id)
|
||||
{
|
||||
std::vector<GroupPtr> groups;
|
||||
|
||||
BOOST_FOREACH( ChildList::value_type child, _children )
|
||||
if( !_transform.valid() )
|
||||
{
|
||||
const ElementPtr& el = child.second;
|
||||
if( el->getProps()->getStringValue("id") == id )
|
||||
warnTransformExpired("getElementById");
|
||||
return ElementPtr();
|
||||
}
|
||||
|
||||
std::vector<GroupPtr> groups;
|
||||
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||
{
|
||||
const ElementPtr& el = getChildByIndex(i);
|
||||
if( el->get<std::string>("id") == id )
|
||||
return el;
|
||||
|
||||
GroupPtr group = boost::dynamic_pointer_cast<Group>(el);
|
||||
@@ -113,11 +161,23 @@ namespace canvas
|
||||
return ElementPtr();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Group::clearEventListener()
|
||||
{
|
||||
if( !_transform.valid() )
|
||||
return warnTransformExpired("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,30 +186,32 @@ 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;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Group::setStyle(const SGPropertyNode* style)
|
||||
bool Group::setStyle( const SGPropertyNode* style,
|
||||
const StyleInfo* style_info )
|
||||
{
|
||||
// Don't propagate styles directly applicable to this group
|
||||
if( Element::setStyle(style) )
|
||||
return true;
|
||||
|
||||
if( style->getParent() != _node
|
||||
&& _style.find(style->getNameString()) != _style.end() )
|
||||
if( !canApplyStyle(style) )
|
||||
return false;
|
||||
|
||||
bool handled = false;
|
||||
BOOST_FOREACH( ChildList::value_type child, _children )
|
||||
bool handled = setStyleImpl(style, style_info);
|
||||
if( style_info->inheritable )
|
||||
{
|
||||
if( child.second->setStyle(style) )
|
||||
handled = true;
|
||||
if( !_transform.valid() )
|
||||
{
|
||||
warnTransformExpired("setStyle");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||
handled |= getChildByIndex(i)->setStyle(style, style_info);
|
||||
}
|
||||
|
||||
return handled;
|
||||
@@ -159,17 +221,23 @@ namespace canvas
|
||||
osg::BoundingBox Group::getTransformedBounds(const osg::Matrix& m) const
|
||||
{
|
||||
osg::BoundingBox bb;
|
||||
|
||||
BOOST_FOREACH( ChildList::value_type child, _children )
|
||||
if( !_transform.valid() )
|
||||
{
|
||||
if( !child.second->getMatrixTransform()->getNodeMask() )
|
||||
warnTransformExpired("getTransformedBounds");
|
||||
return bb;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||
{
|
||||
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,31 +245,42 @@ 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);
|
||||
if( !_transform.valid() )
|
||||
return warnTransformExpired("childAdded");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if( !Element::setStyle(child) )
|
||||
{
|
||||
// Only add style if not applicable to group itself
|
||||
StyleInfo const* style = getStyleInfo(child->getNameString());
|
||||
if( style && style->inheritable )
|
||||
_style[ child->getNameString() ] = child;
|
||||
setStyle(child);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -210,10 +289,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,15 +306,14 @@ 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
|
||||
{
|
||||
Style::iterator style = _style.find(node->getNameString());
|
||||
if( style != _style.end() )
|
||||
_style.erase(style);
|
||||
_style.erase( node->getNameString() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,54 +322,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 || !_transform.valid() )
|
||||
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 +377,42 @@ 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
|
||||
{
|
||||
if( !_transform.valid() )
|
||||
{
|
||||
warnTransformExpired("findChild");
|
||||
return ElementPtr();
|
||||
}
|
||||
|
||||
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,15 @@ namespace simgear
|
||||
namespace canvas
|
||||
{
|
||||
|
||||
typedef std::map<std::string, ElementFactory> ElementFactories;
|
||||
|
||||
class Group:
|
||||
public Element
|
||||
{
|
||||
public:
|
||||
static const std::string TYPE_NAME;
|
||||
static void staticInit();
|
||||
|
||||
typedef std::list< std::pair< const SGPropertyNode*,
|
||||
ElementPtr
|
||||
>
|
||||
@@ -46,6 +51,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,28 +87,36 @@ namespace canvas
|
||||
*/
|
||||
ElementPtr getElementById(const std::string& id);
|
||||
|
||||
virtual void clearEventListener();
|
||||
|
||||
virtual void update(double dt);
|
||||
|
||||
virtual bool traverse(EventVisitor& visitor);
|
||||
|
||||
virtual bool setStyle(const SGPropertyNode* child);
|
||||
virtual bool setStyle( const SGPropertyNode* child,
|
||||
const StyleInfo* style_info = 0 );
|
||||
|
||||
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
|
||||
|
||||
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,8 +78,27 @@ 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";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Image::staticInit()
|
||||
{
|
||||
if( isInit<Image>() )
|
||||
return;
|
||||
|
||||
addStyle("fill", "color", &Image::setFill);
|
||||
addStyle("slice", "", &Image::setSlice);
|
||||
addStyle("slice-width", "", &Image::setSliceWidth);
|
||||
addStyle("outset", "", &Image::setOutset);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -82,6 +112,8 @@ namespace canvas
|
||||
_src_rect(0,0),
|
||||
_region(0,0)
|
||||
{
|
||||
staticInit();
|
||||
|
||||
_geom = new osg::Geometry;
|
||||
_geom->setUseDisplayList(false);
|
||||
|
||||
@@ -91,28 +123,26 @@ 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);
|
||||
setFill("#ffffff"); // TODO how should we handle default values?
|
||||
|
||||
setupStyle();
|
||||
}
|
||||
|
||||
@@ -127,14 +157,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 +277,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 +391,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);
|
||||
|
||||
@@ -208,21 +405,80 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
void Image::setFill(const std::string& fill)
|
||||
{
|
||||
osg::Vec4 color;
|
||||
if( !parseColor(fill, color) )
|
||||
osg::Vec4 color(1,1,1,1);
|
||||
if( !fill.empty() // If no color is given default to white
|
||||
&& !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)
|
||||
{
|
||||
@@ -309,7 +565,7 @@ namespace canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
setImage( canvas->getSystemAdapter()->getImage(path) );
|
||||
setImage( Canvas::getSystemAdapter()->getImage(path) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,36 +575,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,9 @@ namespace canvas
|
||||
public Element
|
||||
{
|
||||
public:
|
||||
static const std::string TYPE_NAME;
|
||||
static void staticInit();
|
||||
|
||||
/**
|
||||
* @param node Property node containing settings for this image:
|
||||
* rect/[left/right/top/bottom] Dimensions of source
|
||||
@@ -43,11 +46,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 +59,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 +100,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 +116,10 @@ namespace canvas
|
||||
SGPropertyNode *_node_src_rect;
|
||||
SGRect<float> _src_rect,
|
||||
_region;
|
||||
|
||||
CSSBorder _slice,
|
||||
_slice_width,
|
||||
_outset;
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
|
||||
@@ -42,7 +42,20 @@ namespace simgear
|
||||
namespace canvas
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
const std::string GEO = "-geo";
|
||||
const std::string Map::TYPE_NAME = "map";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Map::staticInit()
|
||||
{
|
||||
Group::staticInit();
|
||||
|
||||
if( isInit<Map>() )
|
||||
return;
|
||||
|
||||
// Do some initialization if needed...
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Map::Map( const CanvasWeakPtr& canvas,
|
||||
@@ -54,7 +67,7 @@ namespace canvas
|
||||
_projection(new SansonFlamsteedProjection),
|
||||
_projection_dirty(true)
|
||||
{
|
||||
|
||||
staticInit();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -192,6 +205,8 @@ namespace canvas
|
||||
_projection->setOrientation(child->getFloatValue());
|
||||
else if( child->getNameString() == "range" )
|
||||
_projection->setRange(child->getDoubleValue());
|
||||
else if( child->getNameString() == "screen-range" )
|
||||
_projection->setScreenRange(child->getDoubleValue());
|
||||
else
|
||||
return Group::childChanged(child);
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ namespace canvas
|
||||
public Group
|
||||
{
|
||||
public:
|
||||
static const std::string TYPE_NAME;
|
||||
static void staticInit();
|
||||
|
||||
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>
|
||||
@@ -111,7 +110,7 @@ namespace canvas
|
||||
*/
|
||||
void setFill(const std::string& fill)
|
||||
{
|
||||
if( fill == "none" )
|
||||
if( fill.empty() || fill == "none" )
|
||||
{
|
||||
_mode &= ~VG_FILL_PATH;
|
||||
}
|
||||
@@ -151,7 +150,7 @@ namespace canvas
|
||||
*/
|
||||
void setStroke(const std::string& stroke)
|
||||
{
|
||||
if( stroke == "none" )
|
||||
if( stroke.empty() || stroke == "none" )
|
||||
{
|
||||
_mode &= ~VG_STROKE_PATH;
|
||||
}
|
||||
@@ -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,25 @@ namespace canvas
|
||||
};
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
const std::string Path::TYPE_NAME = "path";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Path::staticInit()
|
||||
{
|
||||
if( isInit<Path>() )
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Path::Path( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
@@ -475,16 +496,9 @@ namespace canvas
|
||||
Element(canvas, node, parent_style, parent),
|
||||
_path( new PathDrawable(this) )
|
||||
{
|
||||
staticInit();
|
||||
|
||||
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);
|
||||
|
||||
setupStyle();
|
||||
}
|
||||
|
||||
@@ -517,6 +531,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,9 @@ namespace canvas
|
||||
public Element
|
||||
{
|
||||
public:
|
||||
static const std::string TYPE_NAME;
|
||||
static void staticInit();
|
||||
|
||||
Path( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
const Style& parent_style,
|
||||
@@ -39,6 +43,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 +76,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 <osg/Version>
|
||||
#include <osgText/Text>
|
||||
|
||||
namespace simgear
|
||||
@@ -33,7 +34,9 @@ namespace canvas
|
||||
|
||||
TextOSG(canvas::Text* text);
|
||||
|
||||
void setFontResolution(int res);
|
||||
void setCharacterAspect(float aspect);
|
||||
void setLineHeight(float factor);
|
||||
void setFill(const std::string& fill);
|
||||
void setBackgroundColor(const std::string& fill);
|
||||
|
||||
@@ -44,13 +47,21 @@ namespace canvas
|
||||
protected:
|
||||
|
||||
canvas::Text *_text_element;
|
||||
|
||||
virtual void computePositions(unsigned int contextID) const;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Text::TextOSG::TextOSG(canvas::Text* text):
|
||||
_text_element(text)
|
||||
{
|
||||
setBackdropImplementation(NO_DEPTH_BUFFER);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Text::TextOSG::setFontResolution(int res)
|
||||
{
|
||||
TextBase::setFontResolution(res, res);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -59,6 +70,12 @@ namespace canvas
|
||||
setCharacterSize(getCharacterHeight(), aspect);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Text::TextOSG::setLineHeight(float factor)
|
||||
{
|
||||
setLineSpacing(factor - 1);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Text::TextOSG::setFill(const std::string& fill)
|
||||
{
|
||||
@@ -165,16 +182,118 @@ namespace canvas
|
||||
if( !bb.valid() )
|
||||
return bb;
|
||||
|
||||
#if 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";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Text::staticInit()
|
||||
{
|
||||
if( isInit<Text>() )
|
||||
return;
|
||||
|
||||
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("line-height", "numeric", &TextOSG::setLineHeight, 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, false);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Text::Text( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
@@ -183,28 +302,13 @@ namespace canvas
|
||||
Element(canvas, node, parent_style, parent),
|
||||
_text( new Text::TextOSG(this) )
|
||||
{
|
||||
staticInit();
|
||||
|
||||
setDrawable(_text);
|
||||
_text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
|
||||
_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);
|
||||
|
||||
setupStyle();
|
||||
}
|
||||
|
||||
@@ -223,7 +327,7 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
void Text::setFont(const char* name)
|
||||
{
|
||||
_text->setFont( _canvas.lock()->getSystemAdapter()->getFont(name) );
|
||||
_text->setFont( Canvas::getSystemAdapter()->getFont(name) );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace canvas
|
||||
public Element
|
||||
{
|
||||
public:
|
||||
static const std::string TYPE_NAME;
|
||||
static void staticInit();
|
||||
|
||||
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
|
||||
@@ -46,7 +46,7 @@
|
||||
# warning GCC compilers prior to 3.4 are suspect
|
||||
# endif
|
||||
|
||||
# define GCC_VERSION (__GNUC__ * 10000 \
|
||||
# define SG_GCC_VERSION (__GNUC__ * 10000 \
|
||||
+ __GNUC_MINOR__ * 100 \
|
||||
+ __GNUC_PATCHLEVEL__)
|
||||
# define SG_COMPILER_STR "GNU C++ version " SG_STRINGIZE(__GNUC__) "." SG_STRINGIZE(__GNUC_MINOR__)
|
||||
|
||||
@@ -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
|
||||
|
||||
98
simgear/debug/BufferedLogCallback.cxx
Normal file
98
simgear/debug/BufferedLogCallback.cxx
Normal file
@@ -0,0 +1,98 @@
|
||||
/** \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;
|
||||
vector_cstring m_buffer;
|
||||
unsigned int m_stamp;
|
||||
unsigned int m_maxLength;
|
||||
};
|
||||
|
||||
BufferedLogCallback::BufferedLogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
simgear::LogCallback(c,p),
|
||||
d(new BufferedLogCallbackPrivate)
|
||||
{
|
||||
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 (!shouldLog(c, p)) 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,422 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <iostream>
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "logstream.hxx"
|
||||
|
||||
logstream *logstream::global_logstream = 0;
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
bool logbuf::logging_enabled = true;
|
||||
#ifdef _WIN32
|
||||
bool logbuf::has_console = true;
|
||||
#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>
|
||||
|
||||
#ifdef SG_WINDOWS
|
||||
// for AllocConsole, OutputDebugString
|
||||
#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";
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
m_class(c),
|
||||
m_priority(p)
|
||||
{
|
||||
}
|
||||
|
||||
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
|
||||
{
|
||||
return ((c & m_class) != 0 && p >= m_priority);
|
||||
}
|
||||
|
||||
void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
|
||||
{
|
||||
m_priority = p;
|
||||
m_class = c;
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class FileLogCallback : public simgear::LogCallback
|
||||
{
|
||||
public:
|
||||
FileLogCallback(const std::string& aPath, sgDebugClass c, sgDebugPriority p) :
|
||||
simgear::LogCallback(c, p),
|
||||
m_file(aPath.c_str(), std::ios_base::out | std::ios_base::trunc)
|
||||
{
|
||||
logstream::initGlobalLogstream();
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& message)
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
m_file << debugClassToString(c) << ":" << (int) p
|
||||
<< ":" << file << ":" << line << ":" << message << std::endl;
|
||||
}
|
||||
private:
|
||||
std::ofstream m_file;
|
||||
};
|
||||
|
||||
class StderrLogCallback : public simgear::LogCallback
|
||||
{
|
||||
public:
|
||||
StderrLogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
simgear::LogCallback(c, p)
|
||||
{
|
||||
#ifdef SG_WINDOWS
|
||||
AllocConsole(); // but only if we want a console
|
||||
freopen("conin$", "r", stdin);
|
||||
freopen("conout$", "w", stdout);
|
||||
freopen("conout$", "w", stderr);
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
static ignore_me im;
|
||||
}
|
||||
|
||||
logbuf::logbuf()
|
||||
|
||||
#ifdef SG_WINDOWS
|
||||
|
||||
class WinDebugLogCallback : public simgear::LogCallback
|
||||
{
|
||||
// if ( sbuf == NULL )
|
||||
// sbuf = cerr.rdbuf();
|
||||
}
|
||||
public:
|
||||
WinDebugLogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
simgear::LogCallback(c, p)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
|
||||
std::ostringstream os;
|
||||
os << debugClassToString(c) << ":" << aMessage << std::endl;
|
||||
OutputDebugStringA(os.str().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
logbuf::~logbuf()
|
||||
#endif
|
||||
|
||||
class LogStreamPrivate : public SGThread
|
||||
{
|
||||
if ( sbuf )
|
||||
sync();
|
||||
}
|
||||
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_consoleRequested(false)
|
||||
{
|
||||
|
||||
void
|
||||
logbuf::set_sb( streambuf* sb )
|
||||
#if !defined(SG_WINDOWS)
|
||||
m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
|
||||
m_consoleCallbacks.push_back(m_callbacks.back());
|
||||
m_consoleRequested = true;
|
||||
#endif
|
||||
|
||||
#if defined (SG_WINDOWS) && !defined(NDEBUG)
|
||||
m_callbacks.push_back(new WinDebugLogCallback(m_logClass, m_logPriority));
|
||||
m_consoleCallbacks.push_back(m_callbacks.back());
|
||||
#endif
|
||||
}
|
||||
|
||||
SGMutex m_lock;
|
||||
SGBlockingQueue<LogEntry> m_entries;
|
||||
|
||||
typedef std::vector<simgear::LogCallback*> CallbackVec;
|
||||
CallbackVec m_callbacks;
|
||||
/// subset of callbacks which correspond to stdout / console,
|
||||
/// and hence should dynamically reflect console logging settings
|
||||
CallbackVec m_consoleCallbacks;
|
||||
|
||||
sgDebugClass m_logClass;
|
||||
sgDebugPriority m_logPriority;
|
||||
bool m_isRunning;
|
||||
bool m_consoleRequested;
|
||||
|
||||
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;
|
||||
BOOST_FOREACH(simgear::LogCallback* cb, m_consoleCallbacks) {
|
||||
cb->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);
|
||||
}
|
||||
|
||||
void requestConsole()
|
||||
{
|
||||
PauseThread pause(this);
|
||||
if (m_consoleRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_consoleRequested = true;
|
||||
m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
|
||||
m_consoleCallbacks.push_back(m_callbacks.back());
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static logstream* global_logstream = NULL;
|
||||
static LogStreamPrivate* global_privateLogstream = NULL;
|
||||
|
||||
logstream::logstream()
|
||||
{
|
||||
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;
|
||||
|
||||
// http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
|
||||
// in the absence of portable memory barrier ops in Simgear,
|
||||
// let's keep this correct & safe
|
||||
static SGMutex m;
|
||||
SGGuard<SGMutex> g(m);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
void requestConsole()
|
||||
{
|
||||
sglog(); // force creation
|
||||
global_privateLogstream->requestConsole();
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
@@ -26,222 +26,49 @@
|
||||
#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
|
||||
virtual ~LogCallback() {}
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage) = 0;
|
||||
|
||||
void setLogLevels(sgDebugClass c, sgDebugPriority p);
|
||||
protected:
|
||||
LogCallback(sgDebugClass c, sgDebugPriority p);
|
||||
|
||||
/** sync/flush */
|
||||
inline virtual int sync();
|
||||
|
||||
/** overflow */
|
||||
int_type overflow( int ch );
|
||||
// int xsputn( const char* s, istreamsize n );
|
||||
|
||||
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
|
||||
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;
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Helper force a console on platforms where it might optional, when
|
||||
* we need to show a console. This basically means Windows at the
|
||||
* moment - on other plaforms it's a no-op
|
||||
*/
|
||||
struct logstream_base
|
||||
{
|
||||
// logstream_base( streambuf* sb ) : lbuf(sb) {}
|
||||
logstream_base() {}
|
||||
|
||||
logbuf lbuf;
|
||||
};
|
||||
void requestConsole();
|
||||
|
||||
} // 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,41 +76,49 @@ 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;
|
||||
}
|
||||
logstream& sglog();
|
||||
|
||||
/**
|
||||
* \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();
|
||||
}
|
||||
|
||||
|
||||
/** \def SG_LOG(C,P,M)
|
||||
@@ -296,10 +131,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__)
|
||||
|
||||
@@ -629,7 +629,7 @@ bool SGMetar::scanWeather()
|
||||
weather = pre + weather + post;
|
||||
weather.erase(weather.length() - 1);
|
||||
_weather.push_back(weather);
|
||||
if( w.phenomena.size() > 0 )
|
||||
if( ! w.phenomena.empty() )
|
||||
_weather2.push_back( w );
|
||||
_grpcount++;
|
||||
return true;
|
||||
|
||||
@@ -42,7 +42,6 @@ private:
|
||||
float _rain_intensity;
|
||||
float _clip_distance;
|
||||
|
||||
int _wind_dir;
|
||||
osg::Vec3 _wind_vec;
|
||||
|
||||
osg::ref_ptr<osgParticle::PrecipitationEffect> _precipitationEffect;
|
||||
|
||||
@@ -15,7 +15,14 @@ set(HEADERS
|
||||
sg_socket.hxx
|
||||
sg_socket_udp.hxx
|
||||
HTTPClient.hxx
|
||||
HTTPFileRequest.hxx
|
||||
HTTPMemoryRequest.hxx
|
||||
HTTPRequest.hxx
|
||||
HTTPContentDecode.hxx
|
||||
DAVMultiStatus.hxx
|
||||
SVNRepository.hxx
|
||||
SVNDirectory.hxx
|
||||
SVNReportParser.hxx
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
@@ -31,22 +38,22 @@ set(SOURCES
|
||||
sg_socket.cxx
|
||||
sg_socket_udp.cxx
|
||||
HTTPClient.cxx
|
||||
HTTPFileRequest.cxx
|
||||
HTTPMemoryRequest.cxx
|
||||
HTTPRequest.cxx
|
||||
HTTPContentDecode.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 +73,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)
|
||||
|
||||
402
simgear/io/DAVMultiStatus.cxx
Normal file
402
simgear/io/DAVMultiStatus.cxx
Normal file
@@ -0,0 +1,402 @@
|
||||
// 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <simgear_config.h>
|
||||
#endif
|
||||
|
||||
#include "DAVMultiStatus.hxx"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "simgear/debug/logstream.hxx"
|
||||
#include "simgear/misc/strutils.hxx"
|
||||
#include "simgear/structure/exception.hxx"
|
||||
|
||||
#ifdef SYSTEM_EXPAT
|
||||
# include <expat.h>
|
||||
#else
|
||||
# include "sg_expat.h"
|
||||
#endif
|
||||
|
||||
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());
|
||||
if (strutils::ends_with(href, "/")) {
|
||||
_url = href.substr(0, _url.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
valid(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;
|
||||
bool valid;
|
||||
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;
|
||||
_d->valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DAVMultiStatus::finishParse()
|
||||
{
|
||||
if (_d->parserInited) {
|
||||
if (!XML_Parse(_d->xmlParser, NULL, 0, true)) {
|
||||
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));
|
||||
_d->valid = false;
|
||||
} else {
|
||||
_d->valid = true;
|
||||
}
|
||||
XML_ParserFree(_d->xmlParser);
|
||||
}
|
||||
|
||||
_d->parserInited = false;
|
||||
}
|
||||
|
||||
DAVResource* DAVMultiStatus::resource()
|
||||
{
|
||||
return _d->rootResource;
|
||||
}
|
||||
|
||||
bool DAVMultiStatus::isValid() const
|
||||
{
|
||||
return _d->valid;
|
||||
}
|
||||
|
||||
|
||||
143
simgear/io/DAVMultiStatus.hxx
Normal file
143
simgear/io/DAVMultiStatus.hxx
Normal file
@@ -0,0 +1,143 @@
|
||||
// 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();
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
DAVResource* resource();
|
||||
|
||||
class DAVMultiStatusPrivate;
|
||||
private:
|
||||
std::auto_ptr<DAVMultiStatusPrivate> _d;
|
||||
};
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_IO_DAVMULTISTATUS_HXX
|
||||
@@ -1,22 +1,47 @@
|
||||
/**
|
||||
* \file HTTPClient.cxx - simple HTTP client engine for SimHear
|
||||
*/
|
||||
|
||||
// Written by James Turner
|
||||
//
|
||||
// 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 "HTTPClient.hxx"
|
||||
#include "HTTPFileRequest.hxx"
|
||||
|
||||
#include <sstream>
|
||||
#include <cassert>
|
||||
#include <cstdlib> // rand()
|
||||
#include <list>
|
||||
#include <iostream>
|
||||
#include <errno.h>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include <simgear/io/sg_netChat.hxx>
|
||||
#include <simgear/io/lowlevel.hxx>
|
||||
#include <simgear/io/HTTPContentDecode.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
|
||||
#include "version.h"
|
||||
@@ -26,10 +51,6 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using std::vector;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
@@ -39,19 +60,31 @@ namespace HTTP
|
||||
extern const int DEFAULT_HTTP_PORT = 80;
|
||||
const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
|
||||
const unsigned int MAX_INFLIGHT_REQUESTS = 32;
|
||||
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
|
||||
const int ZLIB_INFLATE_WINDOW_BITS = -MAX_WBITS;
|
||||
|
||||
// see http://www.ietf.org/rfc/rfc1952.txt for these values and
|
||||
// detailed description of the logic
|
||||
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 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;
|
||||
|
||||
class Connection;
|
||||
typedef std::multimap<std::string, Connection*> ConnectionDict;
|
||||
typedef std::list<Request_ptr> RequestList;
|
||||
|
||||
class Client::ClientPrivate
|
||||
{
|
||||
public:
|
||||
std::string userAgent;
|
||||
std::string proxy;
|
||||
int proxyPort;
|
||||
std::string proxyAuth;
|
||||
NetChannelPoller poller;
|
||||
unsigned int maxConnections;
|
||||
|
||||
RequestList pendingRequests;
|
||||
|
||||
// connections by host (potentially more than one)
|
||||
ConnectionDict connections;
|
||||
|
||||
SGTimeStamp timeTransferSample;
|
||||
unsigned int bytesTransferred;
|
||||
unsigned int lastTransferRate;
|
||||
uint64_t totalBytesDownloaded;
|
||||
};
|
||||
|
||||
class Connection : public NetChat
|
||||
{
|
||||
@@ -59,26 +92,28 @@ public:
|
||||
Connection(Client* pr) :
|
||||
client(pr),
|
||||
state(STATE_CLOSED),
|
||||
port(DEFAULT_HTTP_PORT),
|
||||
zlibInflateBuffer(NULL),
|
||||
zlibInflateBufferSize(0),
|
||||
zlibOutputBuffer(NULL)
|
||||
port(DEFAULT_HTTP_PORT)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
virtual ~Connection()
|
||||
{
|
||||
if (zlibInflateBuffer) {
|
||||
free(zlibInflateBuffer);
|
||||
}
|
||||
|
||||
if (zlibOutputBuffer) {
|
||||
free(zlibOutputBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void handleBufferRead (NetBuffer& buffer)
|
||||
{
|
||||
if( !activeRequest || !activeRequest->isComplete() )
|
||||
return NetChat::handleBufferRead(buffer);
|
||||
|
||||
// Request should be aborted (signaled by setting its state to complete).
|
||||
|
||||
// force the state to GETTING_BODY, to simplify logic in
|
||||
// responseComplete and handleClose
|
||||
state = STATE_GETTING_BODY;
|
||||
responseComplete();
|
||||
}
|
||||
|
||||
void setServer(const string& h, short p)
|
||||
void setServer(const std::string& h, short p)
|
||||
{
|
||||
host = h;
|
||||
port = p;
|
||||
@@ -109,6 +144,7 @@ public:
|
||||
SG_LOG(SG_IO, SG_INFO, "HTTP socket error");
|
||||
activeRequest->setFailure(error, "socket error");
|
||||
activeRequest = NULL;
|
||||
_contentDecoder.reset();
|
||||
}
|
||||
|
||||
state = STATE_SOCKET_ERROR;
|
||||
@@ -117,13 +153,28 @@ 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;
|
||||
_contentDecoder.reset();
|
||||
}
|
||||
|
||||
state = STATE_CLOSED;
|
||||
}
|
||||
|
||||
@@ -138,18 +189,37 @@ public:
|
||||
sentRequests.clear();
|
||||
}
|
||||
|
||||
void handleTimeout()
|
||||
{
|
||||
NetChat::handleError(ETIMEDOUT);
|
||||
if (activeRequest) {
|
||||
SG_LOG(SG_IO, SG_DEBUG, "HTTP socket timeout");
|
||||
activeRequest->setFailure(ETIMEDOUT, "socket timeout");
|
||||
activeRequest = NULL;
|
||||
_contentDecoder.reset();
|
||||
}
|
||||
|
||||
state = STATE_SOCKET_ERROR;
|
||||
}
|
||||
|
||||
void queueRequest(const Request_ptr& r)
|
||||
{
|
||||
queuedRequests.push_back(r);
|
||||
tryStartNextRequest();
|
||||
queuedRequests.push_back(r);
|
||||
tryStartNextRequest();
|
||||
}
|
||||
|
||||
void beginResponse()
|
||||
{
|
||||
assert(!sentRequests.empty());
|
||||
|
||||
activeRequest = sentRequests.front();
|
||||
activeRequest->responseStart(buffer);
|
||||
assert(!sentRequests.empty());
|
||||
assert(state == STATE_WAITING_FOR_RESPONSE);
|
||||
|
||||
activeRequest = sentRequests.front();
|
||||
try {
|
||||
activeRequest->responseStart(buffer);
|
||||
} catch (sg_exception& e) {
|
||||
handleError(EIO);
|
||||
}
|
||||
|
||||
state = STATE_GETTING_HEADERS;
|
||||
buffer.clear();
|
||||
if (activeRequest->responseCode() == 204) {
|
||||
@@ -162,11 +232,15 @@ public:
|
||||
|
||||
bodyTransferSize = -1;
|
||||
chunkedTransfer = false;
|
||||
contentGZip = contentDeflate = false;
|
||||
_contentDecoder.reset();
|
||||
}
|
||||
|
||||
void tryStartNextRequest()
|
||||
{
|
||||
while( !queuedRequests.empty()
|
||||
&& queuedRequests.front()->isComplete() )
|
||||
queuedRequests.pop_front();
|
||||
|
||||
if (queuedRequests.empty()) {
|
||||
idleTime.stamp();
|
||||
return;
|
||||
@@ -186,28 +260,29 @@ public:
|
||||
}
|
||||
|
||||
Request_ptr r = queuedRequests.front();
|
||||
requestBodyBytesToSend = r->requestBodyLength();
|
||||
|
||||
stringstream headerData;
|
||||
string path = r->path();
|
||||
r->requestStart();
|
||||
|
||||
std::stringstream headerData;
|
||||
std::string path = r->path();
|
||||
assert(!path.empty());
|
||||
string query = r->query();
|
||||
string bodyData;
|
||||
std::string query = r->query();
|
||||
std::string bodyData;
|
||||
|
||||
if (!client->proxyHost().empty()) {
|
||||
path = r->scheme() + "://" + r->host() + r->path();
|
||||
}
|
||||
|
||||
if (r->requestBodyType() == CONTENT_TYPE_URL_ENCODED) {
|
||||
if (r->bodyType() == CONTENT_TYPE_URL_ENCODED) {
|
||||
headerData << r->method() << " " << path << " HTTP/1.1\r\n";
|
||||
bodyData = query.substr(1); // URL-encode, drop the leading '?'
|
||||
headerData << "Content-Type:" << CONTENT_TYPE_URL_ENCODED << "\r\n";
|
||||
headerData << "Content-Length:" << bodyData.size() << "\r\n";
|
||||
} else {
|
||||
headerData << r->method() << " " << path << query << " HTTP/1.1\r\n";
|
||||
if (requestBodyBytesToSend >= 0) {
|
||||
headerData << "Content-Length:" << requestBodyBytesToSend << "\r\n";
|
||||
headerData << "Content-Type:" << r->requestBodyType() << "\r\n";
|
||||
if( r->hasBodyData() )
|
||||
{
|
||||
headerData << "Content-Length:" << r->bodyLength() << "\r\n";
|
||||
headerData << "Content-Type:" << r->bodyType() << "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,8 +293,8 @@ public:
|
||||
headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
|
||||
}
|
||||
|
||||
BOOST_FOREACH(string h, r->requestHeaders()) {
|
||||
headerData << h << ": " << r->header(h) << "\r\n";
|
||||
BOOST_FOREACH(const StringMap::value_type& h, r->requestHeaders()) {
|
||||
headerData << h.first << ": " << h.second << "\r\n";
|
||||
}
|
||||
|
||||
headerData << "\r\n"; // final CRLF to terminate the headers
|
||||
@@ -229,163 +304,67 @@ 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;
|
||||
}
|
||||
|
||||
while (requestBodyBytesToSend > 0) {
|
||||
char buf[4096];
|
||||
int len = 4096;
|
||||
r->getBodyData(buf, len);
|
||||
if (len > 0) {
|
||||
requestBodyBytesToSend -= len;
|
||||
if (!bufferSend(buf, len)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "overflow the HTTP::Connection output buffer");
|
||||
state = STATE_SOCKET_ERROR;
|
||||
return;
|
||||
|
||||
if( r->hasBodyData() )
|
||||
for(size_t body_bytes_sent = 0; body_bytes_sent < r->bodyLength();)
|
||||
{
|
||||
char buf[4096];
|
||||
size_t len = r->getBodyData(buf, body_bytes_sent, 4096);
|
||||
if( len )
|
||||
{
|
||||
if( !bufferSend(buf, len) )
|
||||
{
|
||||
SG_LOG(SG_IO,
|
||||
SG_WARN,
|
||||
"overflow the HTTP::Connection output buffer");
|
||||
state = STATE_SOCKET_ERROR;
|
||||
return;
|
||||
}
|
||||
body_bytes_sent += len;
|
||||
}
|
||||
else
|
||||
{
|
||||
SG_LOG(SG_IO,
|
||||
SG_WARN,
|
||||
"HTTP asynchronous request body generation is unsupported");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "asynchronous request body generation is unsupported");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//std::cout << "did send request:" << r->url() << std::endl;
|
||||
// successfully sent, remove from queue, and maybe send the next
|
||||
// SG_LOG(SG_IO, SG_INFO, "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);
|
||||
|
||||
// pipelining, let's maybe send the next request right away
|
||||
state = STATE_WAITING_FOR_RESPONSE;
|
||||
|
||||
// pipelining, let's maybe send the next request right away
|
||||
tryStartNextRequest();
|
||||
}
|
||||
|
||||
virtual void collectIncomingData(const char* s, int n)
|
||||
{
|
||||
idleTime.stamp();
|
||||
if ((state == STATE_GETTING_BODY) || (state == STATE_GETTING_CHUNKED_BYTES)) {
|
||||
if (contentGZip || contentDeflate) {
|
||||
expandCompressedData(s, n);
|
||||
} else {
|
||||
activeRequest->processBodyBytes(s, n);
|
||||
}
|
||||
} else {
|
||||
buffer += string(s, n);
|
||||
}
|
||||
client->receivedBytes(static_cast<unsigned int>(n));
|
||||
|
||||
if( (state == STATE_GETTING_BODY)
|
||||
|| (state == STATE_GETTING_CHUNKED_BYTES) )
|
||||
_contentDecoder.receivedBytes(s, n);
|
||||
else
|
||||
buffer.append(s, n);
|
||||
}
|
||||
|
||||
|
||||
void expandCompressedData(const char* s, int n)
|
||||
{
|
||||
int reqSize = n + zlib.avail_in;
|
||||
if (reqSize > zlibInflateBufferSize) {
|
||||
// reallocate
|
||||
unsigned char* newBuf = (unsigned char*) malloc(reqSize);
|
||||
memcpy(newBuf, zlib.next_in, zlib.avail_in);
|
||||
memcpy(newBuf + zlib.avail_in, s, n);
|
||||
free(zlibInflateBuffer);
|
||||
zlibInflateBuffer = newBuf;
|
||||
zlibInflateBufferSize = reqSize;
|
||||
} else {
|
||||
// important to use memmove here, since it's very likely
|
||||
// the source and destination ranges overlap
|
||||
memmove(zlibInflateBuffer, zlib.next_in, zlib.avail_in);
|
||||
memcpy(zlibInflateBuffer + zlib.avail_in, s, n);
|
||||
}
|
||||
|
||||
zlib.next_in = (unsigned char*) zlibInflateBuffer;
|
||||
zlib.avail_in = reqSize;
|
||||
zlib.next_out = zlibOutputBuffer;
|
||||
zlib.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
|
||||
if (contentGZip) {
|
||||
// we clear this down to contentDeflate once the GZip header has been seen
|
||||
if (reqSize < GZIP_HEADER_SIZE) {
|
||||
return; // 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
|
||||
}
|
||||
|
||||
char flags = zlibInflateBuffer[3];
|
||||
int gzipHeaderSize = GZIP_HEADER_SIZE;
|
||||
if (flags & GZIP_HEADER_FEXTRA) {
|
||||
gzipHeaderSize += 2;
|
||||
if (reqSize < gzipHeaderSize) {
|
||||
return; // need more header bytes
|
||||
}
|
||||
|
||||
unsigned short extraHeaderBytes = *(reinterpret_cast<unsigned short*>(zlibInflateBuffer + GZIP_HEADER_FEXTRA));
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( &extraHeaderBytes );
|
||||
}
|
||||
|
||||
gzipHeaderSize += extraHeaderBytes;
|
||||
if (reqSize < gzipHeaderSize) {
|
||||
return; // need more header bytes
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & GZIP_HEADER_FNAME) {
|
||||
gzipHeaderSize++;
|
||||
while (gzipHeaderSize <= reqSize) {
|
||||
if (zlibInflateBuffer[gzipHeaderSize-1] == 0) {
|
||||
break; // found terminating NULL character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & GZIP_HEADER_COMMENT) {
|
||||
gzipHeaderSize++;
|
||||
while (gzipHeaderSize <= reqSize) {
|
||||
if (zlibInflateBuffer[gzipHeaderSize-1] == 0) {
|
||||
break; // found terminating NULL character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & GZIP_HEADER_CRC) {
|
||||
gzipHeaderSize += 2;
|
||||
}
|
||||
|
||||
if (reqSize < gzipHeaderSize) {
|
||||
return; // need more header bytes
|
||||
}
|
||||
|
||||
zlib.next_in += gzipHeaderSize;
|
||||
zlib.avail_in = reqSize - 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);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void foundTerminator(void)
|
||||
{
|
||||
idleTime.stamp();
|
||||
switch (state) {
|
||||
case STATE_IDLE:
|
||||
case STATE_WAITING_FOR_RESPONSE:
|
||||
beginResponse();
|
||||
break;
|
||||
|
||||
@@ -405,6 +384,7 @@ public:
|
||||
case STATE_GETTING_CHUNKED_BYTES:
|
||||
setTerminator("\r\n");
|
||||
state = STATE_GETTING_CHUNKED;
|
||||
buffer.clear();
|
||||
break;
|
||||
|
||||
|
||||
@@ -413,6 +393,9 @@ public:
|
||||
buffer.clear();
|
||||
break;
|
||||
|
||||
case STATE_IDLE:
|
||||
SG_LOG(SG_IO, SG_WARN, "HTTP got data in IDLE state, bad server?");
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -424,6 +407,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(sentRequests.empty());
|
||||
return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
|
||||
}
|
||||
|
||||
@@ -445,6 +429,11 @@ public:
|
||||
{
|
||||
return !queuedRequests.empty() && (sentRequests.size() < MAX_INFLIGHT_REQUESTS);
|
||||
}
|
||||
|
||||
bool isActive() const
|
||||
{
|
||||
return !queuedRequests.empty() || !sentRequests.empty();
|
||||
}
|
||||
private:
|
||||
bool connectToHost()
|
||||
{
|
||||
@@ -465,37 +454,9 @@ private:
|
||||
|
||||
void processHeader()
|
||||
{
|
||||
string h = strutils::simplify(buffer);
|
||||
std::string h = strutils::simplify(buffer);
|
||||
if (h.empty()) { // blank line terminates headers
|
||||
headersComplete();
|
||||
|
||||
if (contentGZip || contentDeflate) {
|
||||
memset(&zlib, 0, sizeof(z_stream));
|
||||
if (!zlibOutputBuffer) {
|
||||
zlibOutputBuffer = (unsigned char*) malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
// NULLs means we'll get default alloc+free methods
|
||||
// which is absolutely fine
|
||||
zlib.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
zlib.next_out = zlibOutputBuffer;
|
||||
if (inflateInit2(&zlib, ZLIB_INFLATE_WINDOW_BITS) != Z_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (chunkedTransfer) {
|
||||
state = STATE_GETTING_CHUNKED;
|
||||
} else if (noMessageBody || (bodyTransferSize == 0)) {
|
||||
// force the state to GETTING_BODY, to simplify logic in
|
||||
// responseComplete and handleClose
|
||||
state = STATE_GETTING_BODY;
|
||||
responseComplete();
|
||||
} else {
|
||||
setByteCount(bodyTransferSize); // may be -1, that's fine
|
||||
state = STATE_GETTING_BODY;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -505,9 +466,9 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
string key = strutils::simplify(buffer.substr(0, colonPos));
|
||||
string lkey = boost::to_lower_copy(key);
|
||||
string value = strutils::strip(buffer.substr(colonPos + 1));
|
||||
std::string key = strutils::simplify(buffer.substr(0, colonPos));
|
||||
std::string lkey = boost::to_lower_copy(key);
|
||||
std::string value = strutils::strip(buffer.substr(colonPos + 1));
|
||||
|
||||
// only consider these if getting headers (as opposed to trailers
|
||||
// of a chunked transfer)
|
||||
@@ -524,20 +485,14 @@ private:
|
||||
} else if (lkey == "transfer-encoding") {
|
||||
processTransferEncoding(value);
|
||||
} else if (lkey == "content-encoding") {
|
||||
if (value == "gzip") {
|
||||
contentGZip = true;
|
||||
} else if (value == "deflate") {
|
||||
contentDeflate = true;
|
||||
} else if (value != "identity") {
|
||||
SG_LOG(SG_IO, SG_WARN, "unsupported content encoding:" << value);
|
||||
}
|
||||
_contentDecoder.setEncoding(value);
|
||||
}
|
||||
}
|
||||
|
||||
activeRequest->responseHeader(lkey, value);
|
||||
}
|
||||
|
||||
void processTransferEncoding(const string& te)
|
||||
void processTransferEncoding(const std::string& te)
|
||||
{
|
||||
if (te == "chunked") {
|
||||
chunkedTransfer = true;
|
||||
@@ -553,7 +508,7 @@ private:
|
||||
// blank line after chunk data
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int chunkSize = 0;
|
||||
int semiPos = buffer.find(';');
|
||||
if (semiPos >= 0) {
|
||||
@@ -588,17 +543,25 @@ private:
|
||||
void headersComplete()
|
||||
{
|
||||
activeRequest->responseHeadersComplete();
|
||||
_contentDecoder.initWithRequest(activeRequest);
|
||||
|
||||
if (chunkedTransfer) {
|
||||
state = STATE_GETTING_CHUNKED;
|
||||
} else if (noMessageBody || (bodyTransferSize == 0)) {
|
||||
// force the state to GETTING_BODY, to simplify logic in
|
||||
// responseComplete and handleClose
|
||||
state = STATE_GETTING_BODY;
|
||||
responseComplete();
|
||||
} else {
|
||||
setByteCount(bodyTransferSize); // may be -1, that's fine
|
||||
state = STATE_GETTING_BODY;
|
||||
}
|
||||
}
|
||||
|
||||
void responseComplete()
|
||||
{
|
||||
//std::cout << "responseComplete:" << activeRequest->url() << std::endl;
|
||||
activeRequest->responseComplete();
|
||||
client->requestFinished(this);
|
||||
|
||||
if (contentDeflate) {
|
||||
inflateEnd(&zlib);
|
||||
}
|
||||
Request_ptr completedRequest = activeRequest;
|
||||
_contentDecoder.finish();
|
||||
|
||||
assert(sentRequests.front() == activeRequest);
|
||||
sentRequests.pop_front();
|
||||
@@ -609,21 +572,29 @@ private:
|
||||
if (doClose) {
|
||||
// this will bring us into handleClose() above, which updates
|
||||
// state to STATE_CLOSED
|
||||
close();
|
||||
close();
|
||||
|
||||
// if we have additional requests waiting, try to start them now
|
||||
tryStartNextRequest();
|
||||
}
|
||||
tryStartNextRequest();
|
||||
}
|
||||
}
|
||||
|
||||
if (state != STATE_CLOSED) {
|
||||
state = STATE_IDLE;
|
||||
}
|
||||
setTerminator("\r\n");
|
||||
if (state != STATE_CLOSED) {
|
||||
state = sentRequests.empty() ? STATE_IDLE : STATE_WAITING_FOR_RESPONSE;
|
||||
}
|
||||
|
||||
// notify request after we change state, so this connection is idle
|
||||
// if completion triggers other requests (which is likely)
|
||||
// SG_LOG(SG_IO, SG_INFO, "*** responseComplete:" << activeRequest->url());
|
||||
completedRequest->responseComplete();
|
||||
client->requestFinished(this);
|
||||
|
||||
setTerminator("\r\n");
|
||||
}
|
||||
|
||||
enum ConnectionState {
|
||||
STATE_IDLE = 0,
|
||||
STATE_WAITING_FOR_RESPONSE,
|
||||
STATE_GETTING_HEADERS,
|
||||
STATE_GETTING_BODY,
|
||||
STATE_GETTING_CHUNKED,
|
||||
@@ -636,74 +607,187 @@ private:
|
||||
Client* client;
|
||||
Request_ptr activeRequest;
|
||||
ConnectionState state;
|
||||
string host;
|
||||
std::string host;
|
||||
short port;
|
||||
std::string buffer;
|
||||
int bodyTransferSize;
|
||||
SGTimeStamp idleTime;
|
||||
bool chunkedTransfer;
|
||||
bool noMessageBody;
|
||||
int requestBodyBytesToSend;
|
||||
|
||||
z_stream zlib;
|
||||
unsigned char* zlibInflateBuffer;
|
||||
int zlibInflateBufferSize;
|
||||
unsigned char* zlibOutputBuffer;
|
||||
bool contentGZip, contentDeflate;
|
||||
|
||||
std::list<Request_ptr> queuedRequests;
|
||||
std::list<Request_ptr> sentRequests;
|
||||
|
||||
RequestList queuedRequests;
|
||||
RequestList sentRequests;
|
||||
|
||||
ContentDecoder _contentDecoder;
|
||||
};
|
||||
|
||||
Client::Client()
|
||||
Client::Client() :
|
||||
d(new ClientPrivate)
|
||||
{
|
||||
d->proxyPort = 0;
|
||||
d->maxConnections = 4;
|
||||
d->bytesTransferred = 0;
|
||||
d->lastTransferRate = 0;
|
||||
d->timeTransferSample.stamp();
|
||||
d->totalBytesDownloaded = 0;
|
||||
|
||||
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
|
||||
}
|
||||
|
||||
Client::~Client()
|
||||
{
|
||||
}
|
||||
|
||||
void Client::setMaxConnections(unsigned int maxCon)
|
||||
{
|
||||
if (maxCon < 1) {
|
||||
throw sg_range_exception("illegal HTTP::Client::setMaxConnections value");
|
||||
}
|
||||
|
||||
d->maxConnections = maxCon;
|
||||
}
|
||||
|
||||
void Client::update(int waitTimeout)
|
||||
{
|
||||
NetChannel::poll(waitTimeout);
|
||||
|
||||
ConnectionDict::iterator it = _connections.begin();
|
||||
for (; it != _connections.end(); ) {
|
||||
if (it->second->hasIdleTimeout() || it->second->hasError() ||
|
||||
it->second->hasErrorTimeout())
|
||||
if (!d->poller.hasChannels() && (waitTimeout > 0)) {
|
||||
SGTimeStamp::sleepForMSec(waitTimeout);
|
||||
} else {
|
||||
d->poller.poll(waitTimeout);
|
||||
}
|
||||
|
||||
bool waitingRequests = !d->pendingRequests.empty();
|
||||
ConnectionDict::iterator it = d->connections.begin();
|
||||
for (; it != d->connections.end(); ) {
|
||||
Connection* con = it->second;
|
||||
if (con->hasIdleTimeout() ||
|
||||
con->hasError() ||
|
||||
con->hasErrorTimeout() ||
|
||||
(!con->isActive() && waitingRequests))
|
||||
{
|
||||
if (con->hasErrorTimeout()) {
|
||||
// tell the connection we're timing it out
|
||||
con->handleTimeout();
|
||||
}
|
||||
|
||||
// connection has been idle for a while, clean it up
|
||||
// (or has an error condition, again clean it up)
|
||||
// (or if we have requests waiting for a different host,
|
||||
// or an error condition
|
||||
ConnectionDict::iterator del = it++;
|
||||
delete del->second;
|
||||
_connections.erase(del);
|
||||
d->connections.erase(del);
|
||||
} else {
|
||||
if (it->second->shouldStartNext()) {
|
||||
it->second->tryStartNextRequest();
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
} // of connecion iteration
|
||||
} // of connection iteration
|
||||
|
||||
if (waitingRequests && (d->connections.size() < d->maxConnections)) {
|
||||
RequestList waiting(d->pendingRequests);
|
||||
d->pendingRequests.clear();
|
||||
|
||||
// re-submit all waiting requests in order; this takes care of
|
||||
// finding multiple pending items targetted to the same (new)
|
||||
// connection
|
||||
BOOST_FOREACH(Request_ptr req, waiting) {
|
||||
makeRequest(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::makeRequest(const Request_ptr& r)
|
||||
{
|
||||
string host = r->host();
|
||||
if( r->isComplete() )
|
||||
return;
|
||||
|
||||
if( r->url().find("://") == std::string::npos ) {
|
||||
r->setFailure(EINVAL, "malformed URL");
|
||||
return;
|
||||
}
|
||||
|
||||
if( r->url().find("http://") != 0 ) {
|
||||
r->setFailure(EINVAL, "only HTTP protocol is supported");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string host = r->host();
|
||||
int port = r->port();
|
||||
if (!_proxy.empty()) {
|
||||
host = _proxy;
|
||||
port = _proxyPort;
|
||||
if (!d->proxy.empty()) {
|
||||
host = d->proxy;
|
||||
port = d->proxyPort;
|
||||
}
|
||||
|
||||
stringstream ss;
|
||||
Connection* con = NULL;
|
||||
std::stringstream ss;
|
||||
ss << host << "-" << port;
|
||||
string connectionId = ss.str();
|
||||
|
||||
if (_connections.find(connectionId) == _connections.end()) {
|
||||
Connection* con = new Connection(this);
|
||||
con->setServer(host, port);
|
||||
_connections[connectionId] = con;
|
||||
std::string connectionId = ss.str();
|
||||
bool havePending = !d->pendingRequests.empty();
|
||||
bool atConnectionsLimit = d->connections.size() >= d->maxConnections;
|
||||
ConnectionDict::iterator consEnd = d->connections.end();
|
||||
|
||||
// assign request to an existing Connection.
|
||||
// various options exist here, examined in order
|
||||
ConnectionDict::iterator it = d->connections.find(connectionId);
|
||||
if (atConnectionsLimit && (it == consEnd)) {
|
||||
// maximum number of connections active, queue this request
|
||||
// when a connection goes inactive, we'll start this one
|
||||
d->pendingRequests.push_back(r);
|
||||
return;
|
||||
}
|
||||
|
||||
_connections[connectionId]->queueRequest(r);
|
||||
// scan for an idle Connection to the same host (likely if we're
|
||||
// retrieving multiple resources from the same host in quick succession)
|
||||
// if we have pending requests (waiting for a free Connection), then
|
||||
// force new requests on this id to always use the first Connection
|
||||
// (instead of the random selection below). This ensures that when
|
||||
// there's pressure on the number of connections to keep alive, one
|
||||
// host can't DoS every other.
|
||||
int count = 0;
|
||||
for (; (it != consEnd) && (it->first == connectionId); ++it, ++count) {
|
||||
if (havePending || !it->second->isActive()) {
|
||||
con = it->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!con && atConnectionsLimit) {
|
||||
// all current connections are busy (active), and we don't
|
||||
// have free connections to allocate, so let's assign to
|
||||
// an existing one randomly. Ideally we'd used whichever one will
|
||||
// complete first but we don't have that info.
|
||||
int index = rand() % count;
|
||||
for (it = d->connections.find(connectionId); index > 0; --index) { ; }
|
||||
con = it->second;
|
||||
}
|
||||
|
||||
// allocate a new connection object
|
||||
if (!con) {
|
||||
con = new Connection(this);
|
||||
con->setServer(host, port);
|
||||
d->poller.addChannel(con);
|
||||
d->connections.insert(d->connections.end(),
|
||||
ConnectionDict::value_type(connectionId, con));
|
||||
}
|
||||
|
||||
con->queueRequest(r);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
FileRequestRef Client::save( const std::string& url,
|
||||
const std::string& filename )
|
||||
{
|
||||
FileRequestRef req = new FileRequest(url, filename);
|
||||
makeRequest(req);
|
||||
return req;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
MemoryRequestRef Client::load(const std::string& url)
|
||||
{
|
||||
MemoryRequestRef req = new MemoryRequest(url);
|
||||
makeRequest(req);
|
||||
return req;
|
||||
}
|
||||
|
||||
void Client::requestFinished(Connection* con)
|
||||
@@ -711,16 +795,80 @@ void Client::requestFinished(Connection* con)
|
||||
|
||||
}
|
||||
|
||||
void Client::setUserAgent(const string& ua)
|
||||
void Client::setUserAgent(const std::string& ua)
|
||||
{
|
||||
_userAgent = ua;
|
||||
d->userAgent = ua;
|
||||
}
|
||||
|
||||
void Client::setProxy(const string& proxy, int port, const string& auth)
|
||||
const std::string& Client::userAgent() const
|
||||
{
|
||||
_proxy = proxy;
|
||||
_proxyPort = port;
|
||||
_proxyAuth = auth;
|
||||
return d->userAgent;
|
||||
}
|
||||
|
||||
const std::string& Client::proxyHost() const
|
||||
{
|
||||
return d->proxy;
|
||||
}
|
||||
|
||||
const std::string& Client::proxyAuth() const
|
||||
{
|
||||
return d->proxyAuth;
|
||||
}
|
||||
|
||||
void Client::setProxy( const std::string& proxy,
|
||||
int port,
|
||||
const std::string& auth )
|
||||
{
|
||||
d->proxy = proxy;
|
||||
d->proxyPort = port;
|
||||
d->proxyAuth = auth;
|
||||
}
|
||||
|
||||
bool Client::hasActiveRequests() const
|
||||
{
|
||||
ConnectionDict::const_iterator it = d->connections.begin();
|
||||
for (; it != d->connections.end(); ++it) {
|
||||
if (it->second->isActive()) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Client::receivedBytes(unsigned int count)
|
||||
{
|
||||
d->bytesTransferred += count;
|
||||
d->totalBytesDownloaded += count;
|
||||
}
|
||||
|
||||
unsigned int Client::transferRateBytesPerSec() const
|
||||
{
|
||||
unsigned int e = d->timeTransferSample.elapsedMSec();
|
||||
if (e > 400) {
|
||||
// too long a window, ignore
|
||||
d->timeTransferSample.stamp();
|
||||
d->bytesTransferred = 0;
|
||||
d->lastTransferRate = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (e < 100) { // avoid really narrow windows
|
||||
return d->lastTransferRate;
|
||||
}
|
||||
|
||||
unsigned int ratio = (d->bytesTransferred * 1000) / e;
|
||||
// run a low-pass filter
|
||||
unsigned int smoothed = ((400 - e) * d->lastTransferRate) + (e * ratio);
|
||||
smoothed /= 400;
|
||||
|
||||
d->timeTransferSample.stamp();
|
||||
d->bytesTransferred = 0;
|
||||
d->lastTransferRate = smoothed;
|
||||
return smoothed;
|
||||
}
|
||||
|
||||
uint64_t Client::totalBytesDownloaded() const
|
||||
{
|
||||
return d->totalBytesDownloaded;
|
||||
}
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
/**
|
||||
* \file HTTPClient.hxx - simple HTTP client engine for SimHear
|
||||
*/
|
||||
|
||||
// Written by James Turner
|
||||
//
|
||||
// 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_HTTP_CLIENT_HXX
|
||||
#define SG_HTTP_CLIENT_HXX
|
||||
|
||||
#include <map>
|
||||
#include <memory> // for std::auto_ptr
|
||||
#include <stdint.h> // for uint_64t
|
||||
|
||||
#include <simgear/io/HTTPRequest.hxx>
|
||||
#include <simgear/io/HTTPFileRequest.hxx>
|
||||
#include <simgear/io/HTTPMemoryRequest.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
@@ -11,42 +36,78 @@ namespace simgear
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
// forward decls
|
||||
class Connection;
|
||||
|
||||
|
||||
class Client
|
||||
{
|
||||
public:
|
||||
Client();
|
||||
~Client();
|
||||
|
||||
void update(int waitTimeout = 0);
|
||||
|
||||
void makeRequest(const Request_ptr& r);
|
||||
|
||||
|
||||
/**
|
||||
* Download a resource and save it to a file.
|
||||
*
|
||||
* @param url The resource to download
|
||||
* @param filename Path to the target file
|
||||
* @param data Data for POST request
|
||||
*/
|
||||
FileRequestRef save( const std::string& url,
|
||||
const std::string& filename );
|
||||
|
||||
/**
|
||||
* Request a resource and keep it in memory.
|
||||
*
|
||||
* @param url The resource to download
|
||||
*/
|
||||
MemoryRequestRef load(const std::string& url);
|
||||
|
||||
void setUserAgent(const std::string& ua);
|
||||
void setProxy(const std::string& proxy, int port, const std::string& auth = "");
|
||||
|
||||
const std::string& userAgent() const
|
||||
{ return _userAgent; }
|
||||
/**
|
||||
* Specify the maximum permitted simultaneous connections
|
||||
* (default value is 1)
|
||||
*/
|
||||
void setMaxConnections(unsigned int maxCons);
|
||||
|
||||
const std::string& userAgent() const;
|
||||
|
||||
const std::string& proxyHost() const
|
||||
{ return _proxy; }
|
||||
const std::string& proxyHost() const;
|
||||
|
||||
const std::string& proxyAuth() const
|
||||
{ return _proxyAuth; }
|
||||
const std::string& proxyAuth() const;
|
||||
|
||||
/**
|
||||
* predicate, check if at least one connection is active, with at
|
||||
* least one request active or queued.
|
||||
*/
|
||||
bool hasActiveRequests() const;
|
||||
|
||||
/**
|
||||
* crude tracking of bytes-per-second transferred over the socket.
|
||||
* suitable for user feedback and rough profiling, nothing more.
|
||||
*/
|
||||
unsigned int transferRateBytesPerSec() const;
|
||||
|
||||
/**
|
||||
* total bytes downloaded by this HTTP client, for bandwidth usage
|
||||
* monitoring
|
||||
*/
|
||||
uint64_t totalBytesDownloaded() const;
|
||||
private:
|
||||
void requestFinished(Connection* con);
|
||||
|
||||
void receivedBytes(unsigned int count);
|
||||
|
||||
friend class Connection;
|
||||
friend class Request;
|
||||
|
||||
std::string _userAgent;
|
||||
std::string _proxy;
|
||||
int _proxyPort;
|
||||
std::string _proxyAuth;
|
||||
|
||||
// connections by host
|
||||
typedef std::map<std::string, Connection*> ConnectionDict;
|
||||
ConnectionDict _connections;
|
||||
class ClientPrivate;
|
||||
std::auto_ptr<ClientPrivate> d;
|
||||
};
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
269
simgear/io/HTTPContentDecode.cxx
Normal file
269
simgear/io/HTTPContentDecode.cxx
Normal file
@@ -0,0 +1,269 @@
|
||||
// Written by James Turner
|
||||
//
|
||||
// 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 "HTTPContentDecode.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib> // rand()
|
||||
#include <cstring> // for memset, memcpy
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/io/lowlevel.hxx> // for sgEndian stuff
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
|
||||
const int ZLIB_INFLATE_WINDOW_BITS = -MAX_WBITS;
|
||||
|
||||
// see http://www.ietf.org/rfc/rfc1952.txt for these values and
|
||||
// detailed description of the logic
|
||||
const int GZIP_HEADER_ID1 = 31;
|
||||
const int GZIP_HEADER_ID2 = 139;
|
||||
const int GZIP_HEADER_METHOD_DEFLATE = 8;
|
||||
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;
|
||||
|
||||
ContentDecoder::ContentDecoder() :
|
||||
_output(NULL),
|
||||
_zlib(NULL),
|
||||
_input(NULL),
|
||||
_inputAllocated(0),
|
||||
_inputSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
ContentDecoder::~ContentDecoder()
|
||||
{
|
||||
free(_output);
|
||||
free(_input);
|
||||
free(_zlib);
|
||||
}
|
||||
|
||||
void ContentDecoder::setEncoding(const std::string& encoding)
|
||||
{
|
||||
if (encoding == "gzip") {
|
||||
_contentDeflate = true;
|
||||
_needGZipHeader = true;
|
||||
} else if (encoding == "deflate") {
|
||||
_contentDeflate = true;
|
||||
_needGZipHeader = false;
|
||||
} else if (encoding != "identity") {
|
||||
SG_LOG(SG_IO, SG_WARN, "unsupported content encoding:" << encoding);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentDecoder::reset()
|
||||
{
|
||||
_request = NULL;
|
||||
_contentDeflate = false;
|
||||
_needGZipHeader = false;
|
||||
_inputSize = 0;
|
||||
}
|
||||
|
||||
void ContentDecoder::initWithRequest(Request_ptr req)
|
||||
{
|
||||
_request = req;
|
||||
if (!_contentDeflate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_zlib) {
|
||||
_zlib = (z_stream*) malloc(sizeof(z_stream));
|
||||
}
|
||||
|
||||
memset(_zlib, 0, sizeof(z_stream));
|
||||
if (!_output) {
|
||||
_output = (unsigned char*) malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
_inputSize = 0;
|
||||
// NULLs means we'll get default alloc+free methods
|
||||
// which is absolutely fine
|
||||
_zlib->avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
_zlib->next_out = _output;
|
||||
if (inflateInit2(_zlib, ZLIB_INFLATE_WINDOW_BITS) != Z_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
|
||||
}
|
||||
}
|
||||
|
||||
void ContentDecoder::finish()
|
||||
{
|
||||
if (_contentDeflate) {
|
||||
runDecoder();
|
||||
inflateEnd(_zlib);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentDecoder::receivedBytes(const char* n, size_t s)
|
||||
{
|
||||
if (!_contentDeflate) {
|
||||
_request->processBodyBytes(n, s);
|
||||
return;
|
||||
}
|
||||
|
||||
// allocate more space if needed (this will only happen rarely once the
|
||||
// buffer has hit something proportionate to the server's compression
|
||||
// window size)
|
||||
size_t requiredSize = _inputSize + s;
|
||||
if (requiredSize > _inputAllocated) {
|
||||
reallocateInputBuffer(requiredSize);
|
||||
}
|
||||
|
||||
// copy newly recieved bytes into the buffer
|
||||
memcpy(_input + _inputSize, n, s);
|
||||
_inputSize += s;
|
||||
|
||||
if (_needGZipHeader && !consumeGZipHeader()) {
|
||||
// still waiting on the full GZIP header, so done
|
||||
return;
|
||||
}
|
||||
|
||||
runDecoder();
|
||||
}
|
||||
|
||||
void ContentDecoder::consumeBytes(size_t consumed)
|
||||
{
|
||||
assert(_inputSize >= consumed);
|
||||
// move existing (consumed) bytes down
|
||||
if (consumed > 0) {
|
||||
size_t newSize = _inputSize - consumed;
|
||||
memmove(_input, _input + consumed, newSize);
|
||||
_inputSize = newSize;
|
||||
}
|
||||
}
|
||||
|
||||
void ContentDecoder::reallocateInputBuffer(size_t newSize)
|
||||
{
|
||||
_input = (unsigned char*) realloc(_input, newSize);
|
||||
_inputAllocated = newSize;
|
||||
}
|
||||
|
||||
void ContentDecoder::runDecoder()
|
||||
{
|
||||
_zlib->next_in = (unsigned char*) _input;
|
||||
_zlib->avail_in = _inputSize;
|
||||
int writtenSize;
|
||||
|
||||
// loop, running zlib() inflate and sending output bytes to
|
||||
// our request body handler. Keep calling inflate until no bytes are
|
||||
// written, and ZLIB has consumed all available input
|
||||
do {
|
||||
_zlib->next_out = _output;
|
||||
_zlib->avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
int result = inflate(_zlib, Z_NO_FLUSH);
|
||||
if (result == Z_OK || result == Z_STREAM_END) {
|
||||
// nothing to do
|
||||
} else if (result == Z_BUF_ERROR) {
|
||||
// transient error, fall through
|
||||
} else {
|
||||
// _error = result;
|
||||
return;
|
||||
}
|
||||
|
||||
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - _zlib->avail_out;
|
||||
if (writtenSize > 0) {
|
||||
_request->processBodyBytes((char*) _output, writtenSize);
|
||||
}
|
||||
|
||||
if (result == Z_STREAM_END) {
|
||||
break;
|
||||
}
|
||||
} while ((_zlib->avail_in > 0) || (writtenSize > 0));
|
||||
|
||||
// update input buffers based on what we consumed
|
||||
consumeBytes(_inputSize - _zlib->avail_in);
|
||||
}
|
||||
|
||||
bool ContentDecoder::consumeGZipHeader()
|
||||
{
|
||||
size_t avail = _inputSize;
|
||||
if (avail < GZIP_HEADER_SIZE) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
|
||||
if ((_input[0] != GZIP_HEADER_ID1) ||
|
||||
(_input[1] != GZIP_HEADER_ID2) ||
|
||||
(_input[2] != GZIP_HEADER_METHOD_DEFLATE))
|
||||
{
|
||||
return false; // invalid GZip header
|
||||
}
|
||||
|
||||
char flags = _input[3];
|
||||
unsigned int gzipHeaderSize = GZIP_HEADER_SIZE;
|
||||
if (flags & GZIP_HEADER_FEXTRA) {
|
||||
gzipHeaderSize += 2;
|
||||
if (avail < gzipHeaderSize) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
|
||||
unsigned short extraHeaderBytes = *(reinterpret_cast<unsigned short*>(_input + GZIP_HEADER_FEXTRA));
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( &extraHeaderBytes );
|
||||
}
|
||||
|
||||
gzipHeaderSize += extraHeaderBytes;
|
||||
if (avail < gzipHeaderSize) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (flags & GZIP_HEADER_FNAME) {
|
||||
gzipHeaderSize++;
|
||||
while (gzipHeaderSize <= avail) {
|
||||
if (_input[gzipHeaderSize-1] == 0) {
|
||||
break; // found terminating NULL character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & GZIP_HEADER_COMMENT) {
|
||||
gzipHeaderSize++;
|
||||
while (gzipHeaderSize <= avail) {
|
||||
if (_input[gzipHeaderSize-1] == 0) {
|
||||
break; // found terminating NULL character
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (flags & GZIP_HEADER_CRC) {
|
||||
gzipHeaderSize += 2;
|
||||
}
|
||||
|
||||
if (avail < gzipHeaderSize) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
|
||||
consumeBytes(gzipHeaderSize);
|
||||
_needGZipHeader = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
72
simgear/io/HTTPContentDecode.hxx
Normal file
72
simgear/io/HTTPContentDecode.hxx
Normal file
@@ -0,0 +1,72 @@
|
||||
// Written by James Turner
|
||||
//
|
||||
// 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_HTTP_CONTENT_DECODER_HXX
|
||||
#define SG_HTTP_CONTENT_DECODER_HXX
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include <simgear/io/HTTPRequest.hxx>
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
class ContentDecoder
|
||||
{
|
||||
public:
|
||||
ContentDecoder();
|
||||
~ContentDecoder();
|
||||
|
||||
void reset();
|
||||
|
||||
void initWithRequest(Request_ptr req);
|
||||
|
||||
void finish();
|
||||
|
||||
void setEncoding(const std::string& encoding);
|
||||
|
||||
void receivedBytes(const char* n, size_t s);
|
||||
|
||||
private:
|
||||
bool consumeGZipHeader();
|
||||
void runDecoder();
|
||||
|
||||
void consumeBytes(size_t consumed);
|
||||
void reallocateInputBuffer(size_t newSize);
|
||||
|
||||
Request_ptr _request;
|
||||
unsigned char* _output;
|
||||
|
||||
z_stream* _zlib;
|
||||
unsigned char* _input;
|
||||
size_t _inputAllocated, _inputSize;
|
||||
bool _contentDeflate, _needGZipHeader;
|
||||
};
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_HTTP_CONTENT_DECODER_HXX
|
||||
91
simgear/io/HTTPFileRequest.cxx
Normal file
91
simgear/io/HTTPFileRequest.cxx
Normal file
@@ -0,0 +1,91 @@
|
||||
// HTTP request writing response to a file.
|
||||
//
|
||||
// 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 "HTTPFileRequest.hxx"
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
FileRequest::FileRequest(const std::string& url, const std::string& path):
|
||||
Request(url, "GET"),
|
||||
_filename(path)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void FileRequest::responseHeadersComplete()
|
||||
{
|
||||
Request::responseHeadersComplete();
|
||||
|
||||
if( responseCode() != 200 )
|
||||
return setFailure(responseCode(), responseReason());
|
||||
|
||||
if( !_filename.empty() )
|
||||
{
|
||||
// TODO validate path? (would require to expose fgValidatePath somehow to
|
||||
// simgear)
|
||||
SGPath path(_filename);
|
||||
path.create_dir(0755);
|
||||
|
||||
_file.open(_filename.c_str(), std::ios::binary | std::ios::trunc);
|
||||
}
|
||||
|
||||
if( !_file )
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_IO,
|
||||
SG_WARN,
|
||||
"HTTP::FileRequest: failed to open file '" << _filename << "'"
|
||||
);
|
||||
|
||||
abort("Failed to open file.");
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void FileRequest::gotBodyData(const char* s, int n)
|
||||
{
|
||||
if( !_file )
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_IO,
|
||||
SG_DEBUG,
|
||||
"HTTP::FileRequest: received data for closed file '" << _filename << "'"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_file.write(s, n);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void FileRequest::onAlways()
|
||||
{
|
||||
_file.close();
|
||||
}
|
||||
|
||||
} // namespace HTTP
|
||||
} // namespace simgear
|
||||
56
simgear/io/HTTPFileRequest.hxx
Normal file
56
simgear/io/HTTPFileRequest.hxx
Normal file
@@ -0,0 +1,56 @@
|
||||
///@file HTTP request writing response to a file.
|
||||
//
|
||||
// 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_HTTP_FILEREQUEST_HXX_
|
||||
#define SG_HTTP_FILEREQUEST_HXX_
|
||||
|
||||
#include "HTTPRequest.hxx"
|
||||
#include <fstream>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
class FileRequest:
|
||||
public Request
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url Adress to download from
|
||||
* @param path Path to file for saving response
|
||||
*/
|
||||
FileRequest(const std::string& url, const std::string& path);
|
||||
|
||||
protected:
|
||||
std::string _filename;
|
||||
std::ofstream _file;
|
||||
|
||||
virtual void responseHeadersComplete();
|
||||
virtual void gotBodyData(const char* s, int n);
|
||||
virtual void onAlways();
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<FileRequest> FileRequestRef;
|
||||
|
||||
} // namespace HTTP
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_HTTP_FILEREQUEST_HXX_ */
|
||||
55
simgear/io/HTTPMemoryRequest.cxx
Normal file
55
simgear/io/HTTPMemoryRequest.cxx
Normal file
@@ -0,0 +1,55 @@
|
||||
// HTTP request keeping response in memory.
|
||||
//
|
||||
// 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 "HTTPMemoryRequest.hxx"
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
MemoryRequest::MemoryRequest(const std::string& url):
|
||||
Request(url, "GET")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
const std::string& MemoryRequest::responseBody() const
|
||||
{
|
||||
return _response;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void MemoryRequest::responseHeadersComplete()
|
||||
{
|
||||
Request::responseHeadersComplete();
|
||||
|
||||
if( responseLength() )
|
||||
_response.reserve( responseLength() );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void MemoryRequest::gotBodyData(const char* s, int n)
|
||||
{
|
||||
_response.append(s, n);
|
||||
}
|
||||
|
||||
} // namespace HTTP
|
||||
} // namespace simgear
|
||||
58
simgear/io/HTTPMemoryRequest.hxx
Normal file
58
simgear/io/HTTPMemoryRequest.hxx
Normal file
@@ -0,0 +1,58 @@
|
||||
///@file HTTP request keeping response in memory.
|
||||
//
|
||||
// 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_HTTP_MEMORYREQUEST_HXX_
|
||||
#define SG_HTTP_MEMORYREQUEST_HXX_
|
||||
|
||||
#include "HTTPRequest.hxx"
|
||||
#include <fstream>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
class MemoryRequest:
|
||||
public Request
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url Adress to download from
|
||||
*/
|
||||
MemoryRequest(const std::string& url);
|
||||
|
||||
/**
|
||||
* Body contents of server response.
|
||||
*/
|
||||
const std::string& responseBody() const;
|
||||
|
||||
protected:
|
||||
std::string _response;
|
||||
|
||||
virtual void responseHeadersComplete();
|
||||
virtual void gotBodyData(const char* s, int n);
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<MemoryRequest> MemoryRequestRef;
|
||||
|
||||
} // namespace HTTP
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_HTTP_MEMORYREQUEST_HXX_ */
|
||||
@@ -1,99 +1,190 @@
|
||||
#include "HTTPRequest.hxx"
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
using std::string;
|
||||
using std::map;
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
extern const int DEFAULT_HTTP_PORT;
|
||||
|
||||
Request::Request(const string& url, const string method) :
|
||||
_method(method),
|
||||
_url(url),
|
||||
_responseVersion(HTTP_VERSION_UNKNOWN),
|
||||
_responseStatus(0),
|
||||
_responseLength(0),
|
||||
_receivedBodyBytes(0),
|
||||
_willClose(false)
|
||||
//------------------------------------------------------------------------------
|
||||
Request::Request(const std::string& url, const std::string method):
|
||||
_method(method),
|
||||
_url(url),
|
||||
_responseVersion(HTTP_VERSION_UNKNOWN),
|
||||
_responseStatus(0),
|
||||
_responseLength(0),
|
||||
_receivedBodyBytes(0),
|
||||
_ready_state(UNSENT),
|
||||
_willClose(false)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
Request::~Request()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Request::setUrl(const string& url)
|
||||
//------------------------------------------------------------------------------
|
||||
Request* Request::done(const Callback& cb)
|
||||
{
|
||||
_url = url;
|
||||
if( _ready_state == DONE )
|
||||
cb(this);
|
||||
else
|
||||
_cb_done = cb;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
string_list Request::requestHeaders() const
|
||||
//------------------------------------------------------------------------------
|
||||
Request* Request::fail(const Callback& cb)
|
||||
{
|
||||
string_list r;
|
||||
return r;
|
||||
if( _ready_state == FAILED )
|
||||
cb(this);
|
||||
else
|
||||
_cb_fail = cb;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
string Request::header(const std::string& name) const
|
||||
//------------------------------------------------------------------------------
|
||||
Request* Request::always(const Callback& cb)
|
||||
{
|
||||
return string();
|
||||
if( isComplete() )
|
||||
cb(this);
|
||||
else
|
||||
_cb_always = cb;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void Request::responseStart(const string& r)
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setBodyData( const std::string& data,
|
||||
const std::string& type )
|
||||
{
|
||||
_request_data = data;
|
||||
_request_media_type = type;
|
||||
|
||||
if( !data.empty() && _method == "GET" )
|
||||
_method = "POST";
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Request::setBodyData(const SGPropertyNode* data)
|
||||
{
|
||||
if( !data )
|
||||
setBodyData("");
|
||||
|
||||
std::stringstream buf;
|
||||
writeProperties(buf, data, true);
|
||||
|
||||
setBodyData(buf.str(), "application/xml");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setUrl(const std::string& url)
|
||||
{
|
||||
_url = url;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::requestStart()
|
||||
{
|
||||
setReadyState(OPENED);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
Request::HTTPVersion decodeHTTPVersion(const std::string& v)
|
||||
{
|
||||
if( v == "HTTP/1.1" ) return Request::HTTP_1_1;
|
||||
if( v == "HTTP/1.0" ) return Request::HTTP_1_0;
|
||||
if( strutils::starts_with(v, "HTTP/0.") ) return Request::HTTP_0_x;
|
||||
return Request::HTTP_VERSION_UNKNOWN;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::responseStart(const std::string& r)
|
||||
{
|
||||
const int maxSplit = 2; // HTTP/1.1 nnn reason-string
|
||||
string_list parts = strutils::split(r, NULL, maxSplit);
|
||||
if (parts.size() != 3) {
|
||||
SG_LOG(SG_IO, SG_WARN, "HTTP::Request: malformed response start:" << r);
|
||||
setFailure(400, "malformed HTTP response header");
|
||||
return;
|
||||
throw sg_io_exception("bad HTTP response");
|
||||
}
|
||||
|
||||
_responseVersion = decodeVersion(parts[0]);
|
||||
_responseVersion = decodeHTTPVersion(parts[0]);
|
||||
_responseStatus = strutils::to_int(parts[1]);
|
||||
_responseReason = parts[2];
|
||||
}
|
||||
|
||||
void Request::responseHeader(const string& key, const string& value)
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::responseHeader(const std::string& key, const std::string& value)
|
||||
{
|
||||
if (key == "connection") {
|
||||
_willClose = (value.find("close") != string::npos);
|
||||
}
|
||||
|
||||
_responseHeaders[key] = value;
|
||||
if( key == "connection" )
|
||||
_willClose = (value.find("close") != std::string::npos);
|
||||
|
||||
_responseHeaders[key] = value;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::responseHeadersComplete()
|
||||
{
|
||||
// no op
|
||||
}
|
||||
|
||||
void Request::processBodyBytes(const char* s, int n)
|
||||
{
|
||||
_receivedBodyBytes += n;
|
||||
gotBodyData(s, n);
|
||||
}
|
||||
|
||||
void Request::gotBodyData(const char* s, int n)
|
||||
{
|
||||
|
||||
setReadyState(HEADERS_RECEIVED);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::responseComplete()
|
||||
{
|
||||
|
||||
if( !isComplete() )
|
||||
setReadyState(DONE);
|
||||
}
|
||||
|
||||
string Request::scheme() const
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::gotBodyData(const char* s, int n)
|
||||
{
|
||||
setReadyState(LOADING);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::onDone()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::onFail()
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_IO,
|
||||
SG_INFO,
|
||||
"request failed:" << url() << " : "
|
||||
<< responseCode() << "/" << responseReason()
|
||||
);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::onAlways()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::processBodyBytes(const char* s, int n)
|
||||
{
|
||||
_receivedBodyBytes += n;
|
||||
gotBodyData(s, n);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::scheme() const
|
||||
{
|
||||
int firstColon = url().find(":");
|
||||
if (firstColon > 0) {
|
||||
@@ -102,10 +193,11 @@ string Request::scheme() const
|
||||
|
||||
return ""; // couldn't parse scheme
|
||||
}
|
||||
|
||||
string Request::path() const
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::path() const
|
||||
{
|
||||
string u(url());
|
||||
std::string u(url());
|
||||
int schemeEnd = u.find("://");
|
||||
if (schemeEnd < 0) {
|
||||
return ""; // couldn't parse scheme
|
||||
@@ -127,10 +219,10 @@ string Request::path() const
|
||||
return u.substr(hostEnd, query - hostEnd);
|
||||
}
|
||||
|
||||
|
||||
string Request::query() const
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::query() const
|
||||
{
|
||||
string u(url());
|
||||
std::string u(url());
|
||||
int query = u.find('?');
|
||||
if (query < 0) {
|
||||
return ""; //no query string found
|
||||
@@ -139,105 +231,159 @@ string Request::query() const
|
||||
return u.substr(query); //includes question mark
|
||||
}
|
||||
|
||||
|
||||
|
||||
string Request::host() const
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::host() const
|
||||
{
|
||||
string hp(hostAndPort());
|
||||
int colonPos = hp.find(':');
|
||||
if (colonPos >= 0) {
|
||||
return hp.substr(0, colonPos); // trim off the colon and port
|
||||
} else {
|
||||
return hp; // no port specifier
|
||||
}
|
||||
std::string hp(hostAndPort());
|
||||
int colonPos = hp.find(':');
|
||||
if (colonPos >= 0) {
|
||||
return hp.substr(0, colonPos); // trim off the colon and port
|
||||
} else {
|
||||
return hp; // no port specifier
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
unsigned short Request::port() const
|
||||
{
|
||||
string hp(hostAndPort());
|
||||
int colonPos = hp.find(':');
|
||||
if (colonPos >= 0) {
|
||||
return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
|
||||
} else {
|
||||
return DEFAULT_HTTP_PORT;
|
||||
}
|
||||
std::string hp(hostAndPort());
|
||||
int colonPos = hp.find(':');
|
||||
if (colonPos >= 0) {
|
||||
return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
|
||||
} else {
|
||||
return DEFAULT_HTTP_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
string Request::hostAndPort() const
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::hostAndPort() const
|
||||
{
|
||||
string u(url());
|
||||
int schemeEnd = u.find("://");
|
||||
if (schemeEnd < 0) {
|
||||
return ""; // couldn't parse scheme
|
||||
}
|
||||
|
||||
int hostEnd = u.find('/', schemeEnd + 3);
|
||||
if (hostEnd < 0) { // all remainder of URL is host
|
||||
return u.substr(schemeEnd + 3);
|
||||
}
|
||||
|
||||
return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
|
||||
std::string u(url());
|
||||
int schemeEnd = u.find("://");
|
||||
if (schemeEnd < 0) {
|
||||
return ""; // couldn't parse scheme
|
||||
}
|
||||
|
||||
int hostEnd = u.find('/', schemeEnd + 3);
|
||||
if (hostEnd < 0) { // all remainder of URL is host
|
||||
return u.substr(schemeEnd + 3);
|
||||
}
|
||||
|
||||
return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setResponseLength(unsigned int l)
|
||||
{
|
||||
_responseLength = l;
|
||||
_responseLength = l;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
unsigned int Request::responseLength() const
|
||||
{
|
||||
// if the server didn't supply a content length, use the number
|
||||
// of bytes we actually received (so far)
|
||||
if ((_responseLength == 0) && (_receivedBodyBytes > 0)) {
|
||||
return _receivedBodyBytes;
|
||||
}
|
||||
|
||||
return _responseLength;
|
||||
// if the server didn't supply a content length, use the number
|
||||
// of bytes we actually received (so far)
|
||||
if( (_responseLength == 0) && (_receivedBodyBytes > 0) )
|
||||
return _receivedBodyBytes;
|
||||
|
||||
return _responseLength;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setFailure(int code, const std::string& reason)
|
||||
{
|
||||
_responseStatus = code;
|
||||
_responseReason = reason;
|
||||
failed();
|
||||
_responseStatus = code;
|
||||
_responseReason = reason;
|
||||
setReadyState(FAILED);
|
||||
}
|
||||
|
||||
void Request::failed()
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setReadyState(ReadyState state)
|
||||
{
|
||||
// no-op in base class
|
||||
SG_LOG(SG_IO, SG_INFO, "request failed:" << url());
|
||||
_ready_state = state;
|
||||
if( state == DONE )
|
||||
{
|
||||
// Finish C++ part of request to ensure everything is finished (for example
|
||||
// files and streams are closed) before calling any callback (possibly using
|
||||
// such files)
|
||||
onDone();
|
||||
onAlways();
|
||||
|
||||
if( _cb_done )
|
||||
_cb_done(this);
|
||||
}
|
||||
else if( state == FAILED )
|
||||
{
|
||||
onFail();
|
||||
onAlways();
|
||||
|
||||
if( _cb_fail )
|
||||
_cb_fail(this);
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
if( _cb_always )
|
||||
_cb_always(this);
|
||||
}
|
||||
|
||||
Request::HTTPVersion Request::decodeVersion(const string& v)
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::abort()
|
||||
{
|
||||
if (v == "HTTP/1.1") return HTTP_1_1;
|
||||
if (v == "HTTP/1.0") return HTTP_1_0;
|
||||
if (strutils::starts_with(v, "HTTP/0.")) return HTTP_0_x;
|
||||
return HTTP_VERSION_UNKNOWN;
|
||||
abort("Request aborted.");
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Request::abort(const std::string& reason)
|
||||
{
|
||||
if( isComplete() )
|
||||
return;
|
||||
|
||||
setFailure(-1, reason);
|
||||
_willClose = true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
bool Request::closeAfterComplete() const
|
||||
{
|
||||
// for non HTTP/1.1 connections, assume server closes
|
||||
return _willClose || (_responseVersion != HTTP_1_1);
|
||||
}
|
||||
|
||||
int Request::requestBodyLength() const
|
||||
{
|
||||
return -1;
|
||||
// for non HTTP/1.1 connections, assume server closes
|
||||
return _willClose || (_responseVersion != HTTP_1_1);
|
||||
}
|
||||
|
||||
std::string Request::requestBodyType() const
|
||||
//------------------------------------------------------------------------------
|
||||
bool Request::isComplete() const
|
||||
{
|
||||
return "text/plain";
|
||||
return _ready_state == DONE || _ready_state == FAILED;
|
||||
}
|
||||
|
||||
void Request::getBodyData(char*, int& count) const
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
bool Request::hasBodyData() const
|
||||
{
|
||||
count = 0;
|
||||
return;
|
||||
return !_request_media_type.empty();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::bodyType() const
|
||||
{
|
||||
return _request_media_type;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
size_t Request::bodyLength() const
|
||||
{
|
||||
return _request_data.length();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
size_t Request::getBodyData(char* s, size_t offset, size_t max_count) const
|
||||
{
|
||||
size_t bytes_available = _request_data.size() - offset;
|
||||
size_t bytes_to_read = std::min(bytes_available, max_count);
|
||||
|
||||
memcpy(s, _request_data.data() + offset, bytes_to_read);
|
||||
|
||||
return bytes_to_read;
|
||||
}
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -3,21 +3,85 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <simgear/structure/map.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
#include <simgear/math/sg_types.hxx>
|
||||
|
||||
#include <boost/function.hpp>
|
||||
|
||||
class SGPropertyNode;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
class Request : public SGReferenced
|
||||
class Request:
|
||||
public SGReferenced
|
||||
{
|
||||
public:
|
||||
typedef boost::function<void(Request*)> Callback;
|
||||
|
||||
enum ReadyState
|
||||
{
|
||||
UNSENT = 0,
|
||||
OPENED,
|
||||
HEADERS_RECEIVED,
|
||||
LOADING,
|
||||
DONE,
|
||||
FAILED
|
||||
};
|
||||
|
||||
virtual ~Request();
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
StringMap& requestHeaders() { return _request_headers; }
|
||||
const StringMap& requestHeaders() const { return _request_headers; }
|
||||
std::string& requestHeader(const std::string& key)
|
||||
{ return _request_headers[key]; }
|
||||
const std::string requestHeader(const std::string& key) const
|
||||
{ return _request_headers.get(key); }
|
||||
|
||||
/**
|
||||
* Set the handler to be called when the request successfully completes.
|
||||
*
|
||||
* @note If the request is already complete, the handler is called
|
||||
* immediately.
|
||||
*/
|
||||
Request* done(const Callback& cb);
|
||||
|
||||
/**
|
||||
* Set the handler to be called when the request completes or aborts with an
|
||||
* error.
|
||||
*
|
||||
* @note If the request has already failed, the handler is called
|
||||
* immediately.
|
||||
*/
|
||||
Request* fail(const Callback& cb);
|
||||
|
||||
/**
|
||||
* Set the handler to be called when the request either successfully
|
||||
* completes or fails.
|
||||
*
|
||||
* @note If the request is already complete or has already failed, the
|
||||
* handler is called immediately.
|
||||
*/
|
||||
Request* always(const Callback& cb);
|
||||
|
||||
/**
|
||||
* Set the data for the body of the request. The request is automatically
|
||||
* send using the POST method.
|
||||
*
|
||||
* @param data Body data
|
||||
* @param type Media Type (aka MIME) of the body data
|
||||
*/
|
||||
void setBodyData( const std::string& data,
|
||||
const std::string& type = "text/plain" );
|
||||
void setBodyData( const SGPropertyNode* data );
|
||||
|
||||
virtual void setUrl(const std::string& url);
|
||||
|
||||
virtual std::string method() const
|
||||
@@ -32,8 +96,8 @@ public:
|
||||
virtual unsigned short port() const;
|
||||
virtual std::string query() const;
|
||||
|
||||
virtual string_list requestHeaders() const;
|
||||
virtual std::string header(const std::string& name) const;
|
||||
StringMap const& responseHeaders() const
|
||||
{ return _responseHeaders; }
|
||||
|
||||
virtual int responseCode() const
|
||||
{ return _responseStatus; }
|
||||
@@ -41,26 +105,30 @@ public:
|
||||
virtual std::string responseReason() const
|
||||
{ return _responseReason; }
|
||||
|
||||
void setResponseLength(unsigned int l);
|
||||
void setResponseLength(unsigned int l);
|
||||
virtual unsigned int responseLength() const;
|
||||
|
||||
/**
|
||||
* Query the size of the request body. -1 (the default value) means no
|
||||
* request body
|
||||
* Check if request contains body data.
|
||||
*/
|
||||
virtual int requestBodyLength() const;
|
||||
virtual bool hasBodyData() const;
|
||||
|
||||
/**
|
||||
* Retrieve the request body content type.
|
||||
*/
|
||||
virtual std::string bodyType() const;
|
||||
|
||||
/**
|
||||
* Retrieve the size of the request body.
|
||||
*/
|
||||
virtual size_t bodyLength() const;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* retrieve the request body content type. Default is text/plain
|
||||
*/
|
||||
virtual std::string requestBodyType() const;
|
||||
virtual size_t getBodyData(char* s, size_t offset, size_t max_count) const;
|
||||
|
||||
/**
|
||||
* running total of body bytes received so far. Can be used
|
||||
@@ -80,43 +148,72 @@ public:
|
||||
HTTPVersion responseVersion() const
|
||||
{ return _responseVersion; }
|
||||
|
||||
static HTTPVersion decodeVersion(const std::string& v);
|
||||
|
||||
ReadyState readyState() const { return _ready_state; }
|
||||
|
||||
/**
|
||||
* Request aborting this request.
|
||||
*/
|
||||
void abort();
|
||||
|
||||
/**
|
||||
* Request aborting this request and specify the reported reaseon for it.
|
||||
*/
|
||||
void abort(const std::string& reason);
|
||||
|
||||
bool closeAfterComplete() const;
|
||||
bool isComplete() const;
|
||||
|
||||
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();
|
||||
virtual void responseComplete();
|
||||
virtual void failed();
|
||||
virtual void gotBodyData(const char* s, int n);
|
||||
|
||||
virtual void onDone();
|
||||
virtual void onFail();
|
||||
virtual void onAlways();
|
||||
|
||||
void setFailure(int code, const std::string& reason);
|
||||
|
||||
private:
|
||||
friend class Client;
|
||||
friend class Connection;
|
||||
friend class ContentDecoder;
|
||||
|
||||
void processBodyBytes(const char* s, int n);
|
||||
void setFailure(int code, const std::string& reason);
|
||||
Request(const Request&); // = delete;
|
||||
Request& operator=(const Request&); // = delete;
|
||||
|
||||
std::string _method;
|
||||
std::string _url;
|
||||
HTTPVersion _responseVersion;
|
||||
int _responseStatus;
|
||||
std::string _responseReason;
|
||||
unsigned int _responseLength;
|
||||
unsigned int _receivedBodyBytes;
|
||||
bool _willClose;
|
||||
|
||||
typedef std::map<std::string, std::string> HeaderDict;
|
||||
HeaderDict _responseHeaders;
|
||||
void processBodyBytes(const char* s, int n);
|
||||
void setReadyState(ReadyState state);
|
||||
|
||||
std::string _method;
|
||||
std::string _url;
|
||||
StringMap _request_headers;
|
||||
std::string _request_data;
|
||||
std::string _request_media_type;
|
||||
|
||||
HTTPVersion _responseVersion;
|
||||
int _responseStatus;
|
||||
std::string _responseReason;
|
||||
StringMap _responseHeaders;
|
||||
unsigned int _responseLength;
|
||||
unsigned int _receivedBodyBytes;
|
||||
|
||||
Callback _cb_done,
|
||||
_cb_fail,
|
||||
_cb_always;
|
||||
|
||||
ReadyState _ready_state;
|
||||
bool _willClose;
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<Request> Request_ptr;
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_HTTP_REQUEST_HXX
|
||||
|
||||
|
||||
384
simgear/io/SVNDirectory.cxx
Normal file
384
simgear/io/SVNDirectory.cxx
Normal file
@@ -0,0 +1,384 @@
|
||||
|
||||
#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";
|
||||
|
||||
// important: with the Google servers, setting this higher than '1' causes
|
||||
// server internal errors (500, the connection is closed). In other words we
|
||||
// can only specify update report items one level deep at most and no more.
|
||||
// (the root and its direct children, not NOT grand-children)
|
||||
const unsigned int MAX_UPDATE_REPORT_DEPTH = 1;
|
||||
|
||||
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());
|
||||
if (!file.is_open()) {
|
||||
SG_LOG(SG_IO, SG_WARN, "unable to open cache file for reading:" << p);
|
||||
return;
|
||||
}
|
||||
bool doneSelf = false;
|
||||
|
||||
file.getline(href, 1024);
|
||||
if (strcmp(CACHE_VERSION_4_TOKEN, href)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "invalid cache file [missing header token]:" << p << " '" << href << "'");
|
||||
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();
|
||||
string s = strutils::strip(versionName);
|
||||
if (!s.empty()) {
|
||||
child->setVersionName(versionName);
|
||||
}
|
||||
} // of done self test
|
||||
} // 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(string(DAV_CACHE_NAME) + ".new");
|
||||
|
||||
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
|
||||
|
||||
file.close();
|
||||
|
||||
// approximately atomic delete + rename operation
|
||||
SGPath cacheName(localPath);
|
||||
cacheName.append(DAV_CACHE_NAME);
|
||||
p.rename(cacheName);
|
||||
}
|
||||
|
||||
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);
|
||||
childCol->setVersionName(child->cachedRevision());
|
||||
_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);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
dav->removeChild(child);
|
||||
delete child;
|
||||
|
||||
writeCache();
|
||||
}
|
||||
|
||||
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) {
|
||||
SG_LOG(SG_IO, SG_INFO, localPath << "exceeded MAX_UPDATE_REPORT_DEPTH, cleaning");
|
||||
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);
|
||||
}
|
||||
108
simgear/io/SVNDirectory.hxx
Normal file
108
simgear/io/SVNDirectory.hxx
Normal file
@@ -0,0 +1,108 @@
|
||||
// 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 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
|
||||
604
simgear/io/SVNReportParser.cxx
Normal file
604
simgear/io/SVNReportParser.cxx
Normal file
@@ -0,0 +1,604 @@
|
||||
// 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <simgear_config.h>
|
||||
#endif
|
||||
|
||||
#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/easyxml.hxx"
|
||||
#include "simgear/misc/strutils.hxx"
|
||||
#include "simgear/package/md5.h"
|
||||
|
||||
#ifdef SYSTEM_EXPAT
|
||||
# include <expat.h>
|
||||
#else
|
||||
# include "sg_expat.h"
|
||||
#endif
|
||||
|
||||
#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_PROP_TAG = SVN_NS "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 = DAV_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<unsigned 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) {
|
||||
// this is inefficent, but ranges can overlap.
|
||||
while (length > 0) {
|
||||
output.push_back(output[offset++]);
|
||||
--length;
|
||||
}
|
||||
} else if (op == svn_txdelta_new) {
|
||||
output.insert(output.end(), newData, newData + length);
|
||||
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);
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "bad opcode logic");
|
||||
return false;
|
||||
}
|
||||
} // 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;
|
||||
|
||||
if (!filePath.exists()) {
|
||||
fail(SVNRepository::SVN_ERROR_FILE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
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) ||
|
||||
!strcmp(name, SVN_PROP_TAG)) {
|
||||
// don't warn on these ones
|
||||
} else {
|
||||
//SG_LOG(SG_IO, SG_WARN, "SVNReportParser: unhandled tag:" << name);
|
||||
}
|
||||
} // 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)
|
||||
{
|
||||
std::vector<unsigned char> output, decoded;
|
||||
strutils::decodeBase64(txDeltaData, decoded);
|
||||
size_t bytesToDecode = decoded.size();
|
||||
|
||||
unsigned char* p = decoded.data();
|
||||
if (memcmp(p, "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) {
|
||||
if (!SVNDeltaWindow::isWindowComplete(p, bytesToDecode)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "SVN txdelta broken window");
|
||||
return false;
|
||||
}
|
||||
|
||||
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((char*) output.data(), output.size());
|
||||
|
||||
// compute MD5 while we have the file in memory
|
||||
memset(&md5Context, 0, sizeof(SG_MD5_CTX));
|
||||
SG_MD5Init(&md5Context);
|
||||
SG_MD5Update(&md5Context, (unsigned char*) output.data(), output.size());
|
||||
SG_MD5Final(&md5Context);
|
||||
decodedFileMd5 = strutils::encodeHex(md5Context.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(currentPath);
|
||||
} else if (!strcmp(name, SVN_OPEN_FILE_TAG)) {
|
||||
finishFile(currentPath);
|
||||
} 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)) {
|
||||
currentDir->updateReportComplete();
|
||||
if (currentDir->parent()) {
|
||||
// pop the collection stack
|
||||
currentDir = currentDir->parent();
|
||||
}
|
||||
|
||||
currentPath = currentDir->fsPath();
|
||||
} else {
|
||||
// std::cout << "element:" << name;
|
||||
}
|
||||
}
|
||||
|
||||
void finishFile(const SGPath& path)
|
||||
{
|
||||
currentPath = path.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.append(s, length);
|
||||
} else if (tagStack.back() == SVN_TXDELTA_TAG) {
|
||||
txDeltaData.append(s, length);
|
||||
} else if (tagStack.back() == SVN_DAV_MD5_CHECKSUM) {
|
||||
md5Sum.append(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;
|
||||
SG_MD5_CTX md5Context;
|
||||
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
|
||||
382
simgear/io/SVNRepository.cxx
Normal file
382
simgear/io/SVNRepository.cxx
Normal file
@@ -0,0 +1,382 @@
|
||||
// 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()
|
||||
<< "\n(repository:" << p->baseUrl() << ")");
|
||||
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)
|
||||
{
|
||||
requestHeader("Depth") = "0";
|
||||
setBodyData( PROPFIND_REQUEST_BODY,
|
||||
"application/xml; charset=\"utf-8\"" );
|
||||
}
|
||||
|
||||
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);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void onDone()
|
||||
{
|
||||
if (responseCode() == 207) {
|
||||
_davStatus.finishParse();
|
||||
if (_davStatus.isValid()) {
|
||||
_repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
|
||||
} else {
|
||||
_repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void gotBodyData(const char* s, int n)
|
||||
{
|
||||
if (responseCode() != 207) {
|
||||
return;
|
||||
}
|
||||
_davStatus.parseXML(s, n);
|
||||
}
|
||||
|
||||
virtual void onFail()
|
||||
{
|
||||
HTTP::Request::onFail();
|
||||
if (_repo) {
|
||||
_repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SVNRepoPrivate* _repo;
|
||||
DAVMultiStatus _davStatus;
|
||||
};
|
||||
|
||||
class UpdateReportRequest:
|
||||
public HTTP::Request
|
||||
{
|
||||
public:
|
||||
UpdateReportRequest(SVNRepoPrivate* repo,
|
||||
const std::string& aVersionName,
|
||||
bool startEmpty) :
|
||||
HTTP::Request("", "REPORT"),
|
||||
_parser(repo->p),
|
||||
_repo(repo),
|
||||
_failed(false)
|
||||
{
|
||||
setUrl(repo->vccUrl);
|
||||
std::string 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"
|
||||
"<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>";
|
||||
|
||||
setBodyData(request, "application/xml; charset=\"utf-8\"");
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void onDone()
|
||||
{
|
||||
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() <<
|
||||
" got HTTP status " << responseCode());
|
||||
_repo->updateFailed(this, SVNRepository::SVN_ERROR_HTTP);
|
||||
_failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void gotBodyData(const char* s, int n)
|
||||
{
|
||||
if (_failed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseCode() != 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
SVNRepository::ResultCode err = _parser.parseXML(s, n);
|
||||
if (err) {
|
||||
_failed = true;
|
||||
SG_LOG(SG_IO, SG_WARN, this << ": SVN: request for:" << url() << " failed:" << err);
|
||||
_repo->updateFailed(this, err);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void onFail()
|
||||
{
|
||||
HTTP::Request::onFail();
|
||||
if (_repo) {
|
||||
_repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
private:
|
||||
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
|
||||
78
simgear/io/SVNRepository.hxx
Normal file
78
simgear/io/SVNRepository.hxx
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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,
|
||||
SVN_ERROR_FILE_NOT_FOUND,
|
||||
SVN_ERROR_HTTP
|
||||
};
|
||||
|
||||
ResultCode failure() const;
|
||||
private:
|
||||
bool isBare() const;
|
||||
|
||||
std::auto_ptr<SVNRepoPrivate> _d;
|
||||
};
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_IO_DAVMIRRORTREE_HXX
|
||||
51
simgear/io/http_svn.cxx
Normal file
51
simgear/io/http_svn.cxx
Normal file
@@ -0,0 +1,51 @@
|
||||
#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/traffic");
|
||||
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://fgfs.goneabitbursar.com/fgfsai/trunk/AI/Traffic");
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -48,35 +48,11 @@ public:
|
||||
}
|
||||
|
||||
string key = h.substr(0, colonPos);
|
||||
_headers[key] = h.substr(colonPos + 1);
|
||||
requestHeader(key) = h.substr(colonPos + 1);
|
||||
}
|
||||
|
||||
virtual string_list requestHeaders() const
|
||||
{
|
||||
string_list r;
|
||||
std::map<string, string>::const_iterator it;
|
||||
for (it = _headers.begin(); it != _headers.end(); ++it) {
|
||||
r.push_back(it->first);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
virtual string header(const string& name) const
|
||||
{
|
||||
std::map<string, string>::const_iterator it = _headers.find(name);
|
||||
if (it == _headers.end()) {
|
||||
return string();
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
protected:
|
||||
virtual void responseHeadersComplete()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void responseComplete()
|
||||
virtual void onDone()
|
||||
{
|
||||
_complete = true;
|
||||
}
|
||||
@@ -88,7 +64,6 @@ protected:
|
||||
private:
|
||||
bool _complete;
|
||||
SGFile* _file;
|
||||
std::map<string, string> _headers;
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
|
||||
@@ -454,6 +454,23 @@ bool Socket::open ( bool stream )
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SO_NOSIGPIPE
|
||||
// Do not generate SIGPIPE signal (which immediately terminates the program),
|
||||
// instead ::send() will return -1 and errno will be set to EPIPE.
|
||||
// SO_NOSIGPIPE should be available on Mac/BSD systems, but is not available
|
||||
// within Posix/Linux.
|
||||
// This only works for calls to ::send() but not for ::write():
|
||||
// http://freebsd.1045724.n5.nabble.com/is-setsockopt-SO-NOSIGPIPE-work-tp4011054p4011055.html
|
||||
int set = 1;
|
||||
setsockopt(handle, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(set));
|
||||
#endif
|
||||
|
||||
#ifndef MSG_NOSIGNAL
|
||||
# define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
// TODO supress SIGPIPE if neither SO_NOSIGPIPE nor MSG_NOSIGNAL is available
|
||||
// http://krokisplace.blogspot.co.at/2010/02/suppressing-sigpipe-in-library.html
|
||||
|
||||
return (handle != -1);
|
||||
}
|
||||
|
||||
@@ -592,7 +609,7 @@ int Socket::connect ( IPAddress* addr )
|
||||
int Socket::send (const void * buffer, int size, int flags)
|
||||
{
|
||||
assert ( handle != -1 ) ;
|
||||
return ::send (handle, (const char*)buffer, size, flags);
|
||||
return ::send (handle, (const char*)buffer, size, flags | MSG_NOSIGNAL);
|
||||
}
|
||||
|
||||
|
||||
@@ -600,8 +617,12 @@ int Socket::sendto ( const void * buffer, int size,
|
||||
int flags, const IPAddress* to )
|
||||
{
|
||||
assert ( handle != -1 ) ;
|
||||
return ::sendto(handle,(const char*)buffer,size,flags,
|
||||
(const sockaddr*) to->getAddr(), to->getAddrLen());
|
||||
return ::sendto( handle,
|
||||
(const char*)buffer,
|
||||
size,
|
||||
flags | MSG_NOSIGNAL,
|
||||
(const sockaddr*)to->getAddr(),
|
||||
to->getAddrLen() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <simgear/bucket/newbucket.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/math/SGGeometry.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "lowlevel.hxx"
|
||||
#include "sg_binobj.hxx"
|
||||
@@ -319,15 +320,18 @@ void SGBinObject::read_object( gzFile fp,
|
||||
}
|
||||
|
||||
if ( sgReadError() ) {
|
||||
cout << "We detected an error reading object properties" << endl;
|
||||
return;
|
||||
throw sg_exception("Error reading object properties");
|
||||
}
|
||||
|
||||
size_t indexCount = std::bitset<32>(idx_mask).count();
|
||||
if (indexCount == 0) {
|
||||
throw sg_exception("object index mask has no bits set");
|
||||
}
|
||||
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
if ( sgReadError() ) {
|
||||
cout << "We detected an error reading element size for :" << j << endl;
|
||||
return;
|
||||
throw sg_exception("Error reading element size");
|
||||
}
|
||||
|
||||
buf.resize( nbytes );
|
||||
@@ -335,8 +339,7 @@ void SGBinObject::read_object( gzFile fp,
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
|
||||
if ( sgReadError() ) {
|
||||
cout << "We detected an error reading object element:" << j << "bytes="<< nbytes << endl;
|
||||
return;
|
||||
throw sg_exception("Error reading element bytes");
|
||||
}
|
||||
|
||||
int_list vs;
|
||||
@@ -405,7 +408,7 @@ bool SGBinObject::read_bin( const string& file ) {
|
||||
SG_LOG( SG_EVENT, SG_ALERT,
|
||||
"ERROR: opening " << file << " or " << filegz << " for reading!");
|
||||
|
||||
return false;
|
||||
throw sg_io_exception("Error opening for reading (and .gz)", sg_location(file));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,9 +426,7 @@ bool SGBinObject::read_bin( const string& file ) {
|
||||
} else {
|
||||
// close the file before we return
|
||||
gzclose(fp);
|
||||
SG_LOG( SG_EVENT, SG_ALERT,
|
||||
"ERROR: " << file << "has bad header");
|
||||
return false;
|
||||
throw sg_io_exception("Bad BTG magic/version", sg_location(file));
|
||||
}
|
||||
|
||||
// read creation time
|
||||
@@ -462,8 +463,7 @@ bool SGBinObject::read_bin( const string& file ) {
|
||||
//cout << "Total objects to read = " << nobjects << endl;
|
||||
|
||||
if ( sgReadError() ) {
|
||||
cout << "Error while reading header of file " << file << "(.gz)" << endl;
|
||||
return false;
|
||||
throw sg_io_exception("Error reading BTG file header", sg_location(file));
|
||||
}
|
||||
|
||||
// read in objects
|
||||
@@ -613,19 +613,13 @@ bool SGBinObject::read_bin( const string& file ) {
|
||||
}
|
||||
|
||||
if ( sgReadError() ) {
|
||||
cout << "Error while reading object:" << i << " in file " << file << "(.gz)" << endl;
|
||||
return false;
|
||||
throw sg_io_exception("Error while reading object", sg_location(file, i));
|
||||
}
|
||||
}
|
||||
|
||||
// close the file
|
||||
gzclose(fp);
|
||||
|
||||
if ( sgReadError() ) {
|
||||
cout << "Error while reading file " << file << "(.gz)" << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -665,7 +659,7 @@ void SGBinObject::write_objects(gzFile fp, int type, const group_list& verts,
|
||||
if (verts.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
unsigned int start = 0, end = 1;
|
||||
string m;
|
||||
int_list emptyList;
|
||||
@@ -688,6 +682,13 @@ void SGBinObject::write_objects(gzFile fp, int type, const group_list& verts,
|
||||
if ( !normals.empty() && !normals.front().empty()) idx_mask |= SG_IDX_NORMALS;
|
||||
if ( !colors.empty() && !colors.front().empty()) idx_mask |= SG_IDX_COLORS;
|
||||
if ( !texCoords.empty() && !texCoords.front().empty()) idx_mask |= SG_IDX_TEXCOORDS;
|
||||
|
||||
if (idx_mask == 0) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "SGBinObject::write_objects: object with material:"
|
||||
<< m << "has no indices set");
|
||||
}
|
||||
|
||||
|
||||
sgWriteChar( fp, (char)SG_INDEX_TYPES ); // property
|
||||
sgWriteUInt( fp, 1 ); // nbytes
|
||||
sgWriteChar( fp, idx_mask );
|
||||
@@ -934,7 +935,7 @@ bool SGBinObject::write_ascii( const string& base, const string& name,
|
||||
fprintf(fp, "\n");
|
||||
|
||||
// dump individual triangles if they exist
|
||||
if ( tris_v.size() > 0 ) {
|
||||
if ( ! tris_v.empty() ) {
|
||||
fprintf(fp, "# triangle groups\n");
|
||||
|
||||
int start = 0;
|
||||
@@ -982,7 +983,7 @@ bool SGBinObject::write_ascii( const string& base, const string& name,
|
||||
}
|
||||
|
||||
// dump triangle groups
|
||||
if ( strips_v.size() > 0 ) {
|
||||
if ( ! strips_v.empty() ) {
|
||||
fprintf(fp, "# triangle strips\n");
|
||||
|
||||
int start = 0;
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user