before update

before update

class Spree extends Ember.Object with Ember.Evented
  key: 'spree_ember'

  # todo globalize environment config

  orderToken: null
  orderId: null

  init: ->
    @restore @localStorageData()

  # Persist Order Details to Local Storage
  persist: (data) ->
    @restore data
    data = JSON.stringify data or {}
    localStorage.setItem @key, data

  restore: (data) ->
    for key, value of data
      @set key, value

  localStorageData: ->
    data = localStorage.getItem @key
    JSON.parse data or "{}"

  clear: ->
    localStorage.removeItem @key

  # TODO - This needs to save to local storage
  clearCurrentOrder: ->
    @orderId = null

  +computed orderId
  currentOrder: ->
    @store.find('order', @orderId) if @orderId

  # TODO - The developer should be able to
  # overwrite these steps.
  +computed currentOrder.state
  currentOrderCanAdvance: ->
    switch @currentOrder.state
      when 'cart' then true
      when 'address'
        @currentOrder.shipAddress and @currentOrder.billAddress
      when 'delivery'
        false
        # Has shipping rate for each shipment
      when 'payment'
        false
        # Order has a Payment
      when 'confirm' then true
      when 'complete' then false

  advanceOrderState: ->
    if @currentOrderCanAdvance
      @applicationAdapter.ajax(@nextURL, 'PUT').then(
        (order) =>
          @store.pushPayload order
          @trigger 'spreeOrderDidAdvance'
        (error) =>
          @trigger 'spreeOrderDidNotAdvance', error
      )
    else
      @trigger 'spreeOrderCanNotAdvance'

  saveCurrentOrder: ->
    @currentOrder.content.save().then(
      (order) =>
        debugger
      (error) =>
        debugger
    )

  +computed container
  applicationAdapter: ->
    @container.lookup('adapter:application')

  +computed orderId
  nextURL: ->
    "#{@applicationAdapter.buildURL('checkout', @currentOrder.id)}/next.json}"

`export default Spree`

class Spree extends Ember.Object with Ember.Evented
  key: 'spree_ember'

  # todo globalize environment config

  orderToken: null
  orderId: null

  init: ->
    @restore @localStorageData()

  # Persist Order Details to Local Storage
  persist: (data) ->
    @restore data
    data = JSON.stringify data or {}
    localStorage.setItem @key, data

  restore: (data) ->
    for key, value of data
      @set key, value

  localStorageData: ->
    data = localStorage.getItem @key
    JSON.parse data or "{}"

  clear: ->
    localStorage.removeItem @key

  # TODO - This needs to save to local storage
  clearCurrentOrder: ->
    @orderId = null

  +computed orderId
  currentOrder: ->
    @store.find('order', @orderId) if @orderId

  # TODO - The developer should be able to
  # overwrite these steps.
  +computed currentOrder.state
  currentOrderCanAdvance: ->
    switch @currentOrder.state
      when 'cart' then true
      when 'address'
        @currentOrder.shipAddress and @currentOrder.billAddress
      when 'delivery'
        false
        # Has shipping rate for each shipment
      when 'payment'
        false
        # Order has a Payment
      when 'confirm' then true
      when 'complete' then false

  advanceOrderState: ->
    if @currentOrderCanAdvance
      @applicationAdapter.ajax(@nextURL, 'PUT').then(
        (order) =>
          @store.pushPayload order
          @trigger 'spreeOrderDidAdvance'
        (error) =>
          @trigger 'spreeOrderDidNotAdvance', error
      )
    else
      @trigger 'spreeOrderCanNotAdvance'

  saveCurrentOrder: ->
    @currentOrder.save().then(
      (order) =>
        debugger
      (error) =>
        debugger
    )

  +computed container
  applicationAdapter: ->
    @container.lookup('adapter:application')

  +computed orderId
  nextURL: ->
    "#{@applicationAdapter.buildURL('checkout', @currentOrder.id)}/next.json}"

`export default Spree`


Fix Emscripten 486-style makefile

Fix Emscripten 486-style makefile

CXXFLAGS = -O3 -Wall -DEA_PLATFORM_SAMSUNG_TV  -DDONT_LOAD_LEVEL_UPON_START -DSDLSW -DVGA  -g  -IDOS-version -I../stb -I../fixed_point/include -fpermissive -Wno-narrowing  -c -std=c++14 -ferror-limit=1  -s USE_ZLIB=1 -s USE_LIBPNG=1 -I../noudar-core/noudar-core/include -Iinclude -I../noudar-rendering -I../gles2-renderer/ -s ALLOW_MEMORY_GROWTH=1 -I../noudar-core/EASTL/include -I../noudar-core/EASTL/test/packages/EABase/include/Common -DUSE_ITEMS_INSTANTLY  -fpermissive -Wno-narrowing
LDFLAGS =  -O3 -s USE_ZLIB=1 -s USE_LIBPNG=1 -s --preload-file ./data.pfs --use-preload-plugins  -s ALLOW_MEMORY_GROWTH=1
CXX = em++
CC = emcc

#CXXFLAGS = -Wall -std=c++14 -O3 --preload-file emdata --use-preload-plugins  -g -DSDLSW -DVGA -DEA_PLATFORM_SAMSUNG_TV -Iinclude -c -Istb -fpermissive -fomit-frame-pointer -fno-exceptions -ffast-math -I../fixed_point/include -IDOS-version -I../fixed_point/include -Iinclude -I../stb -I../noudar-core/noudar-core/include  -I../noudar-core/EASTL/include -I../noudar-core/EASTL/test/packages/EABase/include/Common -I../noudar-rendering -I../gles2-renderer -fpermissive -Wno-narrowing -DUSE_ITEMS_INSTANTLY -s DEMANGLE_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 -Wno-narrowing
#LDFLAGS = -O3 --preload-file emdata --use-preload-plugins  -s USE_ZLIB=1 -s USE_LIBPNG=1 -s -s DEMANGLE_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 ASSERTIONS=2

OBJS = main.o \
        events.o \
        src/CRenderer_Rasterization.o \
        src/CRenderer_Tesselation.o \
        SDLVersion/CSDLRenderer.o \
	    OPL2LPT/MusicSDL.o \
        src/CRenderer.o \
        src/RasterizerCommon.o \
        src/NativeBitmap.o \
        src/CTile3DProperties.o \
	src/CPackedFileReader.o \
	src/VisibilityStrategy.o \
        ../noudar-rendering/LoadPNG.o \
        ../noudar-core/noudar-core/src/Vec2i.o \
        ../noudar-core/noudar-core/src/CActor.o \
        ../noudar-core/noudar-core/src/CMonster.o \
        ../noudar-core/noudar-core/src/CMonsterGenerator.o \
        ../noudar-core/noudar-core/src/CTeam.o \
        ../noudar-core/noudar-core/src/CMap.o \
        ../noudar-core/noudar-core/src/Common.o \
        ../noudar-core/noudar-core/src/CCharacter.o \
        ../noudar-core/noudar-core/src/CCharacterArchetype.o \
        ../noudar-core/noudar-core/src/CDoorway.o \
        ../noudar-core/noudar-core/src/IFileLoaderDelegate.o \
        ../noudar-core/noudar-core/src/IMapElement.o \
        ../noudar-core/noudar-core/src/CGame.o \
        ../noudar-core/noudar-core/src/CItem.o \
        ../noudar-core/noudar-core/src/CStorageItem.o \
        ../noudar-core/noudar-core/src/CPlainFileLoader.o \
        ../noudar-core/noudar-core/src/CGameDelegate.o \
        ../noudar-core/noudar-core/src/commands/IGameCommand.o \
        ../noudar-core/noudar-core/src/commands/CMoveActorCommand.o \
        ../noudar-core/noudar-core/src/commands/CLoadNewLevelCommand.o \
        ../noudar-core/noudar-core/src/commands/CTurnActorCommand.o \
        ../noudar-core/noudar-core/src/commands/CQuitCommand.o \
        ../noudar-core/noudar-core/src/commands/CNullCommand.o \
        ../noudar-core/noudar-core/src/commands/CEndTurnCommand.o \
        ../noudar-core/noudar-core/src/commands/CPickItemCommand.o \
	../noudar-core/noudar-core/src/commands/CSuggestItemCommand.o \
        ../noudar-core/noudar-core/src/commands/CDropItemCommand.o \
        ../noudar-core/noudar-core/src/commands/CUseCurrentItemCommand.o \
        ../noudar-core/noudar-core/src/commands/CCycleNextItemCommand.o \
        ../noudar-core/noudar-core/src/commands/CCyclePreviousItemCommand.o

$(TARGET):	$(OBJS)
	$(CXX) -o $(TARGET) $(OBJS) $(LIBS)

TARGET = noudar.html

$(TARGET):	$(OBJS)
	$(CXX) -o $(TARGET) $(OBJS) $(LDFLAGS)

all:	$(TARGET)

clean:
	rm -f $(OBJS) $(TARGET)
	rm *~
	rm *.js
	rm *.mem
	rm *.html
	rm *.data

CXXFLAGS = -O3 -Wall -DEA_PLATFORM_SAMSUNG_TV  -DDONT_LOAD_LEVEL_UPON_START -DSDLSW -DVGA  -g  -IDOS-version -I../stb -I../fixed_point/include -fpermissive -Wno-narrowing  -c -std=c++14 -ferror-limit=1  -s USE_ZLIB=1 -s USE_LIBPNG=1 -I../noudar-core/noudar-core/include -Iinclude -I../noudar-rendering -I../gles2-renderer/ -s ALLOW_MEMORY_GROWTH=1 -I../noudar-core/EASTL/include -I../noudar-core/EASTL/test/packages/EABase/include/Common -DUSE_ITEMS_INSTANTLY  -fpermissive -Wno-narrowing
LDFLAGS =  -O3 -s USE_ZLIB=1 -s USE_LIBPNG=1 -s --preload-file ./data.pfs --use-preload-plugins  -s ALLOW_MEMORY_GROWTH=1
CXX = em++
CC = emcc

#CXXFLAGS = -Wall -std=c++14 -O3 --preload-file emdata --use-preload-plugins  -g -DSDLSW -DVGA -DEA_PLATFORM_SAMSUNG_TV -Iinclude -c -Istb -fpermissive -fomit-frame-pointer -fno-exceptions -ffast-math -I../fixed_point/include -IDOS-version -I../fixed_point/include -Iinclude -I../stb -I../noudar-core/noudar-core/include  -I../noudar-core/EASTL/include -I../noudar-core/EASTL/test/packages/EABase/include/Common -I../noudar-rendering -I../gles2-renderer -fpermissive -Wno-narrowing -DUSE_ITEMS_INSTANTLY -s DEMANGLE_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 -Wno-narrowing
#LDFLAGS = -O3 --preload-file emdata --use-preload-plugins  -s USE_ZLIB=1 -s USE_LIBPNG=1 -s -s DEMANGLE_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 ASSERTIONS=2

OBJS = main.o \
        events.o \
        src/CRenderer_Rasterization.o \
        src/CRenderer_Tesselation.o \
        SDLVersion/CSDLRenderer.o \
	    OPL2LPT/MusicSDL.o \
        src/CRenderer.o \
        src/RasterizerCommon.o \
        src/NativeBitmap.o \
        src/CTile3DProperties.o \
	src/CPackedFileReader.o \
	src/VisibilityStrategy.o \
	../gles2-renderer/Logger.o \
        ../gles2-renderer/LoadPNG.o \
        ../noudar-core/noudar-core/src/Vec2i.o \
        ../noudar-core/noudar-core/src/CActor.o \
        ../noudar-core/noudar-core/src/CMonster.o \
        ../noudar-core/noudar-core/src/CMonsterGenerator.o \
        ../noudar-core/noudar-core/src/CTeam.o \
        ../noudar-core/noudar-core/src/CMap.o \
        ../noudar-core/noudar-core/src/Common.o \
        ../noudar-core/noudar-core/src/CCharacter.o \
        ../noudar-core/noudar-core/src/CCharacterArchetype.o \
        ../noudar-core/noudar-core/src/CDoorway.o \
        ../noudar-core/noudar-core/src/IFileLoaderDelegate.o \
        ../noudar-core/noudar-core/src/IMapElement.o \
        ../noudar-core/noudar-core/src/CGame.o \
        ../noudar-core/noudar-core/src/CItem.o \
        ../noudar-core/noudar-core/src/CStorageItem.o \
        ../noudar-core/noudar-core/src/CPlainFileLoader.o \
        ../noudar-core/noudar-core/src/CGameDelegate.o \
        ../noudar-core/noudar-core/src/commands/IGameCommand.o \
        ../noudar-core/noudar-core/src/commands/CMoveActorCommand.o \
        ../noudar-core/noudar-core/src/commands/CLoadNewLevelCommand.o \
        ../noudar-core/noudar-core/src/commands/CTurnActorCommand.o \
        ../noudar-core/noudar-core/src/commands/CQuitCommand.o \
        ../noudar-core/noudar-core/src/commands/CNullCommand.o \
        ../noudar-core/noudar-core/src/commands/CEndTurnCommand.o \
        ../noudar-core/noudar-core/src/commands/CPickItemCommand.o \
	../noudar-core/noudar-core/src/commands/CSuggestItemCommand.o \
        ../noudar-core/noudar-core/src/commands/CDropItemCommand.o \
        ../noudar-core/noudar-core/src/commands/CUseCurrentItemCommand.o \
        ../noudar-core/noudar-core/src/commands/CCycleNextItemCommand.o \
        ../noudar-core/noudar-core/src/commands/CCyclePreviousItemCommand.o

$(TARGET):	$(OBJS)
	$(CXX) -o $(TARGET) $(OBJS) $(LIBS)

TARGET = noudar.html

$(TARGET):	$(OBJS)
	$(CXX) -o $(TARGET) $(OBJS) $(LDFLAGS)

all:	$(TARGET)

clean:
	rm -f $(OBJS) $(TARGET)
	rm *~
	rm *.js
	rm *.mem
	rm *.html
	rm *.data


Fix TreeView rows colors

Fix TreeView rows colors

Add parallel rules for fixing rows colors alternation in TreeViews,
e.g, in Log activity side list, for Gtk 3.16+.

Note that parser will skip the previous rules if he fails to parse
the new rules. Therefore, when adding "parallel" rules for newer
gtk versions, these rules must be added separately. Otherwise, it
will break the previous rules for older gtk versions.

Signed-off-by: Martin Abente Lahaye <8bfe93bb895fa79fd0e59550af8c97ff07863991@sugarlabs.org>

${
import math

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

}

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

    /* A lot of these will probably need to be changed, but this has to
       be done when the exact sizes are known */
    -GtkWidget-interior-focus: 0;

    /* we have to disable focus border for GtkTreeView, see #1261 */
    -GtkTreeView-interior-focus: 1;
    -GtkTreeView-focus-line-width: 0;

    -GtkTextView-interior-focus: 1;

    -GtkWidget-focus-line-width: 0;  /* Prevents some drawing glitches */
    -GtkEntry-focus-line-width: 0;
    -GtkScale-focus-line-width: 0;
    -GtkWidget-focus-padding: 0;
    /* 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;

    /* We fake the default border in the theme */
    -GtkButton-default-border: 0 0 0 0;
    -GtkButton-default-outside-border: 0 0 0 0;
    -GtkButton-image-spacing: $subcell_size;

    -GtkEntry-progress-border: $thickness $thickness $thickness $thickness;

    -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);
    -GtkToolbar-shadow-type: none;

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

    -GtkButtonBox-child-min-height: $(3*subcell_size);
    -GtkButtonBox-child-min-width: $(3*subcell_size);
    -GtkButtonBox-child-internal-pad-x: 0;
    -GtkButtonBox-child-internal-pad-y: 0;

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

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

*:insensitive {
    color: @button_grey;
}

/* Backgrounds and windows */

GtkWindow {
    background-color: @panel_grey;
    color: @black;
}

GtkDialog {
    background-color: @black;
    color: @white;
}

SugarIntroWindow {
    background-color: @white;
}

SugarIntroWindow {
    color: @black;
}

GtkLabel, GtkLabel:insensitive {
    background-color: 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-color: @button_grey;
    color: @white;
}

.button:focused {
    border-color: @white;
}

.button:active {
    background-color: @white;
}

.button:insensitive {
    background-color: transparent;
}

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

/* Spin buttons */

.spinbutton.button {
    border-radius: 0px;
    border-width: 0px;
    border-style: solid;
    background-color: @button_grey;
}

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

.spinbutton.button:active {
    background-color: @black;
}

.spinbutton.button:insensitive {
    background-color: @selection_grey;
}

/* Toggle buttons */

GtkToggleButton.button:active {
    background-color: @white;
    color: @black;
}

/* Entries and views */

.view {
    border-width: 0px;
    border-style: none;
    border-radius: 0px;
    padding: 0px;
    background-color: @white;
}

GtkTreeView {
    background-color: @button_grey;
}

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

GtkTreeView row:even {
    background-color: @row_even;
}
GtkTreeView row:odd {
    background-color: @row_odd;
}

GtkTreeView row:nth-child(even) {
    background-color: @row_even;
}
GtkTreeView row:nth-child(odd) {
    background-color: @row_odd;
}

.entry {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    border-color: @text_field_grey;
    border-style: solid;
    background-color: @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.progressbar {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    background-color: @selection_grey;
}

.entry:focused {
    background-color: @white;
}

.toolbar .entry:focused {
    border-color: @white;
}

.entry:insensitive {
    background-color: @button_grey;
}

.entry:insensitive {
    border-color: @button_grey;
}

.view:selected {
    background-color: @panel_grey;
    color: @black;
}

.entry:selected,
.entry:selected:focused,
.view:selected:focused {
    background-color: @selection_grey;
    color: @black;
}

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

/* Frames */

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

GtkFrame.frame.journal-preview-box {
    border-style: solid;
    border-color: @button_grey;
    border-width: $(thickness)px;
}

/* Combo boxes */

GtkComboBox {
    color: @white;
}

GtkComboBox .menu.button {
    border-color: transparent;
    border-radius: 0px;
    border-style: none;
    background-color: transparent;
    color: @white;
}

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

/* Notebooks */

.notebook {
    background-color: @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-color: @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 GtkLabel {
    color: @white;
    padding: $(subcell_size)px 0;
}

.notebook tab .button GtkLabel {
    color: @black;
}

.notebook tab:active {
    background-color: @toolbar_grey;
}

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

/* Browse Widgets */

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

/* Control panel */

SugarSectionView {
    background-color: @white;
    color: @black;
}

SugarSectionView *:insensitive {
    background-color: @white;
}

/* Alert */

SugarAlert {
    background-color: @black;
    color: @white;
}

SugarAlert *:insensitive {
    background-color: @black;
}

.button SugarTimeoutIcon GtkLabel,
.button SugarTimeoutIcon GtkLabel:prelight {
    background-color: @white;
    color: @button_grey;
    border-radius: $(2 * subcell_size)px;
}

.button SugarTimeoutIcon GtkLabel:active {
    background-color: @toolbar_grey;
    color: @white;
}

/* Tray */

SugarHTray, SugarVTray {
    background-color: @toolbar_grey;
}

SugarHTray * , SugarVTray * { background-color: @toolbar_grey;}

/* Menus and palettes */

SugarPaletteWindowWidget,
GtkPopover {
    border-width: $(thickness)px;
    border-color: @button_grey;
    border-style: solid;
    background-color: @black;
    color: @white;
}

SugarPaletteWindowWidget .view {
    color: @black;
}

SugarPaletteMenuWidget {
    background-color: @black;
}

SugarPaletteMenuWidget * {
    color: @white;
}

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

SugarPaletteWindow SugarGroupBox *:insensitive {
    background-color: @toolbar_grey;
}

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

    -GtkMenu-scroll-arrow-vlength: $(my_floor(subcell_size/0.7 + 2*thickness));
    -GtkMenu-horizontal-padding: $thickness;
    -GtkMenu-vertical-padding  : $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-width: 2px;
    border-color: @button_grey;
    border-style: solid;
}

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

.menuitem:prelight, .menuitem:hover {
    background-color: @button_grey;
}

.menuitem.separator {
    padding: 0;
}

.tooltip {
    background-color: @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-color: @button_grey;
    border-width: 0px;
}

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

.scrollbar.slider:active {
    background-color: @text_field_grey;
}

/* Progress bars */

/* Scrollbar padding hack? What was that about? */

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

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

.toolbar GtkProgressBar.trough,
SugarPaletteWindowWidget GtkProgressBar.trough {
    background-color: @black;
}

/* Separators */

.separator {
    border-style: solid;
    border-color: @button_grey;
    border-width: $(thickness)px;
}

/* Tool buttons */

.toolbar {
padding: 0px;
background-color: @toolbar_grey;
color: @white;
}

.toolbar .button,
SugarPaletteWindowWidget SugarRadioToolButton .button {
    border-color: transparent;
    border-radius: 0px;
    border-style: none;
}

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

.toolbar GtkToolButton .button,
.toolbar SugarRadioToolButton .button,
SugarPaletteWindowWidget SugarRadioToolButton .button,
SugarPaletteWindowWidget GtkToolButton .button {
    background-color: transparent;
    border-radius: $(toolbutton_padding)px;
    padding: $(toolbutton_padding)px;
}

.toolbar GtkToolButton .button,
SugarPaletteWindowWidget GtkToolButton .button:prelight {
    padding: $(toolbutton_padding - default_padding)px;
    border-width: $(default_padding)px;
    border-style: solid;
    background-clip: padding-box;
}

.toolbar GtkToolButton .button:prelight,
.toolbar GtkToolButton .button:prelight GtkBox,
SugarPaletteWindowWidget GtkToolButton .button:prelight {
    background-color: @black;
}

.toolbar SugarRadioToolButton *:active,
SugarPaletteWindowWidget SugarRadioToolButton *:active,
.toolbar SugarRadioToolButton *:active GtkBox,
.toolbar SugarRadioToolButton *:checked,
.toolbar SugarRadioToolButton *:checked GtkBox,
SugarPaletteWindowWidget SugarRadioToolButton *:checked,
.toolbar SugarToggleToolButton *:checked,
.toolbar SugarToggleToolButton *:checked GtkBox,
SugarPaletteWindowWidget SugarToggleToolButton *:checked {
    background-color: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

.toolbar SugarRadioToolButton *:checked:prelight,
.toolbar SugarRadioToolButton *:checked:prelight GtkBox,
SugarPaletteWindowWidget SugarRadioToolButton *:checked:prelight,
.toolbar SugarToggleToolButton *:checked:prelight,
.toolbar SugarToggleToolButton *:checked:prelight GtkBox,
SugarPaletteWindowWidget SugarToggleToolButton *:checked:prelight {
    background-color: @button_grey;
}

SugarPaletteWindowWidget SugarRadioToolButton *:checked:prelight,
SugarPaletteWindowWidget SugarToggleToolButton *:checked:prelight {
    border-color: @button_grey;
}

SugarPaletteWindowWidget GtkToolButton .button:active {
    background-color: @button_grey;
}

.toolbar GtkToolButton .button:active {
    background-color: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

SugarPaletteWindowWidget GtkScrolledWindow {
    background-color: @black;
}

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

/* Scales */

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

/* We have to override the color of the scale, otherwise the slider
   background image is invisible or not set for this palettes.
   Upstream bug: https://bugzilla.gnome.org/show_bug.cgi?id=686703 */
SugarPaletteWindowWidget .scale {
    color: transparent;
}

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

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

.scale.trough:focused {
    border-color: @white;
}

.scale.trough.top,
.scale.trough.left {
    background-color: @white;
}

.scale.trough.top:focused,
.scale.trough.left:focused {
    border-color: @selection_grey;
}

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

.scale.trough.vertical {
    border-radius: 0px 0px $(2*subcell_size)px $(2*subcell_size)px;
}

.scale.trough.top {
    border-radius: $(2*subcell_size)px $(2*subcell_size)px 0px 0px;
}

.scale.trough.left {
    border-radius: $(2*subcell_size)px 0px 0px $(2*subcell_size)px;
}

.scale.slider,
.scale.slider:active {
    background-color: transparent;
}

.scale.slider {
    background-image: url("assets/scale-slider.svg");
}

.scale.slider:active {
    background-image: url("assets/scale-slider-active.svg");
}

/* Radio and check buttons */

GtkCheckButton {
    border: 1px solid @black;
}

GtkCheckButton:prelight {
    background-color: alpha(@theme_base_color, 0.0);
}

.toolbar GtkCheckButton,.toolbar .check,
SugarPaletteWindowWidget .check {
    color: @theme_base_color;
    border: 1px solid @theme_base_color;
}

.radio,
.radio row:selected,
.radio row:selected:focused {
    background-image: url("assets/radio.svg");
    /* Gtk 3.14+ expect these to be included in icon theme however we want to
       override that */
    -gtk-icon-source: url("assets/radio.svg");
    background-color: alpha(@theme_base_color, 0.0);
}

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

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

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

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

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

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

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

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

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

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

.check:checked:selected,
.check:selected row:selected:checked,
.check:selected row:selected:focused: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;
    /* outline of the toolbarbutton when palette is expanded */
    border-style: solid;
    border-width: 2px;
    border-color: @button_grey;
}

/* GtkImage */

GtkImage {
    background-color: transparent;
}

/* Sugar Frame Window */

SugarFrameWindow {
    background-color: @toolbar_grey;
}

/* Sugar Canvas icon */

SugarCanvasIcon,
SugarKeepIcon.button {
    border-color: transparent;
    border-radius: $(4 * thickness)px;
    border-width: 2px;
    border-style: solid;
    background-color: transparent;
}

SugarCanvasIcon:prelight,
SugarKeepIcon.button:prelight {
    border-color: @zoom_views_prelight;
    background-color: @zoom_views_prelight;
}

SugarCanvasIcon:active,
SugarKeepIcon.button.toggle-press {
    border-color: @zoom_views_active;
    background-color: @zoom_views_active;
}

/* Sugar CellRenderer Icons */

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

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

.cell.sugar-icon-cell:active {
    background-color: @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-color: @white;
}

.cell.trough {
    background-color: @text_field_grey;
}

/* Text cursor handles */

.cursor-handle.top,
.cursor-handle.bottom {
    background-color: transparent;
    box-shadow: none;
    border-style: none;
    border-image: none;
    border-radius: 0px;
    border-width: 0px;
}

.cursor-handle.bottom {
    background-image: url("assets/cursor-handle-bottom.svg");
}

.cursor-handle.top {
    background-image: url("assets/cursor-handle-top.svg");
}

.cursor-handle.insertion-cursor {
    background-image: url("assets/cursor-handle-insertion.svg");
}

/* Application specific properties */

EvView {
    background-color: @panel_grey;
}

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

${
import math

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

}

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

    /* A lot of these will probably need to be changed, but this has to
       be done when the exact sizes are known */
    -GtkWidget-interior-focus: 0;

    /* we have to disable focus border for GtkTreeView, see #1261 */
    -GtkTreeView-interior-focus: 1;
    -GtkTreeView-focus-line-width: 0;

    -GtkTextView-interior-focus: 1;

    -GtkWidget-focus-line-width: 0;  /* Prevents some drawing glitches */
    -GtkEntry-focus-line-width: 0;
    -GtkScale-focus-line-width: 0;
    -GtkWidget-focus-padding: 0;
    /* 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;

    /* We fake the default border in the theme */
    -GtkButton-default-border: 0 0 0 0;
    -GtkButton-default-outside-border: 0 0 0 0;
    -GtkButton-image-spacing: $subcell_size;

    -GtkEntry-progress-border: $thickness $thickness $thickness $thickness;

    -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);
    -GtkToolbar-shadow-type: none;

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

    -GtkButtonBox-child-min-height: $(3*subcell_size);
    -GtkButtonBox-child-min-width: $(3*subcell_size);
    -GtkButtonBox-child-internal-pad-x: 0;
    -GtkButtonBox-child-internal-pad-y: 0;

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

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

*:insensitive {
    color: @button_grey;
}

/* Backgrounds and windows */

GtkWindow {
    background-color: @panel_grey;
    color: @black;
}

GtkDialog {
    background-color: @black;
    color: @white;
}

SugarIntroWindow {
    background-color: @white;
}

SugarIntroWindow {
    color: @black;
}

GtkLabel, GtkLabel:insensitive {
    background-color: 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-color: @button_grey;
    color: @white;
}

.button:focused {
    border-color: @white;
}

.button:active {
    background-color: @white;
}

.button:insensitive {
    background-color: transparent;
}

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

/* Spin buttons */

.spinbutton.button {
    border-radius: 0px;
    border-width: 0px;
    border-style: solid;
    background-color: @button_grey;
}

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

.spinbutton.button:active {
    background-color: @black;
}

.spinbutton.button:insensitive {
    background-color: @selection_grey;
}

/* Toggle buttons */

GtkToggleButton.button:active {
    background-color: @white;
    color: @black;
}

/* Entries and views */

.view {
    border-width: 0px;
    border-style: none;
    border-radius: 0px;
    padding: 0px;
    background-color: @white;
}

GtkTreeView {
    background-color: @button_grey;
}

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

GtkTreeView row:nth-child(even) {
    background-color: @row_even;
}
GtkTreeView row:nth-child(odd) {
    background-color: @row_odd;
}

.entry {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    border-color: @text_field_grey;
    border-style: solid;
    background-color: @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.progressbar {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    background-color: @selection_grey;
}

.entry:focused {
    background-color: @white;
}

.toolbar .entry:focused {
    border-color: @white;
}

.entry:insensitive {
    background-color: @button_grey;
}

.entry:insensitive {
    border-color: @button_grey;
}

.view:selected {
    background-color: @panel_grey;
    color: @black;
}

.entry:selected,
.entry:selected:focused,
.view:selected:focused {
    background-color: @selection_grey;
    color: @black;
}

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

/* Frames */

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

GtkFrame.frame.journal-preview-box {
    border-style: solid;
    border-color: @button_grey;
    border-width: $(thickness)px;
}

/* Combo boxes */

GtkComboBox {
    color: @white;
}

GtkComboBox .menu.button {
    border-color: transparent;
    border-radius: 0px;
    border-style: none;
    background-color: transparent;
    color: @white;
}

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

/* Notebooks */

.notebook {
    background-color: @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-color: @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 GtkLabel {
    color: @white;
    padding: $(subcell_size)px 0;
}

.notebook tab .button GtkLabel {
    color: @black;
}

.notebook tab:active {
    background-color: @toolbar_grey;
}

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

/* Browse Widgets */

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

/* Control panel */

SugarSectionView {
    background-color: @white;
    color: @black;
}

SugarSectionView *:insensitive {
    background-color: @white;
}

/* Alert */

SugarAlert {
    background-color: @black;
    color: @white;
}

SugarAlert *:insensitive {
    background-color: @black;
}

.button SugarTimeoutIcon GtkLabel,
.button SugarTimeoutIcon GtkLabel:prelight {
    background-color: @white;
    color: @button_grey;
    border-radius: $(2 * subcell_size)px;
}

.button SugarTimeoutIcon GtkLabel:active {
    background-color: @toolbar_grey;
    color: @white;
}

/* Tray */

SugarHTray, SugarVTray {
    background-color: @toolbar_grey;
}

SugarHTray * , SugarVTray * { background-color: @toolbar_grey;}

/* Menus and palettes */

SugarPaletteWindowWidget,
GtkPopover {
    border-width: $(thickness)px;
    border-color: @button_grey;
    border-style: solid;
    background-color: @black;
    color: @white;
}

SugarPaletteWindowWidget .view {
    color: @black;
}

SugarPaletteMenuWidget {
    background-color: @black;
}

SugarPaletteMenuWidget * {
    color: @white;
}

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

SugarPaletteWindow SugarGroupBox *:insensitive {
    background-color: @toolbar_grey;
}

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

    -GtkMenu-scroll-arrow-vlength: $(my_floor(subcell_size/0.7 + 2*thickness));
    -GtkMenu-horizontal-padding: $thickness;
    -GtkMenu-vertical-padding  : $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-width: 2px;
    border-color: @button_grey;
    border-style: solid;
}

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

.menuitem:prelight, .menuitem:hover {
    background-color: @button_grey;
}

.menuitem.separator {
    padding: 0;
}

.tooltip {
    background-color: @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-color: @button_grey;
    border-width: 0px;
}

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

.scrollbar.slider:active {
    background-color: @text_field_grey;
}

/* Progress bars */

/* Scrollbar padding hack? What was that about? */

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

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

.toolbar GtkProgressBar.trough,
SugarPaletteWindowWidget GtkProgressBar.trough {
    background-color: @black;
}

/* Separators */

.separator {
    border-style: solid;
    border-color: @button_grey;
    border-width: $(thickness)px;
}

/* Tool buttons */

.toolbar {
padding: 0px;
background-color: @toolbar_grey;
color: @white;
}

.toolbar .button,
SugarPaletteWindowWidget SugarRadioToolButton .button {
    border-color: transparent;
    border-radius: 0px;
    border-style: none;
}

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

.toolbar GtkToolButton .button,
.toolbar SugarRadioToolButton .button,
SugarPaletteWindowWidget SugarRadioToolButton .button,
SugarPaletteWindowWidget GtkToolButton .button {
    background-color: transparent;
    border-radius: $(toolbutton_padding)px;
    padding: $(toolbutton_padding)px;
}

.toolbar GtkToolButton .button,
SugarPaletteWindowWidget GtkToolButton .button:prelight {
    padding: $(toolbutton_padding - default_padding)px;
    border-width: $(default_padding)px;
    border-style: solid;
    background-clip: padding-box;
}

.toolbar GtkToolButton .button:prelight,
.toolbar GtkToolButton .button:prelight GtkBox,
SugarPaletteWindowWidget GtkToolButton .button:prelight {
    background-color: @black;
}

.toolbar SugarRadioToolButton *:active,
SugarPaletteWindowWidget SugarRadioToolButton *:active,
.toolbar SugarRadioToolButton *:active GtkBox,
.toolbar SugarRadioToolButton *:checked,
.toolbar SugarRadioToolButton *:checked GtkBox,
SugarPaletteWindowWidget SugarRadioToolButton *:checked,
.toolbar SugarToggleToolButton *:checked,
.toolbar SugarToggleToolButton *:checked GtkBox,
SugarPaletteWindowWidget SugarToggleToolButton *:checked {
    background-color: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

.toolbar SugarRadioToolButton *:checked:prelight,
.toolbar SugarRadioToolButton *:checked:prelight GtkBox,
SugarPaletteWindowWidget SugarRadioToolButton *:checked:prelight,
.toolbar SugarToggleToolButton *:checked:prelight,
.toolbar SugarToggleToolButton *:checked:prelight GtkBox,
SugarPaletteWindowWidget SugarToggleToolButton *:checked:prelight {
    background-color: @button_grey;
}

SugarPaletteWindowWidget SugarRadioToolButton *:checked:prelight,
SugarPaletteWindowWidget SugarToggleToolButton *:checked:prelight {
    border-color: @button_grey;
}

SugarPaletteWindowWidget GtkToolButton .button:active {
    background-color: @button_grey;
}

.toolbar GtkToolButton .button:active {
    background-color: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

SugarPaletteWindowWidget GtkScrolledWindow {
    background-color: @black;
}

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

/* Scales */

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

/* We have to override the color of the scale, otherwise the slider
   background image is invisible or not set for this palettes.
   Upstream bug: https://bugzilla.gnome.org/show_bug.cgi?id=686703 */
SugarPaletteWindowWidget .scale {
    color: transparent;
}

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

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

.scale.trough:focused {
    border-color: @white;
}

.scale.trough.top,
.scale.trough.left {
    background-color: @white;
}

.scale.trough.top:focused,
.scale.trough.left:focused {
    border-color: @selection_grey;
}

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

.scale.trough.vertical {
    border-radius: 0px 0px $(2*subcell_size)px $(2*subcell_size)px;
}

.scale.trough.top {
    border-radius: $(2*subcell_size)px $(2*subcell_size)px 0px 0px;
}

.scale.trough.left {
    border-radius: $(2*subcell_size)px 0px 0px $(2*subcell_size)px;
}

.scale.slider,
.scale.slider:active {
    background-color: transparent;
}

.scale.slider {
    background-image: url("assets/scale-slider.svg");
}

.scale.slider:active {
    background-image: url("assets/scale-slider-active.svg");
}

/* Radio and check buttons */

GtkCheckButton {
    border: 1px solid @black;
}

GtkCheckButton:prelight {
    background-color: alpha(@theme_base_color, 0.0);
}

.toolbar GtkCheckButton,.toolbar .check,
SugarPaletteWindowWidget .check {
    color: @theme_base_color;
    border: 1px solid @theme_base_color;
}

.radio,
.radio row:selected,
.radio row:selected:focused {
    background-image: url("assets/radio.svg");
    /* Gtk 3.14+ expect these to be included in icon theme however we want to
       override that */
    -gtk-icon-source: url("assets/radio.svg");
    background-color: alpha(@theme_base_color, 0.0);
}

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

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

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

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

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

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

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

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

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

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

.check:checked:selected,
.check:selected row:selected:checked,
.check:selected row:selected:focused: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;
    /* outline of the toolbarbutton when palette is expanded */
    border-style: solid;
    border-width: 2px;
    border-color: @button_grey;
}

/* GtkImage */

GtkImage {
    background-color: transparent;
}

/* Sugar Frame Window */

SugarFrameWindow {
    background-color: @toolbar_grey;
}

/* Sugar Canvas icon */

SugarCanvasIcon,
SugarKeepIcon.button {
    border-color: transparent;
    border-radius: $(4 * thickness)px;
    border-width: 2px;
    border-style: solid;
    background-color: transparent;
}

SugarCanvasIcon:prelight,
SugarKeepIcon.button:prelight {
    border-color: @zoom_views_prelight;
    background-color: @zoom_views_prelight;
}

SugarCanvasIcon:active,
SugarKeepIcon.button.toggle-press {
    border-color: @zoom_views_active;
    background-color: @zoom_views_active;
}

/* Sugar CellRenderer Icons */

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

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

.cell.sugar-icon-cell:active {
    background-color: @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-color: @white;
}

.cell.trough {
    background-color: @text_field_grey;
}

/* Text cursor handles */

.cursor-handle.top,
.cursor-handle.bottom {
    background-color: transparent;
    box-shadow: none;
    border-style: none;
    border-image: none;
    border-radius: 0px;
    border-width: 0px;
}

.cursor-handle.bottom {
    background-image: url("assets/cursor-handle-bottom.svg");
}

.cursor-handle.top {
    background-image: url("assets/cursor-handle-top.svg");
}

.cursor-handle.insertion-cursor {
    background-image: url("assets/cursor-handle-insertion.svg");
}

/* Application specific properties */

EvView {
    background-color: @panel_grey;
}

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


Make std.Graphics.GUI helpers around eval support JSON-encodable objects. Fixes #279.

/*  Sirikata
 *  graphics.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('std/escape.em');

if (typeof(std) === "undefined") std = {};
if (typeof(std.graphics) === "undefined") std.graphics = {};

(
function() {

    var ns = std.graphics;

    /** @namespace
     *  The GUI class wraps the underlying GUI Invokable
     *  objects. These present 2D user interface widgets to the user,
     *  which are coded as HTML + Javascript pages.
     *
     *  The callback is passed this GUI as a parameter.
     */
    std.graphics.GUI = function(name, me, onready) {
        this._name = name;
        this._gui = me;
        if (onready)
            this.bind('__ready', std.core.bind(onready, undefined, this));
    };

    /** Bind a listener for events from this GUI. */
    std.graphics.GUI.prototype.bind = function(type, cb) {
        this._gui.invoke("bind", this._name + '-' + type, cb);
    };

    /** Evaluate the Javascript string inside the GUI context. */
    std.graphics.GUI.prototype.eval = function(js) {
        this._gui.invoke("eval", js);
    };

    var getGUIJSValue = function(arg) {
        if (typeof(arg) === 'string')
            return Escape.escapeString(arg, '"');
        else if (typeof(arg) === 'undefined' ||
                 (typeof(arg) === 'object' && arg === null) ||
                 typeof(arg) === 'number' ||
                 typeof(arg) === 'boolean')
            return arg.toString();
        else {
            // Non-trivial type. Try to JSONify it.
            try {
                var arg_json = JSON.stringify(arg);
                // And wrap in decoding call
                return 'JSON.parse(' + Escape.escapeString(arg_json) + ')';
            } catch (x) {
                throw new TypeError("Invalid object type passed to GUI.call. Must be basic type (undefined, null, boolean, number, string) or JSON-encodable.");
            }
        }
    };

    /** Call a method in the GUI context. This is just a convenience
     *  wrapper around eval which will build the string to eval
     *  automatically from the method arguments.
     *
     *  Example:
     *   var x = 7;
     *   var st = 'hello "Bob"';
     *   gui.call('myfunc', x, st);
     *  is equivalent to
     *   gui.eval( "myfunc(7, 'hello \"Bob\"');" );
     */
    std.graphics.GUI.prototype.call = function() {
        if (arguments.length < 1) return;
        var methodname = arguments[0];

        var ev_str = methodname + '(';

        for(var i = 1; i < arguments.length; i++) {
            if (i > 1) ev_str += ', ';

            var arg = arguments[i];
            ev_str += getGUIJSValue(arg);
        }

        ev_str += ')';

        this.eval(ev_str);
    };

    /** Set the value of a variable in the GUI context. This is just a
     *  convenience wrapper around eval which will build the string to
     *  eval automatically from the arguments.
     *
     *  Example:
     *   gui.set('myvar', 17)
     *  is equivalent to
     *   gui.eval('var myvar = 17;');
     *
     */
    std.graphics.GUI.prototype.set = function(varname, value) {
        var ev_str = varname + ' = ' + getGUIJSValue(value) + ';';
        this.eval(ev_str);
    };

    /** Hides the GUI window. */
    std.graphics.GUI.prototype.hide = function() {
        this._gui.invoke("hide");
    };

    /** Shows the GUI window. */
    std.graphics.GUI.prototype.show = function() {
        this._gui.invoke("show");
    };

    /** Shows the GUI window. */
    std.graphics.GUI.prototype.focus = function() {
        this._gui.invoke("focus");
    };

    /** Bind a listener for exception events. Listener should have the form fn(message, file, linenum). */
    std.graphics.GUI.prototype.onException = function(cb) {
        this._gui.invoke("onException", cb);
    };
})();

/*  Sirikata
 *  graphics.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('std/escape.em');

if (typeof(std) === "undefined") std = {};
if (typeof(std.graphics) === "undefined") std.graphics = {};

(
function() {

    var ns = std.graphics;

    /** @namespace
     *  The GUI class wraps the underlying GUI Invokable
     *  objects. These present 2D user interface widgets to the user,
     *  which are coded as HTML + Javascript pages.
     *
     *  The callback is passed this GUI as a parameter.
     */
    std.graphics.GUI = function(name, me, onready) {
        this._name = name;
        this._gui = me;
        if (onready)
            this.bind('__ready', std.core.bind(onready, undefined, this));
    };

    /** Bind a listener for events from this GUI. */
    std.graphics.GUI.prototype.bind = function(type, cb) {
        this._gui.invoke("bind", this._name + '-' + type, cb);
    };

    /** Evaluate the Javascript string inside the GUI context. */
    std.graphics.GUI.prototype.eval = function(js) {
        this._gui.invoke("eval", js);
    };

    var getGUIJSValue = function(arg) {
        if (typeof(arg) === 'string')
            return Escape.escapeString(arg, '"');
        else if (typeof(arg) === 'undefined' ||
                 (typeof(arg) === 'object' && arg === null) ||
                 typeof(arg) === 'number' ||
                 typeof(arg) === 'boolean')
            return arg.toString();
        else
            throw new TypeError("Invalid object type passed to GUI.call.");
    };

    /** Call a method in the GUI context. This is just a convenience
     *  wrapper around eval which will build the string to eval
     *  automatically from the method arguments.
     *
     *  Example:
     *   var x = 7;
     *   var st = 'hello "Bob"';
     *   gui.call('myfunc', x, st);
     *  is equivalent to
     *   gui.eval( "myfunc(7, 'hello \"Bob\"');" );
     */
    std.graphics.GUI.prototype.call = function() {
        if (arguments.length < 1) return;
        var methodname = arguments[0];

        var ev_str = methodname + '(';

        for(var i = 1; i < arguments.length; i++) {
            if (i > 1) ev_str += ', ';

            var arg = arguments[i];
            ev_str += getGUIJSValue(arg);
        }

        ev_str += ')';

        this.eval(ev_str);
    };

    /** Set the value of a variable in the GUI context. This is just a
     *  convenience wrapper around eval which will build the string to
     *  eval automatically from the arguments.
     *
     *  Example:
     *   gui.set('myvar', 17)
     *  is equivalent to
     *   gui.eval('var myvar = 17;');
     *
     */
    std.graphics.GUI.prototype.set = function(varname, value) {
        var ev_str = varname + ' = ' + getGUIJSValue(value) + ';';
        this.eval(ev_str);
    };

    /** Hides the GUI window. */
    std.graphics.GUI.prototype.hide = function() {
        this._gui.invoke("hide");
    };

    /** Shows the GUI window. */
    std.graphics.GUI.prototype.show = function() {
        this._gui.invoke("show");
    };

    /** Shows the GUI window. */
    std.graphics.GUI.prototype.focus = function() {
        this._gui.invoke("focus");
    };

    /** Bind a listener for exception events. Listener should have the form fn(message, file, linenum). */
    std.graphics.GUI.prototype.onException = function(cb) {
        this._gui.invoke("onException", cb);
    };
})();


create -> delete -> create -> delete test

describe "rest", ->

  adapter = null
  session = null

  beforeEach ->
    require('./_shared').setupRest.apply(this)
    adapter = @adapter
    session = @session

  context 'simple model', ->

    beforeEach ->
      class @Post extends Ep.Model
        title: Ep.attr('string')
      @App.Post = @Post

      @container.register 'model:post', @Post

    it 'loads', ->
      adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw'}
      session.load(@Post, 1).then (post) ->
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['GET:/posts/1'])

    it 'saves', ->
      adapter.r['POST:/posts'] = -> posts: {client_id: post.clientId, id: 1, title: 'mvcc ftw'}

      post = session.create('post')
      post.title = 'mvcc ftw'

      session.flush().then ->
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['POST:/posts'])

    it 'updates', ->
      adapter.r['PUT:/posts/1'] = -> posts: {id: 1, title: 'updated'}

      session.merge @Post.create(id: "1", title: 'test')

      session.load('post', 1).then (post) ->
        expect(post.title).to.eq('test')
        post.title = 'updated'
        session.flush().then ->
          expect(post.title).to.eq('updated')
          expect(adapter.h).to.eql(['PUT:/posts/1'])

    it 'updates multiple times', ->
      adapter.r['PUT:/posts/1'] = -> posts: {id: 1, title: 'updated'}

      post = session.merge @Post.create(id: "1", title: 'test')

      expect(post.title).to.eq('test')
      post.title = 'updated'

      session.flush().then ->
        expect(post.title).to.eq('updated')
        expect(adapter.h).to.eql(['PUT:/posts/1'])

        adapter.r['PUT:/posts/1'] = -> posts: {id: 1, title: 'updated again'}
        post.title = 'updated again'
        session.flush().then ->
          expect(post.title).to.eq('updated again')
          expect(adapter.h).to.eql(['PUT:/posts/1', 'PUT:/posts/1'])

          session.flush().then -> # NO-OP
            expect(post.title).to.eq('updated again')
            expect(adapter.h).to.eql(['PUT:/posts/1', 'PUT:/posts/1'])

    it 'deletes', ->
      adapter.r['DELETE:/posts/1'] = {}

      session.merge @Post.create(id: "1", title: 'test')

      session.load('post', 1).then (post) ->
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('test')
        session.deleteModel(post)
        session.flush().then ->
          expect(post.isDeleted).to.be.true
          expect(adapter.h).to.eql(['DELETE:/posts/1'])

    it 'deletes multiple times in multiple flushes', ->
      adapter.r['DELETE:/posts/1'] = {}

      post1 = session.merge @Post.create(id: "1", title: 'thing 1')
      post2 = session.merge @Post.create(id: "2", title: 'thing 2')

      session.deleteModel post1

      session.flush().then ->

        expect(post1.isDeleted).to.be.true
        expect(post2.isDeleted).to.be.false

        adapter.r['DELETE:/posts/1'] = -> throw "already deleted"
        adapter.r['DELETE:/posts/2'] = {}

        session.deleteModel post2

        session.flush().then ->
          expect(post1.isDeleted).to.be.true
          expect(post2.isDeleted).to.be.true

    it 'creates, deletes, creates, deletes', ->
      post1 = session.create('post')
      post1.title = 'thing 1'

      adapter.r['POST:/posts'] = -> posts: {client_id: post1.clientId, id: 1, title: 'thing 1'}
      session.flush().then ->
        expect(post1.id).to.eq('1')
        expect(post1.title).to.eq('thing 1')
        session.deleteModel(post1)

        adapter.r['DELETE:/posts/1'] = {}

        session.flush().then ->
          expect(post1.isDeleted).to.be.true
          post2 = session.create('post')
          post2.title = 'thing 2'

          adapter.r['POST:/posts'] = -> posts: {client_id: post2.clientId, id: 2, title: 'thing 2'}

          session.flush().then ->

            adapter.r['DELETE:/posts/1'] = -> throw 'not found'
            adapter.r['DELETE:/posts/2'] = {}

            expect(post2.id).to.eq('2')
            expect(post2.title).to.eq('thing 2')
            session.deleteModel(post2)

            session.flush().then ->
              expect(post2.isDeleted).to.be.true
              expect(adapter.h).to.eql(['POST:/posts', 'DELETE:/posts/1', 'POST:/posts', 'DELETE:/posts/2'])

    it 'refreshes', ->
      adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'something new'}

      session.merge @Post.create(id: "1", title: 'test')

      session.load(@Post, 1).then (post) ->
        expect(post.title).to.eq('test')
        expect(adapter.h).to.eql([])
        session.refresh(post).then (post) ->
          expect(post.title).to.eq('something new')
          expect(adapter.h).to.eql(['GET:/posts/1'])

    it 'finds', ->
      adapter.r['GET:/posts'] = (url, type, hash) ->
        expect(hash.data).to.eql({q: "aardvarks"})
        posts: [{id: 1, title: 'aardvarks explained'}, {id: 2, title: 'aardvarks in depth'}]

      session.find('post', {q: 'aardvarks'}).then (models) ->
        expect(models.length).to.eq(2)
        expect(adapter.h).to.eql(['GET:/posts'])

    it 'loads then updates', ->
      adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw'}
      adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'no more fsm'}

      session.load(@Post, 1).then (post) ->
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['GET:/posts/1'])

        post.title = 'no more fsm'
        session.flush().then ->
          expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])
          expect(post.title).to.eq('no more fsm')

    it 'loads with parameter', ->
      adapter.r['GET:/posts/1'] = (url, type, hash) ->
        expect(hash.data.invite_token).to.eq('fdsavcxz')
        posts: {id: 1, title: 'mvcc ftw'}
      session.load(@Post, 1, params: {invite_token: 'fdsavcxz'}).then (post) ->
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['GET:/posts/1'])

describe "rest", ->

  adapter = null
  session = null

  beforeEach ->
    require('./_shared').setupRest.apply(this)
    adapter = @adapter
    session = @session

  context 'simple model', ->

    beforeEach ->
      class @Post extends Ep.Model
        title: Ep.attr('string')
      @App.Post = @Post

      @container.register 'model:post', @Post

    it 'loads', ->
      adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw'}
      session.load(@Post, 1).then (post) ->
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['GET:/posts/1'])

    it 'saves', ->
      adapter.r['POST:/posts'] = -> posts: {client_id: post.clientId, id: 1, title: 'mvcc ftw'}

      post = session.create('post')
      post.title = 'mvcc ftw'

      session.flush().then ->
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['POST:/posts'])

    it 'updates', ->
      adapter.r['PUT:/posts/1'] = -> posts: {id: 1, title: 'updated'}

      session.merge @Post.create(id: "1", title: 'test')

      session.load('post', 1).then (post) ->
        expect(post.title).to.eq('test')
        post.title = 'updated'
        session.flush().then ->
          expect(post.title).to.eq('updated')
          expect(adapter.h).to.eql(['PUT:/posts/1'])

    it 'updates multiple times', ->
      adapter.r['PUT:/posts/1'] = -> posts: {id: 1, title: 'updated'}

      post = session.merge @Post.create(id: "1", title: 'test')

      expect(post.title).to.eq('test')
      post.title = 'updated'

      session.flush().then ->
        expect(post.title).to.eq('updated')
        expect(adapter.h).to.eql(['PUT:/posts/1'])

        adapter.r['PUT:/posts/1'] = -> posts: {id: 1, title: 'updated again'}
        post.title = 'updated again'
        session.flush().then ->
          expect(post.title).to.eq('updated again')
          expect(adapter.h).to.eql(['PUT:/posts/1', 'PUT:/posts/1'])

          session.flush().then -> # NO-OP
            expect(post.title).to.eq('updated again')
            expect(adapter.h).to.eql(['PUT:/posts/1', 'PUT:/posts/1'])

    it 'deletes', ->
      adapter.r['DELETE:/posts/1'] = {}

      session.merge @Post.create(id: "1", title: 'test')

      session.load('post', 1).then (post) ->
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('test')
        session.deleteModel(post)
        session.flush().then ->
          expect(post.isDeleted).to.be.true
          expect(adapter.h).to.eql(['DELETE:/posts/1'])

    it 'deletes multiple times in multiple flushes', ->
      adapter.r['DELETE:/posts/1'] = {}

      post1 = session.merge @Post.create(id: "1", title: 'thing 1')
      post2 = session.merge @Post.create(id: "2", title: 'thing 2')

      session.deleteModel post1

      session.flush().then ->

        expect(post1.isDeleted).to.be.true
        expect(post2.isDeleted).to.be.false

        adapter.r['DELETE:/posts/1'] = -> throw "already deleted"
        adapter.r['DELETE:/posts/2'] = {}

        session.deleteModel post2

        session.flush().then ->
          expect(post1.isDeleted).to.be.true
          expect(post2.isDeleted).to.be.true

    it 'refreshes', ->
      adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'something new'}

      session.merge @Post.create(id: "1", title: 'test')

      session.load(@Post, 1).then (post) ->
        expect(post.title).to.eq('test')
        expect(adapter.h).to.eql([])
        session.refresh(post).then (post) ->
          expect(post.title).to.eq('something new')
          expect(adapter.h).to.eql(['GET:/posts/1'])

    it 'finds', ->
      adapter.r['GET:/posts'] = (url, type, hash) ->
        expect(hash.data).to.eql({q: "aardvarks"})
        posts: [{id: 1, title: 'aardvarks explained'}, {id: 2, title: 'aardvarks in depth'}]

      session.find('post', {q: 'aardvarks'}).then (models) ->
        expect(models.length).to.eq(2)
        expect(adapter.h).to.eql(['GET:/posts'])

    it 'loads then updates', ->
      adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw'}
      adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'no more fsm'}

      session.load(@Post, 1).then (post) ->
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['GET:/posts/1'])

        post.title = 'no more fsm'
        session.flush().then ->
          expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])
          expect(post.title).to.eq('no more fsm')

    it 'loads with parameter', ->
      adapter.r['GET:/posts/1'] = (url, type, hash) ->
        expect(hash.data.invite_token).to.eq('fdsavcxz')
        posts: {id: 1, title: 'mvcc ftw'}
      session.load(@Post, 1, params: {invite_token: 'fdsavcxz'}).then (post) ->
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['GET:/posts/1'])


Add ros2_distro parameter

Add ros2_distro parameter

and set repos URL to null to default to https://raw.githubusercontent.com/ros2/ros2/$ROS_DISTRO/ros2.repos

%YAML 1.1
# ROS2 Dockerfile database
---
images:
    source:
        base_image: @(os_name):@(os_code_name)
        maintainer_name: @(maintainer_name)
        template_name: docker_images_ros2/source/create_ros_image.Dockerfile.em
        entrypoint_name: docker_images_ros2/source/ros_entrypoint.sh
        template_packages:
            - docker_templates
        upstream_packages:
            - bash-completion
            - git
            - libasio-dev
            - libtinyxml2-dev
            - wget
        ros2_distro: crystal
        ros2_repo_packages:
            - python3-catkin-pkg-modules
            - python3-rosdep
            - python3-vcstool
        pip3_install:
            - argcomplete
            - flake8
            - flake8-blind-except
            - flake8-builtins
            - flake8-class-newline
            - flake8-comprehensions
            - flake8-deprecated
            - flake8-docstrings
            - flake8-import-order
            - flake8-quotes
            - pytest-repeat
            - pytest-rerunfailures
        ws: /root/ros2_ws
        colcon_args:
            - build
            - --cmake-args
                -DSECURITY=ON --no-warn-unused-cli
            - --symlink-install
        rosdep:
            install:
                - --from-paths src
                - --ignore-src
                - --skip-keys "console_bridge fastcdr fastrtps libopensplice67 libopensplice69 rti-connext-dds-5.3.1 urdfdom_headers"
        vcs:
            ros2:
                repos:

%YAML 1.1
# ROS2 Dockerfile database
---
images:
    source:
        base_image: @(os_name):@(os_code_name)
        maintainer_name: @(maintainer_name)
        template_name: docker_images_ros2/source/create_ros_image.Dockerfile.em
        entrypoint_name: docker_images_ros2/source/ros_entrypoint.sh
        template_packages:
            - docker_templates
        upstream_packages:
            - bash-completion
            - git
            - libasio-dev
            - libtinyxml2-dev
            - wget
        ros2_repo_packages:
            - python3-catkin-pkg-modules
            - python3-rosdep
            - python3-vcstool
        pip3_install:
            - argcomplete
            - flake8
            - flake8-blind-except
            - flake8-builtins
            - flake8-class-newline
            - flake8-comprehensions
            - flake8-deprecated
            - flake8-docstrings
            - flake8-import-order
            - flake8-quotes
            - pytest-repeat
            - pytest-rerunfailures
        ws: /root/ros2_ws
        colcon_args:
            - build
            - --cmake-args
                -DSECURITY=ON --no-warn-unused-cli
            - --symlink-install
        rosdep:
            install:
                - --from-paths src
                - --ignore-src
                - --rosdistro crystal
                - --skip-keys "console_bridge fastcdr fastrtps libopensplice67 libopensplice69 rti-connext-dds-5.3.1 urdfdom_headers"
        vcs:
            ros2:
                repos: https://raw.githubusercontent.com/ros2/ros2/release-latest/ros2.repos