diff options
104 files changed, 7112 insertions, 652 deletions
diff --git a/indra/integration_tests/llimage_libtest/CMakeLists.txt b/indra/integration_tests/llimage_libtest/CMakeLists.txt index 36a7d38bb7..8a83ac498f 100755 --- a/indra/integration_tests/llimage_libtest/CMakeLists.txt +++ b/indra/integration_tests/llimage_libtest/CMakeLists.txt @@ -7,6 +7,7 @@ project (llimage_libtest) include(00-Common) include(LLCommon) include(LLImage) +include(LLMath) include(LLImageJ2COJ) include(LLKDU) include(LLVFS) @@ -15,6 +16,7 @@ include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLVFS_INCLUDE_DIRS} ${LLIMAGE_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} ) include_directories(SYSTEM ${LLCOMMON_SYSTEM_INCLUDE_DIRS} @@ -64,6 +66,7 @@ endif (DARWIN) target_link_libraries(llimage_libtest ${LLCOMMON_LIBRARIES} ${LLVFS_LIBRARIES} + ${LLMATH_LIBRARIES} ${LLIMAGE_LIBRARIES} ${LLKDU_LIBRARIES} ${KDU_LIBRARY} diff --git a/indra/integration_tests/llimage_libtest/filters/1970colorize.xml b/indra/integration_tests/llimage_libtest/filters/1970colorize.xml new file mode 100644 index 0000000000..0dab2489a0 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/1970colorize.xml @@ -0,0 +1,41 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>contrast</string> + <real>0.8</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.5</real> + <real>0.0</real> + <real>0.0</real> + </array> + <array> + <string>blend</string> + <real>10.0</real> + <real>0.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.1</real> + <real>0.1</real> + <real>0.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/badtrip.xml b/indra/integration_tests/llimage_libtest/filters/badtrip.xml new file mode 100755 index 0000000000..14ee0baff3 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/badtrip.xml @@ -0,0 +1,36 @@ +<llsd> + <array> + <array> + <string>grayscale</string> + </array> + <array> + <string>linearize</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>posterize</string> + <real>10.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>gradient</string> + </array> + <array> + <string>colorize</string> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>0.15</real> + </array> + <array> + <string>blur</string> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/blowhighlights.xml b/indra/integration_tests/llimage_libtest/filters/blowhighlights.xml new file mode 100644 index 0000000000..2474a1b953 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/blowhighlights.xml @@ -0,0 +1,25 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>stencil</string> + <string>uniform</string> + <string>add</string> + <real>0.0</real> + <real>1.0</real> + </array> + <array> + <string>gamma</string> + <real>0.25</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/blur.xml b/indra/integration_tests/llimage_libtest/filters/blur.xml new file mode 100644 index 0000000000..addd056855 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/blur.xml @@ -0,0 +1,7 @@ +<llsd> + <array> + <array> + <string>blur</string> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/brighten.xml b/indra/integration_tests/llimage_libtest/filters/brighten.xml new file mode 100755 index 0000000000..9b4232229f --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/brighten.xml @@ -0,0 +1,11 @@ +<llsd> + <array> + <array> + <string>brighten</string> + <real>0.5</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/colorize.xml b/indra/integration_tests/llimage_libtest/filters/colorize.xml new file mode 100644 index 0000000000..72e58b0ffe --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/colorize.xml @@ -0,0 +1,24 @@ +<llsd> + <array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>blend</string> + <real>0.0</real> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>10.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>0.5</real> + <real>0.5</real> + <real>0.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/colortransform.xml b/indra/integration_tests/llimage_libtest/filters/colortransform.xml new file mode 100644 index 0000000000..de4bebcce2 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/colortransform.xml @@ -0,0 +1,16 @@ +<llsd> + <array> + <array> + <string>colortransform</string> + <real>0.2125</real> + <real>0.7154</real> + <real>0.0721</real> + <real>0.2125</real> + <real>0.7154</real> + <real>0.0721</real> + <real>0.2125</real> + <real>0.7154</real> + <real>0.0721</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/contrast.xml b/indra/integration_tests/llimage_libtest/filters/contrast.xml new file mode 100644 index 0000000000..00746b8a9e --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/contrast.xml @@ -0,0 +1,11 @@ +<llsd> + <array> + <array> + <string>contrast</string> + <real>1.5</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/convolve.xml b/indra/integration_tests/llimage_libtest/filters/convolve.xml new file mode 100644 index 0000000000..6e65b5f88a --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/convolve.xml @@ -0,0 +1,18 @@ +<llsd> + <array> + <array> + <string>convolve</string> + <real>1.0</real> + <real>0.0</real> + <real>4.0</real> + <real>1.0</real> + <real>4.0</real> + <real>1.0</real> + <real>0.0</real> + <real>1.0</real> + <real>4.0</real> + <real>1.0</real> + <real>4.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/darken.xml b/indra/integration_tests/llimage_libtest/filters/darken.xml new file mode 100755 index 0000000000..5cec3589b6 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/darken.xml @@ -0,0 +1,11 @@ +<llsd> + <array> + <array> + <string>darken</string> + <real>0.5</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/dodgeandburn.xml b/indra/integration_tests/llimage_libtest/filters/dodgeandburn.xml new file mode 100644 index 0000000000..0e2e0ad68c --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/dodgeandburn.xml @@ -0,0 +1,47 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>add</string> + <real>0.0</real> + <real>0.4</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>2.0</real> + </array> + <array> + <string>contrast</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>add</string> + <real>-0.8</real> + <real>0.0</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>2.0</real> + </array> + <array> + <string>contrast</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/edges.xml b/indra/integration_tests/llimage_libtest/filters/edges.xml new file mode 100644 index 0000000000..a66b81d01e --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/edges.xml @@ -0,0 +1,24 @@ +<llsd> + <array> + <array> + <string>gradient</string> + </array> + <array> + <string>blur</string> + </array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>contrast</string> + <real>2.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/focus.xml b/indra/integration_tests/llimage_libtest/filters/focus.xml new file mode 100644 index 0000000000..d8525fea62 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/focus.xml @@ -0,0 +1,39 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>blend</string> + <real>0.0</real> + <real>0.4</real> + <real>0.0</real> + <real>0.0</real> + <real>0.5</real> + <real>2.0</real> + </array> + <array> + <string>sharpen</string> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>blend</string> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>0.0</real> + <real>0.5</real> + <real>2.0</real> + </array> + <array> + <string>blur</string> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/gamma.xml b/indra/integration_tests/llimage_libtest/filters/gamma.xml new file mode 100644 index 0000000000..19af09b046 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/gamma.xml @@ -0,0 +1,11 @@ +<llsd> + <array> + <array> + <string>gamma</string> + <real>1.7</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/grayscale.xml b/indra/integration_tests/llimage_libtest/filters/grayscale.xml new file mode 100644 index 0000000000..984312c4fd --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/grayscale.xml @@ -0,0 +1,14 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>grayscale</string> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/horizontalscreen.xml b/indra/integration_tests/llimage_libtest/filters/horizontalscreen.xml new file mode 100644 index 0000000000..21cab70e54 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/horizontalscreen.xml @@ -0,0 +1,20 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>grayscale</string> + </array> + <array> + <string>screen</string> + <string>line</string> + <real>0.015</real> + <real>0.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/lensflare.xml b/indra/integration_tests/llimage_libtest/filters/lensflare.xml new file mode 100644 index 0000000000..ec8afc8daa --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/lensflare.xml @@ -0,0 +1,138 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.01</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>brighten</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>add</string> + <real>0.0</real> + <real>0.4</real> + <real>-0.5</real> + <real>0.5</real> + <real>0.15</real> + <real>20.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.15</real> + <real>0.15</real> + <real>0.15</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>add</string> + <real>0.0</real> + <real>0.4</real> + <real>-0.5</real> + <real>0.5</real> + <real>0.20</real> + <real>20.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.6</real> + <real>0.0</real> + <real>0.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>add</string> + <real>0.0</real> + <real>0.5</real> + <real>-0.7</real> + <real>0.7</real> + <real>0.10</real> + <real>20.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.6</real> + <real>0.6</real> + <real>0.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>add</string> + <real>0.0</real> + <real>0.5</real> + <real>0.5</real> + <real>-0.5</real> + <real>0.10</real> + <real>20.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.7</real> + <real>0.0</real> + <real>0.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>add</string> + <real>0.0</real> + <real>0.5</real> + <real>0.6</real> + <real>-0.6</real> + <real>0.05</real> + <real>20.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.7</real> + <real>0.0</real> + <real>0.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>add</string> + <real>0.0</real> + <real>0.5</real> + <real>0.4</real> + <real>-0.4</real> + <real>0.025</real> + <real>20.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.7</real> + <real>0.0</real> + <real>0.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/linearize.xml b/indra/integration_tests/llimage_libtest/filters/linearize.xml new file mode 100755 index 0000000000..23d0290e07 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/linearize.xml @@ -0,0 +1,11 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/newsscreen.xml b/indra/integration_tests/llimage_libtest/filters/newsscreen.xml new file mode 100755 index 0000000000..50ed27c6db --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/newsscreen.xml @@ -0,0 +1,20 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>grayscale</string> + </array> + <array> + <string>screen</string> + <string>2Dsine</string> + <real>0.015</real> + <real>0.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/posterize.xml b/indra/integration_tests/llimage_libtest/filters/posterize.xml new file mode 100755 index 0000000000..4d03df3c66 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/posterize.xml @@ -0,0 +1,11 @@ +<llsd> + <array> + <array> + <string>posterize</string> + <real>10.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/rotatecolors180.xml b/indra/integration_tests/llimage_libtest/filters/rotatecolors180.xml new file mode 100644 index 0000000000..e25029720f --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/rotatecolors180.xml @@ -0,0 +1,8 @@ +<llsd> + <array> + <array> + <string>rotate</string> + <real>180.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/saturate.xml b/indra/integration_tests/llimage_libtest/filters/saturate.xml new file mode 100644 index 0000000000..b77f07a037 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/saturate.xml @@ -0,0 +1,8 @@ +<llsd> + <array> + <array> + <string>saturate</string> + <real>3.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/sepia.xml b/indra/integration_tests/llimage_libtest/filters/sepia.xml new file mode 100644 index 0000000000..0304ead015 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/sepia.xml @@ -0,0 +1,14 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>sepia</string> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/sharpen.xml b/indra/integration_tests/llimage_libtest/filters/sharpen.xml new file mode 100644 index 0000000000..6d3f9ae1a2 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/sharpen.xml @@ -0,0 +1,7 @@ +<llsd> + <array> + <array> + <string>sharpen</string> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/slantedscreen.xml b/indra/integration_tests/llimage_libtest/filters/slantedscreen.xml new file mode 100644 index 0000000000..6cd1a96185 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/slantedscreen.xml @@ -0,0 +1,20 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>grayscale</string> + </array> + <array> + <string>screen</string> + <string>line</string> + <real>0.015</real> + <real>45.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/spotlight.xml b/indra/integration_tests/llimage_libtest/filters/spotlight.xml new file mode 100644 index 0000000000..203130bdee --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/spotlight.xml @@ -0,0 +1,45 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>contrast</string> + <real>0.8</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>saturate</string> + <real>1.5</real> + </array> + <array> + <string>fade</string> + <real>1.0</real> + <real>0.25</real> + </array> + <array> + <string>saturate</string> + <real>0.8</real> + </array> + <array> + <string>contrast</string> + <real>1.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>brighten</string> + <real>30</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/stencilgradient.xml b/indra/integration_tests/llimage_libtest/filters/stencilgradient.xml new file mode 100644 index 0000000000..d22809a9bf --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/stencilgradient.xml @@ -0,0 +1,24 @@ +<llsd> + <array> + <array> + <string>stencil</string> + <string>gradient</string> + <string>blend</string> + <real>0.0</real> + <real>1.0</real> + <real>0.0</real> + <real>-1.0</real> + <real>0.0</real> + <real>1.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/stencilscanlines.xml b/indra/integration_tests/llimage_libtest/filters/stencilscanlines.xml new file mode 100644 index 0000000000..3ce428503d --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/stencilscanlines.xml @@ -0,0 +1,22 @@ +<llsd> + <array> + <array> + <string>stencil</string> + <string>scanlines</string> + <string>blend</string> + <real>0.0</real> + <real>0.5</real> + <real>0.1</real> + <real>45.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/stenciluniform.xml b/indra/integration_tests/llimage_libtest/filters/stenciluniform.xml new file mode 100644 index 0000000000..7d72f0ed93 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/stenciluniform.xml @@ -0,0 +1,20 @@ +<llsd> + <array> + <array> + <string>stencil</string> + <string>uniform</string> + <string>blend</string> + <real>0.0</real> + <real>0.5</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/stencilvignette.xml b/indra/integration_tests/llimage_libtest/filters/stencilvignette.xml new file mode 100644 index 0000000000..d30637fef5 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/stencilvignette.xml @@ -0,0 +1,24 @@ +<llsd> + <array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>blend</string> + <real>0.0</real> + <real>0.5</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>10.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/verticalscreen.xml b/indra/integration_tests/llimage_libtest/filters/verticalscreen.xml new file mode 100644 index 0000000000..0768d1d7e1 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/verticalscreen.xml @@ -0,0 +1,20 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>grayscale</string> + </array> + <array> + <string>screen</string> + <string>line</string> + <real>0.015</real> + <real>90.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/filters/video.xml b/indra/integration_tests/llimage_libtest/filters/video.xml new file mode 100755 index 0000000000..8b10687ef5 --- /dev/null +++ b/indra/integration_tests/llimage_libtest/filters/video.xml @@ -0,0 +1,23 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.01</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>lines</string> + <real>10.0</real> + <real>0.0</real> + </array> + <array> + <string>brighten</string> + <real>100.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/integration_tests/llimage_libtest/llimage_libtest.cpp b/indra/integration_tests/llimage_libtest/llimage_libtest.cpp index 034c816742..3d27b4a5b5 100755 --- a/indra/integration_tests/llimage_libtest/llimage_libtest.cpp +++ b/indra/integration_tests/llimage_libtest/llimage_libtest.cpp @@ -32,6 +32,7 @@ // Linden library includes #include "llimage.h" +#include "llimagefilter.h" #include "llimagejpeg.h" #include "llimagepng.h" #include "llimagebmp.h" @@ -39,6 +40,8 @@ #include "llimagej2c.h" #include "lldir.h" #include "lldiriterator.h" +#include "v4coloru.h" +#include "llsdserialize.h" // system libraries #include <iostream> @@ -83,6 +86,8 @@ static const char USAGE[] = "\n" " -rev, --reversible\n" " Set the compression to be lossless (reversible in j2c parlance).\n" " Only valid for output j2c images.\n" +" -f, --filter <file>\n" +" Apply the filter <file> to the input images.\n" " -log, --logmetrics <metric>\n" " Log performance data for <metric>. Results in <metric>.slp\n" " Note: so far, only ImageCompressionTester has been tested.\n" @@ -99,7 +104,7 @@ static bool sAllDone = false; // Create an empty formatted image instance of the correct type from the filename LLPointer<LLImageFormatted> create_image(const std::string &filename) { - std::string exten = gDirUtilp->getExtension(filename); + std::string exten = gDirUtilp->getExtension(filename); LLPointer<LLImageFormatted> image = LLImageFormatted::createFromExtension(exten); return image; } @@ -350,6 +355,7 @@ int main(int argc, char** argv) int blocks_size = -1; int levels = 0; bool reversible = false; + std::string filter_name = ""; // Init whatever is necessary ll_init_apr(); @@ -523,7 +529,26 @@ int main(int argc, char** argv) break; } } - else if (!strcmp(argv[arg], "--analyzeperformance") || !strcmp(argv[arg], "-a")) + else if (!strcmp(argv[arg], "--filter") || !strcmp(argv[arg], "-f")) + { + // '--filter' needs to be specified with a named filter argument + if ((arg + 1) < argc) + { + filter_name = argv[arg+1]; + } + if (((arg + 1) >= argc) || (filter_name[0] == '-')) + { + // We don't have an argument left in the arg list or the next argument is another option + std::cout << "No --filter argument given, no filter will be applied" << std::endl; + } + else + { + arg += 1; // Skip that arg now we know it's a valid test name + if ((arg + 1) == argc) // Break out of the loop if we reach the end of the arg list + break; + } + } + else if (!strcmp(argv[arg], "--analyzeperformance") || !strcmp(argv[arg], "-a")) { analyze_performance = true; } @@ -553,7 +578,10 @@ int main(int argc, char** argv) fast_timer_log_thread = new LogThread(LLFastTimer::sLogName); fast_timer_log_thread->start(); } - + + // Load the filter once and for all + LLImageFilter filter(filter_name); + // Perform action on each input file std::list<std::string>::iterator in_file = input_filenames.begin(); std::list<std::string>::iterator out_file = output_filenames.begin(); @@ -568,7 +596,10 @@ int main(int argc, char** argv) std::cout << "Error: Image " << *in_file << " could not be loaded" << std::endl; continue; } - + + // Apply the filter + filter.executeFilter(raw_image); + // Save file if (out_file != out_end) { diff --git a/indra/llimage/CMakeLists.txt b/indra/llimage/CMakeLists.txt index e837b0cac2..293ada7548 100755 --- a/indra/llimage/CMakeLists.txt +++ b/indra/llimage/CMakeLists.txt @@ -27,6 +27,7 @@ set(llimage_SOURCE_FILES llimage.cpp llimagedimensionsinfo.cpp llimagedxt.cpp + llimagefilter.cpp llimagej2c.cpp llimagejpeg.cpp llimagepng.cpp @@ -42,6 +43,7 @@ set(llimage_HEADER_FILES llimagebmp.h llimagedimensionsinfo.h llimagedxt.h + llimagefilter.h llimagej2c.h llimagejpeg.h llimagepng.h diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index c8a05e1fae..18e08b94a6 100755 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -449,18 +449,8 @@ void LLImageRaw::verticalFlip() void LLImageRaw::expandToPowerOfTwo(S32 max_dim, BOOL scale_image) { // Find new sizes - S32 new_width = MIN_IMAGE_SIZE; - S32 new_height = MIN_IMAGE_SIZE; - - while( (new_width < getWidth()) && (new_width < max_dim) ) - { - new_width <<= 1; - } - - while( (new_height < getHeight()) && (new_height < max_dim) ) - { - new_height <<= 1; - } + S32 new_width = expandDimToPowerOfTwo(getWidth(), max_dim); + S32 new_height = expandDimToPowerOfTwo(getHeight(), max_dim); scale( new_width, new_height, scale_image ); } @@ -468,55 +458,61 @@ void LLImageRaw::expandToPowerOfTwo(S32 max_dim, BOOL scale_image) void LLImageRaw::contractToPowerOfTwo(S32 max_dim, BOOL scale_image) { // Find new sizes - S32 new_width = max_dim; - S32 new_height = max_dim; - - while( (new_width > getWidth()) && (new_width > MIN_IMAGE_SIZE) ) - { - new_width >>= 1; - } - - while( (new_height > getHeight()) && (new_height > MIN_IMAGE_SIZE) ) - { - new_height >>= 1; - } + S32 new_width = contractDimToPowerOfTwo(getWidth(), MIN_IMAGE_SIZE); + S32 new_height = contractDimToPowerOfTwo(getHeight(), MIN_IMAGE_SIZE); scale( new_width, new_height, scale_image ); } -void LLImageRaw::biasedScaleToPowerOfTwo(S32 max_dim) +// static +S32 LLImageRaw::biasedDimToPowerOfTwo(S32 curr_dim, S32 max_dim) { // Strong bias towards rounding down (to save bandwidth) // No bias would mean THRESHOLD == 1.5f; - const F32 THRESHOLD = 1.75f; - + const F32 THRESHOLD = 1.75f; + // Find new sizes - S32 larger_w = max_dim; // 2^n >= mWidth - S32 smaller_w = max_dim; // 2^(n-1) <= mWidth - while( (smaller_w > getWidth()) && (smaller_w > MIN_IMAGE_SIZE) ) + S32 larger_dim = max_dim; // 2^n >= curr_dim + S32 smaller_dim = max_dim; // 2^(n-1) <= curr_dim + while( (smaller_dim > curr_dim) && (smaller_dim > MIN_IMAGE_SIZE) ) { - larger_w = smaller_w; - smaller_w >>= 1; + larger_dim = smaller_dim; + smaller_dim >>= 1; } - S32 new_width = ( (F32)getWidth() / smaller_w > THRESHOLD ) ? larger_w : smaller_w; + return ( ((F32)curr_dim / (F32)smaller_dim) > THRESHOLD ) ? larger_dim : smaller_dim; +} +// static +S32 LLImageRaw::expandDimToPowerOfTwo(S32 curr_dim, S32 max_dim) +{ + S32 new_dim = MIN_IMAGE_SIZE; + while( (new_dim < curr_dim) && (new_dim < max_dim) ) + { + new_dim <<= 1; + } + return new_dim; +} - S32 larger_h = max_dim; // 2^m >= mHeight - S32 smaller_h = max_dim; // 2^(m-1) <= mHeight - while( (smaller_h > getHeight()) && (smaller_h > MIN_IMAGE_SIZE) ) +// static +S32 LLImageRaw::contractDimToPowerOfTwo(S32 curr_dim, S32 min_dim) +{ + S32 new_dim = MAX_IMAGE_SIZE; + while( (new_dim > curr_dim) && (new_dim > min_dim) ) { - larger_h = smaller_h; - smaller_h >>= 1; + new_dim >>= 1; } - S32 new_height = ( (F32)getHeight() / smaller_h > THRESHOLD ) ? larger_h : smaller_h; + return new_dim; +} +void LLImageRaw::biasedScaleToPowerOfTwo(S32 max_dim) +{ + // Find new sizes + S32 new_width = biasedDimToPowerOfTwo(getWidth(),max_dim); + S32 new_height = biasedDimToPowerOfTwo(getHeight(),max_dim); scale( new_width, new_height ); } - - - // Calculates (U8)(255*(a/255.f)*(b/255.f) + 0.5f). Thanks, Jim Blinn! inline U8 LLImageRaw::fastFractionalMult( U8 a, U8 b ) { diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h index 2277afc585..c1ba1e3c21 100755 --- a/indra/llimage/llimage.h +++ b/indra/llimage/llimage.h @@ -205,6 +205,9 @@ public: void verticalFlip(); + static S32 biasedDimToPowerOfTwo(S32 curr_dim, S32 max_dim = MAX_IMAGE_SIZE); + static S32 expandDimToPowerOfTwo(S32 curr_dim, S32 max_dim = MAX_IMAGE_SIZE); + static S32 contractDimToPowerOfTwo(S32 curr_dim, S32 min_dim = MIN_IMAGE_SIZE); void expandToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE, BOOL scale_image = TRUE); void contractToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE, BOOL scale_image = TRUE); void biasedScaleToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE); diff --git a/indra/llimage/llimagefilter.cpp b/indra/llimage/llimagefilter.cpp new file mode 100755 index 0000000000..3d0c488768 --- /dev/null +++ b/indra/llimage/llimagefilter.cpp @@ -0,0 +1,939 @@ +/** + * @file llimagefilter.cpp + * @brief Simple Image Filtering. See https://wiki.lindenlab.com/wiki/SL_Viewer_Image_Filters for complete documentation. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llimagefilter.h" + +#include "llmath.h" +#include "v3color.h" +#include "v4coloru.h" +#include "m3math.h" +#include "v3math.h" +#include "llsdserialize.h" +#include "llstring.h" + +//--------------------------------------------------------------------------- +// LLImageFilter +//--------------------------------------------------------------------------- + +LLImageFilter::LLImageFilter(const std::string& file_path) : + mFilterData(LLSD::emptyArray()), + mImage(NULL), + mHistoRed(NULL), + mHistoGreen(NULL), + mHistoBlue(NULL), + mHistoBrightness(NULL), + mStencilBlendMode(STENCIL_BLEND_MODE_BLEND), + mStencilShape(STENCIL_SHAPE_UNIFORM), + mStencilGamma(1.0), + mStencilMin(0.0), + mStencilMax(1.0) +{ + // Load filter description from file + llifstream filter_xml(file_path); + if (filter_xml.is_open()) + { + // Load and parse the file + LLPointer<LLSDParser> parser = new LLSDXMLParser(); + parser->parse(filter_xml, mFilterData, LLSDSerialize::SIZE_UNLIMITED); + filter_xml.close(); + } +} + +LLImageFilter::~LLImageFilter() +{ + mImage = NULL; + ll_aligned_free_16(mHistoRed); + ll_aligned_free_16(mHistoGreen); + ll_aligned_free_16(mHistoBlue); + ll_aligned_free_16(mHistoBrightness); +} + +/* + *TODO + * Rename stencil to mask + * Improve perf: use LUT for alpha blending in uniform case + * Add gradient coloring as a filter + */ + +//============================================================================ +// Apply the filter data to the image passed as parameter +//============================================================================ + +void LLImageFilter::executeFilter(LLPointer<LLImageRaw> raw_image) +{ + mImage = raw_image; + + //std::cout << "Filter : size = " << mFilterData.size() << std::endl; + for (S32 i = 0; i < mFilterData.size(); ++i) + { + std::string filter_name = mFilterData[i][0].asString(); + // Dump out the filter values (for debug) + //std::cout << "Filter : name = " << mFilterData[i][0].asString() << ", params = "; + //for (S32 j = 1; j < mFilterData[i].size(); ++j) + //{ + // std::cout << mFilterData[i][j].asString() << ", "; + //} + //std::cout << std::endl; + + if (filter_name == "stencil") + { + // Get the shape of the stencil, that is how the procedural alpha is computed geometrically + std::string filter_shape = mFilterData[i][1].asString(); + EStencilShape shape = STENCIL_SHAPE_UNIFORM; + if (filter_shape == "uniform") + { + shape = STENCIL_SHAPE_UNIFORM; + } + else if (filter_shape == "gradient") + { + shape = STENCIL_SHAPE_GRADIENT; + } + else if (filter_shape == "vignette") + { + shape = STENCIL_SHAPE_VIGNETTE; + } + else if (filter_shape == "scanlines") + { + shape = STENCIL_SHAPE_SCAN_LINES; + } + // Get the blend mode of the stencil, that is how the effect is blended in the background through the stencil + std::string filter_mode = mFilterData[i][2].asString(); + EStencilBlendMode mode = STENCIL_BLEND_MODE_BLEND; + if (filter_mode == "blend") + { + mode = STENCIL_BLEND_MODE_BLEND; + } + else if (filter_mode == "add") + { + mode = STENCIL_BLEND_MODE_ADD; + } + else if (filter_mode == "add_back") + { + mode = STENCIL_BLEND_MODE_ABACK; + } + else if (filter_mode == "fade") + { + mode = STENCIL_BLEND_MODE_FADE; + } + // Get the float params: mandatory min, max then the optional parameters (4 max) + F32 min = (F32)(mFilterData[i][3].asReal()); + F32 max = (F32)(mFilterData[i][4].asReal()); + F32 params[4] = {0.0, 0.0, 0.0, 0.0}; + for (S32 j = 5; (j < mFilterData[i].size()) && (j < 9); j++) + { + params[j-5] = (F32)(mFilterData[i][j].asReal()); + } + // Set the stencil + setStencil(shape,mode,min,max,params); + } + else if (filter_name == "sepia") + { + filterSepia(); + } + else if (filter_name == "grayscale") + { + filterGrayScale(); + } + else if (filter_name == "saturate") + { + filterSaturate((float)(mFilterData[i][1].asReal())); + } + else if (filter_name == "rotate") + { + filterRotate((float)(mFilterData[i][1].asReal())); + } + else if (filter_name == "gamma") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterGamma((float)(mFilterData[i][1].asReal()),color); + } + else if (filter_name == "colorize") + { + LLColor3 color((float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal())); + LLColor3 alpha((F32)(mFilterData[i][4].asReal()),(float)(mFilterData[i][5].asReal()),(float)(mFilterData[i][6].asReal())); + filterColorize(color,alpha); + } + else if (filter_name == "contrast") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterContrast((float)(mFilterData[i][1].asReal()),color); + } + else if (filter_name == "brighten") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterBrightness((float)(mFilterData[i][1].asReal()),color); + } + else if (filter_name == "darken") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterBrightness((float)(-mFilterData[i][1].asReal()),color); + } + else if (filter_name == "linearize") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterLinearize((float)(mFilterData[i][1].asReal()),color); + } + else if (filter_name == "posterize") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterEqualize((S32)(mFilterData[i][1].asReal()),color); + } + else if (filter_name == "screen") + { + std::string screen_name = mFilterData[i][1].asString(); + EScreenMode mode = SCREEN_MODE_2DSINE; + if (screen_name == "2Dsine") + { + mode = SCREEN_MODE_2DSINE; + } + else if (screen_name == "line") + { + mode = SCREEN_MODE_LINE; + } + filterScreen(mode,(F32)(mFilterData[i][2].asReal()),(F32)(mFilterData[i][3].asReal())); + } + else if (filter_name == "blur") + { + LLMatrix3 kernel; + for (S32 i = 0; i < NUM_VALUES_IN_MAT3; i++) + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + kernel.mMatrix[i][j] = 1.0; + convolve(kernel,true,false); + } + else if (filter_name == "sharpen") + { + LLMatrix3 kernel; + for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++) + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + kernel.mMatrix[k][j] = -1.0; + kernel.mMatrix[1][1] = 9.0; + convolve(kernel,false,false); + } + else if (filter_name == "gradient") + { + LLMatrix3 kernel; + for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++) + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + kernel.mMatrix[k][j] = -1.0; + kernel.mMatrix[1][1] = 8.0; + convolve(kernel,false,true); + } + else if (filter_name == "convolve") + { + LLMatrix3 kernel; + S32 index = 1; + bool normalize = (mFilterData[i][index++].asReal() > 0.0); + bool abs_value = (mFilterData[i][index++].asReal() > 0.0); + for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++) + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + kernel.mMatrix[k][j] = mFilterData[i][index++].asReal(); + convolve(kernel,normalize,abs_value); + } + else if (filter_name == "colortransform") + { + LLMatrix3 transform; + S32 index = 1; + for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++) + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + transform.mMatrix[k][j] = mFilterData[i][index++].asReal(); + transform.transpose(); + colorTransform(transform); + } + else + { + llwarns << "Filter unknown, cannot execute filter command : " << filter_name << llendl; + } + } +} + +//============================================================================ +// Filter Primitives +//============================================================================ + +void LLImageFilter::blendStencil(F32 alpha, U8* pixel, U8 red, U8 green, U8 blue) +{ + F32 inv_alpha = 1.0 - alpha; + switch (mStencilBlendMode) + { + case STENCIL_BLEND_MODE_BLEND: + // Classic blend of incoming color with the background image + pixel[VRED] = inv_alpha * pixel[VRED] + alpha * red; + pixel[VGREEN] = inv_alpha * pixel[VGREEN] + alpha * green; + pixel[VBLUE] = inv_alpha * pixel[VBLUE] + alpha * blue; + break; + case STENCIL_BLEND_MODE_ADD: + // Add incoming color to the background image + pixel[VRED] = llclampb(pixel[VRED] + alpha * red); + pixel[VGREEN] = llclampb(pixel[VGREEN] + alpha * green); + pixel[VBLUE] = llclampb(pixel[VBLUE] + alpha * blue); + break; + case STENCIL_BLEND_MODE_ABACK: + // Add back background image to the incoming color + pixel[VRED] = llclampb(inv_alpha * pixel[VRED] + red); + pixel[VGREEN] = llclampb(inv_alpha * pixel[VGREEN] + green); + pixel[VBLUE] = llclampb(inv_alpha * pixel[VBLUE] + blue); + break; + case STENCIL_BLEND_MODE_FADE: + // Fade incoming color to black + pixel[VRED] = alpha * red; + pixel[VGREEN] = alpha * green; + pixel[VBLUE] = alpha * blue; + break; + } +} + +void LLImageFilter::colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue) +{ + const S32 components = mImage->getComponents(); + llassert( components >= 1 && components <= 4 ); + + S32 width = mImage->getWidth(); + S32 height = mImage->getHeight(); + + U8* dst_data = mImage->getData(); + for (S32 j = 0; j < height; j++) + { + for (S32 i = 0; i < width; i++) + { + // Blend LUT value + blendStencil(getStencilAlpha(i,j), dst_data, lut_red[dst_data[VRED]], lut_green[dst_data[VGREEN]], lut_blue[dst_data[VBLUE]]); + dst_data += components; + } + } +} + +void LLImageFilter::colorTransform(const LLMatrix3 &transform) +{ + const S32 components = mImage->getComponents(); + llassert( components >= 1 && components <= 4 ); + + S32 width = mImage->getWidth(); + S32 height = mImage->getHeight(); + + U8* dst_data = mImage->getData(); + for (S32 j = 0; j < height; j++) + { + for (S32 i = 0; i < width; i++) + { + // Compute transform + LLVector3 src((F32)(dst_data[VRED]),(F32)(dst_data[VGREEN]),(F32)(dst_data[VBLUE])); + LLVector3 dst = src * transform; + dst.clamp(0.0f,255.0f); + + // Blend result + blendStencil(getStencilAlpha(i,j), dst_data, dst.mV[VRED], dst.mV[VGREEN], dst.mV[VBLUE]); + dst_data += components; + } + } +} + +void LLImageFilter::convolve(const LLMatrix3 &kernel, bool normalize, bool abs_value) +{ + const S32 components = mImage->getComponents(); + llassert( components >= 1 && components <= 4 ); + + // Compute normalization factors + F32 kernel_min = 0.0; + F32 kernel_max = 0.0; + for (S32 i = 0; i < NUM_VALUES_IN_MAT3; i++) + { + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + { + if (kernel.mMatrix[i][j] >= 0.0) + kernel_max += kernel.mMatrix[i][j]; + else + kernel_min += kernel.mMatrix[i][j]; + } + } + if (abs_value) + { + kernel_max = llabs(kernel_max); + kernel_min = llabs(kernel_min); + kernel_max = llmax(kernel_max,kernel_min); + kernel_min = 0.0; + } + F32 kernel_range = kernel_max - kernel_min; + + // Allocate temporary buffers and initialize algorithm's data + S32 width = mImage->getWidth(); + S32 height = mImage->getHeight(); + + U8* dst_data = mImage->getData(); + + S32 buffer_size = width * components; + llassert_always(buffer_size > 0); + std::vector<U8> even_buffer(buffer_size); + std::vector<U8> odd_buffer(buffer_size); + + U8* south_data = dst_data + buffer_size; + U8* east_west_data; + U8* north_data; + + // Line 0 : we set the line to 0 (debatable) + memcpy( &even_buffer[0], dst_data, buffer_size ); /* Flawfinder: ignore */ + for (S32 i = 0; i < width; i++) + { + blendStencil(getStencilAlpha(i,0), dst_data, 0, 0, 0); + dst_data += components; + } + south_data += buffer_size; + + // All other lines + for (S32 j = 1; j < (height-1); j++) + { + // We need to buffer 2 lines. We flip north and east-west (current) to avoid moving too much memory around + if (j % 2) + { + memcpy( &odd_buffer[0], dst_data, buffer_size ); /* Flawfinder: ignore */ + east_west_data = &odd_buffer[0]; + north_data = &even_buffer[0]; + } + else + { + memcpy( &even_buffer[0], dst_data, buffer_size ); /* Flawfinder: ignore */ + east_west_data = &even_buffer[0]; + north_data = &odd_buffer[0]; + } + // First pixel : set to 0 + blendStencil(getStencilAlpha(0,j), dst_data, 0, 0, 0); + dst_data += components; + // Set pointers to kernel + U8* NW = north_data; + U8* N = NW+components; + U8* NE = N+components; + U8* W = east_west_data; + U8* C = W+components; + U8* E = C+components; + U8* SW = south_data; + U8* S = SW+components; + U8* SE = S+components; + // All other pixels + for (S32 i = 1; i < (width-1); i++) + { + // Compute convolution + LLVector3 dst; + dst.mV[VRED] = (kernel.mMatrix[0][0]*NW[VRED] + kernel.mMatrix[0][1]*N[VRED] + kernel.mMatrix[0][2]*NE[VRED] + + kernel.mMatrix[1][0]*W[VRED] + kernel.mMatrix[1][1]*C[VRED] + kernel.mMatrix[1][2]*E[VRED] + + kernel.mMatrix[2][0]*SW[VRED] + kernel.mMatrix[2][1]*S[VRED] + kernel.mMatrix[2][2]*SE[VRED]); + dst.mV[VGREEN] = (kernel.mMatrix[0][0]*NW[VGREEN] + kernel.mMatrix[0][1]*N[VGREEN] + kernel.mMatrix[0][2]*NE[VGREEN] + + kernel.mMatrix[1][0]*W[VGREEN] + kernel.mMatrix[1][1]*C[VGREEN] + kernel.mMatrix[1][2]*E[VGREEN] + + kernel.mMatrix[2][0]*SW[VGREEN] + kernel.mMatrix[2][1]*S[VGREEN] + kernel.mMatrix[2][2]*SE[VGREEN]); + dst.mV[VBLUE] = (kernel.mMatrix[0][0]*NW[VBLUE] + kernel.mMatrix[0][1]*N[VBLUE] + kernel.mMatrix[0][2]*NE[VBLUE] + + kernel.mMatrix[1][0]*W[VBLUE] + kernel.mMatrix[1][1]*C[VBLUE] + kernel.mMatrix[1][2]*E[VBLUE] + + kernel.mMatrix[2][0]*SW[VBLUE] + kernel.mMatrix[2][1]*S[VBLUE] + kernel.mMatrix[2][2]*SE[VBLUE]); + if (abs_value) + { + dst.mV[VRED] = llabs(dst.mV[VRED]); + dst.mV[VGREEN] = llabs(dst.mV[VGREEN]); + dst.mV[VBLUE] = llabs(dst.mV[VBLUE]); + } + if (normalize) + { + dst.mV[VRED] = (dst.mV[VRED] - kernel_min)/kernel_range; + dst.mV[VGREEN] = (dst.mV[VGREEN] - kernel_min)/kernel_range; + dst.mV[VBLUE] = (dst.mV[VBLUE] - kernel_min)/kernel_range; + } + dst.clamp(0.0f,255.0f); + + // Blend result + blendStencil(getStencilAlpha(i,j), dst_data, dst.mV[VRED], dst.mV[VGREEN], dst.mV[VBLUE]); + + // Next pixel + dst_data += components; + NW += components; + N += components; + NE += components; + W += components; + C += components; + E += components; + SW += components; + S += components; + SE += components; + } + // Last pixel : set to 0 + blendStencil(getStencilAlpha(width-1,j), dst_data, 0, 0, 0); + dst_data += components; + south_data += buffer_size; + } + + // Last line + for (S32 i = 0; i < width; i++) + { + blendStencil(getStencilAlpha(i,0), dst_data, 0, 0, 0); + dst_data += components; + } +} + +void LLImageFilter::filterScreen(EScreenMode mode, const F32 wave_length, const F32 angle) +{ + const S32 components = mImage->getComponents(); + llassert( components >= 1 && components <= 4 ); + + S32 width = mImage->getWidth(); + S32 height = mImage->getHeight(); + + F32 wave_length_pixels = wave_length * (F32)(height) / 2.0; + F32 sin = sinf(angle*DEG_TO_RAD); + F32 cos = cosf(angle*DEG_TO_RAD); + + // Precompute the gamma table : gives us the gray level to use when cutting outside the screen (prevents strong aliasing on the screen) + U8 gamma[256]; + for (S32 i = 0; i < 256; i++) + { + F32 gamma_i = llclampf((float)(powf((float)(i)/255.0,1.0/4.0))); + gamma[i] = (U8)(255.0 * gamma_i); + } + + U8* dst_data = mImage->getData(); + for (S32 j = 0; j < height; j++) + { + for (S32 i = 0; i < width; i++) + { + // Compute screen value + F32 value = 0.0; + F32 di = 0.0; + F32 dj = 0.0; + switch (mode) + { + case SCREEN_MODE_2DSINE: + di = cos*i + sin*j; + dj = -sin*i + cos*j; + value = (sinf(2*F_PI*di/wave_length_pixels)*sinf(2*F_PI*dj/wave_length_pixels)+1.0)*255.0/2.0; + break; + case SCREEN_MODE_LINE: + dj = sin*i - cos*j; + value = (sinf(2*F_PI*dj/wave_length_pixels)+1.0)*255.0/2.0; + break; + } + U8 dst_value = (dst_data[VRED] >= (U8)(value) ? gamma[dst_data[VRED] - (U8)(value)] : 0); + + // Blend result + blendStencil(getStencilAlpha(i,j), dst_data, dst_value, dst_value, dst_value); + dst_data += components; + } + } +} + +//============================================================================ +// Procedural Stencils +//============================================================================ +void LLImageFilter::setStencil(EStencilShape shape, EStencilBlendMode mode, F32 min, F32 max, F32* params) +{ + mStencilShape = shape; + mStencilBlendMode = mode; + mStencilMin = llmin(llmax(min, -1.0f), 1.0f); + mStencilMax = llmin(llmax(max, -1.0f), 1.0f); + + // Each shape will interpret the 4 params differenly. + // We compute each systematically, though, clearly, values are meaningless when the shape doesn't correspond to the parameters + mStencilCenterX = (S32)(mImage->getWidth() + params[0] * (F32)(mImage->getHeight()))/2; + mStencilCenterY = (S32)(mImage->getHeight() + params[1] * (F32)(mImage->getHeight()))/2; + mStencilWidth = (S32)(params[2] * (F32)(mImage->getHeight()))/2; + mStencilGamma = (params[3] <= 0.0 ? 1.0 : params[3]); + + mStencilWavelength = (params[0] <= 0.0 ? 10.0 : params[0] * (F32)(mImage->getHeight()) / 2.0); + mStencilSine = sinf(params[1]*DEG_TO_RAD); + mStencilCosine = cosf(params[1]*DEG_TO_RAD); + + mStencilStartX = ((F32)(mImage->getWidth()) + params[0] * (F32)(mImage->getHeight()))/2.0; + mStencilStartY = ((F32)(mImage->getHeight()) + params[1] * (F32)(mImage->getHeight()))/2.0; + F32 end_x = ((F32)(mImage->getWidth()) + params[2] * (F32)(mImage->getHeight()))/2.0; + F32 end_y = ((F32)(mImage->getHeight()) + params[3] * (F32)(mImage->getHeight()))/2.0; + mStencilGradX = end_x - mStencilStartX; + mStencilGradY = end_y - mStencilStartY; + mStencilGradN = mStencilGradX*mStencilGradX + mStencilGradY*mStencilGradY; +} + +F32 LLImageFilter::getStencilAlpha(S32 i, S32 j) +{ + F32 alpha = 1.0; // That init actually takes care of the STENCIL_SHAPE_UNIFORM case... + if (mStencilShape == STENCIL_SHAPE_VIGNETTE) + { + // alpha is a modified gaussian value, with a center and fading in a circular pattern toward the edges + // The gamma parameter controls the intensity of the drop down from alpha 1.0 (center) to 0.0 + F32 d_center_square = (i - mStencilCenterX)*(i - mStencilCenterX) + (j - mStencilCenterY)*(j - mStencilCenterY); + alpha = powf(F_E, -(powf((d_center_square/(mStencilWidth*mStencilWidth)),mStencilGamma)/2.0f)); + } + else if (mStencilShape == STENCIL_SHAPE_SCAN_LINES) + { + // alpha varies according to a squared sine function. + F32 d = mStencilSine*i - mStencilCosine*j; + alpha = (sinf(2*F_PI*d/mStencilWavelength) > 0.0 ? 1.0 : 0.0); + } + else if (mStencilShape == STENCIL_SHAPE_GRADIENT) + { + alpha = (((F32)(i) - mStencilStartX)*mStencilGradX + ((F32)(j) - mStencilStartY)*mStencilGradY) / mStencilGradN; + alpha = llclampf(alpha); + } + + // We rescale alpha between min and max + return (mStencilMin + alpha * (mStencilMax - mStencilMin)); +} + +//============================================================================ +// Histograms +//============================================================================ + +U32* LLImageFilter::getBrightnessHistogram() +{ + if (!mHistoBrightness) + { + computeHistograms(); + } + return mHistoBrightness; +} + +void LLImageFilter::computeHistograms() +{ + const S32 components = mImage->getComponents(); + llassert( components >= 1 && components <= 4 ); + + // Allocate memory for the histograms + if (!mHistoRed) + { + mHistoRed = (U32*) ll_aligned_malloc_16(256*sizeof(U32)); + } + if (!mHistoGreen) + { + mHistoGreen = (U32*) ll_aligned_malloc_16(256*sizeof(U32)); + } + if (!mHistoBlue) + { + mHistoBlue = (U32*) ll_aligned_malloc_16(256*sizeof(U32)); + } + if (!mHistoBrightness) + { + mHistoBrightness = (U32*) ll_aligned_malloc_16(256*sizeof(U32)); + } + + // Initialize them + for (S32 i = 0; i < 256; i++) + { + mHistoRed[i] = 0; + mHistoGreen[i] = 0; + mHistoBlue[i] = 0; + mHistoBrightness[i] = 0; + } + + // Compute them + S32 pixels = mImage->getWidth() * mImage->getHeight(); + U8* dst_data = mImage->getData(); + for (S32 i = 0; i < pixels; i++) + { + mHistoRed[dst_data[VRED]]++; + mHistoGreen[dst_data[VGREEN]]++; + mHistoBlue[dst_data[VBLUE]]++; + // Note: this is a very simple shorthand for brightness but it's OK for our use + S32 brightness = ((S32)(dst_data[VRED]) + (S32)(dst_data[VGREEN]) + (S32)(dst_data[VBLUE])) / 3; + mHistoBrightness[brightness]++; + // next pixel... + dst_data += components; + } +} + +//============================================================================ +// Secondary Filters +//============================================================================ + +void LLImageFilter::filterGrayScale() +{ + LLMatrix3 gray_scale; + LLVector3 luminosity(0.2125, 0.7154, 0.0721); + gray_scale.setRows(luminosity, luminosity, luminosity); + gray_scale.transpose(); + colorTransform(gray_scale); +} + +void LLImageFilter::filterSepia() +{ + LLMatrix3 sepia; + sepia.setRows(LLVector3(0.3588, 0.7044, 0.1368), + LLVector3(0.2990, 0.5870, 0.1140), + LLVector3(0.2392, 0.4696, 0.0912)); + sepia.transpose(); + colorTransform(sepia); +} + +void LLImageFilter::filterSaturate(F32 saturation) +{ + // Matrix to Lij + LLMatrix3 r_a; + LLMatrix3 r_b; + + // 45 degre rotation around z + r_a.setRows(LLVector3( OO_SQRT2, OO_SQRT2, 0.0), + LLVector3(-OO_SQRT2, OO_SQRT2, 0.0), + LLVector3( 0.0, 0.0, 1.0)); + // 54.73 degre rotation around y + float oo_sqrt3 = 1.0f / F_SQRT3; + float sin_54 = F_SQRT2 * oo_sqrt3; + r_b.setRows(LLVector3(oo_sqrt3, 0.0, -sin_54), + LLVector3(0.0, 1.0, 0.0), + LLVector3(sin_54, 0.0, oo_sqrt3)); + + // Coordinate conversion + LLMatrix3 Lij = r_b * r_a; + LLMatrix3 Lij_inv = Lij; + Lij_inv.transpose(); + + // Local saturation transform + LLMatrix3 s; + s.setRows(LLVector3(saturation, 0.0, 0.0), + LLVector3(0.0, saturation, 0.0), + LLVector3(0.0, 0.0, 1.0)); + + // Global saturation transform + LLMatrix3 transfo = Lij_inv * s * Lij; + colorTransform(transfo); +} + +void LLImageFilter::filterRotate(F32 angle) +{ + // Matrix to Lij + LLMatrix3 r_a; + LLMatrix3 r_b; + + // 45 degre rotation around z + r_a.setRows(LLVector3( OO_SQRT2, OO_SQRT2, 0.0), + LLVector3(-OO_SQRT2, OO_SQRT2, 0.0), + LLVector3( 0.0, 0.0, 1.0)); + // 54.73 degre rotation around y + float oo_sqrt3 = 1.0f / F_SQRT3; + float sin_54 = F_SQRT2 * oo_sqrt3; + r_b.setRows(LLVector3(oo_sqrt3, 0.0, -sin_54), + LLVector3(0.0, 1.0, 0.0), + LLVector3(sin_54, 0.0, oo_sqrt3)); + + // Coordinate conversion + LLMatrix3 Lij = r_b * r_a; + LLMatrix3 Lij_inv = Lij; + Lij_inv.transpose(); + + // Local color rotation transform + LLMatrix3 r; + angle *= DEG_TO_RAD; + r.setRows(LLVector3( cosf(angle), sinf(angle), 0.0), + LLVector3(-sinf(angle), cosf(angle), 0.0), + LLVector3( 0.0, 0.0, 1.0)); + + // Global color rotation transform + LLMatrix3 transfo = Lij_inv * r * Lij; + colorTransform(transfo); +} + +void LLImageFilter::filterGamma(F32 gamma, const LLColor3& alpha) +{ + U8 gamma_red_lut[256]; + U8 gamma_green_lut[256]; + U8 gamma_blue_lut[256]; + + for (S32 i = 0; i < 256; i++) + { + F32 gamma_i = llclampf((float)(powf((float)(i)/255.0,1.0/gamma))); + // Blend in with alpha values + gamma_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * 255.0 * gamma_i); + gamma_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * 255.0 * gamma_i); + gamma_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * 255.0 * gamma_i); + } + + colorCorrect(gamma_red_lut,gamma_green_lut,gamma_blue_lut); +} + +void LLImageFilter::filterLinearize(F32 tail, const LLColor3& alpha) +{ + // Get the histogram + U32* histo = getBrightnessHistogram(); + + // Compute cumulated histogram + U32 cumulated_histo[256]; + cumulated_histo[0] = histo[0]; + for (S32 i = 1; i < 256; i++) + { + cumulated_histo[i] = cumulated_histo[i-1] + histo[i]; + } + + // Compute min and max counts minus tail + tail = llclampf(tail); + S32 total = cumulated_histo[255]; + S32 min_c = (S32)((F32)(total) * tail); + S32 max_c = (S32)((F32)(total) * (1.0 - tail)); + + // Find min and max values + S32 min_v = 0; + while (cumulated_histo[min_v] < min_c) + { + min_v++; + } + S32 max_v = 255; + while (cumulated_histo[max_v] > max_c) + { + max_v--; + } + + // Compute linear lookup table + U8 linear_red_lut[256]; + U8 linear_green_lut[256]; + U8 linear_blue_lut[256]; + if (max_v == min_v) + { + // Degenerated binary split case + for (S32 i = 0; i < 256; i++) + { + U8 value_i = (i < min_v ? 0 : 255); + // Blend in with alpha values + linear_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i); + linear_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i); + linear_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i); + } + } + else + { + // Linearize between min and max + F32 slope = 255.0 / (F32)(max_v - min_v); + F32 translate = -min_v * slope; + for (S32 i = 0; i < 256; i++) + { + U8 value_i = (U8)(llclampb((S32)(slope*i + translate))); + // Blend in with alpha values + linear_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i); + linear_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i); + linear_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i); + } + } + + // Apply lookup table + colorCorrect(linear_red_lut,linear_green_lut,linear_blue_lut); +} + +void LLImageFilter::filterEqualize(S32 nb_classes, const LLColor3& alpha) +{ + // Regularize the parameter: must be between 2 and 255 + nb_classes = llmax(nb_classes,2); + nb_classes = llclampb(nb_classes); + + // Get the histogram + U32* histo = getBrightnessHistogram(); + + // Compute cumulated histogram + U32 cumulated_histo[256]; + cumulated_histo[0] = histo[0]; + for (S32 i = 1; i < 256; i++) + { + cumulated_histo[i] = cumulated_histo[i-1] + histo[i]; + } + + // Compute deltas + S32 total = cumulated_histo[255]; + S32 delta_count = total / nb_classes; + S32 current_count = delta_count; + S32 delta_value = 256 / (nb_classes - 1); + S32 current_value = 0; + + // Compute equalized lookup table + U8 equalize_red_lut[256]; + U8 equalize_green_lut[256]; + U8 equalize_blue_lut[256]; + for (S32 i = 0; i < 256; i++) + { + // Blend in current_value with alpha values + equalize_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * current_value); + equalize_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * current_value); + equalize_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * current_value); + if (cumulated_histo[i] >= current_count) + { + current_count += delta_count; + current_value += delta_value; + current_value = llclampb(current_value); + } + } + + // Apply lookup table + colorCorrect(equalize_red_lut,equalize_green_lut,equalize_blue_lut); +} + +void LLImageFilter::filterColorize(const LLColor3& color, const LLColor3& alpha) +{ + U8 red_lut[256]; + U8 green_lut[256]; + U8 blue_lut[256]; + + F32 red_composite = 255.0 * alpha.mV[0] * color.mV[0]; + F32 green_composite = 255.0 * alpha.mV[1] * color.mV[1]; + F32 blue_composite = 255.0 * alpha.mV[2] * color.mV[2]; + + for (S32 i = 0; i < 256; i++) + { + red_lut[i] = (U8)(llclampb((S32)((1.0 - alpha.mV[0]) * (F32)(i) + red_composite))); + green_lut[i] = (U8)(llclampb((S32)((1.0 - alpha.mV[1]) * (F32)(i) + green_composite))); + blue_lut[i] = (U8)(llclampb((S32)((1.0 - alpha.mV[2]) * (F32)(i) + blue_composite))); + } + + colorCorrect(red_lut,green_lut,blue_lut); +} + +void LLImageFilter::filterContrast(F32 slope, const LLColor3& alpha) +{ + U8 contrast_red_lut[256]; + U8 contrast_green_lut[256]; + U8 contrast_blue_lut[256]; + + F32 translate = 128.0 * (1.0 - slope); + + for (S32 i = 0; i < 256; i++) + { + U8 value_i = (U8)(llclampb((S32)(slope*i + translate))); + // Blend in with alpha values + contrast_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i); + contrast_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i); + contrast_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i); + } + + colorCorrect(contrast_red_lut,contrast_green_lut,contrast_blue_lut); +} + +void LLImageFilter::filterBrightness(F32 add, const LLColor3& alpha) +{ + U8 brightness_red_lut[256]; + U8 brightness_green_lut[256]; + U8 brightness_blue_lut[256]; + + S32 add_value = (S32)(add * 255.0); + + for (S32 i = 0; i < 256; i++) + { + U8 value_i = (U8)(llclampb(i + add_value)); + // Blend in with alpha values + brightness_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i); + brightness_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i); + brightness_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i); + } + + colorCorrect(brightness_red_lut,brightness_green_lut,brightness_blue_lut); +} + +//============================================================================ diff --git a/indra/llimage/llimagefilter.h b/indra/llimage/llimagefilter.h new file mode 100755 index 0000000000..0f1cbc3fb8 --- /dev/null +++ b/indra/llimage/llimagefilter.h @@ -0,0 +1,136 @@ +/** + * @file llimagefilter.h + * @brief Simple Image Filtering. See https://wiki.lindenlab.com/wiki/SL_Viewer_Image_Filters for complete documentation. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLIMAGEFILTER_H +#define LL_LLIMAGEFILTER_H + +#include "llimage.h" + +class LLImageRaw; +class LLColor4U; +class LLColor3; +class LLMatrix3; + +typedef enum e_stencil_blend_mode +{ + STENCIL_BLEND_MODE_BLEND = 0, + STENCIL_BLEND_MODE_ADD = 1, + STENCIL_BLEND_MODE_ABACK = 2, + STENCIL_BLEND_MODE_FADE = 3 +} EStencilBlendMode; + +typedef enum e_stencil_shape +{ + STENCIL_SHAPE_UNIFORM = 0, + STENCIL_SHAPE_GRADIENT = 1, + STENCIL_SHAPE_VIGNETTE = 2, + STENCIL_SHAPE_SCAN_LINES = 3 +} EStencilShape; + +typedef enum e_screen_mode +{ + SCREEN_MODE_2DSINE = 0, + SCREEN_MODE_LINE = 1 +} EScreenMode; + +//============================================================================ +// LLImageFilter +//============================================================================ + +class LLImageFilter +{ +public: + LLImageFilter(const std::string& file_path); + ~LLImageFilter(); + + void executeFilter(LLPointer<LLImageRaw> raw_image); + +private: + // Filter Operations : Transforms + void filterGrayScale(); // Convert to grayscale + void filterSepia(); // Convert to sepia + void filterSaturate(F32 saturation); // < 1.0 desaturates, > 1.0 saturates + void filterRotate(F32 angle); // Rotates hue according to angle, angle in degrees + + // Filter Operations : Color Corrections + // When specified, the LLColor3 alpha parameter indicates the intensity of the effect for each color channel + // acting in effect as an alpha blending factor different for each channel. For instance (1.0,0.0,0.0) will apply + // the effect only to the Red channel. Intermediate values blends the effect with the source color. + void filterGamma(F32 gamma, const LLColor3& alpha); // Apply gamma to each channel + void filterLinearize(F32 tail, const LLColor3& alpha); // Use histogram to linearize constrast between min and max values minus tail + void filterEqualize(S32 nb_classes, const LLColor3& alpha); // Use histogram to equalize constrast between nb_classes throughout the image + void filterColorize(const LLColor3& color, const LLColor3& alpha); // Colorize with color and alpha per channel + void filterContrast(F32 slope, const LLColor3& alpha); // Change contrast according to slope: > 1.0 more contrast, < 1.0 less contrast + void filterBrightness(F32 add, const LLColor3& alpha); // Change brightness according to add: > 0 brighter, < 0 darker + + // Filter Primitives + void colorTransform(const LLMatrix3 &transform); + void colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue); + void filterScreen(EScreenMode mode, const F32 wave_length, const F32 angle); + void blendStencil(F32 alpha, U8* pixel, U8 red, U8 green, U8 blue); + void convolve(const LLMatrix3 &kernel, bool normalize, bool abs_value); + + // Procedural Stencils + void setStencil(EStencilShape shape, EStencilBlendMode mode, F32 min, F32 max, F32* params); + F32 getStencilAlpha(S32 i, S32 j); + + // Histograms + U32* getBrightnessHistogram(); + void computeHistograms(); + + LLSD mFilterData; + LLPointer<LLImageRaw> mImage; + + // Histograms (if we ever happen to need them) + U32 *mHistoRed; + U32 *mHistoGreen; + U32 *mHistoBlue; + U32 *mHistoBrightness; + + // Current Stencil Settings + EStencilBlendMode mStencilBlendMode; + EStencilShape mStencilShape; + F32 mStencilMin; + F32 mStencilMax; + + S32 mStencilCenterX; + S32 mStencilCenterY; + S32 mStencilWidth; + F32 mStencilGamma; + + F32 mStencilWavelength; + F32 mStencilSine; + F32 mStencilCosine; + + F32 mStencilStartX; + F32 mStencilStartY; + F32 mStencilGradX; + F32 mStencilGradY; + F32 mStencilGradN; +}; + + +#endif diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 17e340d136..793d972e1c 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -199,6 +199,7 @@ set(viewer_SOURCE_FILES llfilteredwearablelist.cpp llfirstuse.cpp llflexibleobject.cpp + llflickrconnect.cpp llfloaterabout.cpp llfloaterbvhpreview.cpp llfloaterauction.cpp @@ -228,6 +229,8 @@ set(viewer_SOURCE_FILES llfloatereditwater.cpp llfloaterenvironmentsettings.cpp llfloaterevent.cpp + llfloaterfacebook.cpp + llfloaterflickr.cpp llfloaterfonttest.cpp llfloatergesture.cpp llfloatergodtools.cpp @@ -275,7 +278,6 @@ set(viewer_SOURCE_FILES llfloatersettingsdebug.cpp llfloatersidepanelcontainer.cpp llfloatersnapshot.cpp - llfloatersocial.cpp llfloatersounddevices.cpp llfloaterspellchecksettings.cpp llfloatertelehub.cpp @@ -287,6 +289,7 @@ set(viewer_SOURCE_FILES llfloatertos.cpp llfloatertoybox.cpp llfloatertranslationsettings.cpp + llfloatertwitter.cpp llfloateruipreview.cpp llfloaterurlentry.cpp llfloatervoiceeffect.cpp @@ -326,6 +329,7 @@ set(viewer_SOURCE_FILES llfloaterimsessiontab.cpp llfloaterimsession.cpp llfloaterimcontainer.cpp + llimagefiltersmanager.cpp llimhandler.cpp llimview.cpp llinspect.cpp @@ -568,6 +572,7 @@ set(viewer_SOURCE_FILES lltransientdockablefloater.cpp lltransientfloatermgr.cpp lltranslate.cpp + lltwitterconnect.cpp lluilistener.cpp lluploaddialog.cpp lluploadfloaterobservers.cpp @@ -788,6 +793,7 @@ set(viewer_HEADER_FILES llfilteredwearablelist.h llfirstuse.h llflexibleobject.h + llflickrconnect.h llfloaterabout.h llfloaterbvhpreview.h llfloaterauction.h @@ -817,6 +823,8 @@ set(viewer_HEADER_FILES llfloatereditwater.h llfloaterenvironmentsettings.h llfloaterevent.h + llfloaterfacebook.h + llfloaterflickr.h llfloaterfonttest.h llfloatergesture.h llfloatergodtools.h @@ -864,7 +872,6 @@ set(viewer_HEADER_FILES llfloatersettingsdebug.h llfloatersidepanelcontainer.h llfloatersnapshot.h - llfloatersocial.h llfloatersounddevices.h llfloaterspellchecksettings.h llfloatertelehub.h @@ -876,6 +883,7 @@ set(viewer_HEADER_FILES llfloatertos.h llfloatertoybox.h llfloatertranslationsettings.h + llfloatertwitter.h llfloateruipreview.h llfloaterurlentry.h llfloatervoiceeffect.h @@ -914,6 +922,7 @@ set(viewer_HEADER_FILES llfloaterimsessiontab.h llfloaterimsession.h llfloaterimcontainer.h + llimagefiltersmanager.h llimview.h llinspect.h llinspectavatar.h @@ -1147,6 +1156,7 @@ set(viewer_HEADER_FILES lltransientdockablefloater.h lltransientfloatermgr.h lltranslate.h + lltwitterconnect.h lluiconstants.h lluilistener.h lluploaddialog.h diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index 60c942094a..7b329e2092 100755 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -216,15 +216,35 @@ is_running_function="Floater.IsOpen" is_running_parameters="snapshot" /> - <command name="social" + <command name="facebook" available_in_toybox="true" - icon="Command_Social_Icon" - label_ref="Command_Social_Label" - tooltip_ref="Command_Social_Tooltip" + icon="Command_Facebook_Icon" + label_ref="Command_Facebook_Label" + tooltip_ref="Command_Facebook_Tooltip" execute_function="Floater.ToggleOrBringToFront" - execute_parameters="social" + execute_parameters="facebook" is_running_function="Floater.IsOpen" - is_running_parameters="social" + is_running_parameters="facebook" + /> + <command name="flickr" + available_in_toybox="true" + icon="Command_Flickr_Icon" + label_ref="Command_Flickr_Label" + tooltip_ref="Command_Flickr_Tooltip" + execute_function="Floater.ToggleOrBringToFront" + execute_parameters="flickr" + is_running_function="Floater.IsOpen" + is_running_parameters="flickr" + /> + <command name="twitter" + available_in_toybox="true" + icon="Command_Twitter_Icon" + label_ref="Command_Twitter_Label" + tooltip_ref="Command_Twitter_Tooltip" + execute_function="Floater.ToggleOrBringToFront" + execute_parameters="twitter" + is_running_function="Floater.IsOpen" + is_running_parameters="twitter" /> <command name="speak" available_in_toybox="true" diff --git a/indra/newview/app_settings/filters/BlackAndWhite.xml b/indra/newview/app_settings/filters/BlackAndWhite.xml new file mode 100644 index 0000000000..101ed8233a --- /dev/null +++ b/indra/newview/app_settings/filters/BlackAndWhite.xml @@ -0,0 +1,21 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.01</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>contrast</string> + <real>0.8</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>grayscale</string> + </array> + </array> +</llsd> diff --git a/indra/newview/app_settings/filters/Colors1970.xml b/indra/newview/app_settings/filters/Colors1970.xml new file mode 100644 index 0000000000..730d907fa7 --- /dev/null +++ b/indra/newview/app_settings/filters/Colors1970.xml @@ -0,0 +1,47 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>contrast</string> + <real>0.8</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.3</real> + <real>0.0</real> + <real>0.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>blend</string> + <real>0.0</real> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>10.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.1</real> + <real>0.1</real> + <real>0.0</real> + </array> + </array> +</llsd> diff --git a/indra/newview/app_settings/filters/Heatwave.xml b/indra/newview/app_settings/filters/Heatwave.xml new file mode 100644 index 0000000000..a99f41c833 --- /dev/null +++ b/indra/newview/app_settings/filters/Heatwave.xml @@ -0,0 +1,38 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>contrast</string> + <real>0.8</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>fade</string> + <real>0.5</real> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>4.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>0.0</real> + <real>1.0</real> + <real>0.4</real> + <real>0.0</real> + <real>0.2</real> + </array> + </array> +</llsd> diff --git a/indra/newview/app_settings/filters/Intense.xml b/indra/newview/app_settings/filters/Intense.xml new file mode 100644 index 0000000000..b77f07a037 --- /dev/null +++ b/indra/newview/app_settings/filters/Intense.xml @@ -0,0 +1,8 @@ +<llsd> + <array> + <array> + <string>saturate</string> + <real>3.0</real> + </array> + </array> +</llsd> diff --git a/indra/newview/app_settings/filters/JulesVerne.xml b/indra/newview/app_settings/filters/JulesVerne.xml new file mode 100644 index 0000000000..981e221da9 --- /dev/null +++ b/indra/newview/app_settings/filters/JulesVerne.xml @@ -0,0 +1,20 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>grayscale</string> + </array> + <array> + <string>screen</string> + <string>line</string> + <real>0.02</real> + <real>0.0</real> + </array> + </array> +</llsd> diff --git a/indra/newview/app_settings/filters/Newspaper.xml b/indra/newview/app_settings/filters/Newspaper.xml new file mode 100755 index 0000000000..6cfe319281 --- /dev/null +++ b/indra/newview/app_settings/filters/Newspaper.xml @@ -0,0 +1,20 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>grayscale</string> + </array> + <array> + <string>screen</string> + <string>2Dsine</string> + <real>0.02</real> + <real>0.0</real> + </array> + </array> +</llsd> diff --git a/indra/newview/app_settings/filters/Overcast.xml b/indra/newview/app_settings/filters/Overcast.xml new file mode 100644 index 0000000000..dce5ab3e9e --- /dev/null +++ b/indra/newview/app_settings/filters/Overcast.xml @@ -0,0 +1,24 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>colorize</string> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + <real>0.0</real> + <real>0.3</real> + <real>0.0</real> + </array> + <array> + <string>saturate</string> + <real>0.35</real> + </array> + </array> +</llsd> diff --git a/indra/newview/app_settings/filters/Sepia.xml b/indra/newview/app_settings/filters/Sepia.xml new file mode 100644 index 0000000000..3d577b2998 --- /dev/null +++ b/indra/newview/app_settings/filters/Sepia.xml @@ -0,0 +1,32 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.01</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>contrast</string> + <real>0.8</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>fade</string> + <real>0.5</real> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>4.0</real> + </array> + <array> + <string>sepia</string> + </array> + </array> +</llsd> diff --git a/indra/newview/app_settings/filters/Spotlight.xml b/indra/newview/app_settings/filters/Spotlight.xml new file mode 100644 index 0000000000..044b2c0b3a --- /dev/null +++ b/indra/newview/app_settings/filters/Spotlight.xml @@ -0,0 +1,51 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>contrast</string> + <real>0.8</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>saturate</string> + <real>1.5</real> + </array> + <array> + <string>stencil</string> + <string>vignette</string> + <string>fade</string> + <real>0.75</real> + <real>1.0</real> + <real>0.0</real> + <real>0.0</real> + <real>1.0</real> + <real>5.0</real> + </array> + <array> + <string>saturate</string> + <real>0.8</real> + </array> + <array> + <string>contrast</string> + <real>1.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>brighten</string> + <real>0.1</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/newview/app_settings/filters/Video.xml b/indra/newview/app_settings/filters/Video.xml new file mode 100755 index 0000000000..aa65536235 --- /dev/null +++ b/indra/newview/app_settings/filters/Video.xml @@ -0,0 +1,34 @@ +<llsd> + <array> + <array> + <string>linearize</string> + <real>0.0</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>darken</string> + <real>0.2</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + <array> + <string>stencil</string> + <string>scanlines</string> + <string>blend</string> + <real>0.0</real> + <real>1.0</real> + <real>0.02</real> + <real>0.0</real> + </array> + <array> + <string>brighten</string> + <real>0.2</real> + <real>1.0</real> + <real>1.0</real> + <real>1.0</real> + </array> + </array> +</llsd> diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index db151817e0..afc4ac617c 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -13130,7 +13130,7 @@ <key>SocialPhotoResolution</key> <map> <key>Comment</key> - <string>Default resolution when sharing photo using the social floater</string> + <string>Default resolution when sharing photo using the social floaters</string> <key>Persist</key> <integer>1</integer> <key>Type</key> diff --git a/indra/newview/app_settings/toolbars.xml b/indra/newview/app_settings/toolbars.xml index 86f9912815..d61aee9a14 100755 --- a/indra/newview/app_settings/toolbars.xml +++ b/indra/newview/app_settings/toolbars.xml @@ -6,7 +6,6 @@ <command name="speak"/> <command name="destinations"/> <command name="people"/> - <command name="social"/> <command name="profile"/> <command name="move"/> <command name="view"/> @@ -22,5 +21,6 @@ <command name="voice"/> <command name="minimap"/> <command name="snapshot"/> + <command name="facebook"/> </left_toolbar> </toolbars> diff --git a/indra/newview/llfacebookconnect.cpp b/indra/newview/llfacebookconnect.cpp index 9a20ce8f1b..e3ddb31935 100644 --- a/indra/newview/llfacebookconnect.cpp +++ b/indra/newview/llfacebookconnect.cpp @@ -28,6 +28,8 @@ #include "llviewerprecompiledheaders.h" #include "llfacebookconnect.h" +#include "llflickrconnect.h" +#include "lltwitterconnect.h" #include "llagent.h" #include "llcallingcard.h" // for LLAvatarTracker @@ -58,7 +60,7 @@ void log_facebook_connect_error(const std::string& request, U32 status, const st } } -void toast_user_for_success() +void toast_user_for_facebook_success() { LLSD args; args["MESSAGE"] = LLTrans::getString("facebook_post_success"); @@ -74,23 +76,46 @@ public: bool handle(const LLSD& tokens, const LLSD& query_map, LLMediaCtrl* web) { - if (tokens.size() > 0) + if (tokens.size() >= 1) { if (tokens[0].asString() == "connect") { - // this command probably came from the fbc_web browser, so close it - LLFloater* fbc_web = LLFloaterReg::getInstance("fbc_web"); - if (fbc_web) + if (tokens.size() >= 2 && tokens[1].asString() == "flickr") { - fbc_web->closeFloater(); + // this command probably came from the flickr_web browser, so close it + LLFloaterReg::hideInstance("flickr_web"); + + // connect to flickr + if (query_map.has("oauth_token")) + { + LLFlickrConnect::instance().connectToFlickr(query_map["oauth_token"], query_map.get("oauth_verifier")); + } + return true; } - - // connect to facebook - if (query_map.has("code")) + else if (tokens.size() >= 2 && tokens[1].asString() == "twitter") + { + // this command probably came from the twitter_web browser, so close it + LLFloaterReg::hideInstance("twitter_web"); + + // connect to twitter + if (query_map.has("oauth_token")) + { + LLTwitterConnect::instance().connectToTwitter(query_map["oauth_token"], query_map.get("oauth_verifier")); + } + return true; + } + else //if (tokens.size() >= 2 && tokens[1].asString() == "facebook") { - LLFacebookConnect::instance().connectToFacebook(query_map["code"], query_map.get("state")); + // this command probably came from the fbc_web browser, so close it + LLFloaterReg::hideInstance("fbc_web"); + + // connect to facebook + if (query_map.has("code")) + { + LLFacebookConnect::instance().connectToFacebook(query_map["code"], query_map.get("state")); + } + return true; } - return true; } } return false; @@ -150,7 +175,7 @@ public: { if (isGoodStatus(status)) { - toast_user_for_success(); + toast_user_for_facebook_success(); LL_DEBUGS("FacebookConnect") << "Post successful. content: " << content << LL_ENDL; LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_POSTED); @@ -340,10 +365,12 @@ void LLFacebookConnect::openFacebookWeb(std::string url) { // Open the URL in an internal browser window without navigation UI LLFloaterWebContent::Params p; - p.url(url).show_chrome(true); - p.url(url).allow_address_entry(false); - p.url(url).allow_back_forward_navigation(false); - p.url(url).trusted_content(true); + p.url(url); + p.show_chrome(true); + p.allow_address_entry(false); + p.allow_back_forward_navigation(false); + p.trusted_content(true); + p.clean_browser(true); LLFloater *floater = LLFloaterReg::showInstance("fbc_web", p); //the internal web browser has a bug that prevents it from gaining focus unless a mouse event occurs first (it seems). //So when showing the internal web browser, set focus to it's containing floater "fbc_web". When a mouse event @@ -360,6 +387,7 @@ std::string LLFacebookConnect::getFacebookConnectURL(const std::string& route, b LLViewerRegion *regionp = gAgent.getRegion(); if (regionp) { + //url = "http://pdp15.lindenlab.com/fbc/agent/" + gAgentID.asString(); // TEMPORARY FOR TESTING - CHO url = regionp->getCapability("FacebookConnect"); url += route; @@ -375,9 +403,13 @@ void LLFacebookConnect::connectToFacebook(const std::string& auth_code, const st { LLSD body; if (!auth_code.empty()) + { body["code"] = auth_code; + } if (!auth_state.empty()) + { body["state"] = auth_state; + } LLHTTPClient::put(getFacebookConnectURL("/connection"), body, new LLFacebookConnectResponder()); } @@ -421,15 +453,25 @@ void LLFacebookConnect::postCheckin(const std::string& location, const std::stri { LLSD body; if (!location.empty()) + { body["location"] = location; + } if (!name.empty()) + { body["name"] = name; + } if (!description.empty()) + { body["description"] = description; + } if (!image.empty()) + { body["image"] = image; + } if (!message.empty()) + { body["message"] = message; + } // Note: we can use that route for different publish action. We should be able to use the same responder. LLHTTPClient::post(getFacebookConnectURL("/share/checkin", true), body, new LLFacebookShareResponder()); @@ -568,12 +610,13 @@ void LLFacebookConnect::setConnectionState(LLFacebookConnect::EConnectionState c if (mConnectionState != connection_state) { + // set the connection state before notifying watchers + mConnectionState = connection_state; + LLSD state_info; state_info["enum"] = connection_state; sStateWatcher->post(state_info); } - - mConnectionState = connection_state; } void LLFacebookConnect::setConnected(bool connected) diff --git a/indra/newview/llfacebookconnect.h b/indra/newview/llfacebookconnect.h index a77ac24167..c157db2178 100644 --- a/indra/newview/llfacebookconnect.h +++ b/indra/newview/llfacebookconnect.h @@ -89,7 +89,7 @@ private: LLFacebookConnect(); ~LLFacebookConnect() {}; std::string getFacebookConnectURL(const std::string& route = "", bool include_read_from_master = false); - + EConnectionState mConnectionState; BOOL mConnected; LLSD mInfo; diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 16eacc9392..f921dace84 100755 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -167,7 +167,8 @@ BOOL LLFilePicker::setupFilter(ELoadFilter filter) BOOL res = TRUE; switch (filter) { - case FFLOAD_ALL: + case FFLOAD_ALL: + case FFLOAD_EXE: mOFN.lpstrFilter = L"All Files (*.*)\0*.*\0" \ SOUND_FILTER \ IMAGE_FILTER \ @@ -579,6 +580,10 @@ std::vector<std::string>* LLFilePicker::navOpenFilterProc(ELoadFilter filter) // allowedv->push_back("tpic"); allowedv->push_back("png"); break; + case FFLOAD_EXE: + allowedv->push_back("app"); + allowedv->push_back("exe"); + break; case FFLOAD_WAV: allowedv->push_back("wav"); break; @@ -777,9 +782,9 @@ BOOL LLFilePicker::getOpenFile(ELoadFilter filter, bool blocking) mPickOptions &= ~F_FILE; } - if(filter == FFLOAD_ALL) // allow application bundles etc. to be traversed; important for DEV-16869, but generally useful + if (filter == FFLOAD_ALL) // allow application bundles etc. to be traversed; important for DEV-16869, but generally useful { - mPickOptions &= F_NAV_SUPPORT; + mPickOptions |= F_NAV_SUPPORT; } if (blocking) diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h index f0f82c51db..0b89e2716c 100755 --- a/indra/newview/llfilepicker.h +++ b/indra/newview/llfilepicker.h @@ -86,7 +86,8 @@ public: FFLOAD_COLLADA = 10, FFLOAD_SCRIPT = 11, FFLOAD_DICTIONARY = 12, - FFLOAD_DIRECTORY = 13 //To call from lldirpicker. + FFLOAD_DIRECTORY = 13, // To call from lldirpicker. + FFLOAD_EXE = 14 // Note: EXE will be treated as ALL on Windows and Linux but not on Darwin }; enum ESaveFilter diff --git a/indra/newview/llflickrconnect.cpp b/indra/newview/llflickrconnect.cpp new file mode 100644 index 0000000000..c09e738a26 --- /dev/null +++ b/indra/newview/llflickrconnect.cpp @@ -0,0 +1,480 @@ +/** + * @file llflickrconnect.h + * @author Merov, Cho + * @brief Connection to Flickr Service + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llflickrconnect.h" + +#include "llagent.h" +#include "llcallingcard.h" // for LLAvatarTracker +#include "llcommandhandler.h" +#include "llhttpclient.h" +#include "llnotificationsutil.h" +#include "llurlaction.h" +#include "llimagepng.h" +#include "llimagejpeg.h" +#include "lltrans.h" +#include "llevents.h" +#include "llviewerregion.h" + +#include "llfloaterwebcontent.h" +#include "llfloaterreg.h" + +boost::scoped_ptr<LLEventPump> LLFlickrConnect::sStateWatcher(new LLEventStream("FlickrConnectState")); +boost::scoped_ptr<LLEventPump> LLFlickrConnect::sInfoWatcher(new LLEventStream("FlickrConnectInfo")); +boost::scoped_ptr<LLEventPump> LLFlickrConnect::sContentWatcher(new LLEventStream("FlickrConnectContent")); + +// Local functions +void log_flickr_connect_error(const std::string& request, U32 status, const std::string& reason, const std::string& code, const std::string& description) +{ + // Note: 302 (redirect) is *not* an error that warrants logging + if (status != 302) + { + LL_WARNS("FlickrConnect") << request << " request failed with a " << status << " " << reason << ". Reason: " << code << " (" << description << ")" << LL_ENDL; + } +} + +void toast_user_for_flickr_success() +{ + LLSD args; + args["MESSAGE"] = LLTrans::getString("flickr_post_success"); + LLNotificationsUtil::add("FlickrConnect", args); +} + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFlickrConnectResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFlickrConnectResponder); +public: + + LLFlickrConnectResponder() + { + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_CONNECTION_IN_PROGRESS); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("FlickrConnect") << "Connect successful. content: " << content << LL_ENDL; + + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_CONNECTED); + } + else if (status != 302) + { + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_CONNECTION_FAILED); + log_flickr_connect_error("Connect", status, reason, content.get("error_code"), content.get("error_description")); + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLFlickrConnect::instance().openFlickrWeb(content["location"]); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFlickrShareResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFlickrShareResponder); +public: + + LLFlickrShareResponder() + { + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_POSTING); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + toast_user_for_flickr_success(); + LL_DEBUGS("FlickrConnect") << "Post successful. content: " << content << LL_ENDL; + + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_POSTED); + } + else if (status == 404) + { + LLFlickrConnect::instance().connectToFlickr(); + } + else + { + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_POST_FAILED); + log_flickr_connect_error("Share", status, reason, content.get("error_code"), content.get("error_description")); + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLFlickrConnect::instance().openFlickrWeb(content["location"]); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFlickrDisconnectResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFlickrDisconnectResponder); +public: + + LLFlickrDisconnectResponder() + { + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_DISCONNECTING); + } + + void setUserDisconnected() + { + // Clear data + LLFlickrConnect::instance().clearInfo(); + + //Notify state change + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_NOT_CONNECTED); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("FlickrConnect") << "Disconnect successful. content: " << content << LL_ENDL; + setUserDisconnected(); + + } + //User not found so already disconnected + else if(status == 404) + { + LL_DEBUGS("FlickrConnect") << "Already disconnected. content: " << content << LL_ENDL; + setUserDisconnected(); + } + else + { + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_DISCONNECT_FAILED); + log_flickr_connect_error("Disconnect", status, reason, content.get("error_code"), content.get("error_description")); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFlickrConnectedResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFlickrConnectedResponder); +public: + + LLFlickrConnectedResponder(bool auto_connect) : mAutoConnect(auto_connect) + { + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_CONNECTION_IN_PROGRESS); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("FlickrConnect") << "Connect successful. content: " << content << LL_ENDL; + + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_CONNECTED); + } + else + { + // show the flickr login page if not connected yet + if (status == 404) + { + if (mAutoConnect) + { + LLFlickrConnect::instance().connectToFlickr(); + } + else + { + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_NOT_CONNECTED); + } + } + else + { + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_CONNECTION_FAILED); + log_flickr_connect_error("Connected", status, reason, content.get("error_code"), content.get("error_description")); + } + } + } + +private: + bool mAutoConnect; +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFlickrInfoResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFlickrInfoResponder); +public: + + virtual void completed(U32 status, const std::string& reason, const LLSD& info) + { + if (isGoodStatus(status)) + { + llinfos << "Flickr: Info received" << llendl; + LL_DEBUGS("FlickrConnect") << "Getting Flickr info successful. info: " << info << LL_ENDL; + LLFlickrConnect::instance().storeInfo(info); + } + else + { + log_flickr_connect_error("Info", status, reason, info.get("error_code"), info.get("error_description")); + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLFlickrConnect::instance().openFlickrWeb(content["location"]); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +LLFlickrConnect::LLFlickrConnect() +: mConnectionState(FLICKR_NOT_CONNECTED), + mConnected(false), + mInfo(), + mRefreshInfo(false), + mReadFromMaster(false) +{ +} + +void LLFlickrConnect::openFlickrWeb(std::string url) +{ + // Open the URL in an internal browser window without navigation UI + LLFloaterWebContent::Params p; + p.url(url); + p.show_chrome(true); + p.allow_address_entry(false); + p.allow_back_forward_navigation(false); + p.trusted_content(true); + p.clean_browser(true); + LLFloater *floater = LLFloaterReg::showInstance("flickr_web", p); + //the internal web browser has a bug that prevents it from gaining focus unless a mouse event occurs first (it seems). + //So when showing the internal web browser, set focus to it's containing floater "flickr_web". When a mouse event + //occurs on the "webbrowser" panel part of the floater, a mouse cursor will properly show and the "webbrowser" will gain focus. + //flickr_web floater contains the "webbrowser" panel. JIRA: ACME-744 + gFocusMgr.setKeyboardFocus( floater ); + + //LLUrlAction::openURLExternal(url); +} + +std::string LLFlickrConnect::getFlickrConnectURL(const std::string& route, bool include_read_from_master) +{ + std::string url(""); + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + //url = "http://pdp15.lindenlab.com/flickr/agent/" + gAgentID.asString(); // TEMPORARY FOR TESTING - CHO + url = regionp->getCapability("FlickrConnect"); + url += route; + + if (include_read_from_master && mReadFromMaster) + { + url += "?read_from_master=true"; + } + } + return url; +} + +void LLFlickrConnect::connectToFlickr(const std::string& request_token, const std::string& oauth_verifier) +{ + LLSD body; + if (!request_token.empty()) + body["request_token"] = request_token; + if (!oauth_verifier.empty()) + body["oauth_verifier"] = oauth_verifier; + + LLHTTPClient::put(getFlickrConnectURL("/connection"), body, new LLFlickrConnectResponder()); +} + +void LLFlickrConnect::disconnectFromFlickr() +{ + LLHTTPClient::del(getFlickrConnectURL("/connection"), new LLFlickrDisconnectResponder()); +} + +void LLFlickrConnect::checkConnectionToFlickr(bool auto_connect) +{ + const bool follow_redirects = false; + const F32 timeout = HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getFlickrConnectURL("/connection", true), new LLFlickrConnectedResponder(auto_connect), + LLSD(), timeout, follow_redirects); +} + +void LLFlickrConnect::loadFlickrInfo() +{ + if(mRefreshInfo) + { + const bool follow_redirects = false; + const F32 timeout = HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getFlickrConnectURL("/info", true), new LLFlickrInfoResponder(), + LLSD(), timeout, follow_redirects); + } +} + +void LLFlickrConnect::uploadPhoto(const std::string& image_url, const std::string& title, const std::string& description, const std::string& tags, int safety_level) +{ + LLSD body; + body["image"] = image_url; + body["title"] = title; + body["description"] = description; + body["tags"] = tags; + body["safety_level"] = safety_level; + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::post(getFlickrConnectURL("/share/photo", true), body, new LLFlickrShareResponder()); +} + +void LLFlickrConnect::uploadPhoto(LLPointer<LLImageFormatted> image, const std::string& title, const std::string& description, const std::string& tags, int safety_level) +{ + std::string imageFormat; + if (dynamic_cast<LLImagePNG*>(image.get())) + { + imageFormat = "png"; + } + else if (dynamic_cast<LLImageJPEG*>(image.get())) + { + imageFormat = "jpg"; + } + else + { + llwarns << "Image to upload is not a PNG or JPEG" << llendl; + return; + } + + // All this code is mostly copied from LLWebProfile::post() + const std::string boundary = "----------------------------0123abcdefab"; + + LLSD headers; + headers["Content-Type"] = "multipart/form-data; boundary=" + boundary; + + std::ostringstream body; + + // *NOTE: The order seems to matter. + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"title\"\r\n\r\n" + << title << "\r\n"; + + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"description\"\r\n\r\n" + << description << "\r\n"; + + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"tags\"\r\n\r\n" + << tags << "\r\n"; + + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"safety_level\"\r\n\r\n" + << safety_level << "\r\n"; + + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"image\"; filename=\"snapshot." << imageFormat << "\"\r\n" + << "Content-Type: image/" << imageFormat << "\r\n\r\n"; + + // Insert the image data. + // *FIX: Treating this as a string will probably screw it up ... + U8* image_data = image->getData(); + for (S32 i = 0; i < image->getDataSize(); ++i) + { + body << image_data[i]; + } + + body << "\r\n--" << boundary << "--\r\n"; + + // postRaw() takes ownership of the buffer and releases it later. + size_t size = body.str().size(); + U8 *data = new U8[size]; + memcpy(data, body.str().data(), size); + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::postRaw(getFlickrConnectURL("/share/photo", true), data, size, new LLFlickrShareResponder(), headers); +} + +void LLFlickrConnect::storeInfo(const LLSD& info) +{ + mInfo = info; + mRefreshInfo = false; + + sInfoWatcher->post(info); +} + +const LLSD& LLFlickrConnect::getInfo() const +{ + return mInfo; +} + +void LLFlickrConnect::clearInfo() +{ + mInfo = LLSD(); +} + +void LLFlickrConnect::setDataDirty() +{ + mRefreshInfo = true; +} + +void LLFlickrConnect::setConnectionState(LLFlickrConnect::EConnectionState connection_state) +{ + if(connection_state == FLICKR_CONNECTED) + { + mReadFromMaster = true; + setConnected(true); + setDataDirty(); + } + else if(connection_state == FLICKR_NOT_CONNECTED) + { + setConnected(false); + } + else if(connection_state == FLICKR_POSTED) + { + mReadFromMaster = false; + } + + if (mConnectionState != connection_state) + { + // set the connection state before notifying watchers + mConnectionState = connection_state; + + LLSD state_info; + state_info["enum"] = connection_state; + sStateWatcher->post(state_info); + } +} + +void LLFlickrConnect::setConnected(bool connected) +{ + mConnected = connected; +} diff --git a/indra/newview/llflickrconnect.h b/indra/newview/llflickrconnect.h new file mode 100644 index 0000000000..b127e6e104 --- /dev/null +++ b/indra/newview/llflickrconnect.h @@ -0,0 +1,98 @@ +/** + * @file llflickrconnect.h + * @author Merov, Cho + * @brief Connection to Flickr Service + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLICKRCONNECT_H +#define LL_LLFLICKRCONNECT_H + +#include "llsingleton.h" +#include "llimage.h" + +class LLEventPump; + +/** + * @class LLFlickrConnect + * + * Manages authentication to, and interaction with, a web service allowing the + * the viewer to upload photos to Flickr. + */ +class LLFlickrConnect : public LLSingleton<LLFlickrConnect> +{ + LOG_CLASS(LLFlickrConnect); +public: + enum EConnectionState + { + FLICKR_NOT_CONNECTED = 0, + FLICKR_CONNECTION_IN_PROGRESS = 1, + FLICKR_CONNECTED = 2, + FLICKR_CONNECTION_FAILED = 3, + FLICKR_POSTING = 4, + FLICKR_POSTED = 5, + FLICKR_POST_FAILED = 6, + FLICKR_DISCONNECTING = 7, + FLICKR_DISCONNECT_FAILED = 8 + }; + + void connectToFlickr(const std::string& request_token = "", const std::string& oauth_verifier = ""); // Initiate the complete Flickr connection. Please use checkConnectionToFlickr() in normal use. + void disconnectFromFlickr(); // Disconnect from the Flickr service. + void checkConnectionToFlickr(bool auto_connect = false); // Check if an access token is available on the Flickr service. If not, call connectToFlickr(). + + void loadFlickrInfo(); + void uploadPhoto(const std::string& image_url, const std::string& title, const std::string& description, const std::string& tags, int safety_level); + void uploadPhoto(LLPointer<LLImageFormatted> image, const std::string& title, const std::string& description, const std::string& tags, int safety_level); + + void storeInfo(const LLSD& info); + const LLSD& getInfo() const; + void clearInfo(); + void setDataDirty(); + + void setConnectionState(EConnectionState connection_state); + void setConnected(bool connected); + bool isConnected() { return mConnected; } + bool isTransactionOngoing() { return ((mConnectionState == FLICKR_CONNECTION_IN_PROGRESS) || (mConnectionState == FLICKR_POSTING) || (mConnectionState == FLICKR_DISCONNECTING)); } + EConnectionState getConnectionState() { return mConnectionState; } + + void openFlickrWeb(std::string url); + +private: + friend class LLSingleton<LLFlickrConnect>; + + LLFlickrConnect(); + ~LLFlickrConnect() {}; + std::string getFlickrConnectURL(const std::string& route = "", bool include_read_from_master = false); + + EConnectionState mConnectionState; + BOOL mConnected; + LLSD mInfo; + bool mRefreshInfo; + bool mReadFromMaster; + + static boost::scoped_ptr<LLEventPump> sStateWatcher; + static boost::scoped_ptr<LLEventPump> sInfoWatcher; + static boost::scoped_ptr<LLEventPump> sContentWatcher; +}; + +#endif // LL_LLFLICKRCONNECT_H diff --git a/indra/newview/llfloatersocial.cpp b/indra/newview/llfloaterfacebook.cpp index 2a74c8e3ea..3e58b40c75 100644 --- a/indra/newview/llfloatersocial.cpp +++ b/indra/newview/llfloaterfacebook.cpp @@ -1,6 +1,6 @@ /** -* @file llfloatersocial.cpp -* @brief Implementation of llfloatersocial +* @file llfloaterfacebook.cpp +* @brief Implementation of llfloaterfacebook * @author Gilbert@lindenlab.com * * $LicenseInfo:firstyear=2013&license=viewerlgpl$ @@ -27,7 +27,7 @@ #include "llviewerprecompiledheaders.h" -#include "llfloatersocial.h" +#include "llfloaterfacebook.h" #include "llagent.h" #include "llagentui.h" @@ -36,6 +36,7 @@ #include "llfacebookconnect.h" #include "llfloaterreg.h" #include "lliconctrl.h" +#include "llimagefiltersmanager.h" #include "llresmgr.h" // LLLocale #include "llsdserialize.h" #include "llloadingindicator.h" @@ -46,11 +47,12 @@ #include "llviewerregion.h" #include "llviewercontrol.h" #include "llviewermedia.h" +#include "lltabcontainer.h" -static LLRegisterPanelClassWrapper<LLSocialStatusPanel> t_panel_status("llsocialstatuspanel"); -static LLRegisterPanelClassWrapper<LLSocialPhotoPanel> t_panel_photo("llsocialphotopanel"); -static LLRegisterPanelClassWrapper<LLSocialCheckinPanel> t_panel_checkin("llsocialcheckinpanel"); -static LLRegisterPanelClassWrapper<LLSocialAccountPanel> t_panel_account("llsocialaccountpanel"); +static LLRegisterPanelClassWrapper<LLFacebookStatusPanel> t_panel_status("llfacebookstatuspanel"); +static LLRegisterPanelClassWrapper<LLFacebookPhotoPanel> t_panel_photo("llfacebookphotopanel"); +static LLRegisterPanelClassWrapper<LLFacebookCheckinPanel> t_panel_checkin("llfacebookcheckinpanel"); +static LLRegisterPanelClassWrapper<LLFacebookAccountPanel> t_panel_account("llfacebookaccountpanel"); const S32 MAX_POSTCARD_DATASIZE = 1024 * 1024; // one megabyte const std::string DEFAULT_CHECKIN_LOCATION_URL = "http://maps.secondlife.com/"; @@ -58,12 +60,17 @@ const std::string DEFAULT_CHECKIN_ICON_URL = "http://map.secondlife.com.s3.amazo const std::string DEFAULT_CHECKIN_QUERY_PARAMETERS = "?sourceid=slshare_checkin&utm_source=facebook&utm_medium=checkin&utm_campaign=slshare"; const std::string DEFAULT_PHOTO_QUERY_PARAMETERS = "?sourceid=slshare_photo&utm_source=facebook&utm_medium=photo&utm_campaign=slshare"; +const S32 MAX_QUALITY = 100; // Max quality value for jpeg images +const S32 MIN_QUALITY = 0; // Min quality value for jpeg images +const S32 TARGET_DATA_SIZE = 95000; // Size of the image (compressed) we're trying to send to Facebook + std::string get_map_url() { LLVector3d center_agent; - if (gAgent.getRegion()) + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) { - center_agent = gAgent.getRegion()->getCenterGlobal(); + center_agent = regionp->getCenterGlobal(); } int x_pos = center_agent[0] / 256.0; int y_pos = center_agent[1] / 256.0; @@ -71,19 +78,27 @@ std::string get_map_url() return map_url; } +// Compute target jpeg quality : see https://wiki.lindenlab.com/wiki/Facebook_Image_Quality for details +S32 compute_jpeg_quality(S32 width, S32 height) +{ + F32 target_compression_ratio = (F32)(width * height * 3) / (F32)(TARGET_DATA_SIZE); + S32 quality = (S32)(110.0f - (2.0f * target_compression_ratio)); + return llclamp(quality,MIN_QUALITY,MAX_QUALITY); +} + /////////////////////////// -//LLSocialStatusPanel////// +//LLFacebookStatusPanel////// /////////////////////////// -LLSocialStatusPanel::LLSocialStatusPanel() : +LLFacebookStatusPanel::LLFacebookStatusPanel() : mMessageTextEditor(NULL), mPostButton(NULL), mCancelButton(NULL) { - mCommitCallbackRegistrar.add("SocialSharing.SendStatus", boost::bind(&LLSocialStatusPanel::onSend, this)); + mCommitCallbackRegistrar.add("SocialSharing.SendStatus", boost::bind(&LLFacebookStatusPanel::onSend, this)); } -BOOL LLSocialStatusPanel::postBuild() +BOOL LLFacebookStatusPanel::postBuild() { mMessageTextEditor = getChild<LLUICtrl>("status_message"); mPostButton = getChild<LLUICtrl>("post_status_btn"); @@ -92,7 +107,7 @@ BOOL LLSocialStatusPanel::postBuild() return LLPanel::postBuild(); } -void LLSocialStatusPanel::draw() +void LLFacebookStatusPanel::draw() { if (mMessageTextEditor && mPostButton && mCancelButton) { @@ -106,10 +121,10 @@ void LLSocialStatusPanel::draw() LLPanel::draw(); } -void LLSocialStatusPanel::onSend() +void LLFacebookStatusPanel::onSend() { - LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialStatusPanel"); // just in case it is already listening - LLEventPumps::instance().obtain("FacebookConnectState").listen("LLSocialStatusPanel", boost::bind(&LLSocialStatusPanel::onFacebookConnectStateChange, this, _1)); + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLFacebookStatusPanel"); // just in case it is already listening + LLEventPumps::instance().obtain("FacebookConnectState").listen("LLFacebookStatusPanel", boost::bind(&LLFacebookStatusPanel::onFacebookConnectStateChange, this, _1)); // Connect to Facebook if necessary and then post if (LLFacebookConnect::instance().isConnected()) @@ -122,7 +137,7 @@ void LLSocialStatusPanel::onSend() } } -bool LLSocialStatusPanel::onFacebookConnectStateChange(const LLSD& data) +bool LLFacebookStatusPanel::onFacebookConnectStateChange(const LLSD& data) { switch (data.get("enum").asInteger()) { @@ -131,7 +146,7 @@ bool LLSocialStatusPanel::onFacebookConnectStateChange(const LLSD& data) break; case LLFacebookConnect::FB_POSTED: - LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialStatusPanel"); + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLFacebookStatusPanel"); clearAndClose(); break; } @@ -139,7 +154,7 @@ bool LLSocialStatusPanel::onFacebookConnectStateChange(const LLSD& data) return false; } -void LLSocialStatusPanel::sendStatus() +void LLFacebookStatusPanel::sendStatus() { std::string message = mMessageTextEditor->getValue().asString(); if (!message.empty()) @@ -148,7 +163,7 @@ void LLSocialStatusPanel::sendStatus() } } -void LLSocialStatusPanel::clearAndClose() +void LLFacebookStatusPanel::clearAndClose() { mMessageTextEditor->setValue(""); @@ -160,10 +175,10 @@ void LLSocialStatusPanel::clearAndClose() } /////////////////////////// -//LLSocialPhotoPanel/////// +//LLFacebookPhotoPanel/////// /////////////////////////// -LLSocialPhotoPanel::LLSocialPhotoPanel() : +LLFacebookPhotoPanel::LLFacebookPhotoPanel() : mSnapshotPanel(NULL), mResolutionComboBox(NULL), mRefreshBtn(NULL), @@ -171,13 +186,14 @@ mWorkingLabel(NULL), mThumbnailPlaceholder(NULL), mCaptionTextBox(NULL), mLocationCheckbox(NULL), -mPostButton(NULL) +mPostButton(NULL), +mQuality(MAX_QUALITY) { - mCommitCallbackRegistrar.add("SocialSharing.SendPhoto", boost::bind(&LLSocialPhotoPanel::onSend, this)); - mCommitCallbackRegistrar.add("SocialSharing.RefreshPhoto", boost::bind(&LLSocialPhotoPanel::onClickNewSnapshot, this)); + mCommitCallbackRegistrar.add("SocialSharing.SendPhoto", boost::bind(&LLFacebookPhotoPanel::onSend, this)); + mCommitCallbackRegistrar.add("SocialSharing.RefreshPhoto", boost::bind(&LLFacebookPhotoPanel::onClickNewSnapshot, this)); } -LLSocialPhotoPanel::~LLSocialPhotoPanel() +LLFacebookPhotoPanel::~LLFacebookPhotoPanel() { if(mPreviewHandle.get()) { @@ -185,13 +201,16 @@ LLSocialPhotoPanel::~LLSocialPhotoPanel() } } -BOOL LLSocialPhotoPanel::postBuild() +BOOL LLFacebookPhotoPanel::postBuild() { - setVisibleCallback(boost::bind(&LLSocialPhotoPanel::onVisibilityChange, this, _2)); + setVisibleCallback(boost::bind(&LLFacebookPhotoPanel::onVisibilityChange, this, _2)); mSnapshotPanel = getChild<LLUICtrl>("snapshot_panel"); mResolutionComboBox = getChild<LLUICtrl>("resolution_combobox"); - mResolutionComboBox->setCommitCallback(boost::bind(&LLSocialPhotoPanel::updateResolution, this, TRUE)); + mResolutionComboBox->setValue("[i1200,i630]"); // hardcoded defaults ftw! + mResolutionComboBox->setCommitCallback(boost::bind(&LLFacebookPhotoPanel::updateResolution, this, TRUE)); + mFilterComboBox = getChild<LLUICtrl>("filters_combobox"); + mFilterComboBox->setCommitCallback(boost::bind(&LLFacebookPhotoPanel::updateResolution, this, TRUE)); mRefreshBtn = getChild<LLUICtrl>("new_snapshot_btn"); mWorkingLabel = getChild<LLUICtrl>("working_lbl"); mThumbnailPlaceholder = getChild<LLUICtrl>("thumbnail_placeholder"); @@ -200,10 +219,46 @@ BOOL LLSocialPhotoPanel::postBuild() mPostButton = getChild<LLUICtrl>("post_photo_btn"); mCancelButton = getChild<LLUICtrl>("cancel_photo_btn"); + // Update filter list + std::vector<std::string> filter_list = LLImageFiltersManager::getInstance()->getFiltersList(); + LLComboBox* filterbox = static_cast<LLComboBox *>(mFilterComboBox); + for (U32 i = 0; i < filter_list.size(); i++) + { + filterbox->add(filter_list[i]); + } + return LLPanel::postBuild(); } -void LLSocialPhotoPanel::draw() +// virtual +S32 LLFacebookPhotoPanel::notify(const LLSD& info) +{ + if (info.has("snapshot-updating")) + { + // Disable the Post button and whatever else while the snapshot is not updated + // updateControls(); + return 1; + } + + if (info.has("snapshot-updated")) + { + // Enable the send/post/save buttons. + updateControls(); + + // The refresh button is initially hidden. We show it after the first update, + // i.e. after snapshot is taken + LLUICtrl * refresh_button = getRefreshBtn(); + if (!refresh_button->getVisible()) + { + refresh_button->setVisible(true); + } + return 1; + } + + return 0; +} + +void LLFacebookPhotoPanel::draw() { LLSnapshotLivePreview * previewp = static_cast<LLSnapshotLivePreview *>(mPreviewHandle.get()); @@ -212,6 +267,7 @@ void LLSocialPhotoPanel::draw() mCancelButton->setEnabled(no_ongoing_connection); mCaptionTextBox->setEnabled(no_ongoing_connection); mResolutionComboBox->setEnabled(no_ongoing_connection); + mFilterComboBox->setEnabled(no_ongoing_connection); mRefreshBtn->setEnabled(no_ongoing_connection); mLocationCheckbox->setEnabled(no_ongoing_connection); @@ -242,8 +298,6 @@ void LLSocialPhotoPanel::draw() gl_draw_scaled_image(offset_x, offset_y, thumbnail_w, thumbnail_h, previewp->getThumbnailImage(), color % alpha); - - previewp->drawPreviewRect(offset_x, offset_y) ; } // Update the visibility of the working (computing preview) label @@ -256,13 +310,13 @@ void LLSocialPhotoPanel::draw() LLPanel::draw(); } -LLSnapshotLivePreview* LLSocialPhotoPanel::getPreviewView() +LLSnapshotLivePreview* LLFacebookPhotoPanel::getPreviewView() { LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)mPreviewHandle.get(); return previewp; } -void LLSocialPhotoPanel::onVisibilityChange(const LLSD& new_visibility) +void LLFacebookPhotoPanel::onVisibilityChange(const LLSD& new_visibility) { bool visible = new_visibility.asBoolean(); if (visible) @@ -283,10 +337,15 @@ void LLSocialPhotoPanel::onVisibilityChange(const LLSD& new_visibility) p.rect(full_screen_rect); LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); mPreviewHandle = previewp->getHandle(); + mQuality = MAX_QUALITY; + previewp->setContainer(this); previewp->setSnapshotType(previewp->SNAPSHOT_WEB); previewp->setSnapshotFormat(LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG); - //previewp->setSnapshotQuality(98); + previewp->setSnapshotQuality(mQuality, false); + previewp->setThumbnailSubsampled(TRUE); // We want the preview to reflect the *saved* image + previewp->setAllowRenderUI(FALSE); // We do not want the rendered UI in our snapshots + previewp->setAllowFullScreenPreview(FALSE); // No full screen preview in SL Share mode previewp->setThumbnailPlaceholderRect(mThumbnailPlaceholder->getRect()); updateControls(); @@ -294,21 +353,19 @@ void LLSocialPhotoPanel::onVisibilityChange(const LLSD& new_visibility) } } -void LLSocialPhotoPanel::onClickNewSnapshot() +void LLFacebookPhotoPanel::onClickNewSnapshot() { LLSnapshotLivePreview* previewp = getPreviewView(); if (previewp) { - //setStatus(Impl::STATUS_READY); - lldebugs << "updating snapshot" << llendl; previewp->updateSnapshot(TRUE); } } -void LLSocialPhotoPanel::onSend() +void LLFacebookPhotoPanel::onSend() { - LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialPhotoPanel"); // just in case it is already listening - LLEventPumps::instance().obtain("FacebookConnectState").listen("LLSocialPhotoPanel", boost::bind(&LLSocialPhotoPanel::onFacebookConnectStateChange, this, _1)); + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLFacebookPhotoPanel"); // just in case it is already listening + LLEventPumps::instance().obtain("FacebookConnectState").listen("LLFacebookPhotoPanel", boost::bind(&LLFacebookPhotoPanel::onFacebookConnectStateChange, this, _1)); // Connect to Facebook if necessary and then post if (LLFacebookConnect::instance().isConnected()) @@ -321,7 +378,7 @@ void LLSocialPhotoPanel::onSend() } } -bool LLSocialPhotoPanel::onFacebookConnectStateChange(const LLSD& data) +bool LLFacebookPhotoPanel::onFacebookConnectStateChange(const LLSD& data) { switch (data.get("enum").asInteger()) { @@ -330,7 +387,7 @@ bool LLSocialPhotoPanel::onFacebookConnectStateChange(const LLSD& data) break; case LLFacebookConnect::FB_POSTED: - LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialPhotoPanel"); + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLFacebookPhotoPanel"); clearAndClose(); break; } @@ -338,7 +395,7 @@ bool LLSocialPhotoPanel::onFacebookConnectStateChange(const LLSD& data) return false; } -void LLSocialPhotoPanel::sendPhoto() +void LLFacebookPhotoPanel::sendPhoto() { // Get the caption std::string caption = mCaptionTextBox->getValue().asString(); @@ -371,7 +428,7 @@ void LLSocialPhotoPanel::sendPhoto() updateControls(); } -void LLSocialPhotoPanel::clearAndClose() +void LLFacebookPhotoPanel::clearAndClose() { mCaptionTextBox->setValue(""); @@ -382,36 +439,21 @@ void LLSocialPhotoPanel::clearAndClose() } } -void LLSocialPhotoPanel::updateControls() +void LLFacebookPhotoPanel::updateControls() { LLSnapshotLivePreview* previewp = getPreviewView(); - BOOL got_bytes = previewp && previewp->getDataSize() > 0; BOOL got_snap = previewp && previewp->getSnapshotUpToDate(); - LLSnapshotLivePreview::ESnapshotType shot_type = (previewp ? previewp->getSnapshotType() : LLSnapshotLivePreview::SNAPSHOT_POSTCARD); - + // *TODO: Separate maximum size for Web images from postcards lldebugs << "Is snapshot up-to-date? " << got_snap << llendl; - - LLLocale locale(LLLocale::USER_LOCALE); - std::string bytes_string; - if (got_snap) - { - LLResMgr::getInstance()->getIntegerString(bytes_string, (previewp->getDataSize()) >> 10 ); - } - - //getChild<LLUICtrl>("file_size_label")->setTextArg("[SIZE]", got_snap ? bytes_string : getString("unknown")); <---uses localized string - getChild<LLUICtrl>("file_size_label")->setTextArg("[SIZE]", got_snap ? bytes_string : "unknown"); - getChild<LLUICtrl>("file_size_label")->setColor( - shot_type == LLSnapshotLivePreview::SNAPSHOT_POSTCARD - && got_bytes - && previewp->getDataSize() > MAX_POSTCARD_DATASIZE ? LLUIColor(LLColor4::red) : LLUIColorTable::instance().getColor( "LabelTextColor" )); - + updateResolution(FALSE); } -void LLSocialPhotoPanel::updateResolution(BOOL do_update) +void LLFacebookPhotoPanel::updateResolution(BOOL do_update) { LLComboBox* combobox = static_cast<LLComboBox *>(mResolutionComboBox); + LLComboBox* filterbox = static_cast<LLComboBox *>(mFilterComboBox); std::string sdstring = combobox->getSelectedValue(); LLSD sdres; @@ -421,6 +463,9 @@ void LLSocialPhotoPanel::updateResolution(BOOL do_update) S32 width = sdres[0]; S32 height = sdres[1]; + // Note : index 0 of the filter drop down is assumed to be "No filter" in whichever locale + std::string filter_name = (filterbox->getCurrentIndex() ? filterbox->getSimple() : ""); + LLSnapshotLivePreview * previewp = static_cast<LLSnapshotLivePreview *>(mPreviewHandle.get()); if (previewp && combobox->getCurrentIndex() >= 0) { @@ -443,27 +488,35 @@ void LLSocialPhotoPanel::updateResolution(BOOL do_update) checkAspectRatio(width); previewp->getSize(width, height); + + // Recompute quality setting + mQuality = compute_jpeg_quality(width, height); + previewp->setSnapshotQuality(mQuality, false); - if(original_width != width || original_height != height) + if (original_width != width || original_height != height) { previewp->setSize(width, height); - - // hide old preview as the aspect ratio could be wrong - lldebugs << "updating thumbnail" << llendl; - - previewp->updateSnapshot(FALSE, TRUE); - if(do_update) + if (do_update) { - lldebugs << "Will update controls" << llendl; + previewp->updateSnapshot(TRUE); + updateControls(); + } + } + // Get the old filter, compare to the current one "filter_name" and set if changed + std::string original_filter = previewp->getFilter(); + if (original_filter != filter_name) + { + previewp->setFilter(filter_name); + if (do_update) + { + previewp->updateSnapshot(FALSE, TRUE); updateControls(); - LLSocialPhotoPanel::onClickNewSnapshot(); } } - } } -void LLSocialPhotoPanel::checkAspectRatio(S32 index) +void LLFacebookPhotoPanel::checkAspectRatio(S32 index) { LLSnapshotLivePreview *previewp = getPreviewView() ; @@ -484,23 +537,23 @@ void LLSocialPhotoPanel::checkAspectRatio(S32 index) } } -LLUICtrl* LLSocialPhotoPanel::getRefreshBtn() +LLUICtrl* LLFacebookPhotoPanel::getRefreshBtn() { return mRefreshBtn; } //////////////////////// -//LLSocialCheckinPanel// +//LLFacebookCheckinPanel// //////////////////////// -LLSocialCheckinPanel::LLSocialCheckinPanel() : +LLFacebookCheckinPanel::LLFacebookCheckinPanel() : mMapUrl(""), mReloadingMapTexture(false) { - mCommitCallbackRegistrar.add("SocialSharing.SendCheckin", boost::bind(&LLSocialCheckinPanel::onSend, this)); + mCommitCallbackRegistrar.add("SocialSharing.SendCheckin", boost::bind(&LLFacebookCheckinPanel::onSend, this)); } -BOOL LLSocialCheckinPanel::postBuild() +BOOL LLFacebookCheckinPanel::postBuild() { // Keep pointers to widgets so we don't traverse the UI hierarchy too often mPostButton = getChild<LLUICtrl>("post_place_btn"); @@ -514,7 +567,7 @@ BOOL LLSocialCheckinPanel::postBuild() return LLPanel::postBuild(); } -void LLSocialCheckinPanel::draw() +void LLFacebookCheckinPanel::draw() { bool no_ongoing_connection = !(LLFacebookConnect::instance().isTransactionOngoing()); mPostButton->setEnabled(no_ongoing_connection); @@ -555,10 +608,10 @@ void LLSocialCheckinPanel::draw() LLPanel::draw(); } -void LLSocialCheckinPanel::onSend() +void LLFacebookCheckinPanel::onSend() { - LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialCheckinPanel"); // just in case it is already listening - LLEventPumps::instance().obtain("FacebookConnectState").listen("LLSocialCheckinPanel", boost::bind(&LLSocialCheckinPanel::onFacebookConnectStateChange, this, _1)); + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLFacebookCheckinPanel"); // just in case it is already listening + LLEventPumps::instance().obtain("FacebookConnectState").listen("LLFacebookCheckinPanel", boost::bind(&LLFacebookCheckinPanel::onFacebookConnectStateChange, this, _1)); // Connect to Facebook if necessary and then post if (LLFacebookConnect::instance().isConnected()) @@ -571,7 +624,7 @@ void LLSocialCheckinPanel::onSend() } } -bool LLSocialCheckinPanel::onFacebookConnectStateChange(const LLSD& data) +bool LLFacebookCheckinPanel::onFacebookConnectStateChange(const LLSD& data) { switch (data.get("enum").asInteger()) { @@ -580,7 +633,7 @@ bool LLSocialCheckinPanel::onFacebookConnectStateChange(const LLSD& data) break; case LLFacebookConnect::FB_POSTED: - LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialCheckinPanel"); + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLFacebookCheckinPanel"); clearAndClose(); break; } @@ -588,7 +641,7 @@ bool LLSocialCheckinPanel::onFacebookConnectStateChange(const LLSD& data) return false; } -void LLSocialCheckinPanel::sendCheckin() +void LLFacebookCheckinPanel::sendCheckin() { // Get the location SLURL LLSLURL slurl; @@ -606,7 +659,12 @@ void LLSocialCheckinPanel::sendCheckin() slurl_string += DEFAULT_CHECKIN_QUERY_PARAMETERS; // Get the region name - std::string region_name = gAgent.getRegion()->getName(); + std::string region_name(""); + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + region_name = regionp->getName(); + } // Get the region description std::string description; @@ -623,7 +681,7 @@ void LLSocialCheckinPanel::sendCheckin() LLFacebookConnect::instance().postCheckin(slurl_string, region_name, description, map_url, caption); } -void LLSocialCheckinPanel::clearAndClose() +void LLFacebookCheckinPanel::clearAndClose() { mMessageTextEditor->setValue(""); @@ -635,23 +693,23 @@ void LLSocialCheckinPanel::clearAndClose() } /////////////////////////// -//LLSocialAccountPanel////// +//LLFacebookAccountPanel////// /////////////////////////// -LLSocialAccountPanel::LLSocialAccountPanel() : +LLFacebookAccountPanel::LLFacebookAccountPanel() : mAccountCaptionLabel(NULL), mAccountNameLabel(NULL), mPanelButtons(NULL), mConnectButton(NULL), mDisconnectButton(NULL) { - mCommitCallbackRegistrar.add("SocialSharing.Connect", boost::bind(&LLSocialAccountPanel::onConnect, this)); - mCommitCallbackRegistrar.add("SocialSharing.Disconnect", boost::bind(&LLSocialAccountPanel::onDisconnect, this)); + mCommitCallbackRegistrar.add("SocialSharing.Connect", boost::bind(&LLFacebookAccountPanel::onConnect, this)); + mCommitCallbackRegistrar.add("SocialSharing.Disconnect", boost::bind(&LLFacebookAccountPanel::onDisconnect, this)); - setVisibleCallback(boost::bind(&LLSocialAccountPanel::onVisibilityChange, this, _2)); + setVisibleCallback(boost::bind(&LLFacebookAccountPanel::onVisibilityChange, this, _2)); } -BOOL LLSocialAccountPanel::postBuild() +BOOL LLFacebookAccountPanel::postBuild() { mAccountCaptionLabel = getChild<LLTextBox>("account_caption_label"); mAccountNameLabel = getChild<LLTextBox>("account_name_label"); @@ -662,7 +720,7 @@ BOOL LLSocialAccountPanel::postBuild() return LLPanel::postBuild(); } -void LLSocialAccountPanel::draw() +void LLFacebookAccountPanel::draw() { LLFacebookConnect::EConnectionState connection_state = LLFacebookConnect::instance().getConnectionState(); @@ -677,17 +735,17 @@ void LLSocialAccountPanel::draw() LLPanel::draw(); } -void LLSocialAccountPanel::onVisibilityChange(const LLSD& new_visibility) +void LLFacebookAccountPanel::onVisibilityChange(const LLSD& new_visibility) { bool visible = new_visibility.asBoolean(); if(visible) { - LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialAccountPanel"); - LLEventPumps::instance().obtain("FacebookConnectState").listen("LLSocialAccountPanel", boost::bind(&LLSocialAccountPanel::onFacebookConnectStateChange, this, _1)); + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLFacebookAccountPanel"); + LLEventPumps::instance().obtain("FacebookConnectState").listen("LLFacebookAccountPanel", boost::bind(&LLFacebookAccountPanel::onFacebookConnectStateChange, this, _1)); - LLEventPumps::instance().obtain("FacebookConnectInfo").stopListening("LLSocialAccountPanel"); - LLEventPumps::instance().obtain("FacebookConnectInfo").listen("LLSocialAccountPanel", boost::bind(&LLSocialAccountPanel::onFacebookConnectInfoChange, this)); + LLEventPumps::instance().obtain("FacebookConnectInfo").stopListening("LLFacebookAccountPanel"); + LLEventPumps::instance().obtain("FacebookConnectInfo").listen("LLFacebookAccountPanel", boost::bind(&LLFacebookAccountPanel::onFacebookConnectInfoChange, this)); //Connected if(LLFacebookConnect::instance().isConnected()) @@ -707,12 +765,12 @@ void LLSocialAccountPanel::onVisibilityChange(const LLSD& new_visibility) } else { - LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialAccountPanel"); - LLEventPumps::instance().obtain("FacebookConnectInfo").stopListening("LLSocialAccountPanel"); + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLFacebookAccountPanel"); + LLEventPumps::instance().obtain("FacebookConnectInfo").stopListening("LLFacebookAccountPanel"); } } -bool LLSocialAccountPanel::onFacebookConnectStateChange(const LLSD& data) +bool LLFacebookAccountPanel::onFacebookConnectStateChange(const LLSD& data) { if(LLFacebookConnect::instance().isConnected()) { @@ -730,7 +788,7 @@ bool LLSocialAccountPanel::onFacebookConnectStateChange(const LLSD& data) return false; } -bool LLSocialAccountPanel::onFacebookConnectInfoChange() +bool LLFacebookAccountPanel::onFacebookConnectInfoChange() { LLSD info = LLFacebookConnect::instance().getInfo(); std::string clickable_name; @@ -746,7 +804,7 @@ bool LLSocialAccountPanel::onFacebookConnectInfoChange() return false; } -void LLSocialAccountPanel::showConnectButton() +void LLFacebookAccountPanel::showConnectButton() { if(!mConnectButton->getVisible()) { @@ -755,7 +813,7 @@ void LLSocialAccountPanel::showConnectButton() } } -void LLSocialAccountPanel::hideConnectButton() +void LLFacebookAccountPanel::hideConnectButton() { if(mConnectButton->getVisible()) { @@ -764,14 +822,14 @@ void LLSocialAccountPanel::hideConnectButton() } } -void LLSocialAccountPanel::showDisconnectedLayout() +void LLFacebookAccountPanel::showDisconnectedLayout() { mAccountCaptionLabel->setText(getString("facebook_disconnected")); mAccountNameLabel->setText(std::string("")); showConnectButton(); } -void LLSocialAccountPanel::showConnectedLayout() +void LLFacebookAccountPanel::showConnectedLayout() { LLFacebookConnect::instance().loadFacebookInfo(); @@ -779,7 +837,7 @@ void LLSocialAccountPanel::showConnectedLayout() hideConnectButton(); } -void LLSocialAccountPanel::onConnect() +void LLFacebookAccountPanel::onConnect() { LLFacebookConnect::instance().checkConnectionToFacebook(true); @@ -787,7 +845,7 @@ void LLSocialAccountPanel::onConnect() LLViewerMedia::getCookieStore()->removeCookiesByDomain(".facebook.com"); } -void LLSocialAccountPanel::onDisconnect() +void LLFacebookAccountPanel::onDisconnect() { LLFacebookConnect::instance().disconnectFromFacebook(); @@ -795,27 +853,27 @@ void LLSocialAccountPanel::onDisconnect() } //////////////////////// -//LLFloaterSocial/////// +//LLFloaterFacebook/////// //////////////////////// -LLFloaterSocial::LLFloaterSocial(const LLSD& key) : LLFloater(key), - mSocialPhotoPanel(NULL), +LLFloaterFacebook::LLFloaterFacebook(const LLSD& key) : LLFloater(key), + mFacebookPhotoPanel(NULL), mStatusErrorText(NULL), mStatusLoadingText(NULL), mStatusLoadingIndicator(NULL) { - mCommitCallbackRegistrar.add("SocialSharing.Cancel", boost::bind(&LLFloaterSocial::onCancel, this)); + mCommitCallbackRegistrar.add("SocialSharing.Cancel", boost::bind(&LLFloaterFacebook::onCancel, this)); } -void LLFloaterSocial::onCancel() +void LLFloaterFacebook::onCancel() { closeFloater(); } -BOOL LLFloaterSocial::postBuild() +BOOL LLFloaterFacebook::postBuild() { // Keep tab of the Photo Panel - mSocialPhotoPanel = static_cast<LLSocialPhotoPanel*>(getChild<LLUICtrl>("panel_social_photo")); + mFacebookPhotoPanel = static_cast<LLFacebookPhotoPanel*>(getChild<LLUICtrl>("panel_facebook_photo")); // Connection status widgets mStatusErrorText = getChild<LLTextBox>("connection_error_text"); mStatusLoadingText = getChild<LLTextBox>("connection_loading_text"); @@ -823,39 +881,19 @@ BOOL LLFloaterSocial::postBuild() return LLFloater::postBuild(); } -// static -void LLFloaterSocial::preUpdate() +void LLFloaterFacebook::showPhotoPanel() { - LLFloaterSocial* instance = LLFloaterReg::findTypedInstance<LLFloaterSocial>("social"); - if (instance) + LLTabContainer* parent = dynamic_cast<LLTabContainer*>(mFacebookPhotoPanel->getParent()); + if (!parent) { - //Will set file size text to 'unknown' - instance->mSocialPhotoPanel->updateControls(); + llwarns << "Cannot find panel container" << llendl; + return; } -} - -// static -void LLFloaterSocial::postUpdate() -{ - LLFloaterSocial* instance = LLFloaterReg::findTypedInstance<LLFloaterSocial>("social"); - if (instance) - { - //Will set the file size text - instance->mSocialPhotoPanel->updateControls(); - - // The refresh button is initially hidden. We show it after the first update, - // i.e. after snapshot is taken - LLUICtrl * refresh_button = instance->mSocialPhotoPanel->getRefreshBtn(); - if (!refresh_button->getVisible()) - { - refresh_button->setVisible(true); - } - - } + parent->selectTabPanel(mFacebookPhotoPanel); } -void LLFloaterSocial::draw() +void LLFloaterFacebook::draw() { if (mStatusErrorText && mStatusLoadingText && mStatusLoadingIndicator) { diff --git a/indra/newview/llfloatersocial.h b/indra/newview/llfloaterfacebook.h index bbe07c9704..4361dfa628 100644 --- a/indra/newview/llfloatersocial.h +++ b/indra/newview/llfloaterfacebook.h @@ -1,6 +1,6 @@ /** -* @file llfloatersocial.h -* @brief Header file for llfloatersocial +* @file llfloaterfacebook.h +* @brief Header file for llfloaterfacebook * @author Gilbert@lindenlab.com * * $LicenseInfo:firstyear=2013&license=viewerlgpl$ @@ -24,8 +24,8 @@ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ -#ifndef LL_LLFLOATERSOCIAL_H -#define LL_LLFLOATERSOCIAL_H +#ifndef LL_LLFLOATERFACEBOOK_H +#define LL_LLFLOATERFACEBOOK_H #include "llfloater.h" #include "lltextbox.h" @@ -35,10 +35,10 @@ class LLIconCtrl; class LLCheckBoxCtrl; class LLSnapshotLivePreview; -class LLSocialStatusPanel : public LLPanel +class LLFacebookStatusPanel : public LLPanel { public: - LLSocialStatusPanel(); + LLFacebookStatusPanel(); BOOL postBuild(); void draw(); void onSend(); @@ -53,11 +53,11 @@ private: LLUICtrl* mCancelButton; }; -class LLSocialPhotoPanel : public LLPanel +class LLFacebookPhotoPanel : public LLPanel { public: - LLSocialPhotoPanel(); - ~LLSocialPhotoPanel(); + LLFacebookPhotoPanel(); + ~LLFacebookPhotoPanel(); BOOL postBuild(); void draw(); @@ -66,6 +66,7 @@ public: void onVisibilityChange(const LLSD& new_visibility); void onClickNewSnapshot(); void onSend(); + S32 notify(const LLSD& info); bool onFacebookConnectStateChange(const LLSD& data); void sendPhoto(); @@ -81,6 +82,7 @@ private: LLUICtrl * mSnapshotPanel; LLUICtrl * mResolutionComboBox; + LLUICtrl * mFilterComboBox; LLUICtrl * mRefreshBtn; LLUICtrl * mWorkingLabel; LLUICtrl * mThumbnailPlaceholder; @@ -88,12 +90,14 @@ private: LLUICtrl * mLocationCheckbox; LLUICtrl * mPostButton; LLUICtrl* mCancelButton; + + S32 mQuality; // Compression quality }; -class LLSocialCheckinPanel : public LLPanel +class LLFacebookCheckinPanel : public LLPanel { public: - LLSocialCheckinPanel(); + LLFacebookCheckinPanel(); BOOL postBuild(); void draw(); void onSend(); @@ -115,10 +119,10 @@ private: bool mReloadingMapTexture; }; -class LLSocialAccountPanel : public LLPanel +class LLFacebookAccountPanel : public LLPanel { public: - LLSocialAccountPanel(); + LLFacebookAccountPanel(); BOOL postBuild(); void draw(); @@ -143,23 +147,22 @@ private: }; -class LLFloaterSocial : public LLFloater +class LLFloaterFacebook : public LLFloater { public: - LLFloaterSocial(const LLSD& key); + LLFloaterFacebook(const LLSD& key); BOOL postBuild(); void draw(); void onCancel(); - - static void preUpdate(); - static void postUpdate(); + + void showPhotoPanel(); private: - LLSocialPhotoPanel* mSocialPhotoPanel; + LLFacebookPhotoPanel* mFacebookPhotoPanel; LLTextBox* mStatusErrorText; LLTextBox* mStatusLoadingText; LLUICtrl* mStatusLoadingIndicator; }; -#endif // LL_LLFLOATERSOCIAL_H +#endif // LL_LLFLOATERFACEBOOK_H diff --git a/indra/newview/llfloaterflickr.cpp b/indra/newview/llfloaterflickr.cpp new file mode 100644 index 0000000000..607b5b5df9 --- /dev/null +++ b/indra/newview/llfloaterflickr.cpp @@ -0,0 +1,700 @@ +/** +* @file llfloaterflickr.cpp +* @brief Implementation of llfloaterflickr +* @author cho@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterflickr.h" + +#include "llagent.h" +#include "llagentui.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llflickrconnect.h" +#include "llfloaterreg.h" +#include "lliconctrl.h" +#include "llimagefiltersmanager.h" +#include "llresmgr.h" // LLLocale +#include "llsdserialize.h" +#include "llloadingindicator.h" +#include "llplugincookiestore.h" +#include "llslurl.h" +#include "lltrans.h" +#include "llsnapshotlivepreview.h" +#include "llviewerregion.h" +#include "llviewercontrol.h" +#include "llviewermedia.h" +#include "lltabcontainer.h" + +static LLRegisterPanelClassWrapper<LLFlickrPhotoPanel> t_panel_photo("llflickrphotopanel"); +static LLRegisterPanelClassWrapper<LLFlickrAccountPanel> t_panel_account("llflickraccountpanel"); + +const S32 MAX_POSTCARD_DATASIZE = 1024 * 1024; // one megabyte +const std::string DEFAULT_PHOTO_QUERY_PARAMETERS = "?sourceid=slshare_photo&utm_source=flickr&utm_medium=photo&utm_campaign=slshare"; +const std::string DEFAULT_PHOTO_LINK_TEXT = "Visit this location now"; +const std::string DEFAULT_TAG_TEXT = "secondlife "; + +/////////////////////////// +//LLFlickrPhotoPanel/////// +/////////////////////////// + +LLFlickrPhotoPanel::LLFlickrPhotoPanel() : +mSnapshotPanel(NULL), +mResolutionComboBox(NULL), +mRefreshBtn(NULL), +mWorkingLabel(NULL), +mThumbnailPlaceholder(NULL), +mTitleTextBox(NULL), +mDescriptionTextBox(NULL), +mLocationCheckbox(NULL), +mTagsTextBox(NULL), +mRatingComboBox(NULL), +mPostButton(NULL) +{ + mCommitCallbackRegistrar.add("SocialSharing.SendPhoto", boost::bind(&LLFlickrPhotoPanel::onSend, this)); + mCommitCallbackRegistrar.add("SocialSharing.RefreshPhoto", boost::bind(&LLFlickrPhotoPanel::onClickNewSnapshot, this)); +} + +LLFlickrPhotoPanel::~LLFlickrPhotoPanel() +{ + if(mPreviewHandle.get()) + { + mPreviewHandle.get()->die(); + } +} + +BOOL LLFlickrPhotoPanel::postBuild() +{ + setVisibleCallback(boost::bind(&LLFlickrPhotoPanel::onVisibilityChange, this, _2)); + + mSnapshotPanel = getChild<LLUICtrl>("snapshot_panel"); + mResolutionComboBox = getChild<LLUICtrl>("resolution_combobox"); + mResolutionComboBox->setCommitCallback(boost::bind(&LLFlickrPhotoPanel::updateResolution, this, TRUE)); + mFilterComboBox = getChild<LLUICtrl>("filters_combobox"); + mFilterComboBox->setCommitCallback(boost::bind(&LLFlickrPhotoPanel::updateResolution, this, TRUE)); + mRefreshBtn = getChild<LLUICtrl>("new_snapshot_btn"); + mWorkingLabel = getChild<LLUICtrl>("working_lbl"); + mThumbnailPlaceholder = getChild<LLUICtrl>("thumbnail_placeholder"); + mTitleTextBox = getChild<LLUICtrl>("photo_title"); + mDescriptionTextBox = getChild<LLUICtrl>("photo_description"); + mLocationCheckbox = getChild<LLUICtrl>("add_location_cb"); + mTagsTextBox = getChild<LLUICtrl>("photo_tags"); + mTagsTextBox->setValue(DEFAULT_TAG_TEXT); + mRatingComboBox = getChild<LLUICtrl>("rating_combobox"); + mPostButton = getChild<LLUICtrl>("post_photo_btn"); + mCancelButton = getChild<LLUICtrl>("cancel_photo_btn"); + + // Update filter list + std::vector<std::string> filter_list = LLImageFiltersManager::getInstance()->getFiltersList(); + LLComboBox* filterbox = static_cast<LLComboBox *>(mFilterComboBox); + for (U32 i = 0; i < filter_list.size(); i++) + { + filterbox->add(filter_list[i]); + } + + return LLPanel::postBuild(); +} + +// virtual +S32 LLFlickrPhotoPanel::notify(const LLSD& info) +{ + if (info.has("snapshot-updating")) + { + // Disable the Post button and whatever else while the snapshot is not updated + // updateControls(); + return 1; + } + + if (info.has("snapshot-updated")) + { + // Enable the send/post/save buttons. + updateControls(); + + // The refresh button is initially hidden. We show it after the first update, + // i.e. after snapshot is taken + LLUICtrl * refresh_button = getRefreshBtn(); + if (!refresh_button->getVisible()) + { + refresh_button->setVisible(true); + } + return 1; + } + + return 0; +} + +void LLFlickrPhotoPanel::draw() +{ + LLSnapshotLivePreview * previewp = static_cast<LLSnapshotLivePreview *>(mPreviewHandle.get()); + + // Enable interaction only if no transaction with the service is on-going (prevent duplicated posts) + bool no_ongoing_connection = !(LLFlickrConnect::instance().isTransactionOngoing()); + mCancelButton->setEnabled(no_ongoing_connection); + mTitleTextBox->setEnabled(no_ongoing_connection); + mDescriptionTextBox->setEnabled(no_ongoing_connection); + mTagsTextBox->setEnabled(no_ongoing_connection); + mRatingComboBox->setEnabled(no_ongoing_connection); + mResolutionComboBox->setEnabled(no_ongoing_connection); + mFilterComboBox->setEnabled(no_ongoing_connection); + mRefreshBtn->setEnabled(no_ongoing_connection); + mLocationCheckbox->setEnabled(no_ongoing_connection); + + // Display the preview if one is available + if (previewp && previewp->getThumbnailImage()) + { + const LLRect& thumbnail_rect = mThumbnailPlaceholder->getRect(); + const S32 thumbnail_w = previewp->getThumbnailWidth(); + const S32 thumbnail_h = previewp->getThumbnailHeight(); + + // calc preview offset within the preview rect + const S32 local_offset_x = (thumbnail_rect.getWidth() - thumbnail_w) / 2 ; + const S32 local_offset_y = (thumbnail_rect.getHeight() - thumbnail_h) / 2 ; + + // calc preview offset within the floater rect + // Hack : To get the full offset, we need to take into account each and every offset of each widgets up to the floater. + // This is almost as arbitrary as using a fixed offset so that's what we do here for the sake of simplicity. + // *TODO : Get the offset looking through the hierarchy of widgets, should be done in postBuild() so to avoid traversing the hierarchy each time. + S32 offset_x = thumbnail_rect.mLeft + local_offset_x - 1; + S32 offset_y = thumbnail_rect.mBottom + local_offset_y - 39; + + mSnapshotPanel->localPointToOtherView(offset_x, offset_y, &offset_x, &offset_y, getParentByType<LLFloater>()); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + // Apply floater transparency to the texture unless the floater is focused. + F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + LLColor4 color = LLColor4::white; + gl_draw_scaled_image(offset_x, offset_y, + thumbnail_w, thumbnail_h, + previewp->getThumbnailImage(), color % alpha); + } + + // Update the visibility of the working (computing preview) label + mWorkingLabel->setVisible(!(previewp && previewp->getSnapshotUpToDate())); + + // Enable Post if we have a preview to send and no on going connection being processed + mPostButton->setEnabled(no_ongoing_connection && (previewp && previewp->getSnapshotUpToDate())); + + // Draw the rest of the panel on top of it + LLPanel::draw(); +} + +LLSnapshotLivePreview* LLFlickrPhotoPanel::getPreviewView() +{ + LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)mPreviewHandle.get(); + return previewp; +} + +void LLFlickrPhotoPanel::onVisibilityChange(const LLSD& new_visibility) +{ + bool visible = new_visibility.asBoolean(); + if (visible) + { + if (mPreviewHandle.get()) + { + LLSnapshotLivePreview* preview = getPreviewView(); + if(preview) + { + lldebugs << "opened, updating snapshot" << llendl; + preview->updateSnapshot(TRUE); + } + } + else + { + LLRect full_screen_rect = getRootView()->getRect(); + LLSnapshotLivePreview::Params p; + p.rect(full_screen_rect); + LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); + mPreviewHandle = previewp->getHandle(); + + previewp->setContainer(this); + previewp->setSnapshotType(previewp->SNAPSHOT_WEB); + previewp->setSnapshotFormat(LLFloaterSnapshot::SNAPSHOT_FORMAT_PNG); + previewp->setThumbnailSubsampled(TRUE); // We want the preview to reflect the *saved* image + previewp->setAllowRenderUI(FALSE); // We do not want the rendered UI in our snapshots + previewp->setAllowFullScreenPreview(FALSE); // No full screen preview in SL Share mode + previewp->setThumbnailPlaceholderRect(mThumbnailPlaceholder->getRect()); + + updateControls(); + } + } +} + +void LLFlickrPhotoPanel::onClickNewSnapshot() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + if (previewp) + { + previewp->updateSnapshot(TRUE); + } +} + +void LLFlickrPhotoPanel::onSend() +{ + LLEventPumps::instance().obtain("FlickrConnectState").stopListening("LLFlickrPhotoPanel"); // just in case it is already listening + LLEventPumps::instance().obtain("FlickrConnectState").listen("LLFlickrPhotoPanel", boost::bind(&LLFlickrPhotoPanel::onFlickrConnectStateChange, this, _1)); + + // Connect to Flickr if necessary and then post + if (LLFlickrConnect::instance().isConnected()) + { + sendPhoto(); + } + else + { + LLFlickrConnect::instance().checkConnectionToFlickr(true); + } +} + +bool LLFlickrPhotoPanel::onFlickrConnectStateChange(const LLSD& data) +{ + switch (data.get("enum").asInteger()) + { + case LLFlickrConnect::FLICKR_CONNECTED: + sendPhoto(); + break; + + case LLFlickrConnect::FLICKR_POSTED: + LLEventPumps::instance().obtain("FlickrConnectState").stopListening("LLFlickrPhotoPanel"); + clearAndClose(); + break; + } + + return false; +} + +void LLFlickrPhotoPanel::sendPhoto() +{ + // Get the title, description, and tags + std::string title = mTitleTextBox->getValue().asString(); + std::string description = mDescriptionTextBox->getValue().asString(); + std::string tags = mTagsTextBox->getValue().asString(); + + // Add the location if required + bool add_location = mLocationCheckbox->getValue().asBoolean(); + if (add_location) + { + // Get the SLURL for the location + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + std::string slurl_string = slurl.getSLURLString(); + + // Add query parameters so Google Analytics can track incoming clicks! + slurl_string += DEFAULT_PHOTO_QUERY_PARAMETERS; + + slurl_string = "<a href=\"" + slurl_string + "\">" + DEFAULT_PHOTO_LINK_TEXT + "</a>"; + + // Add it to the description (pretty crude, but we don't have a better option with photos) + if (description.empty()) + description = slurl_string; + else + description = description + "\n\n" + slurl_string; + } + + // Get the content rating + int content_rating = mRatingComboBox->getValue().asInteger(); + + // Get the image + LLSnapshotLivePreview* previewp = getPreviewView(); + + // Post to Flickr + LLFlickrConnect::instance().uploadPhoto(previewp->getFormattedImage(), title, description, tags, content_rating); + + updateControls(); +} + +void LLFlickrPhotoPanel::clearAndClose() +{ + mTitleTextBox->setValue(""); + mDescriptionTextBox->setValue(""); + + LLFloater* floater = getParentByType<LLFloater>(); + if (floater) + { + floater->closeFloater(); + } +} + +void LLFlickrPhotoPanel::updateControls() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + BOOL got_snap = previewp && previewp->getSnapshotUpToDate(); + + // *TODO: Separate maximum size for Web images from postcards + lldebugs << "Is snapshot up-to-date? " << got_snap << llendl; + + updateResolution(FALSE); +} + +void LLFlickrPhotoPanel::updateResolution(BOOL do_update) +{ + LLComboBox* combobox = static_cast<LLComboBox *>(mResolutionComboBox); + LLComboBox* filterbox = static_cast<LLComboBox *>(mFilterComboBox); + + std::string sdstring = combobox->getSelectedValue(); + LLSD sdres; + std::stringstream sstream(sdstring); + LLSDSerialize::fromNotation(sdres, sstream, sdstring.size()); + + S32 width = sdres[0]; + S32 height = sdres[1]; + + // Note : index 0 of the filter drop down is assumed to be "No filter" in whichever locale + std::string filter_name = (filterbox->getCurrentIndex() ? filterbox->getSimple() : ""); + + LLSnapshotLivePreview * previewp = static_cast<LLSnapshotLivePreview *>(mPreviewHandle.get()); + if (previewp && combobox->getCurrentIndex() >= 0) + { + S32 original_width = 0 , original_height = 0 ; + previewp->getSize(original_width, original_height) ; + + if (width == 0 || height == 0) + { + // take resolution from current window size + lldebugs << "Setting preview res from window: " << gViewerWindow->getWindowWidthRaw() << "x" << gViewerWindow->getWindowHeightRaw() << llendl; + previewp->setSize(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()); + } + else + { + // use the resolution from the selected pre-canned drop-down choice + lldebugs << "Setting preview res selected from combo: " << width << "x" << height << llendl; + previewp->setSize(width, height); + } + + checkAspectRatio(width); + + previewp->getSize(width, height); + if ((original_width != width) || (original_height != height)) + { + previewp->setSize(width, height); + if (do_update) + { + previewp->updateSnapshot(TRUE); + updateControls(); + } + } + // Get the old filter, compare to the current one "filter_name" and set if changed + std::string original_filter = previewp->getFilter(); + if (original_filter != filter_name) + { + previewp->setFilter(filter_name); + if (do_update) + { + previewp->updateSnapshot(FALSE, TRUE); + updateControls(); + } + } + } +} + +void LLFlickrPhotoPanel::checkAspectRatio(S32 index) +{ + LLSnapshotLivePreview *previewp = getPreviewView() ; + + BOOL keep_aspect = FALSE; + + if (0 == index) // current window size + { + keep_aspect = TRUE; + } + else // predefined resolution + { + keep_aspect = FALSE; + } + + if (previewp) + { + previewp->mKeepAspectRatio = keep_aspect; + } +} + +LLUICtrl* LLFlickrPhotoPanel::getRefreshBtn() +{ + return mRefreshBtn; +} + +/////////////////////////// +//LLFlickrAccountPanel////// +/////////////////////////// + +LLFlickrAccountPanel::LLFlickrAccountPanel() : +mAccountCaptionLabel(NULL), +mAccountNameLabel(NULL), +mPanelButtons(NULL), +mConnectButton(NULL), +mDisconnectButton(NULL) +{ + mCommitCallbackRegistrar.add("SocialSharing.Connect", boost::bind(&LLFlickrAccountPanel::onConnect, this)); + mCommitCallbackRegistrar.add("SocialSharing.Disconnect", boost::bind(&LLFlickrAccountPanel::onDisconnect, this)); + + setVisibleCallback(boost::bind(&LLFlickrAccountPanel::onVisibilityChange, this, _2)); +} + +BOOL LLFlickrAccountPanel::postBuild() +{ + mAccountCaptionLabel = getChild<LLTextBox>("account_caption_label"); + mAccountNameLabel = getChild<LLTextBox>("account_name_label"); + mPanelButtons = getChild<LLUICtrl>("panel_buttons"); + mConnectButton = getChild<LLUICtrl>("connect_btn"); + mDisconnectButton = getChild<LLUICtrl>("disconnect_btn"); + + return LLPanel::postBuild(); +} + +void LLFlickrAccountPanel::draw() +{ + LLFlickrConnect::EConnectionState connection_state = LLFlickrConnect::instance().getConnectionState(); + + //Disable the 'disconnect' button and the 'use another account' button when disconnecting in progress + bool disconnecting = connection_state == LLFlickrConnect::FLICKR_DISCONNECTING; + mDisconnectButton->setEnabled(!disconnecting); + + //Disable the 'connect' button when a connection is in progress + bool connecting = connection_state == LLFlickrConnect::FLICKR_CONNECTION_IN_PROGRESS; + mConnectButton->setEnabled(!connecting); + + LLPanel::draw(); +} + +void LLFlickrAccountPanel::onVisibilityChange(const LLSD& new_visibility) +{ + bool visible = new_visibility.asBoolean(); + + if(visible) + { + LLEventPumps::instance().obtain("FlickrConnectState").stopListening("LLFlickrAccountPanel"); + LLEventPumps::instance().obtain("FlickrConnectState").listen("LLFlickrAccountPanel", boost::bind(&LLFlickrAccountPanel::onFlickrConnectStateChange, this, _1)); + + LLEventPumps::instance().obtain("FlickrConnectInfo").stopListening("LLFlickrAccountPanel"); + LLEventPumps::instance().obtain("FlickrConnectInfo").listen("LLFlickrAccountPanel", boost::bind(&LLFlickrAccountPanel::onFlickrConnectInfoChange, this)); + + //Connected + if(LLFlickrConnect::instance().isConnected()) + { + showConnectedLayout(); + } + //Check if connected (show disconnected layout in meantime) + else + { + showDisconnectedLayout(); + } + if ((LLFlickrConnect::instance().getConnectionState() == LLFlickrConnect::FLICKR_NOT_CONNECTED) || + (LLFlickrConnect::instance().getConnectionState() == LLFlickrConnect::FLICKR_CONNECTION_FAILED)) + { + LLFlickrConnect::instance().checkConnectionToFlickr(); + } + } + else + { + LLEventPumps::instance().obtain("FlickrConnectState").stopListening("LLFlickrAccountPanel"); + LLEventPumps::instance().obtain("FlickrConnectInfo").stopListening("LLFlickrAccountPanel"); + } +} + +bool LLFlickrAccountPanel::onFlickrConnectStateChange(const LLSD& data) +{ + if(LLFlickrConnect::instance().isConnected()) + { + //In process of disconnecting so leave the layout as is + if(data.get("enum").asInteger() != LLFlickrConnect::FLICKR_DISCONNECTING) + { + showConnectedLayout(); + } + } + else + { + showDisconnectedLayout(); + } + + return false; +} + +bool LLFlickrAccountPanel::onFlickrConnectInfoChange() +{ + LLSD info = LLFlickrConnect::instance().getInfo(); + std::string clickable_name; + + //Strings of format [http://www.somewebsite.com Click Me] become clickable text + if(info.has("link") && info.has("name")) + { + clickable_name = "[" + info["link"].asString() + " " + info["name"].asString() + "]"; + } + + mAccountNameLabel->setText(clickable_name); + + return false; +} + +void LLFlickrAccountPanel::showConnectButton() +{ + if(!mConnectButton->getVisible()) + { + mConnectButton->setVisible(TRUE); + mDisconnectButton->setVisible(FALSE); + } +} + +void LLFlickrAccountPanel::hideConnectButton() +{ + if(mConnectButton->getVisible()) + { + mConnectButton->setVisible(FALSE); + mDisconnectButton->setVisible(TRUE); + } +} + +void LLFlickrAccountPanel::showDisconnectedLayout() +{ + mAccountCaptionLabel->setText(getString("flickr_disconnected")); + mAccountNameLabel->setText(std::string("")); + showConnectButton(); +} + +void LLFlickrAccountPanel::showConnectedLayout() +{ + LLFlickrConnect::instance().loadFlickrInfo(); + + mAccountCaptionLabel->setText(getString("flickr_connected")); + hideConnectButton(); +} + +void LLFlickrAccountPanel::onConnect() +{ + LLFlickrConnect::instance().checkConnectionToFlickr(true); + + //Clear only the flickr browser cookies so that the flickr login screen appears + LLViewerMedia::getCookieStore()->removeCookiesByDomain(".flickr.com"); +} + +void LLFlickrAccountPanel::onDisconnect() +{ + LLFlickrConnect::instance().disconnectFromFlickr(); + + LLViewerMedia::getCookieStore()->removeCookiesByDomain(".flickr.com"); +} + +//////////////////////// +//LLFloaterFlickr/////// +//////////////////////// + +LLFloaterFlickr::LLFloaterFlickr(const LLSD& key) : LLFloater(key), + mFlickrPhotoPanel(NULL), + mStatusErrorText(NULL), + mStatusLoadingText(NULL), + mStatusLoadingIndicator(NULL) +{ + mCommitCallbackRegistrar.add("SocialSharing.Cancel", boost::bind(&LLFloaterFlickr::onCancel, this)); +} + +void LLFloaterFlickr::onCancel() +{ + closeFloater(); +} + +BOOL LLFloaterFlickr::postBuild() +{ + // Keep tab of the Photo Panel + mFlickrPhotoPanel = static_cast<LLFlickrPhotoPanel*>(getChild<LLUICtrl>("panel_flickr_photo")); + // Connection status widgets + mStatusErrorText = getChild<LLTextBox>("connection_error_text"); + mStatusLoadingText = getChild<LLTextBox>("connection_loading_text"); + mStatusLoadingIndicator = getChild<LLUICtrl>("connection_loading_indicator"); + return LLFloater::postBuild(); +} + +void LLFloaterFlickr::showPhotoPanel() +{ + LLTabContainer* parent = dynamic_cast<LLTabContainer*>(mFlickrPhotoPanel->getParent()); + if (!parent) + { + llwarns << "Cannot find panel container" << llendl; + return; + } + + parent->selectTabPanel(mFlickrPhotoPanel); +} + +void LLFloaterFlickr::draw() +{ + if (mStatusErrorText && mStatusLoadingText && mStatusLoadingIndicator) + { + mStatusErrorText->setVisible(false); + mStatusLoadingText->setVisible(false); + mStatusLoadingIndicator->setVisible(false); + LLFlickrConnect::EConnectionState connection_state = LLFlickrConnect::instance().getConnectionState(); + std::string status_text; + + switch (connection_state) + { + case LLFlickrConnect::FLICKR_NOT_CONNECTED: + // No status displayed when first opening the panel and no connection done + case LLFlickrConnect::FLICKR_CONNECTED: + // When successfully connected, no message is displayed + case LLFlickrConnect::FLICKR_POSTED: + // No success message to show since we actually close the floater after successful posting completion + break; + case LLFlickrConnect::FLICKR_CONNECTION_IN_PROGRESS: + // Connection loading indicator + mStatusLoadingText->setVisible(true); + status_text = LLTrans::getString("SocialFlickrConnecting"); + mStatusLoadingText->setValue(status_text); + mStatusLoadingIndicator->setVisible(true); + break; + case LLFlickrConnect::FLICKR_POSTING: + // Posting indicator + mStatusLoadingText->setVisible(true); + status_text = LLTrans::getString("SocialFlickrPosting"); + mStatusLoadingText->setValue(status_text); + mStatusLoadingIndicator->setVisible(true); + break; + case LLFlickrConnect::FLICKR_CONNECTION_FAILED: + // Error connecting to the service + mStatusErrorText->setVisible(true); + status_text = LLTrans::getString("SocialFlickrErrorConnecting"); + mStatusErrorText->setValue(status_text); + break; + case LLFlickrConnect::FLICKR_POST_FAILED: + // Error posting to the service + mStatusErrorText->setVisible(true); + status_text = LLTrans::getString("SocialFlickrErrorPosting"); + mStatusErrorText->setValue(status_text); + break; + case LLFlickrConnect::FLICKR_DISCONNECTING: + // Disconnecting loading indicator + mStatusLoadingText->setVisible(true); + status_text = LLTrans::getString("SocialFlickrDisconnecting"); + mStatusLoadingText->setValue(status_text); + mStatusLoadingIndicator->setVisible(true); + break; + case LLFlickrConnect::FLICKR_DISCONNECT_FAILED: + // Error disconnecting from the service + mStatusErrorText->setVisible(true); + status_text = LLTrans::getString("SocialFlickrErrorDisconnecting"); + mStatusErrorText->setValue(status_text); + break; + } + } + LLFloater::draw(); +} + diff --git a/indra/newview/llfloaterflickr.h b/indra/newview/llfloaterflickr.h new file mode 100644 index 0000000000..319ab1278f --- /dev/null +++ b/indra/newview/llfloaterflickr.h @@ -0,0 +1,126 @@ +/** +* @file llfloaterflickr.h +* @brief Header file for llfloaterflickr +* @author cho@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLFLOATERFLICKR_H +#define LL_LLFLOATERFLICKR_H + +#include "llfloater.h" +#include "lltextbox.h" +#include "llviewertexture.h" + +class LLIconCtrl; +class LLCheckBoxCtrl; +class LLSnapshotLivePreview; + +class LLFlickrPhotoPanel : public LLPanel +{ +public: + LLFlickrPhotoPanel(); + ~LLFlickrPhotoPanel(); + + BOOL postBuild(); + S32 notify(const LLSD& info); + void draw(); + + LLSnapshotLivePreview* getPreviewView(); + void onVisibilityChange(const LLSD& new_visibility); + void onClickNewSnapshot(); + void onSend(); + bool onFlickrConnectStateChange(const LLSD& data); + + void sendPhoto(); + void clearAndClose(); + + void updateControls(); + void updateResolution(BOOL do_update); + void checkAspectRatio(S32 index); + LLUICtrl* getRefreshBtn(); + +private: + LLHandle<LLView> mPreviewHandle; + + LLUICtrl * mSnapshotPanel; + LLUICtrl * mResolutionComboBox; + LLUICtrl * mFilterComboBox; + LLUICtrl * mRefreshBtn; + LLUICtrl * mWorkingLabel; + LLUICtrl * mThumbnailPlaceholder; + LLUICtrl * mTitleTextBox; + LLUICtrl * mDescriptionTextBox; + LLUICtrl * mLocationCheckbox; + LLUICtrl * mTagsTextBox; + LLUICtrl * mRatingComboBox; + LLUICtrl * mPostButton; + LLUICtrl * mCancelButton; +}; + +class LLFlickrAccountPanel : public LLPanel +{ +public: + LLFlickrAccountPanel(); + BOOL postBuild(); + void draw(); + +private: + void onVisibilityChange(const LLSD& new_visibility); + bool onFlickrConnectStateChange(const LLSD& data); + bool onFlickrConnectInfoChange(); + void onConnect(); + void onUseAnotherAccount(); + void onDisconnect(); + + void showConnectButton(); + void hideConnectButton(); + void showDisconnectedLayout(); + void showConnectedLayout(); + + LLTextBox * mAccountCaptionLabel; + LLTextBox * mAccountNameLabel; + LLUICtrl * mPanelButtons; + LLUICtrl * mConnectButton; + LLUICtrl * mDisconnectButton; +}; + + +class LLFloaterFlickr : public LLFloater +{ +public: + LLFloaterFlickr(const LLSD& key); + BOOL postBuild(); + void draw(); + void onCancel(); + + void showPhotoPanel(); + +private: + LLFlickrPhotoPanel* mFlickrPhotoPanel; + LLTextBox* mStatusErrorText; + LLTextBox* mStatusLoadingText; + LLUICtrl* mStatusLoadingIndicator; +}; + +#endif // LL_LLFLOATERFLICKR_H + diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp index ea385d7baf..1d52ac5a69 100755 --- a/indra/newview/llfloatersnapshot.cpp +++ b/indra/newview/llfloatersnapshot.cpp @@ -31,7 +31,10 @@ #include "llagent.h" #include "llfacebookconnect.h" #include "llfloaterreg.h" -#include "llfloatersocial.h" +#include "llfloaterfacebook.h" +#include "llfloaterflickr.h" +#include "llfloatertwitter.h" +#include "llimagefiltersmanager.h" #include "llcheckboxctrl.h" #include "llcombobox.h" #include "llpostcard.h" @@ -92,6 +95,7 @@ public: } static void onClickNewSnapshot(void* data); static void onClickAutoSnap(LLUICtrl *ctrl, void* data); + static void onClickFilter(LLUICtrl *ctrl, void* data); //static void onClickAdvanceSnap(LLUICtrl *ctrl, void* data); static void onClickMore(void* data) ; static void onClickUICheck(LLUICtrl *ctrl, void* data); @@ -434,9 +438,8 @@ void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshot* floater) image_res_tb->setVisible(got_snap); if (got_snap) { - LLPointer<LLImageRaw> img = previewp->getEncodedImage(); - image_res_tb->setTextArg("[WIDTH]", llformat("%d", img->getWidth())); - image_res_tb->setTextArg("[HEIGHT]", llformat("%d", img->getHeight())); + image_res_tb->setTextArg("[WIDTH]", llformat("%d", previewp->getEncodedImageWidth())); + image_res_tb->setTextArg("[HEIGHT]", llformat("%d", previewp->getEncodedImageHeight())); } floater->getChild<LLUICtrl>("file_size_label")->setTextArg("[SIZE]", got_snap ? bytes_string : floater->getString("unknown")); @@ -469,12 +472,15 @@ void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshot* floater) default: break; } - - if (previewp) + + if (previewp) { previewp->setSnapshotType(shot_type); previewp->setSnapshotFormat(shot_format); previewp->setSnapshotBufferType(layer_type); + // Filters + //const std::string& filter_name = floater->getChild<LLComboBox>("filters_combobox")->getSimple(); + //previewp->setFilter(filter_name); } LLPanelSnapshot* current_panel = Impl::getActivePanel(floater); @@ -563,6 +569,17 @@ void LLFloaterSnapshot::Impl::onClickAutoSnap(LLUICtrl *ctrl, void* data) } } +// static +void LLFloaterSnapshot::Impl::onClickFilter(LLUICtrl *ctrl, void* data) +{ + LLFloaterSnapshot *view = (LLFloaterSnapshot *)data; + if (view) + { + checkAutoSnapshot(getPreviewView(view)); + updateControls(view); + } +} + void LLFloaterSnapshot::Impl::onClickMore(void* data) { BOOL visible = gSavedSettings.getBOOL("AdvanceSnapshot"); @@ -621,9 +638,9 @@ void LLFloaterSnapshot::Impl::applyKeepAspectCheck(LLFloaterSnapshot* view, BOOL previewp->getSize(w, h) ; updateSpinners(view, previewp, w, h, TRUE); // may change w and h - lldebugs << "updating thumbnail" << llendl; + lldebugs << "updating snapshot" << llendl; previewp->setSize(w, h) ; - previewp->updateSnapshot(FALSE, TRUE); + previewp->updateSnapshot(TRUE); checkAutoSnapshot(previewp, TRUE); } } @@ -820,8 +837,8 @@ void LLFloaterSnapshot::Impl::updateResolution(LLUICtrl* ctrl, void* data, BOOL // hide old preview as the aspect ratio could be wrong checkAutoSnapshot(previewp, FALSE); - lldebugs << "updating thumbnail" << llendl; - getPreviewView(view)->updateSnapshot(FALSE, TRUE); + lldebugs << "updating snapshot" << llendl; + getPreviewView(view)->updateSnapshot(TRUE); if(do_update) { lldebugs << "Will update controls" << llendl; @@ -858,7 +875,6 @@ void LLFloaterSnapshot::Impl::onImageQualityChange(LLFloaterSnapshot* view, S32 { previewp->setSnapshotQuality(quality_val); } - checkAutoSnapshot(previewp, TRUE); } // static @@ -867,8 +883,6 @@ void LLFloaterSnapshot::Impl::onImageFormatChange(LLFloaterSnapshot* view) if (view) { gSavedSettings.setS32("SnapshotFormat", getImageFormat(view)); - lldebugs << "image format changed, updating snapshot" << llendl; - getPreviewView(view)->updateSnapshot(TRUE); updateControls(view); setNeedRefresh(view, false); // we're refreshing } @@ -968,8 +982,8 @@ void LLFloaterSnapshot::Impl::applyCustomResolution(LLFloaterSnapshot* view, S32 previewp->setSize(w,h); checkAutoSnapshot(previewp, FALSE); - lldebugs << "applied custom resolution, updating thumbnail" << llendl; - previewp->updateSnapshot(FALSE, TRUE); + lldebugs << "applied custom resolution, updating snapshot" << llendl; + previewp->updateSnapshot(TRUE); comboSetCustom(view, "profile_size_combo"); comboSetCustom(view, "postcard_size_combo"); comboSetCustom(view, "texture_size_combo"); @@ -1063,6 +1077,16 @@ BOOL LLFloaterSnapshot::postBuild() getChild<LLUICtrl>("auto_snapshot_check")->setValue(gSavedSettings.getBOOL("AutoSnapshot")); childSetCommitCallback("auto_snapshot_check", Impl::onClickAutoSnap, this); + + // Update filter list + //std::vector<std::string> filter_list = LLImageFiltersManager::getInstance()->getFiltersList(); + //LLComboBox* filterbox = getChild<LLComboBox>("filters_combobox"); + //for (U32 i = 0; i < filter_list.size(); i++) + //{ + // filterbox->add(filter_list[i]); + //} + //childSetCommitCallback("filters_combobox", Impl::onClickFilter, this); + LLWebProfile::setImageUploadResultCallback(boost::bind(&LLFloaterSnapshot::Impl::onSnapshotUploadFinished, _1)); LLPostCard::setPostResultCallback(boost::bind(&LLFloaterSnapshot::Impl::onSendingPostcardFinished, _1)); @@ -1093,6 +1117,7 @@ BOOL LLFloaterSnapshot::postBuild() getChild<LLComboBox>("local_format_combo")->selectNthItem(0); impl.mPreviewHandle = previewp->getHandle(); + previewp->setContainer(this); impl.updateControls(this); impl.updateLayout(this); @@ -1257,6 +1282,32 @@ S32 LLFloaterSnapshot::notify(const LLSD& info) impl.setStatus(Impl::STATUS_FINISHED, data["ok"].asBoolean(), data["msg"].asString()); return 1; } + + if (info.has("snapshot-updating")) + { + // Disable the send/post/save buttons until snapshot is ready. + impl.updateControls(this); + // Force hiding the "Refresh to save" hint because we know we've just started refresh. + impl.setNeedRefresh(this, false); + return 1; + } + + if (info.has("snapshot-updated")) + { + // Enable the send/post/save buttons. + impl.updateControls(this); + // We've just done refresh. + impl.setNeedRefresh(this, false); + + // The refresh button is initially hidden. We show it after the first update, + // i.e. when preview appears. + if (!mRefreshBtn->getVisible()) + { + mRefreshBtn->setVisible(true); + } + return 1; + } + return 0; } @@ -1264,9 +1315,11 @@ S32 LLFloaterSnapshot::notify(const LLSD& info) void LLFloaterSnapshot::update() { LLFloaterSnapshot* inst = LLFloaterReg::findTypedInstance<LLFloaterSnapshot>("snapshot"); - LLFloaterSocial* floater_social = LLFloaterReg::findTypedInstance<LLFloaterSocial>("social"); + LLFloaterFacebook* floater_facebook = LLFloaterReg::findTypedInstance<LLFloaterFacebook>("facebook"); + LLFloaterFlickr* floater_flickr = LLFloaterReg::findTypedInstance<LLFloaterFlickr>("flickr"); + LLFloaterTwitter* floater_twitter = LLFloaterReg::findTypedInstance<LLFloaterTwitter>("twitter"); - if (!inst && !floater_social) + if (!inst && !floater_facebook && !floater_flickr && !floater_twitter) return; BOOL changed = FALSE; @@ -1334,43 +1387,6 @@ BOOL LLFloaterSnapshot::saveLocal() } // static -void LLFloaterSnapshot::preUpdate() -{ - // FIXME: duplicated code - LLFloaterSnapshot* instance = LLFloaterReg::findTypedInstance<LLFloaterSnapshot>("snapshot"); - if (instance) - { - // Disable the send/post/save buttons until snapshot is ready. - Impl::updateControls(instance); - - // Force hiding the "Refresh to save" hint because we know we've just started refresh. - Impl::setNeedRefresh(instance, false); - } -} - -// static -void LLFloaterSnapshot::postUpdate() -{ - // FIXME: duplicated code - LLFloaterSnapshot* instance = LLFloaterReg::findTypedInstance<LLFloaterSnapshot>("snapshot"); - if (instance) - { - // Enable the send/post/save buttons. - Impl::updateControls(instance); - - // We've just done refresh. - Impl::setNeedRefresh(instance, false); - - // The refresh button is initially hidden. We show it after the first update, - // i.e. when preview appears. - if (!instance->mRefreshBtn->getVisible()) - { - instance->mRefreshBtn->setVisible(true); - } - } -} - -// static void LLFloaterSnapshot::postSave() { LLFloaterSnapshot* instance = LLFloaterReg::findTypedInstance<LLFloaterSnapshot>("snapshot"); diff --git a/indra/newview/llfloatersnapshot.h b/indra/newview/llfloatersnapshot.h index 82af8c7a9d..3a279c194d 100755 --- a/indra/newview/llfloatersnapshot.h +++ b/indra/newview/llfloatersnapshot.h @@ -58,8 +58,6 @@ public: static LLFloaterSnapshot* getInstance(); static void saveTexture(); static BOOL saveLocal(); - static void preUpdate(); - static void postUpdate(); static void postSave(); static void postPanelSwitch(); static LLPointer<LLImageFormatted> getImageData(); diff --git a/indra/newview/llfloatertwitter.cpp b/indra/newview/llfloatertwitter.cpp new file mode 100644 index 0000000000..e9db6e01dd --- /dev/null +++ b/indra/newview/llfloatertwitter.cpp @@ -0,0 +1,765 @@ +/** +* @file llfloatertwitter.cpp +* @brief Implementation of llfloatertwitter +* @author cho@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatertwitter.h" + +#include "llagent.h" +#include "llagentui.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "lltwitterconnect.h" +#include "llfloaterreg.h" +#include "lliconctrl.h" +#include "llimagefiltersmanager.h" +#include "llresmgr.h" // LLLocale +#include "llsdserialize.h" +#include "llloadingindicator.h" +#include "llplugincookiestore.h" +#include "llslurl.h" +#include "lltrans.h" +#include "llsnapshotlivepreview.h" +#include "llviewerregion.h" +#include "llviewercontrol.h" +#include "llviewermedia.h" +#include "lltabcontainer.h" +#include "lltexteditor.h" + +static LLRegisterPanelClassWrapper<LLTwitterPhotoPanel> t_panel_photo("lltwitterphotopanel"); +static LLRegisterPanelClassWrapper<LLTwitterAccountPanel> t_panel_account("lltwitteraccountpanel"); + +const S32 MAX_POSTCARD_DATASIZE = 1024 * 1024; // one megabyte +const std::string DEFAULT_PHOTO_LOCATION_URL = "http://maps.secondlife.com/"; +const std::string DEFAULT_PHOTO_QUERY_PARAMETERS = "?sourceid=slshare_photo&utm_source=twitter&utm_medium=photo&utm_campaign=slshare"; +const std::string DEFAULT_STATUS_TEXT = " #SecondLife"; + +/////////////////////////// +//LLTwitterPhotoPanel/////// +/////////////////////////// + +LLTwitterPhotoPanel::LLTwitterPhotoPanel() : +mSnapshotPanel(NULL), +mResolutionComboBox(NULL), +mRefreshBtn(NULL), +mWorkingLabel(NULL), +mThumbnailPlaceholder(NULL), +mStatusCounterLabel(NULL), +mStatusTextBox(NULL), +mLocationCheckbox(NULL), +mPhotoCheckbox(NULL), +mPostButton(NULL) +{ + mCommitCallbackRegistrar.add("SocialSharing.SendPhoto", boost::bind(&LLTwitterPhotoPanel::onSend, this)); + mCommitCallbackRegistrar.add("SocialSharing.RefreshPhoto", boost::bind(&LLTwitterPhotoPanel::onClickNewSnapshot, this)); +} + +LLTwitterPhotoPanel::~LLTwitterPhotoPanel() +{ + if(mPreviewHandle.get()) + { + mPreviewHandle.get()->die(); + } +} + +BOOL LLTwitterPhotoPanel::postBuild() +{ + setVisibleCallback(boost::bind(&LLTwitterPhotoPanel::onVisibilityChange, this, _2)); + + mSnapshotPanel = getChild<LLUICtrl>("snapshot_panel"); + mResolutionComboBox = getChild<LLUICtrl>("resolution_combobox"); + mResolutionComboBox->setValue("[i800,i600]"); // hardcoded defaults ftw! + mResolutionComboBox->setCommitCallback(boost::bind(&LLTwitterPhotoPanel::updateResolution, this, TRUE)); + mFilterComboBox = getChild<LLUICtrl>("filters_combobox"); + mFilterComboBox->setCommitCallback(boost::bind(&LLTwitterPhotoPanel::updateResolution, this, TRUE)); + mRefreshBtn = getChild<LLUICtrl>("new_snapshot_btn"); + mWorkingLabel = getChild<LLUICtrl>("working_lbl"); + mThumbnailPlaceholder = getChild<LLUICtrl>("thumbnail_placeholder"); + mStatusCounterLabel = getChild<LLUICtrl>("status_counter_label"); + mStatusTextBox = getChild<LLUICtrl>("photo_status"); + mStatusTextBox->setValue(DEFAULT_STATUS_TEXT); + mLocationCheckbox = getChild<LLUICtrl>("add_location_cb"); + mLocationCheckbox->setCommitCallback(boost::bind(&LLTwitterPhotoPanel::onAddLocationToggled, this)); + mPhotoCheckbox = getChild<LLUICtrl>("add_photo_cb"); + mPhotoCheckbox->setCommitCallback(boost::bind(&LLTwitterPhotoPanel::onAddPhotoToggled, this)); + mPostButton = getChild<LLUICtrl>("post_photo_btn"); + mCancelButton = getChild<LLUICtrl>("cancel_photo_btn"); + + // Update filter list + std::vector<std::string> filter_list = LLImageFiltersManager::getInstance()->getFiltersList(); + LLComboBox* filterbox = static_cast<LLComboBox *>(mFilterComboBox); + for (U32 i = 0; i < filter_list.size(); i++) + { + filterbox->add(filter_list[i]); + } + + return LLPanel::postBuild(); +} + +// virtual +S32 LLTwitterPhotoPanel::notify(const LLSD& info) +{ + if (info.has("snapshot-updating")) + { + // Disable the Post button and whatever else while the snapshot is not updated + // updateControls(); + return 1; + } + + if (info.has("snapshot-updated")) + { + // Enable the send/post/save buttons. + updateControls(); + + // The refresh button is initially hidden. We show it after the first update, + // i.e. after snapshot is taken + LLUICtrl * refresh_button = getRefreshBtn(); + if (!refresh_button->getVisible()) + { + refresh_button->setVisible(true); + } + return 1; + } + + return 0; +} + +void LLTwitterPhotoPanel::draw() +{ + LLSnapshotLivePreview * previewp = static_cast<LLSnapshotLivePreview *>(mPreviewHandle.get()); + + // Enable interaction only if no transaction with the service is on-going (prevent duplicated posts) + bool no_ongoing_connection = !(LLTwitterConnect::instance().isTransactionOngoing()); + bool photo_checked = mPhotoCheckbox->getValue().asBoolean(); + mCancelButton->setEnabled(no_ongoing_connection); + mStatusTextBox->setEnabled(no_ongoing_connection); + mResolutionComboBox->setEnabled(no_ongoing_connection && photo_checked); + mFilterComboBox->setEnabled(no_ongoing_connection && photo_checked); + mRefreshBtn->setEnabled(no_ongoing_connection && photo_checked); + mLocationCheckbox->setEnabled(no_ongoing_connection); + mPhotoCheckbox->setEnabled(no_ongoing_connection); + + bool add_location = mLocationCheckbox->getValue().asBoolean(); + bool add_photo = mPhotoCheckbox->getValue().asBoolean(); + updateStatusTextLength(false); + + // Display the preview if one is available + if (previewp && previewp->getThumbnailImage()) + { + const LLRect& thumbnail_rect = mThumbnailPlaceholder->getRect(); + const S32 thumbnail_w = previewp->getThumbnailWidth(); + const S32 thumbnail_h = previewp->getThumbnailHeight(); + + // calc preview offset within the preview rect + const S32 local_offset_x = (thumbnail_rect.getWidth() - thumbnail_w) / 2 ; + const S32 local_offset_y = (thumbnail_rect.getHeight() - thumbnail_h) / 2 ; + + // calc preview offset within the floater rect + // Hack : To get the full offset, we need to take into account each and every offset of each widgets up to the floater. + // This is almost as arbitrary as using a fixed offset so that's what we do here for the sake of simplicity. + // *TODO : Get the offset looking through the hierarchy of widgets, should be done in postBuild() so to avoid traversing the hierarchy each time. + S32 offset_x = thumbnail_rect.mLeft + local_offset_x - 1; + S32 offset_y = thumbnail_rect.mBottom + local_offset_y - 39; + + mSnapshotPanel->localPointToOtherView(offset_x, offset_y, &offset_x, &offset_y, getParentByType<LLFloater>()); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + // Apply floater transparency to the texture unless the floater is focused. + F32 alpha = (add_photo ? (getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency()) : 0.5f); + LLColor4 color = LLColor4::white; + gl_draw_scaled_image(offset_x, offset_y, + thumbnail_w, thumbnail_h, + previewp->getThumbnailImage(), color % alpha); + } + + // Update the visibility of the working (computing preview) label + mWorkingLabel->setVisible(!(previewp && previewp->getSnapshotUpToDate())); + + // Enable Post if we have a preview to send and no on going connection being processed + mPostButton->setEnabled(no_ongoing_connection && (previewp && previewp->getSnapshotUpToDate()) && (add_photo || add_location || !mStatusTextBox->getValue().asString().empty())); + + // Draw the rest of the panel on top of it + LLPanel::draw(); +} + +LLSnapshotLivePreview* LLTwitterPhotoPanel::getPreviewView() +{ + LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)mPreviewHandle.get(); + return previewp; +} + +void LLTwitterPhotoPanel::onVisibilityChange(const LLSD& new_visibility) +{ + bool visible = new_visibility.asBoolean(); + if (visible) + { + if (mPreviewHandle.get()) + { + LLSnapshotLivePreview* preview = getPreviewView(); + if(preview) + { + lldebugs << "opened, updating snapshot" << llendl; + preview->updateSnapshot(TRUE); + } + } + else + { + LLRect full_screen_rect = getRootView()->getRect(); + LLSnapshotLivePreview::Params p; + p.rect(full_screen_rect); + LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); + mPreviewHandle = previewp->getHandle(); + + previewp->setContainer(this); + previewp->setSnapshotType(previewp->SNAPSHOT_WEB); + previewp->setSnapshotFormat(LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG); + previewp->setThumbnailSubsampled(TRUE); // We want the preview to reflect the *saved* image + previewp->setAllowRenderUI(FALSE); // We do not want the rendered UI in our snapshots + previewp->setAllowFullScreenPreview(FALSE); // No full screen preview in SL Share mode + previewp->setThumbnailPlaceholderRect(mThumbnailPlaceholder->getRect()); + + updateControls(); + } + } +} + +void LLTwitterPhotoPanel::onAddLocationToggled() +{ + bool add_location = mLocationCheckbox->getValue().asBoolean(); + updateStatusTextLength(!add_location); +} + +void LLTwitterPhotoPanel::onAddPhotoToggled() +{ + bool add_photo = mPhotoCheckbox->getValue().asBoolean(); + updateStatusTextLength(!add_photo); +} + +void LLTwitterPhotoPanel::onClickNewSnapshot() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + if (previewp) + { + previewp->updateSnapshot(TRUE); + } +} + +void LLTwitterPhotoPanel::onSend() +{ + LLEventPumps::instance().obtain("TwitterConnectState").stopListening("LLTwitterPhotoPanel"); // just in case it is already listening + LLEventPumps::instance().obtain("TwitterConnectState").listen("LLTwitterPhotoPanel", boost::bind(&LLTwitterPhotoPanel::onTwitterConnectStateChange, this, _1)); + + // Connect to Twitter if necessary and then post + if (LLTwitterConnect::instance().isConnected()) + { + sendPhoto(); + } + else + { + LLTwitterConnect::instance().checkConnectionToTwitter(true); + } +} + +bool LLTwitterPhotoPanel::onTwitterConnectStateChange(const LLSD& data) +{ + switch (data.get("enum").asInteger()) + { + case LLTwitterConnect::TWITTER_CONNECTED: + sendPhoto(); + break; + + case LLTwitterConnect::TWITTER_POSTED: + LLEventPumps::instance().obtain("TwitterConnectState").stopListening("LLTwitterPhotoPanel"); + clearAndClose(); + break; + } + + return false; +} + +void LLTwitterPhotoPanel::sendPhoto() +{ + // Get the status text + std::string status = mStatusTextBox->getValue().asString(); + + // Add the location if required + bool add_location = mLocationCheckbox->getValue().asBoolean(); + if (add_location) + { + // Get the SLURL for the location + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + std::string slurl_string = slurl.getSLURLString(); + + // Use a valid http:// URL if the scheme is secondlife:// + LLURI slurl_uri(slurl_string); + if (slurl_uri.scheme() == LLSLURL::SLURL_SECONDLIFE_SCHEME) + { + slurl_string = DEFAULT_PHOTO_LOCATION_URL; + } + + // Add query parameters so Google Analytics can track incoming clicks! + slurl_string += DEFAULT_PHOTO_QUERY_PARAMETERS; + + // Add it to the status (pretty crude, but we don't have a better option with photos) + if (status.empty()) + status = slurl_string; + else + status = status + " " + slurl_string; + } + + // Add the photo if required + bool add_photo = mPhotoCheckbox->getValue().asBoolean(); + if (add_photo) + { + // Get the image + LLSnapshotLivePreview* previewp = getPreviewView(); + + // Post to Twitter + LLTwitterConnect::instance().uploadPhoto(previewp->getFormattedImage(), status); + } + else + { + // Just post the status to Twitter + LLTwitterConnect::instance().updateStatus(status); + } + + updateControls(); +} + +void LLTwitterPhotoPanel::clearAndClose() +{ + mStatusTextBox->setValue(DEFAULT_STATUS_TEXT); + + LLFloater* floater = getParentByType<LLFloater>(); + if (floater) + { + floater->closeFloater(); + } +} + +void LLTwitterPhotoPanel::updateStatusTextLength(BOOL restore_old_status_text) +{ + bool add_location = mLocationCheckbox->getValue().asBoolean(); + bool add_photo = mPhotoCheckbox->getValue().asBoolean(); + + // Restrict the status text length to Twitter's character limit + LLTextEditor* status_text_box = dynamic_cast<LLTextEditor*>(mStatusTextBox); + if (status_text_box) + { + int max_status_length = 140 - (add_location ? 40 : 0) - (add_photo ? 40 : 0); + status_text_box->setMaxTextLength(max_status_length); + if (restore_old_status_text) + { + if (mOldStatusText.length() > status_text_box->getText().length() && status_text_box->getText() == mOldStatusText.substr(0, status_text_box->getText().length())) + { + status_text_box->setText(mOldStatusText); + } + if (mOldStatusText.length() <= max_status_length) + { + mOldStatusText = ""; + } + } + if (status_text_box->getText().length() > max_status_length) + { + if (mOldStatusText.length() < status_text_box->getText().length() || status_text_box->getText() != mOldStatusText.substr(0, status_text_box->getText().length())) + { + mOldStatusText = status_text_box->getText(); + } + status_text_box->setText(mOldStatusText.substr(0, max_status_length)); + } + + // Update the status character counter + int characters_remaining = max_status_length - status_text_box->getText().length(); + mStatusCounterLabel->setValue(characters_remaining); + } + +} + +void LLTwitterPhotoPanel::updateControls() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + BOOL got_snap = previewp && previewp->getSnapshotUpToDate(); + + // *TODO: Separate maximum size for Web images from postcards + lldebugs << "Is snapshot up-to-date? " << got_snap << llendl; + + updateResolution(FALSE); +} + +void LLTwitterPhotoPanel::updateResolution(BOOL do_update) +{ + LLComboBox* combobox = static_cast<LLComboBox *>(mResolutionComboBox); + LLComboBox* filterbox = static_cast<LLComboBox *>(mFilterComboBox); + + std::string sdstring = combobox->getSelectedValue(); + LLSD sdres; + std::stringstream sstream(sdstring); + LLSDSerialize::fromNotation(sdres, sstream, sdstring.size()); + + S32 width = sdres[0]; + S32 height = sdres[1]; + + // Note : index 0 of the filter drop down is assumed to be "No filter" in whichever locale + std::string filter_name = (filterbox->getCurrentIndex() ? filterbox->getSimple() : ""); + + LLSnapshotLivePreview * previewp = static_cast<LLSnapshotLivePreview *>(mPreviewHandle.get()); + if (previewp && combobox->getCurrentIndex() >= 0) + { + S32 original_width = 0 , original_height = 0 ; + previewp->getSize(original_width, original_height) ; + + if (width == 0 || height == 0) + { + // take resolution from current window size + lldebugs << "Setting preview res from window: " << gViewerWindow->getWindowWidthRaw() << "x" << gViewerWindow->getWindowHeightRaw() << llendl; + previewp->setSize(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()); + } + else + { + // use the resolution from the selected pre-canned drop-down choice + lldebugs << "Setting preview res selected from combo: " << width << "x" << height << llendl; + previewp->setSize(width, height); + } + + checkAspectRatio(width); + + previewp->getSize(width, height); + + if (original_width != width || original_height != height) + { + previewp->setSize(width, height); + if (do_update) + { + previewp->updateSnapshot(TRUE); + updateControls(); + } + } + // Get the old filter, compare to the current one "filter_name" and set if changed + std::string original_filter = previewp->getFilter(); + if (original_filter != filter_name) + { + previewp->setFilter(filter_name); + if (do_update) + { + previewp->updateSnapshot(FALSE, TRUE); + updateControls(); + } + } + } +} + +void LLTwitterPhotoPanel::checkAspectRatio(S32 index) +{ + LLSnapshotLivePreview *previewp = getPreviewView() ; + + BOOL keep_aspect = FALSE; + + if (0 == index) // current window size + { + keep_aspect = TRUE; + } + else // predefined resolution + { + keep_aspect = FALSE; + } + + if (previewp) + { + previewp->mKeepAspectRatio = keep_aspect; + } +} + +LLUICtrl* LLTwitterPhotoPanel::getRefreshBtn() +{ + return mRefreshBtn; +} + +/////////////////////////// +//LLTwitterAccountPanel////// +/////////////////////////// + +LLTwitterAccountPanel::LLTwitterAccountPanel() : +mAccountCaptionLabel(NULL), +mAccountNameLabel(NULL), +mPanelButtons(NULL), +mConnectButton(NULL), +mDisconnectButton(NULL) +{ + mCommitCallbackRegistrar.add("SocialSharing.Connect", boost::bind(&LLTwitterAccountPanel::onConnect, this)); + mCommitCallbackRegistrar.add("SocialSharing.Disconnect", boost::bind(&LLTwitterAccountPanel::onDisconnect, this)); + + setVisibleCallback(boost::bind(&LLTwitterAccountPanel::onVisibilityChange, this, _2)); +} + +BOOL LLTwitterAccountPanel::postBuild() +{ + mAccountCaptionLabel = getChild<LLTextBox>("account_caption_label"); + mAccountNameLabel = getChild<LLTextBox>("account_name_label"); + mPanelButtons = getChild<LLUICtrl>("panel_buttons"); + mConnectButton = getChild<LLUICtrl>("connect_btn"); + mDisconnectButton = getChild<LLUICtrl>("disconnect_btn"); + + return LLPanel::postBuild(); +} + +void LLTwitterAccountPanel::draw() +{ + LLTwitterConnect::EConnectionState connection_state = LLTwitterConnect::instance().getConnectionState(); + + //Disable the 'disconnect' button and the 'use another account' button when disconnecting in progress + bool disconnecting = connection_state == LLTwitterConnect::TWITTER_DISCONNECTING; + mDisconnectButton->setEnabled(!disconnecting); + + //Disable the 'connect' button when a connection is in progress + bool connecting = connection_state == LLTwitterConnect::TWITTER_CONNECTION_IN_PROGRESS; + mConnectButton->setEnabled(!connecting); + + LLPanel::draw(); +} + +void LLTwitterAccountPanel::onVisibilityChange(const LLSD& new_visibility) +{ + bool visible = new_visibility.asBoolean(); + + if(visible) + { + LLEventPumps::instance().obtain("TwitterConnectState").stopListening("LLTwitterAccountPanel"); + LLEventPumps::instance().obtain("TwitterConnectState").listen("LLTwitterAccountPanel", boost::bind(&LLTwitterAccountPanel::onTwitterConnectStateChange, this, _1)); + + LLEventPumps::instance().obtain("TwitterConnectInfo").stopListening("LLTwitterAccountPanel"); + LLEventPumps::instance().obtain("TwitterConnectInfo").listen("LLTwitterAccountPanel", boost::bind(&LLTwitterAccountPanel::onTwitterConnectInfoChange, this)); + + //Connected + if(LLTwitterConnect::instance().isConnected()) + { + showConnectedLayout(); + } + //Check if connected (show disconnected layout in meantime) + else + { + showDisconnectedLayout(); + } + if ((LLTwitterConnect::instance().getConnectionState() == LLTwitterConnect::TWITTER_NOT_CONNECTED) || + (LLTwitterConnect::instance().getConnectionState() == LLTwitterConnect::TWITTER_CONNECTION_FAILED)) + { + LLTwitterConnect::instance().checkConnectionToTwitter(); + } + } + else + { + LLEventPumps::instance().obtain("TwitterConnectState").stopListening("LLTwitterAccountPanel"); + LLEventPumps::instance().obtain("TwitterConnectInfo").stopListening("LLTwitterAccountPanel"); + } +} + +bool LLTwitterAccountPanel::onTwitterConnectStateChange(const LLSD& data) +{ + if(LLTwitterConnect::instance().isConnected()) + { + //In process of disconnecting so leave the layout as is + if(data.get("enum").asInteger() != LLTwitterConnect::TWITTER_DISCONNECTING) + { + showConnectedLayout(); + } + } + else + { + showDisconnectedLayout(); + } + + return false; +} + +bool LLTwitterAccountPanel::onTwitterConnectInfoChange() +{ + LLSD info = LLTwitterConnect::instance().getInfo(); + std::string clickable_name; + + //Strings of format [http://www.somewebsite.com Click Me] become clickable text + if(info.has("link") && info.has("name")) + { + clickable_name = "[" + info["link"].asString() + " " + info["name"].asString() + "]"; + } + + mAccountNameLabel->setText(clickable_name); + + return false; +} + +void LLTwitterAccountPanel::showConnectButton() +{ + if(!mConnectButton->getVisible()) + { + mConnectButton->setVisible(TRUE); + mDisconnectButton->setVisible(FALSE); + } +} + +void LLTwitterAccountPanel::hideConnectButton() +{ + if(mConnectButton->getVisible()) + { + mConnectButton->setVisible(FALSE); + mDisconnectButton->setVisible(TRUE); + } +} + +void LLTwitterAccountPanel::showDisconnectedLayout() +{ + mAccountCaptionLabel->setText(getString("twitter_disconnected")); + mAccountNameLabel->setText(std::string("")); + showConnectButton(); +} + +void LLTwitterAccountPanel::showConnectedLayout() +{ + LLTwitterConnect::instance().loadTwitterInfo(); + + mAccountCaptionLabel->setText(getString("twitter_connected")); + hideConnectButton(); +} + +void LLTwitterAccountPanel::onConnect() +{ + LLTwitterConnect::instance().checkConnectionToTwitter(true); + + //Clear only the twitter browser cookies so that the twitter login screen appears + LLViewerMedia::getCookieStore()->removeCookiesByDomain(".twitter.com"); +} + +void LLTwitterAccountPanel::onDisconnect() +{ + LLTwitterConnect::instance().disconnectFromTwitter(); + + LLViewerMedia::getCookieStore()->removeCookiesByDomain(".twitter.com"); +} + +//////////////////////// +//LLFloaterTwitter/////// +//////////////////////// + +LLFloaterTwitter::LLFloaterTwitter(const LLSD& key) : LLFloater(key), + mTwitterPhotoPanel(NULL), + mStatusErrorText(NULL), + mStatusLoadingText(NULL), + mStatusLoadingIndicator(NULL) +{ + mCommitCallbackRegistrar.add("SocialSharing.Cancel", boost::bind(&LLFloaterTwitter::onCancel, this)); +} + +void LLFloaterTwitter::onCancel() +{ + closeFloater(); +} + +BOOL LLFloaterTwitter::postBuild() +{ + // Keep tab of the Photo Panel + mTwitterPhotoPanel = static_cast<LLTwitterPhotoPanel*>(getChild<LLUICtrl>("panel_twitter_photo")); + // Connection status widgets + mStatusErrorText = getChild<LLTextBox>("connection_error_text"); + mStatusLoadingText = getChild<LLTextBox>("connection_loading_text"); + mStatusLoadingIndicator = getChild<LLUICtrl>("connection_loading_indicator"); + return LLFloater::postBuild(); +} + +void LLFloaterTwitter::showPhotoPanel() +{ + LLTabContainer* parent = dynamic_cast<LLTabContainer*>(mTwitterPhotoPanel->getParent()); + if (!parent) + { + llwarns << "Cannot find panel container" << llendl; + return; + } + + parent->selectTabPanel(mTwitterPhotoPanel); +} + +void LLFloaterTwitter::draw() +{ + if (mStatusErrorText && mStatusLoadingText && mStatusLoadingIndicator) + { + mStatusErrorText->setVisible(false); + mStatusLoadingText->setVisible(false); + mStatusLoadingIndicator->setVisible(false); + LLTwitterConnect::EConnectionState connection_state = LLTwitterConnect::instance().getConnectionState(); + std::string status_text; + + switch (connection_state) + { + case LLTwitterConnect::TWITTER_NOT_CONNECTED: + // No status displayed when first opening the panel and no connection done + case LLTwitterConnect::TWITTER_CONNECTED: + // When successfully connected, no message is displayed + case LLTwitterConnect::TWITTER_POSTED: + // No success message to show since we actually close the floater after successful posting completion + break; + case LLTwitterConnect::TWITTER_CONNECTION_IN_PROGRESS: + // Connection loading indicator + mStatusLoadingText->setVisible(true); + status_text = LLTrans::getString("SocialTwitterConnecting"); + mStatusLoadingText->setValue(status_text); + mStatusLoadingIndicator->setVisible(true); + break; + case LLTwitterConnect::TWITTER_POSTING: + // Posting indicator + mStatusLoadingText->setVisible(true); + status_text = LLTrans::getString("SocialTwitterPosting"); + mStatusLoadingText->setValue(status_text); + mStatusLoadingIndicator->setVisible(true); + break; + case LLTwitterConnect::TWITTER_CONNECTION_FAILED: + // Error connecting to the service + mStatusErrorText->setVisible(true); + status_text = LLTrans::getString("SocialTwitterErrorConnecting"); + mStatusErrorText->setValue(status_text); + break; + case LLTwitterConnect::TWITTER_POST_FAILED: + // Error posting to the service + mStatusErrorText->setVisible(true); + status_text = LLTrans::getString("SocialTwitterErrorPosting"); + mStatusErrorText->setValue(status_text); + break; + case LLTwitterConnect::TWITTER_DISCONNECTING: + // Disconnecting loading indicator + mStatusLoadingText->setVisible(true); + status_text = LLTrans::getString("SocialTwitterDisconnecting"); + mStatusLoadingText->setValue(status_text); + mStatusLoadingIndicator->setVisible(true); + break; + case LLTwitterConnect::TWITTER_DISCONNECT_FAILED: + // Error disconnecting from the service + mStatusErrorText->setVisible(true); + status_text = LLTrans::getString("SocialTwitterErrorDisconnecting"); + mStatusErrorText->setValue(status_text); + break; + } + } + LLFloater::draw(); +} + diff --git a/indra/newview/llfloatertwitter.h b/indra/newview/llfloatertwitter.h new file mode 100644 index 0000000000..bb88557ad8 --- /dev/null +++ b/indra/newview/llfloatertwitter.h @@ -0,0 +1,130 @@ +/** +* @file llfloatertwitter.h +* @brief Header file for llfloatertwitter +* @author cho@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLFLOATERTWITTER_H +#define LL_LLFLOATERTWITTER_H + +#include "llfloater.h" +#include "lltextbox.h" +#include "llviewertexture.h" + +class LLIconCtrl; +class LLCheckBoxCtrl; +class LLSnapshotLivePreview; + +class LLTwitterPhotoPanel : public LLPanel +{ +public: + LLTwitterPhotoPanel(); + ~LLTwitterPhotoPanel(); + + BOOL postBuild(); + void draw(); + + LLSnapshotLivePreview* getPreviewView(); + void onVisibilityChange(const LLSD& new_visibility); + void onAddLocationToggled(); + void onAddPhotoToggled(); + void onClickNewSnapshot(); + void onSend(); + S32 notify(const LLSD& info); + bool onTwitterConnectStateChange(const LLSD& data); + + void sendPhoto(); + void clearAndClose(); + + void updateStatusTextLength(BOOL restore_old_status_text); + void updateControls(); + void updateResolution(BOOL do_update); + void checkAspectRatio(S32 index); + LLUICtrl* getRefreshBtn(); + +private: + LLHandle<LLView> mPreviewHandle; + + LLUICtrl * mSnapshotPanel; + LLUICtrl * mResolutionComboBox; + LLUICtrl * mFilterComboBox; + LLUICtrl * mRefreshBtn; + LLUICtrl * mWorkingLabel; + LLUICtrl * mThumbnailPlaceholder; + LLUICtrl * mStatusCounterLabel; + LLUICtrl * mStatusTextBox; + LLUICtrl * mLocationCheckbox; + LLUICtrl * mPhotoCheckbox; + LLUICtrl * mPostButton; + LLUICtrl * mCancelButton; + + std::string mOldStatusText; +}; + +class LLTwitterAccountPanel : public LLPanel +{ +public: + LLTwitterAccountPanel(); + BOOL postBuild(); + void draw(); + +private: + void onVisibilityChange(const LLSD& new_visibility); + bool onTwitterConnectStateChange(const LLSD& data); + bool onTwitterConnectInfoChange(); + void onConnect(); + void onUseAnotherAccount(); + void onDisconnect(); + + void showConnectButton(); + void hideConnectButton(); + void showDisconnectedLayout(); + void showConnectedLayout(); + + LLTextBox * mAccountCaptionLabel; + LLTextBox * mAccountNameLabel; + LLUICtrl * mPanelButtons; + LLUICtrl * mConnectButton; + LLUICtrl * mDisconnectButton; +}; + + +class LLFloaterTwitter : public LLFloater +{ +public: + LLFloaterTwitter(const LLSD& key); + BOOL postBuild(); + void draw(); + void onCancel(); + + void showPhotoPanel(); + +private: + LLTwitterPhotoPanel* mTwitterPhotoPanel; + LLTextBox* mStatusErrorText; + LLTextBox* mStatusLoadingText; + LLUICtrl* mStatusLoadingIndicator; +}; + +#endif // LL_LLFLOATERTWITTER_H + diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp index 0106a1615d..44649e6ca8 100755 --- a/indra/newview/llfloateruipreview.cpp +++ b/indra/newview/llfloateruipreview.cpp @@ -1020,12 +1020,11 @@ void LLFloaterUIPreview::onClickEditFloater() // Respond to button click to browse for an executable with which to edit XML files void LLFloaterUIPreview::onClickBrowseForEditor() { - // create load dialog box - LLFilePicker::ELoadFilter type = (LLFilePicker::ELoadFilter)((intptr_t)((void*)LLFilePicker::FFLOAD_ALL)); // nothing for *.exe so just use all + // Let the user choose an executable through the file picker dialog box LLFilePicker& picker = LLFilePicker::instance(); - if (!picker.getOpenFile(type)) // user cancelled -- do nothing + if (!picker.getOpenFile(LLFilePicker::FFLOAD_EXE)) { - return; + return; // user cancelled -- do nothing } // put the selected path into text field diff --git a/indra/newview/llfloaterwebcontent.cpp b/indra/newview/llfloaterwebcontent.cpp index 68dbb5ae33..8830accbf2 100755 --- a/indra/newview/llfloaterwebcontent.cpp +++ b/indra/newview/llfloaterwebcontent.cpp @@ -30,6 +30,8 @@ #include "lliconctrl.h" #include "llfloaterreg.h" #include "llfacebookconnect.h" +#include "llflickrconnect.h" +#include "lltwitterconnect.h" #include "lllayoutstack.h" #include "llpluginclassmedia.h" #include "llprogressbar.h" @@ -51,7 +53,8 @@ LLFloaterWebContent::_Params::_Params() allow_back_forward_navigation("allow_back_forward_navigation", true), preferred_media_size("preferred_media_size"), trusted_content("trusted_content", false), - show_page_title("show_page_title", true) + show_page_title("show_page_title", true), + clean_browser("clean_browser", false) {} LLFloaterWebContent::LLFloaterWebContent( const Params& params ) @@ -242,7 +245,7 @@ void LLFloaterWebContent::open_media(const Params& p) LLViewerMedia::proxyWindowOpened(p.target(), p.id()); mWebBrowser->setHomePageUrl(p.url, "text/html"); mWebBrowser->setTarget(p.target); - mWebBrowser->navigateTo(p.url, "text/html"); + mWebBrowser->navigateTo(p.url, "text/html", p.clean_browser); set_current_url(p.url); @@ -253,11 +256,6 @@ void LLFloaterWebContent::open_media(const Params& p) getChildView("address")->setEnabled(address_entry_enabled); getChildView("popexternal")->setEnabled(address_entry_enabled); - if (!address_entry_enabled) - { - mWebBrowser->setFocus(TRUE); - } - if (!p.show_chrome) { setResizeLimits(100, 100); @@ -303,7 +301,24 @@ void LLFloaterWebContent::onClose(bool app_quitting) LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_CONNECTION_FAILED); } } - + // Same with Flickr + LLFloater* flickr_web = LLFloaterReg::getInstance("flickr_web"); + if (flickr_web == this) + { + if (!LLFlickrConnect::instance().isConnected()) + { + LLFlickrConnect::instance().setConnectionState(LLFlickrConnect::FLICKR_CONNECTION_FAILED); + } + } + // And Twitter + LLFloater* twitter_web = LLFloaterReg::getInstance("twitter_web"); + if (twitter_web == this) + { + if (!LLTwitterConnect::instance().isConnected()) + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTION_FAILED); + } + } LLViewerMedia::proxyWindowClosed(mUUID); destroy(); } @@ -439,9 +454,6 @@ void LLFloaterWebContent::set_current_url(const std::string& url) mAddressCombo->remove(mCurrentURL); mAddressCombo->add(mDisplayURL); mAddressCombo->selectByValue(mDisplayURL); - - // Set the focus back to the web page. When setting the url, there's no point to leave the focus anywhere else. - mWebBrowser->setFocus(TRUE); } } diff --git a/indra/newview/llfloaterwebcontent.h b/indra/newview/llfloaterwebcontent.h index f22940cd07..2bb8e3271f 100755 --- a/indra/newview/llfloaterwebcontent.h +++ b/indra/newview/llfloaterwebcontent.h @@ -57,7 +57,8 @@ public: allow_address_entry, allow_back_forward_navigation, trusted_content, - show_page_title; + show_page_title, + clean_browser; Optional<LLRect> preferred_media_size; _Params(); diff --git a/indra/newview/llimagefiltersmanager.cpp b/indra/newview/llimagefiltersmanager.cpp new file mode 100644 index 0000000000..ee6b39efac --- /dev/null +++ b/indra/newview/llimagefiltersmanager.cpp @@ -0,0 +1,115 @@ +/** + * @file llimagefiltersmanager.cpp + * @brief Load image filters list and retrieve their path. Mostly used for Flickr UI at the moment. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llimagefiltersmanager.h" + +#include "lldiriterator.h" +#include "lltrans.h" + +std::string get_sys_dir() +{ + return gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "filters", ""); +} + +//--------------------------------------------------------------------------- +// LLImageFiltersManager +//--------------------------------------------------------------------------- + +LLImageFiltersManager::LLImageFiltersManager() +{ +} + +LLImageFiltersManager::~LLImageFiltersManager() +{ +} + +// virtual static +void LLImageFiltersManager::initSingleton() +{ + loadAllFilters(); +} + +void LLImageFiltersManager::loadAllFilters() +{ + // Load system (coming out of the box) filters + loadFiltersFromDir(get_sys_dir()); +} + +void LLImageFiltersManager::loadFiltersFromDir(const std::string& dir) +{ + mFiltersList.clear(); + + LLDirIterator dir_iter(dir, "*.xml"); + while (1) + { + std::string file_name; + if (!dir_iter.next(file_name)) + { + break; // no more files + } + + // Get the ".xml" out of the file name to get the filter name. That's the one known in strings.xml + std::string filter_name_untranslated = file_name.substr(0,file_name.length()-4); + + // Get the localized name for the filter + std::string filter_name_translated; + bool translated = LLTrans::findString(filter_name_translated, filter_name_untranslated); + std::string filter_name = (translated ? filter_name_translated: filter_name_untranslated); + + // Store the filter in the list with its associated file name + mFiltersList[filter_name] = file_name; + } +} + +// Note : That method is a bit heavy handed but the list of filters is always small (10 or so) +// and that method is typically called only once when building UI widgets. +const std::vector<std::string> LLImageFiltersManager::getFiltersList() const +{ + std::vector<std::string> filter_list; + for (std::map<std::string,std::string>::const_iterator it = mFiltersList.begin(); it != mFiltersList.end(); ++it) + { + filter_list.push_back(it->first); + } + return filter_list; +} + +std::string LLImageFiltersManager::getFilterPath(const std::string& filter_name) +{ + std::string path = ""; + std::map<std::string,std::string>::const_iterator it = mFiltersList.find(filter_name); + if (it != mFiltersList.end()) + { + // Get the file name for that filter and build the complete path + std::string file = it->second; + std::string dir = get_sys_dir(); + path = gDirUtilp->add(dir, file); + } + return path; +} + +//============================================================================ diff --git a/indra/newview/llimagefiltersmanager.h b/indra/newview/llimagefiltersmanager.h new file mode 100644 index 0000000000..4751933065 --- /dev/null +++ b/indra/newview/llimagefiltersmanager.h @@ -0,0 +1,55 @@ +/** + * @file llimagefiltersmanager.h + * @brief Load image filters list and retrieve their path. Mostly used for Flickr UI at the moment. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLIMAGEFILTERSMANAGER_H +#define LL_LLIMAGEFILTERSMANAGER_H + +#include "llsingleton.h" + +//============================================================================ +// LLImageFiltersManager class + +class LLImageFiltersManager : public LLSingleton<LLImageFiltersManager> +{ + LOG_CLASS(LLImageFiltersManager); +public: + const std::vector<std::string> getFiltersList() const; + std::string getFilterPath(const std::string& filter_name); + +private: + void loadAllFilters(); + void loadFiltersFromDir(const std::string& dir); + + friend class LLSingleton<LLImageFiltersManager>; + /*virtual*/ void initSingleton(); + LLImageFiltersManager(); + ~LLImageFiltersManager(); + + // List of filters : first is the user friendly localized name, second is the xml file name + std::map<std::string,std::string> mFiltersList; +}; + +#endif // LL_LLIMAGEFILTERSMANAGER_H diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp index 2075aeed63..229542c1c4 100755 --- a/indra/newview/llmediactrl.cpp +++ b/indra/newview/llmediactrl.cpp @@ -539,7 +539,7 @@ void LLMediaCtrl::clearCache() //////////////////////////////////////////////////////////////////////////////// // -void LLMediaCtrl::navigateTo( std::string url_in, std::string mime_type) +void LLMediaCtrl::navigateTo( std::string url_in, std::string mime_type, bool clean_browser) { // don't browse to anything that starts with secondlife:// or sl:// const std::string protocol1 = "secondlife://"; @@ -556,7 +556,7 @@ void LLMediaCtrl::navigateTo( std::string url_in, std::string mime_type) { mCurrentNavUrl = url_in; mMediaSource->setSize(mTextureWidth, mTextureHeight); - mMediaSource->navigateTo(url_in, mime_type, mime_type.empty()); + mMediaSource->navigateTo(url_in, mime_type, mime_type.empty(), false, clean_browser); } } diff --git a/indra/newview/llmediactrl.h b/indra/newview/llmediactrl.h index 6c38c1fb56..8429d8188e 100755 --- a/indra/newview/llmediactrl.h +++ b/indra/newview/llmediactrl.h @@ -95,7 +95,7 @@ public: virtual BOOL handleToolTip(S32 x, S32 y, MASK mask); // navigation - void navigateTo( std::string url_in, std::string mime_type = ""); + void navigateTo( std::string url_in, std::string mime_type = "", bool clean_browser = false); void navigateBack(); void navigateHome(); void navigateForward(); diff --git a/indra/newview/llpanelsnapshotoptions.cpp b/indra/newview/llpanelsnapshotoptions.cpp index 554fabe5b3..a7b9b6d22e 100755 --- a/indra/newview/llpanelsnapshotoptions.cpp +++ b/indra/newview/llpanelsnapshotoptions.cpp @@ -31,6 +31,10 @@ #include "llsidetraypanelcontainer.h" #include "llfloatersnapshot.h" // FIXME: create a snapshot model +#include "llfloaterreg.h" +#include "llfloaterfacebook.h" +#include "llfloaterflickr.h" +#include "llfloatertwitter.h" /** * Provides several ways to save a snapshot. @@ -44,6 +48,7 @@ class LLPanelSnapshotOptions public: LLPanelSnapshotOptions(); ~LLPanelSnapshotOptions(); + /*virtual*/ BOOL postBuild(); /*virtual*/ void onOpen(const LLSD& key); /*virtual*/ void onEconomyDataChange() { updateUploadCost(); } @@ -54,6 +59,9 @@ private: void onSaveToEmail(); void onSaveToInventory(); void onSaveToComputer(); + void onSendToFacebook(); + void onSendToTwitter(); + void onSendToFlickr(); }; static LLRegisterPanelClassWrapper<LLPanelSnapshotOptions> panel_class("llpanelsnapshotoptions"); @@ -74,6 +82,19 @@ LLPanelSnapshotOptions::~LLPanelSnapshotOptions() } // virtual +BOOL LLPanelSnapshotOptions::postBuild() +{ + LLTextBox* sendToFacebookTextBox = getChild<LLTextBox>("send_to_facebook_textbox"); + sendToFacebookTextBox->setURLClickedCallback(boost::bind(&LLPanelSnapshotOptions::onSendToFacebook, this)); + LLTextBox* sendToTwitterTextBox = getChild<LLTextBox>("send_to_twitter_textbox"); + sendToTwitterTextBox->setURLClickedCallback(boost::bind(&LLPanelSnapshotOptions::onSendToTwitter, this)); + LLTextBox* sendToFlickrTextBox = getChild<LLTextBox>("send_to_flickr_textbox"); + sendToFlickrTextBox->setURLClickedCallback(boost::bind(&LLPanelSnapshotOptions::onSendToFlickr, this)); + + return LLPanel::postBuild(); +} + +// virtual void LLPanelSnapshotOptions::onOpen(const LLSD& key) { updateUploadCost(); @@ -118,3 +139,39 @@ void LLPanelSnapshotOptions::onSaveToComputer() { openPanel("panel_snapshot_local"); } + +void LLPanelSnapshotOptions::onSendToFacebook() +{ + LLFloaterReg::hideInstance("snapshot"); + + LLFloaterFacebook* facebook_floater = dynamic_cast<LLFloaterFacebook*>(LLFloaterReg::getInstance("facebook")); + if (facebook_floater) + { + facebook_floater->showPhotoPanel(); + } + LLFloaterReg::showInstance("facebook"); +} + +void LLPanelSnapshotOptions::onSendToTwitter() +{ + LLFloaterReg::hideInstance("snapshot"); + + LLFloaterTwitter* twitter_floater = dynamic_cast<LLFloaterTwitter*>(LLFloaterReg::getInstance("twitter")); + if (twitter_floater) + { + twitter_floater->showPhotoPanel(); + } + LLFloaterReg::showInstance("twitter"); +} + +void LLPanelSnapshotOptions::onSendToFlickr() +{ + LLFloaterReg::hideInstance("snapshot"); + + LLFloaterFlickr* flickr_floater = dynamic_cast<LLFloaterFlickr*>(LLFloaterReg::getInstance("flickr")); + if (flickr_floater) + { + flickr_floater->showPhotoPanel(); + } + LLFloaterReg::showInstance("flickr"); +} diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp index 7532ebfc57..eef4ed78c8 100644 --- a/indra/newview/llsnapshotlivepreview.cpp +++ b/indra/newview/llsnapshotlivepreview.cpp @@ -34,7 +34,11 @@ #include "lleconomy.h" #include "llfloaterperms.h" #include "llfloaterreg.h" -#include "llfloatersocial.h" +#include "llfloaterfacebook.h" +#include "llfloaterflickr.h" +#include "llfloatertwitter.h" +#include "llimagefilter.h" +#include "llimagefiltersmanager.h" #include "llimagebmp.h" #include "llimagej2c.h" #include "llimagejpeg.h" @@ -74,6 +78,7 @@ LLSnapshotLivePreview::LLSnapshotLivePreview (const LLSnapshotLivePreview::Param mThumbnailImage(NULL) , mThumbnailWidth(0), mThumbnailHeight(0), + mThumbnailSubsampled(FALSE), mPreviewImageEncoded(NULL), mFormattedImage(NULL), mShineCountdown(0), @@ -87,7 +92,11 @@ LLSnapshotLivePreview::LLSnapshotLivePreview (const LLSnapshotLivePreview::Param mCameraPos(LLViewerCamera::getInstance()->getOrigin()), mCameraRot(LLViewerCamera::getInstance()->getQuaternion()), mSnapshotActive(FALSE), - mSnapshotBufferType(LLViewerWindow::SNAPSHOT_TYPE_COLOR) + mSnapshotBufferType(LLViewerWindow::SNAPSHOT_TYPE_COLOR), + mFilterName(""), + mAllowRenderUI(TRUE), + mAllowFullScreenPreview(TRUE), + mViewContainer(NULL) { setSnapshotQuality(gSavedSettings.getS32("SnapshotQuality")); mSnapshotDelayTimer.setTimerExpirySec(0.0f); @@ -121,14 +130,7 @@ LLSnapshotLivePreview::~LLSnapshotLivePreview() void LLSnapshotLivePreview::setMaxImageSize(S32 size) { - if(size < MAX_SNAPSHOT_IMAGE_SIZE) - { - mMaxImageSize = size; - } - else - { - mMaxImageSize = MAX_SNAPSHOT_IMAGE_SIZE ; - } + mMaxImageSize = llmin(size,(S32)(MAX_SNAPSHOT_IMAGE_SIZE)); } LLViewerTexture* LLSnapshotLivePreview::getCurrentImage() @@ -136,97 +138,91 @@ LLViewerTexture* LLSnapshotLivePreview::getCurrentImage() return mViewerImage[mCurImageIndex]; } -F32 LLSnapshotLivePreview::getAspect() -{ - F32 image_aspect_ratio = ((F32)getWidth()) / ((F32)getHeight()); - F32 window_aspect_ratio = ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()); - - if (!mKeepAspectRatio)//gSavedSettings.getBOOL("KeepAspectForSnapshot")) - { - return image_aspect_ratio; - } - else - { - return window_aspect_ratio; - } -} - F32 LLSnapshotLivePreview::getImageAspect() { if (!getCurrentImage()) { return 0.f; } - - return getAspect() ; + // mKeepAspectRatio) == gSavedSettings.getBOOL("KeepAspectForSnapshot")) + return (mKeepAspectRatio ? ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()) : ((F32)getWidth()) / ((F32)getHeight())); } -void LLSnapshotLivePreview::updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail, F32 delay) +void LLSnapshotLivePreview::updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail, F32 delay) { - // Invalidate current image. lldebugs << "updateSnapshot: mSnapshotUpToDate = " << getSnapshotUpToDate() << llendl; - if (getSnapshotUpToDate()) - { - S32 old_image_index = mCurImageIndex; - mCurImageIndex = (mCurImageIndex + 1) % 2; - setSize(mWidth[old_image_index], mHeight[old_image_index]); - mFallAnimTimer.start(); - } - mSnapshotUpToDate = FALSE; - - // Update snapshot source rect depending on whether we keep the aspect ratio. - LLRect& rect = mImageRect[mCurImageIndex]; - rect.set(0, getRect().getHeight(), getRect().getWidth(), 0); - - F32 image_aspect_ratio = ((F32)getWidth()) / ((F32)getHeight()); - F32 window_aspect_ratio = ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()); - - if (mKeepAspectRatio)//gSavedSettings.getBOOL("KeepAspectForSnapshot")) - { - if (image_aspect_ratio > window_aspect_ratio) - { - // trim off top and bottom - S32 new_height = llround((F32)getRect().getWidth() / image_aspect_ratio); - rect.mBottom += (getRect().getHeight() - new_height) / 2; - rect.mTop -= (getRect().getHeight() - new_height) / 2; - } - else if (image_aspect_ratio < window_aspect_ratio) - { - // trim off left and right - S32 new_width = llround((F32)getRect().getHeight() * image_aspect_ratio); - rect.mLeft += (getRect().getWidth() - new_width) / 2; - rect.mRight -= (getRect().getWidth() - new_width) / 2; - } - } - - // Stop shining animation. - mShineAnimTimer.stop(); // Update snapshot if requested. if (new_snapshot) { + if (getSnapshotUpToDate()) + { + S32 old_image_index = mCurImageIndex; + mCurImageIndex = (mCurImageIndex + 1) % 2; + setSize(mWidth[old_image_index], mHeight[old_image_index]); + mFallAnimTimer.start(); + } + mSnapshotUpToDate = FALSE; + + // Update snapshot source rect depending on whether we keep the aspect ratio. + LLRect& rect = mImageRect[mCurImageIndex]; + rect.set(0, getRect().getHeight(), getRect().getWidth(), 0); + + F32 image_aspect_ratio = ((F32)getWidth()) / ((F32)getHeight()); + F32 window_aspect_ratio = ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()); + + if (mKeepAspectRatio)//gSavedSettings.getBOOL("KeepAspectForSnapshot")) + { + if (image_aspect_ratio > window_aspect_ratio) + { + // trim off top and bottom + S32 new_height = llround((F32)getRect().getWidth() / image_aspect_ratio); + rect.mBottom += (getRect().getHeight() - new_height) / 2; + rect.mTop -= (getRect().getHeight() - new_height) / 2; + } + else if (image_aspect_ratio < window_aspect_ratio) + { + // trim off left and right + S32 new_width = llround((F32)getRect().getHeight() * image_aspect_ratio); + rect.mLeft += (getRect().getWidth() - new_width) / 2; + rect.mRight -= (getRect().getWidth() - new_width) / 2; + } + } + + // Stop shining animation. + mShineAnimTimer.stop(); mSnapshotDelayTimer.start(); mSnapshotDelayTimer.setTimerExpirySec(delay); - LLFloaterSnapshot::preUpdate(); - LLFloaterSocial::preUpdate(); + + // Tell the floater container that the snapshot is in the process of updating itself + if (mViewContainer) + { + mViewContainer->notify(LLSD().with("snapshot-updating", true)); + } } // Update thumbnail if requested. - if(new_thumbnail) + if (new_thumbnail) { mThumbnailUpToDate = FALSE ; } } -void LLSnapshotLivePreview::setSnapshotQuality(S32 quality) +// Return true if the quality has been changed, false otherwise +bool LLSnapshotLivePreview::setSnapshotQuality(S32 quality, bool set_by_user) { llclamp(quality, 0, 100); if (quality != mSnapshotQuality) { mSnapshotQuality = quality; - gSavedSettings.setS32("SnapshotQuality", quality); - mSnapshotUpToDate = FALSE; - } + if (set_by_user) + { + gSavedSettings.setS32("SnapshotQuality", quality); + } + mFormattedImage = NULL; // Invalidate the already formatted image if any + return true; + } + return false; } void LLSnapshotLivePreview::drawPreviewRect(S32 offset_x, S32 offset_y) @@ -459,69 +455,58 @@ void LLSnapshotLivePreview::reshape(S32 width, S32 height, BOOL called_from_pare LLView::reshape(width, height, called_from_parent); if (old_rect.getWidth() != width || old_rect.getHeight() != height) { - lldebugs << "window reshaped, updating thumbnail" << llendl; - updateSnapshot(FALSE, TRUE); + lldebugs << "window reshaped, updating snapshot" << llendl; + updateSnapshot(TRUE); } } BOOL LLSnapshotLivePreview::setThumbnailImageSize() { - if(getWidth() < 10 || getHeight() < 10) + if (getWidth() < 10 || getHeight() < 10) { return FALSE ; } - S32 window_width = gViewerWindow->getWindowWidthRaw() ; - S32 window_height = gViewerWindow->getWindowHeightRaw() ; + S32 width = (mThumbnailSubsampled ? mPreviewImage->getWidth() : gViewerWindow->getWindowWidthRaw()); + S32 height = (mThumbnailSubsampled ? mPreviewImage->getHeight() : gViewerWindow->getWindowHeightRaw()) ; - F32 window_aspect_ratio = ((F32)window_width) / ((F32)window_height); + F32 aspect_ratio = ((F32)width) / ((F32)height); // UI size for thumbnail - // *FIXME: the rect does not change, so maybe there's no need to recalculate max w/h. - const LLRect& thumbnail_rect = mThumbnailPlaceholderRect; - S32 max_width = thumbnail_rect.getWidth(); - S32 max_height = thumbnail_rect.getHeight(); + S32 max_width = mThumbnailPlaceholderRect.getWidth(); + S32 max_height = mThumbnailPlaceholderRect.getHeight(); - if (window_aspect_ratio > (F32)max_width / max_height) + if (aspect_ratio > (F32)max_width / (F32)max_height) { // image too wide, shrink to width mThumbnailWidth = max_width; - mThumbnailHeight = llround((F32)max_width / window_aspect_ratio); + mThumbnailHeight = llround((F32)max_width / aspect_ratio); } else { // image too tall, shrink to height mThumbnailHeight = max_height; - mThumbnailWidth = llround((F32)max_height * window_aspect_ratio); + mThumbnailWidth = llround((F32)max_height * aspect_ratio); } - - if(mThumbnailWidth > window_width || mThumbnailHeight > window_height) + + if (mThumbnailWidth > width || mThumbnailHeight > height) { return FALSE ;//if the window is too small, ignore thumbnail updating. } S32 left = 0 , top = mThumbnailHeight, right = mThumbnailWidth, bottom = 0 ; - if(!mKeepAspectRatio) - { - F32 ratio_x = (F32)getWidth() / window_width ; - F32 ratio_y = (F32)getHeight() / window_height ; - - //if(getWidth() > window_width || - // getHeight() > window_height ) - { - if(ratio_x > ratio_y) - { - top = (S32)(top * ratio_y / ratio_x) ; - } - else - { - right = (S32)(right * ratio_x / ratio_y) ; - } - } - //else - //{ - // right = (S32)(right * ratio_x) ; - // top = (S32)(top * ratio_y) ; - //} + if (!mKeepAspectRatio) + { + F32 ratio_x = (F32)getWidth() / width ; + F32 ratio_y = (F32)getHeight() / height ; + + if (ratio_x > ratio_y) + { + top = (S32)(top * ratio_y / ratio_x) ; + } + else + { + right = (S32)(right * ratio_x / ratio_y) ; + } left = (S32)((mThumbnailWidth - right) * 0.5f) ; bottom = (S32)((mThumbnailHeight - top) * 0.5f) ; top += bottom ; @@ -563,19 +548,53 @@ void LLSnapshotLivePreview::generateThumbnailImage(BOOL force_update) } LLPointer<LLImageRaw> raw = new LLImageRaw; - if(!gViewerWindow->thumbnailSnapshot(raw, - mThumbnailWidth, mThumbnailHeight, - gSavedSettings.getBOOL("RenderUIInSnapshot"), - FALSE, - mSnapshotBufferType) ) - { - raw = NULL ; - } - - if(raw) - { - raw->expandToPowerOfTwo(); - mThumbnailImage = LLViewerTextureManager::getLocalTexture(raw.get(), FALSE); + + if (mThumbnailSubsampled) + { + // The thumbnail is be a subsampled version of the preview (used in SL Share previews, i.e. Flickr, Twitter, Facebook) + raw->resize( mPreviewImage->getWidth(), + mPreviewImage->getHeight(), + mPreviewImage->getComponents()); + raw->copy(mPreviewImage); + // Scale to the thumbnail size + if (!raw->scale(mThumbnailWidth, mThumbnailHeight)) + { + raw = NULL ; + } + } + else + { + // The thumbnail is a screen view with screen grab positioning preview + if(!gViewerWindow->thumbnailSnapshot(raw, + mThumbnailWidth, mThumbnailHeight, + mAllowRenderUI && gSavedSettings.getBOOL("RenderUIInSnapshot"), + FALSE, + mSnapshotBufferType) ) + { + raw = NULL ; + } + } + + if (raw) + { + // Filter the thumbnail + // Note: filtering needs to be done *before* the scaling to power of 2 or the effect is distorted + if (getFilter() != "") + { + std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter()); + if (filter_path != "") + { + LLImageFilter filter(filter_path); + filter.executeFilter(raw); + } + else + { + llwarns << "Couldn't find a path to the following filter : " << getFilter() << llendl; + } + } + // Scale to a power of 2 so it can be mapped to a texture + raw->expandToPowerOfTwo(); + mThumbnailImage = LLViewerTextureManager::getLocalTexture(raw.get(), FALSE); mThumbnailUpToDate = TRUE ; } @@ -599,7 +618,7 @@ BOOL LLSnapshotLivePreview::onIdle( void* snapshot_preview ) // If we're in freeze-frame mode and camera has moved, update snapshot. LLVector3 new_camera_pos = LLViewerCamera::getInstance()->getOrigin(); LLQuaternion new_camera_rot = LLViewerCamera::getInstance()->getQuaternion(); - if (gSavedSettings.getBOOL("FreezeTime") && + if (gSavedSettings.getBOOL("FreezeTime") && previewp->mAllowFullScreenPreview && (new_camera_pos != previewp->mCameraPos || dot(new_camera_rot, previewp->mCameraRot) < 0.995f)) { previewp->mCameraPos = new_camera_pos; @@ -617,157 +636,256 @@ BOOL LLSnapshotLivePreview::onIdle( void* snapshot_preview ) previewp->mSnapshotActive = (previewp->mSnapshotDelayTimer.getStarted() && previewp->mSnapshotDelayTimer.hasExpired()) && !LLToolCamera::getInstance()->hasMouseCapture(); // don't take snapshots while ALT-zoom active - if ( ! previewp->mSnapshotActive) + if (!previewp->mSnapshotActive && previewp->getSnapshotUpToDate() && previewp->getThumbnailUpToDate()) { return FALSE; } // time to produce a snapshot - previewp->setThumbnailImageSize(); - - lldebugs << "producing snapshot" << llendl; - if (!previewp->mPreviewImage) + if(!previewp->getSnapshotUpToDate()) + { + lldebugs << "producing snapshot" << llendl; + if (!previewp->mPreviewImage) + { + previewp->mPreviewImage = new LLImageRaw; + } + + previewp->setVisible(FALSE); + previewp->setEnabled(FALSE); + + previewp->getWindow()->incBusyCount(); + previewp->setImageScaled(FALSE); + + // grab the raw image + if (gViewerWindow->rawSnapshot( + previewp->mPreviewImage, + previewp->getWidth(), + previewp->getHeight(), + previewp->mKeepAspectRatio,//gSavedSettings.getBOOL("KeepAspectForSnapshot"), + previewp->getSnapshotType() == LLSnapshotLivePreview::SNAPSHOT_TEXTURE, + previewp->mAllowRenderUI && gSavedSettings.getBOOL("RenderUIInSnapshot"), + FALSE, + previewp->mSnapshotBufferType, + previewp->getMaxImageSize())) + { + // Invalidate/delete any existing encoded image + previewp->mPreviewImageEncoded = NULL; + // Invalidate/delete any existing formatted image + previewp->mFormattedImage = NULL; + // Update the data size + previewp->estimateDataSize(); + + // Full size preview is set: get the decoded image result and save it for animation + if (gSavedSettings.getBOOL("UseFreezeFrame") && previewp->mAllowFullScreenPreview) + { + // Get the decoded version of the formatted image + previewp->getEncodedImage(); + + // We need to scale that a bit for display... + LLPointer<LLImageRaw> scaled = new LLImageRaw( + previewp->mPreviewImageEncoded->getData(), + previewp->mPreviewImageEncoded->getWidth(), + previewp->mPreviewImageEncoded->getHeight(), + previewp->mPreviewImageEncoded->getComponents()); + + if (!scaled->isBufferInvalid()) + { + // leave original image dimensions, just scale up texture buffer + if (previewp->mPreviewImageEncoded->getWidth() > 1024 || previewp->mPreviewImageEncoded->getHeight() > 1024) + { + // go ahead and shrink image to appropriate power of 2 for display + scaled->biasedScaleToPowerOfTwo(1024); + previewp->setImageScaled(TRUE); + } + else + { + // expand image but keep original image data intact + scaled->expandToPowerOfTwo(1024, FALSE); + } + + previewp->mViewerImage[previewp->mCurImageIndex] = LLViewerTextureManager::getLocalTexture(scaled.get(), FALSE); + LLPointer<LLViewerTexture> curr_preview_image = previewp->mViewerImage[previewp->mCurImageIndex]; + gGL.getTexUnit(0)->bind(curr_preview_image); + curr_preview_image->setFilteringOption(previewp->getSnapshotType() == SNAPSHOT_TEXTURE ? LLTexUnit::TFO_ANISOTROPIC : LLTexUnit::TFO_POINT); + curr_preview_image->setAddressMode(LLTexUnit::TAM_CLAMP); + + previewp->mPosTakenGlobal = gAgentCamera.getCameraPositionGlobal(); + previewp->mShineCountdown = 4; // wait a few frames to avoid animation glitch due to readback this frame + } + } + // The snapshot is updated now... + previewp->mSnapshotUpToDate = TRUE; + + // We need to update the thumbnail though + previewp->setThumbnailImageSize(); + previewp->generateThumbnailImage(TRUE) ; + } + previewp->getWindow()->decBusyCount(); + previewp->setVisible(gSavedSettings.getBOOL("UseFreezeFrame") && previewp->mAllowFullScreenPreview); // only show fullscreen preview when in freeze frame mode + previewp->mSnapshotDelayTimer.stop(); + previewp->mSnapshotActive = FALSE; + lldebugs << "done creating snapshot" << llendl; + } + + if (!previewp->getThumbnailUpToDate()) { - previewp->mPreviewImage = new LLImageRaw; + previewp->generateThumbnailImage() ; } + + // Tell the floater container that the snapshot is updated now + if (previewp->mViewContainer) + { + previewp->mViewContainer->notify(LLSD().with("snapshot-updated", true)); + } - if (!previewp->mPreviewImageEncoded) - { - previewp->mPreviewImageEncoded = new LLImageRaw; - } + return TRUE; +} - previewp->setVisible(FALSE); - previewp->setEnabled(FALSE); - - previewp->getWindow()->incBusyCount(); - previewp->setImageScaled(FALSE); - - // grab the raw image and encode it into desired format - if(gViewerWindow->rawSnapshot( - previewp->mPreviewImage, - previewp->getWidth(), - previewp->getHeight(), - previewp->mKeepAspectRatio,//gSavedSettings.getBOOL("KeepAspectForSnapshot"), - previewp->getSnapshotType() == LLSnapshotLivePreview::SNAPSHOT_TEXTURE, - gSavedSettings.getBOOL("RenderUIInSnapshot"), - FALSE, - previewp->mSnapshotBufferType, - previewp->getMaxImageSize())) - { - previewp->mPreviewImageEncoded->resize( - previewp->mPreviewImage->getWidth(), - previewp->mPreviewImage->getHeight(), - previewp->mPreviewImage->getComponents()); +S32 LLSnapshotLivePreview::getEncodedImageWidth() const +{ + S32 width = getWidth(); + if (getSnapshotType() == SNAPSHOT_TEXTURE) + { + width = LLImageRaw::biasedDimToPowerOfTwo(width,MAX_TEXTURE_SIZE); + } + return width; +} +S32 LLSnapshotLivePreview::getEncodedImageHeight() const +{ + S32 height = getHeight(); + if (getSnapshotType() == SNAPSHOT_TEXTURE) + { + height = LLImageRaw::biasedDimToPowerOfTwo(height,MAX_TEXTURE_SIZE); + } + return height; +} - if(previewp->getSnapshotType() == SNAPSHOT_TEXTURE) +LLPointer<LLImageRaw> LLSnapshotLivePreview::getEncodedImage() +{ + if (!mPreviewImageEncoded) + { + mPreviewImageEncoded = new LLImageRaw; + + mPreviewImageEncoded->resize( + mPreviewImage->getWidth(), + mPreviewImage->getHeight(), + mPreviewImage->getComponents()); + + if (getSnapshotType() == SNAPSHOT_TEXTURE) { + // We don't store the intermediate formatted image in mFormattedImage in the J2C case lldebugs << "Encoding new image of format J2C" << llendl; LLPointer<LLImageJ2C> formatted = new LLImageJ2C; + // Copy the preview LLPointer<LLImageRaw> scaled = new LLImageRaw( - previewp->mPreviewImage->getData(), - previewp->mPreviewImage->getWidth(), - previewp->mPreviewImage->getHeight(), - previewp->mPreviewImage->getComponents()); - + mPreviewImage->getData(), + mPreviewImage->getWidth(), + mPreviewImage->getHeight(), + mPreviewImage->getComponents()); + // Scale it as required by J2C scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE); - previewp->setImageScaled(TRUE); + setImageScaled(TRUE); + // Compress to J2C if (formatted->encode(scaled, 0.f)) { - previewp->mDataSize = formatted->getDataSize(); - formatted->decode(previewp->mPreviewImageEncoded, 0); + // We can update the data size precisely at that point + mDataSize = formatted->getDataSize(); + // Decompress back + formatted->decode(mPreviewImageEncoded, 0); } } else { - // delete any existing image - previewp->mFormattedImage = NULL; - // now create the new one of the appropriate format. - LLFloaterSnapshot::ESnapshotFormat format = previewp->getSnapshotFormat(); - lldebugs << "Encoding new image of format " << format << llendl; - - switch(format) - { - case LLFloaterSnapshot::SNAPSHOT_FORMAT_PNG: - previewp->mFormattedImage = new LLImagePNG(); - break; - case LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG: - previewp->mFormattedImage = new LLImageJPEG(previewp->mSnapshotQuality); - break; - case LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP: - previewp->mFormattedImage = new LLImageBMP(); - break; - } - if (previewp->mFormattedImage->encode(previewp->mPreviewImage, 0)) - { - previewp->mDataSize = previewp->mFormattedImage->getDataSize(); - // special case BMP to copy instead of decode otherwise decode will crash. - if(format == LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP) - { - previewp->mPreviewImageEncoded->copy(previewp->mPreviewImage); - } - else - { - previewp->mFormattedImage->decode(previewp->mPreviewImageEncoded, 0); - } - } - } - - LLPointer<LLImageRaw> scaled = new LLImageRaw( - previewp->mPreviewImageEncoded->getData(), - previewp->mPreviewImageEncoded->getWidth(), - previewp->mPreviewImageEncoded->getHeight(), - previewp->mPreviewImageEncoded->getComponents()); - - if(!scaled->isBufferInvalid()) - { - // leave original image dimensions, just scale up texture buffer - if (previewp->mPreviewImageEncoded->getWidth() > 1024 || previewp->mPreviewImageEncoded->getHeight() > 1024) - { - // go ahead and shrink image to appropriate power of 2 for display - scaled->biasedScaleToPowerOfTwo(1024); - previewp->setImageScaled(TRUE); - } - else - { - // expand image but keep original image data intact - scaled->expandToPowerOfTwo(1024, FALSE); - } - - previewp->mViewerImage[previewp->mCurImageIndex] = LLViewerTextureManager::getLocalTexture(scaled.get(), FALSE); - LLPointer<LLViewerTexture> curr_preview_image = previewp->mViewerImage[previewp->mCurImageIndex]; - gGL.getTexUnit(0)->bind(curr_preview_image); - if (previewp->getSnapshotType() != SNAPSHOT_TEXTURE) - { - curr_preview_image->setFilteringOption(LLTexUnit::TFO_POINT); - } - else - { - curr_preview_image->setFilteringOption(LLTexUnit::TFO_ANISOTROPIC); - } - curr_preview_image->setAddressMode(LLTexUnit::TAM_CLAMP); - - previewp->mSnapshotUpToDate = TRUE; - previewp->generateThumbnailImage(TRUE) ; - - previewp->mPosTakenGlobal = gAgentCamera.getCameraPositionGlobal(); - previewp->mShineCountdown = 4; // wait a few frames to avoid animation glitch due to readback this frame + // Update mFormattedImage if necessary + getFormattedImage(); + if (getSnapshotFormat() == LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP) + { + // BMP hack : copy instead of decode otherwise decode will crash. + mPreviewImageEncoded->copy(mPreviewImage); + } + else + { + // Decode back + mFormattedImage->decode(mPreviewImageEncoded, 0); + } } } - previewp->getWindow()->decBusyCount(); - // only show fullscreen preview when in freeze frame mode - previewp->setVisible(gSavedSettings.getBOOL("UseFreezeFrame")); - previewp->mSnapshotDelayTimer.stop(); - previewp->mSnapshotActive = FALSE; + return mPreviewImageEncoded; +} - if(!previewp->getThumbnailUpToDate()) - { - previewp->generateThumbnailImage() ; - } - lldebugs << "done creating snapshot" << llendl; - LLFloaterSnapshot::postUpdate(); - LLFloaterSocial::postUpdate(); +// We actually estimate the data size so that we do not require actual compression when showing the preview +// Note : whenever formatted image is computed, mDataSize will be updated to reflect the true size +void LLSnapshotLivePreview::estimateDataSize() +{ + // Compression ratio + F32 ratio = 1.0; + + if (getSnapshotType() == SNAPSHOT_TEXTURE) + { + ratio = 8.0; // This is what we shoot for when compressing to J2C + } + else + { + LLFloaterSnapshot::ESnapshotFormat format = getSnapshotFormat(); + switch (format) + { + case LLFloaterSnapshot::SNAPSHOT_FORMAT_PNG: + ratio = 3.0; // Average observed PNG compression ratio + break; + case LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG: + // Observed from JPG compression tests + ratio = (110 - mSnapshotQuality) / 2; + break; + case LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP: + ratio = 1.0; // No compression with BMP + break; + } + } + mDataSize = (S32)((F32)mPreviewImage->getDataSize() / ratio); +} - return TRUE; +LLPointer<LLImageFormatted> LLSnapshotLivePreview::getFormattedImage() +{ + if (!mFormattedImage) + { + // Apply the filter to mPreviewImage + if (getFilter() != "") + { + std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter()); + if (filter_path != "") + { + LLImageFilter filter(filter_path); + filter.executeFilter(mPreviewImage); + } + else + { + llwarns << "Couldn't find a path to the following filter : " << getFilter() << llendl; + } + } + + // Create the new formatted image of the appropriate format. + LLFloaterSnapshot::ESnapshotFormat format = getSnapshotFormat(); + lldebugs << "Encoding new image of format " << format << llendl; + + switch (format) + { + case LLFloaterSnapshot::SNAPSHOT_FORMAT_PNG: + mFormattedImage = new LLImagePNG(); + break; + case LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG: + mFormattedImage = new LLImageJPEG(mSnapshotQuality); + break; + case LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP: + mFormattedImage = new LLImageBMP(); + break; + } + if (mFormattedImage->encode(mPreviewImage, 0)) + { + // We can update the data size precisely at that point + mDataSize = mFormattedImage->getDataSize(); + } + } + return mFormattedImage; } void LLSnapshotLivePreview::setSize(S32 w, S32 h) @@ -777,6 +895,15 @@ void LLSnapshotLivePreview::setSize(S32 w, S32 h) setHeight(h); } +void LLSnapshotLivePreview::setSnapshotFormat(LLFloaterSnapshot::ESnapshotFormat format) +{ + if (mSnapshotFormat != format) + { + mFormattedImage = NULL; // Invalidate the already formatted image if any + mSnapshotFormat = format; + } +} + void LLSnapshotLivePreview::getSize(S32& w, S32& h) const { w = getWidth(); @@ -831,12 +958,14 @@ void LLSnapshotLivePreview::saveTexture() } LLViewerStats::getInstance()->incStat(LLViewerStats::ST_SNAPSHOT_COUNT ); - - mDataSize = 0; } BOOL LLSnapshotLivePreview::saveLocal() { + // Update mFormattedImage if necessary + getFormattedImage(); + + // Save the formatted image BOOL success = gViewerWindow->saveImageNumbered(mFormattedImage); if(success) @@ -848,6 +977,9 @@ BOOL LLSnapshotLivePreview::saveLocal() void LLSnapshotLivePreview::saveWeb() { + // Update mFormattedImage if necessary + getFormattedImage(); + // *FIX: Will break if the window closes because of CloseSnapshotOnKeep! // Needs to pass on ownership of the image. LLImageJPEG* jpg = dynamic_cast<LLImageJPEG*>(mFormattedImage.get()); diff --git a/indra/newview/llsnapshotlivepreview.h b/indra/newview/llsnapshotlivepreview.h index fe3d257b02..7f3e7a080b 100644 --- a/indra/newview/llsnapshotlivepreview.h +++ b/indra/newview/llsnapshotlivepreview.h @@ -61,6 +61,8 @@ public: LLSnapshotLivePreview(const LLSnapshotLivePreview::Params& p); ~LLSnapshotLivePreview(); + void setContainer(LLView* container) { mViewContainer = container; } + /*virtual*/ void draw(); /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent); @@ -70,6 +72,9 @@ public: void getSize(S32& w, S32& h) const; S32 getWidth() const { return mWidth[mCurImageIndex]; } S32 getHeight() const { return mHeight[mCurImageIndex]; } + S32 getEncodedImageWidth() const; + S32 getEncodedImageHeight() const; + void estimateDataSize(); S32 getDataSize() const { return mDataSize; } void setMaxImageSize(S32 size) ; S32 getMaxImageSize() {return mMaxImageSize ;} @@ -83,25 +88,30 @@ public: S32 getThumbnailHeight() const { return mThumbnailHeight ; } BOOL getThumbnailLock() const { return mThumbnailUpdateLock ; } BOOL getThumbnailUpToDate() const { return mThumbnailUpToDate ;} + void setThumbnailSubsampled(BOOL subsampled) { mThumbnailSubsampled = subsampled; } + LLViewerTexture* getCurrentImage(); F32 getImageAspect(); - F32 getAspect() ; const LLRect& getImageRect() const { return mImageRect[mCurImageIndex]; } BOOL isImageScaled() const { return mImageScaled[mCurImageIndex]; } void setImageScaled(BOOL scaled) { mImageScaled[mCurImageIndex] = scaled; } const LLVector3d& getPosTakenGlobal() const { return mPosTakenGlobal; } void setSnapshotType(ESnapshotType type) { mSnapshotType = type; } - void setSnapshotFormat(LLFloaterSnapshot::ESnapshotFormat type) { mSnapshotFormat = type; } - void setSnapshotQuality(S32 quality); + void setSnapshotFormat(LLFloaterSnapshot::ESnapshotFormat format); + bool setSnapshotQuality(S32 quality, bool set_by_user = true); void setSnapshotBufferType(LLViewerWindow::ESnapshotType type) { mSnapshotBufferType = type; } + void setAllowRenderUI(BOOL allow) { mAllowRenderUI = allow; } + void setAllowFullScreenPreview(BOOL allow) { mAllowFullScreenPreview = allow; } + void setFilter(std::string filter_name) { mFilterName = filter_name; } + std::string getFilter() const { return mFilterName; } void updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail = FALSE, F32 delay = 0.f); void saveWeb(); void saveTexture(); BOOL saveLocal(); - LLPointer<LLImageFormatted> getFormattedImage() const { return mFormattedImage; } - LLPointer<LLImageRaw> getEncodedImage() const { return mPreviewImageEncoded; } + LLPointer<LLImageFormatted> getFormattedImage(); + LLPointer<LLImageRaw> getEncodedImage(); /// Sets size of preview thumbnail image and thhe surrounding rect. void setThumbnailPlaceholderRect(const LLRect& rect) {mThumbnailPlaceholderRect = rect; } @@ -117,6 +127,8 @@ public: void regionNameCallback(LLImageJPEG* snapshot, LLSD& metadata, const std::string& name, S32 x, S32 y, S32 z); private: + LLView* mViewContainer; + LLColor4 mColor; LLPointer<LLViewerTexture> mViewerImage[2]; //used to represent the scene when the frame is frozen. LLRect mImageRect[2]; @@ -133,11 +145,15 @@ private: BOOL mThumbnailUpdateLock ; BOOL mThumbnailUpToDate ; LLRect mThumbnailPlaceholderRect; + BOOL mThumbnailSubsampled; // TRUE is the thumbnail is a subsampled version of the mPreviewImage S32 mCurImageIndex; + // The logic is mPreviewImage (raw frame) -> mFormattedImage (formatted / filtered) -> mPreviewImageEncoded (decoded back, to show artifacts) LLPointer<LLImageRaw> mPreviewImage; LLPointer<LLImageRaw> mPreviewImageEncoded; LLPointer<LLImageFormatted> mFormattedImage; + BOOL mAllowRenderUI; + BOOL mAllowFullScreenPreview; LLFrameTimer mSnapshotDelayTimer; S32 mShineCountdown; LLFrameTimer mShineAnimTimer; @@ -154,6 +170,7 @@ private: LLQuaternion mCameraRot; BOOL mSnapshotActive; LLViewerWindow::ESnapshotType mSnapshotBufferType; + std::string mFilterName; public: static std::set<LLSnapshotLivePreview*> sList; diff --git a/indra/newview/lltwitterconnect.cpp b/indra/newview/lltwitterconnect.cpp new file mode 100644 index 0000000000..350da84f69 --- /dev/null +++ b/indra/newview/lltwitterconnect.cpp @@ -0,0 +1,474 @@ +/** + * @file lltwitterconnect.h + * @author Merov, Cho + * @brief Connection to Twitter Service + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltwitterconnect.h" + +#include "llagent.h" +#include "llcallingcard.h" // for LLAvatarTracker +#include "llcommandhandler.h" +#include "llhttpclient.h" +#include "llnotificationsutil.h" +#include "llurlaction.h" +#include "llimagepng.h" +#include "llimagejpeg.h" +#include "lltrans.h" +#include "llevents.h" +#include "llviewerregion.h" + +#include "llfloaterwebcontent.h" +#include "llfloaterreg.h" + +boost::scoped_ptr<LLEventPump> LLTwitterConnect::sStateWatcher(new LLEventStream("TwitterConnectState")); +boost::scoped_ptr<LLEventPump> LLTwitterConnect::sInfoWatcher(new LLEventStream("TwitterConnectInfo")); +boost::scoped_ptr<LLEventPump> LLTwitterConnect::sContentWatcher(new LLEventStream("TwitterConnectContent")); + +// Local functions +void log_twitter_connect_error(const std::string& request, U32 status, const std::string& reason, const std::string& code, const std::string& description) +{ + // Note: 302 (redirect) is *not* an error that warrants logging + if (status != 302) + { + LL_WARNS("TwitterConnect") << request << " request failed with a " << status << " " << reason << ". Reason: " << code << " (" << description << ")" << LL_ENDL; + } +} + +void toast_user_for_twitter_success() +{ + LLSD args; + args["MESSAGE"] = LLTrans::getString("twitter_post_success"); + LLNotificationsUtil::add("TwitterConnect", args); +} + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterConnectResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLTwitterConnectResponder); +public: + + LLTwitterConnectResponder() + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTION_IN_PROGRESS); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("TwitterConnect") << "Connect successful. content: " << content << LL_ENDL; + + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTED); + } + else if (status != 302) + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTION_FAILED); + log_twitter_connect_error("Connect", status, reason, content.get("error_code"), content.get("error_description")); + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLTwitterConnect::instance().openTwitterWeb(content["location"]); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterShareResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLTwitterShareResponder); +public: + + LLTwitterShareResponder() + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_POSTING); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + toast_user_for_twitter_success(); + LL_DEBUGS("TwitterConnect") << "Post successful. content: " << content << LL_ENDL; + + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_POSTED); + } + else if (status == 404) + { + LLTwitterConnect::instance().connectToTwitter(); + } + else + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_POST_FAILED); + log_twitter_connect_error("Share", status, reason, content.get("error_code"), content.get("error_description")); + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLTwitterConnect::instance().openTwitterWeb(content["location"]); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterDisconnectResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLTwitterDisconnectResponder); +public: + + LLTwitterDisconnectResponder() + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_DISCONNECTING); + } + + void setUserDisconnected() + { + // Clear data + LLTwitterConnect::instance().clearInfo(); + + //Notify state change + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_NOT_CONNECTED); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("TwitterConnect") << "Disconnect successful. content: " << content << LL_ENDL; + setUserDisconnected(); + + } + //User not found so already disconnected + else if(status == 404) + { + LL_DEBUGS("TwitterConnect") << "Already disconnected. content: " << content << LL_ENDL; + setUserDisconnected(); + } + else + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_DISCONNECT_FAILED); + log_twitter_connect_error("Disconnect", status, reason, content.get("error_code"), content.get("error_description")); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterConnectedResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLTwitterConnectedResponder); +public: + + LLTwitterConnectedResponder(bool auto_connect) : mAutoConnect(auto_connect) + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTION_IN_PROGRESS); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("TwitterConnect") << "Connect successful. content: " << content << LL_ENDL; + + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTED); + } + else + { + // show the twitter login page if not connected yet + if (status == 404) + { + if (mAutoConnect) + { + LLTwitterConnect::instance().connectToTwitter(); + } + else + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_NOT_CONNECTED); + } + } + else + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTION_FAILED); + log_twitter_connect_error("Connected", status, reason, content.get("error_code"), content.get("error_description")); + } + } + } + +private: + bool mAutoConnect; +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterInfoResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLTwitterInfoResponder); +public: + + virtual void completed(U32 status, const std::string& reason, const LLSD& info) + { + if (isGoodStatus(status)) + { + llinfos << "Twitter: Info received" << llendl; + LL_DEBUGS("TwitterConnect") << "Getting Twitter info successful. info: " << info << LL_ENDL; + LLTwitterConnect::instance().storeInfo(info); + } + else + { + log_twitter_connect_error("Info", status, reason, info.get("error_code"), info.get("error_description")); + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLTwitterConnect::instance().openTwitterWeb(content["location"]); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +LLTwitterConnect::LLTwitterConnect() +: mConnectionState(TWITTER_NOT_CONNECTED), + mConnected(false), + mInfo(), + mRefreshInfo(false), + mReadFromMaster(false) +{ +} + +void LLTwitterConnect::openTwitterWeb(std::string url) +{ + // Open the URL in an internal browser window without navigation UI + LLFloaterWebContent::Params p; + p.url(url); + p.show_chrome(true); + p.allow_address_entry(false); + p.allow_back_forward_navigation(false); + p.trusted_content(true); + p.clean_browser(true); + LLFloater *floater = LLFloaterReg::showInstance("twitter_web", p); + //the internal web browser has a bug that prevents it from gaining focus unless a mouse event occurs first (it seems). + //So when showing the internal web browser, set focus to it's containing floater "twitter_web". When a mouse event + //occurs on the "webbrowser" panel part of the floater, a mouse cursor will properly show and the "webbrowser" will gain focus. + //twitter_web floater contains the "webbrowser" panel. JIRA: ACME-744 + gFocusMgr.setKeyboardFocus( floater ); + + //LLUrlAction::openURLExternal(url); +} + +std::string LLTwitterConnect::getTwitterConnectURL(const std::string& route, bool include_read_from_master) +{ + std::string url(""); + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + //url = "http://pdp15.lindenlab.com/twitter/agent/" + gAgentID.asString(); // TEMPORARY FOR TESTING - CHO + url = regionp->getCapability("TwitterConnect"); + url += route; + + if (include_read_from_master && mReadFromMaster) + { + url += "?read_from_master=true"; + } + } + return url; +} + +void LLTwitterConnect::connectToTwitter(const std::string& request_token, const std::string& oauth_verifier) +{ + LLSD body; + if (!request_token.empty()) + body["request_token"] = request_token; + if (!oauth_verifier.empty()) + body["oauth_verifier"] = oauth_verifier; + + LLHTTPClient::put(getTwitterConnectURL("/connection"), body, new LLTwitterConnectResponder()); +} + +void LLTwitterConnect::disconnectFromTwitter() +{ + LLHTTPClient::del(getTwitterConnectURL("/connection"), new LLTwitterDisconnectResponder()); +} + +void LLTwitterConnect::checkConnectionToTwitter(bool auto_connect) +{ + const bool follow_redirects = false; + const F32 timeout = HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getTwitterConnectURL("/connection", true), new LLTwitterConnectedResponder(auto_connect), + LLSD(), timeout, follow_redirects); +} + +void LLTwitterConnect::loadTwitterInfo() +{ + if(mRefreshInfo) + { + const bool follow_redirects = false; + const F32 timeout = HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getTwitterConnectURL("/info", true), new LLTwitterInfoResponder(), + LLSD(), timeout, follow_redirects); + } +} + +void LLTwitterConnect::uploadPhoto(const std::string& image_url, const std::string& status) +{ + LLSD body; + body["image"] = image_url; + body["status"] = status; + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::post(getTwitterConnectURL("/share/photo", true), body, new LLTwitterShareResponder()); +} + +void LLTwitterConnect::uploadPhoto(LLPointer<LLImageFormatted> image, const std::string& status) +{ + std::string imageFormat; + if (dynamic_cast<LLImagePNG*>(image.get())) + { + imageFormat = "png"; + } + else if (dynamic_cast<LLImageJPEG*>(image.get())) + { + imageFormat = "jpg"; + } + else + { + llwarns << "Image to upload is not a PNG or JPEG" << llendl; + return; + } + + // All this code is mostly copied from LLWebProfile::post() + const std::string boundary = "----------------------------0123abcdefab"; + + LLSD headers; + headers["Content-Type"] = "multipart/form-data; boundary=" + boundary; + + std::ostringstream body; + + // *NOTE: The order seems to matter. + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"status\"\r\n\r\n" + << status << "\r\n"; + + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"image\"; filename=\"snapshot." << imageFormat << "\"\r\n" + << "Content-Type: image/" << imageFormat << "\r\n\r\n"; + + // Insert the image data. + // *FIX: Treating this as a string will probably screw it up ... + U8* image_data = image->getData(); + for (S32 i = 0; i < image->getDataSize(); ++i) + { + body << image_data[i]; + } + + body << "\r\n--" << boundary << "--\r\n"; + + // postRaw() takes ownership of the buffer and releases it later. + size_t size = body.str().size(); + U8 *data = new U8[size]; + memcpy(data, body.str().data(), size); + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::postRaw(getTwitterConnectURL("/share/photo", true), data, size, new LLTwitterShareResponder(), headers); +} + +void LLTwitterConnect::updateStatus(const std::string& status) +{ + LLSD body; + body["status"] = status; + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::post(getTwitterConnectURL("/share/status", true), body, new LLTwitterShareResponder()); +} + +void LLTwitterConnect::storeInfo(const LLSD& info) +{ + mInfo = info; + mRefreshInfo = false; + + sInfoWatcher->post(info); +} + +const LLSD& LLTwitterConnect::getInfo() const +{ + return mInfo; +} + +void LLTwitterConnect::clearInfo() +{ + mInfo = LLSD(); +} + +void LLTwitterConnect::setDataDirty() +{ + mRefreshInfo = true; +} + +void LLTwitterConnect::setConnectionState(LLTwitterConnect::EConnectionState connection_state) +{ + if(connection_state == TWITTER_CONNECTED) + { + mReadFromMaster = true; + setConnected(true); + setDataDirty(); + } + else if(connection_state == TWITTER_NOT_CONNECTED) + { + setConnected(false); + } + else if(connection_state == TWITTER_POSTED) + { + mReadFromMaster = false; + } + + if (mConnectionState != connection_state) + { + // set the connection state before notifying watchers + mConnectionState = connection_state; + + LLSD state_info; + state_info["enum"] = connection_state; + sStateWatcher->post(state_info); + } +} + +void LLTwitterConnect::setConnected(bool connected) +{ + mConnected = connected; +} diff --git a/indra/newview/lltwitterconnect.h b/indra/newview/lltwitterconnect.h new file mode 100644 index 0000000000..c1df13f18c --- /dev/null +++ b/indra/newview/lltwitterconnect.h @@ -0,0 +1,99 @@ +/** + * @file lltwitterconnect.h + * @author Merov, Cho + * @brief Connection to Twitter Service + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTWITTERCONNECT_H +#define LL_LLTWITTERCONNECT_H + +#include "llsingleton.h" +#include "llimage.h" + +class LLEventPump; + +/** + * @class LLTwitterConnect + * + * Manages authentication to, and interaction with, a web service allowing the + * the viewer to post status updates and upload photos to Twitter. + */ +class LLTwitterConnect : public LLSingleton<LLTwitterConnect> +{ + LOG_CLASS(LLTwitterConnect); +public: + enum EConnectionState + { + TWITTER_NOT_CONNECTED = 0, + TWITTER_CONNECTION_IN_PROGRESS = 1, + TWITTER_CONNECTED = 2, + TWITTER_CONNECTION_FAILED = 3, + TWITTER_POSTING = 4, + TWITTER_POSTED = 5, + TWITTER_POST_FAILED = 6, + TWITTER_DISCONNECTING = 7, + TWITTER_DISCONNECT_FAILED = 8 + }; + + void connectToTwitter(const std::string& request_token = "", const std::string& oauth_verifier = ""); // Initiate the complete Twitter connection. Please use checkConnectionToTwitter() in normal use. + void disconnectFromTwitter(); // Disconnect from the Twitter service. + void checkConnectionToTwitter(bool auto_connect = false); // Check if an access token is available on the Twitter service. If not, call connectToTwitter(). + + void loadTwitterInfo(); + void uploadPhoto(const std::string& image_url, const std::string& status); + void uploadPhoto(LLPointer<LLImageFormatted> image, const std::string& status); + void updateStatus(const std::string& status); + + void storeInfo(const LLSD& info); + const LLSD& getInfo() const; + void clearInfo(); + void setDataDirty(); + + void setConnectionState(EConnectionState connection_state); + void setConnected(bool connected); + bool isConnected() { return mConnected; } + bool isTransactionOngoing() { return ((mConnectionState == TWITTER_CONNECTION_IN_PROGRESS) || (mConnectionState == TWITTER_POSTING) || (mConnectionState == TWITTER_DISCONNECTING)); } + EConnectionState getConnectionState() { return mConnectionState; } + + void openTwitterWeb(std::string url); + +private: + friend class LLSingleton<LLTwitterConnect>; + + LLTwitterConnect(); + ~LLTwitterConnect() {}; + std::string getTwitterConnectURL(const std::string& route = "", bool include_read_from_master = false); + + EConnectionState mConnectionState; + BOOL mConnected; + LLSD mInfo; + bool mRefreshInfo; + bool mReadFromMaster; + + static boost::scoped_ptr<LLEventPump> sStateWatcher; + static boost::scoped_ptr<LLEventPump> sInfoWatcher; + static boost::scoped_ptr<LLEventPump> sContentWatcher; +}; + +#endif // LL_LLTWITTERCONNECT_H diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index a8eeddb798..1f25110aa3 100755 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -53,13 +53,15 @@ #include "llfloaterconversationlog.h" #include "llfloaterconversationpreview.h" #include "llfloaterdeleteenvpreset.h" +#include "llfloaterdestinations.h" #include "llfloaterdisplayname.h" #include "llfloatereditdaycycle.h" #include "llfloatereditsky.h" #include "llfloatereditwater.h" #include "llfloaterenvironmentsettings.h" #include "llfloaterevent.h" -#include "llfloaterdestinations.h" +#include "llfloaterfacebook.h" +#include "llfloaterflickr.h" #include "llfloaterfonttest.h" #include "llfloatergesture.h" #include "llfloatergodtools.h" @@ -104,7 +106,6 @@ #include "llfloatersettingsdebug.h" #include "llfloatersidepanelcontainer.h" #include "llfloatersnapshot.h" -#include "llfloatersocial.h" #include "llfloatersounddevices.h" #include "llfloaterspellchecksettings.h" #include "llfloatertelehub.h" @@ -116,6 +117,7 @@ #include "llfloatertopobjects.h" #include "llfloatertoybox.h" #include "llfloatertranslationsettings.h" +#include "llfloatertwitter.h" #include "llfloateruipreview.h" #include "llfloatervoiceeffect.h" #include "llfloatervoicevolume.h" @@ -306,7 +308,6 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("sell_land", "floater_sell_land.xml", &LLFloaterSellLand::buildFloater); LLFloaterReg::add("settings_debug", "floater_settings_debug.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSettingsDebug>); LLFloaterReg::add("sound_devices", "floater_sound_devices.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSoundDevices>); - LLFloaterReg::add("social", "floater_social.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSocial>); LLFloaterReg::add("stats", "floater_stats.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloater>); LLFloaterReg::add("start_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRunQueue>); LLFloaterReg::add("stop_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterNotRunQueue>); @@ -314,8 +315,15 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("search", "floater_search.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSearch>); LLFloaterReg::add("my_profile", "floater_my_web_profile.xml", (LLFloaterBuildFunc)&LLFloaterWebProfile::create); LLFloaterReg::add("profile", "floater_web_profile.xml", (LLFloaterBuildFunc)&LLFloaterWebProfile::create); - LLFloaterReg::add("how_to", "floater_how_to.xml", (LLFloaterBuildFunc)&LLFloaterWebContent::create); + LLFloaterReg::add("how_to", "floater_how_to.xml", (LLFloaterBuildFunc)&LLFloaterWebContent::create); + LLFloaterReg::add("fbc_web", "floater_fbc_web.xml", (LLFloaterBuildFunc)&LLFloaterWebContent::create); + LLFloaterReg::add("flickr_web", "floater_fbc_web.xml", (LLFloaterBuildFunc)&LLFloaterWebContent::create); + LLFloaterReg::add("twitter_web", "floater_fbc_web.xml", (LLFloaterBuildFunc)&LLFloaterWebContent::create); + + LLFloaterReg::add("facebook", "floater_facebook.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFacebook>); + LLFloaterReg::add("flickr", "floater_flickr.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFlickr>); + LLFloaterReg::add("twitter", "floater_twitter.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterTwitter>); LLFloaterUIPreviewUtil::registerFloater(); LLFloaterReg::add("upload_anim_bvh", "floater_animation_bvh_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterBvhPreview>, "upload"); diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 21fb8d519b..3a3b00a604 100755 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -429,7 +429,7 @@ viewer_media_t LLViewerMedia::updateMediaImpl(LLMediaEntry* media_entry, const s // Try to find media with the same media ID viewer_media_t media_impl = getMediaImplFromTextureID(media_entry->getMediaID()); - lldebugs << "called, current URL is \"" << media_entry->getCurrentURL() + lldebugs << "called, current URL is \"" << media_entry->getCurrentURL() << "\", previous URL is \"" << previous_url << "\", update_from_self is " << (update_from_self?"true":"false") << llendl; @@ -1534,7 +1534,7 @@ void LLViewerMedia::createSpareBrowserMediaSource() // popping up at the moment we start a media plugin. if (!sSpareBrowserMediaSource && !gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins")) { - // The null owner will keep the browser plugin from fully initializing + // The null owner will keep the browser plugin from fully initializing // (specifically, it keeps LLPluginClassMedia from negotiating a size change, // which keeps MediaPluginWebkit::initBrowserWindow from doing anything until we have some necessary data, like the background color) sSpareBrowserMediaSource = LLViewerMediaImpl::newSourceFromMediaType("text/html", NULL, 0, 0); @@ -1543,7 +1543,7 @@ void LLViewerMedia::createSpareBrowserMediaSource() ///////////////////////////////////////////////////////////////////////////////////////// // static -LLPluginClassMedia* LLViewerMedia::getSpareBrowserMediaSource() +LLPluginClassMedia* LLViewerMedia::getSpareBrowserMediaSource() { LLPluginClassMedia* result = sSpareBrowserMediaSource; sSpareBrowserMediaSource = NULL; @@ -1592,7 +1592,7 @@ std::string LLViewerMedia::getParcelAudioURL() // static void LLViewerMedia::initClass() { - gIdleCallbacks.addFunction(LLViewerMedia::updateMedia, NULL); + gIdleCallbacks.addFunction(LLViewerMedia::updateMedia, NULL); sTeleportFinishConnection = LLViewerParcelMgr::getInstance()-> setTeleportFinishedCallback(boost::bind(&LLViewerMedia::onTeleportFinished)); } @@ -1669,7 +1669,8 @@ LLViewerMediaImpl::LLViewerMediaImpl( const LLUUID& texture_id, mNavigateSuspendedDeferred(false), mIsUpdated(false), mTrustedBrowser(false), - mZoomFactor(1.0) + mZoomFactor(1.0), + mCleanBrowser(false) { // Set up the mute list observer if it hasn't been set up already. @@ -1793,14 +1794,16 @@ void LLViewerMediaImpl::setMediaType(const std::string& media_type) ////////////////////////////////////////////////////////////////////////////////////////// /*static*/ -LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height, const std::string target) +LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height, const std::string target, bool clean_browser) { std::string plugin_basename = LLMIMETypes::implType(media_type); LLPluginClassMedia* media_source = NULL; // HACK: we always try to keep a spare running webkit plugin around to improve launch times. // If a spare was already created before PluginAttachDebuggerToPlugins was set, don't use it. - if(plugin_basename == "media_plugin_webkit" && !gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins")) + // Do not use a spare if launching with full viewer control (e.g. Facebook, Twitter and few others) + if ((plugin_basename == "media_plugin_webkit") && + !gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins") && !clean_browser) { media_source = LLViewerMedia::getSpareBrowserMediaSource(); if(media_source) @@ -1812,7 +1815,6 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ return media_source; } } - if(plugin_basename.empty()) { LL_WARNS_ONCE("Media") << "Couldn't find plugin for media type " << media_type << LL_ENDL; @@ -1856,18 +1858,18 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ // collect 'cookies enabled' setting from prefs and send to embedded browser bool cookies_enabled = gSavedSettings.getBOOL( "CookiesEnabled" ); - media_source->enable_cookies( cookies_enabled ); + media_source->enable_cookies( cookies_enabled || clean_browser); // collect 'plugins enabled' setting from prefs and send to embedded browser bool plugins_enabled = gSavedSettings.getBOOL( "BrowserPluginsEnabled" ); - media_source->setPluginsEnabled( plugins_enabled ); + media_source->setPluginsEnabled( plugins_enabled || clean_browser); // collect 'javascript enabled' setting from prefs and send to embedded browser bool javascript_enabled = gSavedSettings.getBOOL( "BrowserJavascriptEnabled" ); - media_source->setJavascriptEnabled( javascript_enabled ); + media_source->setJavascriptEnabled( javascript_enabled || clean_browser); bool media_plugin_debugging_enabled = gSavedSettings.getBOOL("MediaPluginDebugging"); - media_source->enableMediaPluginDebugging( media_plugin_debugging_enabled ); + media_source->enableMediaPluginDebugging( media_plugin_debugging_enabled || clean_browser); media_source->setTarget(target); @@ -1922,7 +1924,7 @@ bool LLViewerMediaImpl::initializePlugin(const std::string& media_type) // Save the MIME type that really caused the plugin to load mCurrentMimeType = mMimeType; - LLPluginClassMedia* media_source = newSourceFromMediaType(mMimeType, this, mMediaWidth, mMediaHeight, mTarget); + LLPluginClassMedia* media_source = newSourceFromMediaType(mMimeType, this, mMediaWidth, mMediaHeight, mTarget, mCleanBrowser); if (media_source) { @@ -2543,7 +2545,7 @@ void LLViewerMediaImpl::unload() } ////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mime_type, bool rediscover_type, bool server_request) +void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mime_type, bool rediscover_type, bool server_request, bool clean_browser) { cancelMimeTypeProbe(); @@ -2556,6 +2558,7 @@ void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mi // Always set the current URL and MIME type. mMediaURL = url; mMimeType = mime_type; + mCleanBrowser = clean_browser; // Clear the current media URL, since it will no longer be correct. mCurrentMediaURL.clear(); diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index fff5b3fc08..6803adfaa2 100755 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -234,7 +234,7 @@ public: void navigateReload(); void navigateHome(); void unload(); - void navigateTo(const std::string& url, const std::string& mime_type = "", bool rediscover_type = false, bool server_request = false); + void navigateTo(const std::string& url, const std::string& mime_type = "", bool rediscover_type = false, bool server_request = false, bool clean_browser = false); void navigateInternal(); void navigateStop(); bool handleKeyHere(KEY key, MASK mask); @@ -289,7 +289,7 @@ public: void setTarget(const std::string& target) { mTarget = target; } // utility function to create a ready-to-use media instance from a desired media type. - static LLPluginClassMedia* newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height, const std::string target = LLStringUtil::null); + static LLPluginClassMedia* newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height, const std::string target = LLStringUtil::null, bool clean_browser = false); // Internally set our desired browser user agent string, including // the Second Life version and skin name. Used because we can @@ -464,6 +464,7 @@ private: bool mTrustedBrowser; std::string mTarget; LLNotificationPtr mNotification; + bool mCleanBrowser; // force the creation of a clean browsing target with full options enabled private: BOOL mIsUpdated ; diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index be78603e2d..a17dd953ad 100755 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -215,7 +215,8 @@ std::string build_extensions_string(LLFilePicker::ELoadFilter filter) #endif case LLFilePicker::FFLOAD_XML: return XML_EXTENSIONS; - case LLFilePicker::FFLOAD_ALL: + case LLFilePicker::FFLOAD_ALL: + case LLFilePicker::FFLOAD_EXE: return ALL_FILE_EXTENSIONS; #endif default: diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 6018c2d250..f56a2dd04f 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1588,7 +1588,8 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("EstateChangeInfo"); capabilityNames.append("EventQueueGet"); capabilityNames.append("FacebookConnect"); - //capabilityNames.append("FacebookRedirect"); + capabilityNames.append("FlickrConnect"); + capabilityNames.append("TwitterConnect"); if (gSavedSettings.getBOOL("UseHTTPInventory")) { diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 94c187e21a..751558fc93 100755 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -133,6 +133,8 @@ with the same filename but different name <texture name="Command_Chat_Icon" file_name="toolbar_icons/chat.png" preload="true" /> <texture name="Command_Compass_Icon" file_name="toolbar_icons/land.png" preload="true" /> <texture name="Command_Destinations_Icon" file_name="toolbar_icons/destinations.png" preload="true" /> + <texture name="Command_Facebook_Icon" file_name="toolbar_icons/facebook.png" preload="true" /> + <texture name="Command_Flickr_Icon" file_name="toolbar_icons/flickr.png" preload="true" /> <texture name="Command_Gestures_Icon" file_name="toolbar_icons/gestures.png" preload="true" /> <texture name="Command_HowTo_Icon" file_name="toolbar_icons/howto.png" preload="true" /> <texture name="Command_Inventory_Icon" file_name="toolbar_icons/inventory.png" preload="true" /> @@ -148,9 +150,9 @@ with the same filename but different name <texture name="Command_Preferences_Icon" file_name="toolbar_icons/preferences.png" preload="true" /> <texture name="Command_Profile_Icon" file_name="toolbar_icons/profile.png" preload="true" /> <texture name="Command_Search_Icon" file_name="toolbar_icons/search.png" preload="true" /> - <texture name="Command_Social_Icon" file_name="toolbar_icons/facebook.png" preload="true" /> <texture name="Command_Snapshot_Icon" file_name="toolbar_icons/snapshot.png" preload="true" /> <texture name="Command_Speak_Icon" file_name="toolbar_icons/speak.png" preload="true" /> + <texture name="Command_Twitter_Icon" file_name="toolbar_icons/twitter.png" preload="true" /> <texture name="Command_View_Icon" file_name="toolbar_icons/view.png" preload="true" /> <texture name="Command_Voice_Icon" file_name="toolbar_icons/nearbyvoice.png" preload="true" /> <texture name="Caret_Bottom_Icon" file_name="toolbar_icons/caret_bottom.png" preload="true" scale.left="1" scale.top="23" scale.right="15" scale.bottom="1" /> diff --git a/indra/newview/skins/default/textures/toolbar_icons/flickr.png b/indra/newview/skins/default/textures/toolbar_icons/flickr.png Binary files differnew file mode 100644 index 0000000000..7fce9f0df2 --- /dev/null +++ b/indra/newview/skins/default/textures/toolbar_icons/flickr.png diff --git a/indra/newview/skins/default/textures/toolbar_icons/twitter.png b/indra/newview/skins/default/textures/toolbar_icons/twitter.png Binary files differnew file mode 100644 index 0000000000..a99c490887 --- /dev/null +++ b/indra/newview/skins/default/textures/toolbar_icons/twitter.png diff --git a/indra/newview/skins/default/xui/en/floater_social.xml b/indra/newview/skins/default/xui/en/floater_facebook.xml index b7ff374d5f..c1ff8571e9 100644 --- a/indra/newview/skins/default/xui/en/floater_social.xml +++ b/indra/newview/skins/default/xui/en/floater_facebook.xml @@ -3,9 +3,9 @@ positioning="cascading" can_close="true" can_resize="false" - help_topic="floater_social" + help_topic="floater_facebook" layout="topleft" - name="floater_social" + name="floater_facebook" save_rect="true" single_instance="true" reuse_instance="true" @@ -28,31 +28,32 @@ tab_position="top" top="7" height="437" - halign="center"> + halign="center" + use_highlighting_on_hover="true"> <panel - filename="panel_social_status.xml" - class="llsocialstatuspanel" + filename="panel_facebook_status.xml" + class="llfacebookstatuspanel" follows="all" label="STATUS" - name="panel_social_status"/> + name="panel_facebook_status"/> <panel - filename="panel_social_photo.xml" - class="llsocialphotopanel" + filename="panel_facebook_photo.xml" + class="llfacebookphotopanel" follows="all" label="PHOTO" - name="panel_social_photo"/> + name="panel_facebook_photo"/> <panel - filename="panel_social_place.xml" - class="llsocialcheckinpanel" + filename="panel_facebook_place.xml" + class="llfacebookcheckinpanel" follows="all" label="CHECK IN" - name="panel_social_place"/> + name="panel_facebook_place"/> <panel - filename="panel_social_account.xml" - class="llsocialaccountpanel" + filename="panel_facebook_account.xml" + class="llfacebookaccountpanel" follows="all" label="ACCOUNT" - name="panel_social_account"/> + name="panel_facebook_account"/> </tab_container> <panel name="connection_status_panel" diff --git a/indra/newview/skins/default/xui/en/floater_flickr.xml b/indra/newview/skins/default/xui/en/floater_flickr.xml new file mode 100644 index 0000000000..1a9ffd0489 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_flickr.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<floater + positioning="cascading" + can_close="true" + can_resize="false" + help_topic="floater_flickr" + layout="topleft" + name="floater_flickr" + save_rect="true" + single_instance="true" + reuse_instance="true" + title="UPLOAD TO FLICKR" + height="622" + width="304"> + <panel + height="622" + width="304" + visible="true" + name="background" + follows="all" + top="0" + left="0"> + <tab_container + name="tabs" + tab_group="1" + tab_min_width="70" + tab_height="30" + tab_position="top" + top="7" + height="577" + halign="center" + use_highlighting_on_hover="true"> + <panel + filename="panel_flickr_photo.xml" + class="llflickrphotopanel" + follows="all" + label="PHOTO" + name="panel_flickr_photo"/> + <panel + filename="panel_flickr_account.xml" + class="llflickraccountpanel" + follows="all" + label="ACCOUNT" + name="panel_flickr_account"/> + </tab_container> + <panel + name="connection_status_panel" + follows="left|bottom|right" + height="24"> + <text + name="connection_error_text" + type="string" + follows="left|bottom|right" + top="5" + left="9" + width="250" + height="20" + wrap="true" + halign="left" + valign="center" + text_color="DrYellow" + font="SansSerif"> + Error + </text> + <loading_indicator + follows="left|bottom|right" + height="24" + width="24" + name="connection_loading_indicator" + top="2" + left="9" + visible="true"/> + <text + name="connection_loading_text" + type="string" + follows="left|bottom|right" + top="5" + left_pad="5" + width="250" + height="20" + wrap="true" + halign="left" + valign="center" + text_color="EmphasisColor" + font="SansSerif"> + Loading... + </text> + </panel> + </panel> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_snapshot.xml b/indra/newview/skins/default/xui/en/floater_snapshot.xml index 853c209bca..5e3c7d4e3d 100755 --- a/indra/newview/skins/default/xui/en/floater_snapshot.xml +++ b/indra/newview/skins/default/xui/en/floater_snapshot.xml @@ -10,7 +10,7 @@ help_topic="snapshot" save_rect="true" save_visibility="false" - title="SNAPSHOT PREVIEW" + title="SNAPSHOT" width="470"> <floater.string name="unknown"> @@ -381,5 +381,33 @@ top_pad="8" width="180" name="auto_snapshot_check" /> + <!-- If we ever want to add filters to the snapshot panel... + <text + type="string" + length="1" + follows="left|top" + height="13" + layout="topleft" + left="10" + name="layer_type_label" + top_pad="10" + width="50"> + Filter: + </text> + <combo_box + control_name="PhotoFilters" + follows="left|right|top" + name="filters_combobox" + tool_tip="Image filters" + top_pad="8" + left="30" + height="21" + width="135"> + <combo_box.item + label="No Filter" + name="NoFilter" + value="NoFilter" /> + </combo_box> + --> </panel> </floater> diff --git a/indra/newview/skins/default/xui/en/floater_twitter.xml b/indra/newview/skins/default/xui/en/floater_twitter.xml new file mode 100644 index 0000000000..aa5bfce2e9 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_twitter.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<floater + positioning="cascading" + can_close="true" + can_resize="false" + help_topic="floater_twitter" + layout="topleft" + name="floater_twitter" + save_rect="true" + single_instance="true" + reuse_instance="true" + title="TWITTER" + height="502" + width="304"> + <panel + height="502" + width="304" + visible="true" + name="background" + follows="all" + top="0" + left="0"> + <tab_container + name="tabs" + tab_group="1" + tab_min_width="70" + tab_height="30" + tab_position="top" + top="7" + height="457" + halign="center" + use_highlighting_on_hover="true"> + <panel + filename="panel_twitter_photo.xml" + class="lltwitterphotopanel" + follows="all" + label="COMPOSE" + name="panel_twitter_photo"/> + <panel + filename="panel_twitter_account.xml" + class="lltwitteraccountpanel" + follows="all" + label="ACCOUNT" + name="panel_twitter_account"/> + </tab_container> + <panel + name="connection_status_panel" + follows="left|bottom|right" + height="24"> + <text + name="connection_error_text" + type="string" + follows="left|bottom|right" + top="5" + left="9" + width="250" + height="20" + wrap="true" + halign="left" + valign="center" + text_color="DrYellow" + font="SansSerif"> + Error + </text> + <loading_indicator + follows="left|bottom|right" + height="24" + width="24" + name="connection_loading_indicator" + top="2" + left="9" + visible="true"/> + <text + name="connection_loading_text" + type="string" + follows="left|bottom|right" + top="5" + left_pad="5" + width="250" + height="20" + wrap="true" + halign="left" + valign="center" + text_color="EmphasisColor" + font="SansSerif"> + Loading... + </text> + </panel> + </panel> +</floater> diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 64de010eb5..e82f6339d4 100755 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -16,14 +16,6 @@ parameter="agent" /> </menu_item_call> <menu_item_call - label="Post to Facebook..." - name="PostToFacebook"> - <menu_item_call.on_click - function="Floater.Toggle" - parameter="social"/> - </menu_item_call> - <menu_item_separator/> - <menu_item_call label="Appearance..." name="ChangeOutfit"> <menu_item_call.on_click @@ -288,6 +280,28 @@ parameter="conversation" /> </menu_item_check> <menu_item_separator/> + <menu_item_call + label="Facebook..." + name="Facebook"> + <menu_item_call.on_click + function="Floater.Toggle" + parameter="facebook"/> + </menu_item_call> + <menu_item_call + label="Twitter..." + name="Twitter"> + <menu_item_call.on_click + function="Floater.Toggle" + parameter="twitter"/> + </menu_item_call> + <menu_item_call + label="Flickr..." + name="Flickr"> + <menu_item_call.on_click + function="Floater.Toggle" + parameter="flickr"/> + </menu_item_call> + <menu_item_separator/> <menu label="Voice morphing" name="VoiceMorphing" diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index a93601f5fd..7e3d3e5d59 100755 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -6044,7 +6044,21 @@ Please select at least one type of content to search (General, Moderate, or Adul type="notifytip"> [MESSAGE] </notification> - + + <notification + icon="notify.tga" + name="FlickrConnect" + type="notifytip"> + [MESSAGE] + </notification> + + <notification + icon="notify.tga" + name="TwitterConnect" + type="notifytip"> + [MESSAGE] + </notification> + <notification icon="notify.tga" name="PaymentReceived" diff --git a/indra/newview/skins/default/xui/en/panel_social_account.xml b/indra/newview/skins/default/xui/en/panel_facebook_account.xml index d7235396fe..f091d2e9b9 100644 --- a/indra/newview/skins/default/xui/en/panel_social_account.xml +++ b/indra/newview/skins/default/xui/en/panel_facebook_account.xml @@ -2,7 +2,7 @@ height="400" width="304" layout="topleft" - name="panel_social_account"> + name="panel_facebook_account"> <string name="facebook_connected" value="You are connected to Facebook as:" /> diff --git a/indra/newview/skins/default/xui/en/panel_social_photo.xml b/indra/newview/skins/default/xui/en/panel_facebook_photo.xml index a55613b52a..0cde43a20c 100644 --- a/indra/newview/skins/default/xui/en/panel_social_photo.xml +++ b/indra/newview/skins/default/xui/en/panel_facebook_photo.xml @@ -2,7 +2,7 @@ height="400" width="304" layout="topleft" - name="panel_social_photo"> + name="panel_facebook_photo"> <layout_stack layout="topleft" border_size="0" @@ -15,7 +15,7 @@ name="snapshot_panel" height="367"> <combo_box - control_name="SocialPhotoResolution" + control_name="FacebookPhotoResolution" follows="left|top" top="6" left="9" @@ -39,20 +39,25 @@ label="1024x768" name="1024x768" value="[i1024,i768]" /> + <combo_box.item + label="1200x630" + name="1200x630" + value="[i1200,i630]" /> + </combo_box> + <combo_box + control_name="FacebookPhotoFilters" + follows="right|top" + name="filters_combobox" + tool_tip="Image filters" + top="6" + left="165" + height="21" + width="135"> + <combo_box.item + label="No Filter" + name="NoFilter" + value="NoFilter" /> </combo_box> - <text - follows="left|top" - font="SansSerifSmall" - height="14" - left="208" - length="1" - halign="right" - name="file_size_label" - top="9" - type="string" - width="50"> - [SIZE] KB - </text> <panel height="150" width="250" diff --git a/indra/newview/skins/default/xui/en/panel_social_place.xml b/indra/newview/skins/default/xui/en/panel_facebook_place.xml index 13e94f6998..1eea8f317b 100644 --- a/indra/newview/skins/default/xui/en/panel_social_place.xml +++ b/indra/newview/skins/default/xui/en/panel_facebook_place.xml @@ -2,7 +2,7 @@ height="400" width="304" layout="topleft" - name="panel_social_place"> + name="panel_facebook_place"> <layout_stack layout="topleft" border_size="0" diff --git a/indra/newview/skins/default/xui/en/panel_social_status.xml b/indra/newview/skins/default/xui/en/panel_facebook_status.xml index 54cfa3f524..50e15c2e80 100644 --- a/indra/newview/skins/default/xui/en/panel_social_status.xml +++ b/indra/newview/skins/default/xui/en/panel_facebook_status.xml @@ -2,7 +2,7 @@ height="400" width="304" layout="topleft" - name="panel_social_status"> + name="panel_facebook_status"> <layout_stack layout="topleft" border_size="0" diff --git a/indra/newview/skins/default/xui/en/panel_flickr_account.xml b/indra/newview/skins/default/xui/en/panel_flickr_account.xml new file mode 100644 index 0000000000..3a38852049 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_flickr_account.xml @@ -0,0 +1,75 @@ +<panel + height="540" + width="304" + layout="topleft" + name="panel_flickr_account"> + <string + name="flickr_connected" + value="You are connected to Flickr as:" /> + <string + name="flickr_disconnected" + value="Not connected to Flickr" /> + <text + layout="topleft" + length="1" + follows="top|left" + font="SansSerif" + height="16" + left="9" + name="account_caption_label" + top="21" + type="string"> + Not connected to Flickr. + </text> + <text + layout="topleft" + top_pad="2" + length="1" + follows="top|left" + font="SansSerif" + height="16" + left="9" + name="account_name_label" + parse_urls="true" + type="string"/> + <panel + layout="topleft" + name="panel_buttons" + height="345" + left="9"> + <button + layout="topleft" + follows="left|top" + top_pad="9" + visible="true" + height="23" + label="Connect..." + name="connect_btn" + width="210"> + <commit_callback function="SocialSharing.Connect"/> + </button> + + <button + layout="topleft" + follows="left|top" + top_delta="0" + height="23" + label="Disconnect" + name="disconnect_btn" + width="210" + visible="false"> + <commit_callback function="SocialSharing.Disconnect"/> + </button> + <text + layout="topleft" + length="1" + follows="top|left" + height="16" + left="0" + name="account_learn_more_label" + top_pad="20" + type="string"> + [http://community.secondlife.com/t5/English-Knowledge-Base/Second-Life-Share/ta-p/2149711 Learn about posting to Flickr] + </text> + </panel> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_flickr_photo.xml b/indra/newview/skins/default/xui/en/panel_flickr_photo.xml new file mode 100644 index 0000000000..4516c01670 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_flickr_photo.xml @@ -0,0 +1,231 @@ + <panel + height="540" + width="304" + layout="topleft" + name="panel_flickr_photo"> + <layout_stack + layout="topleft" + border_size="0" + height="532" + follows="all" + orientation="vertical" + name="stack_photo" + top="8"> + <layout_panel + name="snapshot_panel" + height="507"> + <combo_box + control_name="FlickrPhotoResolution" + follows="left|top" + top="6" + left="9" + name="resolution_combobox" + tool_tip="Image resolution" + height="21" + width="135"> + <combo_box.item + label="Current Window" + name="CurrentWindow" + value="[i0,i0]" /> + <combo_box.item + label="640x480" + name="640x480" + value="[i640,i480]" /> + <combo_box.item + label="800x600" + name="800x600" + value="[i800,i600]" /> + <combo_box.item + label="1024x768" + name="1024x768" + value="[i1024,i768]" /> + </combo_box> + <combo_box + control_name="FlickrPhotoFilters" + follows="right|top" + name="filters_combobox" + tool_tip="Image filters" + top="6" + left="165" + height="21" + width="135"> + <combo_box.item + label="No Filter" + name="NoFilter" + value="NoFilter" /> + </combo_box> + <panel + height="150" + width="250" + visible="true" + name="thumbnail_placeholder" + top="33" + follows="left|top" + left="9"> + </panel> + <button + follows="left|top" + height="23" + label="Refresh" + left="9" + top_pad="5" + name="new_snapshot_btn" + tool_tip="Click to refresh" + visible="true" + width="100" > + <button.commit_callback + function="SocialSharing.RefreshPhoto" /> + </button> + <text + follows="left|top" + font="SansSerif" + text_color="EmphasisColor" + height="14" + top_pad="-19" + left_pad="-20" + length="1" + halign="center" + name="working_lbl" + translate="false" + type="string" + visible="true" + width="150"> + Refreshing... + </text> + <text + length="1" + follows="top|left|right" + font="SansSerif" + height="16" + left="9" + name="title_label" + top_pad="20" + type="string"> + Title: + </text> + <line_editor + follows="left|top" + height="20" + width="250" + left="9" + length="1" + max_length="256" + name="photo_title" + type="string"> + </line_editor> + <text + length="1" + follows="top|left|right" + font="SansSerif" + height="16" + left="9" + name="description_label" + top_pad="10" + type="string"> + Description: + </text> + <text_editor + follows="left|top" + height="50" + width="250" + left="9" + length="1" + max_length="700" + name="photo_description" + type="string" + word_wrap="true"> + </text_editor> + <check_box + follows="left|top" + initial_value="true" + label="Include SL location at end of description" + name="add_location_cb" + left="9" + height="16" + top_pad="8"/> + <text + length="1" + follows="top|left|right" + font="SansSerif" + height="16" + left="9" + name="tags_label" + top_pad="10" + type="string"> + Tags: + </text> + <text + length="1" + follows="top|left" + font="SansSerifSmall" + text_color="White_50" + height="30" + name="tags_help_label" + left="50" + top_pad="-16" + type="string"> +Separate tags with spaces +Use "" for multi-word tags + </text> + <text_editor + follows="left|top" + height="50" + width="250" + left="9" + length="1" + max_length="700" + name="photo_tags" + type="string" + word_wrap="true"> + </text_editor> + <combo_box + control_name="FlickrPhotoRating" + follows="left|top" + top_pad="16" + left="9" + name="rating_combobox" + tool_tip="Flickr content rating" + height="21" + width="250"> + <combo_box.item + label="Safe Flickr rating" + name="SafeRating" + value="1" /> + <combo_box.item + label="Moderate Flickr rating" + name="ModerateRating" + value="2" /> + <combo_box.item + label="Restricted Flickr rating" + name="RestrictedRating" + value="3" /> + </combo_box> + </layout_panel> + <layout_panel + name="photo_button_panel" + height="25"> + <button + follows="left|top" + top="0" + left="9" + height="23" + label="Upload" + name="post_photo_btn" + width="100"> + <button.commit_callback + function="SocialSharing.SendPhoto" /> + </button> + <button + follows="left|top" + height="23" + label="Cancel" + name="cancel_photo_btn" + left_pad="15" + top_delta="0" + width="100"> + <button.commit_callback + function="SocialSharing.Cancel" /> + </button> + </layout_panel> + </layout_stack> + </panel> diff --git a/indra/newview/skins/default/xui/en/panel_snapshot_options.xml b/indra/newview/skins/default/xui/en/panel_snapshot_options.xml index 61c8c971c2..eff60f8228 100755 --- a/indra/newview/skins/default/xui/en/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/en/panel_snapshot_options.xml @@ -81,4 +81,40 @@ <button.commit_callback function="Snapshot.SaveToComputer" /> </button> + <text + font="SansSerif" + layout="topleft" + length="1" + follows="top|left" + height="16" + left="10" + name="send_to_facebook_textbox" + top_pad="10" + type="string"> + Send to: [secondlife:/// Facebook] + </text> + <text + font="SansSerif" + layout="topleft" + length="1" + follows="top|left" + height="16" + left="140" + name="send_to_twitter_textbox" + top_pad="-16" + type="string"> + [secondlife:/// Twitter] + </text> + <text + font="SansSerif" + layout="topleft" + length="1" + follows="top|left" + height="16" + left="190" + name="send_to_flickr_textbox" + top_pad="-16" + type="string"> + [secondlife:/// Flickr] + </text> </panel> diff --git a/indra/newview/skins/default/xui/en/panel_twitter_account.xml b/indra/newview/skins/default/xui/en/panel_twitter_account.xml new file mode 100644 index 0000000000..4a413bd711 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_twitter_account.xml @@ -0,0 +1,75 @@ +<panel + height="400" + width="304" + layout="topleft" + name="panel_twitter_account"> + <string + name="twitter_connected" + value="You are connected to Twitter as:" /> + <string + name="twitter_disconnected" + value="Not connected to Twitter" /> + <text + layout="topleft" + length="1" + follows="top|left" + font="SansSerif" + height="16" + left="9" + name="account_caption_label" + top="21" + type="string"> + Not connected to Twitter. + </text> + <text + layout="topleft" + top_pad="2" + length="1" + follows="top|left" + font="SansSerif" + height="16" + left="9" + name="account_name_label" + parse_urls="true" + type="string"/> + <panel + layout="topleft" + name="panel_buttons" + height="345" + left="9"> + <button + layout="topleft" + follows="left|top" + top_pad="9" + visible="true" + height="23" + label="Connect..." + name="connect_btn" + width="210"> + <commit_callback function="SocialSharing.Connect"/> + </button> + + <button + layout="topleft" + follows="left|top" + top_delta="0" + height="23" + label="Disconnect" + name="disconnect_btn" + width="210" + visible="false"> + <commit_callback function="SocialSharing.Disconnect"/> + </button> + <text + layout="topleft" + length="1" + follows="top|left" + height="16" + left="0" + name="account_learn_more_label" + top_pad="20" + type="string"> + [http://community.secondlife.com/t5/English-Knowledge-Base/Second-Life-Share/ta-p/2149711 Learn about posting to Twitter] + </text> + </panel> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_twitter_photo.xml b/indra/newview/skins/default/xui/en/panel_twitter_photo.xml new file mode 100644 index 0000000000..bcec09ebab --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_twitter_photo.xml @@ -0,0 +1,179 @@ + <panel + height="420" + width="304" + layout="topleft" + name="panel_twitter_photo"> + <layout_stack + layout="topleft" + border_size="0" + height="412" + follows="all" + orientation="vertical" + name="stack_photo" + top="8"> + <layout_panel + name="text_panel" + height="160"> + <text + length="1" + follows="top|left|right" + font="SansSerif" + height="16" + left="9" + name="status_label" + top="3" + type="string"> + What's happening? + </text> + <text + length="1" + follows="top|left" + font="SansSerif" + text_color="EmphasisColor" + halign="right" + height="16" + width="30" + left="227" + name="status_counter_label" + top="3" + type="string"> + 140 + </text> + <text_editor + follows="left|top" + height="87" + width="250" + left="9" + length="1" + max_length="140" + name="photo_status" + type="string" + word_wrap="true"> + </text_editor> + <check_box + follows="left|top" + initial_value="true" + label="Include SL location" + name="add_location_cb" + left="9" + height="16" + top_pad="10"/> + <check_box + follows="left|top" + initial_value="true" + label="Include a photo" + name="add_photo_cb" + left="9" + height="16" + top_pad="10"/> + </layout_panel> + <layout_panel + name="snapshot_panel" + height="227"> + <combo_box + control_name="TwitterPhotoResolution" + follows="left|top" + top="6" + left="9" + name="resolution_combobox" + tool_tip="Image resolution" + height="21" + width="135"> + <combo_box.item + label="Current Window" + name="CurrentWindow" + value="[i0,i0]" /> + <combo_box.item + label="640x480" + name="640x480" + value="[i640,i480]" /> + <combo_box.item + label="800x600" + name="800x600" + value="[i800,i600]" /> + <combo_box.item + label="1024x768" + name="1024x768" + value="[i1024,i768]" /> + </combo_box> + <combo_box + control_name="TwitterPhotoFilters" + follows="right|top" + name="filters_combobox" + tool_tip="Image filters" + top="6" + left="165" + height="21" + width="135"> + <combo_box.item + label="No Filter" + name="NoFilter" + value="NoFilter" /> + </combo_box> + <panel + height="150" + width="250" + visible="true" + name="thumbnail_placeholder" + top="33" + follows="left|top" + left="9"> + </panel> + <button + follows="left|top" + height="23" + label="Refresh" + left="9" + top_pad="5" + name="new_snapshot_btn" + tool_tip="Click to refresh" + visible="true" + width="100" > + <button.commit_callback + function="SocialSharing.RefreshPhoto" /> + </button> + <text + follows="left|top" + font="SansSerif" + text_color="EmphasisColor" + height="14" + top_pad="-19" + left_pad="-20" + length="1" + halign="center" + name="working_lbl" + translate="false" + type="string" + visible="true" + width="150"> + Refreshing... + </text> + </layout_panel> + <layout_panel + name="photo_button_panel" + height="25"> + <button + follows="left|top" + top="0" + left="9" + height="23" + label="Tweet" + name="post_photo_btn" + width="100"> + <button.commit_callback + function="SocialSharing.SendPhoto" /> + </button> + <button + follows="left|top" + height="23" + label="Cancel" + name="cancel_photo_btn" + left_pad="15" + top_delta="0" + width="100"> + <button.commit_callback + function="SocialSharing.Cancel" /> + </button> + </layout_panel> + </layout_stack> + </panel> diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 7e79d297ef..92913f678d 100755 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -148,13 +148,37 @@ Please try logging in again in a minute.</string> <string name="SentToInvalidRegion">You were sent to an invalid region.</string> <string name="TestingDisconnect">Testing viewer disconnect</string> - <!-- Facebook Connect and, eventually, other Social Network --> - <string name="SocialFacebookConnecting">Connecting to Facebook...</string> - <string name="SocialFacebookPosting">Posting...</string> - <string name="SocialFacebookDisconnecting">Disconnecting from Facebook...</string> - <string name="SocialFacebookErrorConnecting">Problem connecting to Facebook</string> - <string name="SocialFacebookErrorPosting">Problem posting to Facebook</string> - <string name="SocialFacebookErrorDisconnecting">Problem disconnecting from Facebook</string> + <!-- SLShare: Facebook, Flickr, and Twitter --> + <string name="SocialFacebookConnecting">Connecting to Facebook...</string> + <string name="SocialFacebookPosting">Posting...</string> + <string name="SocialFacebookDisconnecting">Disconnecting from Facebook...</string> + <string name="SocialFacebookErrorConnecting">Problem connecting to Facebook</string> + <string name="SocialFacebookErrorPosting">Problem posting to Facebook</string> + <string name="SocialFacebookErrorDisconnecting">Problem disconnecting from Facebook</string> + <string name="SocialFlickrConnecting">Connecting to Flickr...</string> + <string name="SocialFlickrPosting">Posting...</string> + <string name="SocialFlickrDisconnecting">Disconnecting from Flickr...</string> + <string name="SocialFlickrErrorConnecting">Problem connecting to Flickr</string> + <string name="SocialFlickrErrorPosting">Problem posting to Flickr</string> + <string name="SocialFlickrErrorDisconnecting">Problem disconnecting from Flickr</string> + <string name="SocialTwitterConnecting">Connecting to Twitter...</string> + <string name="SocialTwitterPosting">Posting...</string> + <string name="SocialTwitterDisconnecting">Disconnecting from Twitter...</string> + <string name="SocialTwitterErrorConnecting">Problem connecting to Twitter</string> + <string name="SocialTwitterErrorPosting">Problem posting to Twitter</string> + <string name="SocialTwitterErrorDisconnecting">Problem disconnecting from Twitter</string> + + <!-- SLShare: User Friendly Filter Names Translation --> + <string name="BlackAndWhite">Black & White</string> + <string name="Colors1970">1970's Colors</string> + <string name="Heatwave">Heat Wave</string> + <string name="Intense">Intense</string> + <string name="JulesVerne">Jules Verne</string> + <string name="Newspaper">Newspaper</string> + <string name="Overcast">Overcast</string> + <string name="Sepia">Sepia</string> + <string name="Spotlight">Spotlight</string> + <string name="Video">Video</string> <!-- Tooltip --> <string name="TooltipPerson">Person</string><!-- Object under mouse pointer is an avatar --> @@ -3444,6 +3468,12 @@ If you continue to receive this message, contact the [SUPPORT_SITE]. <string name="facebook_post_success"> You posted to Facebook. </string> + <string name="flickr_post_success"> + You posted to Flickr. + </string> + <string name="twitter_post_success"> + You posted to Twitter. + </string> <string name="no_session_message"> (IM Session Doesn't Exist) @@ -3861,6 +3891,8 @@ Try enclosing path to the editor with double quotes. <string name="Command_Conversations_Label">Conversations</string> <string name="Command_Compass_Label">Compass</string> <string name="Command_Destinations_Label">Destinations</string> + <string name="Command_Facebook_Label">Facebook</string> + <string name="Command_Flickr_Label">Flickr</string> <string name="Command_Gestures_Label">Gestures</string> <string name="Command_HowTo_Label">How to</string> <string name="Command_Inventory_Label">Inventory</string> @@ -3876,8 +3908,8 @@ Try enclosing path to the editor with double quotes. <string name="Command_Profile_Label">Profile</string> <string name="Command_Search_Label">Search</string> <string name="Command_Snapshot_Label">Snapshot</string> - <string name="Command_Social_Label">Facebook</string> <string name="Command_Speak_Label">Speak</string> + <string name="Command_Twitter_Label">Twitter</string> <string name="Command_View_Label">Camera controls</string> <string name="Command_Voice_Label">Voice settings</string> @@ -3889,6 +3921,8 @@ Try enclosing path to the editor with double quotes. <string name="Command_Conversations_Tooltip">Converse with everyone</string> <string name="Command_Compass_Tooltip">Compass</string> <string name="Command_Destinations_Tooltip">Destinations of interest</string> + <string name="Command_Facebook_Tooltip">Post to Facebook</string> + <string name="Command_Flickr_Tooltip">Upload to Flickr</string> <string name="Command_Gestures_Tooltip">Gestures for your avatar</string> <string name="Command_HowTo_Tooltip">How to do common tasks</string> <string name="Command_Inventory_Tooltip">View and use your belongings</string> @@ -3904,8 +3938,8 @@ Try enclosing path to the editor with double quotes. <string name="Command_Profile_Tooltip">Edit or view your profile</string> <string name="Command_Search_Tooltip">Find places, events, people</string> <string name="Command_Snapshot_Tooltip">Take a picture</string> - <string name="Command_Social_Tooltip">Post to Facebook</string> <string name="Command_Speak_Tooltip">Speak with people nearby using your microphone</string> + <string name="Command_Twitter_Tooltip">Twitter</string> <string name="Command_View_Tooltip">Changing camera angle</string> <string name="Command_Voice_Tooltip">Volume controls for calls and people near you in world</string> diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 96b4c7268c..dcff9163e0 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -98,6 +98,9 @@ class ViewerManifest(LLManifest): # ... and the entire windlight directory self.path("windlight") + # ... and the entire image filters directory + self.path("filters") + # ... and the included spell checking dictionaries pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') if self.prefix(src=pkgdir,dst=""): |