[qgis] 01/05: Imported Upstream version 2.14.5+dfsg

Bas Couwenberg sebastic at debian.org
Fri Jul 29 14:56:43 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository qgis.

commit e410ce68467b9e5e20b9311f465f9e4ee092ee50
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Fri Jul 29 14:12:53 2016 +0200

    Imported Upstream version 2.14.5+dfsg
---
 .travis.yml                                        |  73 ++-
 CMakeLists.txt                                     |   2 +-
 ChangeLog                                          | 490 +++++++++++++++++++++
 ci/travis/linux/after_script.sh                    |  15 +
 ci/travis/linux/before_install.sh                  |  80 +---
 ci/travis/linux/before_script.sh                   |  15 +
 ci/travis/linux/install.sh                         |  34 +-
 ci/travis/linux/qt4/before_install.sh              |  24 +
 ci/travis/linux/qt4/install.sh                     |  59 +++
 ci/travis/linux/qt4/script.sh                      |  27 ++
 ci/travis/linux/script.sh                          |  20 +-
 ci/travis/osx/before_install.sh                    |  28 +-
 ci/travis/osx/install.sh                           |  37 +-
 ci/travis/osx/script.sh                            |  21 +-
 cmake/SIPMacros.cmake                              |   6 +
 debian/changelog                                   |  10 +-
 debian/qgis.install                                |   1 -
 debian/rules                                       |   6 +-
 python/PyQt/CMakeLists.txt                         |   1 +
 python/core/composer/qgscomposerlegend.sip         |  14 +
 python/core/composer/qgscomposition.sip            |   5 +
 python/core/core.sip                               |   1 +
 python/core/qgsannotation.sip                      |  64 +++
 python/core/qgsapplication.sip                     |  16 +
 python/core/qgsexpressioncontext.sip               |   7 +
 python/core/qgsnetworkaccessmanager.sip            |   2 +-
 python/core/qgsproject.sip                         |  14 +
 python/gui/qgsannotationitem.sip                   |  26 +-
 python/plugins/processing/ProcessingPlugin.py      |   2 +-
 .../plugins/processing/algs/gdal/GdalAlgorithm.py  |   5 +-
 python/plugins/processing/algs/qgis/Buffer.py      |  14 +-
 python/plugins/processing/algs/qgis/Difference.py  |  22 +-
 .../processing/modeler/ModelerParametersDialog.py  |  17 +-
 python/plugins/processing/tools/dataobjects.py     |  18 +-
 src/analysis/raster/qgsninecellfilter.cpp          |  16 +-
 src/analysis/raster/qgsrastercalculator.cpp        |   2 +-
 src/analysis/raster/qgsrelief.cpp                  |  45 +-
 src/analysis/vector/qgszonalstatistics.cpp         |   6 +-
 src/app/composer/qgsatlascompositionwidget.cpp     |   3 +-
 src/app/composer/qgscomposer.cpp                   |  74 ++--
 src/app/composer/qgscomposer.h                     |   9 +-
 src/app/composer/qgscomposeritemwidget.cpp         |  26 +-
 src/app/composer/qgscomposeritemwidget.h           |   3 +-
 src/app/composer/qgscomposerlegendwidget.cpp       |  21 +
 src/app/composer/qgscomposerlegendwidget.h         |   1 +
 src/app/composer/qgscomposermanager.cpp            |  13 +-
 src/app/composer/qgscompositionwidget.cpp          |  25 +-
 src/app/composer/qgscompositionwidget.h            |   2 +
 src/app/qgisapp.cpp                                |   6 +-
 src/app/qgsannotationwidget.cpp                    |  22 +-
 src/app/qgsannotationwidget.h                      |   7 +-
 src/app/qgsattributetabledialog.cpp                |   1 -
 src/app/qgsformannotationdialog.cpp                |   1 -
 src/app/qgshtmlannotationdialog.cpp                |   1 -
 src/app/qgsoptions.cpp                             |  28 +-
 src/app/qgsprojectproperties.cpp                   |   3 +-
 src/app/qgssvgannotationdialog.cpp                 |   1 -
 src/app/qgstextannotationdialog.cpp                |  15 +-
 src/app/qgstextannotationdialog.h                  |   5 +
 src/core/CMakeLists.txt                            |   1 +
 src/core/composer/qgsatlascomposition.cpp          |   5 +-
 src/core/composer/qgscomposerlegend.cpp            |  85 +++-
 src/core/composer/qgscomposerlegend.h              |  23 +
 src/core/composer/qgscomposermap.cpp               |  66 ++-
 src/core/composer/qgscomposermap.h                 |   5 +-
 src/core/composer/qgscomposition.cpp               |   5 +
 src/core/composer/qgscomposition.h                 |   5 +
 src/core/geometry/qgsgeometry.cpp                  |   2 +-
 src/core/pal/feature.cpp                           |  13 +-
 src/core/pal/layer.cpp                             |  29 +-
 src/core/qgsannotation.h                           |  90 ++++
 src/core/qgsapplication.cpp                        |   5 +
 src/core/qgsapplication.h                          |  16 +
 src/core/qgsexpressioncontext.cpp                  |  13 +
 src/core/qgsexpressioncontext.h                    |   7 +
 src/core/qgsmaplayerregistry.cpp                   |  35 +-
 src/core/qgsmaplayerregistry.h                     |   4 +-
 src/core/qgsnetworkaccessmanager.cpp               |   6 +-
 src/core/qgsnetworkaccessmanager.h                 |   2 +-
 src/core/qgsofflineediting.cpp                     |  28 +-
 src/core/qgspallabeling.cpp                        |   7 +-
 src/core/qgsproject.cpp                            |   4 +
 src/core/qgsproject.h                              |  14 +
 .../editorwidgets/qgsrelationreferencewidget.cpp   |   2 +-
 src/gui/qgsannotationitem.cpp                      |  48 +-
 src/gui/qgsannotationitem.h                        |  36 +-
 src/gui/qgscredentialdialog.cpp                    |   2 +
 src/gui/qgsvariableeditorwidget.h                  |  14 +-
 src/plugins/heatmap/heatmap.cpp                    |  19 +-
 .../oracle/ocispatial/qsql_ocispatial.cpp          |   2 +-
 src/providers/oracle/qgsoracleconn.cpp             |   2 +-
 src/providers/oracle/qgsoracleconn.h               |   6 +-
 src/providers/oracle/qgsoracleconnpool.h           |   2 +-
 src/providers/oracle/qgsoracleprovider.cpp         |  14 +-
 src/providers/oracle/qgsoracleprovider.h           |   1 -
 src/providers/wcs/qgswcsprovider.cpp               |  20 +-
 src/providers/wms/CMakeLists.txt                   |   6 +
 src/providers/wms/qgswmscapabilities.cpp           |  11 +
 src/providers/wms/qgswmsprovider.cpp               |  68 ++-
 src/python/qgspythonutilsimpl.cpp                  |   9 -
 src/python/qgspythonutilsimpl.h                    |   3 -
 src/server/qgsowsserver.h                          |   2 +
 src/server/qgswmsprojectparser.cpp                 |   5 +
 src/ui/composer/qgscomposerlegendwidgetbase.ui     |  10 +-
 src/ui/qgscredentialdialog.ui                      |   8 +
 src/ui/qgsoptionsbase.ui                           |   2 +-
 src/ui/qgsprojectpropertiesbase.ui                 | 178 ++------
 tests/src/analysis/testqgsalignraster.cpp          |   4 +-
 tests/src/core/testqgsapplication.cpp              |   1 +
 tests/src/core/testqgscomposition.cpp              |  15 +
 .../src/core/testqgscoordinatereferencesystem.cpp  |   6 +
 tests/src/core/testqgsdistancearea.cpp             |   3 +-
 tests/src/core/testqgsexpressioncontext.cpp        |  12 +
 tests/src/core/testqgsgeometry.cpp                 |  16 +
 tests/src/core/testqgsproject.cpp                  |   8 +
 tests/src/core/testqgsrasterlayer.cpp              |   5 +-
 tests/src/providers/CMakeLists.txt                 |   9 +
 tests/src/providers/testqgswmscapabilities.cpp     |  70 +++
 tests/src/providers/testqgswmsprovider.cpp         |  69 +++
 tests/src/python/CMakeLists.txt                    |   1 +
 tests/src/python/test_qgscomposerlegend.py         | 203 +++++++++
 tests/src/python/test_qgspallabeling_tests.py      |  16 +
 tests/src/python/utilities.py                      |  12 +
 .../expected_composer_legend_mapunits.png          | Bin 0 -> 16960 bytes
 .../expected_composer_legend_mapunits_mask.png     | Bin 0 -> 6230 bytes
 .../expected_composer_legend_noresize.png          | Bin 0 -> 9350 bytes
 .../expected_composer_legend_noresize_mask.png     | Bin 0 -> 6483 bytes
 .../expected_composer_legend_noresize_crop.png     | Bin 0 -> 7177 bytes
 ...expected_composer_legend_noresize_crop_mask.png | Bin 0 -> 5598 bytes
 .../expected_composer_legend_size_content.png      | Bin 0 -> 9364 bytes
 .../expected_composer_legend_size_content_mask.png | Bin 0 -> 6085 bytes
 .../sp_letter_spacing/sp_letter_spacing.png        | Bin 0 -> 5217 bytes
 .../sp_letter_spacing/sp_letter_spacing_mask.png   | Bin 0 -> 1857 bytes
 .../sp_word_spacing/sp_word_spacing.png            | Bin 0 -> 5316 bytes
 .../sp_word_spacing/sp_word_spacing_mask.png       | Bin 0 -> 1857 bytes
 .../sp_curved_placement_above.png                  | Bin 13289 -> 12116 bytes
 .../sp_curved_placement_above_mask.png             | Bin 8892 -> 3494 bytes
 .../sp_curved_placement_below.png                  | Bin 13273 -> 12044 bytes
 .../sp_curved_placement_below_mask.png             | Bin 9798 -> 3625 bytes
 .../sp_curved_placement_online.png                 | Bin 12247 -> 11014 bytes
 .../sp_curved_placement_online_mask.png            | Bin 9404 -> 10684 bytes
 .../sp_img_letter_spacing.png                      | Bin 0 -> 5210 bytes
 .../sp_img_letter_spacing_mask.png                 | Bin 0 -> 1868 bytes
 .../sp_img_word_spacing/sp_img_word_spacing.png    | Bin 0 -> 5305 bytes
 .../sp_img_word_spacing_mask.png                   | Bin 0 -> 1864 bytes
 .../sp_pdf_letter_spacing.png                      | Bin 0 -> 5214 bytes
 .../sp_pdf_letter_spacing_mask.png                 | Bin 0 -> 1893 bytes
 .../sp_pdf_word_spacing/sp_pdf_word_spacing.png    | Bin 0 -> 5352 bytes
 .../sp_pdf_word_spacing_mask.png                   | Bin 0 -> 1985 bytes
 .../sp_svg_letter_spacing.png                      | Bin 0 -> 5205 bytes
 .../sp_svg_letter_spacing_mask.png                 | Bin 0 -> 2657 bytes
 .../sp_svg_word_spacing/sp_svg_word_spacing.png    | Bin 0 -> 5331 bytes
 .../sp_svg_word_spacing_mask.png                   | Bin 0 -> 1734 bytes
 .../sp_img_curved_placement_above.png              | Bin 13289 -> 12116 bytes
 .../sp_img_curved_placement_above_mask.png         | Bin 12103 -> 3494 bytes
 .../sp_img_curved_placement_below.png              | Bin 13273 -> 12044 bytes
 .../sp_img_curved_placement_below_mask.png         | Bin 13105 -> 3625 bytes
 .../sp_img_curved_placement_online.png             | Bin 12247 -> 11014 bytes
 .../sp_img_curved_placement_online_mask.png        | Bin 11918 -> 3398 bytes
 .../sp_pdf_curved_placement_above.png              | Bin 11069 -> 11536 bytes
 .../sp_pdf_curved_placement_above_mask.png         | Bin 11946 -> 3645 bytes
 .../sp_pdf_curved_placement_below.png              | Bin 11094 -> 11542 bytes
 .../sp_pdf_curved_placement_below_mask.png         | Bin 12864 -> 3552 bytes
 .../sp_pdf_curved_placement_online.png             | Bin 10389 -> 10775 bytes
 .../sp_pdf_curved_placement_online_mask.png        | Bin 11755 -> 3416 bytes
 .../sp_svg_curved_placement_above.png              | Bin 13283 -> 12108 bytes
 .../sp_svg_curved_placement_above_mask.png         | Bin 8829 -> 3503 bytes
 .../sp_svg_curved_placement_below.png              | Bin 13298 -> 12035 bytes
 .../sp_svg_curved_placement_below_mask.png         | Bin 9722 -> 3427 bytes
 .../sp_svg_curved_placement_online.png             | Bin 12239 -> 11028 bytes
 .../sp_svg_curved_placement_online_mask.png        | Bin 9343 -> 3044 bytes
 .../sp_letter_spacing/sp_letter_spacing.png        | Bin 0 -> 5214 bytes
 .../sp_letter_spacing/sp_letter_spacing_mask.png   | Bin 0 -> 1876 bytes
 .../sp_word_spacing/sp_word_spacing.png            | Bin 0 -> 5316 bytes
 .../sp_word_spacing/sp_word_spacing_mask.png       | Bin 0 -> 1974 bytes
 .../sp_curved_placement_above.png                  | Bin 13289 -> 12139 bytes
 .../sp_curved_placement_above_mask.png             | Bin 8892 -> 2680 bytes
 .../sp_curved_placement_below.png                  | Bin 13273 -> 12047 bytes
 .../sp_curved_placement_below_mask.png             | Bin 9798 -> 2648 bytes
 .../sp_curved_placement_online.png                 | Bin 12247 -> 11016 bytes
 .../sp_curved_placement_online_mask.png            | Bin 9404 -> 2760 bytes
 tests/testdata/provider/GetCapabilities.xml        | 188 ++++++++
 182 files changed, 2486 insertions(+), 604 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 6136abe..c85a552 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,23 +1,69 @@
-language: cpp
-
 matrix:
-  allow_failures:
-    - os: osx
+  fast_finish: true
   include:
+    # QT4 based build with Python 2.7 // using container based builds and prebuild binary dependencies in osgeo4travis
     - os: linux
-      sudo: required
-      dist: precise
-      group: legacy
-      compiler: clang
+      language: cpp
+      env:
+        - BUILD=qt4
+        - QT_VERSION=4
+#        - LLVM_VERSION=3.8
+      sudo: false
+      cache:
+        apt: true
+        directories:
+          - $HOME/.ccache
+      compiler: gcc
+      addons:
+        postgresql: "9.4"
+        apt:
+          sources:
+#            - llvm-toolchain-precise-3.8
+            - ubuntu-toolchain-r-test
+            - george-edison55-precise-backports # doxygen 1.8.3
+          packages:
+            - bison
+            - gcc-6
+            - g++-6
+            - doxygen
+            - flex
+            - flip
+            - libfcgi-dev
+            - libfftw3-3
+            - libpq-dev
+            - libqscintilla2-dev
+            - libqt4-dev
+            - libqt4-opengl-dev
+            - libqt4-sql-sqlite
+            - libqtwebkit-dev
+            - libqwt-dev
+            - libspatialindex-dev
+            - libspatialite-dev
+            - libsqlite3-dev
+            - pkg-config
+            - poppler-utils
+            - pyqt4-dev-tools
+            - python
+            - python-dev
+            - python-numpy
+            - python-pip
+            - python-psycopg2
+            - python-qscintilla2
+            - python-qt4-dev
+            - python-qt4-sql
+            - python-sip
+            - python-sip-dev
+            - txt2tags
+            - xvfb
+    # OSX based build with QT4 and Python 2
     - os: osx
-      compiler: clang
-
+      env:
+        - BUILD=osx
+        - IGNORE_BUILD_FAILURES=YES
 
 git:
   depth: 30
 
-cache: apt
-
 notifications:
   irc: "chat.freenode.net#qgis-test"
   on_failure: change
@@ -32,9 +78,6 @@ notifications:
     on_failure: always # options: [always|never|change] default: always
     on_start: never # default: never
 
-addons:
-  postgresql: "9.1"
-
 before_install:
   - ./ci/travis/${TRAVIS_OS_NAME}/before_install.sh
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index afccf48..9af0765 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 SET(CPACK_PACKAGE_VERSION_MAJOR "2")
 SET(CPACK_PACKAGE_VERSION_MINOR "14")
-SET(CPACK_PACKAGE_VERSION_PATCH "4")
+SET(CPACK_PACKAGE_VERSION_PATCH "5")
 SET(COMPLETE_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH})
 SET(RELEASE_NAME "Essen")
 IF (POLICY CMP0048) # in CMake 3.0.0+
diff --git a/ChangeLog b/ChangeLog
index 9c2bfa2..eca3ffd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,493 @@
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-27
+
+    Fix indentation:
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-27
+
+    Fix labeling using perimeter with repeating label distance set
+
+    If the visible part of a polygon is clipped and becomes a multipolygon, only
+    one label is plotted on the wrong side of the polygon.
+
+    Settings:
+    Placement: Using Perimeter
+    Allowed positions: Below line / Line orientation dependent position checked
+    Repeat: 100 mm
+
+    Fix #15341
+
+    (cherry-picked from c0b1684058a5acf3ae58ea63bea7b00520e27725)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-27
+
+    Update composer legend test masks
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-27
+
+    Fix failing distance area test on OSX
+
+    (cherry-picked from af9b4a7f45fcc1f0885c04966d07e60970546489)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-27
+
+    [travis] Fix OSX build
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-27
+
+    Fix sip coverage test
+
+    By working around doxygen bug where private members are listed
+    as public
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-27
+
+    Add labeling tests for letter/word spacing
+
+    (cherry-picked from 3d6688cce5598d0c09d13f2cd8db30f1f073e928)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-26
+
+    Update curved label test reference images
+
+    (cherry-picked from 5228de353c84337b3d753fe15c47ee09ecb2643a)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-27
+
+    [labels] Fix word and letter spacing truncated to integers
+
+    (cherry-picked from 449fcad8ce0808780cf662362cf5c6568abd09bb)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-27
+
+    [labeling] Curved labels are now angled per character
+
+    ...instead of shifted along base line (fix #15210)
+
+    (cherry-picked from 22fdb6ab9fcb076f3f3e8601fa4bdc7e0894a103)
+
+Alexander Bruy <alexander.bruy at gmail.com>	2016-07-26
+
+    [processing] fix issues with exported layers in GDAL provider
+
+    (cherry picked from commit b3a38f4018e7b34550f39cffa8bf1197f011dc1e)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-26
+
+    Fix features are not labeled when using merged connected lines and
+    lines are touching but not at endpoints
+
+    (cherry-picked from 9007d5c11b17840e2e06a4a0bd1f403490d5012a)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-26
+
+    Fix build warnings
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-26
+
+    [composer] Fix editing of map item variables
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from 5384e203fb58a7402ed8ff5598257a953171830d)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-26
+
+    Fix annotation position when maps are rotated, remove hacks
+
+    Adds a new interface class QgsAnnotation, and allows for removal
+    of a bunch of hacks in QgsComposerMap without breaking 2.x API
+
+    (cherry-picked from 0fa6499bef93b2949a7f35d8cfc35a90a353a004)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-26
+
+    Fix text annotation edit background should match frame background
+
+    Otherwise white text is not visible. Fix #10553.
+
+    (cherry-picked from 76c4cae)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-26
+
+    Always keep full precision when saving annotation properties
+
+    (cherry-picked from 0554f5656cedb588598ef41204acbff185e08a33)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-26
+
+    Avoid dialog 'flashing' when opening annotation properties
+
+    (cherry-picked from a798ba0637b25654e323090b4e2ce50ddd7ea3a4)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-26
+
+    Fix annotation colors are modified when cancel is clicked
+
+    (cherry-picked from 95fd61c7bd1b9619759382a7f546b385ac33bd8d)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-26
+
+    Make filename in project properties copyable
+
+    (cherry-picked from d56ca40884f360924654e81ac02bd88c7895495c)
+
+Alexander Bruy <alexander.bruy at gmail.com>	2016-07-25
+
+    Revert "[processing] use default models folder when adding model from file (fix #15335)"
+
+    This reverts commit ebd5b0bc3ada82feb65d2ac3932f29b7a7c615b2 cherry-picked by mistake
+
+Alexander Bruy <alexander.bruy at gmail.com>	2016-07-25
+
+    [processing] use default models folder when adding model from file (fix #15335)
+
+    (cherry picked from commit b167c09e4412b1bf8559020a529b80e520b28b22)
+
+Alexander Bruy <alexander.bruy at gmail.com>	2016-07-25
+
+    [processing] different shortcut for commander (fix #15334)
+
+    (cherry picked from commit 4e94963af54894a3811ea8c44cbb6523ceec4ea0)
+
+Juergen E. Fischer <jef at norbit.de>	2016-07-23
+
+    oracle provider: fix binding of output values
+
+    (cherry picked from commit 1368038ca93ce923cae7f507f5f387690a2f3136)
+
+Merge: 2ebd0eb 4f6422a
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-23
+
+    Merge pull request #3330 from nyalldawson/gdal_tests
+
+    Backport GDAL test fixes to 2.14
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-03-31
+
+    Use average mode for align raster downsample tests
+
+    GDAL 2.0 changed (fixed) the bilinear downsampling algorithm, so
+    switch to the average algorithm so that test results are the same
+    for GDAL versions >= 2.0 and < 2.0.
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-03-31
+
+    Fix qgsrasterlayer tests under GDAL >= 2.0
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-03-31
+
+    Fix qgis_coordinatereferencesystemtest with GDAL >= 2.0
+
+Matthias Kuhn <matthias at opengis.ch>	2016-07-23
+
+    [travis] Use containerized builds (#3327)
+
+    * Add some debug output when GDALRasterIO operations fail
+
+    Or the compiler will complain about unhandled returns
+
+    * [travis] Use containerized builds
+
+Alexander Bruy <alexander.bruy at gmail.com>	2016-07-22
+
+    [processing] add missed import
+
+Alexander Bruy <alexander.bruy at gmail.com>	2016-07-22
+
+    [processing] also fix case without dissolving
+
+    (cherry picked from commit 2fbb617d618c31ca9ba0d412712ca6ac823ace04)
+
+Alexander Bruy <alexander.bruy at gmail.com>	2016-07-22
+
+    [processing] fix buffer tool
+
+    (cherry picked from commit 9976c30c9aa7b0134b7e72e64157ac9fdb0b042f)
+
+volaya <volayaf at gmail.com>	2016-06-01
+
+    [processing] fixed wrong call to splitext in dataobjects.py
+
+    (cherry picked from commit 479ceb36b40407643764586fc5122333b7eb38eb)
+
+    Conflicts:
+python/plugins/processing/tools/dataobjects.py
+
+Patrick Valsecchi <patrick.valsecchi at camptocamp.com>	2016-05-13
+
+    WMS: Better logic to pick the legend URL
+
+    QGIS had two problems:
+    1) It was using the specified legend URL only if its mime type was matching
+       the layer's mime type. There is no reason for that.
+    2) When QGIS was using the default layer (empty string), it was not even
+       trying to find out in what style to pick the legend URL.
+
+    (cherry-picked from 69bed218373b3f93671f65bc3d02c45cbf683a48)
+
+Patrick Valsecchi <patrick.valsecchi at camptocamp.com>	2016-05-13
+
+    WMS GetCapabilities: override parent's style if they have the same name
+
+    When there is a layer group with several sub-layers, the group has a
+    "default" style and the sub-layers each have a "default" layer. QGIS
+    was showing two "default" styles for the sub-layers, which would be
+    confusing to the user and could pick the wrong legend for the
+    sub-layer if the user picked the wrong entry (the first one).
+
+    Had to create a static lib for wmsprovider in order to unittest it.
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    [composer] Add a checkbox for legends to prevent automatic resizing
+
+    A new checkbox has been added to the legend settings to control
+    whether or not a legend should be automatically resized to fit
+    its contents.
+
+    If unchecked, then the legend will never resize and instead just
+    stick to whatever size the user has set. Any content which
+    doesn't fit the size is cropped out.
+
+    Refs #10556
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from 2f8c6f52073d4c9c77c39fa119f18ef82783e05d)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    [composer] Fix setting legend content by map not resizing legend
+
+    When a legend was set to filter content by map, it wasn't
+    consistently being resized to fit the legend contents. This caused
+    issues for atlas exports where legends could grow but never
+    shrink.
+
+    Fix #14707
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from 4f31ab656ef04c78d92fce2f1a68833043adb456)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    [composer] Fix initial size of legend is wrong if symbol size in
+    map units is used (fix #11921)
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from 93f2eec711f2d3e1593f497db581a7e6973cfcc9)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    Avoid some unnecessary composer legend updates
+
+    (cherry-picked from aaa654fba9a79b8842ada7570e653dc8b4f39a97)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    [oracle] Fix minor Coverity issues
+
+    (cherry-picked from b94fbc0485b3998312749327974bf8e8f49504a9)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    Fix use of : in label (violation of HIG)
+
+    (cherry-picked from 5a2031349f62d808f78fdc496932d6cc713801d8)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    Fix crash in QgsGeometry::unaryUnion with empty geometries
+
+    (cherry-picked from b61641dc72b140a02de8eb0636a3817f44b9c8fc)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    Remove "restart required" from "open table as dock" option
+
+    Since it seems that a restart *isn't* required!
+
+    (cherry-picked from 8943ed7c9f41ef3f7a33f402f709d9bf9156ffd1)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    [composer] Avoid crash when atlas page name field has spaces
+
+    Fix #15297
+
+    (cherry-picked from 631b5e87c3d4d72b80e6087c48caf7d73a477213)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    [composer] Load all composition properties from template/duplicate
+
+    Previously some settings where not being correctly restore, eg
+    page size, grid settings, expression variables
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    Fix #8705
+
+    (cherry-picked from 7343b36e2574a9d9b16158911adaaf6b9d3740be)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    [composer] Simplify and consolidate loading of templates
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from 656e56e447c5d75476aba2259ada05bb06699f94)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-07-19
+
+    Make sure variable editor widgets always show current variables
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from c7ffdfa5e991743e862f10ae2dbec2c05c4b795c)
+
+Merge: dfef36e f99684a
+Alessandro Pasotti <elpaso at itopen.it>	2016-07-18
+
+    Merge pull request #3300 from luca76/patch-2
+
+    Update qgsowsserver.h
+
+Matthias Kuhn <matthias at opengis.ch>	2016-07-12
+
+    [server] Fix crash in WMS server when... bad things happen
+
+    No idea what exactly the reason is, and it was only discovered by
+    countless hours of printf-debugging. So I'm just pushing the fix
+    for everyone else who might be affected.
+
+Matthias Kuhn <matthias at opengis.ch>	2016-07-12
+
+    Fix single process build
+
+    A race condition triggered that sometimes the file
+    output/python/qgis/__init__.py was not created before a python uic
+    compiler started and therefore the required module could not be
+    imported.
+
+    This leads to errors like
+
+    [ 82%] Generating ui_dialogAbout.py
+    Traceback (most recent call last):
+      File "../../../../../scripts/pyuic-wrapper.py", line 26, in <module>
+        import qgis.PyQt.uic.pyuic
+    ImportError: No module named qgis.PyQt.uic.pyuic
+    python/plugins/GdalTools/tools/CMakeFiles/zzz-GdalTools-2-depend.dir/build.make:117:
+    recipe for target 'python/plugins/GdalTools/tools/ui_dialogAbout.py'
+    failed
+    make[2]: *** [python/plugins/GdalTools/tools/ui_dialogAbout.py] Error 1
+    CMakeFiles/Makefile2:5074: recipe for target
+    'python/plugins/GdalTools/tools/CMakeFiles/zzz-GdalTools-2-depend.dir/all'
+    failed
+    make[1]: ***
+    [python/plugins/GdalTools/tools/CMakeFiles/zzz-GdalTools-2-depend.dir/all]
+    Error 2
+    Makefile:160: recipe for target 'all' failed
+    make: *** [all] Error 2
+
+Luca M <luca76 at users.noreply.github.com>	2016-07-12
+
+    Update qgsowsserver.h
+
+    fix compilation error with HAVE_SERVER_PYTHON_PLUGINS set to OFF in ccmake
+
+Matthias Kuhn <matthias at opengis.ch>	2016-06-06
+
+    Fix on map identification on relation reference widget with complex PK
+
+    References #14882
+
+Matthias Kuhn <matthias at opengis.ch>	2016-06-06
+
+    [offline editing] No reason to crash just because of raster layers
+
+    Fix #14848
+
+Matthias Kuhn <matthias at opengis.ch>	2016-06-26
+
+    Don't put default network cache directory directly in $HOME
+
+    Fix #15111
+
+Matthias Kuhn <matthias at opengis.ch>	2016-06-12
+
+    Show default network cache path in options dialog
+
+Matthias Kuhn <matthias at opengis.ch>	2016-06-12
+
+    Fix network cache configuration
+
+    Fix #14990
+
+Matthias Kuhn <matthias at opengis.ch>	2016-06-13
+
+    Allow docking the attribute table left and right
+
+    Fix #14941
+
+Matthias Kuhn <matthias at opengis.ch>	2016-06-22
+
+    Fix wrong mapping of feature ids in offline editing
+
+    Fix #14727
+
+Matthias Kuhn <matthias at opengis.ch>	2016-06-23
+
+    Fix initial widget focus in credential dialog
+
+Matthias Kuhn <matthias at opengis.ch>	2016-07-02
+
+    Run startup.py only once
+
+    Fix #15189
+
+Matthias Kuhn <matthias at opengis.ch>	2016-07-03
+
+    Safety checks for unregistering map layers from registry
+
+    If a map layer which is registered is deleted outside of the layer
+    registry but not unregistered, the layer registry would still happily
+    return a pointer to this layer if queried with its id.
+
+    Up to now, this caused crashes. Now, the layer will be unregistered and
+    a warning is printed.
+
+    This patch also contains slight improvements to other parts of the map
+    layer registry.
+
+Matthias Kuhn <matthias at opengis.ch>	2016-07-04
+
+    [processing] Difference: don't ignore invalid geometries by default
+
+    Fix #9297
+
+Matthias Kuhn <matthias at opengis.ch>	2016-07-05
+
+    Fix relative paths for offline editing
+
+Juergen E. Fischer <jef at norbit.de>	2016-07-10
+
+    fix 39d6e79
+
+    (cherry picked from commit 2f9ed29d9a5ec7fa79c7f902f036a81c43b8a2da)
+
+Bas Couwenberg <sebastic at xs4all.nl>	2016-07-09
+
+    Fix installation path of scalable icons.
+
+    (cherry picked from commit f5b86230121d533a070748427b613610d308d4d3)
+
+Juergen E. Fischer <jef at norbit.de>	2016-07-08
+
+    Release of 2.14.4
+
 Nyall Dawson <nyall.dawson at gmail.com>	2016-07-08
 
     Fix build
diff --git a/ci/travis/linux/after_script.sh b/ci/travis/linux/after_script.sh
index 81864ac..a3a0dab 100755
--- a/ci/travis/linux/after_script.sh
+++ b/ci/travis/linux/after_script.sh
@@ -1 +1,16 @@
+###########################################################################
+#    after_script.sh
+#    ---------------------
+#    Date                 : September 2015
+#    Copyright            : (C) 2015 by Matthias Kuhn
+#    Email                : matthias at opengis dot ch
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
+
 [ -r /tmp/ctest-important.log ] && cat /tmp/ctest-important.log
diff --git a/ci/travis/linux/before_install.sh b/ci/travis/linux/before_install.sh
index 87f9dea..79a6515 100755
--- a/ci/travis/linux/before_install.sh
+++ b/ci/travis/linux/before_install.sh
@@ -1,65 +1,19 @@
-export DEBIAN_FRONTEND=noninteractive
+#!/bin/bash
+###########################################################################
+#    before_install.sh
+#    ---------------------
+#    Date                 : August 2015
+#    Copyright            : (C) 2015 by Nyall Dawson
+#    Email                : nyall dot dawson at gmail dot com
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
 
-sudo add-apt-repository ppa:ubuntugis/ppa -y
-sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable -y # For postgresql-9.1-postgis-2.1
-sudo add-apt-repository ppa:smspillaz/cmake-3.0.2 -y
-sudo add-apt-repository ppa:kedazo/doxygen-updates-precise -y # For doxygen 1.8.8
-sudo apt-get update -qq
-sudo apt-get install --force-yes --no-install-recommends --no-install-suggests \
-        bison \
-        cmake \
-        cmake-data \
-        doxygen \
-        flex \
-        gdal-bin \
-        git \
-        graphviz \
-        grass-dev \
-        libexpat1-dev \
-        libfcgi-dev \
-        libgdal1-dev \
-        libgeos-dev \
-        libgsl0-dev \
-        libpq-dev \
-        libproj-dev \
-        libqca2-dev \
-        libqca2-plugin-ossl \
-        libqscintilla2-dev \
-        libqt4-dev \
-        libqt4-opengl-dev \
-        libqt4-sql-sqlite \
-        libqtwebkit-dev \
-        libqwt-dev \
-        libspatialindex-dev \
-        libspatialite-dev \
-        libsqlite3-dev \
-        lighttpd \
-        pkg-config \
-        poppler-utils \
-        pyqt4-dev-tools \
-        python \
-        python-dev \
-        python-qt4 \
-        python-qt4-dev \
-        python-qt4-sql \
-        python-qscintilla2 \
-        python-sip \
-        python-sip-dev \
-        python-psycopg2 \
-        python-numpy \
-        python-gdal \
-        spawn-fcgi \
-        txt2tags \
-        xauth \
-        xfonts-100dpi \
-        xfonts-75dpi \
-        xfonts-base \
-        xfonts-scalable \
-        xvfb \
-        python-pip \
-        flip \
-        jq \
-        postgresql-9.1-postgis-2.1/precise # from ubuntugis-unstable, not pgdg
 
-sudo -H pip install autopep8 # TODO when switching to trusty or above: replace python-pip with python-autopep8
-sudo -H pip install nose2 pyyaml mock
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+${DIR}/qt${QT_VERSION}/before_install.sh
diff --git a/ci/travis/linux/before_script.sh b/ci/travis/linux/before_script.sh
index 9a3abc7..f9c0786 100755
--- a/ci/travis/linux/before_script.sh
+++ b/ci/travis/linux/before_script.sh
@@ -1,3 +1,18 @@
+###########################################################################
+#    before_script.sh
+#    ---------------------
+#    Date                 : August 2015
+#    Copyright            : (C) 2015 by Nyall Dawson
+#    Email                : nyall dot dawson at gmail dot com
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
+
 printf "[qgis_test]\nhost=localhost\ndbname=qgis_test\nuser=postgres" > ~/.pg_service.conf
 
 export PGUSER=postgres
diff --git a/ci/travis/linux/install.sh b/ci/travis/linux/install.sh
index a69641a..43b83d3 100755
--- a/ci/travis/linux/install.sh
+++ b/ci/travis/linux/install.sh
@@ -1,19 +1,19 @@
-mkdir build
-cd build
+#!/bin/bash
+###########################################################################
+#    install.sh
+#    ---------------------
+#    Date                 : August 2015
+#    Copyright            : (C) 2015 by Nyall Dawson
+#    Email                : nyall dot dawson at gmail dot com
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
 
-CLANG_WARNINGS="-Wimplicit-fallthrough"
 
-cmake -DWITH_SERVER=ON \
-      -DWITH_STAGED_PLUGINS=ON \
-      -DWITH_GRASS=ON \
-      -DSUPPRESS_QT_WARNINGS=ON \
-      -DENABLE_MODELTEST=ON \
-      -DENABLE_PGTEST=ON \
-      -DWITH_QWTPOLAR=OFF \
-      -DWITH_APIDOC=ON \
-      -DWITH_ASTYLE=ON \
-      -DWITH_PYSPATIALITE=ON \
-      -DGRASS_PREFIX7=/usr/lib/grass70 \
-      -DGRASS_INCLUDE_DIR7=/usr/lib/grass70/include \
-      -DCXX_EXTRA_FLAGS="$CLANG_WARNINGS" \
-      ..
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+${DIR}/qt${QT_VERSION}/install.sh
diff --git a/ci/travis/linux/qt4/before_install.sh b/ci/travis/linux/qt4/before_install.sh
new file mode 100755
index 0000000..b49ef1c
--- /dev/null
+++ b/ci/travis/linux/qt4/before_install.sh
@@ -0,0 +1,24 @@
+###########################################################################
+#    before_install.sh
+#    ---------------------
+#    Date                 : March 2016
+#    Copyright            : (C) 2016 by Matthias Kuhn
+#    Email                : matthias at opengis dot ch
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
+
+export DEBIAN_FRONTEND=noninteractive
+
+pushd ${HOME}
+
+curl -L https://github.com/opengisch/osgeo4travis/archive/qt4bin.tar.gz | tar -xzC /home/travis --strip-components=1
+curl -L https://cmake.org/files/v3.5/cmake-3.5.0-Linux-x86_64.tar.gz | tar --strip-components=1 -zxC /home/travis/osgeo4travis
+
+popd
+pip install --user autopep8 nose2 pyyaml mock future
diff --git a/ci/travis/linux/qt4/install.sh b/ci/travis/linux/qt4/install.sh
new file mode 100755
index 0000000..a4b0d1f
--- /dev/null
+++ b/ci/travis/linux/qt4/install.sh
@@ -0,0 +1,59 @@
+###########################################################################
+#    install.sh
+#    ---------------------
+#    Date                 : March 2016
+#    Copyright            : (C) 2016 by Matthias Kuhn
+#    Email                : matthias at opengis dot ch
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
+
+mkdir build
+cd build
+
+ln -s ${HOME}/osgeo4travis/bin/ccache ${HOME}/osgeo4travis/bin/clang++-${LLVM_VERSION}
+ln -s ${HOME}/osgeo4travis/bin/ccache ${HOME}/osgeo4travis/bin/clang-${LLVM_VERSION}
+ln -s ${HOME}/osgeo4travis/bin/ccache ${HOME}/osgeo4travis/bin/g++-6
+ln -s ${HOME}/osgeo4travis/bin/ccache ${HOME}/osgeo4travis/bin/gcc-6
+
+ccache -s
+
+#export CXX="clang++-${LLVM_VERSION}"
+#export CC="clang-${LLVM_VERSION}"
+export CXX="g++-6"
+export CC="gcc-6"
+
+export PATH=${HOME}/osgeo4travis/bin:${PATH}
+
+cmake --version
+${CC} --version
+${CXX} --version
+
+# CLANG_WARNINGS="-Wimplicit-fallthrough"
+CLANG_WARNINGS=""
+
+# Include this line for debug reasons
+#      -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+#
+cmake \
+      -DCMAKE_PREFIX_PATH=/home/travis/osgeo4travis \
+      -DWITH_STAGED_PLUGINS=ON \
+      -DWITH_GRASS=ON \
+      -DSUPPRESS_QT_WARNINGS=ON \
+      -DENABLE_MODELTEST=ON \
+      -DENABLE_PGTEST=ON \
+      -DWITH_QSPATIALITE=ON \
+      -DWITH_QWTPOLAR=OFF \
+      -DWITH_APIDOC=ON \
+      -DWITH_ASTYLE=ON \
+      -DWITH_SERVER=ON \
+      -DWITH_PYSPATIALITE=ON \
+      -DGRASS_PREFIX7=/usr/lib/grass70 \
+      -DGRASS_INCLUDE_DIR7=/usr/lib/grass70/include \
+      -DCXX_EXTRA_FLAGS="$CLANG_WARNINGS" \
+      ..
diff --git a/ci/travis/linux/qt4/script.sh b/ci/travis/linux/qt4/script.sh
new file mode 100755
index 0000000..6e3a554
--- /dev/null
+++ b/ci/travis/linux/qt4/script.sh
@@ -0,0 +1,27 @@
+###########################################################################
+#    script.sh
+#    ---------------------
+#    Date                 : March 2016
+#    Copyright            : (C) 2016 by Matthias Kuhn
+#    Email                : matthias at opengis dot ch
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
+
+export PYTHONPATH=${HOME}/osgeo4travis/lib/python2.7/site-packages/
+export PATH=${HOME}/osgeo4travis/bin:${HOME}/osgeo4travis/sbin:${PATH}
+export LD_LIBRARY_PATH=${HOME}/osgeo4travis/lib
+export CTEST_PARALLEL_LEVEL=1
+export CCACHE_CPP2=yes
+export CCACHE_TEMPDIR=/tmp
+if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
+  export CCACHE_READONLY=yes
+  chmod -R ugo-w ~/.ccache
+fi
+
+xvfb-run ctest -V -E 'qgis_openstreetmaptest|qgis_wcsprovidertest' -S ./qgis-test-travis.ctest --output-on-failure
diff --git a/ci/travis/linux/script.sh b/ci/travis/linux/script.sh
index 6c729f1..0bf66b4 100755
--- a/ci/travis/linux/script.sh
+++ b/ci/travis/linux/script.sh
@@ -1 +1,19 @@
-xvfb-run ctest -V -E 'qgis_openstreetmaptest|qgis_wcsprovidertest' -S ./qgis-test-travis.ctest --output-on-failure
+#!/bin/bash
+###########################################################################
+#    script.sh
+#    ---------------------
+#    Date                 : August 2015
+#    Copyright            : (C) 2015 by Nyall Dawson
+#    Email                : nyall dot dawson at gmail dot com
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
+
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+${DIR}/qt${QT_VERSION}/script.sh
diff --git a/ci/travis/osx/before_install.sh b/ci/travis/osx/before_install.sh
index 887126b..8d4ff11 100755
--- a/ci/travis/osx/before_install.sh
+++ b/ci/travis/osx/before_install.sh
@@ -1,7 +1,24 @@
+###########################################################################
+#    before_install.sh
+#    ---------------------
+#    Date                 : August 2015
+#    Copyright            : (C) 2015 by Nyall Dawson
+#    Email                : nyall dot dawson at gmail dot com
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
+
+# Remove default gdal provided by travis  (we will replace it with gdal 2)
+brew remove gdal || true
+
 brew tap osgeo/osgeo4mac
 brew update
-brew install osgeo/osgeo4mac/qgis-28 --without-postgis --without-postgresql --without-grass --without-gpsbabel --only-dependencies
-brew install qca
+brew install osgeo/osgeo4mac/qgis-214 --without-postgresql --only-dependencies
 brew install spawn-fcgi
 brew install lighttpd
 brew install poppler
@@ -14,8 +31,9 @@ brew ln libxml2 --force
 brew ln gettext --force
 brew ln libffi --force
 
-mkdir -p /Users/travis/Library/Python/2.7/lib/python/site-packages
-echo 'import site; site.addsitedir("/usr/local/lib/python2.7/site-packages")' >> /Users/travis/Library/Python/2.7/lib/python/site-packages/homebrew.pth
+mkdir -p ${HOME}/Library/Python/2.7/lib/python/site-packages
+echo 'import site; site.addsitedir("/usr/local/lib/python2.7/site-packages")' >> ${HOME}/Library/Python/2.7/lib/python/site-packages/homebrew.pth
+echo 'import site; site.addsitedir("/usr/local/opt/gdal-20/lib/python2.7/site-packages")' >> ${HOME}/Library/Python/2.7/lib/python/site-packages/gdal2.pth
 
 # Needed for Processing
-pip install psycopg2 numpy nose2 pyyaml mock
+pip install psycopg2 numpy nose2 pyyaml mock future
diff --git a/ci/travis/osx/install.sh b/ci/travis/osx/install.sh
index dea056f..d778411 100755
--- a/ci/travis/osx/install.sh
+++ b/ci/travis/osx/install.sh
@@ -1,11 +1,36 @@
+###########################################################################
+#    install.sh
+#    ---------------------
+#    Date                 : August 2015
+#    Copyright            : (C) 2015 by Nyall Dawson
+#    Email                : nyall dot dawson at gmail dot com
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
+
 mkdir build
 cd build
 #no PGTEST for OSX - can't get postgres to start with brew install
 #no APIDOC for OSX - doxygen tests and warnings are covered by linux build
 #no deprecated-declarations warnings... requires QGIS ported to Cocoa
-cmake -DWITH_SERVER=ON -DWITH_STAGED_PLUGINS=ON -DWITH_GRASS=OFF \
-          -DSUPPRESS_QT_WARNINGS=ON -DENABLE_MODELTEST=ON -DENABLE_PGTEST=OFF \
-          -DWITH_QWTPOLAR=OFF -DWITH_PYSPATIALITE=ON \
-          -DQWT_INCLUDE_DIR=/usr/local/opt/qwt/lib/qwt.framework/Headers/ \
-          -DQWT_LIBRARY=/usr/local/opt/qwt/lib/qwt.framework/qwt \
-          -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" ..
+cmake \
+  -DWITH_SERVER=ON \
+  -DWITH_STAGED_PLUGINS=ON \
+  -DWITH_GRASS=OFF \
+  -DSUPPRESS_SIP_WARNINGS=ON \
+  -DSUPPRESS_QT_WARNINGS=ON \
+  -DENABLE_MODELTEST=ON \
+  -DENABLE_PGTEST=OFF \
+  -DWITH_QWTPOLAR=OFF \
+  -DWITH_PYSPATIALITE=ON \
+  -DQWT_INCLUDE_DIR=/usr/local/opt/qwt/lib/qwt.framework/Headers/ \
+  -DQWT_LIBRARY=/usr/local/opt/qwt/lib/qwt.framework/qwt \
+  -DGDAL_CONFIG=/usr/local/opt/gdal-20/bin/gdal-config \
+  -DGRASS_PREFIX7=/usr/local/opt/grass-70/grass-7.0.4 \
+  -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" \
+  ..
diff --git a/ci/travis/osx/script.sh b/ci/travis/osx/script.sh
index 084b71c..a3a2c74 100755
--- a/ci/travis/osx/script.sh
+++ b/ci/travis/osx/script.sh
@@ -1,2 +1,21 @@
-ctest -V -E 'qgis_openstreetmaptest|qgis_wcsprovidertest|PyQgsServer' -S ./qgis-test-travis.ctest --output-on-failure
+###########################################################################
+#    script.sh
+#    ---------------------
+#    Date                 : August 2015
+#    Copyright            : (C) 2015 by Nyall Dawson
+#    Email                : nyall dot dawson at gmail dot com
+###########################################################################
+#                                                                         #
+#   This program is free software; you can redistribute it and/or modify  #
+#   it under the terms of the GNU General Public License as published by  #
+#   the Free Software Foundation; either version 2 of the License, or     #
+#   (at your option) any later version.                                   #
+#                                                                         #
+###########################################################################
+
+echo $PATH
+
+export PATH=/usr/bin:${PATH}
+
+ctest -V -E 'qgis_openstreetmaptest|qgis_wcsprovidertest|PyQgsServer|ProcessingGdalAlgorithmsTest|PyQgsOfflineEditingWFS|ProcessingGrass7AlgorithmsImageryTest|ProcessingGrass7AlgorithmsRasterTest|qgis_composerhtmltest' -S ./qgis-test-travis.ctest --output-on-failure
 
diff --git a/cmake/SIPMacros.cmake b/cmake/SIPMacros.cmake
index e308d04..d026741 100644
--- a/cmake/SIPMacros.cmake
+++ b/cmake/SIPMacros.cmake
@@ -103,6 +103,11 @@ MACRO(ADD_SIP_PYTHON_MODULE MODULE_NAME MODULE_SIP)
 
 
     SET(SIPCMD ${SIP_BINARY_PATH} ${_sip_tags} -w -e ${_sip_x} ${SIP_EXTRA_OPTIONS} -j ${SIP_CONCAT_PARTS} -c ${CMAKE_CURRENT_BINARY_DIR}/${_module_path} ${_sip_includes} ${_abs_module_sip})
+    SET(SUPPRESS_SIP_WARNINGS FALSE CACHE BOOL "Hide SIP warnings")
+    MARK_AS_ADVANCED(SUPPRESS_SIP_WARNINGS)
+    IF(SUPPRESS_SIP_WARNINGS)
+      SET(SIPCMD ${SIPCMD} 2> /dev/null || true)
+    ENDIF(SUPPRESS_SIP_WARNINGS)
 
     ADD_CUSTOM_COMMAND(
         OUTPUT ${_sip_output_files}
@@ -110,6 +115,7 @@ MACRO(ADD_SIP_PYTHON_MODULE MODULE_NAME MODULE_SIP)
         COMMAND ${CMAKE_COMMAND} -E touch ${_sip_output_files}
         COMMAND ${SIPCMD}
         DEPENDS ${_abs_module_sip} ${SIP_EXTRA_FILES_DEPEND}
+        VERBATIM
     )
     # not sure if type MODULE could be uses anywhere, limit to cygwin for now
     IF (CYGWIN OR APPLE)
diff --git a/debian/changelog b/debian/changelog
index da8fb0a..b85a92f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,14 @@
-qgis (2.14.4) UNRELEASED; urgency=medium
+qgis (2.14.5) UNRELEASED; urgency=medium
+
+  * Release of 2.14.5
+
+ -- Jürgen E. Fischer <jef at norbit.de>  Fri, 29 Jul 2016 14:03:54 +0200
+
+qgis (2.14.4) unstable; urgency=medium
 
   * Release of 2.14.4
 
- -- Jürgen E. Fischer <jef at norbit.de>  Fri, 08 Jul 2016 14:00:33 +0200
+ -- Jürgen E. Fischer <jef at norbit.de>  Fri, 29 Jul 2016 14:03:54 +0200
 
 qgis (2.14.3) unstable; urgency=medium
 
diff --git a/debian/qgis.install b/debian/qgis.install
index 74169d1..b452705 100644
--- a/debian/qgis.install
+++ b/debian/qgis.install
@@ -20,4 +20,3 @@ usr/share/applications/
 usr/share/mime/packages/
 usr/share/mimelnk/
 usr/share/icons/hicolor/
-usr/share/icons/scalable/
diff --git a/debian/rules b/debian/rules
index 90e72c7..1d87840 100755
--- a/debian/rules
+++ b/debian/rules
@@ -279,9 +279,9 @@ override_dh_auto_install:
 		install -o root -g root -m 644 $(CURDIR)/debian/qbrowser-icon$${size}.png $(CURDIR)/debian/tmp/usr/share/icons/hicolor/$${size}/apps/qbrowser.png ; \
 	done
 	
-	install -o root -g root -d $(CURDIR)/debian/tmp/usr/share/icons/scalable/apps
-	install -o root -g root -m 644 $(CURDIR)/images/icons/qgis_icon.svg $(CURDIR)/debian/tmp/usr/share/icons/scalable/apps/qgis.svg
-	install -o root -g root -m 644 $(CURDIR)/images/icons/qbrowser_icon.svg $(CURDIR)/debian/tmp/usr/share/icons/scalable/apps/qbrowser.svg
+	install -o root -g root -d $(CURDIR)/debian/tmp/usr/share/icons/hicolor/scalable/apps
+	install -o root -g root -m 644 $(CURDIR)/images/icons/qgis_icon.svg $(CURDIR)/debian/tmp/usr/share/icons/hicolor/scalable/apps/qgis.svg
+	install -o root -g root -m 644 $(CURDIR)/images/icons/qbrowser_icon.svg $(CURDIR)/debian/tmp/usr/share/icons/hicolor/scalable/apps/qbrowser.svg
 
 	# Install desktop files
 	install -o root -g root -d $(CURDIR)/debian/tmp/usr/share/applications
diff --git a/python/PyQt/CMakeLists.txt b/python/PyQt/CMakeLists.txt
index 80fe79c..1520079 100644
--- a/python/PyQt/CMakeLists.txt
+++ b/python/PyQt/CMakeLists.txt
@@ -21,6 +21,7 @@ SET(PYQT_COMPAT_FILES
 )
 
 ADD_CUSTOM_TARGET(pyqtcompat ALL)
+ADD_DEPENDENCIES(pyqtcompat pyutils)
 
 IF(ENABLE_QT5)
   SET(PYQT_PREFIX PyQt5)
diff --git a/python/core/composer/qgscomposerlegend.sip b/python/core/composer/qgscomposerlegend.sip
index 43fd587..c306979 100644
--- a/python/core/composer/qgscomposerlegend.sip
+++ b/python/core/composer/qgscomposerlegend.sip
@@ -46,6 +46,20 @@ class QgsComposerLegend : QgsComposerItem
     /** Sets item box to the whole content*/
     void adjustBoxSize();
 
+    /** Sets whether the legend should automatically resize to fit its contents.
+     * @param enabled set to false to disable automatic resizing. The legend frame will not
+     * be expanded to fit legend items, and items may be cropped from display.
+     * @see resizeToContents()
+     * @note added in QGIS 3.0
+     */
+    void setResizeToContents( bool enabled );
+
+    /** Returns whether the legend should automatically resize to fit its contents.
+     * @see setResizeToContents()
+     * @note added in QGIS 3.0
+     */
+    bool resizeToContents() const;
+
     /** Returns pointer to the legend model*/
     //! @deprecated in 2.6 - use modelV2()
     QgsLegendModel* model() /Deprecated/;
diff --git a/python/core/composer/qgscomposition.sip b/python/core/composer/qgscomposition.sip
index d3b0771..58b14eb 100644
--- a/python/core/composer/qgscomposition.sip
+++ b/python/core/composer/qgscomposition.sip
@@ -856,4 +856,9 @@ class QgsComposition : QGraphicsScene
 
     /** Is emitted when the composition has an updated status bar message for the composer window*/
     void statusMsgChanged( QString message );
+
+    /** Emitted whenever the expression variables stored in the composition have been changed.
+     * @note added in QGIS 3.0
+     */
+    void variablesChanged();
 };
diff --git a/python/core/core.sip b/python/core/core.sip
index f54fddd..8b37734 100644
--- a/python/core/core.sip
+++ b/python/core/core.sip
@@ -18,6 +18,7 @@
 
 %Include qgis.sip
 
+%Include qgsannotation.sip
 %Include qgsapplication.sip
 %Include qgsattributeaction.sip
 %Include qgsbrowsermodel.sip
diff --git a/python/core/qgsannotation.sip b/python/core/qgsannotation.sip
new file mode 100644
index 0000000..76b661a
--- /dev/null
+++ b/python/core/qgsannotation.sip
@@ -0,0 +1,64 @@
+/** \ingroup core
+ * \class QgsAnnotation
+ * \note added in QGIS 3.0
+ *
+ * \brief An interface for annotation items which are drawn over a map.
+ *
+ * QgsAnnotation is an interface class for map annotation items. These annotations can be
+ * drawn within a map, and have either a fixed map position (retrieved using mapPosition())
+ * or are placed relative to the map's frame (retrieved using relativePosition()).
+ * Annotations with a fixed map position also have a corresponding
+ * QgsCoordinateReferenceSystem, which can be determined by calling mapPositionCrs().
+ */
+
+class QgsAnnotation
+{
+%TypeHeaderCode
+#include <qgsannotation.h>
+%End
+  public:
+
+    //! Returns true if the annotation should be shown.
+    virtual bool showItem() const = 0;
+
+    /** Returns true if the annotation is attached to a fixed map position, or
+     * false if the annotation uses a position relative to the current map
+     * extent.
+     * @see mapPosition()
+     * @see relativePositon()
+     */
+    //TODO QGIS 3 - rename to hasFixedMapPosition()
+    virtual bool mapPositionFixed() const = 0;
+
+    /** Returns the map position of the annotation, if it is attached to a fixed map
+     * position.
+     * @see mapPositionFixed()
+     * @see mapPositionCrs()
+     */
+    virtual QgsPoint mapPosition() const;
+
+    /** Returns the CRS of the map position, or an invalid CRS if the annotation does
+     * not have a fixed map position.
+    */
+    virtual QgsCoordinateReferenceSystem mapPositionCrs() const;
+
+    /** Returns the relative position of the annotation, if it is not attached to a fixed map
+     * position. The coordinates in the return point should be between 0 and 1, and represent
+     * the relative percentage for the position compared to the map width and height.
+     * @see mapPositionFixed()
+     */
+    virtual QPointF relativePosition() const;
+
+    /** Returns a scaling factor which should be applied to painters before rendering
+     * the item.
+     */
+    virtual double scaleFactor() const = 0;
+
+    //! deprecated - do not use
+    // TODO QGIS 3.0 - remove
+    virtual void setItemData( int role, const QVariant& value ) = 0;
+
+    //! Paint the annotation to a destination painter
+    virtual void paint( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ) = 0;
+
+};
diff --git a/python/core/qgsapplication.sip b/python/core/qgsapplication.sip
index 40ead0b..be66870 100644
--- a/python/core/qgsapplication.sip
+++ b/python/core/qgsapplication.sip
@@ -379,7 +379,23 @@ static void qtgui_UpdatePyArgv(PyObject *argvlist, int argc, char **argv)
     bool x11EventFilter ( XEvent * event );
 %End
 
+  public slots:
+
+    /** Causes the application instance to emit the settingsChanged() signal. This should
+     * be called whenever global, application-wide settings are altered to advise listeners
+     * that they may need to update their state.
+     * @see settingsChanged()
+     * @note added in QGIS 3.0
+     */
+    void emitSettingsChanged();
+
   signals:
     //! @note not available in python bindings
     // void preNotify( QObject * receiver, QEvent * event, bool * done );
+
+    /** Emitted whenever any global, application-wide settings are changed.
+     * @note added in QGIS 3.0
+     * @see emitSettingsChanged()
+     */
+    void settingsChanged();
 };
diff --git a/python/core/qgsexpressioncontext.sip b/python/core/qgsexpressioncontext.sip
index 405a3c1..bb3ee14 100644
--- a/python/core/qgsexpressioncontext.sip
+++ b/python/core/qgsexpressioncontext.sip
@@ -289,6 +289,13 @@ class QgsExpressionContext
      */
     int indexOfScope( QgsExpressionContextScope* scope ) const;
 
+    /** Returns the index of the first scope with a matching name within the context.
+     * @param scopeName name of scope to find
+     * @returns index of scope, or -1 if scope was not found within the context.
+     * @note added in QGIS 3.0
+     */
+    int indexOfScope( const QString& scopeName ) const;
+
     /** Returns a list of variables names set by all scopes in the context.
      * @returns list of unique variable names
      * @see filteredVariableNames
diff --git a/python/core/qgsnetworkaccessmanager.sip b/python/core/qgsnetworkaccessmanager.sip
index 9a15075..65da26a 100644
--- a/python/core/qgsnetworkaccessmanager.sip
+++ b/python/core/qgsnetworkaccessmanager.sip
@@ -61,7 +61,7 @@ class QgsNetworkAccessManager : QNetworkAccessManager
     void setupDefaultProxyAndCache();
 
     //! return whether the system proxy should be used
-    bool useSystemProxy();
+    bool useSystemProxy() const;
 
   public slots:
     /** Send GET request, calls get().
diff --git a/python/core/qgsproject.sip b/python/core/qgsproject.sip
index 845bde4..3982c0a 100644
--- a/python/core/qgsproject.sip
+++ b/python/core/qgsproject.sip
@@ -345,6 +345,20 @@ class QgsProject : QObject
 
     void snapSettingsChanged();
 
+    /** Emitted whenever the expression variables stored in the project have been changed.
+     * @note added in QGIS 3.0
+     */
+    void variablesChanged();
+
+  public slots:
+
+    /** Causes the project to emit the variablesChanged() signal. This should
+     * be called whenever expression variables related to the project are changed.
+     * @see variablesChanged()
+     * @note added in QGIS 3.0
+     */
+    void emitVariablesChanged();
+
   private:
 
     QgsProject(); // private 'cause it's a singleton
diff --git a/python/gui/qgsannotationitem.sip b/python/gui/qgsannotationitem.sip
index 2526e36..c4d9df8 100644
--- a/python/gui/qgsannotationitem.sip
+++ b/python/gui/qgsannotationitem.sip
@@ -8,7 +8,7 @@
 #include "qgstextannotationitem.h"
 %End
 
-class QgsAnnotationItem: QgsMapCanvasItem
+class QgsAnnotationItem: QgsMapCanvasItem, QgsAnnotation
 {
 %TypeHeaderCode
 #include <qgsannotationitem.h>
@@ -66,6 +66,12 @@ class QgsAnnotationItem: QgsMapCanvasItem
     virtual void setMapPosition( const QgsPoint& pos );
     QgsPoint mapPosition() const;
 
+    virtual QPointF relativePosition() const;
+
+    virtual double scaleFactor() const;
+
+    virtual bool showItem() const;
+
     /** Sets the CRS of the map position.
       @param crs the CRS to set */
     virtual void setMapPositionCrs( const QgsCoordinateReferenceSystem& crs );
@@ -97,18 +103,28 @@ class QgsAnnotationItem: QgsMapCanvasItem
     void _writeXML( QDomDocument& doc, QDomElement& itemElem ) const;
     void _readXML( const QDomDocument& doc, const QDomElement& annotationElem );
 
+    virtual void setItemData( int role, const QVariant& value );
+    virtual void paint( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr );
+    void paint( QPainter* painter );
+
   protected:
     void updateBoundingRect();
     /** Check where to attach the balloon connection between frame and map point*/
     void updateBalloon();
 
-    void drawFrame( QPainter* p );
-    void drawMarkerSymbol( QPainter* p );
-    void drawSelectionBoxes( QPainter* p );
+    //! Draws the annotation frame to a destination painter
+    void drawFrame( QPainter* p ) const;
+
+    //! Draws the map position marker symbol to a destination painter
+    void drawMarkerSymbol( QPainter* p ) const;
+
+    //! Draws selection handles around the item
+    void drawSelectionBoxes( QPainter* p ) const;
+
     /** Returns frame width in painter units*/
     //double scaledFrameWidth( QPainter* p) const;
     /** Gets the frame line (0 is the top line, 1 right, 2 bottom, 3 left)*/
-    QLineF segment( int index );
+    QLineF segment( int index ) const;
     /** Returns a point on the line from startPoint to directionPoint that is a certain distance away from the starting point*/
     QPointF pointOnLineWithDistance( QPointF startPoint, QPointF directionPoint, double distance ) const;
     /** Returns the symbol size scaled in (mapcanvas) pixels. Used for the counding rect calculation*/
diff --git a/python/plugins/processing/ProcessingPlugin.py b/python/plugins/processing/ProcessingPlugin.py
index 3dd031a..cb0c89c 100644
--- a/python/plugins/processing/ProcessingPlugin.py
+++ b/python/plugins/processing/ProcessingPlugin.py
@@ -117,7 +117,7 @@ class ProcessingPlugin:
         self.commanderAction.triggered.connect(self.openCommander)
         self.menu.addAction(self.commanderAction)
         self.iface.registerMainWindowAction(self.commanderAction,
-                                            self.tr('Ctrl+Alt+M'))
+                                            self.tr('Ctrl+Alt+D'))
 
     def unload(self):
         self.toolbox.setVisible(False)
diff --git a/python/plugins/processing/algs/gdal/GdalAlgorithm.py b/python/plugins/processing/algs/gdal/GdalAlgorithm.py
index 9b65d14..ea4a890 100644
--- a/python/plugins/processing/algs/gdal/GdalAlgorithm.py
+++ b/python/plugins/processing/algs/gdal/GdalAlgorithm.py
@@ -16,8 +16,6 @@
 *                                                                         *
 ***************************************************************************
 """
-from processing.tools import dataobjects
-
 
 __author__ = 'Victor Olaya'
 __date__ = 'August 2012'
@@ -34,6 +32,7 @@ from PyQt4.QtGui import QIcon
 from processing.core.GeoAlgorithm import GeoAlgorithm
 from processing.algs.gdal.GdalAlgorithmDialog import GdalAlgorithmDialog
 from processing.algs.gdal.GdalUtils import GdalUtils
+from processing.tools import dataobjects
 
 pluginPath = os.path.normpath(os.path.join(
     os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -59,7 +58,7 @@ class GdalAlgorithm(GeoAlgorithm):
                     c = c.replace(layer.source(), exported)
                     if os.path.isfile(layer.source()):
                         fileName = os.path.splitext(os.path.split(layer.source())[1])[0]
-                        c = c.replace(fileName, exportedFileName)
+                        c = c.replace(' ' + fileName + ' ', ' ' + exportedFileName + ' ')
 
             commands[i] = c
         GdalUtils.runGdal(commands, progress)
diff --git a/python/plugins/processing/algs/qgis/Buffer.py b/python/plugins/processing/algs/qgis/Buffer.py
index db55b0d..433ae2f 100644
--- a/python/plugins/processing/algs/qgis/Buffer.py
+++ b/python/plugins/processing/algs/qgis/Buffer.py
@@ -57,8 +57,11 @@ def buffering(progress, writer, distance, field, useField, layer, dissolve,
                 value = distance
 
             inGeom = QgsGeometry(inFeat.geometry())
-            if inGeom.isGeosEmpty() or not inGeom.isGeosValid():
-                ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, 'Feature {} has empty or invalid geometry. Skipping...'.format(inFeat.id()))
+            if inGeom.isGeosEmpty():
+                ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, 'Feature {} has empty geometry. Skipping...'.format(inFeat.id()))
+                continue
+            if not inGeom.isGeosValid():
+                ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, 'Feature {} has invalid geometry. Skipping...'.format(inFeat.id()))
                 continue
             outGeom = inGeom.buffer(float(value), segments)
             if first:
@@ -82,8 +85,11 @@ def buffering(progress, writer, distance, field, useField, layer, dissolve,
             else:
                 value = distance
             inGeom = QgsGeometry(inFeat.geometry())
-            if inGeom.isGeosEmpty() or not inGeom.isGeosValid():
-                ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, 'Feature {} has empty or invalid geometry. Skipping...'.format(inFeat.id()))
+            if inGeom.isGeosEmpty():
+                ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, 'Feature {} has empty geometry. Skipping...'.format(inFeat.id()))
+                continue
+            if not inGeom.isGeosValid():
+                ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, 'Feature {} has invalid geometry. Skipping...'.format(inFeat.id()))
                 continue
 
             outGeom = inGeom.buffer(float(value), segments)
diff --git a/python/plugins/processing/algs/qgis/Difference.py b/python/plugins/processing/algs/qgis/Difference.py
index d375c30..fafd79c 100644
--- a/python/plugins/processing/algs/qgis/Difference.py
+++ b/python/plugins/processing/algs/qgis/Difference.py
@@ -29,7 +29,7 @@ from qgis.core import QGis, QgsFeatureRequest, QgsFeature, QgsGeometry
 from processing.core.ProcessingLog import ProcessingLog
 from processing.core.GeoAlgorithm import GeoAlgorithm
 from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
-from processing.core.parameters import ParameterVector
+from processing.core.parameters import ParameterVector, ParameterBoolean
 from processing.core.outputs import OutputVector
 from processing.tools import dataobjects, vector
 
@@ -38,6 +38,7 @@ class Difference(GeoAlgorithm):
 
     INPUT = 'INPUT'
     OVERLAY = 'OVERLAY'
+    IGNORE_INVALID = 'IGNORE_INVALID'
     OUTPUT = 'OUTPUT'
 
     #==========================================================================
@@ -52,6 +53,8 @@ class Difference(GeoAlgorithm):
                                           self.tr('Input layer'), [ParameterVector.VECTOR_TYPE_ANY]))
         self.addParameter(ParameterVector(Difference.OVERLAY,
                                           self.tr('Difference layer'), [ParameterVector.VECTOR_TYPE_ANY]))
+        self.addParameter(ParameterBoolean(Difference.IGNORE_INVALID,
+                                           self.tr('Ignore invalid input features'), False, True))
         self.addOutput(OutputVector(Difference.OUTPUT, self.tr('Difference')))
 
     def processAlgorithm(self, progress):
@@ -59,6 +62,7 @@ class Difference(GeoAlgorithm):
             self.getParameterValue(Difference.INPUT))
         layerB = dataobjects.getObjectFromUri(
             self.getParameterValue(Difference.OVERLAY))
+        ignoreInvalid = self.getParameterValue(Difference.IGNORE_INVALID)
 
         geomType = layerA.dataProvider().geometryType()
         writer = self.getOutputFromName(
@@ -82,12 +86,16 @@ class Difference(GeoAlgorithm):
                 tmpGeom = QgsGeometry(inFeatB.geometry())
                 if diff_geom.intersects(tmpGeom):
                     diff_geom = QgsGeometry(diff_geom.difference(tmpGeom))
-                    if diff_geom.isGeosEmpty() or not diff_geom.isGeosValid():
-                        ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
-                                               self.tr('GEOS geoprocessing error: One or '
-                                                       'more input features have invalid '
-                                                       'geometry.'))
-                        add = False
+                    if diff_geom.isGeosEmpty():
+                        ProcessingLog.addToLog(ProcessingLog.LOG_INFO,
+                                               self.tr('Feature with NULL geometry found.'))
+                    if not diff_geom.isGeosValid():
+                        if ignoreInvalid:
+                            ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
+                                                   self.tr('GEOS geoprocessing error: One or more input features have invalid geometry.'))
+                            add = False
+                        else:
+                            raise GeoAlgorithmExecutionException(self.tr('Features with invalid geometries found. Please fix these errors or specify the "Ignore invalid input features" flag'))
                         break
 
             if add:
diff --git a/python/plugins/processing/modeler/ModelerParametersDialog.py b/python/plugins/processing/modeler/ModelerParametersDialog.py
index 72f8336..dc22404 100644
--- a/python/plugins/processing/modeler/ModelerParametersDialog.py
+++ b/python/plugins/processing/modeler/ModelerParametersDialog.py
@@ -26,7 +26,22 @@ __copyright__ = '(C) 2012, Victor Olaya'
 __revision__ = '$Format:%H$'
 
 from PyQt4.QtCore import Qt, QUrl, QMetaObject
-from PyQt4.QtGui import QDialog, QDialogButtonBox, QLabel, QLineEdit, QFrame, QPushButton, QSizePolicy, QVBoxLayout, QHBoxLayout, QTabWidget, QWidget, QScrollArea, QComboBox, QTableWidgetItem, QMessageBox
+from PyQt4.QtGui import (QDialog,
+                         QDialogButtonBox,
+                         QLabel,
+                         QLineEdit,
+                         QFrame,
+                         QPushButton,
+                         QSizePolicy,
+                         QVBoxLayout,
+                         QHBoxLayout,
+                         QTabWidget,
+                         QWidget,
+                         QScrollArea,
+                         QComboBox,
+                         QTableWidgetItem,
+                         QMessageBox,
+                         QTextBrowser)
 from PyQt4.QtNetwork import QNetworkRequest, QNetworkReply
 
 from qgis.core import QgsNetworkAccessManager
diff --git a/python/plugins/processing/tools/dataobjects.py b/python/plugins/processing/tools/dataobjects.py
index b00c57b..0c3640e 100644
--- a/python/plugins/processing/tools/dataobjects.py
+++ b/python/plugins/processing/tools/dataobjects.py
@@ -28,7 +28,8 @@ __revision__ = '$Format:%H$'
 
 import os
 import re
-from qgis.core import QGis, QgsProject, QgsVectorFileWriter, QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsMapLayerRegistry, QgsCoordinateReferenceSystem
+from qgis.core import QGis, QgsProject, QgsVectorFileWriter, QgsMapLayer, QgsRasterLayer, \
+                        QgsVectorLayer, QgsMapLayerRegistry, QgsCoordinateReferenceSystem
 from qgis.gui import QgsSublayersDialog
 from PyQt4.QtCore import QSettings
 from qgis.utils import iface
@@ -289,18 +290,7 @@ def exportVectorLayer(layer, supported=None):
     settings = QSettings()
     systemEncoding = settings.value('/UI/encoding', 'System')
 
-    filename = os.path.basename(unicode(layer.source()))
-    idx = filename.rfind('.')
-    if idx != -1:
-        filename = filename[:idx]
-
-    filename = unicode(layer.name())
-    validChars = \
-        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:'
-    filename = ''.join(c for c in filename if c in validChars)
-    if len(filename) == 0:
-        filename = 'layer'
-    output = getTempFilenameInTempFolder(filename + '.shp')
+    output = getTempFilename('shp')
     provider = layer.dataProvider()
     useSelection = ProcessingConfig.getSetting(ProcessingConfig.USE_SELECTED)
     if useSelection and layer.selectedFeatureCount() != 0:
@@ -318,7 +308,7 @@ def exportVectorLayer(layer, supported=None):
             unicode(layer.source()).decode('ascii')
         except UnicodeEncodeError:
             isASCII = False
-        if not os.path.splitext()[1] in supported or not isASCII:
+        if not os.path.splitext(layer.source())[1].lower() in supported or not isASCII:
             writer = QgsVectorFileWriter(
                 output, systemEncoding,
                 layer.pendingFields(), provider.geometryType(),
diff --git a/src/analysis/raster/qgsninecellfilter.cpp b/src/analysis/raster/qgsninecellfilter.cpp
index af31210..30ccce4 100644
--- a/src/analysis/raster/qgsninecellfilter.cpp
+++ b/src/analysis/raster/qgsninecellfilter.cpp
@@ -16,6 +16,7 @@
  ***************************************************************************/
 
 #include "qgsninecellfilter.h"
+#include "qgslogger.h"
 #include "cpl_string.h"
 #include <QProgressDialog>
 #include <QFile>
@@ -138,7 +139,10 @@ int QgsNineCellFilter::processRaster( QProgressDialog* p )
       {
         scanLine1[a] = mInputNodataValue;
       }
-      GDALRasterIO( rasterBand, GF_Read, 0, 0, xSize, 1, scanLine2, xSize, 1, GDT_Float32, 0, 0 );
+      if ( GDALRasterIO( rasterBand, GF_Read, 0, 0, xSize, 1, scanLine2, xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
+      {
+        QgsDebugMsg( "Raster IO Error" );
+      }
     }
     else
     {
@@ -158,7 +162,10 @@ int QgsNineCellFilter::processRaster( QProgressDialog* p )
     }
     else
     {
-      GDALRasterIO( rasterBand, GF_Read, 0, i + 1, xSize, 1, scanLine3, xSize, 1, GDT_Float32, 0, 0 );
+      if ( GDALRasterIO( rasterBand, GF_Read, 0, i + 1, xSize, 1, scanLine3, xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
+      {
+        QgsDebugMsg( "Raster IO Error" );
+      }
     }
 
     for ( int j = 0; j < xSize; ++j )
@@ -180,7 +187,10 @@ int QgsNineCellFilter::processRaster( QProgressDialog* p )
       }
     }
 
-    GDALRasterIO( outputRasterBand, GF_Write, 0, i, xSize, 1, resultLine, xSize, 1, GDT_Float32, 0, 0 );
+    if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, xSize, 1, resultLine, xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
+    {
+      QgsDebugMsg( "Raster IO Error" );
+    }
   }
 
   if ( p )
diff --git a/src/analysis/raster/qgsrastercalculator.cpp b/src/analysis/raster/qgsrastercalculator.cpp
index 961e790..cd3f431 100644
--- a/src/analysis/raster/qgsrastercalculator.cpp
+++ b/src/analysis/raster/qgsrastercalculator.cpp
@@ -156,7 +156,7 @@ int QgsRasterCalculator::processCalculation( QProgressDialog* p )
       //write scanline to the dataset
       if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, mNumOutputColumns, 1, calcData, mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
       {
-        qWarning( "RasterIO error!" );
+        QgsDebugMsg( "RasterIO error!" );
       }
 
       delete[] calcData;
diff --git a/src/analysis/raster/qgsrelief.cpp b/src/analysis/raster/qgsrelief.cpp
index 296f82d..974840a 100644
--- a/src/analysis/raster/qgsrelief.cpp
+++ b/src/analysis/raster/qgsrelief.cpp
@@ -15,6 +15,7 @@
  *                                                                         *
  ***************************************************************************/
 
+#include "qgslogger.h"
 #include "qgsrelief.h"
 #include "qgsaspectfilter.h"
 #include "qgshillshadefilter.h"
@@ -202,7 +203,10 @@ int QgsRelief::processRaster( QProgressDialog* p )
       {
         scanLine1[a] = mInputNodataValue;
       }
-      GDALRasterIO( rasterBand, GF_Read, 0, 0, xSize, 1, scanLine2, xSize, 1, GDT_Float32, 0, 0 );
+      if ( GDALRasterIO( rasterBand, GF_Read, 0, 0, xSize, 1, scanLine2, xSize, 1, GDT_Float32, 0, 0 )  != CE_None )
+      {
+        QgsDebugMsg( "Raster IO Error" );
+      }
     }
     else
     {
@@ -222,7 +226,10 @@ int QgsRelief::processRaster( QProgressDialog* p )
     }
     else
     {
-      GDALRasterIO( rasterBand, GF_Read, 0, i + 1, xSize, 1, scanLine3, xSize, 1, GDT_Float32, 0, 0 );
+      if ( GDALRasterIO( rasterBand, GF_Read, 0, i + 1, xSize, 1, scanLine3, xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
+      {
+        QgsDebugMsg( "Raster IO Error" );
+      }
     }
 
     for ( int j = 0; j < xSize; ++j )
@@ -254,9 +261,18 @@ int QgsRelief::processRaster( QProgressDialog* p )
       }
     }
 
-    GDALRasterIO( outputRedBand, GF_Write, 0, i, xSize, 1, resultRedLine, xSize, 1, GDT_Byte, 0, 0 );
-    GDALRasterIO( outputGreenBand, GF_Write, 0, i, xSize, 1, resultGreenLine, xSize, 1, GDT_Byte, 0, 0 );
-    GDALRasterIO( outputBlueBand, GF_Write, 0, i, xSize, 1, resultBlueLine, xSize, 1, GDT_Byte, 0, 0 );
+    if ( GDALRasterIO( outputRedBand, GF_Write, 0, i, xSize, 1, resultRedLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
+    {
+      QgsDebugMsg( "Raster IO Error" );
+    }
+    if ( GDALRasterIO( outputGreenBand, GF_Write, 0, i, xSize, 1, resultGreenLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
+    {
+      QgsDebugMsg( "Raster IO Error" );
+    }
+    if ( GDALRasterIO( outputBlueBand, GF_Write, 0, i, xSize, 1, resultBlueLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
+    {
+      QgsDebugMsg( "Raster IO Error" );
+    }
   }
 
   if ( p )
@@ -531,9 +547,13 @@ bool QgsRelief::exportFrequencyDistributionToCsv( const QString& file )
 
   for ( int i = 0; i < nCellsY; ++i )
   {
-    GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1,
-                  scanLine, nCellsX, 1, GDT_Float32,
-                  0, 0 );
+    if ( GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1,
+                       scanLine, nCellsX, 1, GDT_Float32,
+                       0, 0 ) != CE_None )
+    {
+      QgsDebugMsg( "Raster IO Error" );
+    }
+
     for ( int j = 0; j < nCellsX; ++j )
     {
       elevationClass = frequencyClassForElevation( scanLine[j], minMax[0], frequencyClassRange );
@@ -614,9 +634,12 @@ QList< QgsRelief::ReliefColor > QgsRelief::calculateOptimizedReliefClasses()
 
   for ( int i = 0; i < nCellsY; ++i )
   {
-    GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1,
-                  scanLine, nCellsX, 1, GDT_Float32,
-                  0, 0 );
+    if ( GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1,
+                       scanLine, nCellsX, 1, GDT_Float32,
+                       0, 0 ) != CE_None )
+    {
+      QgsDebugMsg( "Raster IO Error" );
+    }
     for ( int j = 0; j < nCellsX; ++j )
     {
       elevationClass = frequencyClassForElevation( scanLine[j], minMax[0], frequencyClassRange );
diff --git a/src/analysis/vector/qgszonalstatistics.cpp b/src/analysis/vector/qgszonalstatistics.cpp
index ec93f14..289a156 100644
--- a/src/analysis/vector/qgszonalstatistics.cpp
+++ b/src/analysis/vector/qgszonalstatistics.cpp
@@ -482,7 +482,11 @@ void QgsZonalStatistics::statisticsFromPreciseIntersection( void* band, const Qg
     double currentX = rasterBBox.xMinimum() + cellSizeX / 2.0 + pixelOffsetX * cellSizeX;
     for ( int col = 0; col < nCellsX; ++col )
     {
-      GDALRasterIO( band, GF_Read, pixelOffsetX + col, pixelOffsetY + row, nCellsX, 1, pixelData, 1, 1, GDT_Float32, 0, 0 );
+      if ( GDALRasterIO( band, GF_Read, pixelOffsetX + col, pixelOffsetY + row, nCellsX, 1, pixelData, 1, 1, GDT_Float32, 0, 0 ) != CE_None )
+      {
+        QgsDebugMsg( "Raster IO Error" );
+      }
+
       if ( !validPixel( *pixelData ) )
         continue;
 
diff --git a/src/app/composer/qgsatlascompositionwidget.cpp b/src/app/composer/qgsatlascompositionwidget.cpp
index d0ea077..2c6c091 100644
--- a/src/app/composer/qgsatlascompositionwidget.cpp
+++ b/src/app/composer/qgsatlascompositionwidget.cpp
@@ -271,8 +271,9 @@ void QgsAtlasCompositionWidget::on_mAtlasFeatureFilterCheckBox_stateChanged( int
   updateAtlasFeatures();
 }
 
-void QgsAtlasCompositionWidget::pageNameExpressionChanged( const QString& expression, bool valid )
+void QgsAtlasCompositionWidget::pageNameExpressionChanged( const QString&, bool valid )
 {
+  QString expression = mPageNameWidget->asExpression();
   QgsAtlasComposition* atlasMap = &mComposition->atlasComposition();
   if ( !atlasMap || ( !valid && !expression.isEmpty() ) )
   {
diff --git a/src/app/composer/qgscomposer.cpp b/src/app/composer/qgscomposer.cpp
index 33c76be..e6618d3 100644
--- a/src/app/composer/qgscomposer.cpp
+++ b/src/app/composer/qgscomposer.cpp
@@ -877,6 +877,30 @@ void QgsComposer::setTitle( const QString& title )
   }
 }
 
+bool QgsComposer::loadFromTemplate( const QDomDocument& templateDoc, bool clearExisting )
+{
+  // provide feedback, since composer will be hidden when loading template (much faster)
+  QScopedPointer< QDialog > dlg( new QgsBusyIndicatorDialog( tr( "Loading template into composer..." ), this ) );
+  dlg->setStyleSheet( mQgis->styleSheet() );
+  dlg->show();
+
+  setUpdatesEnabled( false );
+  bool result = mComposition->loadFromTemplate( templateDoc, nullptr, false, clearExisting );
+  setUpdatesEnabled( true );
+
+  dlg->close();
+
+  if ( result )
+  {
+    // update composition widget
+    QgsCompositionWidget* oldCompositionWidget = qobject_cast<QgsCompositionWidget *>( mGeneralDock->widget() );
+    delete oldCompositionWidget;
+    createCompositionWidget();
+  }
+
+  return result;
+}
+
 void QgsComposer::updateStatusCursorPos( QPointF cursorPosition )
 {
   if ( !mComposition )
@@ -3040,11 +3064,9 @@ void QgsComposer::on_mActionSaveAsTemplate_triggered()
 
 void QgsComposer::on_mActionLoadFromTemplate_triggered()
 {
-  loadTemplate( false );
-}
+  if ( !mComposition )
+    return;
 
-void QgsComposer::loadTemplate( const bool newComposer )
-{
   QSettings settings;
   QString openFileDir = settings.value( "UI/lastComposerTemplateDir", QDir::homePath() ).toString();
   QString openFileString = QFileDialog::getOpenFileName( nullptr, tr( "Load template" ), openFileDir, "*.qpt" );
@@ -3064,48 +3086,10 @@ void QgsComposer::loadTemplate( const bool newComposer )
     return;
   }
 
-  QgsComposer* c = nullptr;
-  QgsComposition* comp = nullptr;
-
-  if ( newComposer )
+  QDomDocument templateDoc;
+  if ( templateDoc.setContent( &templateFile ) )
   {
-    QString newTitle;
-    if ( !mQgis->uniqueComposerTitle( this, newTitle, true ) )
-    {
-      return;
-    }
-    c = mQgis->createNewComposer( newTitle );
-    if ( !c )
-    {
-      QMessageBox::warning( this, tr( "Composer error" ), tr( "Error, could not create new composer" ) );
-      return;
-    }
-    comp = c->composition();
-  }
-  else
-  {
-    c = this;
-    comp = mComposition;
-  }
-
-  if ( comp )
-  {
-    QDomDocument templateDoc;
-    if ( templateDoc.setContent( &templateFile ) )
-    {
-      // provide feedback, since composer will be hidden when loading template (much faster)
-      QDialog* dlg = new QgsBusyIndicatorDialog( tr( "Loading template into composer..." ) );
-      dlg->setStyleSheet( mQgis->styleSheet() );
-      dlg->show();
-
-      c->setUpdatesEnabled( false );
-      comp->loadFromTemplate( templateDoc, nullptr, false, newComposer );
-      c->setUpdatesEnabled( true );
-
-      dlg->close();
-      delete dlg;
-      dlg = nullptr;
-    }
+    loadFromTemplate( templateDoc, false );
   }
 }
 
diff --git a/src/app/composer/qgscomposer.h b/src/app/composer/qgscomposer.h
index aa9ad48..4eb573a 100644
--- a/src/app/composer/qgscomposer.h
+++ b/src/app/composer/qgscomposer.h
@@ -99,9 +99,12 @@ class QgsComposer: public QMainWindow, private Ui::QgsComposerBase
     const QString& title() const {return mTitle;}
     void setTitle( const QString& title );
 
-    //! Load template into current or blank composer
-    //! @param newComposer whether to create a new composer first
-    void loadTemplate( const bool newComposer );
+    /** Loads the contents of a template document into the composer's composition.
+     * @param templateDoc template document to load
+     * @param clearExisting set to true to remove all existing composition settings and items before loading template
+     * @returns true if template load was successful
+     */
+    bool loadFromTemplate( const QDomDocument& templateDoc, bool clearExisting );
 
   protected:
     //! Move event
diff --git a/src/app/composer/qgscomposeritemwidget.cpp b/src/app/composer/qgscomposeritemwidget.cpp
index f0f8a5c..280edc3 100644
--- a/src/app/composer/qgscomposeritemwidget.cpp
+++ b/src/app/composer/qgscomposeritemwidget.cpp
@@ -23,6 +23,7 @@
 #include "qgspoint.h"
 #include "qgsdatadefinedbutton.h"
 #include "qgsexpressioncontext.h"
+#include "qgsproject.h"
 #include <QColorDialog>
 #include <QPen>
 
@@ -109,6 +110,16 @@ QgsVectorLayer* QgsComposerItemBaseWidget::atlasCoverageLayer() const
 
 //QgsComposerItemWidget
 
+void QgsComposerItemWidget::updateVariables()
+{
+  QgsExpressionContext* context = mItem->createExpressionContext();
+  mVariableEditor->setContext( context );
+  int editableIndex = context->indexOfScope( tr( "Composer Item" ) );
+  if ( editableIndex >= 0 )
+    mVariableEditor->setEditableScopeIndex( editableIndex );
+  delete context;
+}
+
 QgsComposerItemWidget::QgsComposerItemWidget( QWidget* parent, QgsComposerItem* item )
     : QgsComposerItemBaseWidget( parent, item )
     , mItem( item )
@@ -141,12 +152,17 @@ QgsComposerItemWidget::QgsComposerItemWidget( QWidget* parent, QgsComposerItem*
 
   connect( mTransparencySlider, SIGNAL( valueChanged( int ) ), mTransparencySpnBx, SLOT( setValue( int ) ) );
 
-  QgsExpressionContext* context = mItem->createExpressionContext();
-  mVariableEditor->setContext( context );
-  mVariableEditor->setEditableScopeIndex( context->scopeCount() - 1 );
-  delete context;
-
+  updateVariables();
   connect( mVariableEditor, SIGNAL( scopeChanged() ), this, SLOT( variablesChanged() ) );
+  // listen out for variable edits
+  QgsApplication* app = qobject_cast<QgsApplication*>( QgsApplication::instance() );
+  if ( app )
+  {
+    connect( app, SIGNAL( settingsChanged() ), this, SLOT( updateVariables() ) );
+  }
+  connect( QgsProject::instance(), SIGNAL( variablesChanged() ), this, SLOT( updateVariables() ) );
+  if ( mItem->composition() )
+    connect( mItem->composition(), SIGNAL( variablesChanged() ), this, SLOT( updateVariables() ) );
 
   //connect atlas signals to data defined buttons
   QgsAtlasComposition* atlas = atlasComposition();
diff --git a/src/app/composer/qgscomposeritemwidget.h b/src/app/composer/qgscomposeritemwidget.h
index f5e884c..10ea98b 100644
--- a/src/app/composer/qgscomposeritemwidget.h
+++ b/src/app/composer/qgscomposeritemwidget.h
@@ -142,8 +142,7 @@ class QgsComposerItemWidget: public QgsComposerItemBaseWidget, private Ui::QgsCo
   private slots:
 
     void variablesChanged();
-
-
+    void updateVariables();
 };
 
 #endif //QGSCOMPOSERITEMWIDGET_H
diff --git a/src/app/composer/qgscomposerlegendwidget.cpp b/src/app/composer/qgscomposerlegendwidget.cpp
index f3de5bb..b48f23d 100644
--- a/src/app/composer/qgscomposerlegendwidget.cpp
+++ b/src/app/composer/qgscomposerlegendwidget.cpp
@@ -134,6 +134,8 @@ void QgsComposerLegendWidget::setGuiElements()
   mCheckBoxAutoUpdate->setChecked( mLegend->autoUpdateModel() );
   refreshMapComboBox();
 
+  mCheckboxResizeContents->setChecked( mLegend->resizeToContents() );
+
   const QgsComposerMap* map = mLegend->composerMap();
   if ( map )
   {
@@ -590,6 +592,21 @@ void QgsComposerLegendWidget::on_mMapComboBox_currentIndexChanged( int index )
   }
 }
 
+void QgsComposerLegendWidget::on_mCheckboxResizeContents_toggled( bool checked )
+{
+  if ( !mLegend )
+  {
+    return;
+  }
+
+  mLegend->beginCommand( tr( "Legend resize to contents" ) );
+  mLegend->setResizeToContents( checked );
+  if ( checked )
+    mLegend->adjustBoxSize();
+  mLegend->updateItem();
+  mLegend->endCommand();
+}
+
 void QgsComposerLegendWidget::on_mRasterBorderGroupBox_toggled( bool state )
 {
   if ( !mLegend )
@@ -902,6 +919,10 @@ void QgsComposerLegendWidget::blockAllSignals( bool b )
   mRasterBorderGroupBox->blockSignals( b );
   mRasterBorderColorButton->blockSignals( b );
   mRasterBorderWidthSpinBox->blockSignals( b );
+  mWmsLegendWidthSpinBox->blockSignals( b );
+  mWmsLegendHeightSpinBox->blockSignals( b );
+  mCheckboxResizeContents->blockSignals( b );
+  mTitleSpaceBottomSpinBox->blockSignals( b );
 }
 
 void QgsComposerLegendWidget::refreshMapComboBox()
diff --git a/src/app/composer/qgscomposerlegendwidget.h b/src/app/composer/qgscomposerlegendwidget.h
index 738951e..9fba67c 100644
--- a/src/app/composer/qgscomposerlegendwidget.h
+++ b/src/app/composer/qgscomposerlegendwidget.h
@@ -68,6 +68,7 @@ class QgsComposerLegendWidget: public QgsComposerItemBaseWidget, private Ui::Qgs
     void on_mColumnSpaceSpinBox_valueChanged( double d );
     void on_mCheckBoxAutoUpdate_stateChanged( int state );
     void on_mMapComboBox_currentIndexChanged( int index );
+    void on_mCheckboxResizeContents_toggled( bool checked );
 
     void on_mRasterBorderGroupBox_toggled( bool state );
     void on_mRasterBorderWidthSpinBox_valueChanged( double d );
diff --git a/src/app/composer/qgscomposermanager.cpp b/src/app/composer/qgscomposermanager.cpp
index d074d9f..81fdf30 100644
--- a/src/app/composer/qgscomposermanager.cpp
+++ b/src/app/composer/qgscomposermanager.cpp
@@ -289,19 +289,8 @@ void QgsComposerManager::on_mAddButton_clicked()
     QDomDocument templateDoc;
     if ( templateDoc.setContent( &templateFile, false ) )
     {
-      // provide feedback, since composer will be hidden when loading template (much faster)
-      // (not needed for empty composer)
-      QDialog* dlg = new QgsBusyIndicatorDialog( tr( "Loading template into composer..." ) );
-      dlg->setStyleSheet( QgisApp::instance()->styleSheet() );
-      dlg->show();
-
-      newComposer->hide();
-      loadedOK = newComposer->composition()->loadFromTemplate( templateDoc, nullptr, false );
+      loadedOK = newComposer->loadFromTemplate( templateDoc, true );
       newComposer->activate();
-
-      dlg->close();
-      delete dlg;
-      dlg = nullptr;
     }
   }
 
diff --git a/src/app/composer/qgscompositionwidget.cpp b/src/app/composer/qgscompositionwidget.cpp
index c6716f0..ed92569 100644
--- a/src/app/composer/qgscompositionwidget.cpp
+++ b/src/app/composer/qgscompositionwidget.cpp
@@ -23,6 +23,7 @@
 #include "qgssymbolv2selectordialog.h"
 #include "qgssymbollayerv2utils.h"
 #include "qgsexpressioncontext.h"
+#include "qgsproject.h"
 #include <QColorDialog>
 #include <QWidget>
 #include <QPrinter> //for screen resolution
@@ -45,13 +46,15 @@ QgsCompositionWidget::QgsCompositionWidget( QWidget* parent, QgsComposition* c )
   //read with/height from composition and find suitable entries to display
   displayCompositionWidthHeight();
 
-  mVariableEditor->context()->appendScope( QgsExpressionContextUtils::globalScope() );
-  mVariableEditor->context()->appendScope( QgsExpressionContextUtils::projectScope() );
-  mVariableEditor->context()->appendScope( QgsExpressionContextUtils::compositionScope( mComposition ) );
-  mVariableEditor->reloadContext();
-  mVariableEditor->setEditableScopeIndex( 2 );
-
+  updateVariables();
   connect( mVariableEditor, SIGNAL( scopeChanged() ), this, SLOT( variablesChanged() ) );
+  // listen out for variable edits
+  QgsApplication* app = qobject_cast<QgsApplication*>( QgsApplication::instance() );
+  if ( app )
+  {
+    connect( app, SIGNAL( settingsChanged() ), this, SLOT( updateVariables() ) );
+  }
+  connect( QgsProject::instance(), SIGNAL( variablesChanged() ), this, SLOT( updateVariables() ) );
 
   if ( mComposition )
   {
@@ -221,6 +224,16 @@ void QgsCompositionWidget::resizeMarginsChanged()
       mLeftMarginSpinBox->value() );
 }
 
+void QgsCompositionWidget::updateVariables()
+{
+  QgsExpressionContext context;
+  context << QgsExpressionContextUtils::globalScope()
+  << QgsExpressionContextUtils::projectScope()
+  << QgsExpressionContextUtils::compositionScope( mComposition );
+  mVariableEditor->setContext( &context );
+  mVariableEditor->setEditableScopeIndex( 2 );
+}
+
 void QgsCompositionWidget::setDataDefinedProperty( const QgsDataDefinedButton* ddBtn, QgsComposerObject::DataDefinedProperty property )
 {
   if ( !mComposition )
diff --git a/src/app/composer/qgscompositionwidget.h b/src/app/composer/qgscompositionwidget.h
index 5283155..56ab79f 100644
--- a/src/app/composer/qgscompositionwidget.h
+++ b/src/app/composer/qgscompositionwidget.h
@@ -88,6 +88,8 @@ class QgsCompositionWidget: public QWidget, private Ui::QgsCompositionWidgetBase
 
     void resizeMarginsChanged();
 
+    void updateVariables();
+
   private:
     QgsComposition* mComposition;
     QMap<QString, QgsCompositionPaper> mPaperMap;
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index 8b71c47..cc174fa 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -6137,8 +6137,7 @@ QgsComposer* QgisApp::duplicateComposer( QgsComposer* currentComposer, QString t
 
   // hiding composer until template is loaded is much faster, provide feedback to user
   newComposer->hide();
-  QApplication::setOverrideCursor( Qt::BusyCursor );
-  if ( !newComposer->composition()->loadFromTemplate( currentDoc, nullptr, false ) )
+  if ( !newComposer->loadFromTemplate( currentDoc, true ) )
   {
     deleteComposer( newComposer );
     newComposer = nullptr;
@@ -6146,7 +6145,6 @@ QgsComposer* QgisApp::duplicateComposer( QgsComposer* currentComposer, QString t
     return newComposer;
   }
   newComposer->activate();
-  QApplication::restoreOverrideCursor();
 
   return newComposer;
 }
@@ -10880,8 +10878,6 @@ void QgisApp::namSetup()
 {
   QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance();
 
-  namUpdate();
-
   connect( nam, SIGNAL( authenticationRequired( QNetworkReply *, QAuthenticator * ) ),
            this, SLOT( namAuthenticationRequired( QNetworkReply *, QAuthenticator * ) ) );
 
diff --git a/src/app/qgsannotationwidget.cpp b/src/app/qgsannotationwidget.cpp
index a1dce25..dacff37 100644
--- a/src/app/qgsannotationwidget.cpp
+++ b/src/app/qgsannotationwidget.cpp
@@ -54,6 +54,8 @@ QgsAnnotationWidget::QgsAnnotationWidget( QgsAnnotationItem* item, QWidget * par
     mBackgroundColorButton->setNoColorString( tr( "Transparent" ) );
     mBackgroundColorButton->setShowNoColor( true );
 
+    connect( mBackgroundColorButton, SIGNAL( colorChanged( QColor ) ), this, SIGNAL( backgroundColorChanged( QColor ) ) );
+
     const QgsMarkerSymbolV2* symbol = mItem->markerSymbol();
     if ( symbol )
     {
@@ -112,16 +114,6 @@ void QgsAnnotationWidget::on_mMapMarkerButton_clicked()
   }
 }
 
-void QgsAnnotationWidget::on_mFrameColorButton_colorChanged( const QColor &color )
-{
-  if ( !mItem )
-  {
-    return;
-  }
-
-  mItem->setFrameColor( color );
-}
-
 void QgsAnnotationWidget::updateCenterIcon()
 {
   if ( !mMarkerSymbol )
@@ -132,13 +124,3 @@ void QgsAnnotationWidget::updateCenterIcon()
   mMapMarkerButton->setIcon( icon );
 }
 
-void QgsAnnotationWidget::on_mBackgroundColorButton_colorChanged( const QColor &color )
-{
-  if ( !mItem )
-  {
-    return;
-  }
-
-  mItem->setFrameBackgroundColor( color );
-}
-
diff --git a/src/app/qgsannotationwidget.h b/src/app/qgsannotationwidget.h
index 1c0855d..49d530f 100644
--- a/src/app/qgsannotationwidget.h
+++ b/src/app/qgsannotationwidget.h
@@ -34,10 +34,13 @@ class APP_EXPORT QgsAnnotationWidget: public QWidget, private Ui::QgsAnnotationW
 
     void apply();
 
+  signals:
+
+    //! Emitted when the background color of the annotation is changed
+    void backgroundColorChanged( const QColor& color );
+
   private slots:
     void on_mMapMarkerButton_clicked();
-    void on_mFrameColorButton_colorChanged( const QColor &color );
-    void on_mBackgroundColorButton_colorChanged( const QColor &color );
 
   private:
     QgsAnnotationItem* mItem;
diff --git a/src/app/qgsattributetabledialog.cpp b/src/app/qgsattributetabledialog.cpp
index fd69f58..8a1684b 100644
--- a/src/app/qgsattributetabledialog.cpp
+++ b/src/app/qgsattributetabledialog.cpp
@@ -156,7 +156,6 @@ QgsAttributeTableDialog::QgsAttributeTableDialog( QgsVectorLayer *theLayer, QWid
   if ( myDockFlag )
   {
     mDock = new QgsAttributeTableDock( tr( "%1 (%n Feature(s))", "feature count", mMainView->featureCount() ).arg( mLayer->name() ), QgisApp::instance() );
-    mDock->setAllowedAreas( Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea );
     mDock->setWidget( this );
     connect( this, SIGNAL( destroyed() ), mDock, SLOT( close() ) );
     QgisApp::instance()->addDockWidget( Qt::BottomDockWidgetArea, mDock );
diff --git a/src/app/qgsformannotationdialog.cpp b/src/app/qgsformannotationdialog.cpp
index bc14b4c..04c99e7 100644
--- a/src/app/qgsformannotationdialog.cpp
+++ b/src/app/qgsformannotationdialog.cpp
@@ -24,7 +24,6 @@ QgsFormAnnotationDialog::QgsFormAnnotationDialog( QgsFormAnnotationItem* item, Q
 {
   setupUi( this );
   mEmbeddedWidget = new QgsAnnotationWidget( mItem );
-  mEmbeddedWidget->show();
   mStackedWidget->addWidget( mEmbeddedWidget );
   mStackedWidget->setCurrentWidget( mEmbeddedWidget );
 
diff --git a/src/app/qgshtmlannotationdialog.cpp b/src/app/qgshtmlannotationdialog.cpp
index fbdd2f4..06168fc 100644
--- a/src/app/qgshtmlannotationdialog.cpp
+++ b/src/app/qgshtmlannotationdialog.cpp
@@ -25,7 +25,6 @@ QgsHtmlAnnotationDialog::QgsHtmlAnnotationDialog( QgsHtmlAnnotationItem* item, Q
   setupUi( this );
   setWindowTitle( tr( "HTML annotation" ) );
   mEmbeddedWidget = new QgsAnnotationWidget( mItem );
-  mEmbeddedWidget->show();
   mStackedWidget->addWidget( mEmbeddedWidget );
   mStackedWidget->setCurrentWidget( mEmbeddedWidget );
 
diff --git a/src/app/qgsoptions.cpp b/src/app/qgsoptions.cpp
index 4afba0b..6aed1bf 100644
--- a/src/app/qgsoptions.cpp
+++ b/src/app/qgsoptions.cpp
@@ -311,16 +311,12 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl ) :
   }
 
   // cache settings
-  QNetworkDiskCache *cache = qobject_cast<QNetworkDiskCache*>( QgsNetworkAccessManager::instance()->cache() );
-  if ( cache )
-  {
-    mCacheDirectory->setText( cache->cacheDirectory() );
-    mCacheSize->setMinimum( 0 );
-    mCacheSize->setMaximum( std::numeric_limits<int>::max() );
-    mCacheSize->setSingleStep( 1024 );
-    QgsDebugMsg( QString( "set cacheSize: %1" ).arg( cache->maximumCacheSize() ) );
-    mCacheSize->setValue( cache->maximumCacheSize() / 1024 );
-  }
+  mCacheDirectory->setText( mSettings->value( "cache/directory" ).toString() );
+  mCacheDirectory->setPlaceholderText( QDir( QgsApplication::qgisSettingsDirPath() ).canonicalPath() + QDir::separator() + "cache" );
+  mCacheSize->setMinimum( 0 );
+  mCacheSize->setMaximum( std::numeric_limits<int>::max() );
+  mCacheSize->setSingleStep( 1024 );
+  mCacheSize->setValue( mSettings->value( "cache/size" ).toInt() / 1024 );
 
   //wms search server
   leWmsSearch->setText( mSettings->value( "/qgis/WMSSearchUrl", "http://geopole.org/wms/search?search=%1&type=rss" ).toString() );
@@ -1090,7 +1086,11 @@ void QgsOptions::saveOptions()
   mSettings->setValue( "proxy/proxyPassword", leProxyPassword->text() );
   mSettings->setValue( "proxy/proxyType", mProxyTypeComboBox->currentText() );
 
-  mSettings->setValue( "cache/directory", mCacheDirectory->text() );
+  if ( !mCacheDirectory->text().isEmpty() )
+    mSettings->setValue( "cache/directory", mCacheDirectory->text() );
+  else
+    mSettings->remove( "cache/directory" );
+
   mSettings->setValue( "cache/size", QVariant::fromValue( mCacheSize->value()*1024L ) );
 
   //url to exclude from proxys
@@ -1418,6 +1418,12 @@ void QgsOptions::saveOptions()
   }
 
   saveDefaultDatumTransformations();
+
+  QgsApplication* app = qobject_cast<QgsApplication*>( QgsApplication::instance() );
+  if ( app )
+  {
+    app->emitSettingsChanged();
+  }
 }
 
 void QgsOptions::rejectOptions()
diff --git a/src/app/qgsprojectproperties.cpp b/src/app/qgsprojectproperties.cpp
index 044506f..c58308d 100644
--- a/src/app/qgsprojectproperties.cpp
+++ b/src/app/qgsprojectproperties.cpp
@@ -139,7 +139,7 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa
   // Properties stored in QgsProject
 
   title( QgsProject::instance()->title() );
-  projectFileName->setText( QgsProject::instance()->fileName() );
+  mProjectFileLineEdit->setText( QgsProject::instance()->fileName() );
 
   // get the manner in which the number of decimal places in the mouse
   // position display is set (manual or automatic)
@@ -1151,6 +1151,7 @@ void QgsProjectProperties::apply()
 
   //save variables
   QgsExpressionContextUtils::setProjectVariables( mVariableEditor->variablesInActiveScope() );
+  QgsProject::instance()->emitVariablesChanged();
 
   emit refresh();
 }
diff --git a/src/app/qgssvgannotationdialog.cpp b/src/app/qgssvgannotationdialog.cpp
index 93ba43c..453421f 100644
--- a/src/app/qgssvgannotationdialog.cpp
+++ b/src/app/qgssvgannotationdialog.cpp
@@ -28,7 +28,6 @@ QgsSvgAnnotationDialog::QgsSvgAnnotationDialog( QgsSvgAnnotationItem* item, QWid
   setupUi( this );
   setWindowTitle( tr( "SVG annotation" ) );
   mEmbeddedWidget = new QgsAnnotationWidget( mItem );
-  mEmbeddedWidget->show();
   mStackedWidget->addWidget( mEmbeddedWidget );
   mStackedWidget->setCurrentWidget( mEmbeddedWidget );
 
diff --git a/src/app/qgstextannotationdialog.cpp b/src/app/qgstextannotationdialog.cpp
index 1b6ed20..ef32a60 100644
--- a/src/app/qgstextannotationdialog.cpp
+++ b/src/app/qgstextannotationdialog.cpp
@@ -25,9 +25,10 @@ QgsTextAnnotationDialog::QgsTextAnnotationDialog( QgsTextAnnotationItem* item, Q
 {
   setupUi( this );
   mEmbeddedWidget = new QgsAnnotationWidget( mItem );
-  mEmbeddedWidget->show();
   mStackedWidget->addWidget( mEmbeddedWidget );
   mStackedWidget->setCurrentWidget( mEmbeddedWidget );
+  connect( mEmbeddedWidget, SIGNAL( backgroundColorChanged( QColor ) ), this, SLOT( backgroundColorChanged( QColor ) ) );
+  mTextEdit->setAttribute( Qt::WA_TranslucentBackground );
   if ( mItem )
   {
     mTextDocument = mItem->document();
@@ -57,6 +58,18 @@ QgsTextAnnotationDialog::~QgsTextAnnotationDialog()
   delete mTextDocument;
 }
 
+void QgsTextAnnotationDialog::showEvent( QShowEvent* )
+{
+  backgroundColorChanged( mItem ? mItem->frameBackgroundColor() : Qt::white );
+}
+
+void QgsTextAnnotationDialog::backgroundColorChanged( const QColor& color )
+{
+  QPalette p = mTextEdit->viewport()->palette();
+  p.setColor( QPalette::Base, color );
+  mTextEdit->viewport()->setPalette( p );
+}
+
 void QgsTextAnnotationDialog::applyTextToItem()
 {
   if ( mItem && mTextDocument )
diff --git a/src/app/qgstextannotationdialog.h b/src/app/qgstextannotationdialog.h
index 26c8807..3634f42 100644
--- a/src/app/qgstextannotationdialog.h
+++ b/src/app/qgstextannotationdialog.h
@@ -30,6 +30,10 @@ class APP_EXPORT QgsTextAnnotationDialog: public QDialog, private Ui::QgsTextAnn
     QgsTextAnnotationDialog( QgsTextAnnotationItem* item, QWidget * parent = nullptr, Qt::WindowFlags f = nullptr );
     ~QgsTextAnnotationDialog();
 
+  protected:
+
+    virtual void showEvent( QShowEvent * event ) override;
+
   private:
     QgsTextAnnotationItem* mItem;
     /** Text document (a clone of the annotation items document)*/
@@ -44,6 +48,7 @@ class APP_EXPORT QgsTextAnnotationDialog: public QDialog, private Ui::QgsTextAnn
     void on_mFontColorButton_colorChanged( const QColor& color );
     void setCurrentFontPropertiesToGui();
     void deleteItem();
+    void backgroundColorChanged( const QColor& color );
 };
 
 #endif // QGSTEXTANNOTATIONDIALOG_H
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index bbbdf8d..468d698 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -581,6 +581,7 @@ SET(QGIS_CORE_HDRS
   ../plugins/qgisplugin.h
 
   qgis.h
+  qgsannotation.h
   qgsattributeaction.h
   qgscachedfeatureiterator.h
   qgscacheindex.h
diff --git a/src/core/composer/qgsatlascomposition.cpp b/src/core/composer/qgsatlascomposition.cpp
index fbdfeea..d74580f 100644
--- a/src/core/composer/qgsatlascomposition.cpp
+++ b/src/core/composer/qgsatlascomposition.cpp
@@ -216,7 +216,10 @@ int QgsAtlasComposition::updateFeatures()
     {
       nameExpression.reset( nullptr );
     }
-    nameExpression->prepare( &expressionContext );
+    else
+    {
+      nameExpression->prepare( &expressionContext );
+    }
   }
 
   // We cannot use nextFeature() directly since the feature pointer is rewinded by the rendering process
diff --git a/src/core/composer/qgscomposerlegend.cpp b/src/core/composer/qgscomposerlegend.cpp
index d51b5aa..f14d939 100644
--- a/src/core/composer/qgscomposerlegend.cpp
+++ b/src/core/composer/qgscomposerlegend.cpp
@@ -42,11 +42,12 @@ QgsComposerLegend::QgsComposerLegend( QgsComposition* composition )
     , mFilterOutAtlas( false )
     , mFilterAskedForUpdate( false )
     , mInAtlas( false )
+    , mInitialMapScaleCalculated( false )
+    , mForceResize( false )
+    , mSizeToContents( true )
 {
   mLegendModel2 = new QgsLegendModelV2( QgsProject::instance()->layerTreeRoot() );
 
-  adjustBoxSize();
-
   connect( &mLegendModel, SIGNAL( layersChanged() ), this, SLOT( synchronizeWithModel() ) );
 
   connect( &composition->atlasComposition(), SIGNAL( renderEnded() ), this, SLOT( onAtlasEnded() ) );
@@ -67,6 +68,9 @@ QgsComposerLegend::QgsComposerLegend()
     , mFilterOutAtlas( false )
     , mFilterAskedForUpdate( false )
     , mInAtlas( false )
+    , mInitialMapScaleCalculated( false )
+    , mForceResize( false )
+    , mSizeToContents( true )
 {
 
 }
@@ -115,6 +119,35 @@ void QgsComposerLegend::paint( QPainter* painter, const QStyleOptionGraphicsItem
     ms.setOutputDpi( dpi );
     mSettings.setMapScale( ms.scale() );
   }
+  mInitialMapScaleCalculated = true;
+
+  QgsLegendRenderer legendRenderer( mLegendModel2, mSettings );
+  legendRenderer.setLegendSize( mForceResize && mSizeToContents ? QSize() : rect().size() );
+
+  //adjust box if width or height is too small
+  if ( mSizeToContents )
+  {
+    QSizeF size = legendRenderer.minimumSize();
+    if ( mForceResize )
+    {
+      mForceResize = false;
+      //set new rect, respecting position mode and data defined size/position
+      QRectF targetRect = QRectF( pos().x(), pos().y(), size.width(), size.height() );
+      setSceneRect( evalItemRect( targetRect, true ) );
+    }
+    else if ( size.height() > rect().height() || size.width() > rect().width() )
+    {
+      //need to resize box
+      QRectF targetRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
+      if ( size.height() > targetRect.height() )
+        targetRect.setHeight( size.height() );
+      if ( size.width() > rect().width() )
+        targetRect.setWidth( size.width() );
+
+      //set new rect, respecting position mode and data defined size/position
+      setSceneRect( evalItemRect( targetRect, true ) );
+    }
+  }
 
   drawBackground( painter );
   painter->save();
@@ -122,22 +155,11 @@ void QgsComposerLegend::paint( QPainter* painter, const QStyleOptionGraphicsItem
   painter->setRenderHint( QPainter::Antialiasing, true );
   painter->setPen( QPen( QColor( 0, 0, 0 ) ) );
 
-  QgsLegendRenderer legendRenderer( mLegendModel2, mSettings );
-  legendRenderer.setLegendSize( rect().size() );
-
-  //adjust box if width or height is too small
-  QSizeF size = legendRenderer.minimumSize();
-  if ( size.height() > rect().height() || size.width() > rect().width() )
+  if ( !mSizeToContents )
   {
-    //need to resize box
-    QRectF targetRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
-    if ( size.height() > targetRect.height() )
-      targetRect.setHeight( size.height() );
-    if ( size.width() > rect().width() )
-      targetRect.setWidth( size.width() );
-
-    //set new rect, respecting position mode and data defined size/position
-    setSceneRect( evalItemRect( targetRect, true ) );
+    // set a clip region to crop out parts of legend which don't fit
+    QRectF thisPaintRect = QRectF( 0, 0, rect().width(), rect().height() );
+    painter->setClipRect( thisPaintRect );
   }
 
   legendRenderer.drawLegend( painter );
@@ -170,6 +192,18 @@ QSizeF QgsComposerLegend::paintAndDetermineSize( QPainter* painter )
 
 void QgsComposerLegend::adjustBoxSize()
 {
+  if ( !mSizeToContents )
+    return;
+
+  if ( !mInitialMapScaleCalculated )
+  {
+    // this is messy - but until we have painted the item we have no knowledge of the current DPI
+    // and so cannot correctly calculate the map scale. This results in incorrect size calculations
+    // for marker symbols with size in map units, causing the legends to initially expand to huge
+    // sizes if we attempt to calculate the box size first.
+    return;
+  }
+
   QgsLegendRenderer legendRenderer( mLegendModel2, mSettings );
   QSizeF size = legendRenderer.minimumSize();
   QgsDebugMsg( QString( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) );
@@ -181,6 +215,15 @@ void QgsComposerLegend::adjustBoxSize()
   }
 }
 
+void QgsComposerLegend::setResizeToContents( bool enabled )
+{
+  mSizeToContents = enabled;
+}
+
+bool QgsComposerLegend::resizeToContents() const
+{
+  return mSizeToContents;
+}
 
 void QgsComposerLegend::setCustomLayerTree( QgsLayerTreeGroup* rootGroup )
 {
@@ -299,7 +342,9 @@ void QgsComposerLegend::synchronizeWithModel()
 void QgsComposerLegend::updateLegend()
 {
   // take layer list from map renderer (to have legend order)
+  mLegendModel.blockSignals( true );
   mLegendModel.setLayerSet( mComposition ? mComposition->mapSettings().layers() : QStringList() );
+  mLegendModel.blockSignals( false );
   adjustBoxSize();
   updateItem();
 }
@@ -342,6 +387,8 @@ bool QgsComposerLegend::writeXML( QDomElement& elem, QDomDocument & doc ) const
   composerLegendElem.setAttribute( "wrapChar", mSettings.wrapChar() );
   composerLegendElem.setAttribute( "fontColor", mSettings.fontColor().name() );
 
+  composerLegendElem.setAttribute( "resizeToContents", mSizeToContents );
+
   if ( mComposerMap )
   {
     composerLegendElem.setAttribute( "map", mComposerMap->id() );
@@ -468,6 +515,8 @@ bool QgsComposerLegend::readXML( const QDomElement& itemElem, const QDomDocument
 
   mSettings.setWrapChar( itemElem.attribute( "wrapChar" ) );
 
+  mSizeToContents = itemElem.attribute( "resizeToContents", "1" ) != "0";
+
   //composer map
   mLegendFilterByMap = itemElem.attribute( "legendFilterByMap", "0" ).toInt();
   if ( !itemElem.attribute( "map" ).isEmpty() )
@@ -663,6 +712,8 @@ void QgsComposerLegend::doUpdateFilterByMap()
   }
   else
     mLegendModel2->setLegendFilterByMap( nullptr );
+
+  mForceResize = true;
 }
 
 void QgsComposerLegend::setLegendFilterOutAtlas( bool doFilter )
diff --git a/src/core/composer/qgscomposerlegend.h b/src/core/composer/qgscomposerlegend.h
index 9a4090c..1f950b3 100644
--- a/src/core/composer/qgscomposerlegend.h
+++ b/src/core/composer/qgscomposerlegend.h
@@ -75,6 +75,20 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
     /** Sets item box to the whole content*/
     void adjustBoxSize();
 
+    /** Sets whether the legend should automatically resize to fit its contents.
+     * @param enabled set to false to disable automatic resizing. The legend frame will not
+     * be expanded to fit legend items, and items may be cropped from display.
+     * @see resizeToContents()
+     * @note added in QGIS 3.0
+     */
+    void setResizeToContents( bool enabled );
+
+    /** Returns whether the legend should automatically resize to fit its contents.
+     * @see setResizeToContents()
+     * @note added in QGIS 3.0
+     */
+    bool resizeToContents() const;
+
     /** Returns pointer to the legend model*/
     //! @deprecated in 2.6 - use modelV2()
     Q_DECL_DEPRECATED QgsLegendModel* model() {return &mLegendModel;}
@@ -293,6 +307,15 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
     void doUpdateFilterByMap();
 
     bool mInAtlas;
+
+    //! Will be false until the associated map scale and DPI have been calculated
+    bool mInitialMapScaleCalculated;
+
+    //! Will be true if the legend size should be totally reset at next paint
+    bool mForceResize;
+
+    //! Will be true if the legend should be resized automatically to fit contents
+    bool mSizeToContents;
 };
 
 #endif
diff --git a/src/core/composer/qgscomposermap.cpp b/src/core/composer/qgscomposermap.cpp
index 2eda9e0..58bfbe7 100644
--- a/src/core/composer/qgscomposermap.cpp
+++ b/src/core/composer/qgscomposermap.cpp
@@ -34,6 +34,7 @@
 #include "qgspallabeling.h"
 #include "qgsexpression.h"
 #include "qgsvisibilitypresetcollection.h"
+#include "qgsannotation.h"
 
 #include "qgslabel.h"
 #include "qgslabelattributes.h"
@@ -2346,18 +2347,19 @@ void QgsComposerMap::drawCanvasItems( QPainter* painter, const QStyleOptionGraph
   for ( int i = itemList.size() - 1; i >= 0; --i )
   {
     currentItem = itemList.at( i );
-    //don't draw mapcanvasmap (has z value -10)
-    if ( !currentItem || currentItem->data( 0 ).toString() != "AnnotationItem" )
+
+    const QgsAnnotation* annotation = dynamic_cast< const QgsAnnotation* >( currentItem );
+    if ( !annotation )
     {
       continue;
     }
-    drawCanvasItem( currentItem, painter, itemStyle );
+    drawCanvasItem( annotation, painter, itemStyle );
   }
 }
 
-void QgsComposerMap::drawCanvasItem( QGraphicsItem* item, QPainter* painter, const QStyleOptionGraphicsItem* itemStyle )
+void QgsComposerMap::drawCanvasItem( const QgsAnnotation* annotation, QPainter* painter, const QStyleOptionGraphicsItem* itemStyle )
 {
-  if ( !item || !mMapCanvas || !item->isVisible() )
+  if ( !annotation || !annotation->showItem() )
   {
     return;
   }
@@ -2365,56 +2367,52 @@ void QgsComposerMap::drawCanvasItem( QGraphicsItem* item, QPainter* painter, con
   painter->save();
   painter->setRenderHint( QPainter::Antialiasing );
 
-  //determine scale factor according to graphics view dpi
-  double scaleFactor = 1.0 / mMapCanvas->logicalDpiX() * 25.4;
+  double scaleFactor = annotation->scaleFactor();
 
   double itemX, itemY;
-  QGraphicsItem* parent = item->parentItem();
-  if ( !parent )
+  if ( annotation->mapPositionFixed() )
   {
-    QPointF mapPos = composerMapPosForItem( item );
+    QPointF mapPos = composerMapPosForItem( annotation );
     itemX = mapPos.x();
     itemY = mapPos.y();
   }
-  else //place item relative to the parent item
+  else
   {
-    QPointF itemScenePos = item->scenePos();
-    QPointF parentScenePos = parent->scenePos();
-
-    QPointF mapPos = composerMapPosForItem( parent );
-
-    itemX = mapPos.x() + ( itemScenePos.x() - parentScenePos.x() ) * scaleFactor;
-    itemY = mapPos.y() + ( itemScenePos.y() - parentScenePos.y() ) * scaleFactor;
+    itemX = annotation->relativePosition().x() * rect().width();
+    itemY = annotation->relativePosition().y() * rect().height();
   }
-  painter->translate( itemX, itemY );
 
+  painter->translate( itemX, itemY );
   painter->scale( scaleFactor, scaleFactor );
 
   //a little trick to let the item know that the paint request comes from the composer
-  item->setData( 1, "composer" );
-  item->paint( painter, itemStyle, nullptr );
-  item->setData( 1, "" );
+  const_cast< QgsAnnotation* >( annotation )->setItemData( 1, "composer" );
+  const_cast< QgsAnnotation* >( annotation )->paint( painter, itemStyle, nullptr );
+  const_cast< QgsAnnotation* >( annotation )->setItemData( 1, "" );
+
   painter->restore();
 }
 
-QPointF QgsComposerMap::composerMapPosForItem( const QGraphicsItem* item ) const
+QPointF QgsComposerMap::composerMapPosForItem( const QgsAnnotation* annotation ) const
 {
-  if ( !item || !mMapCanvas )
-  {
+  if ( !annotation )
     return QPointF( 0, 0 );
-  }
 
-  if ( currentMapExtent()->height() <= 0 || currentMapExtent()->width() <= 0 || mMapCanvas->width() <= 0 || mMapCanvas->height() <= 0 )
+  double mapX = 0.0;
+  double mapY = 0.0;
+
+  mapX = annotation->mapPosition().x();
+  mapY = annotation->mapPosition().y();
+  QgsCoordinateReferenceSystem crs = annotation->mapPositionCrs();
+
+  if ( crs != mComposition->mapSettings().destinationCrs() )
   {
-    return QPointF( 0, 0 );
+    //need to reproject
+    QgsCoordinateTransform t( crs, mComposition->mapSettings().destinationCrs() );
+    double z = 0.0;
+    t.transformInPlace( mapX, mapY, z );
   }
 
-  QRectF graphicsSceneRect = mMapCanvas->sceneRect();
-  QPointF itemScenePos = item->scenePos();
-  QgsRectangle mapRendererExtent = mComposition->mapSettings().visibleExtent();
-
-  double mapX = itemScenePos.x() / graphicsSceneRect.width() * mapRendererExtent.width() + mapRendererExtent.xMinimum();
-  double mapY = mapRendererExtent.yMaximum() - itemScenePos.y() / graphicsSceneRect.height() * mapRendererExtent.height();
   return mapToItemCoords( QPointF( mapX, mapY ) );
 }
 
diff --git a/src/core/composer/qgscomposermap.h b/src/core/composer/qgscomposermap.h
index 2c2aeda..7eef869 100644
--- a/src/core/composer/qgscomposermap.h
+++ b/src/core/composer/qgscomposermap.h
@@ -37,6 +37,7 @@ class QPainter;
 class QgsFillSymbolV2;
 class QgsLineSymbolV2;
 class QgsVectorLayer;
+class QgsAnnotation;
 
 /** \ingroup MapComposer
  *  \class QgsComposerMap
@@ -943,8 +944,8 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
     void transformShift( double& xShift, double& yShift ) const;
 
     void drawCanvasItems( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle );
-    void drawCanvasItem( QGraphicsItem* item, QPainter* painter, const QStyleOptionGraphicsItem* itemStyle );
-    QPointF composerMapPosForItem( const QGraphicsItem* item ) const;
+    void drawCanvasItem( const QgsAnnotation* item, QPainter* painter, const QStyleOptionGraphicsItem* itemStyle );
+    QPointF composerMapPosForItem( const QgsAnnotation* item ) const;
 
     enum PartType
     {
diff --git a/src/core/composer/qgscomposition.cpp b/src/core/composer/qgscomposition.cpp
index 1f7f83a..b4722ca 100644
--- a/src/core/composer/qgscomposition.cpp
+++ b/src/core/composer/qgscomposition.cpp
@@ -1057,6 +1057,8 @@ bool QgsComposition::readXML( const QDomElement& compositionElem, const QDomDocu
 
   updateBounds();
 
+  emit variablesChanged();
+
   return true;
 }
 
@@ -3287,6 +3289,9 @@ void QgsComposition::setDataDefinedProperty( const QgsComposerObject::DataDefine
 void QgsComposition::setCustomProperty( const QString& key, const QVariant& value )
 {
   mCustomProperties.setValue( key, value );
+
+  if ( key.startsWith( "variable" ) )
+    emit variablesChanged();
 }
 
 QVariant QgsComposition::customProperty( const QString& key, const QVariant& defaultValue ) const
diff --git a/src/core/composer/qgscomposition.h b/src/core/composer/qgscomposition.h
index 9f30742..c95e61a 100644
--- a/src/core/composer/qgscomposition.h
+++ b/src/core/composer/qgscomposition.h
@@ -1088,6 +1088,11 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
     /** Is emitted when the composition has an updated status bar message for the composer window*/
     void statusMsgChanged( const QString& message );
 
+    /** Emitted whenever the expression variables stored in the composition have been changed.
+     * @note added in QGIS 3.0
+     */
+    void variablesChanged();
+
     friend class QgsComposerObject; //for accessing dataDefinedEvaluate, readDataDefinedPropertyMap and writeDataDefinedPropertyMap
     friend class QgsComposerModel; //for accessing updateZValues (should not be public)
 };
diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp
index ae3a176..cb4dcc6 100644
--- a/src/core/geometry/qgsgeometry.cpp
+++ b/src/core/geometry/qgsgeometry.cpp
@@ -1596,7 +1596,7 @@ QgsGeometry *QgsGeometry::unaryUnion( const QList<QgsGeometry*> &geometryList )
   QList<QgsGeometry*>::const_iterator it = geometryList.constBegin();
   for ( ; it != geometryList.constEnd(); ++it )
   {
-    if ( *it )
+    if ( *it && !(( *it )->isEmpty() ) )
     {
       geomV2List.append(( *it )->geometry() );
     }
diff --git a/src/core/pal/feature.cpp b/src/core/pal/feature.cpp
index 080a6a6..b2cdcaa 100644
--- a/src/core/pal/feature.cpp
+++ b/src/core/pal/feature.cpp
@@ -879,6 +879,13 @@ LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, d
       delete slp;
       return nullptr;
     }
+    // Shift the character downwards since the draw position is specified at the baseline
+    // and we're calculating the mean line here
+    double dist = 0.9 * li->label_height / 2;
+    if ( orientation < 0 )
+      dist = -dist;
+    start_x += dist * cos( angle + M_PI_2 );
+    start_y -= dist * sin( angle + M_PI_2 );
 
     double render_angle = angle;
 
@@ -1042,11 +1049,11 @@ int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition* >& lPos,
       double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
       // displacement
       if (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) )
-        positions.append( _createCurvedCandidate( slp, angle_avg, mLF->distLabel() ) );
+        positions.append( _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 ) );
       if ( flags & FLAG_ON_LINE )
-        positions.append( _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 ) );
+        positions.append( _createCurvedCandidate( slp, angle_avg, 0 ) );
       if (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) )
-        positions.append( _createCurvedCandidate( slp, angle_avg, -li->label_height - mLF->distLabel() ) );
+        positions.append( _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() ) );
 
       // delete original candidate
       delete slp;
diff --git a/src/core/pal/layer.cpp b/src/core/pal/layer.cpp
index 4334223..604d2d1 100644
--- a/src/core/pal/layer.cpp
+++ b/src/core/pal/layer.cpp
@@ -362,7 +362,7 @@ void Layer::joinConnectedFeatures()
     QLinkedList<FeaturePart*>* parts = mConnectedHashtable.value( labelText );
 
     // go one-by-one part, try to merge
-    while ( !parts->isEmpty() )
+    while ( !parts->isEmpty() && parts->count() > 1 )
     {
       // part we'll be checking against other in this round
       FeaturePart* partCheck = parts->takeFirst();
@@ -371,24 +371,29 @@ void Layer::joinConnectedFeatures()
       if ( otherPart )
       {
         // remove partCheck from r-tree
-        double bmin[2], bmax[2];
-        partCheck->getBoundingBox( bmin, bmax );
-        mFeatureIndex->Remove( bmin, bmax, partCheck );
-        mFeatureParts.removeOne( partCheck );
+        double checkpartBMin[2], checkpartBMax[2];
+        partCheck->getBoundingBox( checkpartBMin, checkpartBMax );
 
-        mConnectedFeaturesIds.insert( partCheck->featureId(), connectedFeaturesId );
-        otherPart->getBoundingBox( bmin, bmax );
+        double otherPartBMin[2], otherPartBMax[2];
+        otherPart->getBoundingBox( otherPartBMin, otherPartBMax );
 
         // merge points from partCheck to p->item
         if ( otherPart->mergeWithFeaturePart( partCheck ) )
         {
+          // remove the parts we are joining from the index
+          mFeatureIndex->Remove( checkpartBMin, checkpartBMax, partCheck );
+          mFeatureIndex->Remove( otherPartBMin, otherPartBMax, otherPart );
+
+          // reinsert merged line to r-tree (probably not needed)
+          otherPart->getBoundingBox( otherPartBMin, otherPartBMax );
+          mFeatureIndex->Insert( otherPartBMin, otherPartBMax, otherPart );
+
+          mConnectedFeaturesIds.insert( partCheck->featureId(), connectedFeaturesId );
           mConnectedFeaturesIds.insert( otherPart->featureId(), connectedFeaturesId );
-          // reinsert p->item to r-tree (probably not needed)
-          mFeatureIndex->Remove( bmin, bmax, otherPart );
-          otherPart->getBoundingBox( bmin, bmax );
-          mFeatureIndex->Insert( bmin, bmax, otherPart );
+
+          mFeatureParts.removeOne( partCheck );
+          delete partCheck;
         }
-        delete partCheck;
       }
     }
 
diff --git a/src/core/qgsannotation.h b/src/core/qgsannotation.h
new file mode 100644
index 0000000..5ed3e5d
--- /dev/null
+++ b/src/core/qgsannotation.h
@@ -0,0 +1,90 @@
+/***************************************************************************
+                             qgsannotation.h
+                             ---------------
+    begin                : July 2016
+    copyright            : (C) 2016 by Nyall Dawson
+    email                : nyall dot dawson at gmail dot com
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef QGSANNOTATION_H
+#define QGSANNOTATION_H
+
+#include "qgspoint.h"
+#include "qgscoordinatereferencesystem.h"
+
+class QPainter;
+class QStyleOptionGraphicsItem;
+
+/** \ingroup core
+ * \class QgsAnnotation
+ * \note added in QGIS 3.0
+ *
+ * \brief An interface for annotation items which are drawn over a map.
+ *
+ * QgsAnnotation is an interface class for map annotation items. These annotations can be
+ * drawn within a map, and have either a fixed map position (retrieved using mapPosition())
+ * or are placed relative to the map's frame (retrieved using relativePosition()).
+ * Annotations with a fixed map position also have a corresponding
+ * QgsCoordinateReferenceSystem, which can be determined by calling mapPositionCrs().
+ */
+
+class CORE_EXPORT QgsAnnotation
+{
+  public:
+
+    //! Returns true if the annotation should be shown.
+    virtual bool showItem() const = 0;
+
+    /** Returns true if the annotation is attached to a fixed map position, or
+     * false if the annotation uses a position relative to the current map
+     * extent.
+     * @see mapPosition()
+     * @see relativePositon()
+     */
+    //TODO QGIS 3 - rename to hasFixedMapPosition()
+    virtual bool mapPositionFixed() const = 0;
+
+    /** Returns the map position of the annotation, if it is attached to a fixed map
+     * position.
+     * @see mapPositionFixed()
+     * @see mapPositionCrs()
+     */
+    virtual QgsPoint mapPosition() const { return QgsPoint(); }
+
+    /** Returns the CRS of the map position, or an invalid CRS if the annotation does
+     * not have a fixed map position.
+    */
+    virtual QgsCoordinateReferenceSystem mapPositionCrs() const { return QgsCoordinateReferenceSystem(); }
+
+    /** Returns the relative position of the annotation, if it is not attached to a fixed map
+     * position. The coordinates in the return point should be between 0 and 1, and represent
+     * the relative percentage for the position compared to the map width and height.
+     * @see mapPositionFixed()
+     */
+    virtual QPointF relativePosition() const { return QPointF(); }
+
+    /** Returns a scaling factor which should be applied to painters before rendering
+     * the item.
+     */
+    virtual double scaleFactor() const = 0;
+
+    //! deprecated - do not use
+    // TODO QGIS 3.0 - remove
+    virtual void setItemData( int role, const QVariant& value ) = 0;
+
+    //! Paint the annotation to a destination painter
+    virtual void paint( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ) = 0;
+
+
+};
+
+#endif // QGSANNOTATION_H
diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp
index a1ca088..c9f0d45 100644
--- a/src/core/qgsapplication.cpp
+++ b/src/core/qgsapplication.cpp
@@ -1361,3 +1361,8 @@ void QgsApplication::setMaxThreads( int maxThreads )
   QgsDebugMsg( QString( "set QThreadPool max thread count to %1" ).arg( QThreadPool::globalInstance()->maxThreadCount() ) );
 }
 
+void QgsApplication::emitSettingsChanged()
+{
+  emit settingsChanged();
+}
+
diff --git a/src/core/qgsapplication.h b/src/core/qgsapplication.h
index 5b1bdf4..2011cc5 100644
--- a/src/core/qgsapplication.h
+++ b/src/core/qgsapplication.h
@@ -363,10 +363,26 @@ class CORE_EXPORT QgsApplication : public QApplication
     }
 #endif
 
+  public slots:
+
+    /** Causes the application instance to emit the settingsChanged() signal. This should
+     * be called whenever global, application-wide settings are altered to advise listeners
+     * that they may need to update their state.
+     * @see settingsChanged()
+     * @note added in QGIS 3.0
+     */
+    void emitSettingsChanged();
+
   signals:
     //! @note not available in python bindings
     void preNotify( QObject * receiver, QEvent * event, bool * done );
 
+    /** Emitted whenever any global, application-wide settings are changed.
+     * @note added in QGIS 3.0
+     * @see emitSettingsChanged()
+     */
+    void settingsChanged();
+
   private:
     static void copyPath( const QString& src, const QString& dst );
     static QObject* ABISYM( mFileOpenEventReceiver );
diff --git a/src/core/qgsexpressioncontext.cpp b/src/core/qgsexpressioncontext.cpp
index 1e1827a..1697911 100644
--- a/src/core/qgsexpressioncontext.cpp
+++ b/src/core/qgsexpressioncontext.cpp
@@ -307,6 +307,19 @@ int QgsExpressionContext::indexOfScope( QgsExpressionContextScope* scope ) const
   return mStack.indexOf( scope );
 }
 
+int QgsExpressionContext::indexOfScope( const QString& scopeName ) const
+{
+  int index = 0;
+  Q_FOREACH ( const QgsExpressionContextScope* scope, mStack )
+  {
+    if ( scope->name() == scopeName )
+      return index;
+
+    index++;
+  }
+  return -1;
+}
+
 QStringList QgsExpressionContext::variableNames() const
 {
   QStringList names;
diff --git a/src/core/qgsexpressioncontext.h b/src/core/qgsexpressioncontext.h
index 64b2b9d..6d511e2 100644
--- a/src/core/qgsexpressioncontext.h
+++ b/src/core/qgsexpressioncontext.h
@@ -324,6 +324,13 @@ class CORE_EXPORT QgsExpressionContext
      */
     int indexOfScope( QgsExpressionContextScope* scope ) const;
 
+    /** Returns the index of the first scope with a matching name within the context.
+     * @param scopeName name of scope to find
+     * @returns index of scope, or -1 if scope was not found within the context.
+     * @note added in QGIS 3.0
+     */
+    int indexOfScope( const QString& scopeName ) const;
+
     /** Returns a list of variables names set by all scopes in the context.
      * @returns list of unique variable names
      * @see filteredVariableNames
diff --git a/src/core/qgsmaplayerregistry.cpp b/src/core/qgsmaplayerregistry.cpp
index f13a913..f596668 100644
--- a/src/core/qgsmaplayerregistry.cpp
+++ b/src/core/qgsmaplayerregistry.cpp
@@ -73,12 +73,11 @@ QList<QgsMapLayer *> QgsMapLayerRegistry::addMapLayers(
   bool takeOwnership )
 {
   QList<QgsMapLayer *> myResultList;
-  for ( int i = 0; i < theMapLayers.size(); ++i )
+  Q_FOREACH ( QgsMapLayer* myLayer, theMapLayers )
   {
-    QgsMapLayer * myLayer = theMapLayers.at( i );
     if ( !myLayer || !myLayer->isValid() )
     {
-      QgsDebugMsg( "cannot add invalid layers" );
+      QgsDebugMsg( "Cannot add invalid layers" );
       continue;
     }
     //check the layer is not already registered!
@@ -87,7 +86,10 @@ QList<QgsMapLayer *> QgsMapLayerRegistry::addMapLayers(
       mMapLayers[myLayer->id()] = myLayer;
       myResultList << mMapLayers[myLayer->id()];
       if ( takeOwnership )
-        mOwnedLayers << myLayer;
+      {
+        myLayer->setParent( this );
+      }
+      connect( myLayer, SIGNAL( destroyed( QObject* ) ), this, SLOT( onMapLayerDeleted( QObject* ) ) );
       emit layerWasAdded( myLayer );
     }
   }
@@ -151,12 +153,11 @@ void QgsMapLayerRegistry::removeMapLayers( const QList<QgsMapLayer*>& layers )
     QString myId( lyr->id() );
     emit layerWillBeRemoved( myId );
     emit layerWillBeRemoved( lyr );
-    if ( mOwnedLayers.contains( lyr ) )
+    mMapLayers.remove( myId );
+    if ( lyr->parent() == this )
     {
       delete lyr;
-      mOwnedLayers.remove( lyr );
     }
-    mMapLayers.remove( myId );
     emit layerRemoved( myId );
   }
 
@@ -185,14 +186,20 @@ void QgsMapLayerRegistry::removeAllMapLayers()
 
 void QgsMapLayerRegistry::reloadAllLayers()
 {
-  QMap<QString, QgsMapLayer *>::iterator it;
-  for ( it = mMapLayers.begin(); it != mMapLayers.end() ; ++it )
+  Q_FOREACH ( QgsMapLayer* layer, mMapLayers )
   {
-    QgsMapLayer* layer = it.value();
-    if ( layer )
-    {
-      layer->reload();
-    }
+    layer->reload();
+  }
+}
+
+void QgsMapLayerRegistry::onMapLayerDeleted( QObject* obj )
+{
+  QString id = mMapLayers.key( static_cast<QgsMapLayer*>( obj ) );
+
+  if ( !id.isNull() )
+  {
+    QgsDebugMsg( QString( "Map layer deleted without unregistering! %1" ).arg( id ) );
+    mMapLayers.remove( id );
   }
 }
 
diff --git a/src/core/qgsmaplayerregistry.h b/src/core/qgsmaplayerregistry.h
index f8e3f21..7b16f7f 100644
--- a/src/core/qgsmaplayerregistry.h
+++ b/src/core/qgsmaplayerregistry.h
@@ -283,12 +283,14 @@ class CORE_EXPORT QgsMapLayerRegistry : public QObject
     void connectNotify( const char * signal ) override;
 #endif
 
+  private slots:
+    void onMapLayerDeleted( QObject* obj );
+
   private:
     //! private singleton constructor
     QgsMapLayerRegistry( QObject * parent = nullptr );
 
     QMap<QString, QgsMapLayer*> mMapLayers;
-    QSet<QgsMapLayer*> mOwnedLayers;
 }; // class QgsMapLayerRegistry
 
 #endif //QgsMapLayerRegistry_H
diff --git a/src/core/qgsnetworkaccessmanager.cpp b/src/core/qgsnetworkaccessmanager.cpp
index 2111cde..aa9aba8 100644
--- a/src/core/qgsnetworkaccessmanager.cpp
+++ b/src/core/qgsnetworkaccessmanager.cpp
@@ -372,10 +372,10 @@ void QgsNetworkAccessManager::setupDefaultProxyAndCache()
   if ( !newcache )
     newcache = new QgsNetworkDiskCache( this );
 
-  QString cacheDirectory = settings.value( "cache/directory", QgsApplication::qgisSettingsDirPath() + "cache" ).toString();
+  QString cacheDirectory = settings.value( "cache/directory" ).toString();
+  if ( cacheDirectory.isEmpty() )
+    cacheDirectory = QgsApplication::qgisSettingsDirPath() + "cache";
   qint64 cacheSize = settings.value( "cache/size", 50 * 1024 * 1024 ).toULongLong();
-  QgsDebugMsg( QString( "setCacheDirectory: %1" ).arg( cacheDirectory ) );
-  QgsDebugMsg( QString( "setMaximumCacheSize: %1" ).arg( cacheSize ) );
   newcache->setCacheDirectory( cacheDirectory );
   newcache->setMaximumCacheSize( cacheSize );
   QgsDebugMsg( QString( "cacheDirectory: %1" ).arg( newcache->cacheDirectory() ) );
diff --git a/src/core/qgsnetworkaccessmanager.h b/src/core/qgsnetworkaccessmanager.h
index e388e4a..b169f19 100644
--- a/src/core/qgsnetworkaccessmanager.h
+++ b/src/core/qgsnetworkaccessmanager.h
@@ -83,7 +83,7 @@ class CORE_EXPORT QgsNetworkAccessManager : public QNetworkAccessManager
     void setupDefaultProxyAndCache();
 
     //! return whether the system proxy should be used
-    bool useSystemProxy() { return mUseSystemProxy; }
+    bool useSystemProxy() const { return mUseSystemProxy; }
 
   public slots:
     /** Send GET request, calls get().
diff --git a/src/core/qgsofflineediting.cpp b/src/core/qgsofflineediting.cpp
index cfb6015..dc21f4c 100644
--- a/src/core/qgsofflineediting.cpp
+++ b/src/core/qgsofflineediting.cpp
@@ -134,15 +134,18 @@ bool QgsOfflineEditing::convertToOfflineProject( const QString& offlineDataPath,
 
         QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerIds.at( i ) );
         QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( layer );
-        QString origLayerId = vl->id();
-        QgsVectorLayer* newLayer = copyVectorLayer( vl, db, dbPath );
-
-        if ( newLayer )
+        if ( vl )
         {
-          layerIdMapping.insert( origLayerId, newLayer );
-          // remove remote layer
-          QgsMapLayerRegistry::instance()->removeMapLayers(
-            QStringList() << origLayerId );
+          QString origLayerId = vl->id();
+          QgsVectorLayer* newLayer = copyVectorLayer( vl, db, dbPath );
+
+          if ( newLayer )
+          {
+            layerIdMapping.insert( origLayerId, newLayer );
+            // remove remote layer
+            QgsMapLayerRegistry::instance()->removeMapLayers(
+              QStringList() << origLayerId );
+          }
         }
       }
 
@@ -181,7 +184,7 @@ bool QgsOfflineEditing::convertToOfflineProject( const QString& offlineDataPath,
       projectTitle += " (offline)";
       QgsProject::instance()->setTitle( projectTitle );
 
-      QgsProject::instance()->writeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH, dbPath );
+      QgsProject::instance()->writeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH, QgsProject::instance()->writePath( dbPath ) );
 
       return true;
     }
@@ -656,11 +659,11 @@ QgsVectorLayer* QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlit
           // Check if the online feature has been fetched (WFS download aborted for some reason)
           if ( i < offlineFeatureIds.count() )
           {
-            addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( remoteCount - ( i + 1 ) ) );
+            addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ) );
           }
           else
           {
-            showWarning( QString( "Feature cannot be copied to the offline layer, please check if the online layer '%1' is sill accessible." ).arg( layer->name() ) );
+            showWarning( tr( "Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->name() ) );
             return nullptr;
           }
           emit progressUpdated( featureCount++ );
@@ -915,7 +918,8 @@ sqlite3* QgsOfflineEditing::openLoggingDb()
   QString dbPath = QgsProject::instance()->readEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH );
   if ( !dbPath.isEmpty() )
   {
-    int rc = sqlite3_open( dbPath.toUtf8().constData(), &db );
+    QString absoluteDbPath = QgsProject::instance()->readPath( dbPath );
+    int rc = sqlite3_open( absoluteDbPath.toUtf8().constData(), &db );
     if ( rc != SQLITE_OK )
     {
       showWarning( tr( "Could not open the spatialite logging database" ) );
diff --git a/src/core/qgspallabeling.cpp b/src/core/qgspallabeling.cpp
index d88b771..fb7c994 100644
--- a/src/core/qgspallabeling.cpp
+++ b/src/core/qgspallabeling.cpp
@@ -2498,7 +2498,8 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont
   }
 
   GEOSGeometry* geos_geom_clone;
-  if ( GEOSGeomTypeId_r( QgsGeometry::getGEOSHandler(), geos_geom ) == GEOS_POLYGON && repeatDistance > 0 && placement == Line )
+  GEOSGeomTypes geomType = ( GEOSGeomTypes ) GEOSGeomTypeId_r( QgsGeometry::getGEOSHandler(), geos_geom );
+  if (( geomType == GEOS_POLYGON || geomType == GEOS_MULTIPOLYGON ) && repeatDistance > 0 && placement == Line )
   {
     geos_geom_clone = GEOSBoundary_r( QgsGeometry::getGEOSHandler(), geos_geom );
   }
@@ -3320,7 +3321,7 @@ void QgsPalLayerSettings::parseTextStyle( QFont& labelFont,
       wordspace = wspacing;
     }
   }
-  labelFont.setWordSpacing( sizeToPixel( wordspace, context, fontunits, false, fontSizeMapUnitScale ) );
+  labelFont.setWordSpacing( scaleToPixelContext( wordspace, context, fontunits, false, fontSizeMapUnitScale ) );
 
   // data defined letter spacing?
   double letterspace = labelFont.letterSpacing();
@@ -3334,7 +3335,7 @@ void QgsPalLayerSettings::parseTextStyle( QFont& labelFont,
       letterspace = lspacing;
     }
   }
-  labelFont.setLetterSpacing( QFont::AbsoluteSpacing, sizeToPixel( letterspace, context, fontunits, false, fontSizeMapUnitScale ) );
+  labelFont.setLetterSpacing( QFont::AbsoluteSpacing, scaleToPixelContext( letterspace, context, fontunits, false, fontSizeMapUnitScale ) );
 
   // data defined font capitalization?
   QFont::Capitalization fontcaps = labelFont.capitalization();
diff --git a/src/core/qgsproject.cpp b/src/core/qgsproject.cpp
index d30d4b1..ad8133e 100644
--- a/src/core/qgsproject.cpp
+++ b/src/core/qgsproject.cpp
@@ -417,6 +417,10 @@ void QgsProject::setDirty( bool b )
   dirty( b );
 }
 
+void QgsProject::emitVariablesChanged()
+{
+  emit variablesChanged();
+}
 
 
 void QgsProject::setFileName( QString const &name )
diff --git a/src/core/qgsproject.h b/src/core/qgsproject.h
index 79c0cbd..34cbe08 100644
--- a/src/core/qgsproject.h
+++ b/src/core/qgsproject.h
@@ -391,6 +391,20 @@ class CORE_EXPORT QgsProject : public QObject
 
     void snapSettingsChanged();
 
+    /** Emitted whenever the expression variables stored in the project have been changed.
+     * @note added in QGIS 3.0
+     */
+    void variablesChanged();
+
+  public slots:
+
+    /** Causes the project to emit the variablesChanged() signal. This should
+     * be called whenever expression variables related to the project are changed.
+     * @see variablesChanged()
+     * @note added in QGIS 3.0
+     */
+    void emitVariablesChanged();
+
   private:
 
     QgsProject(); // private 'cause it's a singleton
diff --git a/src/gui/editorwidgets/qgsrelationreferencewidget.cpp b/src/gui/editorwidgets/qgsrelationreferencewidget.cpp
index b3fc72c..93b99aa 100644
--- a/src/gui/editorwidgets/qgsrelationreferencewidget.cpp
+++ b/src/gui/editorwidgets/qgsrelationreferencewidget.cpp
@@ -707,7 +707,7 @@ void QgsRelationReferenceWidget::featureIdentified( const QgsFeature& feature )
   }
   else
   {
-    mComboBox->setCurrentIndex( mComboBox->findData( feature.attribute( mReferencedFieldIdx ), QgsAttributeTableModel::FeatureIdRole ) );
+    mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
     mFeature = feature;
   }
 
diff --git a/src/gui/qgsannotationitem.cpp b/src/gui/qgsannotationitem.cpp
index d2c9462..e63ea74 100644
--- a/src/gui/qgsannotationitem.cpp
+++ b/src/gui/qgsannotationitem.cpp
@@ -54,7 +54,19 @@ void QgsAnnotationItem::setMapPosition( const QgsPoint& pos )
 {
   mMapPosition = pos;
   setPos( toCanvasCoordinates( mMapPosition ) );
-  mMapPositionCrs = mMapCanvas->mapSettings().destinationCrs();
+  setMapPositionCrs( mMapCanvas->mapSettings().destinationCrs() );
+}
+
+QPointF QgsAnnotationItem::relativePosition() const
+{
+  double x = pos().x() / mMapCanvas->width();
+  double y = pos().y() / mMapCanvas->height();
+  return QPointF( x, y );
+}
+
+double QgsAnnotationItem::scaleFactor() const
+{
+  return 1.0 / mMapCanvas->logicalDpiX() * 25.4;
 }
 
 void QgsAnnotationItem::setMapPositionCrs( const QgsCoordinateReferenceSystem& crs )
@@ -185,7 +197,7 @@ void QgsAnnotationItem::updateBalloon()
   mBalloonSegmentPoint2 = pointOnLineWithDistance( mBalloonSegmentPoint1, minEdge.p2(), 10 );
 }
 
-void QgsAnnotationItem::drawFrame( QPainter* p )
+void QgsAnnotationItem::drawFrame( QPainter* p ) const
 {
   QPen framePen( mFrameColor );
   framePen.setWidthF( mFrameBorderWidth );
@@ -219,7 +231,7 @@ void QgsAnnotationItem::setFrameSize( QSizeF size )
   updateBalloon();
 }
 
-void QgsAnnotationItem::drawMarkerSymbol( QPainter* p )
+void QgsAnnotationItem::drawMarkerSymbol( QPainter* p ) const
 {
   if ( !p )
   {
@@ -240,7 +252,7 @@ void QgsAnnotationItem::drawMarkerSymbol( QPainter* p )
   }
 }
 
-void QgsAnnotationItem::drawSelectionBoxes( QPainter* p )
+void QgsAnnotationItem::drawSelectionBoxes( QPainter* p ) const
 {
   if ( !p )
   {
@@ -262,7 +274,7 @@ void QgsAnnotationItem::drawSelectionBoxes( QPainter* p )
   p->drawRect( QRectF( mBoundingRect.left(), mBoundingRect.bottom() - handlerSize, handlerSize, handlerSize ) );
 }
 
-QLineF QgsAnnotationItem::segment( int index )
+QLineF QgsAnnotationItem::segment( int index ) const
 {
   switch ( index )
   {
@@ -407,12 +419,12 @@ void QgsAnnotationItem::_writeXML( QDomDocument& doc, QDomElement& itemElem ) co
     mMapPositionCrs.writeXML( annotationElem, doc );
   annotationElem.setAttribute( "offsetX", qgsDoubleToString( mOffsetFromReferencePoint.x() ) );
   annotationElem.setAttribute( "offsetY", qgsDoubleToString( mOffsetFromReferencePoint.y() ) );
-  annotationElem.setAttribute( "frameWidth", QString::number( mFrameSize.width() ) );
-  annotationElem.setAttribute( "frameHeight", QString::number( mFrameSize.height() ) );
+  annotationElem.setAttribute( "frameWidth", qgsDoubleToString( mFrameSize.width() ) );
+  annotationElem.setAttribute( "frameHeight", qgsDoubleToString( mFrameSize.height() ) );
   QPointF canvasPos = pos();
   annotationElem.setAttribute( "canvasPosX", qgsDoubleToString( canvasPos.x() ) );
   annotationElem.setAttribute( "canvasPosY", qgsDoubleToString( canvasPos.y() ) );
-  annotationElem.setAttribute( "frameBorderWidth", QString::number( mFrameBorderWidth ) );
+  annotationElem.setAttribute( "frameBorderWidth", qgsDoubleToString( mFrameBorderWidth ) );
   annotationElem.setAttribute( "frameColor", mFrameColor.name() );
   annotationElem.setAttribute( "frameColorAlpha", mFrameColor.alpha() );
   annotationElem.setAttribute( "frameBackgroundColor", mFrameBackgroundColor.name() );
@@ -444,8 +456,12 @@ void QgsAnnotationItem::_readXML( const QDomDocument& doc, const QDomElement& an
   mapPos.setX( annotationElem.attribute( "mapPosX", "0" ).toDouble() );
   mapPos.setY( annotationElem.attribute( "mapPosY", "0" ).toDouble() );
   mMapPosition = mapPos;
+
   if ( !mMapPositionCrs.readXML( annotationElem ) )
+  {
     mMapPositionCrs = mMapCanvas->mapSettings().destinationCrs();
+  }
+
   mFrameBorderWidth = annotationElem.attribute( "frameBorderWidth", "0.5" ).toDouble();
   mFrameColor.setNamedColor( annotationElem.attribute( "frameColor", "#000000" ) );
   mFrameColor.setAlpha( annotationElem.attribute( "frameColorAlpha", "255" ).toInt() );
@@ -473,3 +489,19 @@ void QgsAnnotationItem::_readXML( const QDomDocument& doc, const QDomElement& an
   updateBoundingRect();
   updateBalloon();
 }
+
+void QgsAnnotationItem::setItemData( int role, const QVariant& value )
+{
+  setData( role, value );
+}
+
+void QgsAnnotationItem::paint( QPainter* painter, const QStyleOptionGraphicsItem*, QWidget* )
+{
+  // maintain API compatibility, if annotation item subclasses only implement the paint( QPainter* ) override
+  paint( painter );
+}
+
+void QgsAnnotationItem::paint( QPainter* painter )
+{
+  Q_UNUSED( painter );
+}
diff --git a/src/gui/qgsannotationitem.h b/src/gui/qgsannotationitem.h
index 4b187c7..e77eab8 100644
--- a/src/gui/qgsannotationitem.h
+++ b/src/gui/qgsannotationitem.h
@@ -20,6 +20,7 @@
 
 #include "qgsmapcanvasitem.h"
 #include "qgscoordinatereferencesystem.h"
+#include "qgsannotation.h"
 
 class QDomDocument;
 class QDomElement;
@@ -29,7 +30,8 @@ class QgsMarkerSymbolV2;
 
 /** An annotation item can be either placed either on screen corrdinates or on map coordinates.
   It may reference a feature and displays that associatiation with a balloon like appearance*/
-class GUI_EXPORT QgsAnnotationItem: public QgsMapCanvasItem
+
+class GUI_EXPORT QgsAnnotationItem: public QgsMapCanvasItem, public QgsAnnotation
 {
   public:
     enum MouseMoveAction
@@ -64,16 +66,22 @@ class GUI_EXPORT QgsAnnotationItem: public QgsMapCanvasItem
 
     //setters and getters
     void setMapPositionFixed( bool fixed );
-    bool mapPositionFixed() const { return mMapPositionFixed; }
+    bool mapPositionFixed() const override { return mMapPositionFixed; }
 
     virtual void setMapPosition( const QgsPoint& pos );
-    QgsPoint mapPosition() const { return mMapPosition; }
+    QgsPoint mapPosition() const override { return mMapPosition; }
+
+    virtual QPointF relativePosition() const override;
+
+    virtual double scaleFactor() const override;
+
+    virtual bool showItem() const override { return isVisible(); }
 
     /** Sets the CRS of the map position.
       @param crs the CRS to set */
     virtual void setMapPositionCrs( const QgsCoordinateReferenceSystem& crs );
     /** Returns the CRS of the map position.*/
-    QgsCoordinateReferenceSystem mapPositionCrs() const { return mMapPositionCrs; }
+    QgsCoordinateReferenceSystem mapPositionCrs() const override { return mMapPositionCrs; }
 
     void setFrameSize( QSizeF size );
     QSizeF frameSize() const { return mFrameSize; }
@@ -100,6 +108,12 @@ class GUI_EXPORT QgsAnnotationItem: public QgsMapCanvasItem
     void _writeXML( QDomDocument& doc, QDomElement& itemElem ) const;
     void _readXML( const QDomDocument& doc, const QDomElement& annotationElem );
 
+    virtual void setItemData( int role, const QVariant& value ) override;
+
+    virtual void paint( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ) override;
+
+    void paint( QPainter* painter ) override;
+
   protected:
     /** True: the item stays at the same map position, False: the item stays on same screen position*/
     bool mMapPositionFixed;
@@ -135,13 +149,19 @@ class GUI_EXPORT QgsAnnotationItem: public QgsMapCanvasItem
     /** Check where to attach the balloon connection between frame and map point*/
     void updateBalloon();
 
-    void drawFrame( QPainter* p );
-    void drawMarkerSymbol( QPainter* p );
-    void drawSelectionBoxes( QPainter* p );
+    //! Draws the annotation frame to a destination painter
+    void drawFrame( QPainter* p ) const;
+
+    //! Draws the map position marker symbol to a destination painter
+    void drawMarkerSymbol( QPainter* p ) const;
+
+    //! Draws selection handles around the item
+    void drawSelectionBoxes( QPainter* p ) const;
+
     /** Returns frame width in painter units*/
     //double scaledFrameWidth( QPainter* p) const;
     /** Gets the frame line (0 is the top line, 1 right, 2 bottom, 3 left)*/
-    QLineF segment( int index );
+    QLineF segment( int index ) const;
     /** Returns a point on the line from startPoint to directionPoint that is a certain distance away from the starting point*/
     QPointF pointOnLineWithDistance( QPointF startPoint, QPointF directionPoint, double distance ) const;
     /** Returns the symbol size scaled in (mapcanvas) pixels. Used for the counding rect calculation*/
diff --git a/src/gui/qgscredentialdialog.cpp b/src/gui/qgscredentialdialog.cpp
index 9394ec5..7887aca 100644
--- a/src/gui/qgscredentialdialog.cpp
+++ b/src/gui/qgscredentialdialog.cpp
@@ -43,6 +43,7 @@ QgsCredentialDialog::QgsCredentialDialog( QWidget *parent, const Qt::WindowFlags
            Qt::BlockingQueuedConnection );
   mOkButton = buttonBox->button( QDialogButtonBox::Ok );
   leMasterPass->setPlaceholderText( tr( "Required" ) );
+  leUsername->setFocus();
 }
 
 QgsCredentialDialog::~QgsCredentialDialog()
@@ -119,6 +120,7 @@ void QgsCredentialDialog::requestCredentialsMasterPassword( QString * password,
 {
   QgsDebugMsg( "Entering." );
   stackedWidget->setCurrentIndex( 1 );
+  leMasterPass->setFocus();
 
   QString titletxt( stored ? tr( "Enter CURRENT master authentication password" ) : tr( "Set NEW master authentication password" ) );
   lblPasswordTitle->setText( titletxt );
diff --git a/src/gui/qgsvariableeditorwidget.h b/src/gui/qgsvariableeditorwidget.h
index f28a511..85b1141 100644
--- a/src/gui/qgsvariableeditorwidget.h
+++ b/src/gui/qgsvariableeditorwidget.h
@@ -65,12 +65,6 @@ class GUI_EXPORT QgsVariableEditorWidget : public QWidget
      */
     QgsExpressionContext* context() const { return mContext.data(); }
 
-    /** Reloads all scopes from the editor's current context. This method should be called
-     * after adding or removing scopes from the attached context.
-     * @see context()
-     */
-    void reloadContext();
-
     /** Sets the editable scope for the widget. Only variables from the editable scope can
      * be modified by users.
      * @param scopeIndex index of current editable scope. Set to -1 to disable
@@ -107,6 +101,14 @@ class GUI_EXPORT QgsVariableEditorWidget : public QWidget
      */
     QgsStringMap variablesInActiveScope() const;
 
+  public slots:
+
+    /** Reloads all scopes from the editor's current context. This method should be called
+     * after adding or removing scopes from the attached context.
+     * @see context()
+     */
+    void reloadContext();
+
   signals:
 
     /** Emitted when the user has modified a scope using the widget.
diff --git a/src/plugins/heatmap/heatmap.cpp b/src/plugins/heatmap/heatmap.cpp
index d753adb..95138ec 100644
--- a/src/plugins/heatmap/heatmap.cpp
+++ b/src/plugins/heatmap/heatmap.cpp
@@ -170,7 +170,10 @@ void Heatmap::run()
   // Write the empty raster
   for ( int i = 0; i < rows ; i++ )
   {
-    poBand->RasterIO( GF_Write, 0, i, columns, 1, line, columns, 1, GDT_Float32, 0, 0 );
+    if ( poBand->RasterIO( GF_Write, 0, i, columns, 1, line, columns, 1, GDT_Float32, 0, 0 ) != CE_None )
+    {
+      QgsDebugMsg( "Raster IO Error" );
+    }
   }
 
   CPLFree( line );
@@ -299,8 +302,11 @@ void Heatmap::run()
 
       // get the data
       float *dataBuffer = ( float * ) CPLMalloc( sizeof( float ) * blockSize * blockSize );
-      poBand->RasterIO( GF_Read, xPosition, yPosition, blockSize, blockSize,
-                        dataBuffer, blockSize, blockSize, GDT_Float32, 0, 0 );
+      if ( poBand->RasterIO( GF_Read, xPosition, yPosition, blockSize, blockSize,
+                             dataBuffer, blockSize, blockSize, GDT_Float32, 0, 0 ) != CE_None )
+      {
+        QgsDebugMsg( "Raster IO Error" );
+      }
 
       for ( int xp = 0; xp <= myBuffer; xp++ )
       {
@@ -341,8 +347,11 @@ void Heatmap::run()
           }
         }
       }
-      poBand->RasterIO( GF_Write, xPosition, yPosition, blockSize, blockSize,
-                        dataBuffer, blockSize, blockSize, GDT_Float32, 0, 0 );
+      if ( poBand->RasterIO( GF_Write, xPosition, yPosition, blockSize, blockSize,
+                             dataBuffer, blockSize, blockSize, GDT_Float32, 0, 0 ) != CE_None )
+      {
+        QgsDebugMsg( "Raster IO Error" );
+      }
       CPLFree( dataBuffer );
     }
   }
diff --git a/src/providers/oracle/ocispatial/qsql_ocispatial.cpp b/src/providers/oracle/ocispatial/qsql_ocispatial.cpp
index 7059aca..c20ad29 100644
--- a/src/providers/oracle/ocispatial/qsql_ocispatial.cpp
+++ b/src/providers/oracle/ocispatial/qsql_ocispatial.cpp
@@ -658,6 +658,7 @@ int QOCISpatialResultPrivate::bindValue( OCIStmt *sql, OCIBind **hbnd, OCIError
                           ba.data(),
                           ba.capacity(),
                           SQLT_STR, indPtr, tmpSize, 0, 0, 0, OCI_DEFAULT );
+        tmpStorage.append( ba );
       }
       else
       {
@@ -669,7 +670,6 @@ int QOCISpatialResultPrivate::bindValue( OCIStmt *sql, OCIBind **hbnd, OCIError
       }
       if ( r == OCI_SUCCESS )
         setCharset( *hbnd, OCI_HTYPE_BIND );
-      tmpStorage.append( ba );
       break;
     } // default case
   } // switch
diff --git a/src/providers/oracle/qgsoracleconn.cpp b/src/providers/oracle/qgsoracleconn.cpp
index 2fa7d40..536efae 100644
--- a/src/providers/oracle/qgsoracleconn.cpp
+++ b/src/providers/oracle/qgsoracleconn.cpp
@@ -31,7 +31,7 @@ QMap<QString, QgsOracleConn *> QgsOracleConn::sConnections;
 int QgsOracleConn::snConnections = 0;
 const int QgsOracleConn::sGeomTypeSelectLimit = 100;
 
-QgsOracleConn *QgsOracleConn::connectDb( QgsDataSourceURI uri )
+QgsOracleConn *QgsOracleConn::connectDb( const QgsDataSourceURI& uri )
 {
   QString conninfo = uri.connectionInfo();
 
diff --git a/src/providers/oracle/qgsoracleconn.h b/src/providers/oracle/qgsoracleconn.h
index 45d3a9a..b0dd1a4 100644
--- a/src/providers/oracle/qgsoracleconn.h
+++ b/src/providers/oracle/qgsoracleconn.h
@@ -45,6 +45,10 @@ struct QgsOracleLayerProperty
   QStringList          pkCols;
   QString              sql;
 
+  QgsOracleLayerProperty()
+      : isView( false )
+  {}
+
   int size() const { Q_ASSERT( types.size() == srids.size() ); return types.size(); }
 
   bool operator==( const QgsOracleLayerProperty& other )
@@ -107,7 +111,7 @@ class QgsOracleConn : public QObject
 {
     Q_OBJECT
   public:
-    static QgsOracleConn *connectDb( QgsDataSourceURI uri );
+    static QgsOracleConn *connectDb( const QgsDataSourceURI &uri );
     void disconnect();
 
     /** Double quote a Oracle identifier for placement in a SQL string.
diff --git a/src/providers/oracle/qgsoracleconnpool.h b/src/providers/oracle/qgsoracleconnpool.h
index 2c5004d..108a52b 100644
--- a/src/providers/oracle/qgsoracleconnpool.h
+++ b/src/providers/oracle/qgsoracleconnpool.h
@@ -25,7 +25,7 @@ inline QString qgsConnectionPool_ConnectionToName( QgsOracleConn* c )
   return c->connInfo();
 }
 
-inline void qgsConnectionPool_ConnectionCreate( QgsDataSourceURI uri, QgsOracleConn*& c )
+inline void qgsConnectionPool_ConnectionCreate( const QgsDataSourceURI& uri, QgsOracleConn*& c )
 {
   c = QgsOracleConn::connectDb( uri );
 }
diff --git a/src/providers/oracle/qgsoracleprovider.cpp b/src/providers/oracle/qgsoracleprovider.cpp
index 61e8b5e..3d03f3d 100644
--- a/src/providers/oracle/qgsoracleprovider.cpp
+++ b/src/providers/oracle/qgsoracleprovider.cpp
@@ -1772,7 +1772,6 @@ void QgsOracleProvider::appendGeomParam( const QgsGeometry *geom, QSqlQuery &qry
     g.eleminfo.clear();
     g.ordinates.clear();
 
-    QString expr;
     int iOrdinate = 1;
     QGis::WkbType type = ( QGis::WkbType ) * ptr.iPtr++;
     int dim = 2;
@@ -1781,6 +1780,8 @@ void QgsOracleProvider::appendGeomParam( const QgsGeometry *geom, QSqlQuery &qry
     {
       case QGis::WKBPoint25D:
         dim = 3;
+        FALLTHROUGH;
+
       case QGis::WKBPoint:
         g.srid  = mSrid;
         g.gtype = SDO_GTYPE( dim, gtPoint );
@@ -1792,6 +1793,8 @@ void QgsOracleProvider::appendGeomParam( const QgsGeometry *geom, QSqlQuery &qry
       case QGis::WKBLineString25D:
       case QGis::WKBMultiLineString25D:
         dim = 3;
+        FALLTHROUGH;
+
       case QGis::WKBLineString:
       case QGis::WKBMultiLineString:
       {
@@ -1827,6 +1830,8 @@ void QgsOracleProvider::appendGeomParam( const QgsGeometry *geom, QSqlQuery &qry
       case QGis::WKBPolygon25D:
       case QGis::WKBMultiPolygon25D:
         dim = 3;
+        FALLTHROUGH;
+
       case QGis::WKBPolygon:
       case QGis::WKBMultiPolygon:
       {
@@ -1866,6 +1871,8 @@ void QgsOracleProvider::appendGeomParam( const QgsGeometry *geom, QSqlQuery &qry
 
       case QGis::WKBMultiPoint25D:
         dim = 3;
+        FALLTHROUGH;
+
       case QGis::WKBMultiPoint:
       {
         g.gtype = SDO_GTYPE( dim, gtMultiPoint );
@@ -2734,7 +2741,7 @@ QgsVectorLayerImport::ImportError QgsOracleProvider::createEmptyLayer(
 
   QgsDebugMsg( QString( "layer %1 created" ).arg( ownerTableName ) );
 
-  // use the provider to edit the table
+  // use the provider to edit the table1
   dsUri.setDataSource( ownerName, tableName, geometryColumn, QString(), primaryKey );
   QgsOracleProvider *provider = new QgsOracleProvider( dsUri.uri() );
   if ( !provider->isValid() )
@@ -2749,8 +2756,7 @@ QgsVectorLayerImport::ImportError QgsOracleProvider::createEmptyLayer(
   QgsDebugMsg( "layer loaded" );
 
   // add fields to the layer
-  if ( oldToNewAttrIdxMap )
-    oldToNewAttrIdxMap->clear();
+  oldToNewAttrIdxMap->clear();
 
   if ( fields.size() > 0 )
   {
diff --git a/src/providers/oracle/qgsoracleprovider.h b/src/providers/oracle/qgsoracleprovider.h
index 89015c0..840ed93 100644
--- a/src/providers/oracle/qgsoracleprovider.h
+++ b/src/providers/oracle/qgsoracleprovider.h
@@ -400,7 +400,6 @@ class QgsOracleProvider : public QgsVectorDataProvider
     QgsFeatureId mFidCounter;                //! next feature id if map is used
     QgsOracleConn *mConnection;
 
-    bool mHasSpatial;                        //! Oracle Spatial is installed
     bool mHasSpatialIndex;                   //! Geometry column is indexed
     QString mSpatialIndexName;               //! name of spatial index of geometry column
 
diff --git a/src/providers/wcs/qgswcsprovider.cpp b/src/providers/wcs/qgswcsprovider.cpp
index cf73afc..9c78f81 100644
--- a/src/providers/wcs/qgswcsprovider.cpp
+++ b/src/providers/wcs/qgswcsprovider.cpp
@@ -587,7 +587,10 @@ void QgsWcsProvider::readBlock( int bandNo, QgsRectangle  const & viewExtent, in
         QgsDebugMsg( QString( "Couldn't allocate memory of %1 bytes" ).arg( size ) );
         return;
       }
-      GDALRasterIO( gdalBand, GF_Read, 0, 0, width, height, tmpData, width, height, ( GDALDataType ) mGdalDataType.at( bandNo - 1 ), 0, 0 );
+      if ( GDALRasterIO( gdalBand, GF_Read, 0, 0, width, height, tmpData, width, height, ( GDALDataType ) mGdalDataType.at( bandNo - 1 ), 0, 0 ) != CE_None )
+      {
+        QgsDebugMsg( "Raster IO Error" );
+      }
       for ( int i = 0; i < pixelHeight; i++ )
       {
         for ( int j = 0; j < pixelWidth; j++ )
@@ -601,13 +604,22 @@ void QgsWcsProvider::readBlock( int bandNo, QgsRectangle  const & viewExtent, in
     }
     else if ( width == pixelWidth && height == pixelHeight )
     {
-      GDALRasterIO( gdalBand, GF_Read, 0, 0, pixelWidth, pixelHeight, block, pixelWidth, pixelHeight, ( GDALDataType ) mGdalDataType.at( bandNo - 1 ), 0, 0 );
-      QgsDebugMsg( tr( "Block read OK" ) );
+      if ( GDALRasterIO( gdalBand, GF_Read, 0, 0, pixelWidth, pixelHeight, block, pixelWidth, pixelHeight, ( GDALDataType ) mGdalDataType.at( bandNo - 1 ), 0, 0 ) != CE_None )
+      {
+        QgsDebugMsg( "Raster IO Error" );
+      }
+      else
+      {
+        QgsDebugMsg( "Block read OK" );
+      }
     }
     else
     {
       // This should not happen, but it is better to give distorted result + warning
-      GDALRasterIO( gdalBand, GF_Read, 0, 0, width, height, block, pixelWidth, pixelHeight, ( GDALDataType ) mGdalDataType.at( bandNo - 1 ), 0, 0 );
+      if ( GDALRasterIO( gdalBand, GF_Read, 0, 0, width, height, block, pixelWidth, pixelHeight, ( GDALDataType ) mGdalDataType.at( bandNo - 1 ), 0, 0 ) != CE_None )
+      {
+        QgsDebugMsg( "Raster IO Error" );
+      }
       QgsMessageLog::logMessage( tr( "Received coverage has wrong size %1 x %2 (expected %3 x %4)" ).arg( width ).arg( height ).arg( pixelWidth ).arg( pixelHeight ), tr( "WCS" ) );
     }
   }
diff --git a/src/providers/wms/CMakeLists.txt b/src/providers/wms/CMakeLists.txt
index 310d566..bc19de4 100644
--- a/src/providers/wms/CMakeLists.txt
+++ b/src/providers/wms/CMakeLists.txt
@@ -41,6 +41,7 @@ INCLUDE_DIRECTORIES(SYSTEM
   ${QCA_INCLUDE_DIR}
 )
 
+ADD_LIBRARY(wmsprovider_a STATIC ${WMS_SRCS} ${WMS_MOC_SRCS})
 ADD_LIBRARY(wmsprovider MODULE ${WMS_SRCS} ${WMS_MOC_SRCS})
 
 TARGET_LINK_LIBRARIES(wmsprovider
@@ -50,6 +51,11 @@ TARGET_LINK_LIBRARIES(wmsprovider
   ${GDAL_LIBRARY}  # for OGR_G_CreateGeometryFromJson()
 )
 
+TARGET_LINK_LIBRARIES(wmsprovider_a
+  qgis_core
+  ${QT_QTSCRIPT_LIBRARY}
+)
+
 INSTALL (TARGETS wmsprovider
   RUNTIME DESTINATION ${QGIS_PLUGIN_DIR}
   LIBRARY DESTINATION ${QGIS_PLUGIN_DIR})
diff --git a/src/providers/wms/qgswmscapabilities.cpp b/src/providers/wms/qgswmscapabilities.cpp
index edc33fb..5ce19bf 100644
--- a/src/providers/wms/qgswmscapabilities.cpp
+++ b/src/providers/wms/qgswmscapabilities.cpp
@@ -905,6 +905,17 @@ void QgsWmsCapabilities::parseLayer( QDomElement const & e, QgsWmsLayerProperty&
 
         parseStyle( e1, styleProperty );
 
+        for ( int i = 0; i < layerProperty.style.size(); ++i )
+        {
+          if ( layerProperty.style.at( i ).name == styleProperty.name )
+          {
+            // override inherited parent's style if it has the same name
+            // according to the WMS spec, it should not happen, but Mapserver
+            // does it all the time.
+            layerProperty.style.remove( i );
+            break;
+          }
+        }
         layerProperty.style.push_back( styleProperty );
       }
       else if ( tagName == "MinScaleDenominator" )
diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp
index a39354c..d6d22bf 100644
--- a/src/providers/wms/qgswmsprovider.cpp
+++ b/src/providers/wms/qgswmsprovider.cpp
@@ -215,6 +215,36 @@ QString QgsWmsProvider::getTileUrl() const
   }
 }
 
+static bool isValidLegend( const QgsWmsLegendUrlProperty &l )
+{
+  return l.format.startsWith( "image/" );
+}
+
+/**
+ * Picks a usable legend URL for a given style.
+ */
+static QString pickLegend( const QgsWmsStyleProperty &s )
+{
+  QString url;
+  for ( int k = 0; k < s.legendUrl.size() && url.isEmpty(); k++ )
+  {
+    const QgsWmsLegendUrlProperty &l = s.legendUrl[k];
+    if ( isValidLegend( l ) )
+    {
+      url = l.onlineResource.xlinkHref;
+    }
+  }
+  return url;
+}
+
+static const QgsWmsStyleProperty *searchStyle( const QVector<QgsWmsStyleProperty>& styles, const QString& name )
+{
+  Q_FOREACH ( const QgsWmsStyleProperty &s, styles )
+    if ( s.name == name )
+      return &s;
+  return nullptr;
+}
+
 QString QgsWmsProvider::getLegendGraphicUrl() const
 {
   QString url;
@@ -223,25 +253,31 @@ QString QgsWmsProvider::getLegendGraphicUrl() const
   {
     const QgsWmsLayerProperty &l = mCaps.mLayersSupported[i];
 
-    if ( l.name != mSettings.mActiveSubLayers[0] )
-      continue;
-
-    for ( int j = 0; j < l.style.size() && url.isEmpty(); j++ )
+    if ( l.name == mSettings.mActiveSubLayers[0] )
     {
-      const QgsWmsStyleProperty &s = l.style[j];
-
-      if ( s.name != mSettings.mActiveSubStyles[0] )
-        continue;
-
-      for ( int k = 0; k < s.legendUrl.size() && url.isEmpty(); k++ )
+      if ( !mSettings.mActiveSubStyles[0].isEmpty() && mSettings.mActiveSubStyles[0] != "default" )
       {
-        const QgsWmsLegendUrlProperty &l = s.legendUrl[k];
-
-        if ( l.format != mSettings.mImageMimeType )
-          continue;
-
-        url = l.onlineResource.xlinkHref;
+        const QgsWmsStyleProperty *s = searchStyle( l.style, mSettings.mActiveSubStyles[0] );
+        if ( s )
+          url = pickLegend( *s );
       }
+      else
+      {
+        // QGIS wants the default style, but GetCapabilities doesn't give us a
+        // way to know what is the default style. So we look for the onlineResource
+        // only if there is a single style available or if there is a style called "default".
+        if ( l.style.size() == 1 )
+        {
+          url = pickLegend( l.style[0] );
+        }
+        else
+        {
+          const QgsWmsStyleProperty *s = searchStyle( l.style, "default" );
+          if ( s )
+            url = pickLegend( *s );
+        }
+      }
+      break;
     }
   }
 
diff --git a/src/python/qgspythonutilsimpl.cpp b/src/python/qgspythonutilsimpl.cpp
index 5ac0352..126ce33 100644
--- a/src/python/qgspythonutilsimpl.cpp
+++ b/src/python/qgspythonutilsimpl.cpp
@@ -202,13 +202,6 @@ bool QgsPythonUtilsImpl::checkQgisUser()
   return true;
 }
 
-void QgsPythonUtilsImpl::doUserImports()
-{
-
-  QString startuppath = homePythonPath() + " + \"/startup.py\"";
-  runString( "if os.path.exists(" + startuppath + "): from startup import *\n" );
-}
-
 void QgsPythonUtilsImpl::initPython( QgisInterface* interface )
 {
   init();
@@ -224,7 +217,6 @@ void QgsPythonUtilsImpl::initPython( QgisInterface* interface )
     exitPython();
     return;
   }
-  doUserImports();
   finish();
 }
 
@@ -250,7 +242,6 @@ void QgsPythonUtilsImpl::initServerPython( QgsServerInterface* interface )
   // This is the other main difference with initInterface() for desktop plugins
   runString( "qgis.utils.initServerInterface(" + QString::number(( unsigned long ) interface ) + ')' );
 
-  doUserImports();
   finish();
 }
 
diff --git a/src/python/qgspythonutilsimpl.h b/src/python/qgspythonutilsimpl.h
index f4a8e57..348689f 100644
--- a/src/python/qgspythonutilsimpl.h
+++ b/src/python/qgspythonutilsimpl.h
@@ -126,9 +126,6 @@ class QgsPythonUtilsImpl : public QgsPythonUtils
     //@return true if qgis.user could be imported
     bool checkQgisUser();
 
-    //! import user defined Python code
-    void doUserImports();
-
     //! cleanup Python context
     void finish();
 
diff --git a/src/server/qgsowsserver.h b/src/server/qgsowsserver.h
index 4c05f07..48a13e0 100644
--- a/src/server/qgsowsserver.h
+++ b/src/server/qgsowsserver.h
@@ -21,6 +21,8 @@
 #include "qgsrequesthandler.h"
 #ifdef HAVE_SERVER_PYTHON_PLUGINS
 #include "qgsaccesscontrol.h"
+#else
+class QgsMapLayer;
 #endif
 
 #include <QHash>
diff --git a/src/server/qgswmsprojectparser.cpp b/src/server/qgswmsprojectparser.cpp
index 056064a..ea47596 100644
--- a/src/server/qgswmsprojectparser.cpp
+++ b/src/server/qgswmsprojectparser.cpp
@@ -1146,6 +1146,11 @@ void QgsWMSProjectParser::addLayers( QDomDocument &doc,
         }
       }
 
+      if ( !ltGroup )
+      {
+        QgsDebugMsg( QString( "Skipping group %1, it could not be found" ).arg( name ) );
+        continue;
+      }
       QString shortName = ltGroup->customProperty( "wmsShortName" ).toString();
       QString title = ltGroup->customProperty( "wmsTitle" ).toString();
 
diff --git a/src/ui/composer/qgscomposerlegendwidgetbase.ui b/src/ui/composer/qgscomposerlegendwidgetbase.ui
index 5c8b6f9..bbf2f2c 100644
--- a/src/ui/composer/qgscomposerlegendwidgetbase.ui
+++ b/src/ui/composer/qgscomposerlegendwidgetbase.ui
@@ -130,7 +130,7 @@
           <item row="1" column="0">
            <widget class="QLabel" name="label_15">
             <property name="text">
-             <string>Title alignment:</string>
+             <string>Title alignment</string>
             </property>
            </widget>
           </item>
@@ -153,6 +153,13 @@
             </item>
            </widget>
           </item>
+          <item row="4" column="0" colspan="2">
+           <widget class="QCheckBox" name="mCheckboxResizeContents">
+            <property name="text">
+             <string>Resize to fit contents</string>
+            </property>
+           </widget>
+          </item>
          </layout>
         </widget>
        </item>
@@ -1029,6 +1036,7 @@
   <tabstop>mTitleAlignCombo</tabstop>
   <tabstop>mMapComboBox</tabstop>
   <tabstop>mWrapCharLineEdit</tabstop>
+  <tabstop>mCheckboxResizeContents</tabstop>
   <tabstop>mLegendItemColGroupBox</tabstop>
   <tabstop>mCheckBoxAutoUpdate</tabstop>
   <tabstop>mUpdateAllPushButton</tabstop>
diff --git a/src/ui/qgscredentialdialog.ui b/src/ui/qgscredentialdialog.ui
index e3ee990..5798dcc 100644
--- a/src/ui/qgscredentialdialog.ui
+++ b/src/ui/qgscredentialdialog.ui
@@ -220,6 +220,14 @@ font-style: italic;
    </item>
   </layout>
  </widget>
+ <tabstops>
+  <tabstop>leUsername</tabstop>
+  <tabstop>lePassword</tabstop>
+  <tabstop>leMasterPass</tabstop>
+  <tabstop>leMasterPassVerify</tabstop>
+  <tabstop>chkMasterPassShow</tabstop>
+  <tabstop>chkbxEraseAuthDb</tabstop>
+ </tabstops>
  <resources/>
  <connections>
   <connection>
diff --git a/src/ui/qgsoptionsbase.ui b/src/ui/qgsoptionsbase.ui
index 6dcbac2..b3109fc 100644
--- a/src/ui/qgsoptionsbase.ui
+++ b/src/ui/qgsoptionsbase.ui
@@ -1408,7 +1408,7 @@
                   <item>
                    <widget class="QCheckBox" name="cbxAttributeTableDocked">
                     <property name="text">
-                     <string>Open attribute table in a dock window (QGIS restart required)</string>
+                     <string>Open attribute table in a dock window</string>
                     </property>
                    </widget>
                   </item>
diff --git a/src/ui/qgsprojectpropertiesbase.ui b/src/ui/qgsprojectpropertiesbase.ui
index 58bc53a..f08fb0f 100644
--- a/src/ui/qgsprojectpropertiesbase.ui
+++ b/src/ui/qgsprojectpropertiesbase.ui
@@ -42,16 +42,7 @@
        <enum>QFrame::Raised</enum>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_2">
-       <property name="leftMargin">
-        <number>0</number>
-       </property>
-       <property name="topMargin">
-        <number>0</number>
-       </property>
-       <property name="rightMargin">
-        <number>0</number>
-       </property>
-       <property name="bottomMargin">
+       <property name="margin">
         <number>0</number>
        </property>
        <item>
@@ -197,16 +188,7 @@
        <enum>QFrame::Raised</enum>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_3">
-       <property name="leftMargin">
-        <number>0</number>
-       </property>
-       <property name="topMargin">
-        <number>0</number>
-       </property>
-       <property name="rightMargin">
-        <number>0</number>
-       </property>
-       <property name="bottomMargin">
+       <property name="margin">
         <number>0</number>
        </property>
        <item>
@@ -222,16 +204,7 @@
          </property>
          <widget class="QWidget" name="mProjOpts_01">
           <layout class="QVBoxLayout" name="verticalLayout_6">
-           <property name="leftMargin">
-            <number>0</number>
-           </property>
-           <property name="topMargin">
-            <number>0</number>
-           </property>
-           <property name="rightMargin">
-            <number>0</number>
-           </property>
-           <property name="bottomMargin">
+           <property name="margin">
             <number>0</number>
            </property>
            <item>
@@ -263,7 +236,7 @@
                  <property name="title">
                   <string>General settings</string>
                  </property>
-                 <property name="syncGroup" stdset="0">
+                 <property name="syncGroup">
                   <string notr="true">projgeneral</string>
                  </property>
                  <layout class="QGridLayout" name="gridLayout_26">
@@ -412,22 +385,6 @@
                     </property>
                    </widget>
                   </item>
-                  <item row="0" column="1">
-                   <widget class="QLabel" name="projectFileName">
-                    <property name="sizePolicy">
-                     <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-                      <horstretch>0</horstretch>
-                      <verstretch>0</verstretch>
-                     </sizepolicy>
-                    </property>
-                    <property name="text">
-                     <string>Project title</string>
-                    </property>
-                    <property name="buddy">
-                     <cstring>titleEdit</cstring>
-                    </property>
-                   </widget>
-                  </item>
                   <item row="3" column="2" colspan="2">
                    <spacer>
                     <property name="orientation">
@@ -465,6 +422,13 @@
                     </property>
                    </widget>
                   </item>
+                  <item row="0" column="1" colspan="3">
+                   <widget class="QLineEdit" name="mProjectFileLineEdit">
+                    <property name="readOnly">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
                  </layout>
                 </widget>
                </item>
@@ -473,7 +437,7 @@
                  <property name="title">
                   <string>Measurements</string>
                  </property>
-                 <property name="syncGroup" stdset="0">
+                 <property name="syncGroup">
                   <string notr="true">projgeneral</string>
                  </property>
                  <layout class="QGridLayout" name="gridLayoutMeasureTool">
@@ -542,7 +506,7 @@
                  <property name="title">
                   <string>Coordinate display</string>
                  </property>
-                 <property name="syncGroup" stdset="0">
+                 <property name="syncGroup">
                   <string notr="true">projgeneral</string>
                  </property>
                  <layout class="QGridLayout" name="gridLayout_18">
@@ -632,7 +596,7 @@
                  <property name="checked">
                   <bool>false</bool>
                  </property>
-                 <property name="syncGroup" stdset="0">
+                 <property name="syncGroup">
                   <string notr="true">projgeneral</string>
                  </property>
                  <layout class="QGridLayout" name="gridLayout_7">
@@ -724,16 +688,7 @@
          </widget>
          <widget class="QWidget" name="mProjOpts_02">
           <layout class="QVBoxLayout" name="verticalLayout_5">
-           <property name="leftMargin">
-            <number>0</number>
-           </property>
-           <property name="topMargin">
-            <number>0</number>
-           </property>
-           <property name="rightMargin">
-            <number>0</number>
-           </property>
-           <property name="bottomMargin">
+           <property name="margin">
             <number>0</number>
            </property>
            <item>
@@ -783,16 +738,7 @@
          </widget>
          <widget class="QWidget" name="mProjOpts_03">
           <layout class="QVBoxLayout" name="verticalLayout_9">
-           <property name="leftMargin">
-            <number>0</number>
-           </property>
-           <property name="topMargin">
-            <number>0</number>
-           </property>
-           <property name="rightMargin">
-            <number>0</number>
-           </property>
-           <property name="bottomMargin">
+           <property name="margin">
             <number>0</number>
            </property>
            <item>
@@ -808,7 +754,7 @@
                <rect>
                 <x>0</x>
                 <y>0</y>
-                <width>133</width>
+                <width>134</width>
                 <height>100</height>
                </rect>
               </property>
@@ -864,16 +810,7 @@
          </widget>
          <widget class="QWidget" name="mProjOpts_04">
           <layout class="QVBoxLayout" name="verticalLayout_11">
-           <property name="leftMargin">
-            <number>0</number>
-           </property>
-           <property name="topMargin">
-            <number>0</number>
-           </property>
-           <property name="rightMargin">
-            <number>0</number>
-           </property>
-           <property name="bottomMargin">
+           <property name="margin">
             <number>0</number>
            </property>
            <item>
@@ -899,7 +836,7 @@
                  <property name="title">
                   <string>Default symbols</string>
                  </property>
-                 <property name="syncGroup" stdset="0">
+                 <property name="syncGroup">
                   <string notr="true">projstyles</string>
                  </property>
                  <layout class="QGridLayout" name="gridLayout_11">
@@ -1114,7 +1051,7 @@
                  <property name="title">
                   <string>Options</string>
                  </property>
-                 <property name="syncGroup" stdset="0">
+                 <property name="syncGroup">
                   <string notr="true">projstyles</string>
                  </property>
                  <layout class="QVBoxLayout" name="verticalLayout_18">
@@ -1289,16 +1226,7 @@
          </widget>
          <widget class="QWidget" name="mProjOpts_05">
           <layout class="QVBoxLayout" name="verticalLayout_14">
-           <property name="leftMargin">
-            <number>0</number>
-           </property>
-           <property name="topMargin">
-            <number>0</number>
-           </property>
-           <property name="rightMargin">
-            <number>0</number>
-           </property>
-           <property name="bottomMargin">
+           <property name="margin">
             <number>0</number>
            </property>
            <item>
@@ -1314,7 +1242,7 @@
                <rect>
                 <x>0</x>
                 <y>0</y>
-                <width>663</width>
+                <width>665</width>
                 <height>2249</height>
                </rect>
               </property>
@@ -1336,13 +1264,13 @@
                  <property name="checked">
                   <bool>false</bool>
                  </property>
-                 <property name="collapsed" stdset="0">
+                 <property name="collapsed">
                   <bool>false</bool>
                  </property>
-                 <property name="saveCollapsedState" stdset="0">
+                 <property name="saveCollapsedState">
                   <bool>true</bool>
                  </property>
-                 <property name="syncGroup" stdset="0">
+                 <property name="syncGroup">
                   <string notr="true">projowsserver</string>
                  </property>
                  <layout class="QGridLayout" name="gridLayout_6">
@@ -1579,7 +1507,7 @@
                  <property name="title">
                   <string>WMS capabilities</string>
                  </property>
-                 <property name="syncGroup" stdset="0">
+                 <property name="syncGroup">
                   <string notr="true">projowsserver</string>
                  </property>
                  <layout class="QGridLayout" name="gridLayout_13">
@@ -1594,10 +1522,10 @@
                     <property name="checked">
                      <bool>false</bool>
                     </property>
-                    <property name="collapsed" stdset="0">
+                    <property name="collapsed">
                      <bool>false</bool>
                     </property>
-                    <property name="saveCollapsedState" stdset="0">
+                    <property name="saveCollapsedState">
                      <bool>true</bool>
                     </property>
                     <layout class="QGridLayout" name="gridLayout_4">
@@ -1703,10 +1631,10 @@
                     <property name="checked">
                      <bool>false</bool>
                     </property>
-                    <property name="collapsed" stdset="0">
+                    <property name="collapsed">
                      <bool>false</bool>
                     </property>
-                    <property name="saveCollapsedState" stdset="0">
+                    <property name="saveCollapsedState">
                      <bool>true</bool>
                     </property>
                     <layout class="QGridLayout" name="gridLayout_10">
@@ -1762,10 +1690,10 @@
                     <property name="checked">
                      <bool>false</bool>
                     </property>
-                    <property name="collapsed" stdset="0">
+                    <property name="collapsed">
                      <bool>false</bool>
                     </property>
-                    <property name="saveCollapsedState" stdset="0">
+                    <property name="saveCollapsedState">
                      <bool>true</bool>
                     </property>
                     <layout class="QGridLayout" name="gridLayout">
@@ -1821,10 +1749,10 @@
                     <property name="checked">
                      <bool>false</bool>
                     </property>
-                    <property name="collapsed" stdset="0">
+                    <property name="collapsed">
                      <bool>false</bool>
                     </property>
-                    <property name="saveCollapsedState" stdset="0">
+                    <property name="saveCollapsedState">
                      <bool>true</bool>
                     </property>
                     <layout class="QGridLayout" name="gridLayout_5">
@@ -2129,7 +2057,7 @@
                  <property name="title">
                   <string>WFS capabilities (also influences DXF export)</string>
                  </property>
-                 <property name="syncGroup" stdset="0">
+                 <property name="syncGroup">
                   <string notr="true">projowsserver</string>
                  </property>
                  <layout class="QGridLayout" name="gridLayout_8">
@@ -2215,7 +2143,7 @@
                  <property name="title">
                   <string>WCS capabilities</string>
                  </property>
-                 <property name="syncGroup" stdset="0">
+                 <property name="syncGroup">
                   <string notr="true">projowsserver</string>
                  </property>
                  <layout class="QGridLayout" name="gridLayout_9">
@@ -2355,16 +2283,7 @@
          </widget>
          <widget class="QWidget" name="mProjOpts_06">
           <layout class="QVBoxLayout" name="verticalLayout_15">
-           <property name="leftMargin">
-            <number>0</number>
-           </property>
-           <property name="topMargin">
-            <number>0</number>
-           </property>
-           <property name="rightMargin">
-            <number>0</number>
-           </property>
-           <property name="bottomMargin">
+           <property name="margin">
             <number>0</number>
            </property>
            <item>
@@ -2417,16 +2336,7 @@
          </widget>
          <widget class="QWidget" name="mTabRelations">
           <layout class="QGridLayout" name="gridLayout_16">
-           <property name="leftMargin">
-            <number>0</number>
-           </property>
-           <property name="topMargin">
-            <number>0</number>
-           </property>
-           <property name="rightMargin">
-            <number>0</number>
-           </property>
-           <property name="bottomMargin">
+           <property name="margin">
             <number>0</number>
            </property>
           </layout>
@@ -2472,16 +2382,7 @@
       <enum>QFrame::Raised</enum>
      </property>
      <layout class="QHBoxLayout" name="horizontalLayout">
-      <property name="leftMargin">
-       <number>0</number>
-      </property>
-      <property name="topMargin">
-       <number>0</number>
-      </property>
-      <property name="rightMargin">
-       <number>0</number>
-      </property>
-      <property name="bottomMargin">
+      <property name="margin">
        <number>0</number>
       </property>
       <item>
@@ -2540,6 +2441,7 @@
  <tabstops>
   <tabstop>mOptionsListWidget</tabstop>
   <tabstop>scrollArea_2</tabstop>
+  <tabstop>mProjectFileLineEdit</tabstop>
   <tabstop>titleEdit</tabstop>
   <tabstop>pbnSelectionColor</tabstop>
   <tabstop>pbnCanvasColor</tabstop>
diff --git a/tests/src/analysis/testqgsalignraster.cpp b/tests/src/analysis/testqgsalignraster.cpp
index ba62980..dbeb79b 100644
--- a/tests/src/analysis/testqgsalignraster.cpp
+++ b/tests/src/analysis/testqgsalignraster.cpp
@@ -169,7 +169,7 @@ class TestAlignRaster : public QObject
       QgsAlignRaster align;
       QgsAlignRaster::List rasters;
       rasters << QgsAlignRaster::Item( SRC_FILE, tmpFile );
-      rasters[0].resampleMethod = QgsAlignRaster::RA_Bilinear;
+      rasters[0].resampleMethod = QgsAlignRaster::RA_Average;
       align.setRasters( rasters );
       align.setParametersFromRaster( SRC_FILE, QString(), QSizeF( 0.4, 0.4 ) );
       bool res = align.run();
@@ -190,7 +190,7 @@ class TestAlignRaster : public QObject
       QgsAlignRaster align;
       QgsAlignRaster::List rasters;
       rasters << QgsAlignRaster::Item( SRC_FILE, tmpFile );
-      rasters[0].resampleMethod = QgsAlignRaster::RA_Bilinear;
+      rasters[0].resampleMethod = QgsAlignRaster::RA_Average;
       rasters[0].rescaleValues = true;
       align.setRasters( rasters );
       align.setParametersFromRaster( SRC_FILE, QString(), QSizeF( 0.4, 0.4 ) );
diff --git a/tests/src/core/testqgsapplication.cpp b/tests/src/core/testqgsapplication.cpp
index ab74259..35a1275 100644
--- a/tests/src/core/testqgsapplication.cpp
+++ b/tests/src/core/testqgsapplication.cpp
@@ -83,6 +83,7 @@ void TestQgsApplication::platformName()
   QCOMPARE( QgsApplication::platform(), QString( "desktop" ) );
 }
 
+
 void TestQgsApplication::checkPaths()
 {
   QString myPath = QgsApplication::authorsFilePath();
diff --git a/tests/src/core/testqgscomposition.cpp b/tests/src/core/testqgscomposition.cpp
index f4041d2..3af8b1a 100644
--- a/tests/src/core/testqgscomposition.cpp
+++ b/tests/src/core/testqgscomposition.cpp
@@ -51,6 +51,7 @@ class TestQgsComposition : public QObject
     void resizeToContents();
     void resizeToContentsMargin();
     void resizeToContentsMultiPage();
+    void variablesEdited();
 
   private:
     QgsComposition *mComposition;
@@ -511,5 +512,19 @@ void TestQgsComposition::resizeToContentsMultiPage()
   delete composition;
 }
 
+void TestQgsComposition::variablesEdited()
+{
+  QgsMapSettings ms;
+  QgsComposition c( ms );
+  QSignalSpy spyVariablesChanged( &c, SIGNAL( variablesChanged() ) );
+
+  c.setCustomProperty( "not a variable", "1" );
+  QVERIFY( spyVariablesChanged.count() == 0 );
+  c.setCustomProperty( "variableNames", "1" );
+  QVERIFY( spyVariablesChanged.count() == 1 );
+  c.setCustomProperty( "variableValues", "1" );
+  QVERIFY( spyVariablesChanged.count() == 2 );
+}
+
 QTEST_MAIN( TestQgsComposition )
 #include "testqgscomposition.moc"
diff --git a/tests/src/core/testqgscoordinatereferencesystem.cpp b/tests/src/core/testqgscoordinatereferencesystem.cpp
index afa1c02..37b3b79 100644
--- a/tests/src/core/testqgscoordinatereferencesystem.cpp
+++ b/tests/src/core/testqgscoordinatereferencesystem.cpp
@@ -214,8 +214,14 @@ void TestQgsCoordinateReferenceSystem::createFromESRIWkt()
   myWktStrings << "GEOGCS[\"GCS_South_American_1969\",DATUM[\"D_South_American_1969\",SPHEROID[\"GRS_1967_Truncated\",6378160.0,298.25]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]";
   myFiles << "";
   myGdalVersionOK << 1900;
+#if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 2000000
+  //proj definition for EPSG:4618 was updated in GDAL 2.0 - see https://github.com/OSGeo/proj.4/issues/241
+  myProj4Strings << "+proj=longlat +ellps=aust_SA +towgs84=-66.87,4.37,-38.52,0,0,0,0 +no_defs";
+  myTOWGS84Strings << "+towgs84=-66.87,4.37,-38.52,0,0,0,0";
+#else
   myProj4Strings << "+proj=longlat +ellps=aust_SA +towgs84=-57,1,-41,0,0,0,0 +no_defs";
   myTOWGS84Strings << "+towgs84=-57,1,-41,0,0,0,0";
+#endif
   myAuthIdStrings << "EPSG:4618";
 
   // do test with WKT definitions
diff --git a/tests/src/core/testqgsdistancearea.cpp b/tests/src/core/testqgsdistancearea.cpp
index 02f337c..3e4c9d7 100644
--- a/tests/src/core/testqgsdistancearea.cpp
+++ b/tests/src/core/testqgsdistancearea.cpp
@@ -365,7 +365,8 @@ void TestQgsDistanceArea::regression14675()
   calc.setEllipsoid( "GRS80" );
   calc.setSourceCrs( 145L );
   QgsGeometry geom( QgsGeometryFactory::geomFromWkt( "Polygon ((917593.5791854317067191 6833700.00807378999888897, 917596.43389983859378844 6833700.67099479306489229, 917599.53056440979707986 6833700.78673478215932846, 917593.5791854317067191 6833700.00807378999888897))" ) );
-  QVERIFY( qgsDoubleNear( calc.measureArea( &geom ), 0.83301, 0.0001 ) );
+  //lots of tolerance here - the formulas get quite unstable with small areas due to division by very small floats
+  QVERIFY( qgsDoubleNear( calc.measureArea( &geom ), 0.83301, 0.02 ) );
 }
 
 QTEST_MAIN( TestQgsDistanceArea )
diff --git a/tests/src/core/testqgsexpressioncontext.cpp b/tests/src/core/testqgsexpressioncontext.cpp
index 9bcc666..c62e857 100644
--- a/tests/src/core/testqgsexpressioncontext.cpp
+++ b/tests/src/core/testqgsexpressioncontext.cpp
@@ -37,6 +37,7 @@ class TestQgsExpressionContext : public QObject
     void contextScopeCopy();
     void contextScopeFunctions();
     void contextStack();
+    void scopeByName();
     void contextCopy();
     void contextStackFunctions();
     void evaluate();
@@ -300,6 +301,17 @@ void TestQgsExpressionContext::contextStack()
   QCOMPARE( scopes.at( 0 ), scope1 );
 }
 
+void TestQgsExpressionContext::scopeByName()
+{
+  QgsExpressionContext context;
+  QCOMPARE( context.indexOfScope( "test1" ), -1 );
+  context << new QgsExpressionContextScope( "test1" );
+  context << new QgsExpressionContextScope( "test2" );
+  QCOMPARE( context.indexOfScope( "test1" ), 0 );
+  QCOMPARE( context.indexOfScope( "test2" ), 1 );
+  QCOMPARE( context.indexOfScope( "not in context" ), -1 );
+}
+
 void TestQgsExpressionContext::contextCopy()
 {
   QgsExpressionContext context;
diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp
index 8fe4cdd..785c4ab 100644
--- a/tests/src/core/testqgsgeometry.cpp
+++ b/tests/src/core/testqgsgeometry.cpp
@@ -88,6 +88,8 @@ class TestQgsGeometry : public QObject
     void bufferCheck();
     void smoothCheck();
 
+    void unaryUnion();
+
     void dataStream();
 
     void exportToGeoJSON();
@@ -3240,6 +3242,20 @@ void TestQgsGeometry::smoothCheck()
   QVERIFY( QgsGeometry::compare( multipoly, expectedMultiPoly ) );
 }
 
+void TestQgsGeometry::unaryUnion()
+{
+  //test QgsGeometry::unaryUnion with null geometry
+  QString wkt1 = "Polygon ((0 0, 10 0, 10 10, 0 10, 0 0 ))";
+  QString wkt2 = "Polygon ((2 2, 4 2, 4 4, 2 4, 2 2))";
+  QScopedPointer< QgsGeometry > geom1( QgsGeometry::fromWkt( wkt1 ) );
+  QScopedPointer< QgsGeometry > geom2( QgsGeometry::fromWkt( wkt2 ) );
+  QScopedPointer< QgsGeometry > empty( new QgsGeometry() );
+  QList< QgsGeometry* > list;
+  list << geom1.data() << empty.data() << geom2.data();
+
+  QScopedPointer< QgsGeometry > result( QgsGeometry::unaryUnion( list ) );
+}
+
 void TestQgsGeometry::dataStream()
 {
   QString wkt = "Point (40 50)";
diff --git a/tests/src/core/testqgsproject.cpp b/tests/src/core/testqgsproject.cpp
index d8a7414..7a8b3c7 100644
--- a/tests/src/core/testqgsproject.cpp
+++ b/tests/src/core/testqgsproject.cpp
@@ -31,6 +31,7 @@ class TestQgsProject : public QObject
 
     void testReadPath();
     void testProjectUnits();
+    void variablesChanged();
 };
 
 void TestQgsProject::init()
@@ -124,6 +125,13 @@ void TestQgsProject::testProjectUnits()
   QCOMPARE( prj->areaUnits(), QgsUnitTypes::Acres );
 }
 
+void TestQgsProject::variablesChanged()
+{
+  QSignalSpy spyVariablesChanged( QgsProject::instance(), SIGNAL( variablesChanged() ) );
+  QgsProject::instance()->emitVariablesChanged();
+  QVERIFY( spyVariablesChanged.count() == 1 );
+}
+
 
 QTEST_MAIN( TestQgsProject )
 #include "testqgsproject.moc"
diff --git a/tests/src/core/testqgsrasterlayer.cpp b/tests/src/core/testqgsrasterlayer.cpp
index 77efc58..5cf9326 100644
--- a/tests/src/core/testqgsrasterlayer.cpp
+++ b/tests/src/core/testqgsrasterlayer.cpp
@@ -372,12 +372,11 @@ void TestQgsRasterLayer::checkStats()
   //QVERIFY( myStatistics.elementCount == 100 );
   QVERIFY( myStatistics.minimumValue == 0 );
   QVERIFY( myStatistics.maximumValue == 9 );
-  QVERIFY( myStatistics.mean == 4.5 );
+  QVERIFY( qgsDoubleNear( myStatistics.mean, 4.5 ) );
   double stdDev = 2.87228132326901431;
   // TODO: verify why GDAL stdDev is so different from generic (2.88675)
   mReport += QString( "stdDev = %1 expected = %2<br>\n" ).arg( myStatistics.stdDev ).arg( stdDev );
-  QVERIFY( fabs( myStatistics.stdDev - stdDev )
-           < 0.0000000000000001 );
+  QVERIFY( qgsDoubleNear( myStatistics.stdDev, stdDev, 0.00000000000001 ) );
   mReport += "<p>Passed</p>";
 }
 
diff --git a/tests/src/providers/CMakeLists.txt b/tests/src/providers/CMakeLists.txt
index badad73..7e8b2e9 100644
--- a/tests/src/providers/CMakeLists.txt
+++ b/tests/src/providers/CMakeLists.txt
@@ -9,6 +9,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
   ${CMAKE_SOURCE_DIR}/src/core/auth
   ${CMAKE_SOURCE_DIR}/src/core/geometry
   ${CMAKE_SOURCE_DIR}/src/core/raster
+  ${CMAKE_SOURCE_DIR}/src/providers/wms
 )
 INCLUDE_DIRECTORIES(SYSTEM
   ${QT_INCLUDE_DIR}
@@ -84,6 +85,14 @@ SET_TARGET_PROPERTIES(qgis_wcsprovidertest PROPERTIES
 
 ADD_QGIS_TEST(gdalprovidertest testqgsgdalprovider.cpp)
 
+ADD_QGIS_TEST(wmscapabilititestest
+              testqgswmscapabilities.cpp)
+TARGET_LINK_LIBRARIES(qgis_wmscapabilititestest wmsprovider_a)
+
+ADD_QGIS_TEST(wmsprovidertest
+              testqgswmsprovider.cpp)
+TARGET_LINK_LIBRARIES(qgis_wmsprovidertest wmsprovider_a)
+
 #############################################################
 # WCS public servers test:
 # No need to test on all platforms
diff --git a/tests/src/providers/testqgswmscapabilities.cpp b/tests/src/providers/testqgswmscapabilities.cpp
new file mode 100644
index 0000000..686c30a
--- /dev/null
+++ b/tests/src/providers/testqgswmscapabilities.cpp
@@ -0,0 +1,70 @@
+#include <QFile>
+#include <QObject>
+#include <QtTest/QtTest>
+#include <qgswmscapabilities.h>
+#include <qgsapplication.h>
+
+/** \ingroup UnitTests
+ * This is a unit test for the WMS capabilities parser.
+ */
+class TestQgsWmsCapabilities: public QObject
+{
+    Q_OBJECT
+  private slots:
+
+    void initTestCase()
+    {
+      // init QGIS's paths - true means that all path will be inited from prefix
+      QgsApplication::init();
+      QgsApplication::initQgis();
+    }
+
+    //runs after all tests
+    void cleanupTestCase()
+    {
+      QgsApplication::exitQgis();
+    }
+
+
+    void read()
+    {
+      QgsWmsCapabilities capabilities;
+
+      QFile file( QString( TEST_DATA_DIR ) + "/provider/GetCapabilities.xml" );
+      QVERIFY( file.open( QIODevice::ReadOnly | QIODevice::Text ) );
+      const QByteArray content = file.readAll();
+      QVERIFY( content.size() > 0 );
+      const QgsWmsParserSettings config;
+
+      QVERIFY( capabilities.parseResponse( content, config ) );
+      QCOMPARE( capabilities.supportedLayers().size(), 5 );
+      QCOMPARE( capabilities.supportedLayers()[0].name, QString( "agri_zones" ) );
+      QCOMPARE( capabilities.supportedLayers()[1].name, QString( "buildings" ) );
+      QCOMPARE( capabilities.supportedLayers()[2].name, QString( "land_surveing_parcels" ) );
+      QCOMPARE( capabilities.supportedLayers()[3].name, QString( "cadastre" ) );
+      QCOMPARE( capabilities.supportedLayers()[4].name, QString( "test" ) );
+
+      // make sure the default style is not seen twice in the child layers
+      QCOMPARE( capabilities.supportedLayers()[3].style.size(), 1 );
+      QCOMPARE( capabilities.supportedLayers()[3].style[0].name, QString( "default" ) );
+      QCOMPARE( capabilities.supportedLayers()[1].style.size(), 1 );
+      QCOMPARE( capabilities.supportedLayers()[1].style[0].name, QString( "default" ) );
+      QCOMPARE( capabilities.supportedLayers()[2].style.size(), 1 );
+      QCOMPARE( capabilities.supportedLayers()[2].style[0].name, QString( "default" ) );
+
+      // check it can read 2 styles for a layer and that the legend URL is OK
+      QCOMPARE( capabilities.supportedLayers()[0].style.size(), 2 );
+      QCOMPARE( capabilities.supportedLayers()[0].style[0].name, QString( "yt_style" ) );
+      QCOMPARE( capabilities.supportedLayers()[0].style[0].legendUrl.size(), 1 );
+      QCOMPARE( capabilities.supportedLayers()[0].style[0].legendUrl[0].onlineResource.xlinkHref,
+                QString( "http://www.example.com/yt.png" ) );
+      QCOMPARE( capabilities.supportedLayers()[0].style[1].name, QString( "fb_style" ) );
+      QCOMPARE( capabilities.supportedLayers()[0].style[1].legendUrl.size(), 1 );
+      QCOMPARE( capabilities.supportedLayers()[0].style[1].legendUrl[0].onlineResource.xlinkHref,
+                QString( "http://www.example.com/fb.png" ) );
+    }
+
+};
+
+QTEST_MAIN( TestQgsWmsCapabilities )
+#include "testqgswmscapabilities.moc"
diff --git a/tests/src/providers/testqgswmsprovider.cpp b/tests/src/providers/testqgswmsprovider.cpp
new file mode 100644
index 0000000..1f55efd
--- /dev/null
+++ b/tests/src/providers/testqgswmsprovider.cpp
@@ -0,0 +1,69 @@
+#include <QFile>
+#include <QObject>
+#include <QtTest/QtTest>
+#include <qgswmsprovider.h>
+#include <qgsapplication.h>
+
+/** \ingroup UnitTests
+ * This is a unit test for the WMS provider.
+ */
+class TestQgsWmsProvider: public QObject
+{
+    Q_OBJECT
+  private slots:
+
+    void initTestCase()
+    {
+      // init QGIS's paths - true means that all path will be inited from prefix
+      QgsApplication::init();
+      QgsApplication::initQgis();
+
+      QFile file( QString( TEST_DATA_DIR ) + "/provider/GetCapabilities.xml" );
+      QVERIFY( file.open( QIODevice::ReadOnly | QIODevice::Text ) );
+      const QByteArray content = file.readAll();
+      QVERIFY( content.size() > 0 );
+      const QgsWmsParserSettings config;
+
+      mCapabilities = new QgsWmsCapabilities();
+      QVERIFY( mCapabilities->parseResponse( content, config ) );
+    }
+
+    //runs after all tests
+    void cleanupTestCase()
+    {
+      delete mCapabilities;
+      QgsApplication::exitQgis();
+    }
+
+    void legendGraphicsWithStyle()
+    {
+      QgsWmsProvider provider( "http://localhost:8380/mapserv?xxx&layers=agri_zones&styles=fb_style&format=image/jpg", mCapabilities );
+      QCOMPARE( provider.getLegendGraphicUrl(), QString( "http://www.example.com/fb.png?" ) );
+    }
+
+    void legendGraphicsWithSecondStyle()
+    {
+      QgsWmsProvider provider( "http://localhost:8380/mapserv?xxx&layers=agri_zones&styles=yt_style&format=image/jpg", mCapabilities );
+      QCOMPARE( provider.getLegendGraphicUrl(), QString( "http://www.example.com/yt.png?" ) );
+    }
+
+    void legendGraphicsWithoutStyleWithDefault()
+    {
+      QgsWmsProvider provider( "http://localhost:8380/mapserv?xxx&layers=buildings&styles=&format=image/jpg", mCapabilities );
+      //only one style, can guess default => use it
+      QCOMPARE( provider.getLegendGraphicUrl(), QString( "http://www.example.com/buildings.png?" ) );
+    }
+
+    void legendGraphicsWithoutStyleWithoutDefault()
+    {
+      QgsWmsProvider provider( "http://localhost:8380/mapserv?xxx&layers=agri_zones&styles=&format=image/jpg", mCapabilities );
+      //two style, cannot guess default => use the WMS GetLegendGraphics
+      QCOMPARE( provider.getLegendGraphicUrl(), QString( "http://localhost:8380/mapserv?" ) );
+    }
+
+  private:
+    QgsWmsCapabilities* mCapabilities;
+};
+
+QTEST_MAIN( TestQgsWmsProvider )
+#include "testqgswmsprovider.moc"
diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt
index 38ab9e9..3dbd0e1 100644
--- a/tests/src/python/CMakeLists.txt
+++ b/tests/src/python/CMakeLists.txt
@@ -21,6 +21,7 @@ ADD_PYTHON_TEST(PyQgsColorSchemeRegistry test_qgscolorschemeregistry.py)
 ADD_PYTHON_TEST(PyQgsComposerEffects test_qgscomposereffects.py)
 ADD_PYTHON_TEST(PyQgsComposerHtml test_qgscomposerhtml.py)
 ADD_PYTHON_TEST(PyQgsComposerLabel test_qgscomposerlabel.py)
+ADD_PYTHON_TEST(PyQgsComposerLegend test_qgscomposerlegend.py)
 ADD_PYTHON_TEST(PyQgsComposerMap test_qgscomposermap.py)
 ADD_PYTHON_TEST(PyQgsComposerMapGrid test_qgscomposermapgrid.py)
 ADD_PYTHON_TEST(PyQgsComposerPicture test_qgscomposerpicture.py)
diff --git a/tests/src/python/test_qgscomposerlegend.py b/tests/src/python/test_qgscomposerlegend.py
new file mode 100644
index 0000000..dd3e9c1
--- /dev/null
+++ b/tests/src/python/test_qgscomposerlegend.py
@@ -0,0 +1,203 @@
+# -*- coding: utf-8 -*-
+"""QGIS Unit tests for QgsComposerLegend.
+
+.. note:: This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+"""
+__author__ = '(C) 2016 by Nyall Dawson'
+__date__ = '13/07/2016'
+__copyright__ = 'Copyright 2016, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+from qgis.PyQt.QtCore import QRectF
+from qgis.PyQt.QtGui import QColor
+
+from qgis.core import (QgsComposerLegend,
+                       QgsComposerMap,
+                       QgsComposition,
+                       QgsMapSettings,
+                       QgsVectorLayer,
+                       QgsMapLayerRegistry,
+                       QgsMarkerSymbolV2,
+                       QgsSingleSymbolRendererV2,
+                       QgsRectangle
+                       )
+from qgis.testing import (start_app,
+                          unittest
+                          )
+from utilities import unitTestDataPath
+from qgscompositionchecker import QgsCompositionChecker
+import os
+
+start_app()
+TEST_DATA_DIR = unitTestDataPath()
+
+
+class TestQgsComposerLegend(unittest.TestCase):
+
+    def testInitialSizeSymbolMapUnits(self):
+        """Test initial size of legend with a symbol size in map units"""
+
+        point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
+        point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
+        QgsMapLayerRegistry.instance().addMapLayers([point_layer])
+
+        marker_symbol = QgsMarkerSymbolV2.createSimple({'color': '#ff0000', 'outline_style': 'no', 'size': '5', 'size_unit': 'MapUnit'})
+
+        point_layer.setRendererV2(QgsSingleSymbolRendererV2(marker_symbol))
+
+        s = QgsMapSettings()
+        s.setLayers([point_layer.id()])
+        s.setCrsTransformEnabled(False)
+        composition = QgsComposition(s)
+        composition.setPaperSize(297, 210)
+
+        composer_map = QgsComposerMap(composition, 20, 20, 80, 80)
+        composer_map.setFrameEnabled(True)
+        composition.addComposerMap(composer_map)
+        composer_map.setNewExtent(point_layer.extent())
+
+        legend = QgsComposerLegend(composition)
+        legend.setSceneRect(QRectF(120, 20, 80, 80))
+        legend.setFrameEnabled(True)
+        legend.setFrameOutlineWidth(2)
+        legend.setBackgroundColor(QColor(200, 200, 200))
+        legend.setTitle('')
+        composition.addComposerLegend(legend)
+        legend.setComposerMap(composer_map)
+
+        checker = QgsCompositionChecker(
+            'composer_legend_mapunits', composition)
+        checker.setControlPathPrefix("composer_legend")
+        result, message = checker.testComposition()
+        self.assertTrue(result, message)
+
+        QgsMapLayerRegistry.instance().removeMapLayers([point_layer])
+
+    def testResizeWithMapContent(self):
+        """Test test legend resizes to match map content"""
+
+        point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
+        point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
+        QgsMapLayerRegistry.instance().addMapLayers([point_layer])
+
+        s = QgsMapSettings()
+        s.setLayers([point_layer.id()])
+        s.setCrsTransformEnabled(False)
+        composition = QgsComposition(s)
+        composition.setPaperSize(297, 210)
+
+        composer_map = QgsComposerMap(composition, 20, 20, 80, 80)
+        composer_map.setFrameEnabled(True)
+        composition.addComposerMap(composer_map)
+        composer_map.setNewExtent(point_layer.extent())
+
+        legend = QgsComposerLegend(composition)
+        legend.setSceneRect(QRectF(120, 20, 80, 80))
+        legend.setFrameEnabled(True)
+        legend.setFrameOutlineWidth(2)
+        legend.setBackgroundColor(QColor(200, 200, 200))
+        legend.setTitle('')
+        legend.setLegendFilterByMapEnabled(True)
+        composition.addComposerLegend(legend)
+        legend.setComposerMap(composer_map)
+
+        composer_map.setNewExtent(QgsRectangle(-102.51, 41.16, -102.36, 41.30))
+
+        checker = QgsCompositionChecker(
+            'composer_legend_size_content', composition)
+        checker.setControlPathPrefix("composer_legend")
+        result, message = checker.testComposition()
+        self.assertTrue(result, message)
+
+        QgsMapLayerRegistry.instance().removeMapLayers([point_layer])
+
+    def testResizeDisabled(self):
+        """Test that test legend does not resize if auto size is disabled"""
+
+        point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
+        point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
+        QgsMapLayerRegistry.instance().addMapLayers([point_layer])
+
+        s = QgsMapSettings()
+        s.setLayers([point_layer.id()])
+        s.setCrsTransformEnabled(False)
+        composition = QgsComposition(s)
+        composition.setPaperSize(297, 210)
+
+        composer_map = QgsComposerMap(composition, 20, 20, 80, 80)
+        composer_map.setFrameEnabled(True)
+        composition.addComposerMap(composer_map)
+        composer_map.setNewExtent(point_layer.extent())
+
+        legend = QgsComposerLegend(composition)
+        legend.setSceneRect(QRectF(120, 20, 80, 80))
+        legend.setFrameEnabled(True)
+        legend.setFrameOutlineWidth(2)
+        legend.setBackgroundColor(QColor(200, 200, 200))
+        legend.setTitle('')
+        legend.setLegendFilterByMapEnabled(True)
+
+        #disable auto resizing
+        legend.setResizeToContents(False)
+
+        composition.addComposerLegend(legend)
+        legend.setComposerMap(composer_map)
+
+        composer_map.setNewExtent(QgsRectangle(-102.51, 41.16, -102.36, 41.30))
+
+        checker = QgsCompositionChecker(
+            'composer_legend_noresize', composition)
+        checker.setControlPathPrefix("composer_legend")
+        result, message = checker.testComposition()
+        self.assertTrue(result, message)
+
+        QgsMapLayerRegistry.instance().removeMapLayers([point_layer])
+
+    def testResizeDisabledCrop(self):
+        """Test that if legend resizing is disabled, and legend is too small, then content is cropped"""
+
+        point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
+        point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
+        QgsMapLayerRegistry.instance().addMapLayers([point_layer])
+
+        s = QgsMapSettings()
+        s.setLayers([point_layer.id()])
+        s.setCrsTransformEnabled(False)
+        composition = QgsComposition(s)
+        composition.setPaperSize(297, 210)
+
+        composer_map = QgsComposerMap(composition, 20, 20, 80, 80)
+        composer_map.setFrameEnabled(True)
+        composition.addComposerMap(composer_map)
+        composer_map.setNewExtent(point_layer.extent())
+
+        legend = QgsComposerLegend(composition)
+        legend.setSceneRect(QRectF(120, 20, 20, 20))
+        legend.setFrameEnabled(True)
+        legend.setFrameOutlineWidth(2)
+        legend.setBackgroundColor(QColor(200, 200, 200))
+        legend.setTitle('')
+        legend.setLegendFilterByMapEnabled(True)
+
+        # disable auto resizing
+        legend.setResizeToContents(False)
+
+        composition.addComposerLegend(legend)
+        legend.setComposerMap(composer_map)
+
+        composer_map.setNewExtent(QgsRectangle(-102.51, 41.16, -102.36, 41.30))
+
+        checker = QgsCompositionChecker(
+            'composer_legend_noresize_crop', composition)
+        checker.setControlPathPrefix("composer_legend")
+        result, message = checker.testComposition()
+        self.assertTrue(result, message)
+
+        QgsMapLayerRegistry.instance().removeMapLayers([point_layer])
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/src/python/test_qgspallabeling_tests.py b/tests/src/python/test_qgspallabeling_tests.py
index 2fa9978..a70a740 100644
--- a/tests/src/python/test_qgspallabeling_tests.py
+++ b/tests/src/python/test_qgspallabeling_tests.py
@@ -184,6 +184,22 @@ class TestPointBase(object):
         self.lyr.shadowTransparency = 0
         self.checkTest()
 
+    def test_letter_spacing(self):
+        # Modified letter spacing
+        font = QFont(self._TestFont)
+        font.setLetterSpacing(QFont.AbsoluteSpacing, 3.5)
+        font.setPointSizeF(30)
+        self.lyr.textFont = font
+        self.checkTest()
+
+    def test_word_spacing(self):
+        # Modified word spacing
+        font = QFont(self._TestFont)
+        font.setPointSizeF(30)
+        font.setWordSpacing(20.5)
+        self.lyr.textFont = font
+        self.checkTest()
+
 # noinspection PyPep8Naming
 
 
diff --git a/tests/src/python/utilities.py b/tests/src/python/utilities.py
index 14930a7..b6faa51 100644
--- a/tests/src/python/utilities.py
+++ b/tests/src/python/utilities.py
@@ -453,6 +453,7 @@ class DoxygenParser():
         for m in e.getiterator('memberdef'):
             if self.elemIsBindableMember(m):
                 bindable_member = [e.find('compoundname').text, m.find('name').text]
+
                 if not bindable_member in bindable_members:
                     bindable_members.append(bindable_member)
             if self.elemIsDocumentableMember(m):
@@ -481,6 +482,17 @@ class DoxygenParser():
             :param elem: XML element for a class member
         """
 
+        name = elem.find('name').text
+        # hack to work around doxygen mistakenly flagging some private members as public
+        if name in ['runBlockOperationInThreads',
+                    'runLineOperation',
+                    'runLineOperationOnWholeImage',
+                    'runPixelOperation',
+                    'runPixelOperationOnWholeImage',
+                    'runRectOperation',
+                    'runRectOperationOnWholeImage']:
+            return False
+
         # only public or protected members are bindable
         if not self.visibility(elem) in ('public', 'protected'):
             return False
diff --git a/tests/testdata/control_images/composer_legend/expected_composer_legend_mapunits/expected_composer_legend_mapunits.png b/tests/testdata/control_images/composer_legend/expected_composer_legend_mapunits/expected_composer_legend_mapunits.png
new file mode 100644
index 0000000..b06acdf
Binary files /dev/null and b/tests/testdata/control_images/composer_legend/expected_composer_legend_mapunits/expected_composer_legend_mapunits.png differ
diff --git a/tests/testdata/control_images/composer_legend/expected_composer_legend_mapunits/expected_composer_legend_mapunits_mask.png b/tests/testdata/control_images/composer_legend/expected_composer_legend_mapunits/expected_composer_legend_mapunits_mask.png
new file mode 100644
index 0000000..b889494
Binary files /dev/null and b/tests/testdata/control_images/composer_legend/expected_composer_legend_mapunits/expected_composer_legend_mapunits_mask.png differ
diff --git a/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize/expected_composer_legend_noresize.png b/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize/expected_composer_legend_noresize.png
new file mode 100644
index 0000000..b157219
Binary files /dev/null and b/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize/expected_composer_legend_noresize.png differ
diff --git a/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize/expected_composer_legend_noresize_mask.png b/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize/expected_composer_legend_noresize_mask.png
new file mode 100644
index 0000000..6fd8107
Binary files /dev/null and b/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize/expected_composer_legend_noresize_mask.png differ
diff --git a/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize_crop/expected_composer_legend_noresize_crop.png b/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize_crop/expected_composer_legend_noresize_crop.png
new file mode 100644
index 0000000..1f43f67
Binary files /dev/null and b/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize_crop/expected_composer_legend_noresize_crop.png differ
diff --git a/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize_crop/expected_composer_legend_noresize_crop_mask.png b/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize_crop/expected_composer_legend_noresize_crop_mask.png
new file mode 100644
index 0000000..9950334
Binary files /dev/null and b/tests/testdata/control_images/composer_legend/expected_composer_legend_noresize_crop/expected_composer_legend_noresize_crop_mask.png differ
diff --git a/tests/testdata/control_images/composer_legend/expected_composer_legend_size_content/expected_composer_legend_size_content.png b/tests/testdata/control_images/composer_legend/expected_composer_legend_size_content/expected_composer_legend_size_content.png
new file mode 100644
index 0000000..604dbbc
Binary files /dev/null and b/tests/testdata/control_images/composer_legend/expected_composer_legend_size_content/expected_composer_legend_size_content.png differ
diff --git a/tests/testdata/control_images/composer_legend/expected_composer_legend_size_content/expected_composer_legend_size_content_mask.png b/tests/testdata/control_images/composer_legend/expected_composer_legend_size_content/expected_composer_legend_size_content_mask.png
new file mode 100644
index 0000000..98e4b26
Binary files /dev/null and b/tests/testdata/control_images/composer_legend/expected_composer_legend_size_content/expected_composer_legend_size_content_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_canvas/sp_letter_spacing/sp_letter_spacing.png b/tests/testdata/control_images/expected_pal_canvas/sp_letter_spacing/sp_letter_spacing.png
new file mode 100644
index 0000000..5304b16
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_canvas/sp_letter_spacing/sp_letter_spacing.png differ
diff --git a/tests/testdata/control_images/expected_pal_canvas/sp_letter_spacing/sp_letter_spacing_mask.png b/tests/testdata/control_images/expected_pal_canvas/sp_letter_spacing/sp_letter_spacing_mask.png
new file mode 100644
index 0000000..c3493bd
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_canvas/sp_letter_spacing/sp_letter_spacing_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_canvas/sp_word_spacing/sp_word_spacing.png b/tests/testdata/control_images/expected_pal_canvas/sp_word_spacing/sp_word_spacing.png
new file mode 100644
index 0000000..7c40ca0
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_canvas/sp_word_spacing/sp_word_spacing.png differ
diff --git a/tests/testdata/control_images/expected_pal_canvas/sp_word_spacing/sp_word_spacing_mask.png b/tests/testdata/control_images/expected_pal_canvas/sp_word_spacing/sp_word_spacing_mask.png
new file mode 100644
index 0000000..f7db90a
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_canvas/sp_word_spacing/sp_word_spacing_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_above/sp_curved_placement_above.png b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_above/sp_curved_placement_above.png
index 2195736..08aecbf 100644
Binary files a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_above/sp_curved_placement_above.png and b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_above/sp_curved_placement_above.png differ
diff --git a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_above/sp_curved_placement_above_mask.png b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_above/sp_curved_placement_above_mask.png
index 31c3799..015aa4b 100644
Binary files a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_above/sp_curved_placement_above_mask.png and b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_above/sp_curved_placement_above_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_below/sp_curved_placement_below.png b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_below/sp_curved_placement_below.png
index d46334b..c036e7ec 100644
Binary files a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_below/sp_curved_placement_below.png and b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_below/sp_curved_placement_below.png differ
diff --git a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_below/sp_curved_placement_below_mask.png b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_below/sp_curved_placement_below_mask.png
index 730de01..57d1b24 100644
Binary files a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_below/sp_curved_placement_below_mask.png and b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_below/sp_curved_placement_below_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_online/sp_curved_placement_online.png b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_online/sp_curved_placement_online.png
index 603b97d..a60b0bb 100644
Binary files a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_online/sp_curved_placement_online.png and b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_online/sp_curved_placement_online.png differ
diff --git a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_online/sp_curved_placement_online_mask.png b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_online/sp_curved_placement_online_mask.png
index 89dbf8b..c41b629 100644
Binary files a/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_online/sp_curved_placement_online_mask.png and b/tests/testdata/control_images/expected_pal_canvas_line/sp_curved_placement_online/sp_curved_placement_online_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_img_letter_spacing/sp_img_letter_spacing.png b/tests/testdata/control_images/expected_pal_composer/sp_img_letter_spacing/sp_img_letter_spacing.png
new file mode 100644
index 0000000..ae88a86
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_img_letter_spacing/sp_img_letter_spacing.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_img_letter_spacing/sp_img_letter_spacing_mask.png b/tests/testdata/control_images/expected_pal_composer/sp_img_letter_spacing/sp_img_letter_spacing_mask.png
new file mode 100644
index 0000000..91577fd
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_img_letter_spacing/sp_img_letter_spacing_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_img_word_spacing/sp_img_word_spacing.png b/tests/testdata/control_images/expected_pal_composer/sp_img_word_spacing/sp_img_word_spacing.png
new file mode 100644
index 0000000..bad11f5
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_img_word_spacing/sp_img_word_spacing.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_img_word_spacing/sp_img_word_spacing_mask.png b/tests/testdata/control_images/expected_pal_composer/sp_img_word_spacing/sp_img_word_spacing_mask.png
new file mode 100644
index 0000000..f60d864
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_img_word_spacing/sp_img_word_spacing_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_pdf_letter_spacing/sp_pdf_letter_spacing.png b/tests/testdata/control_images/expected_pal_composer/sp_pdf_letter_spacing/sp_pdf_letter_spacing.png
new file mode 100644
index 0000000..dbb4c60
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_pdf_letter_spacing/sp_pdf_letter_spacing.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_pdf_letter_spacing/sp_pdf_letter_spacing_mask.png b/tests/testdata/control_images/expected_pal_composer/sp_pdf_letter_spacing/sp_pdf_letter_spacing_mask.png
new file mode 100644
index 0000000..71ba79b
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_pdf_letter_spacing/sp_pdf_letter_spacing_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_pdf_word_spacing/sp_pdf_word_spacing.png b/tests/testdata/control_images/expected_pal_composer/sp_pdf_word_spacing/sp_pdf_word_spacing.png
new file mode 100644
index 0000000..6506a8e
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_pdf_word_spacing/sp_pdf_word_spacing.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_pdf_word_spacing/sp_pdf_word_spacing_mask.png b/tests/testdata/control_images/expected_pal_composer/sp_pdf_word_spacing/sp_pdf_word_spacing_mask.png
new file mode 100644
index 0000000..2f22903
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_pdf_word_spacing/sp_pdf_word_spacing_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_svg_letter_spacing/sp_svg_letter_spacing.png b/tests/testdata/control_images/expected_pal_composer/sp_svg_letter_spacing/sp_svg_letter_spacing.png
new file mode 100644
index 0000000..13ae419
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_svg_letter_spacing/sp_svg_letter_spacing.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_svg_letter_spacing/sp_svg_letter_spacing_mask.png b/tests/testdata/control_images/expected_pal_composer/sp_svg_letter_spacing/sp_svg_letter_spacing_mask.png
new file mode 100644
index 0000000..7563c4e
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_svg_letter_spacing/sp_svg_letter_spacing_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_svg_word_spacing/sp_svg_word_spacing.png b/tests/testdata/control_images/expected_pal_composer/sp_svg_word_spacing/sp_svg_word_spacing.png
new file mode 100644
index 0000000..47924d7
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_svg_word_spacing/sp_svg_word_spacing.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer/sp_svg_word_spacing/sp_svg_word_spacing_mask.png b/tests/testdata/control_images/expected_pal_composer/sp_svg_word_spacing/sp_svg_word_spacing_mask.png
new file mode 100644
index 0000000..d5e8dbc
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_composer/sp_svg_word_spacing/sp_svg_word_spacing_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_above/sp_img_curved_placement_above.png b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_above/sp_img_curved_placement_above.png
index 2195736..08aecbf 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_above/sp_img_curved_placement_above.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_above/sp_img_curved_placement_above.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_above/sp_img_curved_placement_above_mask.png b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_above/sp_img_curved_placement_above_mask.png
index a9de5f9..6d4e6e1 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_above/sp_img_curved_placement_above_mask.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_above/sp_img_curved_placement_above_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_below/sp_img_curved_placement_below.png b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_below/sp_img_curved_placement_below.png
index d46334b..c036e7ec 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_below/sp_img_curved_placement_below.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_below/sp_img_curved_placement_below.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_below/sp_img_curved_placement_below_mask.png b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_below/sp_img_curved_placement_below_mask.png
index 4c7696b..4579f23 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_below/sp_img_curved_placement_below_mask.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_below/sp_img_curved_placement_below_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_online/sp_img_curved_placement_online.png b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_online/sp_img_curved_placement_online.png
index 603b97d..a60b0bb 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_online/sp_img_curved_placement_online.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_online/sp_img_curved_placement_online.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_online/sp_img_curved_placement_online_mask.png b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_online/sp_img_curved_placement_online_mask.png
index afebd87..8b38ff0 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_online/sp_img_curved_placement_online_mask.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_img_curved_placement_online/sp_img_curved_placement_online_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_above/sp_pdf_curved_placement_above.png b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_above/sp_pdf_curved_placement_above.png
index 41d79d1..fd53058 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_above/sp_pdf_curved_placement_above.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_above/sp_pdf_curved_placement_above.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_above/sp_pdf_curved_placement_above_mask.png b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_above/sp_pdf_curved_placement_above_mask.png
index f940725..d2bd597 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_above/sp_pdf_curved_placement_above_mask.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_above/sp_pdf_curved_placement_above_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_below/sp_pdf_curved_placement_below.png b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_below/sp_pdf_curved_placement_below.png
index 67d8198..383d378 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_below/sp_pdf_curved_placement_below.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_below/sp_pdf_curved_placement_below.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_below/sp_pdf_curved_placement_below_mask.png b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_below/sp_pdf_curved_placement_below_mask.png
index 1edbc10..5a3a681 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_below/sp_pdf_curved_placement_below_mask.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_below/sp_pdf_curved_placement_below_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_online/sp_pdf_curved_placement_online.png b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_online/sp_pdf_curved_placement_online.png
index 97537ec..9c44172 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_online/sp_pdf_curved_placement_online.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_online/sp_pdf_curved_placement_online.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_online/sp_pdf_curved_placement_online_mask.png b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_online/sp_pdf_curved_placement_online_mask.png
index 6ae7594..13363c2 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_online/sp_pdf_curved_placement_online_mask.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_pdf_curved_placement_online/sp_pdf_curved_placement_online_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_above/sp_svg_curved_placement_above.png b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_above/sp_svg_curved_placement_above.png
index a9742f3..0360eb4 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_above/sp_svg_curved_placement_above.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_above/sp_svg_curved_placement_above.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_above/sp_svg_curved_placement_above_mask.png b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_above/sp_svg_curved_placement_above_mask.png
index 0dc5613..7daa9d8 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_above/sp_svg_curved_placement_above_mask.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_above/sp_svg_curved_placement_above_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_below/sp_svg_curved_placement_below.png b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_below/sp_svg_curved_placement_below.png
index 2a7f371..2d3db48 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_below/sp_svg_curved_placement_below.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_below/sp_svg_curved_placement_below.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_below/sp_svg_curved_placement_below_mask.png b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_below/sp_svg_curved_placement_below_mask.png
index 14523ec..6821d03 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_below/sp_svg_curved_placement_below_mask.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_below/sp_svg_curved_placement_below_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_online/sp_svg_curved_placement_online.png b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_online/sp_svg_curved_placement_online.png
index 4117405..693293b 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_online/sp_svg_curved_placement_online.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_online/sp_svg_curved_placement_online.png differ
diff --git a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_online/sp_svg_curved_placement_online_mask.png b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_online/sp_svg_curved_placement_online_mask.png
index 76c53bd..3511a94 100644
Binary files a/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_online/sp_svg_curved_placement_online_mask.png and b/tests/testdata/control_images/expected_pal_composer_line/sp_svg_curved_placement_online/sp_svg_curved_placement_online_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_server/sp_letter_spacing/sp_letter_spacing.png b/tests/testdata/control_images/expected_pal_server/sp_letter_spacing/sp_letter_spacing.png
new file mode 100644
index 0000000..9350bcd
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_server/sp_letter_spacing/sp_letter_spacing.png differ
diff --git a/tests/testdata/control_images/expected_pal_server/sp_letter_spacing/sp_letter_spacing_mask.png b/tests/testdata/control_images/expected_pal_server/sp_letter_spacing/sp_letter_spacing_mask.png
new file mode 100644
index 0000000..300ec88
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_server/sp_letter_spacing/sp_letter_spacing_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_server/sp_word_spacing/sp_word_spacing.png b/tests/testdata/control_images/expected_pal_server/sp_word_spacing/sp_word_spacing.png
new file mode 100644
index 0000000..a184716
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_server/sp_word_spacing/sp_word_spacing.png differ
diff --git a/tests/testdata/control_images/expected_pal_server/sp_word_spacing/sp_word_spacing_mask.png b/tests/testdata/control_images/expected_pal_server/sp_word_spacing/sp_word_spacing_mask.png
new file mode 100644
index 0000000..091ec72
Binary files /dev/null and b/tests/testdata/control_images/expected_pal_server/sp_word_spacing/sp_word_spacing_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_above/sp_curved_placement_above.png b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_above/sp_curved_placement_above.png
index 2195736..b3dd3e2 100644
Binary files a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_above/sp_curved_placement_above.png and b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_above/sp_curved_placement_above.png differ
diff --git a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_above/sp_curved_placement_above_mask.png b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_above/sp_curved_placement_above_mask.png
index 31c3799..8382fe3 100644
Binary files a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_above/sp_curved_placement_above_mask.png and b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_above/sp_curved_placement_above_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_below/sp_curved_placement_below.png b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_below/sp_curved_placement_below.png
index d46334b..abf8a8d 100644
Binary files a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_below/sp_curved_placement_below.png and b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_below/sp_curved_placement_below.png differ
diff --git a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_below/sp_curved_placement_below_mask.png b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_below/sp_curved_placement_below_mask.png
index 730de01..d3eaebd 100644
Binary files a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_below/sp_curved_placement_below_mask.png and b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_below/sp_curved_placement_below_mask.png differ
diff --git a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_online/sp_curved_placement_online.png b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_online/sp_curved_placement_online.png
index 603b97d..785ae18 100644
Binary files a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_online/sp_curved_placement_online.png and b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_online/sp_curved_placement_online.png differ
diff --git a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_online/sp_curved_placement_online_mask.png b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_online/sp_curved_placement_online_mask.png
index 89dbf8b..726c704 100644
Binary files a/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_online/sp_curved_placement_online_mask.png and b/tests/testdata/control_images/expected_pal_server_line/sp_curved_placement_online/sp_curved_placement_online_mask.png differ
diff --git a/tests/testdata/provider/GetCapabilities.xml b/tests/testdata/provider/GetCapabilities.xml
new file mode 100644
index 0000000..ed593d8
--- /dev/null
+++ b/tests/testdata/provider/GetCapabilities.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<WMS_Capabilities version="1.3.0" xmlns="http://www.opengis.net/wms" xmlns:sld="http://www.opengis.net/sld" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ms="http://mapserver.gis.umn.edu/mapserver" xsi:schemaLocation="http://www.opengis.net/wms http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xsd  http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/sld_capabilities.xsd  http://mapserver.gis.umn.edu/mapserver http://localhost:8380/mapserv?service=WMS&v [...]
+  <!-- MapServer version 7.0.1 OUTPUT=PNG OUTPUT=JPEG OUTPUT=KML SUPPORTS=PROJ SUPPORTS=AGG SUPPORTS=FREETYPE SUPPORTS=CAIRO SUPPORTS=SVG_SYMBOLS SUPPORTS=RSVG SUPPORTS=ICONV SUPPORTS=FRIBIDI SUPPORTS=WMS_SERVER SUPPORTS=WMS_CLIENT SUPPORTS=WFS_SERVER SUPPORTS=WFS_CLIENT SUPPORTS=WCS_SERVER SUPPORTS=SOS_SERVER SUPPORTS=FASTCGI SUPPORTS=THREADS SUPPORTS=GEOS INPUT=JPEG INPUT=POSTGIS INPUT=OGR INPUT=GDAL INPUT=SHAPEFILE -->
+  <Service>
+    <Name>WMS</Name>
+    <Title>Test</Title>
+    <Abstract>Test</Abstract>
+    <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/>
+    <ContactInformation>
+    </ContactInformation>
+    <MaxWidth>5000</MaxWidth>
+    <MaxHeight>5000</MaxHeight>
+  </Service>
+
+  <Capability>
+    <Request>
+      <GetCapabilities>
+        <Format>text/xml</Format>
+        <DCPType>
+          <HTTP>
+            <Get><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Get>
+            <Post><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Post>
+          </HTTP>
+        </DCPType>
+      </GetCapabilities>
+      <GetMap>
+        <Format>image/jpeg</Format>
+        <Format>image/png</Format>
+        <DCPType>
+          <HTTP>
+            <Get><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Get>
+            <Post><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Post>
+          </HTTP>
+        </DCPType>
+      </GetMap>
+      <GetFeatureInfo>
+        <Format>text/plain</Format>
+        <Format>application/vnd.ogc.gml</Format>
+        <DCPType>
+          <HTTP>
+            <Get><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Get>
+            <Post><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Post>
+          </HTTP>
+        </DCPType>
+      </GetFeatureInfo>
+      <sld:DescribeLayer>
+        <Format>text/xml</Format>
+        <DCPType>
+          <HTTP>
+            <Get><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Get>
+            <Post><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Post>
+          </HTTP>
+        </DCPType>
+      </sld:DescribeLayer>
+      <sld:GetLegendGraphic>
+        <Format>image/jpeg</Format>
+        <Format>image/png</Format>
+        <Format>image/png; mode=8bit</Format>
+        <DCPType>
+          <HTTP>
+            <Get><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Get>
+            <Post><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Post>
+          </HTTP>
+        </DCPType>
+      </sld:GetLegendGraphic>
+      <ms:GetStyles>
+        <Format>text/xml</Format>
+        <DCPType>
+          <HTTP>
+            <Get><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Get>
+            <Post><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://localhost:8380/mapserv?"/></Post>
+          </HTTP>
+        </DCPType>
+      </ms:GetStyles>
+    </Request>
+    <Exception>
+      <Format>XML</Format>
+      <Format>BLANK</Format>
+    </Exception>
+    <sld:UserDefinedSymbolization SupportSLD="1" UserLayer="0" UserStyle="1" RemoteWFS="0" InlineFeature="0" RemoteWCS="0"/>
+    <Layer>
+      <Name>test</Name>
+      <Title>Test</Title>
+      <Abstract>Test</Abstract>
+      <CRS>EPSG:2056</CRS>
+      <EX_GeographicBoundingBox>
+          <westBoundLongitude>5.01393</westBoundLongitude>
+          <eastBoundLongitude>11.4774</eastBoundLongitude>
+          <southBoundLatitude>45.356</southBoundLatitude>
+          <northBoundLatitude>48.3001</northBoundLatitude>
+      </EX_GeographicBoundingBox>
+      <BoundingBox CRS="EPSG:2056" minx="2.42e+06" miny="1.03e+06" maxx="2.9e+06" maxy="1.35e+06"/>
+      <Layer queryable="1" opaque="0" cascaded="0">
+          <Name>agri_zones</Name>
+          <Title>agri_zones</Title>
+          <CRS>EPSG:2056</CRS>
+          <EX_GeographicBoundingBox>
+              <westBoundLongitude>5.01393</westBoundLongitude>
+              <eastBoundLongitude>11.4774</eastBoundLongitude>
+              <southBoundLatitude>45.356</southBoundLatitude>
+              <northBoundLatitude>48.3001</northBoundLatitude>
+          </EX_GeographicBoundingBox>
+          <BoundingBox CRS="EPSG:2056" minx="2.42e+06" miny="1.03e+06" maxx="2.9e+06" maxy="1.35e+06"/>
+          <MetadataURL type="TC211">
+            <Format>text/html</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.example.com/bar"/>
+          </MetadataURL>
+          <Style>
+            <Name>yt_style</Name>
+            <Title>yt_style</Title>
+            <LegendURL width="23" height="19">
+              <Format>image/png</Format>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.example.com/yt.png"/>
+            </LegendURL>
+          </Style>
+          <Style>
+            <Name>fb_style</Name>
+            <Title>fb_style</Title>
+            <LegendURL width="23" height="19">
+              <Format>image/png</Format>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.example.com/fb.png"/>
+            </LegendURL>
+          </Style>
+      </Layer>
+      <Layer>
+        <Name>cadastre</Name>
+        <Title>cadastre</Title>
+        <Abstract>cadastre</Abstract>
+        <Style>
+          <Name>default</Name>
+          <Title>default</Title>
+          <LegendURL width="88" height="50">
+              <Format>image/png</Format>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8380/mapserv?version=1.3.0&service=WMS&request=GetLegendGraphic&sld_version=1.1.0&layer=cadastre&format=image/png&STYLE=default"/>
+          </LegendURL>
+        </Style>
+        <Layer queryable="1" opaque="0" cascaded="0">
+          <Name>buildings</Name>
+          <Title>buildings</Title>
+          <CRS>EPSG:2056</CRS>
+          <EX_GeographicBoundingBox>
+              <westBoundLongitude>5.01393</westBoundLongitude>
+              <eastBoundLongitude>11.4774</eastBoundLongitude>
+              <southBoundLatitude>45.356</southBoundLatitude>
+              <northBoundLatitude>48.3001</northBoundLatitude>
+          </EX_GeographicBoundingBox>
+          <BoundingBox CRS="EPSG:2056" minx="2.42e+06" miny="1.03e+06" maxx="2.9e+06" maxy="1.35e+06"/>
+          <MetadataURL type="TC211">
+            <Format>text/html</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.example.com/bar"/>
+          </MetadataURL>
+          <Style>
+            <Name>default</Name>
+            <Title>default</Title>
+            <LegendURL width="88" height="20">
+              <Format>image/png</Format>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.example.com/buildings.png"/>
+            </LegendURL>
+          </Style>
+        </Layer>
+        <Layer queryable="1" opaque="0" cascaded="0">
+          <Name>land_surveing_parcels</Name>
+          <Title>land_surveing_parcels</Title>
+          <CRS>EPSG:2056</CRS>
+          <EX_GeographicBoundingBox>
+              <westBoundLongitude>5.01393</westBoundLongitude>
+              <eastBoundLongitude>11.4774</eastBoundLongitude>
+              <southBoundLatitude>45.356</southBoundLatitude>
+              <northBoundLatitude>48.3001</northBoundLatitude>
+          </EX_GeographicBoundingBox>
+          <BoundingBox CRS="EPSG:2056" minx="2.42e+06" miny="1.03e+06" maxx="2.9e+06" maxy="1.35e+06"/>
+          <MetadataURL type="TC211">
+            <Format>text/html</Format>
+            <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.example.com/bar"/>
+          </MetadataURL>
+          <Style>
+            <Name>default</Name>
+            <Title>default</Title>
+            <LegendURL width="84" height="20">
+              <Format>image/png</Format>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8380/mapserv?version=1.3.0&service=WMS&request=GetLegendGraphic&sld_version=1.1.0&layer=land_surveing_parcels&format=image/png&STYLE=default"/>
+            </LegendURL>
+          </Style>
+        </Layer>
+      </Layer>
+    </Layer>
+  </Capability>
+</WMS_Capabilities>
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/qgis.git



More information about the Pkg-grass-devel mailing list