Compare commits
408
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fbdd9ad2f | ||
|
|
24539968fd | ||
|
|
f13b356531 | ||
|
|
477594d7bc | ||
|
|
e955a18126 | ||
|
|
adec1c4507 | ||
|
|
172b8a032c | ||
|
|
d6c8008978 | ||
|
|
488ca0fcc5 | ||
|
|
dfd15cadab | ||
|
|
1613257bdc | ||
|
|
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 | ||
|
|
7489771ad2 |
@@ -1,4 +1,5 @@
|
|||||||
.*
|
.*
|
||||||
|
*~
|
||||||
Makefile
|
Makefile
|
||||||
*.o
|
*.o
|
||||||
lib*.a
|
lib*.a
|
||||||
|
|||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
if (NOT SYSTEM_EXPAT)
|
||||||
|
add_subdirectory(expat)
|
||||||
|
endif()
|
||||||
|
|
||||||
Vendored
+33
@@ -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()
|
||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "expat_external.h"
|
#include "sg_expat_external.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
+1
-1
@@ -23,7 +23,7 @@
|
|||||||
#endif /* ndef COMPILED_FROM_DSP */
|
#endif /* ndef COMPILED_FROM_DSP */
|
||||||
|
|
||||||
#include "ascii.h"
|
#include "ascii.h"
|
||||||
#include "expat.h"
|
#include "sg_expat.h"
|
||||||
|
|
||||||
#ifdef XML_UNICODE
|
#ifdef XML_UNICODE
|
||||||
#define XML_ENCODE_MAX XML_UTF16_ENCODE_MAX
|
#define XML_ENCODE_MAX XML_UTF16_ENCODE_MAX
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif /* ndef COMPILED_FROM_DSP */
|
#endif /* ndef COMPILED_FROM_DSP */
|
||||||
|
|
||||||
#include "expat_external.h"
|
#include "sg_expat_external.h"
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
#include "xmlrole.h"
|
#include "xmlrole.h"
|
||||||
#include "ascii.h"
|
#include "ascii.h"
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif /* ndef COMPILED_FROM_DSP */
|
#endif /* ndef COMPILED_FROM_DSP */
|
||||||
|
|
||||||
#include "expat_external.h"
|
#include "sg_expat_external.h"
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
#include "xmltok.h"
|
#include "xmltok.h"
|
||||||
#include "nametab.h"
|
#include "nametab.h"
|
||||||
+67
-38
@@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 2.6.4)
|
|||||||
include (CheckFunctionExists)
|
include (CheckFunctionExists)
|
||||||
include (CheckIncludeFile)
|
include (CheckIncludeFile)
|
||||||
include (CheckCXXSourceCompiles)
|
include (CheckCXXSourceCompiles)
|
||||||
|
include (CheckCXXCompilerFlag)
|
||||||
|
|
||||||
project(SimGear)
|
project(SimGear)
|
||||||
|
|
||||||
@@ -10,6 +10,8 @@ project(SimGear)
|
|||||||
file(READ version versionFile)
|
file(READ version versionFile)
|
||||||
string(STRIP ${versionFile} SIMGEAR_VERSION)
|
string(STRIP ${versionFile} SIMGEAR_VERSION)
|
||||||
|
|
||||||
|
set(FIND_LIBRARY_USE_LIB64_PATHS ON)
|
||||||
|
|
||||||
# use simgear version also as the SO version (if building SOs)
|
# use simgear version also as the SO version (if building SOs)
|
||||||
SET(SIMGEAR_SOVERSION ${SIMGEAR_VERSION})
|
SET(SIMGEAR_SOVERSION ${SIMGEAR_VERSION})
|
||||||
|
|
||||||
@@ -85,13 +87,6 @@ if(NOT "${CMAKE_LIBRARY_ARCHITECTURE}" STREQUAL "")
|
|||||||
message(STATUS "additional library directories: ${ADDITIONAL_LIBRARY_PATHS}")
|
message(STATUS "additional library directories: ${ADDITIONAL_LIBRARY_PATHS}")
|
||||||
endif()
|
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)
|
if (NOT MSVC)
|
||||||
@@ -106,13 +101,13 @@ endif()
|
|||||||
|
|
||||||
option(SIMGEAR_HEADLESS "Set to ON to build SimGear without GUI/graphics support" OFF)
|
option(SIMGEAR_HEADLESS "Set to ON to build SimGear without GUI/graphics support" OFF)
|
||||||
option(JPEG_FACTORY "Enable JPEG-factory 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_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_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_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)
|
if (MSVC)
|
||||||
GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_SOURCE_DIR} PATH)
|
GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_BINARY_DIR} PATH)
|
||||||
if (CMAKE_CL_64)
|
if (CMAKE_CL_64)
|
||||||
SET(TEST_3RDPARTY_DIR "${PARENT_DIR}/3rdparty.x64")
|
SET(TEST_3RDPARTY_DIR "${PARENT_DIR}/3rdparty.x64")
|
||||||
else (CMAKE_CL_64)
|
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)
|
set (OPENAL_LIBRARY_DIR ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib)
|
||||||
endif (MSVC AND MSVC_3RDPARTY_ROOT)
|
endif (MSVC AND MSVC_3RDPARTY_ROOT)
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
find_library(CORE_SERVICES_LIBRARY CoreServices)
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(Boost REQUIRED)
|
find_package(Boost REQUIRED)
|
||||||
set (BOOST_CXX_FLAGS "-DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION -DBOOST_BIMAP_DISABLE_SERIALIZATION")
|
set (BOOST_CXX_FLAGS "-DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION -DBOOST_BIMAP_DISABLE_SERIALIZATION")
|
||||||
|
|
||||||
@@ -191,23 +190,6 @@ else()
|
|||||||
message(STATUS "JPEG-factory: DISABLED")
|
message(STATUS "JPEG-factory: DISABLED")
|
||||||
endif(JPEG_FACTORY)
|
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(ZLIB REQUIRED)
|
||||||
find_package(Threads 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")
|
message(STATUS "Requested to use system Expat library, forcing SIMGEAR_SHARED to true")
|
||||||
set(SIMGEAR_SHARED ON)
|
set(SIMGEAR_SHARED ON)
|
||||||
find_package(EXPAT REQUIRED)
|
find_package(EXPAT REQUIRED)
|
||||||
include_directories(${EXPAT_INCLUDE_DIRS})
|
|
||||||
else()
|
else()
|
||||||
message(STATUS "Using built-in expat code")
|
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)
|
endif(SYSTEM_EXPAT)
|
||||||
|
|
||||||
|
include_directories(${EXPAT_INCLUDE_DIRS})
|
||||||
|
|
||||||
check_include_file(inttypes.h HAVE_INTTYPES_H)
|
check_include_file(inttypes.h HAVE_INTTYPES_H)
|
||||||
check_include_file(sys/time.h HAVE_SYS_TIME_H)
|
check_include_file(sys/time.h HAVE_SYS_TIME_H)
|
||||||
check_include_file(sys/timeb.h HAVE_SYS_TIMEB_H)
|
check_include_file(sys/timeb.h HAVE_SYS_TIMEB_H)
|
||||||
@@ -269,6 +258,20 @@ if(HAVE_CLOCK_GETTIME)
|
|||||||
endif(HAVE_RT)
|
endif(HAVE_RT)
|
||||||
endif(HAVE_CLOCK_GETTIME)
|
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_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_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
|
||||||
SET(CMAKE_RELWITHDEBINFO_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")
|
set(WARNING_FLAGS_C "-Wall")
|
||||||
endif()
|
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(WIN32)
|
||||||
|
|
||||||
if(MINGW)
|
if(MINGW)
|
||||||
@@ -314,7 +325,10 @@ if(WIN32)
|
|||||||
# SET(WARNING_FLAGS "${WARNING_FLAGS} /wd${warning}")
|
# SET(WARNING_FLAGS "${WARNING_FLAGS} /wd${warning}")
|
||||||
# endforeach(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)
|
endif(MSVC)
|
||||||
|
|
||||||
# assumed on Windows
|
# assumed on Windows
|
||||||
@@ -326,15 +340,17 @@ endif(WIN32)
|
|||||||
|
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS_C} ${MSVC_FLAGS}")
|
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_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})
|
||||||
include_directories(${PROJECT_SOURCE_DIR}/simgear/canvas/ShivaVG/include)
|
include_directories(${PROJECT_SOURCE_DIR}/simgear/canvas/ShivaVG/include)
|
||||||
include_directories(${PROJECT_BINARY_DIR}/simgear)
|
include_directories(${PROJECT_BINARY_DIR}/simgear)
|
||||||
include_directories(${PROJECT_BINARY_DIR}/simgear/xml)
|
|
||||||
|
|
||||||
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}
|
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}
|
||||||
${Boost_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR}
|
${Boost_INCLUDE_DIRS}
|
||||||
${OPENAL_INCLUDE_DIR} )
|
${ZLIB_INCLUDE_DIR}
|
||||||
|
${OPENAL_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
add_definitions(-DHAVE_CONFIG_H)
|
add_definitions(-DHAVE_CONFIG_H)
|
||||||
|
|
||||||
@@ -345,11 +361,6 @@ configure_file (
|
|||||||
"${PROJECT_BINARY_DIR}/simgear/simgear_config.h"
|
"${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)
|
if(ENABLE_TESTS)
|
||||||
# enable CTest / make test target
|
# enable CTest / make test target
|
||||||
message(STATUS "Tests: ENABLED")
|
message(STATUS "Tests: ENABLED")
|
||||||
@@ -360,7 +371,23 @@ else()
|
|||||||
message(STATUS "Tests: DISABLED")
|
message(STATUS "Tests: DISABLED")
|
||||||
endif(ENABLE_TESTS)
|
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/)
|
install (FILES ${PROJECT_BINARY_DIR}/simgear/simgear_config.h DESTINATION include/simgear/)
|
||||||
|
|
||||||
|
add_subdirectory(3rdparty)
|
||||||
add_subdirectory(simgear)
|
add_subdirectory(simgear)
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
@@ -372,3 +399,5 @@ CONFIGURE_FILE(
|
|||||||
IMMEDIATE @ONLY)
|
IMMEDIATE @ONLY)
|
||||||
ADD_CUSTOM_TARGET(uninstall
|
ADD_CUSTOM_TARGET(uninstall
|
||||||
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
|
"${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)
|
|
||||||
@@ -22,7 +22,7 @@ PROJECT_NAME = SimGear
|
|||||||
# This could be handy for archiving the generated documentation or
|
# This could be handy for archiving the generated documentation or
|
||||||
# if some version control system is used.
|
# 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)
|
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
|
||||||
# base path where the generated documentation will be put.
|
# 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
|
# If the EXTRACT_STATIC tag is set to YES all static members of a file
|
||||||
# will be included in the documentation.
|
# 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
|
# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
|
||||||
# undocumented members of documented classes, files or namespaces.
|
# undocumented members of documented classes, files or namespaces.
|
||||||
|
|||||||
+27
-31
@@ -18,6 +18,7 @@ foreach( mylibfolder
|
|||||||
threads
|
threads
|
||||||
timing
|
timing
|
||||||
xml
|
xml
|
||||||
|
package
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(${mylibfolder})
|
add_subdirectory(${mylibfolder})
|
||||||
@@ -44,52 +45,29 @@ install (FILES ${HEADERS} DESTINATION include/simgear/)
|
|||||||
get_property(coreSources GLOBAL PROPERTY CORE_SOURCES)
|
get_property(coreSources GLOBAL PROPERTY CORE_SOURCES)
|
||||||
get_property(sceneSources GLOBAL PROPERTY SCENE_SOURCES)
|
get_property(sceneSources GLOBAL PROPERTY SCENE_SOURCES)
|
||||||
get_property(publicHeaders GLOBAL PROPERTY PUBLIC_HEADERS)
|
get_property(publicHeaders GLOBAL PROPERTY PUBLIC_HEADERS)
|
||||||
|
get_property(localExpatSources GLOBAL PROPERTY LOCAL_EXPAT_SOURCES)
|
||||||
if(LIBSVN_FOUND)
|
|
||||||
add_definitions(${APR_CFLAGS})
|
|
||||||
include_directories(${LIBSVN_INCLUDE_DIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(SIMGEAR_SHARED)
|
if(SIMGEAR_SHARED)
|
||||||
message(STATUS "Library building mode: SHARED LIBRARIES")
|
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 LINKER_LANGUAGE CXX)
|
||||||
|
|
||||||
set_property(TARGET SimGearCore PROPERTY VERSION ${SIMGEAR_VERSION})
|
set_property(TARGET SimGearCore PROPERTY VERSION ${SIMGEAR_VERSION})
|
||||||
set_property(TARGET SimGearCore PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
|
set_property(TARGET SimGearCore PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
|
||||||
|
install(TARGETS SimGearCore EXPORT SimGearCoreConfig LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
target_link_libraries(SimGearCore ${ZLIB_LIBRARY} ${RT_LIBRARY}
|
install(EXPORT SimGearCoreConfig DESTINATION share/SimGearCore)
|
||||||
${EXPAT_LIBRARIES}
|
|
||||||
${CMAKE_THREAD_LIBS_INIT})
|
|
||||||
|
|
||||||
if(LIBSVN_FOUND)
|
|
||||||
target_link_libraries(SimGearCore ${LIBSVN_LIBRARIES})
|
|
||||||
endif(LIBSVN_FOUND)
|
|
||||||
|
|
||||||
if(NOT SIMGEAR_HEADLESS)
|
if(NOT SIMGEAR_HEADLESS)
|
||||||
add_library(SimGearScene SHARED ${sceneSources})
|
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 LINKER_LANGUAGE CXX)
|
||||||
set_property(TARGET SimGearScene PROPERTY VERSION ${SIMGEAR_VERSION})
|
set_property(TARGET SimGearScene PROPERTY VERSION ${SIMGEAR_VERSION})
|
||||||
set_property(TARGET SimGearScene PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
|
set_property(TARGET SimGearScene PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
|
||||||
|
|
||||||
target_link_libraries(SimGearScene
|
# EXPORT SimGearSceneConfig
|
||||||
SimGearCore
|
install(TARGETS SimGearScene LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} )
|
||||||
${ZLIB_LIBRARY}
|
# install(EXPORT SimGearSceneConfig DESTINATION share/SimGearScene)
|
||||||
${OPENSCENEGRAPH_LIBRARIES}
|
|
||||||
${OPENAL_LIBRARY}
|
|
||||||
${OPENGL_LIBRARY}
|
|
||||||
${JPEG_LIBRARY})
|
|
||||||
|
|
||||||
install(TARGETS SimGearScene LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
install(TARGETS SimGearCore LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
|
||||||
else()
|
else()
|
||||||
message(STATUS "Library building mode: STATIC LIBRARIES")
|
message(STATUS "Library building mode: STATIC LIBRARIES")
|
||||||
|
|
||||||
@@ -111,7 +89,7 @@ else()
|
|||||||
source_group("${name}\\Headers" FILES ${g2})
|
source_group("${name}\\Headers" FILES ${g2})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
add_library(SimGearCore STATIC ${coreSources})
|
add_library(SimGearCore STATIC ${coreSources} ${localExpatSources})
|
||||||
install(TARGETS SimGearCore ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
install(TARGETS SimGearCore ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
|
||||||
if(NOT SIMGEAR_HEADLESS)
|
if(NOT SIMGEAR_HEADLESS)
|
||||||
@@ -138,6 +116,24 @@ else()
|
|||||||
endif(NOT SIMGEAR_HEADLESS)
|
endif(NOT SIMGEAR_HEADLESS)
|
||||||
endif(SIMGEAR_SHARED)
|
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)
|
if(ENABLE_RTI)
|
||||||
# Ugly first aid to make hla compile agian
|
# Ugly first aid to make hla compile agian
|
||||||
set_property(SOURCE hla/RTI13InteractionClass.cxx hla/RTI13ObjectClass.cxx
|
set_property(SOURCE hla/RTI13InteractionClass.cxx hla/RTI13ObjectClass.cxx
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ std::string SGBucket::gen_base_path() const {
|
|||||||
main_lat *= -1;
|
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, top_lon, pole, top_lat,
|
||||||
hem, main_lon, pole, main_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
|
// return width of the tile in degrees
|
||||||
double SGBucket::get_width() const {
|
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() );
|
return sg_bucket_span( get_center_lat() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,4 +345,13 @@ void sgBucketDiff( const SGBucket& b1, const SGBucket& b2, int *dx, int *dy ) {
|
|||||||
#endif
|
#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 <cstdio> // sprintf()
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* standard size of a bucket in degrees (1/8 of a degree)
|
* 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
|
// return the horizontal tile span factor based on latitude
|
||||||
static double sg_bucket_span( double l ) {
|
static double sg_bucket_span( double l ) {
|
||||||
if ( l >= 89.0 ) {
|
if ( l >= 89.0 ) {
|
||||||
return 360.0;
|
return 12.0;
|
||||||
} else if ( l >= 88.0 ) {
|
|
||||||
return 8.0;
|
|
||||||
} else if ( l >= 86.0 ) {
|
} else if ( l >= 86.0 ) {
|
||||||
return 4.0;
|
return 4.0;
|
||||||
} else if ( l >= 83.0 ) {
|
} else if ( l >= 83.0 ) {
|
||||||
@@ -80,12 +79,10 @@ static double sg_bucket_span( double l ) {
|
|||||||
return 1.0;
|
return 1.0;
|
||||||
} else if ( l >= -86.0 ) {
|
} else if ( l >= -86.0 ) {
|
||||||
return 2.0;
|
return 2.0;
|
||||||
} else if ( l >= -88.0 ) {
|
|
||||||
return 4.0;
|
|
||||||
} else if ( l >= -89.0 ) {
|
} else if ( l >= -89.0 ) {
|
||||||
return 8.0;
|
return 4.0;
|
||||||
} else {
|
} 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 );
|
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.
|
* Write the bucket lon, lat, x, and y to the output stream.
|
||||||
* @param out output stream
|
* @param out output stream
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ set(HEADERS
|
|||||||
canvas_fwd.hxx
|
canvas_fwd.hxx
|
||||||
Canvas.hxx
|
Canvas.hxx
|
||||||
CanvasEvent.hxx
|
CanvasEvent.hxx
|
||||||
CanvasEventListener.hxx
|
|
||||||
CanvasEventManager.hxx
|
CanvasEventManager.hxx
|
||||||
CanvasEventTypes.hxx
|
CanvasEventTypes.hxx
|
||||||
CanvasEventVisitor.hxx
|
CanvasEventVisitor.hxx
|
||||||
CanvasMgr.hxx
|
CanvasMgr.hxx
|
||||||
|
CanvasObjectPlacement.hxx
|
||||||
CanvasPlacement.hxx
|
CanvasPlacement.hxx
|
||||||
CanvasSystemAdapter.hxx
|
CanvasSystemAdapter.hxx
|
||||||
MouseEvent.hxx
|
MouseEvent.hxx
|
||||||
@@ -19,10 +19,10 @@ set(HEADERS
|
|||||||
set(SOURCES
|
set(SOURCES
|
||||||
Canvas.cxx
|
Canvas.cxx
|
||||||
CanvasEvent.cxx
|
CanvasEvent.cxx
|
||||||
CanvasEventListener.cxx
|
|
||||||
CanvasEventManager.cxx
|
CanvasEventManager.cxx
|
||||||
CanvasEventVisitor.cxx
|
CanvasEventVisitor.cxx
|
||||||
CanvasMgr.cxx
|
CanvasMgr.cxx
|
||||||
|
CanvasObjectPlacement.cxx
|
||||||
CanvasPlacement.cxx
|
CanvasPlacement.cxx
|
||||||
ODGauge.cxx
|
ODGauge.cxx
|
||||||
VGInitOperation.cxx
|
VGInitOperation.cxx
|
||||||
|
|||||||
+149
-39
@@ -31,7 +31,6 @@
|
|||||||
|
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/foreach.hpp>
|
#include <boost/foreach.hpp>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
{
|
{
|
||||||
@@ -82,16 +81,13 @@ namespace canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void Canvas::setSystemAdapter(const SystemAdapterPtr& system_adapter)
|
void Canvas::onDestroy()
|
||||||
{
|
{
|
||||||
_system_adapter = system_adapter;
|
if( _root_group )
|
||||||
_texture.setSystemAdapter(system_adapter);
|
{
|
||||||
|
_root_group->clearEventListener();
|
||||||
|
_root_group->onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
SystemAdapterPtr Canvas::getSystemAdapter() const
|
|
||||||
{
|
|
||||||
return _system_adapter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
@@ -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() )
|
if( canvas.expired() )
|
||||||
{
|
{
|
||||||
@@ -115,28 +117,67 @@ namespace canvas
|
|||||||
(
|
(
|
||||||
SG_GENERAL,
|
SG_GENERAL,
|
||||||
SG_WARN,
|
SG_WARN,
|
||||||
"Canvas::addDependentCanvas: got an expired Canvas dependent on "
|
"Canvas::addParentCanvas(" << _node->getPath(true) << "): "
|
||||||
<< _node->getPath()
|
"got an expired parent!"
|
||||||
);
|
);
|
||||||
return;
|
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)
|
GroupPtr Canvas::createGroup(const std::string& name)
|
||||||
{
|
{
|
||||||
return boost::dynamic_pointer_cast<Group>
|
return _root_group->createChild<Group>(name);
|
||||||
(
|
}
|
||||||
_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
|
// Resizing causes a new texture to be created so we need to reapply all
|
||||||
// existing placements
|
// existing placements
|
||||||
for(size_t i = 0; i < _placements.size(); ++i)
|
reloadPlacements();
|
||||||
{
|
|
||||||
if( !_placements[i].empty() )
|
|
||||||
_dirty_placements.push_back( _placements[i].front()->getProps() );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Camera* camera = _texture.getCamera();
|
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);
|
osg::Vec4 clear_color(0.0f, 0.0f, 0.0f , 1.0f);
|
||||||
parseColor(_node->getStringValue("background"), clear_color);
|
parseColor(_node->getStringValue("background"), clear_color);
|
||||||
camera->setClearColor(clear_color);
|
camera->setClearColor(clear_color);
|
||||||
|
|
||||||
camera->addChild(_root_group->getMatrixTransform());
|
camera->addChild(_root_group->getMatrixTransform());
|
||||||
|
|
||||||
// Ensure objects are drawn in order of traversal
|
|
||||||
camera->getOrCreateStateSet()->setBinName("TraversalOrderBin");
|
|
||||||
|
|
||||||
if( _texture.serviceable() )
|
if( _texture.serviceable() )
|
||||||
{
|
{
|
||||||
setStatusFlags(STATUS_OK);
|
setStatusFlags(STATUS_OK);
|
||||||
@@ -201,10 +239,18 @@ namespace canvas
|
|||||||
|
|
||||||
if( _visible || _render_always )
|
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 )
|
if( _render_dirty )
|
||||||
{
|
{
|
||||||
// Also mark all dependent (eg. recursively used) canvases as dirty
|
// Also mark all canvases this canvas is displayed within as dirty
|
||||||
BOOST_FOREACH(CanvasWeakPtr canvas, _dependent_canvases)
|
BOOST_FOREACH(CanvasWeakPtr canvas, _parent_canvases)
|
||||||
{
|
{
|
||||||
if( !canvas.expired() )
|
if( !canvas.expired() )
|
||||||
canvas.lock()->_render_dirty = true;
|
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() )
|
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
|
int Canvas::getViewWidth() const
|
||||||
{
|
{
|
||||||
return _view_width;
|
return _texture.getViewSize().x();
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
int Canvas::getViewHeight() const
|
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,
|
EventVisitor visitor( EventVisitor::TRAVERSE_DOWN,
|
||||||
event->getClientPos(),
|
event->getClientPos(),
|
||||||
event->getDelta() );
|
event->getDelta(),
|
||||||
|
_root_group );
|
||||||
if( !_root_group->accept(visitor) )
|
if( !_root_group->accept(visitor) )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -408,9 +462,13 @@ namespace canvas
|
|||||||
if( node->getParent()->getParent() == _node
|
if( node->getParent()->getParent() == _node
|
||||||
&& node->getParent()->getNameString() == "placement" )
|
&& node->getParent()->getNameString() == "placement" )
|
||||||
{
|
{
|
||||||
bool placement_dirty = false;
|
size_t index = node->getIndex();
|
||||||
BOOST_FOREACH(Placements& placements, _placements)
|
if( index < _placements.size() )
|
||||||
{
|
{
|
||||||
|
Placements& placements = _placements[index];
|
||||||
|
if( !placements.empty() )
|
||||||
|
{
|
||||||
|
bool placement_dirty = false;
|
||||||
BOOST_FOREACH(PlacementPtr& placement, placements)
|
BOOST_FOREACH(PlacementPtr& placement, placements)
|
||||||
{
|
{
|
||||||
// check if change can be directly handled by placement
|
// check if change can be directly handled by placement
|
||||||
@@ -418,10 +476,11 @@ namespace canvas
|
|||||||
&& !placement->childChanged(node) )
|
&& !placement->childChanged(node) )
|
||||||
placement_dirty = true;
|
placement_dirty = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if( !placement_dirty )
|
if( !placement_dirty )
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// prevent double updates...
|
// prevent double updates...
|
||||||
for( size_t i = 0; i < _dirty_placements.size(); ++i )
|
for( size_t i = 0; i < _dirty_placements.size(); ++i )
|
||||||
@@ -449,6 +508,10 @@ namespace canvas
|
|||||||
{
|
{
|
||||||
_sampling_dirty = true;
|
_sampling_dirty = true;
|
||||||
}
|
}
|
||||||
|
else if( node->getNameString() == "additive-blend" )
|
||||||
|
{
|
||||||
|
_texture.useAdditiveBlend( node->getBoolValue() );
|
||||||
|
}
|
||||||
else if( node->getNameString() == "render-always" )
|
else if( node->getNameString() == "render-always" )
|
||||||
{
|
{
|
||||||
_render_always = node->getBoolValue();
|
_render_always = node->getBoolValue();
|
||||||
@@ -491,6 +554,24 @@ namespace canvas
|
|||||||
return _cull_callback;
|
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,
|
void Canvas::addPlacementFactory( const std::string& type,
|
||||||
PlacementFactory factory )
|
PlacementFactory factory )
|
||||||
@@ -500,12 +581,40 @@ namespace canvas
|
|||||||
(
|
(
|
||||||
SG_GENERAL,
|
SG_GENERAL,
|
||||||
SG_WARN,
|
SG_WARN,
|
||||||
"Canvas::addPlacementFactory: replace existing factor for type " << type
|
"Canvas::addPlacementFactory: replace existing factory '" << type << "'"
|
||||||
);
|
);
|
||||||
|
|
||||||
_placement_factories[type] = factory;
|
_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)
|
void Canvas::setSelf(const PropertyBasedElementPtr& self)
|
||||||
{
|
{
|
||||||
@@ -547,6 +656,7 @@ namespace canvas
|
|||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
Canvas::PlacementFactoryMap Canvas::_placement_factories;
|
Canvas::PlacementFactoryMap Canvas::_placement_factories;
|
||||||
|
SystemAdapterPtr Canvas::_system_adapter;
|
||||||
|
|
||||||
} // namespace canvas
|
} // namespace canvas
|
||||||
} // namespace simgear
|
} // namespace simgear
|
||||||
|
|||||||
+53
-13
@@ -23,12 +23,13 @@
|
|||||||
#include "ODGauge.hxx"
|
#include "ODGauge.hxx"
|
||||||
|
|
||||||
#include <simgear/canvas/elements/CanvasGroup.hxx>
|
#include <simgear/canvas/elements/CanvasGroup.hxx>
|
||||||
|
#include <simgear/math/SGRect.hxx>
|
||||||
#include <simgear/props/PropertyBasedElement.hxx>
|
#include <simgear/props/PropertyBasedElement.hxx>
|
||||||
#include <simgear/props/propertyObject.hxx>
|
#include <simgear/props/propertyObject.hxx>
|
||||||
#include <osg/NodeCallback>
|
#include <osg/NodeCallback>
|
||||||
#include <osg/observer_ptr>
|
#include <osg/observer_ptr>
|
||||||
|
|
||||||
#include <memory>
|
#include <boost/scoped_ptr.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
@@ -71,29 +72,54 @@ namespace canvas
|
|||||||
|
|
||||||
Canvas(SGPropertyNode* node);
|
Canvas(SGPropertyNode* node);
|
||||||
virtual ~Canvas();
|
virtual ~Canvas();
|
||||||
|
virtual void onDestroy();
|
||||||
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
|
|
||||||
SystemAdapterPtr getSystemAdapter() const;
|
|
||||||
|
|
||||||
void setCanvasMgr(CanvasMgr* canvas_mgr);
|
void setCanvasMgr(CanvasMgr* canvas_mgr);
|
||||||
CanvasMgr* getCanvasMgr() const;
|
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.
|
* canvas.
|
||||||
*
|
*
|
||||||
* This mechanism is used to eg. redraw a canvas if it's displaying
|
* This mechanism is used to eg. redraw a canvas if it's displaying
|
||||||
* another canvas (recursive canvases)
|
* 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
|
* 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 = "");
|
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
|
* Enable rendering for the next frame
|
||||||
*
|
*
|
||||||
@@ -104,7 +130,7 @@ namespace canvas
|
|||||||
|
|
||||||
void update(double delta_time_sec);
|
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 setSizeX(int sx);
|
||||||
void setSizeY(int sy);
|
void setSizeY(int sy);
|
||||||
@@ -117,6 +143,7 @@ namespace canvas
|
|||||||
|
|
||||||
int getViewWidth() const;
|
int getViewWidth() const;
|
||||||
int getViewHeight() const;
|
int getViewHeight() const;
|
||||||
|
SGRect<int> getViewport() const;
|
||||||
|
|
||||||
bool handleMouseEvent(const MouseEventPtr& event);
|
bool handleMouseEvent(const MouseEventPtr& event);
|
||||||
|
|
||||||
@@ -130,15 +157,26 @@ namespace canvas
|
|||||||
|
|
||||||
CullCallbackPtr getCullCallback() const;
|
CullCallbackPtr getCullCallback() const;
|
||||||
|
|
||||||
|
void reloadPlacements( const std::string& type = std::string() );
|
||||||
static void addPlacementFactory( const std::string& type,
|
static void addPlacementFactory( const std::string& type,
|
||||||
PlacementFactory factory );
|
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:
|
protected:
|
||||||
|
|
||||||
SystemAdapterPtr _system_adapter;
|
|
||||||
CanvasMgr *_canvas_mgr;
|
CanvasMgr *_canvas_mgr;
|
||||||
|
|
||||||
std::auto_ptr<EventManager> _event_manager;
|
boost::scoped_ptr<EventManager> _event_manager;
|
||||||
|
|
||||||
int _size_x,
|
int _size_x,
|
||||||
_size_y,
|
_size_y,
|
||||||
@@ -160,9 +198,9 @@ namespace canvas
|
|||||||
|
|
||||||
std::vector<SGPropertyNode*> _dirty_placements;
|
std::vector<SGPropertyNode*> _dirty_placements;
|
||||||
std::vector<Placements> _placements;
|
std::vector<Placements> _placements;
|
||||||
std::set<CanvasWeakPtr> _dependent_canvases; //<! Canvases which use this
|
std::set<CanvasWeakPtr> _parent_canvases, //<! Canvases showing this canvas
|
||||||
// canvas and should be
|
_child_canvases; //<! Canvases displayed within
|
||||||
// notified about changes
|
// this canvas
|
||||||
|
|
||||||
typedef std::map<std::string, PlacementFactory> PlacementFactoryMap;
|
typedef std::map<std::string, PlacementFactory> PlacementFactoryMap;
|
||||||
static PlacementFactoryMap _placement_factories;
|
static PlacementFactoryMap _placement_factories;
|
||||||
@@ -172,6 +210,8 @@ namespace canvas
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
static SystemAdapterPtr _system_adapter;
|
||||||
|
|
||||||
Canvas(const Canvas&); // = delete;
|
Canvas(const Canvas&); // = delete;
|
||||||
Canvas& operator=(const Canvas&); // = delete;
|
Canvas& operator=(const Canvas&); // = delete;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ namespace canvas
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
ElementWeakPtr Event::getCurrentTarget() const
|
||||||
|
{
|
||||||
|
return current_target;
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
double Event::getTime() const
|
double Event::getTime() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ namespace canvas
|
|||||||
};
|
};
|
||||||
|
|
||||||
Type type;
|
Type type;
|
||||||
ElementWeakPtr target;
|
ElementWeakPtr target,
|
||||||
|
current_target;
|
||||||
double time;
|
double time;
|
||||||
bool propagation_stopped;
|
bool propagation_stopped;
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ namespace canvas
|
|||||||
std::string getTypeString() const;
|
std::string getTypeString() const;
|
||||||
|
|
||||||
ElementWeakPtr getTarget() const;
|
ElementWeakPtr getTarget() const;
|
||||||
|
ElementWeakPtr getCurrentTarget() const;
|
||||||
|
|
||||||
double getTime() 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 "CanvasEventManager.hxx"
|
||||||
#include "MouseEvent.hxx"
|
#include "MouseEvent.hxx"
|
||||||
#include <simgear/canvas/elements/CanvasElement.hxx>
|
#include <simgear/canvas/elements/CanvasElement.hxx>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
{
|
{
|
||||||
@@ -71,6 +72,7 @@ namespace canvas
|
|||||||
bool EventManager::handleEvent( const MouseEventPtr& event,
|
bool EventManager::handleEvent( const MouseEventPtr& event,
|
||||||
const EventPropagationPath& path )
|
const EventPropagationPath& path )
|
||||||
{
|
{
|
||||||
|
bool handled = false;
|
||||||
switch( event->type )
|
switch( event->type )
|
||||||
{
|
{
|
||||||
case Event::MOUSE_DOWN:
|
case Event::MOUSE_DOWN:
|
||||||
@@ -78,20 +80,27 @@ namespace canvas
|
|||||||
break;
|
break;
|
||||||
case Event::MOUSE_UP:
|
case Event::MOUSE_UP:
|
||||||
{
|
{
|
||||||
if( _last_mouse_down.path.empty() )
|
// If the mouse has moved while a button was down (aka. dragging) we
|
||||||
// Ignore mouse up without any previous mouse down
|
// need to notify the original element that the mouse has left it, and
|
||||||
return false;
|
// the new element that it has been entered
|
||||||
|
if( _last_mouse_down.path != path )
|
||||||
|
handled |= handleMove(event, path);
|
||||||
|
|
||||||
// normal mouseup
|
// 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
|
// now handle click/dblclick
|
||||||
if( checkClickDistance(path, _last_mouse_down.path) )
|
if( checkClickDistance(path, _last_mouse_down.path) )
|
||||||
|
handled |=
|
||||||
handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
|
handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
|
||||||
|
|
||||||
_last_mouse_down.clear();
|
_last_mouse_down.clear();
|
||||||
|
|
||||||
return true;
|
return handled;
|
||||||
}
|
}
|
||||||
case Event::DRAG:
|
case Event::DRAG:
|
||||||
if( !_last_mouse_down.valid() )
|
if( !_last_mouse_down.valid() )
|
||||||
@@ -99,11 +108,18 @@ namespace canvas
|
|||||||
else
|
else
|
||||||
return propagateEvent(event, _last_mouse_down.path);
|
return propagateEvent(event, _last_mouse_down.path);
|
||||||
case Event::MOUSE_MOVE:
|
case Event::MOUSE_MOVE:
|
||||||
handleMove(event, path);
|
handled |= handleMove(event, path);
|
||||||
break;
|
break;
|
||||||
case Event::MOUSE_LEAVE:
|
case Event::MOUSE_LEAVE:
|
||||||
// Mouse leaves window and therefore also current mouseover element
|
// Mouse leaves window and therefore also current mouseover element
|
||||||
handleMove(event, EventPropagationPath());
|
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;
|
return true;
|
||||||
case Event::WHEEL:
|
case Event::WHEEL:
|
||||||
break;
|
break;
|
||||||
@@ -111,11 +127,11 @@ namespace canvas
|
|||||||
return false;
|
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 )
|
const EventPropagationPath& path )
|
||||||
{
|
{
|
||||||
MouseEventPtr click(new MouseEvent(*event));
|
MouseEventPtr click(new MouseEvent(*event));
|
||||||
@@ -145,36 +161,76 @@ namespace canvas
|
|||||||
dbl_click->type = Event::DBL_CLICK;
|
dbl_click->type = Event::DBL_CLICK;
|
||||||
}
|
}
|
||||||
|
|
||||||
propagateEvent(click, path);
|
bool handled = propagateEvent(click, path);
|
||||||
|
|
||||||
if( dbl_click )
|
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());
|
_last_click = StampedPropagationPath(path, event->getTime());
|
||||||
|
|
||||||
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void EventManager::handleMove( const MouseEventPtr& event,
|
bool EventManager::handleMove( const MouseEventPtr& event,
|
||||||
const EventPropagationPath& path )
|
const EventPropagationPath& path )
|
||||||
{
|
{
|
||||||
if( _last_mouse_over.path == path )
|
EventPropagationPath& last_path = _last_mouse_over.path;
|
||||||
return;
|
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));
|
MouseEventPtr mouseout(new MouseEvent(*event));
|
||||||
mouseout->type = Event::MOUSE_OUT;
|
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() )
|
if( !path.empty() )
|
||||||
{
|
{
|
||||||
MouseEventPtr mouseover(new MouseEvent(*event));
|
MouseEventPtr mouseover(new MouseEvent(*event));
|
||||||
mouseover->type = Event::MOUSE_OVER;
|
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;
|
_last_mouse_over.path = path;
|
||||||
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
@@ -188,15 +244,29 @@ namespace canvas
|
|||||||
// http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
|
// http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
|
||||||
|
|
||||||
// Capturing phase
|
// Capturing phase
|
||||||
// for( EventTargets::iterator it = _target_path.begin();
|
// for( EventPropagationPath::const_iterator it = path.begin();
|
||||||
// it != _target_path.end();
|
// it != path.end();
|
||||||
// ++it )
|
// ++it )
|
||||||
// {
|
// {
|
||||||
// if( it->element )
|
// if( !it->element.expired() )
|
||||||
// std::cout << it->element->getProps()->getPath() << " "
|
// std::cout << it->element.lock()->getProps()->getPath() << std::endl;
|
||||||
// << "(" << it->local_pos.x() << "|" << it->local_pos.y() << ")\n";
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// 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
|
// Bubbling phase
|
||||||
for( EventPropagationPath::const_reverse_iterator
|
for( EventPropagationPath::const_reverse_iterator
|
||||||
it = path.rbegin();
|
it = path.rbegin();
|
||||||
@@ -206,29 +276,34 @@ namespace canvas
|
|||||||
ElementPtr el = it->element.lock();
|
ElementPtr el = it->element.lock();
|
||||||
|
|
||||||
if( !el )
|
if( !el )
|
||||||
|
{
|
||||||
// Ignore element if it has been destroyed while traversing the event
|
// Ignore element if it has been destroyed while traversing the event
|
||||||
// (eg. removed by another event handler)
|
// (eg. removed by another event handler)
|
||||||
|
if( do_bubble )
|
||||||
continue;
|
continue;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO provide functions to convert position and delta to local
|
// TODO provide functions to convert delta to local coordinates on demand.
|
||||||
// coordinates on demand. Events shouldn't contain informations in
|
// Maybe also provide a clone method for events as local coordinates
|
||||||
// local coordinates as they might differe between different elements
|
// might differ between different elements receiving the same event.
|
||||||
// receiving the same event.
|
if( mouse_event ) //&& event->type != Event::DRAG )
|
||||||
// if( mouse_event && event->type != Event::DRAG )
|
{
|
||||||
// {
|
// TODO transform pos and delta for drag events. Maybe we should just
|
||||||
// // TODO transform pos and delta for drag events. Maybe we should just
|
// store the global coordinates and convert to local coordinates
|
||||||
// // store the global coordinates and convert to local coordinates
|
// on demand.
|
||||||
// // 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;
|
|
||||||
// }
|
|
||||||
|
|
||||||
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,8 +316,8 @@ namespace canvas
|
|||||||
const EventPropagationPath& path2 ) const
|
const EventPropagationPath& path2 ) const
|
||||||
{
|
{
|
||||||
osg::Vec2 delta = path1.front().local_pos - path2.front().local_pos;
|
osg::Vec2 delta = path1.front().local_pos - path2.front().local_pos;
|
||||||
return delta.x() < drag_threshold
|
return std::fabs(delta.x()) < drag_threshold
|
||||||
&& delta.y() < drag_threshold;
|
&& std::fabs(delta.y()) < drag_threshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
@@ -250,6 +325,9 @@ namespace canvas
|
|||||||
EventManager::getCommonAncestor( const EventPropagationPath& path1,
|
EventManager::getCommonAncestor( const EventPropagationPath& path1,
|
||||||
const EventPropagationPath& path2 ) const
|
const EventPropagationPath& path2 ) const
|
||||||
{
|
{
|
||||||
|
if( path1.empty() || path2.empty() )
|
||||||
|
return EventPropagationPath();
|
||||||
|
|
||||||
if( path1.back().element.lock() == path2.back().element.lock() )
|
if( path1.back().element.lock() == path2.back().element.lock() )
|
||||||
return path2;
|
return path2;
|
||||||
|
|
||||||
|
|||||||
@@ -70,13 +70,13 @@ namespace canvas
|
|||||||
/**
|
/**
|
||||||
* Propagate click event and handle multi-click (eg. create dblclick)
|
* Propagate click event and handle multi-click (eg. create dblclick)
|
||||||
*/
|
*/
|
||||||
void handleClick( const MouseEventPtr& event,
|
bool handleClick( const MouseEventPtr& event,
|
||||||
const EventPropagationPath& path );
|
const EventPropagationPath& path );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle mouseover/enter/out/leave
|
* Handle mouseover/enter/out/leave
|
||||||
*/
|
*/
|
||||||
void handleMove( const MouseEventPtr& event,
|
bool handleMove( const MouseEventPtr& event,
|
||||||
const EventPropagationPath& path );
|
const EventPropagationPath& path );
|
||||||
|
|
||||||
bool propagateEvent( const EventPtr& event,
|
bool propagateEvent( const EventPtr& event,
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ namespace canvas
|
|||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
EventVisitor::EventVisitor( TraverseMode mode,
|
EventVisitor::EventVisitor( TraverseMode mode,
|
||||||
const osg::Vec2f& pos,
|
const osg::Vec2f& pos,
|
||||||
const osg::Vec2f& delta ):
|
const osg::Vec2f& delta,
|
||||||
_traverse_mode( mode )
|
const ElementPtr& root ):
|
||||||
|
_traverse_mode( mode ),
|
||||||
|
_root(root)
|
||||||
{
|
{
|
||||||
if( mode == TRAVERSE_DOWN )
|
if( mode == TRAVERSE_DOWN )
|
||||||
{
|
{
|
||||||
@@ -70,10 +72,11 @@ namespace canvas
|
|||||||
m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1)
|
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)
|
// Don't check specified root element for collision, as its purpose is to
|
||||||
// do event listeners attached to the canvas itself (its root group)
|
// catch all events which have no target. This allows for example calling
|
||||||
// always get called even if no element has been hit.
|
// event listeners attached to the canvas itself (its root group) even if
|
||||||
if( _target_path.size() > 2 && !el.hitBound(pos, local_pos) )
|
// no element has been hit.
|
||||||
|
if( _root.get() != &el && !el.hitBound(pos, local_pos) )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const osg::Vec2f& delta = _target_path.back().local_delta;
|
const osg::Vec2f& delta = _target_path.back().local_delta;
|
||||||
@@ -86,7 +89,7 @@ namespace canvas
|
|||||||
EventTarget target = {el.getWeakPtr(), local_pos, local_delta};
|
EventTarget target = {el.getWeakPtr(), local_pos, local_delta};
|
||||||
_target_path.push_back(target);
|
_target_path.push_back(target);
|
||||||
|
|
||||||
if( el.traverse(*this) || _target_path.size() <= 2 )
|
if( el.traverse(*this) || &el == _root.get() )
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
_target_path.pop_back();
|
_target_path.pop_back();
|
||||||
|
|||||||
@@ -38,9 +38,17 @@ namespace canvas
|
|||||||
TRAVERSE_DOWN
|
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,
|
EventVisitor( TraverseMode mode,
|
||||||
const osg::Vec2f& pos,
|
const osg::Vec2f& pos,
|
||||||
const osg::Vec2f& delta );
|
const osg::Vec2f& delta,
|
||||||
|
const ElementPtr& root = ElementPtr() );
|
||||||
virtual ~EventVisitor();
|
virtual ~EventVisitor();
|
||||||
virtual bool traverse(Element& el);
|
virtual bool traverse(Element& el);
|
||||||
virtual bool apply(Element& el);
|
virtual bool apply(Element& el);
|
||||||
@@ -51,6 +59,7 @@ namespace canvas
|
|||||||
|
|
||||||
TraverseMode _traverse_mode;
|
TraverseMode _traverse_mode;
|
||||||
EventPropagationPath _target_path;
|
EventPropagationPath _target_path;
|
||||||
|
ElementPtr _root;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "CanvasMgr.hxx"
|
#include "CanvasMgr.hxx"
|
||||||
#include "Canvas.hxx"
|
#include "Canvas.hxx"
|
||||||
|
#include "CanvasEventManager.hxx"
|
||||||
|
|
||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
|
|
||||||
@@ -35,10 +36,8 @@ namespace canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
CanvasMgr::CanvasMgr( SGPropertyNode_ptr node,
|
CanvasMgr::CanvasMgr(SGPropertyNode_ptr node):
|
||||||
SystemAdapterPtr system_adapter ):
|
PropertyBasedMgr(node, "texture", &canvasFactory)
|
||||||
PropertyBasedMgr(node, "texture", &canvasFactory),
|
|
||||||
_system_adapter(system_adapter)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -65,7 +64,6 @@ namespace canvas
|
|||||||
void CanvasMgr::elementCreated(PropertyBasedElementPtr element)
|
void CanvasMgr::elementCreated(PropertyBasedElementPtr element)
|
||||||
{
|
{
|
||||||
CanvasPtr canvas = boost::static_pointer_cast<Canvas>(element);
|
CanvasPtr canvas = boost::static_pointer_cast<Canvas>(element);
|
||||||
canvas->setSystemAdapter(_system_adapter);
|
|
||||||
canvas->setCanvasMgr(this);
|
canvas->setCanvasMgr(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,12 +34,8 @@ namespace canvas
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param node Root node of branch used to control canvasses
|
* @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,
|
CanvasMgr(SGPropertyNode_ptr node);
|
||||||
SystemAdapterPtr system_adapter );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new canvas
|
* Create a new canvas
|
||||||
@@ -65,8 +61,6 @@ namespace canvas
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
SystemAdapterPtr _system_adapter;
|
|
||||||
|
|
||||||
virtual void elementCreated(PropertyBasedElementPtr element);
|
virtual void elementCreated(PropertyBasedElementPtr element);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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_
|
#define SG_CANVAS_SYSTEM_ADAPTER_HXX_
|
||||||
|
|
||||||
#include "canvas_fwd.hxx"
|
#include "canvas_fwd.hxx"
|
||||||
#include <simgear/nasal/nasal.h>
|
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
{
|
{
|
||||||
@@ -36,29 +35,6 @@ namespace canvas
|
|||||||
virtual void addCamera(osg::Camera* camera) const = 0;
|
virtual void addCamera(osg::Camera* camera) const = 0;
|
||||||
virtual void removeCamera(osg::Camera* camera) const = 0;
|
virtual void removeCamera(osg::Camera* camera) const = 0;
|
||||||
virtual osg::Image* getImage(const std::string& path) 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
|
} // namespace canvas
|
||||||
|
|||||||
@@ -38,8 +38,18 @@ namespace canvas
|
|||||||
click_count(0)
|
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 getScreenPos() const { return screen_pos; }
|
||||||
osg::Vec2f getClientPos() const { return client_pos; }
|
osg::Vec2f getClientPos() const { return client_pos; }
|
||||||
|
osg::Vec2f getLocalPos() const { return local_pos; }
|
||||||
osg::Vec2f getDelta() const { return delta; }
|
osg::Vec2f getDelta() const { return delta; }
|
||||||
|
|
||||||
float getScreenX() const { return screen_pos.x(); }
|
float getScreenX() const { return screen_pos.x(); }
|
||||||
@@ -48,6 +58,9 @@ namespace canvas
|
|||||||
float getClientX() const { return client_pos.x(); }
|
float getClientX() const { return client_pos.x(); }
|
||||||
float getClientY() const { return client_pos.y(); }
|
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 getDeltaX() const { return delta.x(); }
|
||||||
float getDeltaY() const { return delta.y(); }
|
float getDeltaY() const { return delta.y(); }
|
||||||
|
|
||||||
@@ -55,6 +68,7 @@ namespace canvas
|
|||||||
|
|
||||||
osg::Vec2f screen_pos, //<! Position in screen coordinates
|
osg::Vec2f screen_pos, //<! Position in screen coordinates
|
||||||
client_pos, //<! Position in window/canvas coordinates
|
client_pos, //<! Position in window/canvas coordinates
|
||||||
|
local_pos, //<! Position in local/element coordinates
|
||||||
delta;
|
delta;
|
||||||
int button, //<! Button for this event
|
int button, //<! Button for this event
|
||||||
state, //<! Current button state
|
state, //<! Current button state
|
||||||
|
|||||||
+116
-43
@@ -29,6 +29,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "ODGauge.hxx"
|
#include "ODGauge.hxx"
|
||||||
|
#include "Canvas.hxx"
|
||||||
#include "CanvasSystemAdapter.hxx"
|
#include "CanvasSystemAdapter.hxx"
|
||||||
|
|
||||||
#include <simgear/debug/logstream.hxx>
|
#include <simgear/debug/logstream.hxx>
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
#include <osg/ShadeModel>
|
#include <osg/ShadeModel>
|
||||||
#include <osg/StateSet>
|
#include <osg/StateSet>
|
||||||
#include <osg/FrameBufferObject> // for GL_DEPTH_STENCIL_EXT on Windows
|
#include <osg/FrameBufferObject> // for GL_DEPTH_STENCIL_EXT on Windows
|
||||||
|
#include <osgUtil/RenderBin>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
@@ -51,18 +53,67 @@ namespace simgear
|
|||||||
namespace canvas
|
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():
|
ODGauge::ODGauge():
|
||||||
_size_x( -1 ),
|
_size_x( -1 ),
|
||||||
_size_y( -1 ),
|
_size_y( -1 ),
|
||||||
_view_width( -1 ),
|
_view_width( -1 ),
|
||||||
_view_height( -1 ),
|
_view_height( -1 ),
|
||||||
_use_image_coords( false ),
|
_flags(0),
|
||||||
_use_stencil( false ),
|
|
||||||
_use_mipmapping( false ),
|
|
||||||
_coverage_samples( 0 ),
|
_coverage_samples( 0 ),
|
||||||
_color_samples( 0 ),
|
_color_samples( 0 )
|
||||||
rtAvailable( false )
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -73,12 +124,6 @@ namespace canvas
|
|||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
void ODGauge::setSystemAdapter(const SystemAdapterPtr& system_adapter)
|
|
||||||
{
|
|
||||||
_system_adapter = system_adapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void ODGauge::setSize(int size_x, int size_y)
|
void ODGauge::setSize(int size_x, int size_y)
|
||||||
{
|
{
|
||||||
@@ -101,42 +146,43 @@ namespace canvas
|
|||||||
updateCoordinateFrame();
|
updateCoordinateFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
osg::Vec2s ODGauge::getViewSize() const
|
||||||
|
{
|
||||||
|
return osg::Vec2s(_view_width, _view_height);
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void ODGauge::useImageCoords(bool use)
|
void ODGauge::useImageCoords(bool use)
|
||||||
{
|
{
|
||||||
if( use == _use_image_coords )
|
if( updateFlag(USE_IMAGE_COORDS, use) && texture )
|
||||||
return;
|
|
||||||
|
|
||||||
_use_image_coords = use;
|
|
||||||
|
|
||||||
if( texture )
|
|
||||||
updateCoordinateFrame();
|
updateCoordinateFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void ODGauge::useStencil(bool use)
|
void ODGauge::useStencil(bool use)
|
||||||
{
|
{
|
||||||
if( use == _use_stencil )
|
if( updateFlag(USE_STENCIL, use) && texture )
|
||||||
return;
|
|
||||||
|
|
||||||
_use_stencil = use;
|
|
||||||
|
|
||||||
if( texture )
|
|
||||||
updateStencil();
|
updateStencil();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void ODGauge::useAdditiveBlend(bool use)
|
||||||
|
{
|
||||||
|
if( updateFlag(USE_ADDITIVE_BLEND, use) && camera )
|
||||||
|
updateBlendMode();
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void ODGauge::setSampling( bool mipmapping,
|
void ODGauge::setSampling( bool mipmapping,
|
||||||
int coverage_samples,
|
int coverage_samples,
|
||||||
int color_samples )
|
int color_samples )
|
||||||
{
|
{
|
||||||
if( _use_mipmapping == mipmapping
|
if( !updateFlag(USE_MIPMAPPING, mipmapping)
|
||||||
&& _coverage_samples == coverage_samples
|
&& _coverage_samples == coverage_samples
|
||||||
&& _color_samples == color_samples )
|
&& _color_samples == color_samples )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_use_mipmapping = mipmapping;
|
|
||||||
|
|
||||||
if( color_samples > coverage_samples )
|
if( color_samples > coverage_samples )
|
||||||
{
|
{
|
||||||
SG_LOG
|
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::PolygonMode::FILL ),
|
||||||
osg::StateAttribute::ON );
|
osg::StateAttribute::ON );
|
||||||
stateSet->setAttributeAndModes(
|
stateSet->setAttributeAndModes(
|
||||||
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.0f),
|
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.001f),
|
||||||
osg::StateAttribute::ON );
|
osg::StateAttribute::ON );
|
||||||
stateSet->setAttribute(new osg::ShadeModel(osg::ShadeModel::FLAT));
|
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 )
|
if( !texture )
|
||||||
{
|
{
|
||||||
texture = new osg::Texture2D;
|
texture = new osg::Texture2D;
|
||||||
|
texture->setResizeNonPowerOfTwoHint(false);
|
||||||
texture->setTextureSize(_size_x, _size_y);
|
texture->setTextureSize(_size_x, _size_y);
|
||||||
texture->setInternalFormat(GL_RGBA);
|
texture->setInternalFormat(GL_RGBA);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSampling();
|
updateSampling();
|
||||||
|
updateBlendMode();
|
||||||
|
|
||||||
if( _system_adapter )
|
if( Canvas::getSystemAdapter() )
|
||||||
_system_adapter->addCamera(camera.get());
|
Canvas::getSystemAdapter()->addCamera(camera.get());
|
||||||
|
|
||||||
rtAvailable = true;
|
_flags |= AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
@@ -229,12 +274,22 @@ namespace canvas
|
|||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void ODGauge::clear()
|
void ODGauge::clear()
|
||||||
{
|
{
|
||||||
if( camera.valid() && _system_adapter )
|
if( camera.valid() && Canvas::getSystemAdapter() )
|
||||||
_system_adapter->removeCamera(camera.get());
|
Canvas::getSystemAdapter()->removeCamera(camera.get());
|
||||||
camera.release();
|
camera.release();
|
||||||
texture.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);
|
camera->setViewport(0, 0, _size_x, _size_y);
|
||||||
|
|
||||||
if( _use_image_coords )
|
if( _flags & USE_IMAGE_COORDS )
|
||||||
camera->setProjectionMatrix(
|
camera->setProjectionMatrix(
|
||||||
osg::Matrix::ortho2D(0, _view_width, _view_height, 0)
|
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;
|
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,
|
camera->attach( osg::Camera::PACKED_DEPTH_STENCIL_BUFFER,
|
||||||
GL_DEPTH_STENCIL_EXT );
|
GL_DEPTH_STENCIL_EXT );
|
||||||
@@ -289,18 +344,36 @@ namespace canvas
|
|||||||
|
|
||||||
texture->setFilter(
|
texture->setFilter(
|
||||||
osg::Texture2D::MIN_FILTER,
|
osg::Texture2D::MIN_FILTER,
|
||||||
_use_mipmapping ? osg::Texture2D::LINEAR_MIPMAP_LINEAR
|
(_flags & USE_MIPMAPPING) ? osg::Texture2D::LINEAR_MIPMAP_LINEAR
|
||||||
: osg::Texture2D::LINEAR
|
: osg::Texture2D::LINEAR
|
||||||
);
|
);
|
||||||
camera->attach(
|
camera->attach(
|
||||||
osg::Camera::COLOR_BUFFER,
|
osg::Camera::COLOR_BUFFER,
|
||||||
texture.get(),
|
texture.get(),
|
||||||
0, 0,
|
0, 0,
|
||||||
_use_mipmapping,
|
_flags & USE_MIPMAPPING,
|
||||||
_coverage_samples,
|
_coverage_samples,
|
||||||
_color_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 canvas
|
||||||
} // namespace simgear
|
} // namespace simgear
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ namespace canvas
|
|||||||
ODGauge();
|
ODGauge();
|
||||||
virtual ~ODGauge();
|
virtual ~ODGauge();
|
||||||
|
|
||||||
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the size of the render target.
|
* Set the size of the render target.
|
||||||
*
|
*
|
||||||
@@ -71,6 +69,8 @@ namespace canvas
|
|||||||
*/
|
*/
|
||||||
void setViewSize(int width, int height = -1);
|
void setViewSize(int width, int height = -1);
|
||||||
|
|
||||||
|
osg::Vec2s getViewSize() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DEPRECATED
|
* DEPRECATED
|
||||||
*
|
*
|
||||||
@@ -91,6 +91,12 @@ namespace canvas
|
|||||||
*/
|
*/
|
||||||
void useStencil(bool use = true);
|
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
|
* Set sampling parameters for mipmapping and coverage sampling
|
||||||
* antialiasing.
|
* antialiasing.
|
||||||
@@ -112,7 +118,7 @@ namespace canvas
|
|||||||
* Say if we can render to a texture.
|
* Say if we can render to a texture.
|
||||||
* @return true if rtt is available
|
* @return true if rtt is available
|
||||||
*/
|
*/
|
||||||
bool serviceable(void);
|
bool serviceable() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the OSG camera for drawing this gauge.
|
* Get the OSG camera for drawing this gauge.
|
||||||
@@ -128,27 +134,35 @@ namespace canvas
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
SystemAdapterPtr _system_adapter;
|
|
||||||
|
|
||||||
int _size_x,
|
int _size_x,
|
||||||
_size_y,
|
_size_y,
|
||||||
_view_width,
|
_view_width,
|
||||||
_view_height;
|
_view_height;
|
||||||
bool _use_image_coords,
|
|
||||||
_use_stencil,
|
enum Flags
|
||||||
_use_mipmapping;
|
{
|
||||||
|
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
|
// Multisampling parameters
|
||||||
int _coverage_samples,
|
int _coverage_samples,
|
||||||
_color_samples;
|
_color_samples;
|
||||||
|
|
||||||
bool rtAvailable;
|
|
||||||
osg::ref_ptr<osg::Camera> camera;
|
osg::ref_ptr<osg::Camera> camera;
|
||||||
osg::ref_ptr<osg::Texture2D> texture;
|
osg::ref_ptr<osg::Texture2D> texture;
|
||||||
|
|
||||||
|
bool updateFlag(Flags flag, bool enable);
|
||||||
|
|
||||||
void updateCoordinateFrame();
|
void updateCoordinateFrame();
|
||||||
void updateStencil();
|
void updateStencil();
|
||||||
void updateSampling();
|
void updateSampling();
|
||||||
|
void updateBlendMode();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -627,6 +627,7 @@ VG_API_CALL const VGubyte * vgGetString(VGStringID name);
|
|||||||
#define OVG_SH_blend_dst_atop 1
|
#define OVG_SH_blend_dst_atop 1
|
||||||
|
|
||||||
VG_API_CALL VGboolean vgCreateContextSH(VGint width, VGint height);
|
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 vgResizeSurfaceSH(VGint width, VGint height);
|
||||||
VG_API_CALL void vgDestroyContextSH(void);
|
VG_API_CALL void vgDestroyContextSH(void);
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,11 @@ VG_API_CALL VGboolean vgCreateContextSH(VGint width, VGint height)
|
|||||||
return VG_TRUE;
|
return VG_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VG_API_CALL VGboolean vgHasContextSH()
|
||||||
|
{
|
||||||
|
return g_context != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
VG_API_CALL void vgResizeSurfaceSH(VGint width, VGint height)
|
VG_API_CALL void vgResizeSurfaceSH(VGint width, VGint height)
|
||||||
{
|
{
|
||||||
VG_GETCONTEXT(VG_NO_RETVAL);
|
VG_GETCONTEXT(VG_NO_RETVAL);
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ namespace canvas
|
|||||||
SG_FWD_DECL(Text)
|
SG_FWD_DECL(Text)
|
||||||
|
|
||||||
SG_FWD_DECL(Event)
|
SG_FWD_DECL(Event)
|
||||||
SG_FWD_DECL(EventListener)
|
|
||||||
SG_FWD_DECL(MouseEvent)
|
SG_FWD_DECL(MouseEvent)
|
||||||
SG_FWD_DECL(Placement)
|
SG_FWD_DECL(Placement)
|
||||||
SG_FWD_DECL(SystemAdapter)
|
SG_FWD_DECL(SystemAdapter)
|
||||||
@@ -73,6 +72,8 @@ namespace canvas
|
|||||||
typedef boost::function<Placements( SGPropertyNode*,
|
typedef boost::function<Placements( SGPropertyNode*,
|
||||||
CanvasPtr )> PlacementFactory;
|
CanvasPtr )> PlacementFactory;
|
||||||
|
|
||||||
|
typedef boost::function<void(const EventPtr&)> EventListener;
|
||||||
|
|
||||||
} // namespace canvas
|
} // namespace canvas
|
||||||
} // namespace simgear
|
} // namespace simgear
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ set(HEADERS
|
|||||||
CanvasText.hxx
|
CanvasText.hxx
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(DETAIL_HEADERS
|
||||||
|
detail/add_segment_variadic.hxx
|
||||||
|
)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
CanvasElement.cxx
|
CanvasElement.cxx
|
||||||
CanvasGroup.cxx
|
CanvasGroup.cxx
|
||||||
@@ -19,3 +23,4 @@ set(SOURCES
|
|||||||
)
|
)
|
||||||
|
|
||||||
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
|
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||||
|
|
||||||
#include "CanvasElement.hxx"
|
#include "CanvasElement.hxx"
|
||||||
#include <simgear/canvas/Canvas.hxx>
|
|
||||||
#include <simgear/canvas/CanvasEventListener.hxx>
|
|
||||||
#include <simgear/canvas/CanvasEventVisitor.hxx>
|
#include <simgear/canvas/CanvasEventVisitor.hxx>
|
||||||
#include <simgear/canvas/MouseEvent.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/Drawable>
|
||||||
#include <osg/Geode>
|
#include <osg/Geode>
|
||||||
@@ -28,11 +29,10 @@
|
|||||||
|
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/foreach.hpp>
|
#include <boost/foreach.hpp>
|
||||||
#include <boost/lexical_cast.hpp>
|
|
||||||
#include <boost/make_shared.hpp>
|
#include <boost/make_shared.hpp>
|
||||||
#include <boost/tokenizer.hpp>
|
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
@@ -41,20 +41,110 @@ namespace canvas
|
|||||||
{
|
{
|
||||||
const std::string NAME_TRANSFORM = "tf";
|
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()
|
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())
|
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());
|
return boost::static_pointer_cast<Element>(_self.lock());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
ElementPtr Element::getParent()
|
||||||
|
{
|
||||||
|
return _parent ? _parent->getWeakPtr().lock() : ElementPtr();
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void Element::update(double dt)
|
void Element::update(double dt)
|
||||||
{
|
{
|
||||||
@@ -71,8 +167,370 @@ namespace canvas
|
|||||||
// Don't do anything if element is hidden
|
// Don't do anything if element is hidden
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if( _transform_dirty )
|
// Trigger matrix update
|
||||||
|
getMatrix();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
bool Element::addEventListener( const std::string& type_str,
|
||||||
|
const EventListener& cb )
|
||||||
|
{
|
||||||
|
SG_LOG
|
||||||
|
(
|
||||||
|
SG_GENERAL,
|
||||||
|
SG_INFO,
|
||||||
|
"addEventListener(" << _node->getPath() << ", " << type_str << ")"
|
||||||
|
);
|
||||||
|
|
||||||
|
Event::Type type = Event::strToType(type_str);
|
||||||
|
if( type == Event::UNKNOWN )
|
||||||
|
{
|
||||||
|
SG_LOG( SG_GENERAL,
|
||||||
|
SG_WARN,
|
||||||
|
"addEventListener: Unknown event type " << type_str );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_listener[ type ].push_back(cb);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void Element::clearEventListener()
|
||||||
|
{
|
||||||
|
_listener.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
bool Element::accept(EventVisitor& visitor)
|
||||||
|
{
|
||||||
|
if( !isVisible() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return visitor.apply(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
bool Element::ascend(EventVisitor& visitor)
|
||||||
|
{
|
||||||
|
if( _parent )
|
||||||
|
return _parent->accept(visitor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
bool Element::traverse(EventVisitor& visitor)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
bool Element::handleEvent(canvas::EventPtr event)
|
||||||
|
{
|
||||||
|
ListenerMap::iterator listeners = _listener.find(event->getType());
|
||||||
|
if( listeners == _listener.end() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
BOOST_FOREACH(EventListener const& listener, listeners->second)
|
||||||
|
listener(event);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
bool Element::hitBound( const osg::Vec2f& pos,
|
||||||
|
const osg::Vec2f& local_pos ) const
|
||||||
|
{
|
||||||
|
const osg::Vec3f pos3(pos, 0);
|
||||||
|
|
||||||
|
// Drawables have a bounding box...
|
||||||
|
if( _drawable )
|
||||||
|
return _drawable->getBound().contains(osg::Vec3f(local_pos, 0));
|
||||||
|
// ... for other elements, i.e. groups only a bounding sphere is available
|
||||||
|
else
|
||||||
|
return _transform->getBound().contains(osg::Vec3f(pos, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
bool Element::isVisible() const
|
||||||
|
{
|
||||||
|
return _transform.valid() && _transform->getNodeMask() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
osg::MatrixTransform* Element::getMatrixTransform()
|
||||||
|
{
|
||||||
|
return _transform.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
osg::MatrixTransform const* Element::getMatrixTransform() const
|
||||||
|
{
|
||||||
|
return _transform.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void Element::childAdded(SGPropertyNode* parent, SGPropertyNode* child)
|
||||||
|
{
|
||||||
|
if( parent == _node
|
||||||
|
&& child->getNameString() == NAME_TRANSFORM )
|
||||||
|
{
|
||||||
|
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
|
||||||
|
_transform_types.resize( child->getIndex() + 1 );
|
||||||
|
|
||||||
|
_transform_types[ child->getIndex() ] = TT_NONE;
|
||||||
|
_attributes_dirty |= TRANSFORM;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if( parent->getParent() == _node
|
||||||
|
&& parent->getNameString() == NAME_TRANSFORM )
|
||||||
|
{
|
||||||
|
assert(parent->getIndex() < static_cast<int>(_transform_types.size()));
|
||||||
|
|
||||||
|
const std::string& name = child->getNameString();
|
||||||
|
|
||||||
|
TransformType& type = _transform_types[parent->getIndex()];
|
||||||
|
|
||||||
|
if( name == "m" )
|
||||||
|
type = TT_MATRIX;
|
||||||
|
else if( name == "t" )
|
||||||
|
type = TT_TRANSLATE;
|
||||||
|
else if( name == "rot" )
|
||||||
|
type = TT_ROTATE;
|
||||||
|
else if( name == "s" )
|
||||||
|
type = TT_SCALE;
|
||||||
|
|
||||||
|
_attributes_dirty |= TRANSFORM;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
childAdded(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
|
||||||
|
{
|
||||||
|
if( parent == _node )
|
||||||
|
{
|
||||||
|
if( child->getNameString() == NAME_TRANSFORM )
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
_attributes_dirty |= TRANSFORM;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if( StyleInfo const* style = getStyleInfo(child->getNameString()) )
|
||||||
|
{
|
||||||
|
if( setStyle(getParentStyle(child), style) )
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
childRemoved(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void Element::valueChanged(SGPropertyNode* child)
|
||||||
|
{
|
||||||
|
SGPropertyNode *parent = child->getParent();
|
||||||
|
if( parent == _node )
|
||||||
|
{
|
||||||
|
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( name == "update" )
|
||||||
|
return update(0);
|
||||||
|
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 )
|
||||||
|
{
|
||||||
|
_attributes_dirty |= TRANSFORM;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
childChanged(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
bool Element::setStyle( const SGPropertyNode* child,
|
||||||
|
const StyleInfo* style_info )
|
||||||
|
{
|
||||||
|
return canApplyStyle(child) && setStyleImpl(child, style_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void Element::setClip(const std::string& clip)
|
||||||
|
{
|
||||||
|
if( clip.empty() || clip == "auto" )
|
||||||
|
{
|
||||||
|
getOrCreateStateSet()->removeAttribute(osg::StateAttribute::SCISSOR);
|
||||||
|
_scissor = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO generalize CSS property parsing
|
||||||
|
const std::string RECT("rect(");
|
||||||
|
if( !boost::ends_with(clip, ")")
|
||||||
|
|| !boost::starts_with(clip, RECT) )
|
||||||
|
{
|
||||||
|
SG_LOG(SG_GENERAL, SG_WARN, "Canvas: invalid clip: " << clip);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string sep(", \t\npx");
|
||||||
|
int comp = 0;
|
||||||
|
float values[4];
|
||||||
|
|
||||||
|
for(size_t pos = RECT.size(); comp < 4; ++comp)
|
||||||
|
{
|
||||||
|
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 )
|
||||||
|
{
|
||||||
|
SG_LOG(SG_GENERAL, SG_WARN, "Canvas: invalid clip: " << clip);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float width = values[1] - values[3],
|
||||||
|
height = values[2] - values[0];
|
||||||
|
|
||||||
|
if( width < 0 || height < 0 )
|
||||||
|
{
|
||||||
|
SG_LOG(SG_GENERAL, SG_WARN, "Canvas: negative clip size: " << clip);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scissor = new RelativeScissor();
|
||||||
|
// <top>, <right>, <bottom>, <left>
|
||||||
|
_scissor->x() = SGMiscf::roundToInt(values[3]);
|
||||||
|
_scissor->y() = SGMiscf::roundToInt(values[0]);
|
||||||
|
_scissor->width() = SGMiscf::roundToInt(width);
|
||||||
|
_scissor->height() = SGMiscf::roundToInt(height);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void Element::setBoundingBox(const osg::BoundingBox& bb)
|
||||||
|
{
|
||||||
|
if( _bounding_box.empty() )
|
||||||
|
{
|
||||||
|
SGPropertyNode* bb_node = _node->getChild("bounding-box", 0, true);
|
||||||
|
_bounding_box.resize(4);
|
||||||
|
_bounding_box[0] = bb_node->getChild("min-x", 0, true);
|
||||||
|
_bounding_box[1] = bb_node->getChild("min-y", 0, true);
|
||||||
|
_bounding_box[2] = bb_node->getChild("max-x", 0, true);
|
||||||
|
_bounding_box[3] = bb_node->getChild("max-y", 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_bounding_box[0]->setFloatValue(bb._min.x());
|
||||||
|
_bounding_box[1]->setFloatValue(bb._min.y());
|
||||||
|
_bounding_box[2]->setFloatValue(bb._max.x());
|
||||||
|
_bounding_box[3]->setFloatValue(bb._max.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
osg::BoundingBox Element::getTransformedBounds(const osg::Matrix& m) const
|
||||||
|
{
|
||||||
|
if( !_drawable )
|
||||||
|
return osg::BoundingBox();
|
||||||
|
|
||||||
|
osg::BoundingBox transformed;
|
||||||
|
const osg::BoundingBox& bb = _drawable->getBound();
|
||||||
|
for(int i = 0; i < 4; ++i)
|
||||||
|
transformed.expandBy( bb.corner(i) * m );
|
||||||
|
|
||||||
|
return transformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
osg::Matrix Element::getMatrix() const
|
||||||
|
{
|
||||||
|
if( !(_attributes_dirty & TRANSFORM) )
|
||||||
|
return _transform->getMatrix();
|
||||||
|
|
||||||
osg::Matrix m;
|
osg::Matrix m;
|
||||||
for( size_t i = 0; i < _transform_types.size(); ++i )
|
for( size_t i = 0; i < _transform_types.size(); ++i )
|
||||||
{
|
{
|
||||||
@@ -128,293 +586,14 @@ namespace canvas
|
|||||||
m.postMult( tf );
|
m.postMult( tf );
|
||||||
}
|
}
|
||||||
_transform->setMatrix(m);
|
_transform->setMatrix(m);
|
||||||
_transform_dirty = false;
|
_attributes_dirty &= ~TRANSFORM;
|
||||||
}
|
_attributes_dirty |= SCISSOR_COORDS;
|
||||||
|
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
naRef Element::addEventListener(const nasal::CallContext& ctx)
|
Element::StyleSetters Element::_style_setters;
|
||||||
{
|
|
||||||
const std::string type_str = ctx.requireArg<std::string>(0);
|
|
||||||
naRef code = ctx.requireArg<naRef>(1);
|
|
||||||
|
|
||||||
SG_LOG
|
|
||||||
(
|
|
||||||
SG_NASAL,
|
|
||||||
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() );
|
|
||||||
|
|
||||||
_listener[ type ].push_back
|
|
||||||
(
|
|
||||||
boost::make_shared<EventListener>( code,
|
|
||||||
_canvas.lock()->getSystemAdapter() )
|
|
||||||
);
|
|
||||||
|
|
||||||
return naNil();
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
bool Element::accept(EventVisitor& visitor)
|
|
||||||
{
|
|
||||||
return visitor.apply(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
bool Element::ascend(EventVisitor& visitor)
|
|
||||||
{
|
|
||||||
if( _parent )
|
|
||||||
return _parent->accept(visitor);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
bool Element::traverse(EventVisitor& visitor)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
void Element::callListeners(const canvas::EventPtr& event)
|
|
||||||
{
|
|
||||||
ListenerMap::iterator listeners = _listener.find(event->getType());
|
|
||||||
if( listeners == _listener.end() )
|
|
||||||
return;
|
|
||||||
|
|
||||||
BOOST_FOREACH(EventListenerPtr listener, listeners->second)
|
|
||||||
listener->call(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
bool Element::hitBound( const osg::Vec2f& pos,
|
|
||||||
const osg::Vec2f& local_pos ) const
|
|
||||||
{
|
|
||||||
const osg::Vec3f pos3(pos, 0);
|
|
||||||
|
|
||||||
// Drawables have a bounding box...
|
|
||||||
if( _drawable )
|
|
||||||
{
|
|
||||||
if( !_drawable->getBound().contains(osg::Vec3f(local_pos, 0)) )
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// ... 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
osg::ref_ptr<osg::MatrixTransform> Element::getMatrixTransform()
|
|
||||||
{
|
|
||||||
return _transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
void Element::childAdded(SGPropertyNode* parent, SGPropertyNode* child)
|
|
||||||
{
|
|
||||||
if( parent == _node
|
|
||||||
&& child->getNameString() == NAME_TRANSFORM )
|
|
||||||
{
|
|
||||||
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
|
|
||||||
_transform_types.resize( child->getIndex() + 1 );
|
|
||||||
|
|
||||||
_transform_types[ child->getIndex() ] = TT_NONE;
|
|
||||||
_transform_dirty = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if( parent->getParent() == _node
|
|
||||||
&& parent->getNameString() == NAME_TRANSFORM )
|
|
||||||
{
|
|
||||||
assert(parent->getIndex() < static_cast<int>(_transform_types.size()));
|
|
||||||
|
|
||||||
const std::string& name = child->getNameString();
|
|
||||||
|
|
||||||
TransformType& type = _transform_types[parent->getIndex()];
|
|
||||||
|
|
||||||
if( name == "m" )
|
|
||||||
type = TT_MATRIX;
|
|
||||||
else if( name == "t" )
|
|
||||||
type = TT_TRANSLATE;
|
|
||||||
else if( name == "rot" )
|
|
||||||
type = TT_ROTATE;
|
|
||||||
else if( name == "s" )
|
|
||||||
type = TT_SCALE;
|
|
||||||
|
|
||||||
_transform_dirty = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
childAdded(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
|
|
||||||
{
|
|
||||||
if( parent == _node && child->getNameString() == NAME_TRANSFORM )
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
childRemoved(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
void Element::valueChanged(SGPropertyNode* child)
|
|
||||||
{
|
|
||||||
SGPropertyNode *parent = child->getParent();
|
|
||||||
if( parent == _node )
|
|
||||||
{
|
|
||||||
if( setStyle(child) )
|
|
||||||
return;
|
|
||||||
else if( child->getNameString() == "update" )
|
|
||||||
return update(0);
|
|
||||||
else if( child->getNameString() == "visible" )
|
|
||||||
// TODO check if we need another nodemask
|
|
||||||
return _transform->setNodeMask( child->getBoolValue() ? 0xffffffff : 0 );
|
|
||||||
}
|
|
||||||
else if( parent->getParent() == _node
|
|
||||||
&& parent->getNameString() == NAME_TRANSFORM )
|
|
||||||
{
|
|
||||||
_transform_dirty = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
childChanged(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
bool Element::setStyle(const SGPropertyNode* child)
|
|
||||||
{
|
|
||||||
StyleSetters::const_iterator setter =
|
|
||||||
_style_setters.find(child->getNameString());
|
|
||||||
if( setter == _style_setters.end() )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
setter->second(child);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
void Element::setClip(const std::string& clip)
|
|
||||||
{
|
|
||||||
if( clip.empty() || clip == "auto" )
|
|
||||||
{
|
|
||||||
getOrCreateStateSet()->removeAttribute(osg::StateAttribute::SCISSOR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO generalize CSS property parsing
|
|
||||||
const std::string RECT("rect(");
|
|
||||||
if( !boost::ends_with(clip, ")")
|
|
||||||
|| !boost::starts_with(clip, RECT) )
|
|
||||||
{
|
|
||||||
SG_LOG(SG_GENERAL, SG_WARN, "Canvas: invalid clip: " << clip);
|
|
||||||
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);
|
|
||||||
int comp = 0;
|
|
||||||
int values[4];
|
|
||||||
for( tokenizer::const_iterator tok = tokens.begin();
|
|
||||||
tok != tokens.end() && comp < 4;
|
|
||||||
++tok, ++comp )
|
|
||||||
{
|
|
||||||
values[comp] = boost::lexical_cast<int>(*tok);
|
|
||||||
}
|
|
||||||
|
|
||||||
if( comp < 4 )
|
|
||||||
{
|
|
||||||
SG_LOG(SG_GENERAL, SG_WARN, "Canvas: invalid clip: " << clip);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float scale_x = 1,
|
|
||||||
scale_y = 1;
|
|
||||||
|
|
||||||
CanvasPtr canvas = _canvas.lock();
|
|
||||||
if( canvas )
|
|
||||||
{
|
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::Scissor* scissor = new osg::Scissor();
|
|
||||||
// <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]);
|
|
||||||
|
|
||||||
if( canvas )
|
|
||||||
// Canvas has y axis upside down
|
|
||||||
scissor->y() = canvas->getSizeY() - scissor->y() - scissor->height();
|
|
||||||
|
|
||||||
getOrCreateStateSet()->setAttributeAndModes(scissor);
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
void Element::setBoundingBox(const osg::BoundingBox& bb)
|
|
||||||
{
|
|
||||||
if( _bounding_box.empty() )
|
|
||||||
{
|
|
||||||
SGPropertyNode* bb_node = _node->getChild("bounding-box", 0, true);
|
|
||||||
_bounding_box.resize(4);
|
|
||||||
_bounding_box[0] = bb_node->getChild("min-x", 0, true);
|
|
||||||
_bounding_box[1] = bb_node->getChild("min-y", 0, true);
|
|
||||||
_bounding_box[2] = bb_node->getChild("max-x", 0, true);
|
|
||||||
_bounding_box[3] = bb_node->getChild("max-y", 0, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
_bounding_box[0]->setFloatValue(bb._min.x());
|
|
||||||
_bounding_box[1]->setFloatValue(bb._min.y());
|
|
||||||
_bounding_box[2]->setFloatValue(bb._max.x());
|
|
||||||
_bounding_box[3]->setFloatValue(bb._max.y());
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
osg::BoundingBox Element::getTransformedBounds(const osg::Matrix& m) const
|
|
||||||
{
|
|
||||||
if( !_drawable )
|
|
||||||
return osg::BoundingBox();
|
|
||||||
|
|
||||||
osg::BoundingBox transformed;
|
|
||||||
const osg::BoundingBox& bb = _drawable->getBound();
|
|
||||||
for(int i = 0; i < 4; ++i)
|
|
||||||
transformed.expandBy( m * bb.corner(i) );
|
|
||||||
|
|
||||||
return transformed;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
Element::Element( const CanvasWeakPtr& canvas,
|
Element::Element( const CanvasWeakPtr& canvas,
|
||||||
@@ -424,11 +603,14 @@ namespace canvas
|
|||||||
PropertyBasedElement(node),
|
PropertyBasedElement(node),
|
||||||
_canvas( canvas ),
|
_canvas( canvas ),
|
||||||
_parent( parent ),
|
_parent( parent ),
|
||||||
_transform_dirty( false ),
|
_attributes_dirty( 0 ),
|
||||||
_transform( new osg::MatrixTransform ),
|
_transform( new osg::MatrixTransform ),
|
||||||
_style( parent_style ),
|
_style( parent_style ),
|
||||||
|
_scissor( 0 ),
|
||||||
_drawable( 0 )
|
_drawable( 0 )
|
||||||
{
|
{
|
||||||
|
staticInit();
|
||||||
|
|
||||||
SG_LOG
|
SG_LOG
|
||||||
(
|
(
|
||||||
SG_GL,
|
SG_GL,
|
||||||
@@ -436,7 +618,93 @@ namespace canvas
|
|||||||
"New canvas element " << node->getPath()
|
"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/canvas/CanvasEvent.hxx>
|
||||||
#include <simgear/props/PropertyBasedElement.hxx>
|
#include <simgear/props/PropertyBasedElement.hxx>
|
||||||
#include <simgear/misc/stdint.hxx> // for uint32_t
|
#include <simgear/misc/stdint.hxx> // for uint32_t
|
||||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
|
||||||
|
|
||||||
#include <osg/BoundingBox>
|
#include <osg/BoundingBox>
|
||||||
#include <osg/MatrixTransform>
|
#include <osg/MatrixTransform>
|
||||||
|
|
||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
#include <boost/function.hpp>
|
#include <boost/function.hpp>
|
||||||
|
#include <boost/type_traits/is_base_of.hpp>
|
||||||
|
|
||||||
namespace osg
|
namespace osg
|
||||||
{
|
{
|
||||||
@@ -45,24 +45,57 @@ namespace canvas
|
|||||||
public PropertyBasedElement
|
public PropertyBasedElement
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef boost::function<void(const SGPropertyNode*)> StyleSetter;
|
|
||||||
typedef std::map<std::string, StyleSetter> StyleSetters;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the property listener of the element.
|
* Store pointer to window as user data
|
||||||
*
|
|
||||||
* You will need to call the appropriate methods (#childAdded,
|
|
||||||
* #childRemoved, #valueChanged) yourself to ensure the element still
|
|
||||||
* receives the needed events.
|
|
||||||
*/
|
*/
|
||||||
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 ~Element() = 0;
|
||||||
|
|
||||||
|
virtual void setSelf(const PropertyBasedElementPtr& self);
|
||||||
|
virtual void onDestroy();
|
||||||
|
|
||||||
ElementWeakPtr getWeakPtr() const;
|
ElementWeakPtr getWeakPtr() const;
|
||||||
|
ElementPtr getParent();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called every frame to update internal state
|
* Called every frame to update internal state
|
||||||
@@ -71,19 +104,26 @@ namespace canvas
|
|||||||
*/
|
*/
|
||||||
virtual void update(double dt);
|
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 accept(EventVisitor& visitor);
|
||||||
virtual bool ascend(EventVisitor& visitor);
|
virtual bool ascend(EventVisitor& visitor);
|
||||||
virtual bool traverse(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,
|
virtual bool hitBound( const osg::Vec2f& pos,
|
||||||
const osg::Vec2f& local_pos ) const;
|
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,
|
virtual void childAdded( SGPropertyNode * parent,
|
||||||
SGPropertyNode * child );
|
SGPropertyNode * child );
|
||||||
@@ -91,7 +131,8 @@ namespace canvas
|
|||||||
SGPropertyNode * child );
|
SGPropertyNode * child );
|
||||||
virtual void valueChanged(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
|
* Set clipping shape
|
||||||
@@ -101,6 +142,11 @@ namespace canvas
|
|||||||
*/
|
*/
|
||||||
void setClip(const std::string& clip);
|
void setClip(const std::string& clip);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clipping coordinates reference frame
|
||||||
|
*/
|
||||||
|
void setClipFrame(ReferenceFrame rf);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write the given bounding box to the property tree
|
* Write the given bounding box to the property tree
|
||||||
*/
|
*/
|
||||||
@@ -111,11 +157,39 @@ namespace canvas
|
|||||||
*/
|
*/
|
||||||
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
|
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the transformation matrix (product of all transforms)
|
||||||
|
*/
|
||||||
|
osg::Matrix getMatrix() 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:
|
protected:
|
||||||
|
|
||||||
enum Attributes
|
enum Attributes
|
||||||
{
|
{
|
||||||
LAST_ATTRIBUTE = 0x0001
|
TRANSFORM = 1,
|
||||||
|
BLEND_FUNC = TRANSFORM << 1,
|
||||||
|
SCISSOR_COORDS = BLEND_FUNC << 1,
|
||||||
|
LAST_ATTRIBUTE = SCISSOR_COORDS << 1
|
||||||
};
|
};
|
||||||
|
|
||||||
enum TransformType
|
enum TransformType
|
||||||
@@ -127,64 +201,331 @@ namespace canvas
|
|||||||
TT_SCALE
|
TT_SCALE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class RelativeScissor;
|
||||||
|
|
||||||
CanvasWeakPtr _canvas;
|
CanvasWeakPtr _canvas;
|
||||||
Element *_parent;
|
Element *_parent;
|
||||||
|
|
||||||
uint32_t _attributes_dirty;
|
mutable uint32_t _attributes_dirty;
|
||||||
|
|
||||||
bool _transform_dirty;
|
osg::observer_ptr<osg::MatrixTransform> _transform;
|
||||||
osg::ref_ptr<osg::MatrixTransform> _transform;
|
|
||||||
std::vector<TransformType> _transform_types;
|
std::vector<TransformType> _transform_types;
|
||||||
|
|
||||||
Style _style;
|
Style _style;
|
||||||
StyleSetters _style_setters;
|
|
||||||
std::vector<SGPropertyNode_ptr> _bounding_box;
|
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;
|
typedef std::map<Event::Type, Listener> ListenerMap;
|
||||||
|
|
||||||
ListenerMap _listener;
|
ListenerMap _listener;
|
||||||
|
|
||||||
|
typedef std::map<std::string, StyleInfo> StyleSetters;
|
||||||
|
static StyleSetters _style_setters;
|
||||||
|
|
||||||
|
static void staticInit();
|
||||||
|
|
||||||
Element( const CanvasWeakPtr& canvas,
|
Element( const CanvasWeakPtr& canvas,
|
||||||
const SGPropertyNode_ptr& node,
|
const SGPropertyNode_ptr& node,
|
||||||
const Style& parent_style,
|
const Style& parent_style,
|
||||||
Element* parent );
|
Element* parent );
|
||||||
|
|
||||||
template<typename T, class C1, class C2>
|
/**
|
||||||
Element::StyleSetter
|
* Returns false on first call and true on any successive call. Use to
|
||||||
addStyle(const std::string& name, void (C1::*setter)(T), C2 instance)
|
* 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 ] =
|
static bool is_init = false;
|
||||||
bindStyleSetter<T>(name, setter, instance);
|
if( is_init )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
is_init = true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T1, typename T2, class C1, class C2>
|
/**
|
||||||
Element::StyleSetter
|
* Register a function for setting a style specified by the given property
|
||||||
addStyle(const std::string& name, void (C1::*setter)(T2), C2 instance)
|
*
|
||||||
{
|
* @param name Property name
|
||||||
return _style_setters[ name ] =
|
* @param type Interpolation type
|
||||||
bindStyleSetter<T1>(name, setter, instance);
|
* @param setter Setter function
|
||||||
}
|
*
|
||||||
|
* @tparam T1 Type of value used to retrieve value from property
|
||||||
template<class C1, class C2>
|
* node
|
||||||
Element::StyleSetter
|
* @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,
|
addStyle( const std::string& name,
|
||||||
void (C1::*setter)(const std::string&),
|
const std::string& type,
|
||||||
C2 instance )
|
const boost::function<void (Derived&, T2)>& setter,
|
||||||
|
bool inheritable = true )
|
||||||
{
|
{
|
||||||
return _style_setters[ name ] =
|
StyleInfo& style_info = _style_setters[ name ];
|
||||||
bindStyleSetter<const char*>(name, setter, instance);
|
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>
|
template<
|
||||||
Element::StyleSetter
|
typename T,
|
||||||
bindStyleSetter( const std::string& name,
|
class Derived
|
||||||
void (C1::*setter)(T2),
|
>
|
||||||
C2 instance )
|
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 childAdded(SGPropertyNode * child) {}
|
||||||
virtual void childRemoved(SGPropertyNode * child){}
|
virtual void childRemoved(SGPropertyNode * child){}
|
||||||
virtual void childChanged(SGPropertyNode * child){}
|
virtual void childChanged(SGPropertyNode * child){}
|
||||||
@@ -203,9 +544,63 @@ namespace canvas
|
|||||||
osg::ref_ptr<osg::Drawable> _drawable;
|
osg::ref_ptr<osg::Drawable> _drawable;
|
||||||
|
|
||||||
Element(const Element&);// = delete
|
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
|
} // 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
|
} // namespace simgear
|
||||||
|
|
||||||
#endif /* CANVAS_ELEMENT_HXX_ */
|
#endif /* CANVAS_ELEMENT_HXX_ */
|
||||||
|
|||||||
@@ -33,17 +33,37 @@ namespace simgear
|
|||||||
namespace canvas
|
namespace canvas
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Create an canvas Element of type T
|
* Add canvas Element type to factory map
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template<typename ElementType>
|
||||||
ElementPtr createElement( const CanvasWeakPtr& canvas,
|
void add(ElementFactories& factories)
|
||||||
const SGPropertyNode_ptr& node,
|
|
||||||
const Style& style,
|
|
||||||
Element* parent )
|
|
||||||
{
|
{
|
||||||
ElementPtr el( new T(canvas, node, style, parent) );
|
ElementType::staticInit();
|
||||||
el->setSelf(el);
|
factories[ElementType::TYPE_NAME] = &Element::create<ElementType>;
|
||||||
return el;
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
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* parent ):
|
||||||
Element(canvas, node, parent_style, parent)
|
Element(canvas, node, parent_style, parent)
|
||||||
{
|
{
|
||||||
_child_factories["group"] = &createElement<Group>;
|
staticInit();
|
||||||
_child_factories["image"] = &createElement<Image>;
|
|
||||||
_child_factories["map" ] = &createElement<Map >;
|
|
||||||
_child_factories["path" ] = &createElement<Path >;
|
|
||||||
_child_factories["text" ] = &createElement<Text >;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
@@ -80,22 +96,54 @@ namespace canvas
|
|||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
ElementPtr Group::getChild(const SGPropertyNode* node)
|
ElementPtr Group::getChild(const SGPropertyNode* node)
|
||||||
{
|
{
|
||||||
ChildList::iterator child = findChild(node);
|
return findChild(node, "");
|
||||||
if( child == _children.end() )
|
}
|
||||||
return ElementPtr();
|
|
||||||
|
|
||||||
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)
|
ElementPtr Group::getElementById(const std::string& id)
|
||||||
{
|
{
|
||||||
std::vector<GroupPtr> groups;
|
if( !_transform.valid() )
|
||||||
|
|
||||||
BOOST_FOREACH( ChildList::value_type child, _children )
|
|
||||||
{
|
{
|
||||||
const ElementPtr& el = child.second;
|
warnTransformExpired("getElementById");
|
||||||
if( el->getProps()->getStringValue("id") == id )
|
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;
|
return el;
|
||||||
|
|
||||||
GroupPtr group = boost::dynamic_pointer_cast<Group>(el);
|
GroupPtr group = boost::dynamic_pointer_cast<Group>(el);
|
||||||
@@ -113,11 +161,23 @@ namespace canvas
|
|||||||
return ElementPtr();
|
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)
|
void Group::update(double dt)
|
||||||
{
|
{
|
||||||
BOOST_FOREACH( ChildList::value_type child, _children )
|
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||||
child.second->update(dt);
|
getChildByIndex(i)->update(dt);
|
||||||
|
|
||||||
Element::update(dt);
|
Element::update(dt);
|
||||||
}
|
}
|
||||||
@@ -126,30 +186,32 @@ namespace canvas
|
|||||||
bool Group::traverse(EventVisitor& visitor)
|
bool Group::traverse(EventVisitor& visitor)
|
||||||
{
|
{
|
||||||
// Iterate in reverse order as last child is displayed on top
|
// 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 true;
|
||||||
}
|
}
|
||||||
return false;
|
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( !canApplyStyle(style) )
|
||||||
if( Element::setStyle(style) )
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if( style->getParent() != _node
|
|
||||||
&& _style.find(style->getNameString()) != _style.end() )
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool handled = false;
|
bool handled = setStyleImpl(style, style_info);
|
||||||
BOOST_FOREACH( ChildList::value_type child, _children )
|
if( style_info->inheritable )
|
||||||
{
|
{
|
||||||
if( child.second->setStyle(style) )
|
if( !_transform.valid() )
|
||||||
handled = true;
|
{
|
||||||
|
warnTransformExpired("setStyle");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||||
|
handled |= getChildByIndex(i)->setStyle(style, style_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handled;
|
return handled;
|
||||||
@@ -159,17 +221,23 @@ namespace canvas
|
|||||||
osg::BoundingBox Group::getTransformedBounds(const osg::Matrix& m) const
|
osg::BoundingBox Group::getTransformedBounds(const osg::Matrix& m) const
|
||||||
{
|
{
|
||||||
osg::BoundingBox bb;
|
osg::BoundingBox bb;
|
||||||
|
if( !_transform.valid() )
|
||||||
BOOST_FOREACH( ChildList::value_type child, _children )
|
|
||||||
{
|
{
|
||||||
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;
|
continue;
|
||||||
|
|
||||||
bb.expandBy
|
bb.expandBy
|
||||||
(
|
(
|
||||||
child.second->getTransformedBounds
|
child->getTransformedBounds
|
||||||
(
|
(
|
||||||
child.second->getMatrixTransform()->getMatrix() * m
|
child->getMatrixTransform()->getMatrix() * m
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -177,31 +245,42 @@ namespace canvas
|
|||||||
return bb;
|
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)
|
void Group::childAdded(SGPropertyNode* child)
|
||||||
{
|
{
|
||||||
if( child->getParent() != _node )
|
if( child->getParent() != _node )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ChildFactories::iterator child_factory =
|
ElementFactory child_factory = getChildFactory( child->getNameString() );
|
||||||
_child_factories.find( child->getNameString() );
|
if( child_factory )
|
||||||
if( child_factory != _child_factories.end() )
|
|
||||||
{
|
{
|
||||||
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...
|
// Add to osg scene graph...
|
||||||
_transform->addChild( element->getMatrixTransform() );
|
_transform->addChild( element->getMatrixTransform() );
|
||||||
_children.push_back( ChildList::value_type(child, element) );
|
|
||||||
|
// ...and ensure correct ordering
|
||||||
|
handleZIndexChanged(element);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !Element::setStyle(child) )
|
StyleInfo const* style = getStyleInfo(child->getNameString());
|
||||||
{
|
if( style && style->inheritable )
|
||||||
// Only add style if not applicable to group itself
|
|
||||||
_style[ child->getNameString() ] = child;
|
_style[ child->getNameString() ] = child;
|
||||||
setStyle(child);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
@@ -210,10 +289,15 @@ namespace canvas
|
|||||||
if( node->getParent() != _node )
|
if( node->getParent() != _node )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if( _child_factories.find(node->getNameString()) != _child_factories.end() )
|
if( getChildFactory(node->getNameString()) )
|
||||||
{
|
{
|
||||||
ChildList::iterator child = findChild(node);
|
if( !_transform.valid() )
|
||||||
if( child == _children.end() )
|
// 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_LOG
|
||||||
(
|
(
|
||||||
SG_GL,
|
SG_GL,
|
||||||
@@ -222,15 +306,14 @@ namespace canvas
|
|||||||
);
|
);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_transform->removeChild( child->second->getMatrixTransform() );
|
// Remove child from the scenegraph (this automatically invalidates the
|
||||||
_children.erase(child);
|
// reference on the element hold by the MatrixTransform)
|
||||||
|
child->onDestroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Style::iterator style = _style.find(node->getNameString());
|
_style.erase( node->getNameString() );
|
||||||
if( style != _style.end() )
|
|
||||||
_style.erase(style);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,54 +322,52 @@ namespace canvas
|
|||||||
{
|
{
|
||||||
if( node->getParent()->getParent() == _node
|
if( node->getParent()->getParent() == _node
|
||||||
&& node->getNameString() == "z-index" )
|
&& 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 || !_transform.valid() )
|
||||||
if( child == _children.end() )
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
osg::Node* tf = child->second->getMatrixTransform();
|
osg::ref_ptr<osg::MatrixTransform> tf = child->getMatrixTransform();
|
||||||
int index = _transform->getChildIndex(tf),
|
size_t index = _transform->getChildIndex(tf),
|
||||||
index_new = index;
|
index_new = index;
|
||||||
|
|
||||||
ChildList::iterator next = child;
|
for(;; ++index_new)
|
||||||
++next;
|
|
||||||
|
|
||||||
while( next != _children.end()
|
|
||||||
&& next->first->getIntValue("z-index", 0) <= z_index )
|
|
||||||
{
|
{
|
||||||
++index_new;
|
if( index_new + 1 == _transform->getNumChildren() )
|
||||||
++next;
|
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);
|
// We were not able to move upwards so now try downwards
|
||||||
}
|
for(;; --index_new)
|
||||||
else
|
|
||||||
{
|
{
|
||||||
ChildList::iterator prev = child;
|
if( index_new == 0 )
|
||||||
while( prev != _children.begin()
|
break;
|
||||||
&& (--prev)->first->getIntValue("z-index", 0) > z_index)
|
|
||||||
{
|
// Move to end of block with same index (= move downwards until the
|
||||||
--index_new;
|
// 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 )
|
if( index == index_new )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_children.insert(prev, *child);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_transform->removeChild(index);
|
_transform->removeChild(index);
|
||||||
_transform->insertChild(index_new, tf);
|
_transform->insertChild(index_new, tf);
|
||||||
|
|
||||||
_children.erase(child);
|
|
||||||
|
|
||||||
SG_LOG
|
SG_LOG
|
||||||
(
|
(
|
||||||
SG_GENERAL,
|
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
|
assert(_transform.valid());
|
||||||
(
|
OSGUserData* ud =
|
||||||
_children.begin(),
|
static_cast<OSGUserData*>(_transform->getChild(index)->getUserData());
|
||||||
_children.end(),
|
assert(ud);
|
||||||
boost::bind(&ChildList::value_type::first, _1) == node
|
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
|
} // namespace canvas
|
||||||
|
|||||||
@@ -28,10 +28,15 @@ namespace simgear
|
|||||||
namespace canvas
|
namespace canvas
|
||||||
{
|
{
|
||||||
|
|
||||||
|
typedef std::map<std::string, ElementFactory> ElementFactories;
|
||||||
|
|
||||||
class Group:
|
class Group:
|
||||||
public Element
|
public Element
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static const std::string TYPE_NAME;
|
||||||
|
static void staticInit();
|
||||||
|
|
||||||
typedef std::list< std::pair< const SGPropertyNode*,
|
typedef std::list< std::pair< const SGPropertyNode*,
|
||||||
ElementPtr
|
ElementPtr
|
||||||
>
|
>
|
||||||
@@ -46,6 +51,34 @@ namespace canvas
|
|||||||
ElementPtr createChild( const std::string& type,
|
ElementPtr createChild( const std::string& type,
|
||||||
const std::string& id = "" );
|
const std::string& id = "" );
|
||||||
ElementPtr getChild(const SGPropertyNode* node);
|
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)
|
* Get first child with given id (breadth-first search)
|
||||||
@@ -54,28 +87,36 @@ namespace canvas
|
|||||||
*/
|
*/
|
||||||
ElementPtr getElementById(const std::string& id);
|
ElementPtr getElementById(const std::string& id);
|
||||||
|
|
||||||
|
virtual void clearEventListener();
|
||||||
|
|
||||||
virtual void update(double dt);
|
virtual void update(double dt);
|
||||||
|
|
||||||
virtual bool traverse(EventVisitor& visitor);
|
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;
|
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
|
||||||
|
|
||||||
protected:
|
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 childAdded(SGPropertyNode * child);
|
||||||
virtual void childRemoved(SGPropertyNode * child);
|
virtual void childRemoved(SGPropertyNode * child);
|
||||||
virtual void childChanged(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
|
} // namespace canvas
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
#include <simgear/canvas/Canvas.hxx>
|
#include <simgear/canvas/Canvas.hxx>
|
||||||
#include <simgear/canvas/CanvasMgr.hxx>
|
#include <simgear/canvas/CanvasMgr.hxx>
|
||||||
#include <simgear/canvas/CanvasSystemAdapter.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/scene/util/parse_color.hxx>
|
||||||
#include <simgear/misc/sg_path.hxx>
|
#include <simgear/misc/sg_path.hxx>
|
||||||
|
|
||||||
@@ -43,9 +45,11 @@ namespace canvas
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CullCallback(const CanvasWeakPtr& canvas);
|
CullCallback(const CanvasWeakPtr& canvas);
|
||||||
|
void cullNextFrame();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CanvasWeakPtr _canvas;
|
CanvasWeakPtr _canvas;
|
||||||
|
mutable bool _cull_next_frame;
|
||||||
|
|
||||||
virtual bool cull( osg::NodeVisitor* nv,
|
virtual bool cull( osg::NodeVisitor* nv,
|
||||||
osg::Drawable* drawable,
|
osg::Drawable* drawable,
|
||||||
@@ -54,11 +58,18 @@ namespace canvas
|
|||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
CullCallback::CullCallback(const CanvasWeakPtr& 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,
|
bool CullCallback::cull( osg::NodeVisitor* nv,
|
||||||
osg::Drawable* drawable,
|
osg::Drawable* drawable,
|
||||||
@@ -67,8 +78,27 @@ namespace canvas
|
|||||||
if( !_canvas.expired() )
|
if( !_canvas.expired() )
|
||||||
_canvas.lock()->enableRendering();
|
_canvas.lock()->enableRendering();
|
||||||
|
|
||||||
|
if( !_cull_next_frame )
|
||||||
// TODO check if window/image should be culled
|
// TODO check if window/image should be culled
|
||||||
return false;
|
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),
|
_src_rect(0,0),
|
||||||
_region(0,0)
|
_region(0,0)
|
||||||
{
|
{
|
||||||
|
staticInit();
|
||||||
|
|
||||||
_geom = new osg::Geometry;
|
_geom = new osg::Geometry;
|
||||||
_geom->setUseDisplayList(false);
|
_geom->setUseDisplayList(false);
|
||||||
|
|
||||||
@@ -91,28 +123,26 @@ namespace canvas
|
|||||||
|
|
||||||
// allocate arrays for the image
|
// allocate arrays for the image
|
||||||
_vertices = new osg::Vec3Array(4);
|
_vertices = new osg::Vec3Array(4);
|
||||||
_vertices->setDataVariance(osg::Object::STATIC);
|
_vertices->setDataVariance(osg::Object::DYNAMIC);
|
||||||
_geom->setVertexArray(_vertices);
|
_geom->setVertexArray(_vertices);
|
||||||
|
|
||||||
_texCoords = new osg::Vec2Array(4);
|
_texCoords = new osg::Vec2Array(4);
|
||||||
_texCoords->setDataVariance(osg::Object::STATIC);
|
_texCoords->setDataVariance(osg::Object::DYNAMIC);
|
||||||
_geom->setTexCoordArray(0, _texCoords);
|
_geom->setTexCoordArray(0, _texCoords);
|
||||||
|
|
||||||
_colors = new osg::Vec4Array(4);
|
_colors = new osg::Vec4Array(1);
|
||||||
_colors->setDataVariance(osg::Object::STATIC);
|
_colors->setDataVariance(osg::Object::DYNAMIC);
|
||||||
_geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
|
|
||||||
_geom->setColorArray(_colors);
|
_geom->setColorArray(_colors);
|
||||||
|
_geom->setColorBinding(osg::Geometry::BIND_OVERALL);
|
||||||
|
|
||||||
osg::DrawArrays* prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
|
_prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
|
||||||
prim->set(osg::PrimitiveSet::QUADS, 0, 4);
|
_prim->set(osg::PrimitiveSet::QUADS, 0, 4);
|
||||||
prim->setDataVariance(osg::Object::STATIC);
|
_prim->setDataVariance(osg::Object::DYNAMIC);
|
||||||
_geom->addPrimitiveSet(prim);
|
_geom->addPrimitiveSet(_prim);
|
||||||
|
|
||||||
setDrawable(_geom);
|
setDrawable(_geom);
|
||||||
|
|
||||||
addStyle("fill", &Image::setFill, this);
|
|
||||||
setFill("#ffffff"); // TODO how should we handle default values?
|
setFill("#ffffff"); // TODO how should we handle default values?
|
||||||
|
|
||||||
setupStyle();
|
setupStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,14 +157,119 @@ namespace canvas
|
|||||||
{
|
{
|
||||||
Element::update(dt);
|
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 )
|
if( _attributes_dirty & DEST_SIZE )
|
||||||
{
|
{
|
||||||
(*_vertices)[0].set(_region.l(), _region.t(), 0);
|
size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
|
||||||
(*_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();
|
|
||||||
|
|
||||||
|
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;
|
_attributes_dirty &= ~DEST_SIZE;
|
||||||
_geom->dirtyBound();
|
_geom->dirtyBound();
|
||||||
setBoundingBox(_geom->getBound());
|
setBoundingBox(_geom->getBound());
|
||||||
@@ -142,46 +277,105 @@ namespace canvas
|
|||||||
|
|
||||||
if( _attributes_dirty & SRC_RECT )
|
if( _attributes_dirty & SRC_RECT )
|
||||||
{
|
{
|
||||||
double u0 = _src_rect.l(),
|
SGRect<float> src_rect = _src_rect;
|
||||||
u1 = _src_rect.r(),
|
|
||||||
v0 = _src_rect.b(),
|
|
||||||
v1 = _src_rect.t();
|
|
||||||
|
|
||||||
if( !_node_src_rect->getBoolValue("normalized", true) )
|
if( !_node_src_rect->getBoolValue("normalized", true) )
|
||||||
{
|
{
|
||||||
const SGRect<int>& tex_dim = getTextureDimensions();
|
src_rect.t() /= tex_dim.height();
|
||||||
|
src_rect.r() /= tex_dim.width();
|
||||||
u0 /= tex_dim.width();
|
src_rect.b() /= tex_dim.height();
|
||||||
u1 /= tex_dim.width();
|
src_rect.l() /= tex_dim.width();
|
||||||
v0 /= tex_dim.height();
|
|
||||||
v1 /= tex_dim.height();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(*_texCoords)[0].set(u0, v0);
|
// Image coordinate systems y-axis is flipped
|
||||||
(*_texCoords)[1].set(u1, v0);
|
std::swap(src_rect.t(), src_rect.b());
|
||||||
(*_texCoords)[2].set(u1, v1);
|
|
||||||
(*_texCoords)[3].set(u0, v1);
|
|
||||||
_texCoords->dirty();
|
|
||||||
|
|
||||||
|
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;
|
_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)
|
void Image::setSrcCanvas(CanvasPtr canvas)
|
||||||
{
|
{
|
||||||
if( !_src_canvas.expired() )
|
CanvasPtr src_canvas = _src_canvas.lock(),
|
||||||
_src_canvas.lock()->removeDependentCanvas(_canvas);
|
self_canvas = _canvas.lock();
|
||||||
|
|
||||||
_src_canvas = canvas;
|
if( src_canvas )
|
||||||
_geom->getOrCreateStateSet()
|
src_canvas->removeParentCanvas(self_canvas);
|
||||||
->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
|
if( self_canvas )
|
||||||
|
self_canvas->removeChildCanvas(src_canvas);
|
||||||
|
|
||||||
|
_src_canvas = src_canvas = canvas;
|
||||||
|
_attributes_dirty |= SRC_CANVAS;
|
||||||
_geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
|
_geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
|
||||||
|
|
||||||
if( !_src_canvas.expired() )
|
if( src_canvas )
|
||||||
{
|
{
|
||||||
setupDefaultDimensions();
|
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...
|
// remove canvas...
|
||||||
setSrcCanvas( CanvasPtr() );
|
setSrcCanvas( CanvasPtr() );
|
||||||
|
|
||||||
|
_texture->setResizeNonPowerOfTwoHint(false);
|
||||||
_texture->setImage(img);
|
_texture->setImage(img);
|
||||||
|
_texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
|
||||||
|
_texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
|
||||||
_geom->getOrCreateStateSet()
|
_geom->getOrCreateStateSet()
|
||||||
->setTextureAttributeAndModes(0, _texture);
|
->setTextureAttributeAndModes(0, _texture);
|
||||||
|
|
||||||
@@ -208,21 +405,80 @@ namespace canvas
|
|||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void Image::setFill(const std::string& fill)
|
void Image::setFill(const std::string& fill)
|
||||||
{
|
{
|
||||||
osg::Vec4 color;
|
osg::Vec4 color(1,1,1,1);
|
||||||
if( !parseColor(fill, color) )
|
if( !fill.empty() // If no color is given default to white
|
||||||
|
&& !parseColor(fill, color) )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for( int i = 0; i < 4; ++i )
|
_colors->front() = color;
|
||||||
(*_colors)[i] = color;
|
|
||||||
_colors->dirty();
|
_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
|
const SGRect<float>& Image::getRegion() const
|
||||||
{
|
{
|
||||||
return _region;
|
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)
|
void Image::childChanged(SGPropertyNode* child)
|
||||||
{
|
{
|
||||||
@@ -309,7 +565,7 @@ namespace canvas
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setImage( canvas->getSystemAdapter()->getImage(path) );
|
setImage( Canvas::getSystemAdapter()->getImage(path) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,36 +575,67 @@ namespace canvas
|
|||||||
{
|
{
|
||||||
if( !_src_rect.width() || !_src_rect.height() )
|
if( !_src_rect.width() || !_src_rect.height() )
|
||||||
{
|
{
|
||||||
const SGRect<int>& tex_dim = getTextureDimensions();
|
// Show whole image by default
|
||||||
|
_node_src_rect->setBoolValue("normalized", true);
|
||||||
_node_src_rect->setBoolValue("normalized", false);
|
_node_src_rect->setFloatValue("right", 1);
|
||||||
_node_src_rect->setFloatValue("right", tex_dim.width());
|
_node_src_rect->setFloatValue("bottom", 1);
|
||||||
_node_src_rect->setFloatValue("bottom", tex_dim.height());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !_region.width() || !_region.height() )
|
if( !_region.width() || !_region.height() )
|
||||||
{
|
{
|
||||||
_node->setFloatValue("size[0]", _src_rect.width());
|
// Default to image size.
|
||||||
_node->setFloatValue("size[1]", _src_rect.height());
|
// 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
|
SGRect<int> Image::getTextureDimensions() const
|
||||||
{
|
{
|
||||||
osg::Texture2D *texture = !_src_canvas.expired()
|
CanvasPtr canvas = _src_canvas.lock();
|
||||||
? _src_canvas.lock()->getTexture()
|
SGRect<int> dim(0,0);
|
||||||
: _texture.get();
|
|
||||||
|
|
||||||
if( !texture )
|
// Use canvas/image dimensions rather than texture dimensions, as they could
|
||||||
return SGRect<int>();
|
// 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>
|
if( img )
|
||||||
(
|
{
|
||||||
0,0,
|
dim.setRight( img->s() );
|
||||||
texture->getTextureWidth(),
|
dim.setBottom( img->t() );
|
||||||
texture->getTextureHeight()
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
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
|
} // namespace canvas
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
#include "CanvasElement.hxx"
|
#include "CanvasElement.hxx"
|
||||||
|
|
||||||
#include <simgear/canvas/canvas_fwd.hxx>
|
#include <simgear/canvas/canvas_fwd.hxx>
|
||||||
#include <simgear/math/SGRect.hxx>
|
#include <simgear/misc/CSSBorder.hxx>
|
||||||
#include <osg/Texture2D>
|
#include <osg/Texture2D>
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
@@ -34,6 +34,9 @@ namespace canvas
|
|||||||
public Element
|
public Element
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static const std::string TYPE_NAME;
|
||||||
|
static void staticInit();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param node Property node containing settings for this image:
|
* @param node Property node containing settings for this image:
|
||||||
* rect/[left/right/top/bottom] Dimensions of source
|
* rect/[left/right/top/bottom] Dimensions of source
|
||||||
@@ -43,11 +46,12 @@ namespace canvas
|
|||||||
*/
|
*/
|
||||||
Image( const CanvasWeakPtr& canvas,
|
Image( const CanvasWeakPtr& canvas,
|
||||||
const SGPropertyNode_ptr& node,
|
const SGPropertyNode_ptr& node,
|
||||||
const Style& parent_style,
|
const Style& parent_style = Style(),
|
||||||
Element* parent = 0 );
|
Element* parent = 0 );
|
||||||
virtual ~Image();
|
virtual ~Image();
|
||||||
|
|
||||||
virtual void update(double dt);
|
virtual void update(double dt);
|
||||||
|
virtual void valueChanged(SGPropertyNode* child);
|
||||||
|
|
||||||
void setSrcCanvas(CanvasPtr canvas);
|
void setSrcCanvas(CanvasPtr canvas);
|
||||||
CanvasWeakPtr getSrcCanvas() const;
|
CanvasWeakPtr getSrcCanvas() const;
|
||||||
@@ -55,14 +59,40 @@ namespace canvas
|
|||||||
void setImage(osg::Image *img);
|
void setImage(osg::Image *img);
|
||||||
void setFill(const std::string& fill);
|
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;
|
const SGRect<float>& getRegion() const;
|
||||||
|
|
||||||
|
bool handleEvent(EventPtr event);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
enum ImageAttributes
|
enum ImageAttributes
|
||||||
{
|
{
|
||||||
SRC_RECT = LAST_ATTRIBUTE << 1, // Source image rectangle
|
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);
|
virtual void childChanged(SGPropertyNode * child);
|
||||||
@@ -70,11 +100,15 @@ namespace canvas
|
|||||||
void setupDefaultDimensions();
|
void setupDefaultDimensions();
|
||||||
SGRect<int> getTextureDimensions() const;
|
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;
|
osg::ref_ptr<osg::Texture2D> _texture;
|
||||||
// TODO optionally forward events to canvas
|
// TODO optionally forward events to canvas
|
||||||
CanvasWeakPtr _src_canvas;
|
CanvasWeakPtr _src_canvas;
|
||||||
|
|
||||||
osg::ref_ptr<osg::Geometry> _geom;
|
osg::ref_ptr<osg::Geometry> _geom;
|
||||||
|
osg::ref_ptr<osg::DrawArrays>_prim;
|
||||||
osg::ref_ptr<osg::Vec3Array> _vertices;
|
osg::ref_ptr<osg::Vec3Array> _vertices;
|
||||||
osg::ref_ptr<osg::Vec2Array> _texCoords;
|
osg::ref_ptr<osg::Vec2Array> _texCoords;
|
||||||
osg::ref_ptr<osg::Vec4Array> _colors;
|
osg::ref_ptr<osg::Vec4Array> _colors;
|
||||||
@@ -82,6 +116,10 @@ namespace canvas
|
|||||||
SGPropertyNode *_node_src_rect;
|
SGPropertyNode *_node_src_rect;
|
||||||
SGRect<float> _src_rect,
|
SGRect<float> _src_rect,
|
||||||
_region;
|
_region;
|
||||||
|
|
||||||
|
CSSBorder _slice,
|
||||||
|
_slice_width,
|
||||||
|
_outset;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace canvas
|
} // namespace canvas
|
||||||
|
|||||||
@@ -42,7 +42,20 @@ namespace simgear
|
|||||||
namespace canvas
|
namespace canvas
|
||||||
{
|
{
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
const std::string GEO = "-geo";
|
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,
|
Map::Map( const CanvasWeakPtr& canvas,
|
||||||
@@ -54,7 +67,7 @@ namespace canvas
|
|||||||
_projection(new SansonFlamsteedProjection),
|
_projection(new SansonFlamsteedProjection),
|
||||||
_projection_dirty(true)
|
_projection_dirty(true)
|
||||||
{
|
{
|
||||||
|
staticInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
@@ -192,6 +205,8 @@ namespace canvas
|
|||||||
_projection->setOrientation(child->getFloatValue());
|
_projection->setOrientation(child->getFloatValue());
|
||||||
else if( child->getNameString() == "range" )
|
else if( child->getNameString() == "range" )
|
||||||
_projection->setRange(child->getDoubleValue());
|
_projection->setRange(child->getDoubleValue());
|
||||||
|
else if( child->getNameString() == "screen-range" )
|
||||||
|
_projection->setScreenRange(child->getDoubleValue());
|
||||||
else
|
else
|
||||||
return Group::childChanged(child);
|
return Group::childChanged(child);
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ namespace canvas
|
|||||||
public Group
|
public Group
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static const std::string TYPE_NAME;
|
||||||
|
static void staticInit();
|
||||||
|
|
||||||
Map( const CanvasWeakPtr& canvas,
|
Map( const CanvasWeakPtr& canvas,
|
||||||
const SGPropertyNode_ptr& node,
|
const SGPropertyNode_ptr& node,
|
||||||
const Style& parent_style,
|
const Style& parent_style,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include <simgear/scene/util/parse_color.hxx>
|
#include <simgear/scene/util/parse_color.hxx>
|
||||||
|
|
||||||
#include <osg/Drawable>
|
#include <osg/Drawable>
|
||||||
#include <osg/BlendFunc>
|
|
||||||
|
|
||||||
#include <vg/openvg.h>
|
#include <vg/openvg.h>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
@@ -111,7 +110,7 @@ namespace canvas
|
|||||||
*/
|
*/
|
||||||
void setFill(const std::string& fill)
|
void setFill(const std::string& fill)
|
||||||
{
|
{
|
||||||
if( fill == "none" )
|
if( fill.empty() || fill == "none" )
|
||||||
{
|
{
|
||||||
_mode &= ~VG_FILL_PATH;
|
_mode &= ~VG_FILL_PATH;
|
||||||
}
|
}
|
||||||
@@ -151,7 +150,7 @@ namespace canvas
|
|||||||
*/
|
*/
|
||||||
void setStroke(const std::string& stroke)
|
void setStroke(const std::string& stroke)
|
||||||
{
|
{
|
||||||
if( stroke == "none" )
|
if( stroke.empty() || stroke == "none" )
|
||||||
{
|
{
|
||||||
_mode &= ~VG_STROKE_PATH;
|
_mode &= ~VG_STROKE_PATH;
|
||||||
}
|
}
|
||||||
@@ -278,7 +277,8 @@ namespace canvas
|
|||||||
{
|
{
|
||||||
osg::BoundingBox bb;
|
osg::BoundingBox bb;
|
||||||
|
|
||||||
osg::Vec2f cur; // VG "Current point" (in local coordinates)
|
osg::Vec2f cur(0, 0), // VG "Current point" (in local coordinates)
|
||||||
|
sub(0, 0); // beginning of current sub path
|
||||||
VGubyte cmd_index = 0;
|
VGubyte cmd_index = 0;
|
||||||
for( size_t i = 0, ci = 0;
|
for( size_t i = 0, ci = 0;
|
||||||
i < _cmds.size() && ci < _coords.size();
|
i < _cmds.size() && ci < _coords.size();
|
||||||
@@ -298,6 +298,7 @@ namespace canvas
|
|||||||
switch( cmd )
|
switch( cmd )
|
||||||
{
|
{
|
||||||
case VG_CLOSE_PATH:
|
case VG_CLOSE_PATH:
|
||||||
|
cur = sub;
|
||||||
break;
|
break;
|
||||||
case VG_MOVE_TO:
|
case VG_MOVE_TO:
|
||||||
case VG_LINE_TO:
|
case VG_LINE_TO:
|
||||||
@@ -353,7 +354,12 @@ namespace canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
if( num_coords > 0 )
|
if( num_coords > 0 )
|
||||||
|
{
|
||||||
cur = points[ num_coords - 1 ];
|
cur = points[ num_coords - 1 ];
|
||||||
|
|
||||||
|
if( cmd == VG_MOVE_TO )
|
||||||
|
sub = cur;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bb;
|
return bb;
|
||||||
@@ -429,6 +435,9 @@ namespace canvas
|
|||||||
*/
|
*/
|
||||||
void update()
|
void update()
|
||||||
{
|
{
|
||||||
|
if( !vgHasContextSH() )
|
||||||
|
return;
|
||||||
|
|
||||||
if( _attributes_dirty & PATH )
|
if( _attributes_dirty & PATH )
|
||||||
{
|
{
|
||||||
const VGbitfield caps = VG_PATH_CAPABILITY_APPEND_TO
|
const VGbitfield caps = VG_PATH_CAPABILITY_APPEND_TO
|
||||||
@@ -467,6 +476,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,
|
Path::Path( const CanvasWeakPtr& canvas,
|
||||||
const SGPropertyNode_ptr& node,
|
const SGPropertyNode_ptr& node,
|
||||||
@@ -475,16 +503,9 @@ namespace canvas
|
|||||||
Element(canvas, node, parent_style, parent),
|
Element(canvas, node, parent_style, parent),
|
||||||
_path( new PathDrawable(this) )
|
_path( new PathDrawable(this) )
|
||||||
{
|
{
|
||||||
|
staticInit();
|
||||||
|
|
||||||
setDrawable(_path);
|
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();
|
setupStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,6 +538,60 @@ namespace canvas
|
|||||||
return _path->getTransformedBounds(m);
|
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)
|
void Path::childRemoved(SGPropertyNode* child)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#define CANVAS_PATH_HXX_
|
#define CANVAS_PATH_HXX_
|
||||||
|
|
||||||
#include "CanvasElement.hxx"
|
#include "CanvasElement.hxx"
|
||||||
|
#include <boost/preprocessor/iteration/iterate.hpp>
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
{
|
{
|
||||||
@@ -29,6 +30,9 @@ namespace canvas
|
|||||||
public Element
|
public Element
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static const std::string TYPE_NAME;
|
||||||
|
static void staticInit();
|
||||||
|
|
||||||
Path( const CanvasWeakPtr& canvas,
|
Path( const CanvasWeakPtr& canvas,
|
||||||
const SGPropertyNode_ptr& node,
|
const SGPropertyNode_ptr& node,
|
||||||
const Style& parent_style,
|
const Style& parent_style,
|
||||||
@@ -39,6 +43,30 @@ namespace canvas
|
|||||||
|
|
||||||
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
|
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:
|
protected:
|
||||||
|
|
||||||
enum PathAttributes
|
enum PathAttributes
|
||||||
@@ -48,7 +76,8 @@ namespace canvas
|
|||||||
};
|
};
|
||||||
|
|
||||||
class PathDrawable;
|
class PathDrawable;
|
||||||
osg::ref_ptr<PathDrawable> _path;
|
typedef osg::ref_ptr<PathDrawable> PathDrawableRef;
|
||||||
|
PathDrawableRef _path;
|
||||||
|
|
||||||
virtual void childRemoved(SGPropertyNode * child);
|
virtual void childRemoved(SGPropertyNode * child);
|
||||||
virtual void childChanged(SGPropertyNode * child);
|
virtual void childChanged(SGPropertyNode * child);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include <simgear/canvas/Canvas.hxx>
|
#include <simgear/canvas/Canvas.hxx>
|
||||||
#include <simgear/canvas/CanvasSystemAdapter.hxx>
|
#include <simgear/canvas/CanvasSystemAdapter.hxx>
|
||||||
#include <simgear/scene/util/parse_color.hxx>
|
#include <simgear/scene/util/parse_color.hxx>
|
||||||
|
#include <osg/Version>
|
||||||
#include <osgText/Text>
|
#include <osgText/Text>
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
@@ -33,7 +34,9 @@ namespace canvas
|
|||||||
|
|
||||||
TextOSG(canvas::Text* text);
|
TextOSG(canvas::Text* text);
|
||||||
|
|
||||||
|
void setFontResolution(int res);
|
||||||
void setCharacterAspect(float aspect);
|
void setCharacterAspect(float aspect);
|
||||||
|
void setLineHeight(float factor);
|
||||||
void setFill(const std::string& fill);
|
void setFill(const std::string& fill);
|
||||||
void setBackgroundColor(const std::string& fill);
|
void setBackgroundColor(const std::string& fill);
|
||||||
|
|
||||||
@@ -44,13 +47,21 @@ namespace canvas
|
|||||||
protected:
|
protected:
|
||||||
|
|
||||||
canvas::Text *_text_element;
|
canvas::Text *_text_element;
|
||||||
|
|
||||||
|
virtual void computePositions(unsigned int contextID) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
Text::TextOSG::TextOSG(canvas::Text* text):
|
Text::TextOSG::TextOSG(canvas::Text* text):
|
||||||
_text_element(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);
|
setCharacterSize(getCharacterHeight(), aspect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void Text::TextOSG::setLineHeight(float factor)
|
||||||
|
{
|
||||||
|
setLineSpacing(factor - 1);
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void Text::TextOSG::setFill(const std::string& fill)
|
void Text::TextOSG::setFill(const std::string& fill)
|
||||||
{
|
{
|
||||||
@@ -165,16 +182,118 @@ namespace canvas
|
|||||||
if( !bb.valid() )
|
if( !bb.valid() )
|
||||||
return bb;
|
return bb;
|
||||||
|
|
||||||
|
#if OSG_VERSION_LESS_THAN(3,1,0)
|
||||||
// TODO bounding box still doesn't seem always right (eg. with center
|
// TODO bounding box still doesn't seem always right (eg. with center
|
||||||
// horizontal alignment not completely accurate)
|
// horizontal alignment not completely accurate)
|
||||||
bb._min.y() += _offset.y();
|
bb._min.y() += _offset.y();
|
||||||
bb._max.y() += _offset.y();
|
bb._max.y() += _offset.y();
|
||||||
|
#endif
|
||||||
|
|
||||||
_text_element->setBoundingBox(bb);
|
_text_element->setBoundingBox(bb);
|
||||||
|
|
||||||
return 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,
|
Text::Text( const CanvasWeakPtr& canvas,
|
||||||
const SGPropertyNode_ptr& node,
|
const SGPropertyNode_ptr& node,
|
||||||
@@ -183,28 +302,13 @@ namespace canvas
|
|||||||
Element(canvas, node, parent_style, parent),
|
Element(canvas, node, parent_style, parent),
|
||||||
_text( new Text::TextOSG(this) )
|
_text( new Text::TextOSG(this) )
|
||||||
{
|
{
|
||||||
|
staticInit();
|
||||||
|
|
||||||
setDrawable(_text);
|
setDrawable(_text);
|
||||||
_text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
|
_text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
|
||||||
_text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
|
_text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
|
||||||
_text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
|
_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();
|
setupStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +327,7 @@ namespace canvas
|
|||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
void Text::setFont(const char* name)
|
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 Element
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static const std::string TYPE_NAME;
|
||||||
|
static void staticInit();
|
||||||
|
|
||||||
Text( const CanvasWeakPtr& canvas,
|
Text( const CanvasWeakPtr& canvas,
|
||||||
const SGPropertyNode_ptr& node,
|
const SGPropertyNode_ptr& node,
|
||||||
const Style& parent_style,
|
const Style& parent_style,
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef CANVAS_PATH_HXX_
|
||||||
|
# error Canvas - do not include this file!
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define n BOOST_PP_ITERATION()
|
||||||
|
|
||||||
|
Path& addSegment( uint8_t cmd
|
||||||
|
BOOST_PP_COMMA_IF(n)
|
||||||
|
BOOST_PP_ENUM_PARAMS(n, float coord) )
|
||||||
|
{
|
||||||
|
_node->addChild("cmd")->setIntValue(cmd);
|
||||||
|
|
||||||
|
#define SG_CANVAS_PATH_SET_COORD(z, n, dummy)\
|
||||||
|
_node->addChild("coord")->setFloatValue(coord##n);
|
||||||
|
|
||||||
|
BOOST_PP_REPEAT(n, SG_CANVAS_PATH_SET_COORD, 0)
|
||||||
|
#undef SG_CANVAS_PATH_SET_COORD
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef n
|
||||||
+1
-1
@@ -46,7 +46,7 @@
|
|||||||
# warning GCC compilers prior to 3.4 are suspect
|
# warning GCC compilers prior to 3.4 are suspect
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
# define GCC_VERSION (__GNUC__ * 10000 \
|
# define SG_GCC_VERSION (__GNUC__ * 10000 \
|
||||||
+ __GNUC_MINOR__ * 100 \
|
+ __GNUC_MINOR__ * 100 \
|
||||||
+ __GNUC_PATCHLEVEL__)
|
+ __GNUC_PATCHLEVEL__)
|
||||||
# define SG_COMPILER_STR "GNU C++ version " SG_STRINGIZE(__GNUC__) "." SG_STRINGIZE(__GNUC_MINOR__)
|
# define SG_COMPILER_STR "GNU C++ version " SG_STRINGIZE(__GNUC__) "." SG_STRINGIZE(__GNUC_MINOR__)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// constants.h -- various constant definitions
|
// constants.h -- various constant definitions
|
||||||
//
|
//
|
||||||
// Written by Curtis Olson, started February 2000.
|
// 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/
|
// 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) */
|
/** Radius squared (meter) */
|
||||||
#define SG_EQ_RAD_SQUARE_M 40680645877797.1344
|
#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
|
// Conversions
|
||||||
|
|
||||||
/** Arc seconds to radians. (arcsec*pi)/(3600*180) = rad */
|
/** 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 */
|
/** Inch Mercury to Pascal */
|
||||||
#define SG_INHG_TO_PA 3386.388640341
|
#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 */
|
/** For divide by zero avoidance, this will be close enough to zero */
|
||||||
#define SG_EPSILON 0.0000001
|
#define SG_EPSILON 0.0000001
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
include (SimGearComponent)
|
||||||
|
|
||||||
set(HEADERS debug_types.h logstream.hxx)
|
set(HEADERS debug_types.h logstream.hxx BufferedLogCallback.hxx)
|
||||||
set(SOURCES logstream.cxx)
|
set(SOURCES logstream.cxx BufferedLogCallback.cxx)
|
||||||
|
|
||||||
simgear_component(debug debug "${SOURCES}" "${HEADERS}")
|
simgear_component(debug debug "${SOURCES}" "${HEADERS}")
|
||||||
+391
-64
@@ -20,95 +20,422 @@
|
|||||||
//
|
//
|
||||||
// $Id$
|
// $Id$
|
||||||
|
|
||||||
#include <iostream>
|
#include <simgear_config.h>
|
||||||
|
|
||||||
#include "logstream.hxx"
|
#include "logstream.hxx"
|
||||||
|
|
||||||
logstream *logstream::global_logstream = 0;
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
bool logbuf::logging_enabled = true;
|
#include <boost/foreach.hpp>
|
||||||
#ifdef _WIN32
|
|
||||||
bool logbuf::has_console = true;
|
#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
|
#endif
|
||||||
sgDebugClass logbuf::logClass = SG_NONE;
|
|
||||||
sgDebugPriority logbuf::logPriority = SG_INFO;
|
|
||||||
streambuf* logbuf::sbuf = NULL;
|
|
||||||
|
|
||||||
namespace {
|
const char* debugClassToString(sgDebugClass c)
|
||||||
struct ignore_me
|
|
||||||
{
|
{
|
||||||
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 )
|
public:
|
||||||
// sbuf = cerr.rdbuf();
|
WinDebugLogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||||
}
|
simgear::LogCallback(c, p)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
logbuf::~logbuf()
|
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());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class LogStreamPrivate : public SGThread
|
||||||
{
|
{
|
||||||
if ( sbuf )
|
private:
|
||||||
sync();
|
/**
|
||||||
}
|
* 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void
|
sgDebugClass debugClass;
|
||||||
logbuf::set_sb( streambuf* sb )
|
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)
|
||||||
|
{
|
||||||
|
|
||||||
|
#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 )
|
global_privateLogstream = new LogStreamPrivate;
|
||||||
sync();
|
global_privateLogstream->startLog();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
logstream::setLogLevels( sgDebugClass c, sgDebugPriority p )
|
logstream::setLogLevels( sgDebugClass c, sgDebugPriority p )
|
||||||
{
|
{
|
||||||
logbuf::set_log_level( c, p );
|
global_privateLogstream->setLogLevels(c, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
logstream *
|
void
|
||||||
logstream::initGlobalLogstream()
|
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.
|
// Force initialization of cerr.
|
||||||
static std::ios_base::Init initializer;
|
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 )
|
if( !global_logstream )
|
||||||
global_logstream = new logstream(std::cerr);
|
global_logstream = new logstream();
|
||||||
return global_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
|
||||||
+61
-224
@@ -26,222 +26,49 @@
|
|||||||
#define _LOGSTREAM_H
|
#define _LOGSTREAM_H
|
||||||
|
|
||||||
#include <simgear/compiler.h>
|
#include <simgear/compiler.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
# include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <streambuf>
|
|
||||||
#include <ostream>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#include <simgear/debug/debug_types.h>
|
#include <simgear/debug/debug_types.h>
|
||||||
|
|
||||||
using std::streambuf;
|
#include <sstream>
|
||||||
using std::ostream;
|
|
||||||
|
|
||||||
//
|
// forward decls
|
||||||
// TODO:
|
class SGPath;
|
||||||
//
|
|
||||||
// 1. Change output destination. Done.
|
|
||||||
// 2. Make logbuf thread safe.
|
|
||||||
// 3. Read environment for default debugClass and debugPriority.
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
namespace simgear
|
||||||
* 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.
|
class LogCallback
|
||||||
*/
|
|
||||||
#ifdef SG_NEED_STREAMBUF_HACK
|
|
||||||
class logbuf : public __streambuf
|
|
||||||
#else
|
|
||||||
class logbuf : public std::streambuf
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// logbuf( streambuf* sb ) : sbuf(sb) {}
|
virtual ~LogCallback() {}
|
||||||
/** Constructor */
|
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||||
logbuf();
|
const char* file, int line, const std::string& aMessage) = 0;
|
||||||
|
|
||||||
/** 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
|
|
||||||
|
|
||||||
|
void setLogLevels(sgDebugClass c, sgDebugPriority p);
|
||||||
protected:
|
protected:
|
||||||
|
LogCallback(sgDebugClass c, sgDebugPriority p);
|
||||||
|
|
||||||
/** sync/flush */
|
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
|
||||||
inline virtual int sync();
|
|
||||||
|
|
||||||
/** overflow */
|
|
||||||
int_type overflow( int ch );
|
|
||||||
// int xsputn( const char* s, istreamsize n );
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
sgDebugClass m_class;
|
||||||
// The streambuf used for actual output. Defaults to cerr.rdbuf().
|
sgDebugPriority m_priority;
|
||||||
static std::streambuf* sbuf;
|
|
||||||
|
|
||||||
static bool logging_enabled;
|
|
||||||
#ifdef _WIN32
|
|
||||||
static bool has_console;
|
|
||||||
#endif
|
|
||||||
static sgDebugClass logClass;
|
|
||||||
static sgDebugPriority logPriority;
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
// Not defined.
|
|
||||||
logbuf( const logbuf& );
|
|
||||||
void operator= ( const logbuf& );
|
|
||||||
};
|
|
||||||
|
|
||||||
inline int
|
|
||||||
logbuf::sync()
|
|
||||||
{
|
|
||||||
return sbuf->pubsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void
|
|
||||||
logbuf::set_log_state( sgDebugClass c, sgDebugPriority p )
|
|
||||||
{
|
|
||||||
logging_enabled = ((c & logClass) != 0 && p >= logPriority);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool
|
|
||||||
logbuf::would_log( sgDebugClass c, sgDebugPriority p ) const
|
|
||||||
{
|
|
||||||
return ((c & logClass) != 0 && p >= logPriority);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline logbuf::int_type
|
|
||||||
logbuf::overflow( int c )
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
if ( logging_enabled ) {
|
|
||||||
if ( !has_console ) {
|
|
||||||
AllocConsole();
|
|
||||||
freopen("conin$", "r", stdin);
|
|
||||||
freopen("conout$", "w", stdout);
|
|
||||||
freopen("conout$", "w", stderr);
|
|
||||||
has_console = true;
|
|
||||||
}
|
|
||||||
return sbuf->sputc(c);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return EOF == 0 ? 1: 0;
|
|
||||||
#else
|
|
||||||
return logging_enabled ? sbuf->sputc(c) : (EOF == 0 ? 1: 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* logstream manipulator for setting the log level of a message.
|
|
||||||
*/
|
|
||||||
struct loglevel
|
|
||||||
{
|
|
||||||
loglevel( sgDebugClass c, sgDebugPriority p )
|
|
||||||
: logClass(c), logPriority(p) {}
|
|
||||||
|
|
||||||
sgDebugClass logClass;
|
|
||||||
sgDebugPriority logPriority;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class that ensures a streambuf and ostream are constructed and
|
* Helper force a console on platforms where it might optional, when
|
||||||
* destroyed in the correct order. The streambuf must be created before the
|
* we need to show a console. This basically means Windows at the
|
||||||
* ostream but bases are constructed before members. Thus, making this class
|
* moment - on other plaforms it's a no-op
|
||||||
* a private base of logstream, declared to the left of ostream, we ensure the
|
|
||||||
* correct order of construction and destruction.
|
|
||||||
*/
|
*/
|
||||||
struct logstream_base
|
void requestConsole();
|
||||||
{
|
|
||||||
// logstream_base( streambuf* sb ) : lbuf(sb) {}
|
|
||||||
logstream_base() {}
|
|
||||||
|
|
||||||
logbuf lbuf;
|
} // of namespace simgear
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to manage the debug logging stream.
|
* Class to manage the debug logging stream.
|
||||||
*/
|
*/
|
||||||
class logstream : private logstream_base, public std::ostream
|
class logstream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
static void initGlobalLogstream();
|
||||||
* 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() ); }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the global log class and priority level.
|
* Set the global log class and priority level.
|
||||||
* @param c debug class
|
* @param c debug class
|
||||||
@@ -249,41 +76,49 @@ public:
|
|||||||
*/
|
*/
|
||||||
void setLogLevels( sgDebugClass c, sgDebugPriority p );
|
void setLogLevels( sgDebugClass c, sgDebugPriority p );
|
||||||
|
|
||||||
bool would_log( sgDebugClass c, sgDebugPriority p ) const
|
bool would_log( sgDebugClass c, sgDebugPriority p ) const;
|
||||||
{
|
|
||||||
return lbuf.would_log( c, p );
|
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.
|
* the core logging method
|
||||||
* @param l log level
|
|
||||||
*/
|
*/
|
||||||
inline std::ostream& operator<< ( const loglevel& l );
|
void log( sgDebugClass c, sgDebugPriority p,
|
||||||
friend logstream& sglog();
|
const char* fileName, int line, const std::string& msg);
|
||||||
static logstream *initGlobalLogstream();
|
|
||||||
protected:
|
|
||||||
static logstream *global_logstream;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline std::ostream&
|
/**
|
||||||
logstream::operator<< ( const loglevel& l )
|
|
||||||
{
|
|
||||||
lbuf.set_log_state( l.logClass, l.logPriority );
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \relates logstream
|
* \relates logstream
|
||||||
* Return the one and only logstream instance.
|
* Return the one and only logstream instance.
|
||||||
* We use a function instead of a global object so we are assured that cerr
|
* We use a function instead of a global object so we are assured that cerr
|
||||||
* has been initialised.
|
* has been initialised.
|
||||||
* @return current logstream
|
* @return current logstream
|
||||||
*/
|
*/
|
||||||
inline logstream&
|
friend logstream& sglog();
|
||||||
sglog()
|
|
||||||
{
|
/**
|
||||||
return *logstream::initGlobalLogstream();
|
* 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();
|
||||||
|
};
|
||||||
|
|
||||||
|
logstream& sglog();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** \def SG_LOG(C,P,M)
|
/** \def SG_LOG(C,P,M)
|
||||||
@@ -296,10 +131,12 @@ sglog()
|
|||||||
# define SG_LOG(C,P,M)
|
# define SG_LOG(C,P,M)
|
||||||
#else
|
#else
|
||||||
# define SG_LOG(C,P,M) do { \
|
# define SG_LOG(C,P,M) do { \
|
||||||
logstream& __tmplogstreamref(sglog()); \
|
if(sglog().would_log(C,P)) { \
|
||||||
if(__tmplogstreamref.would_log(C,P)) { \
|
std::ostringstream os; \
|
||||||
__tmplogstreamref << loglevel(C,P) << M << std::endl; } \
|
os << M; \
|
||||||
} while(0)
|
sglog().log(C, P, __FILE__, __LINE__, os.str()); \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define SG_ORIGIN __FILE__ ":" SG_STRINGIZE(__LINE__)
|
#define SG_ORIGIN __FILE__ ":" SG_STRINGIZE(__LINE__)
|
||||||
|
|||||||
@@ -629,7 +629,7 @@ bool SGMetar::scanWeather()
|
|||||||
weather = pre + weather + post;
|
weather = pre + weather + post;
|
||||||
weather.erase(weather.length() - 1);
|
weather.erase(weather.length() - 1);
|
||||||
_weather.push_back(weather);
|
_weather.push_back(weather);
|
||||||
if( w.phenomena.size() > 0 )
|
if( ! w.phenomena.empty() )
|
||||||
_weather2.push_back( w );
|
_weather2.push_back( w );
|
||||||
_grpcount++;
|
_grpcount++;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ private:
|
|||||||
float _rain_intensity;
|
float _rain_intensity;
|
||||||
float _clip_distance;
|
float _clip_distance;
|
||||||
|
|
||||||
int _wind_dir;
|
|
||||||
osg::Vec3 _wind_vec;
|
osg::Vec3 _wind_vec;
|
||||||
|
|
||||||
osg::ref_ptr<osgParticle::PrecipitationEffect> _precipitationEffect;
|
osg::ref_ptr<osgParticle::PrecipitationEffect> _precipitationEffect;
|
||||||
|
|||||||
@@ -15,7 +15,14 @@ set(HEADERS
|
|||||||
sg_socket.hxx
|
sg_socket.hxx
|
||||||
sg_socket_udp.hxx
|
sg_socket_udp.hxx
|
||||||
HTTPClient.hxx
|
HTTPClient.hxx
|
||||||
|
HTTPFileRequest.hxx
|
||||||
|
HTTPMemoryRequest.hxx
|
||||||
HTTPRequest.hxx
|
HTTPRequest.hxx
|
||||||
|
HTTPContentDecode.hxx
|
||||||
|
DAVMultiStatus.hxx
|
||||||
|
SVNRepository.hxx
|
||||||
|
SVNDirectory.hxx
|
||||||
|
SVNReportParser.hxx
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
@@ -31,22 +38,22 @@ set(SOURCES
|
|||||||
sg_socket.cxx
|
sg_socket.cxx
|
||||||
sg_socket_udp.cxx
|
sg_socket_udp.cxx
|
||||||
HTTPClient.cxx
|
HTTPClient.cxx
|
||||||
|
HTTPFileRequest.cxx
|
||||||
|
HTTPMemoryRequest.cxx
|
||||||
HTTPRequest.cxx
|
HTTPRequest.cxx
|
||||||
|
HTTPContentDecode.cxx
|
||||||
|
DAVMultiStatus.cxx
|
||||||
|
SVNRepository.cxx
|
||||||
|
SVNDirectory.cxx
|
||||||
|
SVNReportParser.cxx
|
||||||
)
|
)
|
||||||
|
|
||||||
simgear_component(io io "${SOURCES}" "${HEADERS}")
|
simgear_component(io io "${SOURCES}" "${HEADERS}")
|
||||||
|
|
||||||
if(ENABLE_TESTS)
|
if(ENABLE_TESTS)
|
||||||
|
|
||||||
if (SIMGEAR_SHARED)
|
add_executable(http_svn http_svn.cxx)
|
||||||
set(TEST_LIBS SimGearCore)
|
target_link_libraries(http_svn ${TEST_LIBS})
|
||||||
else()
|
|
||||||
set(TEST_LIBS SimGearCore
|
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
|
||||||
${WINSOCK_LIBRARY}
|
|
||||||
${ZLIB_LIBRARY}
|
|
||||||
${RT_LIBRARY})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(test_sock socktest.cxx)
|
add_executable(test_sock socktest.cxx)
|
||||||
target_link_libraries(test_sock ${TEST_LIBS})
|
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})
|
target_link_libraries(test_binobj ${TEST_LIBS})
|
||||||
|
|
||||||
add_test(binobj ${EXECUTABLE_OUTPUT_PATH}/test_binobj)
|
add_test(binobj ${EXECUTABLE_OUTPUT_PATH}/test_binobj)
|
||||||
|
|
||||||
endif(ENABLE_TESTS)
|
endif(ENABLE_TESTS)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
+402
-254
@@ -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 "HTTPClient.hxx"
|
||||||
|
#include "HTTPFileRequest.hxx"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdlib> // rand()
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <iostream>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <map>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <boost/foreach.hpp>
|
#include <boost/foreach.hpp>
|
||||||
#include <boost/algorithm/string/case_conv.hpp>
|
#include <boost/algorithm/string/case_conv.hpp>
|
||||||
|
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
#include <simgear/io/sg_netChat.hxx>
|
#include <simgear/io/sg_netChat.hxx>
|
||||||
#include <simgear/io/lowlevel.hxx>
|
#include <simgear/io/HTTPContentDecode.hxx>
|
||||||
#include <simgear/misc/strutils.hxx>
|
#include <simgear/misc/strutils.hxx>
|
||||||
#include <simgear/compiler.h>
|
#include <simgear/compiler.h>
|
||||||
#include <simgear/debug/logstream.hxx>
|
#include <simgear/debug/logstream.hxx>
|
||||||
#include <simgear/timing/timestamp.hxx>
|
#include <simgear/timing/timestamp.hxx>
|
||||||
|
#include <simgear/structure/exception.hxx>
|
||||||
|
|
||||||
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
|
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
@@ -26,10 +51,6 @@
|
|||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using std::string;
|
|
||||||
using std::stringstream;
|
|
||||||
using std::vector;
|
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -39,19 +60,31 @@ namespace HTTP
|
|||||||
extern const int DEFAULT_HTTP_PORT = 80;
|
extern const int DEFAULT_HTTP_PORT = 80;
|
||||||
const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
|
const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
|
||||||
const unsigned int MAX_INFLIGHT_REQUESTS = 32;
|
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
|
class Connection;
|
||||||
// detailed description of the logic
|
typedef std::multimap<std::string, Connection*> ConnectionDict;
|
||||||
const int GZIP_HEADER_ID1 = 31;
|
typedef std::list<Request_ptr> RequestList;
|
||||||
const int GZIP_HEADER_ID2 = 139;
|
|
||||||
const int GZIP_HEADER_METHOD_DEFLATE = 8;
|
class Client::ClientPrivate
|
||||||
const int GZIP_HEADER_SIZE = 10;
|
{
|
||||||
const int GZIP_HEADER_FEXTRA = 1 << 2;
|
public:
|
||||||
const int GZIP_HEADER_FNAME = 1 << 3;
|
std::string userAgent;
|
||||||
const int GZIP_HEADER_COMMENT = 1 << 4;
|
std::string proxy;
|
||||||
const int GZIP_HEADER_CRC = 1 << 1;
|
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
|
class Connection : public NetChat
|
||||||
{
|
{
|
||||||
@@ -59,26 +92,28 @@ public:
|
|||||||
Connection(Client* pr) :
|
Connection(Client* pr) :
|
||||||
client(pr),
|
client(pr),
|
||||||
state(STATE_CLOSED),
|
state(STATE_CLOSED),
|
||||||
port(DEFAULT_HTTP_PORT),
|
port(DEFAULT_HTTP_PORT)
|
||||||
zlibInflateBuffer(NULL),
|
|
||||||
zlibInflateBufferSize(0),
|
|
||||||
zlibOutputBuffer(NULL)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~Connection()
|
virtual ~Connection()
|
||||||
{
|
{
|
||||||
if (zlibInflateBuffer) {
|
|
||||||
free(zlibInflateBuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zlibOutputBuffer) {
|
virtual void handleBufferRead (NetBuffer& buffer)
|
||||||
free(zlibOutputBuffer);
|
{
|
||||||
}
|
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;
|
host = h;
|
||||||
port = p;
|
port = p;
|
||||||
@@ -109,6 +144,7 @@ public:
|
|||||||
SG_LOG(SG_IO, SG_INFO, "HTTP socket error");
|
SG_LOG(SG_IO, SG_INFO, "HTTP socket error");
|
||||||
activeRequest->setFailure(error, "socket error");
|
activeRequest->setFailure(error, "socket error");
|
||||||
activeRequest = NULL;
|
activeRequest = NULL;
|
||||||
|
_contentDecoder.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
state = STATE_SOCKET_ERROR;
|
state = STATE_SOCKET_ERROR;
|
||||||
@@ -118,12 +154,27 @@ public:
|
|||||||
{
|
{
|
||||||
NetChat::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
|
// force state here, so responseComplete can avoid closing the
|
||||||
// socket again
|
// socket again
|
||||||
state = STATE_CLOSED;
|
state = STATE_CLOSED;
|
||||||
responseComplete();
|
responseComplete();
|
||||||
} else {
|
} 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;
|
state = STATE_CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +189,19 @@ public:
|
|||||||
sentRequests.clear();
|
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)
|
void queueRequest(const Request_ptr& r)
|
||||||
{
|
{
|
||||||
queuedRequests.push_back(r);
|
queuedRequests.push_back(r);
|
||||||
@@ -147,9 +211,15 @@ public:
|
|||||||
void beginResponse()
|
void beginResponse()
|
||||||
{
|
{
|
||||||
assert(!sentRequests.empty());
|
assert(!sentRequests.empty());
|
||||||
|
assert(state == STATE_WAITING_FOR_RESPONSE);
|
||||||
|
|
||||||
activeRequest = sentRequests.front();
|
activeRequest = sentRequests.front();
|
||||||
|
try {
|
||||||
activeRequest->responseStart(buffer);
|
activeRequest->responseStart(buffer);
|
||||||
|
} catch (sg_exception& e) {
|
||||||
|
handleError(EIO);
|
||||||
|
}
|
||||||
|
|
||||||
state = STATE_GETTING_HEADERS;
|
state = STATE_GETTING_HEADERS;
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
if (activeRequest->responseCode() == 204) {
|
if (activeRequest->responseCode() == 204) {
|
||||||
@@ -162,11 +232,15 @@ public:
|
|||||||
|
|
||||||
bodyTransferSize = -1;
|
bodyTransferSize = -1;
|
||||||
chunkedTransfer = false;
|
chunkedTransfer = false;
|
||||||
contentGZip = contentDeflate = false;
|
_contentDecoder.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void tryStartNextRequest()
|
void tryStartNextRequest()
|
||||||
{
|
{
|
||||||
|
while( !queuedRequests.empty()
|
||||||
|
&& queuedRequests.front()->isComplete() )
|
||||||
|
queuedRequests.pop_front();
|
||||||
|
|
||||||
if (queuedRequests.empty()) {
|
if (queuedRequests.empty()) {
|
||||||
idleTime.stamp();
|
idleTime.stamp();
|
||||||
return;
|
return;
|
||||||
@@ -186,28 +260,29 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Request_ptr r = queuedRequests.front();
|
Request_ptr r = queuedRequests.front();
|
||||||
requestBodyBytesToSend = r->requestBodyLength();
|
r->requestStart();
|
||||||
|
|
||||||
stringstream headerData;
|
std::stringstream headerData;
|
||||||
string path = r->path();
|
std::string path = r->path();
|
||||||
assert(!path.empty());
|
assert(!path.empty());
|
||||||
string query = r->query();
|
std::string query = r->query();
|
||||||
string bodyData;
|
std::string bodyData;
|
||||||
|
|
||||||
if (!client->proxyHost().empty()) {
|
if (!client->proxyHost().empty()) {
|
||||||
path = r->scheme() + "://" + r->host() + r->path();
|
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";
|
headerData << r->method() << " " << path << " HTTP/1.1\r\n";
|
||||||
bodyData = query.substr(1); // URL-encode, drop the leading '?'
|
bodyData = query.substr(1); // URL-encode, drop the leading '?'
|
||||||
headerData << "Content-Type:" << CONTENT_TYPE_URL_ENCODED << "\r\n";
|
headerData << "Content-Type:" << CONTENT_TYPE_URL_ENCODED << "\r\n";
|
||||||
headerData << "Content-Length:" << bodyData.size() << "\r\n";
|
headerData << "Content-Length:" << bodyData.size() << "\r\n";
|
||||||
} else {
|
} else {
|
||||||
headerData << r->method() << " " << path << query << " HTTP/1.1\r\n";
|
headerData << r->method() << " " << path << query << " HTTP/1.1\r\n";
|
||||||
if (requestBodyBytesToSend >= 0) {
|
if( r->hasBodyData() )
|
||||||
headerData << "Content-Length:" << requestBodyBytesToSend << "\r\n";
|
{
|
||||||
headerData << "Content-Type:" << r->requestBodyType() << "\r\n";
|
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";
|
headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_FOREACH(string h, r->requestHeaders()) {
|
BOOST_FOREACH(const StringMap::value_type& h, r->requestHeaders()) {
|
||||||
headerData << h << ": " << r->header(h) << "\r\n";
|
headerData << h.first << ": " << h.second << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
headerData << "\r\n"; // final CRLF to terminate the headers
|
headerData << "\r\n"; // final CRLF to terminate the headers
|
||||||
@@ -229,32 +304,45 @@ public:
|
|||||||
|
|
||||||
bool ok = push(headerData.str().c_str());
|
bool ok = push(headerData.str().c_str());
|
||||||
if (!ok) {
|
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
|
// we've over-stuffed the socket, give up for now, let things
|
||||||
// drain down before trying to start any more requests.
|
// drain down before trying to start any more requests.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (requestBodyBytesToSend > 0) {
|
if( r->hasBodyData() )
|
||||||
|
for(size_t body_bytes_sent = 0; body_bytes_sent < r->bodyLength();)
|
||||||
|
{
|
||||||
char buf[4096];
|
char buf[4096];
|
||||||
int len = 4096;
|
size_t len = r->getBodyData(buf, body_bytes_sent, 4096);
|
||||||
r->getBodyData(buf, len);
|
if( len )
|
||||||
if (len > 0) {
|
{
|
||||||
requestBodyBytesToSend -= len;
|
if( !bufferSend(buf, len) )
|
||||||
if (!bufferSend(buf, len)) {
|
{
|
||||||
SG_LOG(SG_IO, SG_WARN, "overflow the HTTP::Connection output buffer");
|
SG_LOG(SG_IO,
|
||||||
|
SG_WARN,
|
||||||
|
"overflow the HTTP::Connection output buffer");
|
||||||
state = STATE_SOCKET_ERROR;
|
state = STATE_SOCKET_ERROR;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
body_bytes_sent += len;
|
||||||
SG_LOG(SG_IO, SG_WARN, "asynchronous request body generation is unsupported");
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SG_LOG(SG_IO,
|
||||||
|
SG_WARN,
|
||||||
|
"HTTP asynchronous request body generation is unsupported");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//std::cout << "did send request:" << r->url() << std::endl;
|
// 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
|
// successfully sent, remove from queue, and maybe send the next
|
||||||
queuedRequests.pop_front();
|
queuedRequests.pop_front();
|
||||||
sentRequests.push_back(r);
|
sentRequests.push_back(r);
|
||||||
|
state = STATE_WAITING_FOR_RESPONSE;
|
||||||
|
|
||||||
// pipelining, let's maybe send the next request right away
|
// pipelining, let's maybe send the next request right away
|
||||||
tryStartNextRequest();
|
tryStartNextRequest();
|
||||||
@@ -263,129 +351,20 @@ public:
|
|||||||
virtual void collectIncomingData(const char* s, int n)
|
virtual void collectIncomingData(const char* s, int n)
|
||||||
{
|
{
|
||||||
idleTime.stamp();
|
idleTime.stamp();
|
||||||
if ((state == STATE_GETTING_BODY) || (state == STATE_GETTING_CHUNKED_BYTES)) {
|
client->receivedBytes(static_cast<unsigned int>(n));
|
||||||
if (contentGZip || contentDeflate) {
|
|
||||||
expandCompressedData(s, n);
|
|
||||||
} else {
|
|
||||||
activeRequest->processBodyBytes(s, n);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buffer += string(s, n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if( (state == STATE_GETTING_BODY)
|
||||||
void expandCompressedData(const char* s, int n)
|
|| (state == STATE_GETTING_CHUNKED_BYTES) )
|
||||||
{
|
_contentDecoder.receivedBytes(s, n);
|
||||||
int reqSize = n + zlib.avail_in;
|
else
|
||||||
if (reqSize > zlibInflateBufferSize) {
|
buffer.append(s, n);
|
||||||
// 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)
|
virtual void foundTerminator(void)
|
||||||
{
|
{
|
||||||
idleTime.stamp();
|
idleTime.stamp();
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_IDLE:
|
case STATE_WAITING_FOR_RESPONSE:
|
||||||
beginResponse();
|
beginResponse();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -405,6 +384,7 @@ public:
|
|||||||
case STATE_GETTING_CHUNKED_BYTES:
|
case STATE_GETTING_CHUNKED_BYTES:
|
||||||
setTerminator("\r\n");
|
setTerminator("\r\n");
|
||||||
state = STATE_GETTING_CHUNKED;
|
state = STATE_GETTING_CHUNKED;
|
||||||
|
buffer.clear();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
@@ -413,6 +393,9 @@ public:
|
|||||||
buffer.clear();
|
buffer.clear();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case STATE_IDLE:
|
||||||
|
SG_LOG(SG_IO, SG_WARN, "HTTP got data in IDLE state, bad server?");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -424,6 +407,7 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(sentRequests.empty());
|
||||||
return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
|
return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,6 +429,11 @@ public:
|
|||||||
{
|
{
|
||||||
return !queuedRequests.empty() && (sentRequests.size() < MAX_INFLIGHT_REQUESTS);
|
return !queuedRequests.empty() && (sentRequests.size() < MAX_INFLIGHT_REQUESTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isActive() const
|
||||||
|
{
|
||||||
|
return !queuedRequests.empty() || !sentRequests.empty();
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
bool connectToHost()
|
bool connectToHost()
|
||||||
{
|
{
|
||||||
@@ -465,37 +454,9 @@ private:
|
|||||||
|
|
||||||
void processHeader()
|
void processHeader()
|
||||||
{
|
{
|
||||||
string h = strutils::simplify(buffer);
|
std::string h = strutils::simplify(buffer);
|
||||||
if (h.empty()) { // blank line terminates headers
|
if (h.empty()) { // blank line terminates headers
|
||||||
headersComplete();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,9 +466,9 @@ private:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string key = strutils::simplify(buffer.substr(0, colonPos));
|
std::string key = strutils::simplify(buffer.substr(0, colonPos));
|
||||||
string lkey = boost::to_lower_copy(key);
|
std::string lkey = boost::to_lower_copy(key);
|
||||||
string value = strutils::strip(buffer.substr(colonPos + 1));
|
std::string value = strutils::strip(buffer.substr(colonPos + 1));
|
||||||
|
|
||||||
// only consider these if getting headers (as opposed to trailers
|
// only consider these if getting headers (as opposed to trailers
|
||||||
// of a chunked transfer)
|
// of a chunked transfer)
|
||||||
@@ -524,20 +485,14 @@ private:
|
|||||||
} else if (lkey == "transfer-encoding") {
|
} else if (lkey == "transfer-encoding") {
|
||||||
processTransferEncoding(value);
|
processTransferEncoding(value);
|
||||||
} else if (lkey == "content-encoding") {
|
} else if (lkey == "content-encoding") {
|
||||||
if (value == "gzip") {
|
_contentDecoder.setEncoding(value);
|
||||||
contentGZip = true;
|
|
||||||
} else if (value == "deflate") {
|
|
||||||
contentDeflate = true;
|
|
||||||
} else if (value != "identity") {
|
|
||||||
SG_LOG(SG_IO, SG_WARN, "unsupported content encoding:" << value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activeRequest->responseHeader(lkey, value);
|
activeRequest->responseHeader(lkey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void processTransferEncoding(const string& te)
|
void processTransferEncoding(const std::string& te)
|
||||||
{
|
{
|
||||||
if (te == "chunked") {
|
if (te == "chunked") {
|
||||||
chunkedTransfer = true;
|
chunkedTransfer = true;
|
||||||
@@ -588,17 +543,25 @@ private:
|
|||||||
void headersComplete()
|
void headersComplete()
|
||||||
{
|
{
|
||||||
activeRequest->responseHeadersComplete();
|
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()
|
void responseComplete()
|
||||||
{
|
{
|
||||||
//std::cout << "responseComplete:" << activeRequest->url() << std::endl;
|
Request_ptr completedRequest = activeRequest;
|
||||||
activeRequest->responseComplete();
|
_contentDecoder.finish();
|
||||||
client->requestFinished(this);
|
|
||||||
|
|
||||||
if (contentDeflate) {
|
|
||||||
inflateEnd(&zlib);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(sentRequests.front() == activeRequest);
|
assert(sentRequests.front() == activeRequest);
|
||||||
sentRequests.pop_front();
|
sentRequests.pop_front();
|
||||||
@@ -617,13 +580,21 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state != STATE_CLOSED) {
|
if (state != STATE_CLOSED) {
|
||||||
state = STATE_IDLE;
|
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");
|
setTerminator("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ConnectionState {
|
enum ConnectionState {
|
||||||
STATE_IDLE = 0,
|
STATE_IDLE = 0,
|
||||||
|
STATE_WAITING_FOR_RESPONSE,
|
||||||
STATE_GETTING_HEADERS,
|
STATE_GETTING_HEADERS,
|
||||||
STATE_GETTING_BODY,
|
STATE_GETTING_BODY,
|
||||||
STATE_GETTING_CHUNKED,
|
STATE_GETTING_CHUNKED,
|
||||||
@@ -636,74 +607,187 @@ private:
|
|||||||
Client* client;
|
Client* client;
|
||||||
Request_ptr activeRequest;
|
Request_ptr activeRequest;
|
||||||
ConnectionState state;
|
ConnectionState state;
|
||||||
string host;
|
std::string host;
|
||||||
short port;
|
short port;
|
||||||
std::string buffer;
|
std::string buffer;
|
||||||
int bodyTransferSize;
|
int bodyTransferSize;
|
||||||
SGTimeStamp idleTime;
|
SGTimeStamp idleTime;
|
||||||
bool chunkedTransfer;
|
bool chunkedTransfer;
|
||||||
bool noMessageBody;
|
bool noMessageBody;
|
||||||
int requestBodyBytesToSend;
|
|
||||||
|
|
||||||
z_stream zlib;
|
RequestList queuedRequests;
|
||||||
unsigned char* zlibInflateBuffer;
|
RequestList sentRequests;
|
||||||
int zlibInflateBufferSize;
|
|
||||||
unsigned char* zlibOutputBuffer;
|
|
||||||
bool contentGZip, contentDeflate;
|
|
||||||
|
|
||||||
std::list<Request_ptr> queuedRequests;
|
ContentDecoder _contentDecoder;
|
||||||
std::list<Request_ptr> sentRequests;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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));
|
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)
|
void Client::update(int waitTimeout)
|
||||||
{
|
{
|
||||||
NetChannel::poll(waitTimeout);
|
if (!d->poller.hasChannels() && (waitTimeout > 0)) {
|
||||||
|
SGTimeStamp::sleepForMSec(waitTimeout);
|
||||||
|
} else {
|
||||||
|
d->poller.poll(waitTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
ConnectionDict::iterator it = _connections.begin();
|
bool waitingRequests = !d->pendingRequests.empty();
|
||||||
for (; it != _connections.end(); ) {
|
ConnectionDict::iterator it = d->connections.begin();
|
||||||
if (it->second->hasIdleTimeout() || it->second->hasError() ||
|
for (; it != d->connections.end(); ) {
|
||||||
it->second->hasErrorTimeout())
|
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
|
// 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++;
|
ConnectionDict::iterator del = it++;
|
||||||
delete del->second;
|
delete del->second;
|
||||||
_connections.erase(del);
|
d->connections.erase(del);
|
||||||
} else {
|
} else {
|
||||||
if (it->second->shouldStartNext()) {
|
if (it->second->shouldStartNext()) {
|
||||||
it->second->tryStartNextRequest();
|
it->second->tryStartNextRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
++it;
|
++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)
|
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();
|
int port = r->port();
|
||||||
if (!_proxy.empty()) {
|
if (!d->proxy.empty()) {
|
||||||
host = _proxy;
|
host = d->proxy;
|
||||||
port = _proxyPort;
|
port = d->proxyPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
stringstream ss;
|
Connection* con = NULL;
|
||||||
|
std::stringstream ss;
|
||||||
ss << host << "-" << port;
|
ss << host << "-" << port;
|
||||||
string connectionId = ss.str();
|
std::string connectionId = ss.str();
|
||||||
|
bool havePending = !d->pendingRequests.empty();
|
||||||
|
bool atConnectionsLimit = d->connections.size() >= d->maxConnections;
|
||||||
|
ConnectionDict::iterator consEnd = d->connections.end();
|
||||||
|
|
||||||
if (_connections.find(connectionId) == _connections.end()) {
|
// assign request to an existing Connection.
|
||||||
Connection* con = new Connection(this);
|
// various options exist here, examined in order
|
||||||
con->setServer(host, port);
|
ConnectionDict::iterator it = d->connections.find(connectionId);
|
||||||
_connections[connectionId] = con;
|
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)
|
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;
|
return d->userAgent;
|
||||||
_proxyPort = port;
|
}
|
||||||
_proxyAuth = auth;
|
|
||||||
|
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
|
} // of namespace HTTP
|
||||||
|
|||||||
+77
-16
@@ -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
|
#ifndef SG_HTTP_CLIENT_HXX
|
||||||
#define 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
|
namespace simgear
|
||||||
{
|
{
|
||||||
@@ -11,42 +36,78 @@ namespace simgear
|
|||||||
namespace HTTP
|
namespace HTTP
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// forward decls
|
||||||
class Connection;
|
class Connection;
|
||||||
|
|
||||||
class Client
|
class Client
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Client();
|
Client();
|
||||||
|
~Client();
|
||||||
|
|
||||||
void update(int waitTimeout = 0);
|
void update(int waitTimeout = 0);
|
||||||
|
|
||||||
void makeRequest(const Request_ptr& r);
|
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 setUserAgent(const std::string& ua);
|
||||||
void setProxy(const std::string& proxy, int port, const std::string& auth = "");
|
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& proxyHost() const
|
const std::string& userAgent() const;
|
||||||
{ return _proxy; }
|
|
||||||
|
|
||||||
const std::string& proxyAuth() const
|
const std::string& proxyHost() 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:
|
private:
|
||||||
void requestFinished(Connection* con);
|
void requestFinished(Connection* con);
|
||||||
|
|
||||||
|
void receivedBytes(unsigned int count);
|
||||||
|
|
||||||
friend class Connection;
|
friend class Connection;
|
||||||
friend class Request;
|
friend class Request;
|
||||||
|
|
||||||
std::string _userAgent;
|
class ClientPrivate;
|
||||||
std::string _proxy;
|
std::auto_ptr<ClientPrivate> d;
|
||||||
int _proxyPort;
|
|
||||||
std::string _proxyAuth;
|
|
||||||
|
|
||||||
// connections by host
|
|
||||||
typedef std::map<std::string, Connection*> ConnectionDict;
|
|
||||||
ConnectionDict _connections;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // of namespace HTTP
|
} // of namespace HTTP
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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_ */
|
||||||
@@ -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
|
||||||
@@ -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_ */
|
||||||
+213
-67
@@ -1,99 +1,190 @@
|
|||||||
#include "HTTPRequest.hxx"
|
#include "HTTPRequest.hxx"
|
||||||
|
|
||||||
#include <simgear/misc/strutils.hxx>
|
|
||||||
#include <simgear/compiler.h>
|
#include <simgear/compiler.h>
|
||||||
#include <simgear/debug/logstream.hxx>
|
#include <simgear/debug/logstream.hxx>
|
||||||
|
#include <simgear/misc/strutils.hxx>
|
||||||
using std::string;
|
#include <simgear/props/props_io.hxx>
|
||||||
using std::map;
|
#include <simgear/structure/exception.hxx>
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
{
|
{
|
||||||
|
|
||||||
namespace HTTP
|
namespace HTTP
|
||||||
{
|
{
|
||||||
|
|
||||||
extern const int DEFAULT_HTTP_PORT;
|
extern const int DEFAULT_HTTP_PORT;
|
||||||
|
|
||||||
Request::Request(const string& url, const string method) :
|
//------------------------------------------------------------------------------
|
||||||
|
Request::Request(const std::string& url, const std::string method):
|
||||||
_method(method),
|
_method(method),
|
||||||
_url(url),
|
_url(url),
|
||||||
_responseVersion(HTTP_VERSION_UNKNOWN),
|
_responseVersion(HTTP_VERSION_UNKNOWN),
|
||||||
_responseStatus(0),
|
_responseStatus(0),
|
||||||
_responseLength(0),
|
_responseLength(0),
|
||||||
_receivedBodyBytes(0),
|
_receivedBodyBytes(0),
|
||||||
|
_ready_state(UNSENT),
|
||||||
_willClose(false)
|
_willClose(false)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
Request::~Request()
|
Request::~Request()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Request::setUrl(const string& url)
|
//------------------------------------------------------------------------------
|
||||||
|
Request* Request::done(const Callback& cb)
|
||||||
|
{
|
||||||
|
if( _ready_state == DONE )
|
||||||
|
cb(this);
|
||||||
|
else
|
||||||
|
_cb_done = cb;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
Request* Request::fail(const Callback& cb)
|
||||||
|
{
|
||||||
|
if( _ready_state == FAILED )
|
||||||
|
cb(this);
|
||||||
|
else
|
||||||
|
_cb_fail = cb;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
Request* Request::always(const Callback& cb)
|
||||||
|
{
|
||||||
|
if( isComplete() )
|
||||||
|
cb(this);
|
||||||
|
else
|
||||||
|
_cb_always = cb;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
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;
|
_url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
string_list Request::requestHeaders() const
|
//------------------------------------------------------------------------------
|
||||||
|
void Request::requestStart()
|
||||||
{
|
{
|
||||||
string_list r;
|
setReadyState(OPENED);
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string Request::header(const std::string& name) const
|
//------------------------------------------------------------------------------
|
||||||
|
Request::HTTPVersion decodeHTTPVersion(const std::string& v)
|
||||||
{
|
{
|
||||||
return string();
|
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 string& r)
|
//------------------------------------------------------------------------------
|
||||||
|
void Request::responseStart(const std::string& r)
|
||||||
{
|
{
|
||||||
const int maxSplit = 2; // HTTP/1.1 nnn reason-string
|
const int maxSplit = 2; // HTTP/1.1 nnn reason-string
|
||||||
string_list parts = strutils::split(r, NULL, maxSplit);
|
string_list parts = strutils::split(r, NULL, maxSplit);
|
||||||
if (parts.size() != 3) {
|
if (parts.size() != 3) {
|
||||||
SG_LOG(SG_IO, SG_WARN, "HTTP::Request: malformed response start:" << r);
|
throw sg_io_exception("bad HTTP response");
|
||||||
setFailure(400, "malformed HTTP response header");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_responseVersion = decodeVersion(parts[0]);
|
_responseVersion = decodeHTTPVersion(parts[0]);
|
||||||
_responseStatus = strutils::to_int(parts[1]);
|
_responseStatus = strutils::to_int(parts[1]);
|
||||||
_responseReason = parts[2];
|
_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") {
|
if( key == "connection" )
|
||||||
_willClose = (value.find("close") != string::npos);
|
_willClose = (value.find("close") != std::string::npos);
|
||||||
}
|
|
||||||
|
|
||||||
_responseHeaders[key] = value;
|
_responseHeaders[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
void Request::responseHeadersComplete()
|
void Request::responseHeadersComplete()
|
||||||
{
|
{
|
||||||
// no op
|
setReadyState(HEADERS_RECEIVED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
void Request::responseComplete()
|
||||||
|
{
|
||||||
|
if( !isComplete() )
|
||||||
|
setReadyState(DONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
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)
|
void Request::processBodyBytes(const char* s, int n)
|
||||||
{
|
{
|
||||||
_receivedBodyBytes += n;
|
_receivedBodyBytes += n;
|
||||||
gotBodyData(s, n);
|
gotBodyData(s, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Request::gotBodyData(const char* s, int n)
|
//------------------------------------------------------------------------------
|
||||||
{
|
std::string Request::scheme() const
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Request::responseComplete()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
string Request::scheme() const
|
|
||||||
{
|
{
|
||||||
int firstColon = url().find(":");
|
int firstColon = url().find(":");
|
||||||
if (firstColon > 0) {
|
if (firstColon > 0) {
|
||||||
@@ -103,9 +194,10 @@ string Request::scheme() const
|
|||||||
return ""; // couldn't parse scheme
|
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("://");
|
int schemeEnd = u.find("://");
|
||||||
if (schemeEnd < 0) {
|
if (schemeEnd < 0) {
|
||||||
return ""; // couldn't parse scheme
|
return ""; // couldn't parse scheme
|
||||||
@@ -127,10 +219,10 @@ string Request::path() const
|
|||||||
return u.substr(hostEnd, query - hostEnd);
|
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('?');
|
int query = u.find('?');
|
||||||
if (query < 0) {
|
if (query < 0) {
|
||||||
return ""; //no query string found
|
return ""; //no query string found
|
||||||
@@ -139,11 +231,10 @@ string Request::query() const
|
|||||||
return u.substr(query); //includes question mark
|
return u.substr(query); //includes question mark
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
std::string Request::host() const
|
||||||
string Request::host() const
|
|
||||||
{
|
{
|
||||||
string hp(hostAndPort());
|
std::string hp(hostAndPort());
|
||||||
int colonPos = hp.find(':');
|
int colonPos = hp.find(':');
|
||||||
if (colonPos >= 0) {
|
if (colonPos >= 0) {
|
||||||
return hp.substr(0, colonPos); // trim off the colon and port
|
return hp.substr(0, colonPos); // trim off the colon and port
|
||||||
@@ -152,9 +243,10 @@ string Request::host() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
unsigned short Request::port() const
|
unsigned short Request::port() const
|
||||||
{
|
{
|
||||||
string hp(hostAndPort());
|
std::string hp(hostAndPort());
|
||||||
int colonPos = hp.find(':');
|
int colonPos = hp.find(':');
|
||||||
if (colonPos >= 0) {
|
if (colonPos >= 0) {
|
||||||
return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
|
return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
|
||||||
@@ -163,9 +255,10 @@ unsigned short Request::port() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string Request::hostAndPort() const
|
//------------------------------------------------------------------------------
|
||||||
|
std::string Request::hostAndPort() const
|
||||||
{
|
{
|
||||||
string u(url());
|
std::string u(url());
|
||||||
int schemeEnd = u.find("://");
|
int schemeEnd = u.find("://");
|
||||||
if (schemeEnd < 0) {
|
if (schemeEnd < 0) {
|
||||||
return ""; // couldn't parse scheme
|
return ""; // couldn't parse scheme
|
||||||
@@ -179,65 +272,118 @@ string Request::hostAndPort() const
|
|||||||
return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
|
return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
void Request::setResponseLength(unsigned int l)
|
void Request::setResponseLength(unsigned int l)
|
||||||
{
|
{
|
||||||
_responseLength = l;
|
_responseLength = l;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
unsigned int Request::responseLength() const
|
unsigned int Request::responseLength() const
|
||||||
{
|
{
|
||||||
// if the server didn't supply a content length, use the number
|
// if the server didn't supply a content length, use the number
|
||||||
// of bytes we actually received (so far)
|
// of bytes we actually received (so far)
|
||||||
if ((_responseLength == 0) && (_receivedBodyBytes > 0)) {
|
if( (_responseLength == 0) && (_receivedBodyBytes > 0) )
|
||||||
return _receivedBodyBytes;
|
return _receivedBodyBytes;
|
||||||
}
|
|
||||||
|
|
||||||
return _responseLength;
|
return _responseLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
void Request::setFailure(int code, const std::string& reason)
|
void Request::setFailure(int code, const std::string& reason)
|
||||||
{
|
{
|
||||||
_responseStatus = code;
|
_responseStatus = code;
|
||||||
_responseReason = reason;
|
_responseReason = reason;
|
||||||
failed();
|
setReadyState(FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Request::failed()
|
//------------------------------------------------------------------------------
|
||||||
|
void Request::setReadyState(ReadyState state)
|
||||||
{
|
{
|
||||||
// no-op in base class
|
_ready_state = state;
|
||||||
SG_LOG(SG_IO, SG_INFO, "request failed:" << url());
|
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;
|
abort("Request aborted.");
|
||||||
if (v == "HTTP/1.0") return HTTP_1_0;
|
|
||||||
if (strutils::starts_with(v, "HTTP/0.")) return HTTP_0_x;
|
|
||||||
return HTTP_VERSION_UNKNOWN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void Request::abort(const std::string& reason)
|
||||||
|
{
|
||||||
|
if( isComplete() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
setFailure(-1, reason);
|
||||||
|
_willClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
bool Request::closeAfterComplete() const
|
bool Request::closeAfterComplete() const
|
||||||
{
|
{
|
||||||
// for non HTTP/1.1 connections, assume server closes
|
// for non HTTP/1.1 connections, assume server closes
|
||||||
return _willClose || (_responseVersion != HTTP_1_1);
|
return _willClose || (_responseVersion != HTTP_1_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Request::requestBodyLength() const
|
//------------------------------------------------------------------------------
|
||||||
|
bool Request::isComplete() const
|
||||||
{
|
{
|
||||||
return -1;
|
return _ready_state == DONE || _ready_state == FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Request::requestBodyType() const
|
//------------------------------------------------------------------------------
|
||||||
|
bool Request::hasBodyData() const
|
||||||
{
|
{
|
||||||
return "text/plain";
|
return !_request_media_type.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Request::getBodyData(char*, int& count) const
|
//------------------------------------------------------------------------------
|
||||||
|
std::string Request::bodyType() const
|
||||||
{
|
{
|
||||||
count = 0;
|
return _request_media_type;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
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 HTTP
|
||||||
|
|
||||||
} // of namespace simgear
|
} // of namespace simgear
|
||||||
|
|||||||
+119
-22
@@ -3,21 +3,85 @@
|
|||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
#include <simgear/structure/map.hxx>
|
||||||
#include <simgear/structure/SGReferenced.hxx>
|
#include <simgear/structure/SGReferenced.hxx>
|
||||||
#include <simgear/structure/SGSharedPtr.hxx>
|
#include <simgear/structure/SGSharedPtr.hxx>
|
||||||
#include <simgear/math/sg_types.hxx>
|
#include <simgear/math/sg_types.hxx>
|
||||||
|
|
||||||
|
#include <boost/function.hpp>
|
||||||
|
|
||||||
|
class SGPropertyNode;
|
||||||
|
|
||||||
namespace simgear
|
namespace simgear
|
||||||
{
|
{
|
||||||
|
|
||||||
namespace HTTP
|
namespace HTTP
|
||||||
{
|
{
|
||||||
|
|
||||||
class Request : public SGReferenced
|
class Request:
|
||||||
|
public SGReferenced
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
typedef boost::function<void(Request*)> Callback;
|
||||||
|
|
||||||
|
enum ReadyState
|
||||||
|
{
|
||||||
|
UNSENT = 0,
|
||||||
|
OPENED,
|
||||||
|
HEADERS_RECEIVED,
|
||||||
|
LOADING,
|
||||||
|
DONE,
|
||||||
|
FAILED
|
||||||
|
};
|
||||||
|
|
||||||
virtual ~Request();
|
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 void setUrl(const std::string& url);
|
||||||
|
|
||||||
virtual std::string method() const
|
virtual std::string method() const
|
||||||
@@ -32,8 +96,8 @@ public:
|
|||||||
virtual unsigned short port() const;
|
virtual unsigned short port() const;
|
||||||
virtual std::string query() const;
|
virtual std::string query() const;
|
||||||
|
|
||||||
virtual string_list requestHeaders() const;
|
StringMap const& responseHeaders() const
|
||||||
virtual std::string header(const std::string& name) const;
|
{ return _responseHeaders; }
|
||||||
|
|
||||||
virtual int responseCode() const
|
virtual int responseCode() const
|
||||||
{ return _responseStatus; }
|
{ return _responseStatus; }
|
||||||
@@ -45,22 +109,26 @@ public:
|
|||||||
virtual unsigned int responseLength() const;
|
virtual unsigned int responseLength() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query the size of the request body. -1 (the default value) means no
|
* Check if request contains body data.
|
||||||
* request body
|
|
||||||
*/
|
*/
|
||||||
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
|
* 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.
|
* of bytes written.
|
||||||
*/
|
*/
|
||||||
virtual void getBodyData(char* s, int& count) const;
|
virtual size_t getBodyData(char* s, size_t offset, size_t max_count) const;
|
||||||
|
|
||||||
/**
|
|
||||||
* retrieve the request body content type. Default is text/plain
|
|
||||||
*/
|
|
||||||
virtual std::string requestBodyType() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* running total of body bytes received so far. Can be used
|
* running total of body bytes received so far. Can be used
|
||||||
@@ -80,43 +148,72 @@ public:
|
|||||||
HTTPVersion responseVersion() const
|
HTTPVersion responseVersion() const
|
||||||
{ return _responseVersion; }
|
{ 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 closeAfterComplete() const;
|
||||||
|
bool isComplete() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Request(const std::string& url, const std::string method = "GET");
|
Request(const std::string& url, const std::string method = "GET");
|
||||||
|
|
||||||
|
virtual void requestStart();
|
||||||
virtual void responseStart(const std::string& r);
|
virtual void responseStart(const std::string& r);
|
||||||
virtual void responseHeader(const std::string& key, const std::string& value);
|
virtual void responseHeader(const std::string& key, const std::string& value);
|
||||||
virtual void responseHeadersComplete();
|
virtual void responseHeadersComplete();
|
||||||
virtual void responseComplete();
|
virtual void responseComplete();
|
||||||
virtual void failed();
|
|
||||||
virtual void gotBodyData(const char* s, int n);
|
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:
|
private:
|
||||||
friend class Client;
|
friend class Client;
|
||||||
friend class Connection;
|
friend class Connection;
|
||||||
|
friend class ContentDecoder;
|
||||||
|
|
||||||
|
Request(const Request&); // = delete;
|
||||||
|
Request& operator=(const Request&); // = delete;
|
||||||
|
|
||||||
void processBodyBytes(const char* s, int n);
|
void processBodyBytes(const char* s, int n);
|
||||||
void setFailure(int code, const std::string& reason);
|
void setReadyState(ReadyState state);
|
||||||
|
|
||||||
std::string _method;
|
std::string _method;
|
||||||
std::string _url;
|
std::string _url;
|
||||||
|
StringMap _request_headers;
|
||||||
|
std::string _request_data;
|
||||||
|
std::string _request_media_type;
|
||||||
|
|
||||||
HTTPVersion _responseVersion;
|
HTTPVersion _responseVersion;
|
||||||
int _responseStatus;
|
int _responseStatus;
|
||||||
std::string _responseReason;
|
std::string _responseReason;
|
||||||
|
StringMap _responseHeaders;
|
||||||
unsigned int _responseLength;
|
unsigned int _responseLength;
|
||||||
unsigned int _receivedBodyBytes;
|
unsigned int _receivedBodyBytes;
|
||||||
bool _willClose;
|
|
||||||
|
|
||||||
typedef std::map<std::string, std::string> HeaderDict;
|
Callback _cb_done,
|
||||||
HeaderDict _responseHeaders;
|
_cb_fail,
|
||||||
|
_cb_always;
|
||||||
|
|
||||||
|
ReadyState _ready_state;
|
||||||
|
bool _willClose;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef SGSharedPtr<Request> Request_ptr;
|
typedef SGSharedPtr<Request> Request_ptr;
|
||||||
|
|
||||||
} // of namespace HTTP
|
} // of namespace HTTP
|
||||||
|
|
||||||
} // of namespace simgear
|
} // of namespace simgear
|
||||||
|
|
||||||
#endif // of SG_HTTP_REQUEST_HXX
|
#endif // of SG_HTTP_REQUEST_HXX
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,383 @@
|
|||||||
|
|
||||||
|
#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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,605 @@
|
|||||||
|
// 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());
|
||||||
|
unsigned char digest[MD5_DIGEST_LENGTH];
|
||||||
|
SG_MD5Final(digest, &md5Context);
|
||||||
|
decodedFileMd5 = strutils::encodeHex(digest, MD5_DIGEST_LENGTH);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
+2
-27
@@ -48,35 +48,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
string key = h.substr(0, colonPos);
|
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:
|
protected:
|
||||||
virtual void responseHeadersComplete()
|
virtual void onDone()
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void responseComplete()
|
|
||||||
{
|
{
|
||||||
_complete = true;
|
_complete = true;
|
||||||
}
|
}
|
||||||
@@ -88,7 +64,6 @@ protected:
|
|||||||
private:
|
private:
|
||||||
bool _complete;
|
bool _complete;
|
||||||
SGFile* _file;
|
SGFile* _file;
|
||||||
std::map<string, string> _headers;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
|
|||||||
@@ -454,6 +454,23 @@ bool Socket::open ( bool stream )
|
|||||||
#endif
|
#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);
|
return (handle != -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,7 +609,7 @@ int Socket::connect ( IPAddress* addr )
|
|||||||
int Socket::send (const void * buffer, int size, int flags)
|
int Socket::send (const void * buffer, int size, int flags)
|
||||||
{
|
{
|
||||||
assert ( handle != -1 ) ;
|
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 )
|
int flags, const IPAddress* to )
|
||||||
{
|
{
|
||||||
assert ( handle != -1 ) ;
|
assert ( handle != -1 ) ;
|
||||||
return ::sendto(handle,(const char*)buffer,size,flags,
|
return ::sendto( handle,
|
||||||
(const sockaddr*) to->getAddr(), to->getAddrLen());
|
(const char*)buffer,
|
||||||
|
size,
|
||||||
|
flags | MSG_NOSIGNAL,
|
||||||
|
(const sockaddr*)to->getAddr(),
|
||||||
|
to->getAddrLen() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+22
-21
@@ -43,6 +43,7 @@
|
|||||||
#include <simgear/bucket/newbucket.hxx>
|
#include <simgear/bucket/newbucket.hxx>
|
||||||
#include <simgear/misc/sg_path.hxx>
|
#include <simgear/misc/sg_path.hxx>
|
||||||
#include <simgear/math/SGGeometry.hxx>
|
#include <simgear/math/SGGeometry.hxx>
|
||||||
|
#include <simgear/structure/exception.hxx>
|
||||||
|
|
||||||
#include "lowlevel.hxx"
|
#include "lowlevel.hxx"
|
||||||
#include "sg_binobj.hxx"
|
#include "sg_binobj.hxx"
|
||||||
@@ -319,15 +320,18 @@ void SGBinObject::read_object( gzFile fp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( sgReadError() ) {
|
if ( sgReadError() ) {
|
||||||
cout << "We detected an error reading object properties" << endl;
|
throw sg_exception("Error reading object properties");
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
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 ) {
|
for ( j = 0; j < nelements; ++j ) {
|
||||||
sgReadUInt( fp, &nbytes );
|
sgReadUInt( fp, &nbytes );
|
||||||
if ( sgReadError() ) {
|
if ( sgReadError() ) {
|
||||||
cout << "We detected an error reading element size for :" << j << endl;
|
throw sg_exception("Error reading element size");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.resize( nbytes );
|
buf.resize( nbytes );
|
||||||
@@ -335,8 +339,7 @@ void SGBinObject::read_object( gzFile fp,
|
|||||||
sgReadBytes( fp, nbytes, ptr );
|
sgReadBytes( fp, nbytes, ptr );
|
||||||
|
|
||||||
if ( sgReadError() ) {
|
if ( sgReadError() ) {
|
||||||
cout << "We detected an error reading object element:" << j << "bytes="<< nbytes << endl;
|
throw sg_exception("Error reading element bytes");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int_list vs;
|
int_list vs;
|
||||||
@@ -405,7 +408,7 @@ bool SGBinObject::read_bin( const string& file ) {
|
|||||||
SG_LOG( SG_EVENT, SG_ALERT,
|
SG_LOG( SG_EVENT, SG_ALERT,
|
||||||
"ERROR: opening " << file << " or " << filegz << " for reading!");
|
"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 {
|
} else {
|
||||||
// close the file before we return
|
// close the file before we return
|
||||||
gzclose(fp);
|
gzclose(fp);
|
||||||
SG_LOG( SG_EVENT, SG_ALERT,
|
throw sg_io_exception("Bad BTG magic/version", sg_location(file));
|
||||||
"ERROR: " << file << "has bad header");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// read creation time
|
// read creation time
|
||||||
@@ -462,8 +463,7 @@ bool SGBinObject::read_bin( const string& file ) {
|
|||||||
//cout << "Total objects to read = " << nobjects << endl;
|
//cout << "Total objects to read = " << nobjects << endl;
|
||||||
|
|
||||||
if ( sgReadError() ) {
|
if ( sgReadError() ) {
|
||||||
cout << "Error while reading header of file " << file << "(.gz)" << endl;
|
throw sg_io_exception("Error reading BTG file header", sg_location(file));
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// read in objects
|
// read in objects
|
||||||
@@ -613,19 +613,13 @@ bool SGBinObject::read_bin( const string& file ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( sgReadError() ) {
|
if ( sgReadError() ) {
|
||||||
cout << "Error while reading object:" << i << " in file " << file << "(.gz)" << endl;
|
throw sg_io_exception("Error while reading object", sg_location(file, i));
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// close the file
|
// close the file
|
||||||
gzclose(fp);
|
gzclose(fp);
|
||||||
|
|
||||||
if ( sgReadError() ) {
|
|
||||||
cout << "Error while reading file " << file << "(.gz)" << endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 ( !normals.empty() && !normals.front().empty()) idx_mask |= SG_IDX_NORMALS;
|
||||||
if ( !colors.empty() && !colors.front().empty()) idx_mask |= SG_IDX_COLORS;
|
if ( !colors.empty() && !colors.front().empty()) idx_mask |= SG_IDX_COLORS;
|
||||||
if ( !texCoords.empty() && !texCoords.front().empty()) idx_mask |= SG_IDX_TEXCOORDS;
|
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
|
sgWriteChar( fp, (char)SG_INDEX_TYPES ); // property
|
||||||
sgWriteUInt( fp, 1 ); // nbytes
|
sgWriteUInt( fp, 1 ); // nbytes
|
||||||
sgWriteChar( fp, idx_mask );
|
sgWriteChar( fp, idx_mask );
|
||||||
@@ -934,7 +935,7 @@ bool SGBinObject::write_ascii( const string& base, const string& name,
|
|||||||
fprintf(fp, "\n");
|
fprintf(fp, "\n");
|
||||||
|
|
||||||
// dump individual triangles if they exist
|
// dump individual triangles if they exist
|
||||||
if ( tris_v.size() > 0 ) {
|
if ( ! tris_v.empty() ) {
|
||||||
fprintf(fp, "# triangle groups\n");
|
fprintf(fp, "# triangle groups\n");
|
||||||
|
|
||||||
int start = 0;
|
int start = 0;
|
||||||
@@ -982,7 +983,7 @@ bool SGBinObject::write_ascii( const string& base, const string& name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dump triangle groups
|
// dump triangle groups
|
||||||
if ( strips_v.size() > 0 ) {
|
if ( ! strips_v.empty() ) {
|
||||||
fprintf(fp, "# triangle strips\n");
|
fprintf(fp, "# triangle strips\n");
|
||||||
|
|
||||||
int start = 0;
|
int start = 0;
|
||||||
|
|||||||
+113
-105
@@ -41,8 +41,6 @@
|
|||||||
|
|
||||||
namespace simgear {
|
namespace simgear {
|
||||||
|
|
||||||
static NetChannel* channels = 0 ;
|
|
||||||
|
|
||||||
NetChannel::NetChannel ()
|
NetChannel::NetChannel ()
|
||||||
{
|
{
|
||||||
closed = true ;
|
closed = true ;
|
||||||
@@ -51,31 +49,14 @@ NetChannel::NetChannel ()
|
|||||||
accepting = false ;
|
accepting = false ;
|
||||||
write_blocked = false ;
|
write_blocked = false ;
|
||||||
should_delete = false ;
|
should_delete = false ;
|
||||||
|
poller = NULL;
|
||||||
next_channel = channels ;
|
|
||||||
channels = this ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NetChannel::~NetChannel ()
|
NetChannel::~NetChannel ()
|
||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
|
if (poller) {
|
||||||
NetChannel* prev = NULL ;
|
poller->removeChannel(this);
|
||||||
|
|
||||||
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 ;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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) {
|
void NetChannel::handleRead (void) {
|
||||||
SG_LOG(SG_IO, SG_WARN, "Network:" << getHandle() << ": unhandled read");
|
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
|
} // 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