Fix MoveAndRotate class to handle quick changes in movement and rotational velocity by forcing it to reevaluate velocity immediately when orientation velocity changes. This fixes the bug where release of the rotation key quickly followed by release of the movement key could lead to slow drifting.

/*  Sirikata
 *  movement.em
 *
 *  Copyright (c) 2011, Ewen Cheslack-Postava
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are
 *  met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of Sirikata nor the names of its contributors may
 *    be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

system.require('movement.em');

/** MoveAndRotate gives you intuitive control over moving and
 *  rotating. The most common use for this is when you want to combine
 *  movement and rotation simultaneously, but prefer to specify
 *  velocity post-rotation. For example, if you want to specify that
 *  an object continues to move 'forward' by specifying a single
 *  velocity, but also have the object continue even as rotation
 *  occurs.
 */
std.movement.MoveAndRotate = system.Class.extend(
    {
        UPDATE_TYPE_MOVEMENT: 1,
        UPDATE_TYPE_ROTATION: 2,
        UPDATE_TYPE_ALL: 3,

        /** Initialize the controller with a presence. The optional parameter
         *  update_cb is a function which is invoked periodically when the
         *  position or orientation are updated. This is useful if you derive
         *  some other information (e.g. camera position) from this state. The
         *  final parameter controls what kind of updates you get callbacks for:
         *  'all', 'rotation', or 'movement'. It defaults to 'all'.
         */
        init: function(pres, update_cb, update_type) {
            this._pres = pres;
            this._updatecb = update_cb; // May be undefined
            if (update_type == 'movement')
                this._update_type = this.UPDATE_TYPE_MOVEMENT;
            else if (update_type == 'rotation')
                this._update_type = this.UPDATE_TYPE_ROTATION;
            else
                this._update_type = this.UPDATE_TYPE_ALL;

            this._localVel = pres.velocity;
            this._localOrientVel = pres.orientationVel;

            this._moving = this.moving();
            this._rotating = this.rotating();
        },
        moving: function() {
            return this._pres.velocity.lengthSquared() > 1e-08;
        },
        rotating: function() {
            return !this._pres.orientationVel.isZero();
        },
        move: function(vel, scaling) {
            var scaled_vel = vel.mul(scaling);
            this._localVel = this._localVel.add(scaled_vel);
            this._pres.velocity = this._pres.velocity.add(
                this._pres.orientation.mul(scaled_vel)
            );
            this._startReeval(true);
        },
        rotate: function(axis, angle) {
            var newVel = this._localOrientVel.mul(
                new util.Quaternion(axis, angle)
            );
            this._pres.orientationVel = newVel;
            this._localOrientVel = newVel;
            this._startReeval(true);
        },
        _startReeval: function(is_first) {
            this._moving = this.moving();
            this._rotating = this.rotating();

            // Usually people expect that holding down forward and left/right
            // will result in moving in a curve, i.e. that the velocity is in
            // local coordinates and gets updated automatically with rotation.
            // Further, we also provide callbacks if requested, so we may need
            // to setup timeouts for those, even if we don't need to update the
            // info.

            // These various conditions mean we can trigger reevaluation due to
            // a number of conditions.
            if ((this._update_type == this.UPDATE_TYPE_ALL && (this._moving || this._rotating)) ||
                (this._update_type == this.UPDATE_TYPE_MOVEMENT && this._moving) ||
                (this._update_type == this.UPDATE_TYPE_ROTATION && this._rotating) ||
                (this._moving && this._rotating || is_first) // Real reeval
               ) {
                   // is_first tracks whether this is the first
                   // request for reeval. We do this specially to
                   // force reevaluation immediately upon setting
                   // values. We need to do this because when the
                   // orientation changes we want to make sure the
                   // velocity also gets the corresponding
                   // update. Otherwise, the deltas used can become
                   // incorrect so a sequence like [ move forward,
                   // rotate right, rotate_left, move backward ],
                   // which should have all elements cancel out, can
                   // end up with a small velocity because the move
                   // backwards is computed using the right local
                   // orientation but the presence's velocity might
                   // not have been updated yet (still waiting for
                   // first timeout).
                   if (is_first)
                       this._reeval(is_first);
                   else
                       system.timeout(.05, std.core.bind(this._reeval, this));
               }
        },
        _reeval: function(is_first) {
            // Only perform reeval if we really need it
            if (this._moving && this._rotating || is_first)
                this._pres.velocity = this._pres.orientation.mul(this._localVel);

            // Then do callbacks
            if (this._updatecb &&
                (this._update_type == this.UPDATE_TYPE_ALL ||
                 (this._update_type == this.UPDATE_TYPE_MOVEMENT && this._moving) ||
                 (this._update_type == this.UPDATE_TYPE_ROTATION && this._rotating))) {
                this._updatecb();
            }

            // Loop, if necessary.
            this._startReeval();
        }
    }
);

/*  Sirikata
 *  movement.em
 *
 *  Copyright (c) 2011, Ewen Cheslack-Postava
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are
 *  met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of Sirikata nor the names of its contributors may
 *    be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

system.require('movement.em');

/** MoveAndRotate gives you intuitive control over moving and
 *  rotating. The most common use for this is when you want to combine
 *  movement and rotation simultaneously, but prefer to specify
 *  velocity post-rotation. For example, if you want to specify that
 *  an object continues to move 'forward' by specifying a single
 *  velocity, but also have the object continue even as rotation
 *  occurs.
 */
std.movement.MoveAndRotate = system.Class.extend(
    {
        UPDATE_TYPE_MOVEMENT: 1,
        UPDATE_TYPE_ROTATION: 2,
        UPDATE_TYPE_ALL: 3,

        /** Initialize the controller with a presence. The optional parameter
         *  update_cb is a function which is invoked periodically when the
         *  position or orientation are updated. This is useful if you derive
         *  some other information (e.g. camera position) from this state. The
         *  final parameter controls what kind of updates you get callbacks for:
         *  'all', 'rotation', or 'movement'. It defaults to 'all'.
         */
        init: function(pres, update_cb, update_type) {
            this._pres = pres;
            this._updatecb = update_cb; // May be undefined
            if (update_type == 'movement')
                this._update_type = this.UPDATE_TYPE_MOVEMENT;
            else if (update_type == 'rotation')
                this._update_type = this.UPDATE_TYPE_ROTATION;
            else
                this._update_type = this.UPDATE_TYPE_ALL;

            this._localVel = pres.velocity;
            this._localOrientVel = pres.orientationVel;

            this._moving = this.moving();
            this._rotating = this.rotating();
        },
        moving: function() {
            return this._pres.velocity.lengthSquared() > 1e-08;
        },
        rotating: function() {
            return !this._pres.orientationVel.isZero();
        },
        move: function(vel, scaling) {
            var scaled_vel = vel.mul(scaling);
            this._localVel = this._localVel.add(scaled_vel);
            this._pres.velocity = this._pres.velocity.add(
                this._pres.orientation.mul(scaled_vel)
            );
            this._startReeval();
        },
        rotate: function(axis, angle) {
            var newVel = this._localOrientVel.mul(
                new util.Quaternion(axis, angle)
            );
            this._pres.orientationVel = newVel;
            this._localOrientVel = newVel;
            this._startReeval();
        },
        _startReeval: function() {
            this._moving = this.moving();
            this._rotating = this.rotating();

            // Usually people expect that holding down forward and left/right
            // will result in moving in a curve, i.e. that the velocity is in
            // local coordinates and gets updated automatically with rotation.
            // Further, we also provide callbacks if requested, so we may need
            // to setup timeouts for those, even if we don't need to update the
            // info.

            // These various conditions mean we can trigger reevaluation due to
            // a number of conditions.
            if ((this._update_type == this.UPDATE_TYPE_ALL && (this._moving || this._rotating)) ||
                (this._update_type == this.UPDATE_TYPE_MOVEMENT && this._moving) ||
                (this._update_type == this.UPDATE_TYPE_ROTATION && this._rotating) ||
                (this._moving && this._rotating) // Real reeval
               )
                system.timeout(.05, std.core.bind(this._reeval, this));
        },
        _reeval: function() {
            // Only perform reeval if we really need it
            if (this._moving && this._rotating)
                this._pres.velocity = this._pres.orientation.mul(this._localVel);

            // Then do callbacks
            if (this._updatecb &&
                (this._update_type == this.UPDATE_TYPE_ALL ||
                 (this._update_type == this.UPDATE_TYPE_MOVEMENT && this._moving) ||
                 (this._update_type == this.UPDATE_TYPE_ROTATION && this._rotating))) {
                this._updatecb();
            }

            // Loop, if necessary.
            this._startReeval();
        }
    }
);


Rewrote the setup.bat.em template so that it work with the new catkin stuff.

@@echo off
REM generated from catkin/cmake/em/setup.bat.em

REM remember type of shell if not already set
if [%CATKIN_SHELL%]==[] (
  set CATKIN_SHELL=bat
)

REM called without arguments: create a clean environment, resetting all already set variables
REM   - removes all prepended items from environment variables (based on current CATKIN_WORKSPACES)
REM   - sets CATKIN_WORKSPACES to current + list of parents
REM   - calls setup from all parent workspaces with "--recursive" argument
REM   - prepends current to all variables
REM   - sources current environment hooks
REM called with "--recursive": used in recursive call from other setup
REM   - does not touch CATKIN_WORKSPACES
REM   - prepends current to all variables
REM   - sources current environment hooks
REM called with "--extend": used to extend one environment with a second one
REM   - prepends current + list of parent to existing CATKIN_WORKSPACES
REM   - calls setup from all parent workspaces with "--recursive" argument
REM   - prepends current to all variables
REM   - sources current environment hooks

REM bin folder of workspace prepended to PATH
set PATH_dir=/bin
REM lib/pythonX.Y/..-packages folder of workspace prepended to PYTHONPATH
REM the path suffix is calculated in catkin/cmake/python.cmake
set PYTHONPATH_dir=/@PYTHON_INSTALL_DIR
REM include folder of workspace prepended to CPATH
set CPATH_dir=/include
REM lib folder of workspace prepended to (DY)LD_LIBRARY_PATH
set LD_LIBRARY_PATH_dir=/lib
REM folder of workspace prepended to CMAKE_PREFIX_PATH
set CMAKE_PREFIX_PATH_dir=""
REM lib/pkgconfig folder of workspace prepended to PKG_CONFIG_PATH
set PKG_CONFIG_PATH_dir=/lib/pkgconfig
REM set the DIRNAME
for %%F in (%0) do set DIRNAME=%%~dpF
REM set the python executable
@{import sys}
set python=@(sys.executable)

REM decide which mode is requested
set RECURSIVE_MODE=0
set EXTEND_MODE=0
set NORMAL_MODE=0
if [%_CATKIN_SETUP_RECURSION%] == [1] (
  set RECURSIVE_MODE=1
) else (
  if [%1] == ["--extend"] (
    set EXTEND_MODE=1
  ) else (
    set NORMAL_MODE=1
  )
)

REM reset environment variables by unrolling modifications based on CATKIN_WORKSPACES (when called without arguments)
if [%NORMAL_MODE%] == [1] (
  for /f %%a in ('%python% %DIRNAME%\setup.py --remove --name PATH --value "%PATH_dir%"') do set PATH=%%a
  for /f %%a in ('%python% %DIRNAME%\setup.py --remove --name PYTHONPATH --value "%PYTHONPATH_dir%"') do set PYTHONPATH=%%a
  for /f %%a in ('%python% %DIRNAME%\setup.py --remove --name CPATH --value "%CPATH_dir%"') do set CPATH=%%a
  for /f %%a in ('%python% %DIRNAME%\setup.py --remove --name LD_LIBRARY_PATH --value "%LD_LIBRARY_PATH_dir%"') do set LD_LIBRARY_PATH=%%a
  for /f %%a in ('%python% %DIRNAME%\setup.py --remove --name CMAKE_PREFIX_PATH --value "%CMAKE_PREFIX_PATH_dir%"') do set CMAKE_PREFIX_PATH=%%a
  for /f %%a in ('%python% %DIRNAME%\setup.py --remove --name PKG_CONFIG_PATH --value "%PKG_CONFIG_PATH_dir%"') do set PKG_CONFIG_PATH=%%a
)

REM set CATKIN_WORKSPACES (when called without arguments)
if [%NORMAL_MODE%] == [1] (
  for /f %%a in ('%python% %DIRNAME%\setup.py --set-workspaces --value "@CURRENT_WORKSPACE;@CATKIN_WORKSPACES"') do set CATKIN_WORKSPACES=%%a
)

REM prepend current and parent workspaces to CATKIN_WORKSPACES (when called with argument --extend)
if [%EXTEND_MODE%] == [1] (
  for /f %%a in ('%python% %DIRNAME%\setup.py --set-workspaces --value "@CURRENT_WORKSPACE;@CATKIN_WORKSPACES"') do set CATKIN_WORKSPACES=%%a%CATKIN_WORKSPACES%
)

REM source setup.SHELL from parent workspaces (if any) (when called without --recursive argument)
@[if CATKIN_WORKSPACES]@
if [%EXTEND_MODE%] == [1] (
  set _CATKIN_SETUP_RECURSION=1
  for %%a in (@(' '.join(['"%s"' % ws.split(':')[0] for ws in reversed(CATKIN_WORKSPACES.split(';'))]))) do call %%a\setup.bat --recursive
  set _CATKIN_SETUP_RECURSION=0
)
@[end if]@

REM define base dir of workspace (based on CURRENT_WORKSPACE) (after sourcing the parents)
set BASE_DIR="@(CURRENT_WORKSPACE.split(':')[1].split(':')[0])"

REM prepend folders of workspace to environment variables
for /f %%a in ('%python% %DIRNAME%\setup.py --prefix --name PATH --value %BASE_DIR%%PATH_dir%') do set PATH=%%a%PATH%
for /f %%a in ('%python% %DIRNAME%\setup.py --prefix --name PYTHONPATH --value %BASE_DIR%%PYTHONPATH_dir%') do set PYTHONPATH=%%a%PYTHONPATH%
for /f %%a in ('%python% %DIRNAME%\setup.py --prefix --name CPATH --value %BASE_DIR%%CPATH_dir%') do set CPATH=%%a%CPATH%
REM for /f %%a in ('%python% %DIRNAME%\setup.py --prefix --name LD_LIBRARY_PATH --value %BASE_DIR%%LD_LIBRARY_PATH_dir%) do set LD_LIBRARY_PATH=%%a%LD_LIBRARY_PATH%
REM for /f %%a in ('%python% %DIRNAME%\setup.py --prefix --name CMAKE_PREFIX_PATH --value %BASE_DIR%%CMAKE_PREFIX_PATH_dir%) do set CMAKE_PREFIX_PATH=%%a%CMAKE_PREFIX_PATH%
REM for /f %%a in ('%python% %DIRNAME%\setup.py --prefix --name PKG_CONFIG_PATH --value %BASE_DIR%%PKG_CONFIG_PATH_dir%) do set PKG_CONFIG_PATH=%%a%PKG_CONFIG_PATH%

REM run all environment hooks of this workspace
for /r %%a in (%BASE_DIR%\etc\catkin\profile.d\*.bat) do call "%%a"

@@echo off
REM generated from catkin/cmake/em/setup.bat.em

REM remember type of shell if not already set
if [%CATKIN_SHELL%]==[] (
  set CATKIN_SHELL=bat
)

REM source setup.SHELL from parent workspace (if any)
@{
import os
parent_path = None
if 'CATKIN_WORKSPACES' in os.environ and os.environ['CATKIN_WORKSPACES'] != '':
    parent_path = os.environ['CATKIN_WORKSPACES'].split(';')[0].split(':')[0]
    print('. %s/setup.$CATKIN_SHELL' % parent_path)
}@

REM python function to generate ROS package path based on all parent workspaces (prepends the separator if necessary)
REM do not use EnableDelayedExpansion here, it messes with the != symbols
echo from __future__ import print_function > catkin_prefix.py
echo import os >> catkin_prefix.py
echo import sys >> catkin_prefix.py
echo if len(sys.argv) != 4: >> catkin_prefix.py
echo     print("\nWrong number of arguments passed to Python code\n", file=sys.stderr) >> catkin_prefix.py
echo     exit(1) >> catkin_prefix.py
echo prepended_item, separator, env_name = sys.argv[1:] >> catkin_prefix.py
echo items = os.environ[env_name].split(separator) if env_name in os.environ and os.environ[env_name] != '' else [] >> catkin_prefix.py
echo print(prepended_item + (separator if items else '') if prepended_item not in items else '') >> catkin_prefix.py

setlocal EnableDelayedExpansion

REM prepend current workspace to CATKIN_WORKSPACES
set CATKIN_WORKSPACES_PREFIX=
for /f %%a in ('python catkin_prefix.py "@CURRENT_WORKSPACE" ";" "CATKIN_WORKSPACES"') do set CATKIN_WORKSPACES_PREFIX=!CATKIN_WORKSPACES_PREFIX!%%a
set CATKIN_WORKSPACES=%CATKIN_WORKSPACES_PREFIX%%CATKIN_WORKSPACES%

REM define base dir for further variables (based on current workspace)
set BASE_DIR=@(CURRENT_WORKSPACE.split(';')[0])

REM prepend bin folder of workspace to PATH
set PATH_PREFIX=
for /f %%a in ('python catkin_prefix.py "%BASE_DIR%/bin" ";" "PATH"') do set PATH_PREFIX=!PATH_PREFIX!%%a
set PATH=%PATH_PREFIX%%PATH%

REM prepend lib/pythonX.Y/..-packages folder of workspace to PYTHONPATH
REM the path suffix is calculated in catkin/cmake/python.cmake
set PYTHONPATH_PREFIX=
for /f %%a in ('python catkin_prefix.py "%BASE_DIR%/@PYTHON_INSTALL_DIR" ";" "PYTHONPATH"') do set PYTHONPATH_PREFIX=!PYTHONPATH_PREFIX!%%a
set PYTHONPATH=%PYTHONPATH_PREFIX%%PYTHONPATH%

REM prepend include folder of workspace to CPATH
set CPATH_PREFIX=
for /f %%a in ('python catkin_prefix.py "%BASE_DIR%/include" ";" "CPATH"') do set CPATH_PREFIX=!CPATH_PREFIX!%%a
set CPATH=%CPATH_PREFIX%%CPATH%

REM prepend lib folder of workspace to LD_LIBRARY_PATH
set LD_LIBRARYPATH_PREFIX=
for /f %%a in ('python catkin_prefix.py "%BASE_DIR%/lib" ";" "LD_LIBRARY_PATH"') do set LD_LIBRARYPATH_PREFIX=!LD_LIBRARYPATH_PREFIX!%%a
set LD_LIBRARY_PATH=%LD_LIBRARYPATH_PREFIX%%LD_LIBRARY_PATH%

REM prepend folder of workspace (cmake-subfolder for build-space) to CMAKE_PREFIX_PATH
set CMAKE_PREFIXPATH_PREFIX=
for /f %%a in ('python catkin_prefix.py "%BASE_DIR%" ";" "CMAKE_PREFIX_PATH"') do set CMAKE_PREFIXPATH_PREFIX=!CMAKE_PREFIXPATH_PREFIX!%%a
set CMAKE_PREFIX_PATH=%CMAKE_PREFIXPATH_PREFIX%%CMAKE_PREFIX_PATH%

REM prepend lib/pkgconfig folder of workspace to PKG_CONFIG_PATH
set PKG_CONFIGPATH_PREFIX=
for /f %%a in ('python catkin_prefix.py "%BASE_DIR%/lib/pkgconfig" ";" "PKG_CONFIG_PATH"') do set PKG_CONFIGPATH_PREFIX=!PKG_CONFIGPATH_PREFIX!%%a
set PKG_CONFIG_PATH=%PKG_CONFIGPATH_PREFIX%%PKG_CONFIG_PATH%

REM run all environment hooks of workspace
for %%f in ("%BASE_DIR%\etc\catkin\profile.d\*.%CATKIN_SHELL%") do (
    call %%f
)

del catkin_prefix.py

endlocal


rename nested loop index (#643)

rename nested loop index (#643)

Signed-off-by: ibnHatab <302d0be123b3d36dd1382cf675cfa71e60998d01@gmail.com>

@#typedefs for arrays need to be defined outside of the struct
@{
from collections import OrderedDict

from rosidl_adapter.msg import get_idl_type
from rosidl_adapter.msg import to_idl_literal
from rosidl_adapter.msg import string_to_idl_string_literal

typedefs = OrderedDict()
def get_idl_type_identifier(idl_type):
    return idl_type.replace('::', '__') \
        .replace('<', '__').replace('>', '') \
        .replace('[', '__').replace(']', '')
}@
@[for field in msg.fields]@
@{
idl_type = get_idl_type(field.type)
}@
@[  if field.type.is_fixed_size_array()]@
@{
idl_base_type = idl_type.split('[', 1)[0]
idl_base_type_identifier = idl_base_type.replace('::', '__')
# only necessary for complex types
if idl_base_type_identifier != idl_base_type:
    if idl_base_type_identifier not in typedefs:
        typedefs[idl_base_type_identifier] = idl_base_type
    else:
        assert typedefs[idl_base_type_identifier] == idl_base_type
idl_type_identifier = get_idl_type_identifier(idl_type) + '[' + str(field.type.array_size) + ']'
if idl_type_identifier not in typedefs:
    typedefs[idl_type_identifier] = idl_base_type_identifier
else:
    assert typedefs[idl_type_identifier] == idl_base_type_identifier
}@
@[  end if]@
@[end for]@
@[for k, v in typedefs.items()]@
    typedef @(v) @(k);
@[end for]@
@[if msg.constants]@
    module @(msg.msg_name)_Constants {
@[  for constant in msg.constants]@
@[    if constant.annotations.get('comment', [])]@
      @@verbatim (language="comment", text=
@[      for i, line in enumerate(constant.annotations['comment'])]@
        @(string_to_idl_string_literal(line))@
@[        if i < len(constant.annotations.get('comment')) - 1]@
 "\n"@
@[        end if]@
@[      end for]@
)
@[    end if]@
      const @(get_idl_type(constant.type)) @(constant.name) = @(to_idl_literal(get_idl_type(constant.type), constant.value));
@[  end for]@
    };
@[end if]@
@#
@[if msg.annotations.get('comment', [])]@
    @@verbatim (language="comment", text=@
@[  for i, line in enumerate(msg.annotations['comment'])]
      @(string_to_idl_string_literal(line))@
@[    if i < len(msg.annotations.get('comment')) - 1]@
 "\n"@
@[    end if]@
@[  end for]@
)
@[end if]@
    struct @(msg.msg_name) {
@[if msg.fields]@
@[  for i, field in enumerate(msg.fields)]@
@[    if i > 0]@

@[    end if]@
@[    if field.annotations.get('comment', [])]@
      @@verbatim (language="comment", text=@
@[      for j, line in enumerate(field.annotations['comment'])]
        @(string_to_idl_string_literal(line))@
@[        if j < len(field.annotations.get('comment')) - 1]@
 "\n"@
@[        end if]@
@[      end for]@
)
@[    end if]@
@[    if field.default_value is not None]@
      @@default (value=@(to_idl_literal(get_idl_type(field.type), field.default_value)))
@[    end if]@
@[    if 'unit' in field.annotations]@
      @@unit (value=@(string_to_idl_string_literal(field.annotations['unit'])))
@[    end if]@
@{
idl_type = get_idl_type(field.type)
}@
@[    if field.type.is_fixed_size_array()]@
@{
idl_type = get_idl_type_identifier(idl_type)
}@
@[    end if]@
      @(idl_type) @(field.name);
@[  end for]@
@[else]@
      uint8 structure_needs_at_least_one_member;
@[end if]@
    };

@#typedefs for arrays need to be defined outside of the struct
@{
from collections import OrderedDict

from rosidl_adapter.msg import get_idl_type
from rosidl_adapter.msg import to_idl_literal
from rosidl_adapter.msg import string_to_idl_string_literal

typedefs = OrderedDict()
def get_idl_type_identifier(idl_type):
    return idl_type.replace('::', '__') \
        .replace('<', '__').replace('>', '') \
        .replace('[', '__').replace(']', '')
}@
@[for field in msg.fields]@
@{
idl_type = get_idl_type(field.type)
}@
@[  if field.type.is_fixed_size_array()]@
@{
idl_base_type = idl_type.split('[', 1)[0]
idl_base_type_identifier = idl_base_type.replace('::', '__')
# only necessary for complex types
if idl_base_type_identifier != idl_base_type:
    if idl_base_type_identifier not in typedefs:
        typedefs[idl_base_type_identifier] = idl_base_type
    else:
        assert typedefs[idl_base_type_identifier] == idl_base_type
idl_type_identifier = get_idl_type_identifier(idl_type) + '[' + str(field.type.array_size) + ']'
if idl_type_identifier not in typedefs:
    typedefs[idl_type_identifier] = idl_base_type_identifier
else:
    assert typedefs[idl_type_identifier] == idl_base_type_identifier
}@
@[  end if]@
@[end for]@
@[for k, v in typedefs.items()]@
    typedef @(v) @(k);
@[end for]@
@[if msg.constants]@
    module @(msg.msg_name)_Constants {
@[  for constant in msg.constants]@
@[    if constant.annotations.get('comment', [])]@
      @@verbatim (language="comment", text=
@[      for i, line in enumerate(constant.annotations['comment'])]@
        @(string_to_idl_string_literal(line))@
@[        if i < len(constant.annotations.get('comment')) - 1]@
 "\n"@
@[        end if]@
@[      end for]@
)
@[    end if]@
      const @(get_idl_type(constant.type)) @(constant.name) = @(to_idl_literal(get_idl_type(constant.type), constant.value));
@[  end for]@
    };
@[end if]@
@#
@[if msg.annotations.get('comment', [])]@
    @@verbatim (language="comment", text=@
@[  for i, line in enumerate(msg.annotations['comment'])]
      @(string_to_idl_string_literal(line))@
@[    if i < len(msg.annotations.get('comment')) - 1]@
 "\n"@
@[    end if]@
@[  end for]@
)
@[end if]@
    struct @(msg.msg_name) {
@[if msg.fields]@
@[  for i, field in enumerate(msg.fields)]@
@[if i > 0]@

@[end if]@
@[    if field.annotations.get('comment', [])]@
      @@verbatim (language="comment", text=@
@[      for i, line in enumerate(field.annotations['comment'])]
        @(string_to_idl_string_literal(line))@
@[        if i < len(field.annotations.get('comment')) - 1]@
 "\n"@
@[        end if]@
@[      end for]@
)
@[    end if]@
@[    if field.default_value is not None]@
      @@default (value=@(to_idl_literal(get_idl_type(field.type), field.default_value)))
@[    end if]@
@[    if 'unit' in field.annotations]@
      @@unit (value=@(string_to_idl_string_literal(field.annotations['unit'])))
@[    end if]@
@{
idl_type = get_idl_type(field.type)
}@
@[    if field.type.is_fixed_size_array()]@
@{
idl_type = get_idl_type_identifier(idl_type)
}@
@[    end if]@
      @(idl_type) @(field.name);
@[  end for]@
@[else]@
      uint8 structure_needs_at_least_one_member;
@[end if]@
    };


fix overmatching of binary job pattern in trigger-broken-with-non-broken-upstream job (fix #49)

<project>
  <actions/>
  <description>Generated at @ESCAPE(now_str) from template '@ESCAPE(template_name)'</description>
@(SNIPPET(
    'log-rotator',
    days_to_keep=365,
    num_to_keep=30,
))@
  <keepDependencies>false</keepDependencies>
  <properties>
@(SNIPPET(
    'property_job-priority',
    priority=40,
))@
@(SNIPPET(
    'property_requeue-job',
))@
  </properties>
@(SNIPPET(
    'scm_null',
))@
  <assignedNode>master</assignedNode>
  <canRoam>false</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers>
@(SNIPPET(
    'trigger_timer',
    spec='1 23 * * *',
))@
  </triggers>
  <concurrentBuild>false</concurrentBuild>
  <builders>
@(SNIPPET(
    'builder_system-groovy',
    command=
"""import java.util.regex.Matcher
import java.util.regex.Pattern

import hudson.model.Cause
import hudson.model.Result

println "Triggering builds for the following jobs:"

def source_prefix = "%s"
def binary_prefix = "%s"

// 1. prefix: e.g. Jsrc
// 2. os_name, os_code_name, arch: e.g. _uT64
// 3. package name: e.g. __roscpp
// 4. os_name, os_code_name: e.g. __ubuntu_trusty
// 5. suffix: __source
pattern_src = Pattern.compile(source_prefix + "_.+__.+__.+__source")
//                            1............    2..3...4...5.......

// 1. prefix including optional build file name: e.g. Jbin_arm
// 2. os_name, os_code_name, arch: e.g. _uT64
// 3. package name: e.g. __roscpp
// 4. os_name, os_code_name, arch: e.g. __ubuntu_trusty_amd64
// 5. suffix: __binary
pattern_bin = Pattern.compile(binary_prefix + "_.+__.+__.+__binary")
//                            1............    2..3...4...5.......

for (p in hudson.model.Hudson.instance.projects) {
    if (!pattern_src.matcher(p.name).matches() && !pattern_bin.matcher(p.name).matches()) continue
    if (p.isDisabled()) continue
    if (p.isInQueue() || p.isBuilding()) continue

    // skip (un)stable jobs
    if (p.getNextBuildNumber() > 1) {
        def lb = p.getLastBuild()
        if (lb) {
            def r = lb.getResult()
            if (r) {
                if (r.isBetterOrEqualTo(Result.UNSTABLE)) continue
            }
        }
    }

    // skip if any upstream job is broken
    any_upstream_broken = false
    for (u in p.getUpstreamProjects()) {
        any_upstream_broken = true
        if (u.getNextBuildNumber() > 1) {
            def lb = u.getLastBuild()
            if (lb) {
                def r = lb.getResult()
                if (r) {
                    if (r.isBetterOrEqualTo(Result.UNSTABLE)) {
                        any_upstream_broken = false
                    }
                }
            }
        }
        if (any_upstream_broken) break
    }
    if (any_upstream_broken) continue

    println p.name

    scheduled = p.scheduleBuild(new Cause.UserIdCause())
    if (!scheduled) {
        println "  FAILED to schedule build"
    }
}
""" % (source_project_name_prefix, binary_project_name_prefix),
    script_file=None,
    classpath='',
))@
  </builders>
  <publishers>
@(SNIPPET(
    'publisher_mailer',
    recipients=recipients,
    dynamic_recipients=[],
    send_to_individuals=False,
))@
  </publishers>
  <buildWrappers>
@(SNIPPET(
    'build-wrapper_timestamper',
))@
  </buildWrappers>
</project>

<project>
  <actions/>
  <description>Generated at @ESCAPE(now_str) from template '@ESCAPE(template_name)'</description>
@(SNIPPET(
    'log-rotator',
    days_to_keep=365,
    num_to_keep=30,
))@
  <keepDependencies>false</keepDependencies>
  <properties>
@(SNIPPET(
    'property_job-priority',
    priority=40,
))@
@(SNIPPET(
    'property_requeue-job',
))@
  </properties>
@(SNIPPET(
    'scm_null',
))@
  <assignedNode>master</assignedNode>
  <canRoam>false</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers>
@(SNIPPET(
    'trigger_timer',
    spec='1 23 * * *',
))@
  </triggers>
  <concurrentBuild>false</concurrentBuild>
  <builders>
@(SNIPPET(
    'builder_system-groovy',
    command=
"""import java.util.regex.Matcher
import java.util.regex.Pattern

import hudson.model.Cause
import hudson.model.Result

println "Triggering builds for the following jobs:"

def source_prefix = "%s"
def binary_prefix = "%s"

// 1. prefix: e.g. Jsrc
// 2. os_name, os_code_name, arch: e.g. _uT64
// 3. package name: e.g. __roscpp
// 4. os_name, os_code_name: e.g. __ubuntu_trusty
// 5. suffix: __source
pattern_src = Pattern.compile(source_prefix + "_.+__.+__.+__source")
//                            1............    2..3...4...5.......

// 1. prefix: e.g. Jbin
// 2. optional build file name: e.g. _arm
// 3. os_name, os_code_name, arch: e.g. _uT64
// 4. package name: e.g. __roscpp
// 5. os_name, os_code_name, arch: e.g. __ubuntu_trusty_amd64
// 6. suffix: __binary
pattern_bin = Pattern.compile(binary_prefix + "(_.+)?_.+__.+__.+__binary")
//                            1............    2.....3..4...5...6.......

for (p in hudson.model.Hudson.instance.projects) {
    if (!pattern_src.matcher(p.name).matches() && !pattern_bin.matcher(p.name).matches()) continue
    if (p.isDisabled()) continue
    if (p.isInQueue() || p.isBuilding()) continue

    // skip (un)stable jobs
    if (p.getNextBuildNumber() > 1) {
        def lb = p.getLastBuild()
        if (lb) {
            def r = lb.getResult()
            if (r) {
                if (r.isBetterOrEqualTo(Result.UNSTABLE)) continue
            }
        }
    }

    // skip if any upstream job is broken
    any_upstream_broken = false
    for (u in p.getUpstreamProjects()) {
        any_upstream_broken = true
        if (u.getNextBuildNumber() > 1) {
            def lb = u.getLastBuild()
            if (lb) {
                def r = lb.getResult()
                if (r) {
                    if (r.isBetterOrEqualTo(Result.UNSTABLE)) {
                        any_upstream_broken = false
                    }
                }
            }
        }
        if (any_upstream_broken) break
    }
    if (any_upstream_broken) continue

    println p.name

    scheduled = p.scheduleBuild(new Cause.UserIdCause())
    if (!scheduled) {
        println "  FAILED to schedule build"
    }
}
""" % (source_project_name_prefix, binary_project_name_prefix),
    script_file=None,
    classpath='',
))@
  </builders>
  <publishers>
@(SNIPPET(
    'publisher_mailer',
    recipients=recipients,
    dynamic_recipients=[],
    send_to_individuals=False,
))@
  </publishers>
  <buildWrappers>
@(SNIPPET(
    'build-wrapper_timestamper',
))@
  </buildWrappers>
</project>


Revert "Work around a broken binutils release in RHEL 8.4" (#928)

This reverts commit e2a135fd62c36d4d05a8b49527c8e7ecb5f6d329.

include('/etc/mock/default.cfg')

# Change the root name since we're modifying the chroot
config_opts['root'] += '-ros-buildfarm'

# Disable mock bootstrapping
config_opts['use_bootstrap'] = False

# Add python3-rpm-macros to resolve %{python3_pkgversion} in rosdep rules
config_opts['chroot_setup_cmd'] += ' python3-rpm-macros'

# Install weak dependencies to get group members
config_opts[f'{config_opts.package_manager}_builddep_opts'] = config_opts.get(f'{config_opts.package_manager}_builddep_opts', []) + ['--setopt=install_weak_deps=True']

@[if env_vars]@
# Set environment vars from the build config
@[for env_key, env_val in env_vars.items()]@
config_opts['environment']['@env_key'] = '@env_val'
@[end for]
@[end if]@
# Make debuginfo/debugsource packages best-effort
config_opts['macros']['%_empty_manifest_terminate_build'] = '%{nil}'
config_opts['macros']['%_missing_build_ids_terminate_build'] = '%{nil}'

# Hack the %{dist} macro to allow release suffixing
config_opts['macros']['%dist'] = '.' + config_opts['dist'] + '%{?dist_suffix}'

# Disable automatic out-of-source CMake builds
config_opts['macros']['%__cmake_in_source_build'] = '1'
config_opts['macros']['%__cmake3_in_source_build'] = '1'

# Required for running mock in Docker
config_opts['use_nspawn'] = False

@[if os_name == 'rhel' and os_code_name == '7']@
# Inject g++ 8 into RHEL 7 builds
config_opts['chroot_setup_cmd'] += ' devtoolset-8-gcc-c++ devtoolset-8-make-nonblocking'
config_opts['macros']['%_buildshell'] = '/usr/bin/scl enable devtoolset-8 -- /bin/sh'

# Disable weak dependencies on RHEL 7 builds
config_opts['macros']['%_without_weak_deps'] = '1'
@[else]@
# Add g++, which is an assumed dependency in ROS
config_opts['chroot_setup_cmd'] += ' gcc-c++ make'
@[end if]@

config_opts[f'{config_opts.package_manager}.conf'] += """
@[for i, url in enumerate(distribution_repository_urls)]@
[ros-buildfarm-@(i)]
name=ROS Buildfarm Repository @(i) - $basearch
baseurl=@(url)
gpgkey=file:///etc/pki/mock/RPM-GPG-KEY-ros-buildfarm-@(i)
repo_gpgcheck=@(1 if i < len(distribution_repository_keys) and distribution_repository_keys[i] else 0)
gpgcheck=0
enabled=1
skip_if_unavailable=False

@[end for]@
@[if target_repository]@
[ros-buildfarm-target-source]
name=ROS Buildfarm Repository Target - SRPMS
baseurl=@(target_repository)
repo_gpgcheck=0
gpgcheck=0
enabled=0
skip_if_unavailable=False

@[end if]@
"""

include('/etc/mock/default.cfg')

# Change the root name since we're modifying the chroot
config_opts['root'] += '-ros-buildfarm'

# Disable mock bootstrapping
config_opts['use_bootstrap'] = False

# Add python3-rpm-macros to resolve %{python3_pkgversion} in rosdep rules
config_opts['chroot_setup_cmd'] += ' python3-rpm-macros'

# Install weak dependencies to get group members
config_opts[f'{config_opts.package_manager}_builddep_opts'] = config_opts.get(f'{config_opts.package_manager}_builddep_opts', []) + ['--setopt=install_weak_deps=True']

@[if env_vars]@
# Set environment vars from the build config
@[for env_key, env_val in env_vars.items()]@
config_opts['environment']['@env_key'] = '@env_val'
@[end for]
@[end if]@
# Make debuginfo/debugsource packages best-effort
config_opts['macros']['%_empty_manifest_terminate_build'] = '%{nil}'
config_opts['macros']['%_missing_build_ids_terminate_build'] = '%{nil}'

# Hack the %{dist} macro to allow release suffixing
config_opts['macros']['%dist'] = '.' + config_opts['dist'] + '%{?dist_suffix}'

# Disable automatic out-of-source CMake builds
config_opts['macros']['%__cmake_in_source_build'] = '1'
config_opts['macros']['%__cmake3_in_source_build'] = '1'

# Required for running mock in Docker
config_opts['use_nspawn'] = False

@[if os_name == 'rhel' and os_code_name == '7']@
# Inject g++ 8 into RHEL 7 builds
config_opts['chroot_setup_cmd'] += ' devtoolset-8-gcc-c++ devtoolset-8-make-nonblocking'
config_opts['macros']['%_buildshell'] = '/usr/bin/scl enable devtoolset-8 -- /bin/sh'

# Disable weak dependencies on RHEL 7 builds
config_opts['macros']['%_without_weak_deps'] = '1'
@[else]@
# Add g++, which is an assumed dependency in ROS
config_opts['chroot_setup_cmd'] += ' gcc-c++ make'
@[end if]@

@[if os_name == 'rhel' and os_code_name == '8']@
# Work around a broken binutils release in RHEL 8.4
config_opts['chroot_setup_cmd'] += ' https://ftp.osuosl.org/pub/ros/download.ros.org/downloads/binutils/binutils-2.30-79.el8.x86_64.rpm'
config_opts[f'{config_opts.package_manager}.conf'] += """
[baseos]
excludepkgs=binutils

"""

@[end if]@
config_opts[f'{config_opts.package_manager}.conf'] += """
@[for i, url in enumerate(distribution_repository_urls)]@
[ros-buildfarm-@(i)]
name=ROS Buildfarm Repository @(i) - $basearch
baseurl=@(url)
gpgkey=file:///etc/pki/mock/RPM-GPG-KEY-ros-buildfarm-@(i)
repo_gpgcheck=@(1 if i < len(distribution_repository_keys) and distribution_repository_keys[i] else 0)
gpgcheck=0
enabled=1
skip_if_unavailable=False

@[end for]@
@[if target_repository]@
[ros-buildfarm-target-source]
name=ROS Buildfarm Repository Target - SRPMS
baseurl=@(target_repository)
repo_gpgcheck=0
gpgcheck=0
enabled=0
skip_if_unavailable=False

@[end if]@
"""


add explicit dependency, remove workaround for wrong python3 package

FROM @os_name:@os_code_name
MAINTAINER @maintainer_name @maintainer_email

VOLUME ["/var/cache/apt/archives"]

ENV DEBIAN_FRONTEND noninteractive

RUN mkdir /tmp/keys
@[for i, key in enumerate(distribution_repository_keys)]@
RUN echo "@('\\n'.join(key.splitlines()))" > /tmp/keys/@(i).key
RUN apt-key add /tmp/keys/@(i).key
@[end for]@
@[for url in distribution_repository_urls]@
RUN echo deb @url @os_code_name main | tee /etc/apt/sources.list.d/buildfarm.list
@[end for]@

RUN apt-get update
RUN apt-get install -q -y python3-apt python3-catkin-pkg python3-empy python3-rosdep

RUN useradd -u @uid -m buildfarm
RUN rosdep init
RUN su buildfarm -c "rosdep --rosdistro=@rosdistro_name update"

CMD ["su", "buildfarm", "-c", "PYTHONPATH=/tmp/ros_buildfarm:$PYTHONPATH python3 -u /tmp/ros_buildfarm/scripts/devel/create_devel_task_generator.py --rosdistro-name @rosdistro_name --workspace-root /tmp/catkin_workspace --os-name @os_name --os-code-name @os_code_name --distribution-repository-urls @(' '.join(distribution_repository_urls)) --distribution-repository-key-files @(' ' .join(['/tmp/keys/%d.key' % i for i in range(len(distribution_repository_keys))])) --dockerfile-dir /tmp/docker_build_and_install && PYTHONPATH=/tmp/ros_buildfarm:$PYTHONPATH python3 -u /tmp/ros_buildfarm/scripts/devel/create_devel_task_generator.py --rosdistro-name @rosdistro_name --workspace-root /tmp/catkin_workspace --os-name @os_name --os-code-name @os_code_name --distribution-repository-urls @(' '.join(distribution_repository_urls)) --distribution-repository-key-files @(' ' .join(['/tmp/keys/%d.key' % i for i in range(len(distribution_repository_keys))])) --testing --dockerfile-dir /tmp/docker_build_and_test"]

FROM @os_name:@os_code_name
MAINTAINER @maintainer_name @maintainer_email

VOLUME ["/var/cache/apt/archives"]

ENV DEBIAN_FRONTEND noninteractive

RUN mkdir /tmp/keys
@[for i, key in enumerate(distribution_repository_keys)]@
RUN echo "@('\\n'.join(key.splitlines()))" > /tmp/keys/@(i).key
RUN apt-key add /tmp/keys/@(i).key
@[end for]@
@[for url in distribution_repository_urls]@
RUN echo deb @url @os_code_name main | tee /etc/apt/sources.list.d/buildfarm.list
@[end for]@

RUN apt-get update
RUN apt-get install -q -y python3-apt python3-empy python3-rosdep

RUN useradd -u @uid -m buildfarm
RUN rosdep init
RUN su buildfarm -c "rosdep --rosdistro=@rosdistro_name update"

CMD ["su", "buildfarm", "-c", "PYTHONPATH=/tmp/ros_buildfarm:/usr/lib/python2.7/dist-packages:$PYTHONPATH python3 -u /tmp/ros_buildfarm/scripts/devel/create_devel_task_generator.py --rosdistro-name @rosdistro_name --workspace-root /tmp/catkin_workspace --os-name @os_name --os-code-name @os_code_name --distribution-repository-urls @(' '.join(distribution_repository_urls)) --distribution-repository-key-files @(' ' .join(['/tmp/keys/%d.key' % i for i in range(len(distribution_repository_keys))])) --dockerfile-dir /tmp/docker_build_and_install && PYTHONPATH=/tmp/ros_buildfarm:/usr/lib/python2.7/dist-packages:$PYTHONPATH python3 -u /tmp/ros_buildfarm/scripts/devel/create_devel_task_generator.py --rosdistro-name @rosdistro_name --workspace-root /tmp/catkin_workspace --os-name @os_name --os-code-name @os_code_name --distribution-repository-urls @(' '.join(distribution_repository_urls)) --distribution-repository-key-files @(' ' .join(['/tmp/keys/%d.key' % i for i in range(len(distribution_repository_keys))])) --testing --dockerfile-dir /tmp/docker_build_and_test"]


3.20:  Fix palette seperator sizing

3.20:  Fix palette seperator sizing

${
import os
import math

gtk_major, gtk_minor, gtk_patch = map(int, gtk.split('.'))
treeview_pseudo_element = gtk_major >= 3 and gtk_minor >= 16

def my_floor(num):
    return int(math.floor(num))

def my_ceil(num):
    return int(math.ceil(num))

# Should we set the line width in the engine to 2.25, and draw non pixel aligned lines?
# Are these already the correct sizes for the XO?

# These sizes need to be sanity checked ...
if scaling == "100":
    xo = True
    line_width = 2.0        # 2.25px, rounded down
    thick_line_width = 3.5  # 3.5
    subcell_size = 15
    icon_base = 11
    bullet_size = 9.5
    font_height = 24
    default_padding = 6
    toolbutton_padding = 10
    radio_size = 26
    scale_trough_margin = 8
else: # About 72% of the XO size, adjusted so that eg. toolbuttons work
    xo = False
    line_width = 2.0            # 1.62 rounded up
    thick_line_width = 3.0      # 2.52
    subcell_size = 11           # 10.8
    icon_base = 8               # 7.92
    bullet_size = 6.5           # 6.84
    # This is a guess on the font size (Sans 10 at 96 DPI)
    font_height = 17
    default_padding = 4         # 4.32
    toolbutton_padding = 7      # 7.68
    radio_size = 18             # 18.72
    scale_trough_margin = 6     # 5.76

scale_slider_width = my_floor(2 * subcell_size + line_width)
thickness = my_ceil(line_width)

icon_small = icon_base * 3
icon_large = icon_base * 5

disabled_opacity = 0.5

}

* {
    -sugar-focus-line: @white;

    /* 0.05 works good for both the sugar and sugar-xo themes */
    -GtkWidget-cursor-aspect-ratio: 0.05;

    -GtkWidget-wide-separators: true;
    -GtkWidget-separator-height: $thickness;
    -GtkWidget-separator-width: $thickness;

    -GtkWidget-scroll-arrow-hlength: $subcell_size;
    -GtkWidget-scroll-arrow-vlength: $subcell_size;

    -GtkRange-activate-slider: 1;

    -GtkScrolledWindow-scrollbar-spacing: 0;

    -GtkExpander-expander-size: $font_height;
    -GtkExpander-expander-spacing: 2;

    -GtkTreeView-expander-size: $font_height;

    -GtkArrow-arrow-size: 1.0;

    -GtkToolbar-space-size: $(2*subcell_size);

    -GtkProgressBar-min-horizontal-bar-height: $subcell_size;
    -GtkProgressBar-min-vertical-bar-width: $subcell_size;

    -GtkCheckButton-indicator-size: $radio_size;
    -GtkCheckButton-indicator-spacing: 3;

    -GtkWidget-text-handle-width: 110px;
    -GtkWidget-text-handle-height: 110px;
}

*:disabled {
    color: @panel_grey;
}

/* Backgrounds and windows */

window {
    background: @panel_grey;
    color: @black;
}

dialog {
    background: @black;
    color: @white;
}

introwindow {
    background: @white;
    color: @black;
}

label, label:disabled {
    background: transparent;
}

/* Buttons */

button {
    ${ border = max(0, my_ceil((3*subcell_size/2.0 - icon_small / 2.0))) }
    padding: $(border)px $(border)px $(border)px $(border)px;
    border-width: $(thickness)px;
    border-color: @button_grey;
    border-style: solid;
    border-radius: $(2*subcell_size)px;
    background: @button_grey;
    color: @white;
}

button:focus {
    border-color: @white;
    color: @white;
}

button:active {
    background: @white;
    color: @black;
}

button:disabled {
    background: transparent;
}

button:active:focus {
    color: @black;
    border-color: @button_grey;
}

/* Linked boxes (eg. combobox entries) */

box.linked *:not(:first-child):not(:last-child) {
    border-radius: 0;
}

box.linked *:first-child:not(:last-child) {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

box.linked *:not(:first-child):last-child {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

/* Spin buttons */

spinbutton button {
    background: @button_grey;
    border-style: solid;
    border-radius: 0;
}

spinbutton.horizontal button:last-child {
    border-radius: 0 $(2*subcell_size)px $(2*subcell_size)px 0;
}

spinbutton.vertical button:first-child {
    border-radius: $(2*subcell_size)px $(2*subcell_size)px 0 0;
    border-width: $(thickness)px $(thickness)px 0 $(thickness)px;
    border-style: solid;
}

spinbutton.vertical button:last-child {
    border-radius: 0 0 $(2*subcell_size)px $(2*subcell_size)px;
    border-width: 0 $(thickness)px $(thickness)px $(thickness)px;
    border-style: solid;
}

spinbutton button:active {
    background: @black;
}

spinbutton button:disabled {
    background: @selection_grey;
}

/* Toggle buttons */

button:checked {
    background: @white;
    color: @black;
}

/* Entries and views (textview, treeview) */

.view,
textview text {  /* Setting a textview to background white makes it black,
                    unless you only style the text css node */
    border-width: 0px;
    border-style: none;
    border-radius: 0px;
    padding: 0px;
    background: @white;
    color: @black;
}

treeview header button,
treeview header button:hover:active {
    border-radius: 0px;
    background: @button_grey;
    border-width: 0px;
    color: @white;
}

/* FIXME: treeview row css nodes aren't a thing

    $[if treeview_pseudo_element] GtkTreeView row:even
    $[else] GtkTreeView row:nth-child(even)
    $[end if] {
        background: @row_even;
    }

    $[if treeview_pseudo_element] GtkTreeView row:odd
    $[else] GtkTreeView row:nth-child(odd)
    $[end if] {
        background: @row_odd;
    }
*/

entry {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    border-color: @text_field_grey;
    border-style: solid;
    background: @text_field_grey;
    color: @black;
    ${ entry_ythickness = my_ceil(0.2 * (subcell_size*3.0/2.0 - thickness) + thickness) }
    ${ entry_xthickness = my_ceil(0.32 * (subcell_size*3.0/2.0 - thickness) + thickness) }
    padding: $(2 * max(my_ceil((3*subcell_size - font_height - entry_ythickness*2)/2.0),0))px $(2 * max(subcell_size - entry_xthickness, 0))px $(2 * max(my_floor((3*subcell_size - font_height - entry_ythickness*2)/2.0), 0))px $(2 * max(subcell_size - entry_xthickness, 0))px;
}

entry image {
    /* Default padding is too large and causes toolbar height expansion */
    margin: $(default_padding-1)px;
}

toolitem entry {
    margin: $(subcell_size)px;
}

entry progress {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    background: @selection_grey;
}

entry:focus {
    background: @white;
}

toolbar entry:focus {
    border-color: @white;
}

entry:disabled {
    background: @button_grey;
    border-color: @button_grey;
}

treeview:selected {
    background: @panel_grey;
    color: @black;
}

entry:selected,
entry:selected:focus {
    background: @selection_grey;
    color: @black;
}

entry:selected,
entry:selected:focus {
    border-color: @selection_grey;
}

selection {
    background: @selected_bg_color;
    color: @selected_fg_color;
}

/* Frames */

frame {
    border-style: solid;
    border-color: @selection_grey;
    border-width: $(thickness)px;
    border-radius: 0;
    padding: $(default_padding)px;
}

frame.journal-preview-box {
    border-color: @button_grey;
}

/* Combo boxes */

combobox {
    color: @white;
}

combobox button.combo arrow {
    -gtk-icon-source: -gtk-icontheme('control-popup-arrow');
    -gtk-icon-transform: scale(0.3);
    min-width: $(subcell_size)px;
    min-height: $(subcell_size)px;
}

/* What does this do?
    combobox .menu.button {
        border-color: transparent;
        border-radius: 0px;
        border-style: none;
        background: transparent;
        color: @white;
    }
*/

combobox menu {
    -GtkWidget-scroll-arrow-hlength: $(my_floor(2.5 * subcell_size));
    -GtkWidget-scroll-arrow-vlength: $(my_floor(2.5 * subcell_size));
}

/* Notebooks */

notebook {
    background: @selection_grey;
    color: @black;
    padding: 0px;
    -GtkNotebook-tab-overlap: -2;
    -GtkNotebook-tab-curvature: $default_padding;
    -GtkWidget-scroll-arrow-hlength: $(my_floor(2.5 * subcell_size));
    -GtkWidget-scroll-arrow-vlength: $(my_floor(2.5 * subcell_size));
}

notebook arrow {
    color: @white;
}

notebook tab {
    background: @selection_grey;
}

/* Setting white color to the tab labels using only the previous rule */
/* doesn't work, so we have to set white color to the GtkLabel, and make */
/* sure the buttons get black color in the next two rules: */
notebook tab label {
    color: @white;
    padding: $(subcell_size)px 0;
}

notebook tab button label {
    color: @black;
}

notebook tab:checked {
    background: @toolbar_grey;
}

notebook tab button {
    border-radius: $(toolbutton_padding)px;
}

/* Browse Widgets */

BrowseSearchWindow treeview {
    background: @black;
    color: @white;
    border-color: @button_grey;
    border-width: 0 $(thickness)px $(thickness)px $(thickness)px;
    border-style: solid;
}

/* Control panel */

cpanelsectionview {
    background: @white;
    color: @black;
}

/* TODO:  this should only effect the control panel */
controlpanel image:disabled {
    opacity: $(disabled_opacity);
}

/* Alert */

alert {
    background: @black;
    color: @white;
}

alert *:disabled {
    background: @black;
}

button timeouticon label,
button timeouticon label:hover {
    background: @white;
    color: @button_grey;
    border-radius: $(2 * subcell_size)px;
}

button:active timeouticon label {
    background: @toolbar_grey;
    color: @white;
}

/* Tray */

htray, vtray {
    background: @toolbar_grey;
}

htray * , vtray * { background: @toolbar_grey;}

/* Menus and palettes */

palette,
popover {
    border: $(thickness)px solid @button_grey;
    background: @black;
    color: @white;
}

palette .view {
    color: @black;
}

SugarPaletteMenuWidget {
    background: @black;
}

SugarPaletteMenuWidget * {
    color: @white;
}

SugarPaletteWindow SugarGroupBox {
    background: @toolbar_grey;
    color: @white;
}

SugarPaletteWindow SugarGroupBox *:disabled {
    background: @toolbar_grey;
}

menu {
    background: @black;
    color: @white;

    -GtkMenu-scroll-arrow-vlength: $(my_floor(subcell_size/0.7 + 2*thickness));
    /* This means the outline of the submenu overlaps with a palette.
     * However in the case of two normal menus, they are next to each other.
     * It is not possible to be smarter about this, because the style comes from
     * the submenu. */
    -GtkMenu-horizontal-offset :  -$(line_width)px;
    -GtkMenu-vertical-offset   : 0;

    padding: 0px;

    border: $(thickness)px solid @button_grey;
}

menuitem {
    padding: $(subcell_size)px $((subcell_size * 3 - font_height) / 2)px;
}

menuitem:hover,
menuitem:hover {
    background: @button_grey;
}

menuitem separator {
    padding: 0;
}

tooltip {
    background: @black;
    border-style: solid;
    border-width: $(thickness)px;
    border-color: @button_grey;
}

tooltip * {
    color: @white;
}

/* Scrollbars */

scrollbar {
    -GtkRange-slider-width: $(subcell_size + 2*thickness);
    -GtkRange-trough-border: 0;
    -GtkRange-stepper-size: 0;
    -GtkScrollbar-min-slider-length: $(3*subcell_size);
    -GtkScrollbar-has-forward-stepper: 0;
    -GtkScrollbar-has-backward-stepper: 0;
    -GtkScrollbar-has-secondary-forward-stepper: 0;
    -GtkScrollbar-has-secondary-backward-stepper: 0;
}

scrollbar trough {
    background: @button_grey;
    border-width: 0px;
}

scrollbar slider {
    background: @white;
    border-radius: $(2*subcell_size)px;
    border-width: $(thickness)px;
    border-color: @button_grey;
    border-style: solid;
}

scrollbar slider:active {
    background: @text_field_grey;
}

/* Progress bars */

progressbar progress {
    background: @white;
    border-color: @button_grey;
    border-radius: $(subcell_size)px;
    border-style: solid;
    border-width: $(thickness)px;
}

progressbar trough {
    background: @selection_grey;
    border-style: solid;
    border-radius: $(subcell_size)px;
    border-color: @button_grey;
    border-width: $(thickness)px;
}

toolbar progressbar trough,
palette progressbar trough {
    background: @black;
}

/* Separators */

separator {
    background-color: @button_grey;
}

/* If we set the background for these, we get about 20px high
   separator.  But we only want a border through the middle */
palette widget separator {
    border-top: $(thickness)px solid @button_grey;
    margin-top: $(subcell_size)px;
    margin-bottom: -$(subcell_size)px;
    background: transparent;
}

/* Tool buttons */

toolbar,
toolbarbox {
    padding: 0;
    background: @toolbar_grey;
    color: @white;
}

/* FIXME: I needed to add the margin in the gtk3.20 port, but that wasn't
          there in the code before.  I ended up having to reduce the border
          to 2px */
toolbutton button,
filtertoolbutton button {
    border-style: none;
    background: transparent;
    border-radius: $(toolbutton_padding)px;
    padding: $(toolbutton_padding - default_padding)px;
    margin: $(default_padding)px;
}

toolbutton button #gtk-toolbar-arrow {
    padding: 0 $(subcell_size + default_padding)px;
}

toolbutton button:disabled {
    opacity: $(disabled_opacity);
}

toolbutton button:hover {
    padding: $(toolbutton_padding - 2 - default_padding)px;
    border-width: 2px;
    border-style: solid;
    background-clip: padding-box;
}

toolbutton button:hover:not(:checked),
toolbutton button:hover:not(:checked) * {
    background: @black;
}

toolbutton button.radio:checked,
toolbutton button.radio:checked * {
    background: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

toolbutton button.radio:checked:hover,
toolbutton button.radio:checked:hover * {
    background: @button_grey;
    border-color: @button_grey;
}

toolbutton button:active,
toolbutton button:active * {
    background: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

palette scrolledwindow {
    background: @black;
}

toolbar combobox button {
    border-radius: $(2*subcell_size)px;
}

/* Scales */

scale {
    -GtkScale-slider-length: $scale_slider_width;
    -GtkRange-slider-width: $scale_slider_width;
}

scale highlight {
    background: @white;
    border-style: solid;
    border-color: @button_grey;
    border-width: $(thickness)px;
}

scale trough {
    background: @button_grey;
    border-style: solid;
    border-color: @button_grey;
    border-width: $(thickness)px;
    margin: $(scale_trough_margin)px 0;
}

scale.vertical trough {
    margin: 0 $(scale_trough_margin)px;
}

scale trough:focus {
    border-color: @white;
}

scale trough,
scale highlight {
    border-radius: $(2*subcell_size)px $(2*subcell_size)px;
}

scale slider,
scale slider:active {
    background: transparent;
}

scale slider {
    -gtk-icon-source: url("assets/scale-slider.svg");
    margin: -$(my_floor(subcell_size/2))px;
    min-width: $(2*subcell_size)px;
    min-height: $(2*subcell_size)px;
}

scale slider:active {
    -gtk-icon-source: url("assets/scale-slider-active.svg");
    margin: -$(my_floor(subcell_size/2))px;
    min-width: $(2*subcell_size)px;
    min-height: $(2*subcell_size)px;
}

/* Radio and check buttons */

check:hover {
    background: alpha(@theme_base_color, 0.0);
}

toolbar check,
palette check {
    color: @theme_base_color;
    border: 1px solid @theme_base_color;
}

radio,
radio row:selected,
radio row:selected:focus {
    -gtk-icon-source: url("assets/radio.svg");
    background: alpha(@theme_base_color, 0.0);
}

radio:selected,
radio:selected row:selected,
radio:selected row:selected:focus {
    -gtk-icon-source: url("assets/radio-selected.svg");
}

radio:active,
radio row:selected:active,
radio row:selected:focus:active {
    -gtk-icon-source: url("assets/radio-active.svg");
}

radio:checked,
radio row:selected:checked,
radio row:selected:focus:checked {
   -gtk-icon-source: url("assets/radio-active.svg");
}

radio:active:selected,
radio:selected row:selected:active,
radio:selected row:selected:focus:active {
    -gtk-icon-source: url("assets/radio-active-selected.svg");
}

radio:checked:selected,
radio:selected row:selected:checked,
radio:selected row:selected:focus:checked {
    -gtk-icon-source: url("assets/radio-active-selected.svg");
}

check,
check row:selected,
check row:selected:focus {
    -gtk-icon-source: url("assets/checkbox-unchecked.svg");
    background: alpha(@theme_base_color, 0.0);
}

check:selected,
check:selected row:selected,
check:selected row:selected:focus {
    -gtk-icon-source: url("assets/checkbox-unchecked-selected.svg");
}

check:active,
check row:selected:active,
check row:selected:focus:active {
    -gtk-icon-source: url("assets/checkbox-checked.svg");
}

check:checked,
check row:selected:checked,
check row:selected:focus:checked {
    -gtk-icon-source: url("assets/checkbox-checked.svg");
}

check:active:selected,
check:selected row:selected:active,
check:selected row:selected:focus:active {
    -gtk-icon-source: url("assets/checkbox-checked-selected.svg");
}

check:checked:selected,
check:selected row:selected:checked,
check:selected row:selected:focus:checked {
    background-image: url("assets/checkbox-checked-selected.svg");
    -gtk-icon-source: url("assets/checkbox-checked-selected.svg");
}

/* Tool items */

toolitem {
    /* arrow in the toolbarbox */
    color: @white;
}

.palette-down,
.toolbar-down {
    /* outline of the toolbarbutton when palette is expanded */
    border-style: solid;
    border-width: 2px;
    border-color: @button_grey;
}

/* GtkImage */

image {
    background: transparent;
}

/* Sugar Frame Window */

framewindow {
    background: @toolbar_grey;
}

/* Sugar Canvas icon */

canvasicon {
    border-color: transparent;
    border-radius: $(4 * thickness)px;
    border-width: 2px;
    border-style: solid;
    background: transparent;
}

canvasicon:hover {
    border-color: @zoom_views_prelight;
    background: @zoom_views_prelight;
}

canvasicon.toggle-press {
    border-color: @zoom_views_active;
    background: @zoom_views_active;
}

/* Sugar CellRenderer Icons */

.cell.sugar-icon-cell {
    background: transparent;
    border-radius: $(4 * thickness)px;
    border-color: @white;
    border-width: $(thickness)px;
    border-style: solid;
}

.cell.sugar-icon-cell:hover {
    background: @zoom_views_prelight;
}

.cell.sugar-icon-cell:active {
    background: @zoom_views_active;
}

/* CellRendererProgress */

.cell.progressbar, .cell.trough {
    border-style: solid;
    border-width: $(thickness)px;
    border-color: @selection_grey;
    border-radius: $(2*subcell_size)px;
}

.cell.progressbar {
    background: @white;
}

.cell.trough {
    background: @text_field_grey;
}

/* Text cursor handles */

cursor-handle.top,
cursor-handle.bottom {
    background: transparent;
    border-radius: 10px;
    border-width: $(thickness)px;
    border-color: @panel_grey;
    border-style: solid;
}

cursor-handle.bottom {
    -gtk-icon-source: url("assets/cursor-handle-bottom.svg");
}

cursor-handle.top {
    -gtk-icon-source: url("assets/cursor-handle-top.svg");
}

cursor-handle.insertion-cursor {
    -gtk-icon-source: url("assets/cursor-handle-insertion.svg");
}

/* Application specific properties */

EvView {
    background: @panel_grey;
}

EvView:selected,
ApDocView:selected {
    background: @selection_grey;
}

${
import os
import math

gtk_major, gtk_minor, gtk_patch = map(int, gtk.split('.'))
treeview_pseudo_element = gtk_major >= 3 and gtk_minor >= 16

def my_floor(num):
    return int(math.floor(num))

def my_ceil(num):
    return int(math.ceil(num))

# Should we set the line width in the engine to 2.25, and draw non pixel aligned lines?
# Are these already the correct sizes for the XO?

# These sizes need to be sanity checked ...
if scaling == "100":
    xo = True
    line_width = 2.0        # 2.25px, rounded down
    thick_line_width = 3.5  # 3.5
    subcell_size = 15
    icon_base = 11
    bullet_size = 9.5
    font_height = 24
    default_padding = 6
    toolbutton_padding = 10
    radio_size = 26
    scale_trough_margin = 8
else: # About 72% of the XO size, adjusted so that eg. toolbuttons work
    xo = False
    line_width = 2.0            # 1.62 rounded up
    thick_line_width = 3.0      # 2.52
    subcell_size = 11           # 10.8
    icon_base = 8               # 7.92
    bullet_size = 6.5           # 6.84
    # This is a guess on the font size (Sans 10 at 96 DPI)
    font_height = 17
    default_padding = 4         # 4.32
    toolbutton_padding = 7      # 7.68
    radio_size = 18             # 18.72
    scale_trough_margin = 6     # 5.76

scale_slider_width = my_floor(2 * subcell_size + line_width)
thickness = my_ceil(line_width)

icon_small = icon_base * 3
icon_large = icon_base * 5

disabled_opacity = 0.5

}

* {
    -sugar-focus-line: @white;

    /* 0.05 works good for both the sugar and sugar-xo themes */
    -GtkWidget-cursor-aspect-ratio: 0.05;

    -GtkWidget-wide-separators: true;
    -GtkWidget-separator-height: $thickness;
    -GtkWidget-separator-width: $thickness;

    -GtkWidget-scroll-arrow-hlength: $subcell_size;
    -GtkWidget-scroll-arrow-vlength: $subcell_size;

    -GtkRange-activate-slider: 1;

    -GtkScrolledWindow-scrollbar-spacing: 0;

    -GtkExpander-expander-size: $font_height;
    -GtkExpander-expander-spacing: 2;

    -GtkTreeView-expander-size: $font_height;

    -GtkArrow-arrow-size: 1.0;

    -GtkToolbar-space-size: $(2*subcell_size);

    -GtkProgressBar-min-horizontal-bar-height: $subcell_size;
    -GtkProgressBar-min-vertical-bar-width: $subcell_size;

    -GtkCheckButton-indicator-size: $radio_size;
    -GtkCheckButton-indicator-spacing: 3;

    -GtkWidget-text-handle-width: 110px;
    -GtkWidget-text-handle-height: 110px;
}

*:disabled {
    color: @panel_grey;
}

/* Backgrounds and windows */

window {
    background: @panel_grey;
    color: @black;
}

dialog {
    background: @black;
    color: @white;
}

introwindow {
    background: @white;
    color: @black;
}

label, label:disabled {
    background: transparent;
}

/* Buttons */

button {
    ${ border = max(0, my_ceil((3*subcell_size/2.0 - icon_small / 2.0))) }
    padding: $(border)px $(border)px $(border)px $(border)px;
    border-width: $(thickness)px;
    border-color: @button_grey;
    border-style: solid;
    border-radius: $(2*subcell_size)px;
    background: @button_grey;
    color: @white;
}

button:focus {
    border-color: @white;
    color: @white;
}

button:active {
    background: @white;
    color: @black;
}

button:disabled {
    background: transparent;
}

button:active:focus {
    color: @black;
    border-color: @button_grey;
}

/* Linked boxes (eg. combobox entries) */

box.linked *:not(:first-child):not(:last-child) {
    border-radius: 0;
}

box.linked *:first-child:not(:last-child) {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

box.linked *:not(:first-child):last-child {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

/* Spin buttons */

spinbutton button {
    background: @button_grey;
    border-style: solid;
    border-radius: 0;
}

spinbutton.horizontal button:last-child {
    border-radius: 0 $(2*subcell_size)px $(2*subcell_size)px 0;
}

spinbutton.vertical button:first-child {
    border-radius: $(2*subcell_size)px $(2*subcell_size)px 0 0;
    border-width: $(thickness)px $(thickness)px 0 $(thickness)px;
    border-style: solid;
}

spinbutton.vertical button:last-child {
    border-radius: 0 0 $(2*subcell_size)px $(2*subcell_size)px;
    border-width: 0 $(thickness)px $(thickness)px $(thickness)px;
    border-style: solid;
}

spinbutton button:active {
    background: @black;
}

spinbutton button:disabled {
    background: @selection_grey;
}

/* Toggle buttons */

button:checked {
    background: @white;
    color: @black;
}

/* Entries and views (textview, treeview) */

.view,
textview text {  /* Setting a textview to background white makes it black,
                    unless you only style the text css node */
    border-width: 0px;
    border-style: none;
    border-radius: 0px;
    padding: 0px;
    background: @white;
    color: @black;
}

treeview header button,
treeview header button:hover:active {
    border-radius: 0px;
    background: @button_grey;
    border-width: 0px;
    color: @white;
}

/* FIXME: treeview row css nodes aren't a thing

    $[if treeview_pseudo_element] GtkTreeView row:even
    $[else] GtkTreeView row:nth-child(even)
    $[end if] {
        background: @row_even;
    }

    $[if treeview_pseudo_element] GtkTreeView row:odd
    $[else] GtkTreeView row:nth-child(odd)
    $[end if] {
        background: @row_odd;
    }
*/

entry {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    border-color: @text_field_grey;
    border-style: solid;
    background: @text_field_grey;
    color: @black;
    ${ entry_ythickness = my_ceil(0.2 * (subcell_size*3.0/2.0 - thickness) + thickness) }
    ${ entry_xthickness = my_ceil(0.32 * (subcell_size*3.0/2.0 - thickness) + thickness) }
    padding: $(2 * max(my_ceil((3*subcell_size - font_height - entry_ythickness*2)/2.0),0))px $(2 * max(subcell_size - entry_xthickness, 0))px $(2 * max(my_floor((3*subcell_size - font_height - entry_ythickness*2)/2.0), 0))px $(2 * max(subcell_size - entry_xthickness, 0))px;
}

entry image {
    /* Default padding is too large and causes toolbar height expansion */
    margin: $(default_padding-1)px;
}

toolitem entry {
    margin: $(subcell_size)px;
}

entry progress {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    background: @selection_grey;
}

entry:focus {
    background: @white;
}

toolbar entry:focus {
    border-color: @white;
}

entry:disabled {
    background: @button_grey;
    border-color: @button_grey;
}

treeview:selected {
    background: @panel_grey;
    color: @black;
}

entry:selected,
entry:selected:focus {
    background: @selection_grey;
    color: @black;
}

entry:selected,
entry:selected:focus {
    border-color: @selection_grey;
}

selection {
    background: @selected_bg_color;
    color: @selected_fg_color;
}

/* Frames */

frame {
    border-style: solid;
    border-color: @selection_grey;
    border-width: $(thickness)px;
    border-radius: 0;
    padding: $(default_padding)px;
}

frame.journal-preview-box {
    border-color: @button_grey;
}

/* Combo boxes */

combobox {
    color: @white;
}

combobox button.combo arrow {
    -gtk-icon-source: -gtk-icontheme('control-popup-arrow');
    -gtk-icon-transform: scale(0.3);
    min-width: $(subcell_size)px;
    min-height: $(subcell_size)px;
}

/* What does this do?
    combobox .menu.button {
        border-color: transparent;
        border-radius: 0px;
        border-style: none;
        background: transparent;
        color: @white;
    }
*/

combobox menu {
    -GtkWidget-scroll-arrow-hlength: $(my_floor(2.5 * subcell_size));
    -GtkWidget-scroll-arrow-vlength: $(my_floor(2.5 * subcell_size));
}

/* Notebooks */

notebook {
    background: @selection_grey;
    color: @black;
    padding: 0px;
    -GtkNotebook-tab-overlap: -2;
    -GtkNotebook-tab-curvature: $default_padding;
    -GtkWidget-scroll-arrow-hlength: $(my_floor(2.5 * subcell_size));
    -GtkWidget-scroll-arrow-vlength: $(my_floor(2.5 * subcell_size));
}

notebook arrow {
    color: @white;
}

notebook tab {
    background: @selection_grey;
}

/* Setting white color to the tab labels using only the previous rule */
/* doesn't work, so we have to set white color to the GtkLabel, and make */
/* sure the buttons get black color in the next two rules: */
notebook tab label {
    color: @white;
    padding: $(subcell_size)px 0;
}

notebook tab button label {
    color: @black;
}

notebook tab:checked {
    background: @toolbar_grey;
}

notebook tab button {
    border-radius: $(toolbutton_padding)px;
}

/* Browse Widgets */

BrowseSearchWindow treeview {
    background: @black;
    color: @white;
    border-color: @button_grey;
    border-width: 0 $(thickness)px $(thickness)px $(thickness)px;
    border-style: solid;
}

/* Control panel */

cpanelsectionview {
    background: @white;
    color: @black;
}

/* TODO:  this should only effect the control panel */
controlpanel image:disabled {
    opacity: $(disabled_opacity);
}

/* Alert */

alert {
    background: @black;
    color: @white;
}

alert *:disabled {
    background: @black;
}

button timeouticon label,
button timeouticon label:hover {
    background: @white;
    color: @button_grey;
    border-radius: $(2 * subcell_size)px;
}

button:active timeouticon label {
    background: @toolbar_grey;
    color: @white;
}

/* Tray */

htray, vtray {
    background: @toolbar_grey;
}

htray * , vtray * { background: @toolbar_grey;}

/* Menus and palettes */

palette,
popover {
    border: $(thickness)px solid @button_grey;
    background: @black;
    color: @white;
}

palette .view {
    color: @black;
}

SugarPaletteMenuWidget {
    background: @black;
}

SugarPaletteMenuWidget * {
    color: @white;
}

SugarPaletteWindow SugarGroupBox {
    background: @toolbar_grey;
    color: @white;
}

SugarPaletteWindow SugarGroupBox *:disabled {
    background: @toolbar_grey;
}

menu {
    background: @black;
    color: @white;

    -GtkMenu-scroll-arrow-vlength: $(my_floor(subcell_size/0.7 + 2*thickness));
    /* This means the outline of the submenu overlaps with a palette.
     * However in the case of two normal menus, they are next to each other.
     * It is not possible to be smarter about this, because the style comes from
     * the submenu. */
    -GtkMenu-horizontal-offset :  -$(line_width)px;
    -GtkMenu-vertical-offset   : 0;

    padding: 0px;

    border: $(thickness)px solid @button_grey;
}

menuitem {
    padding: $(subcell_size)px $((subcell_size * 3 - font_height) / 2)px;
}

menuitem:hover,
menuitem:hover {
    background: @button_grey;
}

menuitem separator {
    padding: 0;
}

tooltip {
    background: @black;
    border-style: solid;
    border-width: $(thickness)px;
    border-color: @button_grey;
}

tooltip * {
    color: @white;
}

/* Scrollbars */

scrollbar {
    -GtkRange-slider-width: $(subcell_size + 2*thickness);
    -GtkRange-trough-border: 0;
    -GtkRange-stepper-size: 0;
    -GtkScrollbar-min-slider-length: $(3*subcell_size);
    -GtkScrollbar-has-forward-stepper: 0;
    -GtkScrollbar-has-backward-stepper: 0;
    -GtkScrollbar-has-secondary-forward-stepper: 0;
    -GtkScrollbar-has-secondary-backward-stepper: 0;
}

scrollbar trough {
    background: @button_grey;
    border-width: 0px;
}

scrollbar slider {
    background: @white;
    border-radius: $(2*subcell_size)px;
    border-width: $(thickness)px;
    border-color: @button_grey;
    border-style: solid;
}

scrollbar slider:active {
    background: @text_field_grey;
}

/* Progress bars */

progressbar progress {
    background: @white;
    border-color: @button_grey;
    border-radius: $(subcell_size)px;
    border-style: solid;
    border-width: $(thickness)px;
}

progressbar trough {
    background: @selection_grey;
    border-style: solid;
    border-radius: $(subcell_size)px;
    border-color: @button_grey;
    border-width: $(thickness)px;
}

toolbar progressbar trough,
palette progressbar trough {
    background: @black;
}

/* Separators */

separator {
    /*border-top: $(thickness)px solid @button_grey;*/
    background-color: @button_grey;
}

/* Tool buttons */

toolbar,
toolbarbox {
    padding: 0;
    background: @toolbar_grey;
    color: @white;
}

/* FIXME: I needed to add the margin in the gtk3.20 port, but that wasn't
          there in the code before.  I ended up having to reduce the border
          to 2px */
toolbutton button,
filtertoolbutton button {
    border-style: none;
    background: transparent;
    border-radius: $(toolbutton_padding)px;
    padding: $(toolbutton_padding - default_padding)px;
    margin: $(default_padding)px;
}

toolbutton button #gtk-toolbar-arrow {
    padding: 0 $(subcell_size + default_padding)px;
}

toolbutton button:disabled {
    opacity: $(disabled_opacity);
}

toolbutton button:hover {
    padding: $(toolbutton_padding - 2 - default_padding)px;
    border-width: 2px;
    border-style: solid;
    background-clip: padding-box;
}

toolbutton button:hover:not(:checked),
toolbutton button:hover:not(:checked) * {
    background: @black;
}

toolbutton button.radio:checked,
toolbutton button.radio:checked * {
    background: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

toolbutton button.radio:checked:hover,
toolbutton button.radio:checked:hover * {
    background: @button_grey;
    border-color: @button_grey;
}

toolbutton button:active,
toolbutton button:active * {
    background: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

palette scrolledwindow {
    background: @black;
}

toolbar combobox button {
    border-radius: $(2*subcell_size)px;
}

/* Scales */

scale {
    -GtkScale-slider-length: $scale_slider_width;
    -GtkRange-slider-width: $scale_slider_width;
}

scale highlight {
    background: @white;
    border-style: solid;
    border-color: @button_grey;
    border-width: $(thickness)px;
}

scale trough {
    background: @button_grey;
    border-style: solid;
    border-color: @button_grey;
    border-width: $(thickness)px;
    margin: $(scale_trough_margin)px 0;
}

scale.vertical trough {
    margin: 0 $(scale_trough_margin)px;
}

scale trough:focus {
    border-color: @white;
}

scale trough,
scale highlight {
    border-radius: $(2*subcell_size)px $(2*subcell_size)px;
}

scale slider,
scale slider:active {
    background: transparent;
}

scale slider {
    -gtk-icon-source: url("assets/scale-slider.svg");
    margin: -$(my_floor(subcell_size/2))px;
    min-width: $(2*subcell_size)px;
    min-height: $(2*subcell_size)px;
}

scale slider:active {
    -gtk-icon-source: url("assets/scale-slider-active.svg");
    margin: -$(my_floor(subcell_size/2))px;
    min-width: $(2*subcell_size)px;
    min-height: $(2*subcell_size)px;
}

/* Radio and check buttons */

check:hover {
    background: alpha(@theme_base_color, 0.0);
}

toolbar check,
palette check {
    color: @theme_base_color;
    border: 1px solid @theme_base_color;
}

radio,
radio row:selected,
radio row:selected:focus {
    -gtk-icon-source: url("assets/radio.svg");
    background: alpha(@theme_base_color, 0.0);
}

radio:selected,
radio:selected row:selected,
radio:selected row:selected:focus {
    -gtk-icon-source: url("assets/radio-selected.svg");
}

radio:active,
radio row:selected:active,
radio row:selected:focus:active {
    -gtk-icon-source: url("assets/radio-active.svg");
}

radio:checked,
radio row:selected:checked,
radio row:selected:focus:checked {
   -gtk-icon-source: url("assets/radio-active.svg");
}

radio:active:selected,
radio:selected row:selected:active,
radio:selected row:selected:focus:active {
    -gtk-icon-source: url("assets/radio-active-selected.svg");
}

radio:checked:selected,
radio:selected row:selected:checked,
radio:selected row:selected:focus:checked {
    -gtk-icon-source: url("assets/radio-active-selected.svg");
}

check,
check row:selected,
check row:selected:focus {
    -gtk-icon-source: url("assets/checkbox-unchecked.svg");
    background: alpha(@theme_base_color, 0.0);
}

check:selected,
check:selected row:selected,
check:selected row:selected:focus {
    -gtk-icon-source: url("assets/checkbox-unchecked-selected.svg");
}

check:active,
check row:selected:active,
check row:selected:focus:active {
    -gtk-icon-source: url("assets/checkbox-checked.svg");
}

check:checked,
check row:selected:checked,
check row:selected:focus:checked {
    -gtk-icon-source: url("assets/checkbox-checked.svg");
}

check:active:selected,
check:selected row:selected:active,
check:selected row:selected:focus:active {
    -gtk-icon-source: url("assets/checkbox-checked-selected.svg");
}

check:checked:selected,
check:selected row:selected:checked,
check:selected row:selected:focus:checked {
    background-image: url("assets/checkbox-checked-selected.svg");
    -gtk-icon-source: url("assets/checkbox-checked-selected.svg");
}

/* Tool items */

toolitem {
    /* arrow in the toolbarbox */
    color: @white;
}

.palette-down,
.toolbar-down {
    /* outline of the toolbarbutton when palette is expanded */
    border-style: solid;
    border-width: 2px;
    border-color: @button_grey;
}

/* GtkImage */

image {
    background: transparent;
}

/* Sugar Frame Window */

framewindow {
    background: @toolbar_grey;
}

/* Sugar Canvas icon */

canvasicon {
    border-color: transparent;
    border-radius: $(4 * thickness)px;
    border-width: 2px;
    border-style: solid;
    background: transparent;
}

canvasicon:hover {
    border-color: @zoom_views_prelight;
    background: @zoom_views_prelight;
}

canvasicon.toggle-press {
    border-color: @zoom_views_active;
    background: @zoom_views_active;
}

/* Sugar CellRenderer Icons */

.cell.sugar-icon-cell {
    background: transparent;
    border-radius: $(4 * thickness)px;
    border-color: @white;
    border-width: $(thickness)px;
    border-style: solid;
}

.cell.sugar-icon-cell:hover {
    background: @zoom_views_prelight;
}

.cell.sugar-icon-cell:active {
    background: @zoom_views_active;
}

/* CellRendererProgress */

.cell.progressbar, .cell.trough {
    border-style: solid;
    border-width: $(thickness)px;
    border-color: @selection_grey;
    border-radius: $(2*subcell_size)px;
}

.cell.progressbar {
    background: @white;
}

.cell.trough {
    background: @text_field_grey;
}

/* Text cursor handles */

cursor-handle.top,
cursor-handle.bottom {
    background: transparent;
    border-radius: 10px;
    border-width: $(thickness)px;
    border-color: @panel_grey;
    border-style: solid;
}

cursor-handle.bottom {
    -gtk-icon-source: url("assets/cursor-handle-bottom.svg");
}

cursor-handle.top {
    -gtk-icon-source: url("assets/cursor-handle-top.svg");
}

cursor-handle.insertion-cursor {
    -gtk-icon-source: url("assets/cursor-handle-insertion.svg");
}

/* Application specific properties */

EvView {
    background: @panel_grey;
}

EvView:selected,
ApDocView:selected {
    background: @selection_grey;
}