Pad menus differently so that no item is selected in popup menus (#1350)

${
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 = 9
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 = 6      # 7.68

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

# Colors:

black = '"#000000"'
toolbar_grey = '"#282828"'
button_grey = '"#808080"'
selection_grey = '"#A6A6A6"'
panel_grey = '"#C0C0C0"'
text_field_grey = '"#E5E5E5"'
white = '"#FFFFFF"'

}
${
icon_small = icon_base * 3
icon_large = icon_base * 5

small_icons = [ "gtk-menu", "gtk-dnd", "gtk-small-toolbar", "gtk-button" ]
large_icons = [ "gtk-large-toolbar" ]

icon_sizes = []
for icon in small_icons:
    icon_sizes += [icon + "=" + str(icon_small) + ',' + str(icon_small)]
for icon in large_icons:
    icon_sizes += [icon + "=" + str(icon_large) + ',' + str(icon_large)]

icon_sizes = ":".join(icon_sizes)

}
gtk-icon-sizes="$icon_sizes"

style "default"
{
    # Will be overriden in some widgets
    color["focus_line"] = $white

    bg[NORMAL] = $button_grey
    bg[PRELIGHT] = $button_grey
    bg[ACTIVE] = $white
    bg[SELECTED] = $text_field_grey

    fg[NORMAL]        = $black
    fg[ACTIVE]        = $black
    fg[SELECTED]      = $white
    fg[PRELIGHT]      = $white

    text[NORMAL] = $black # button_grey? 1. need to patch GTK+ to use ACTIVE when focused, 2. what color should this really be?
    text[ACTIVE] = $black
    text[SELECTED] = $black
    text[PRELIGHT] = $black

    base[NORMAL] = $text_field_grey
    base[ACTIVE] = $white
    base[SELECTED] = $selection_grey
    base[PRELIGHT] = $white

    fg[INSENSITIVE] = $button_grey
    text[INSENSITIVE] = $button_grey

    xthickness = $thickness
    ythickness = $thickness

    # 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
    GtkWidget::focus-line-width = 1   # Prevents some drawing glitches
    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 = 0
    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 = 24
    GtkExpander::expander-spacing = 2     # XXX

    GtkTreeView::expander-size = 24

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

    GtkArrow::arrow-size = 1.0

    GtkToolbar::shadow-type = GTK_SHADOW_NONE
    GtkToolbar::space-size = $(2*subcell_size)

    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

    engine "sugar" {
        line_width = $line_width
        thick_line_width = $thick_line_width
        subcell_size = $subcell_size

        max_radius = $( 2*subcell_size )
    }
}

#############################################################
# Styles that set default background/foreground colors
#
#  We almost always need two styles because of the X windows.
#  For example bg[NORMAL] of a window (or eventbox) needs to
#  be set to be the correct window color so that X fills the
#  window correctly from the beginning, and it does not
#  flash in the wrong color.
#############################################################

style "window"
{
    bg[NORMAL] = $panel_grey
}

style "window-child"
{
    bg[INSENSITIVE] = $panel_grey
    base[INSENSITIVE] = $panel_grey

    fg[NORMAL] = $black

    engine "sugar" {
        parent_fg_color = $black
        parent_bg_color = $panel_grey
    }
}

style "white-bg"
{
    bg[NORMAL] = $white
}

style "white-bg-child"
{
    bg[INSENSITIVE] = $white
    base[INSENSITIVE] = $white

    fg[NORMAL] = $black

    engine "sugar" {
        parent_fg_color = $black
        parent_bg_color = $white
    }
}

style "black-bg"
{
    bg[NORMAL] = $black
}

style "black-bg-child"
{
    bg[INSENSITIVE] = $black
    base[INSENSITIVE] = $black

    fg[NORMAL] = $white

    engine "sugar" {
        parent_fg_color = $white
        parent_bg_color = $black
    }
}

style "groupbox-panel"
{
    bg[NORMAL] = $selection_grey
}

style "groupbox-panel-child"
{
    bg[INSENSITIVE] = $selection_grey
    base[INSENSITIVE] = $selection_grey

    fg[NORMAL] = $black

    engine "sugar" {
        parent_fg_color = $black
        parent_bg_color = $selection_grey
    }
}

style "groupbox-palette"
{
    bg[NORMAL] = $toolbar_grey
}

style "groupbox-palette-child"
{
    bg[INSENSITIVE] = $toolbar_grey
    base[INSENSITIVE] = $toolbar_grey

    fg[NORMAL] = $white

    engine "sugar" {
        parent_fg_color = $white
        parent_bg_color = $toolbar_grey
    }
}

style "menu"
{
    bg[NORMAL] = $black
    # Used for the arrow colour
    fg[NORMAL]   = $white
    fg[PRELIGHT] = $white
    bg[ACTIVE] = $button_grey

    ${# This is just the exact reverse of what is going on inside GTK+ ...}
    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  = 0
    GtkMenu::vertical-offset    = 0

    xthickness = 0
    ythickness = 0
}

style "palette" = "menu"
{
    xthickness = $thickness
    ythickness = $thickness
}

style "palette-menu" = "menu"
{
    GtkMenu::horizontal-padding = 0
    GtkMenu::vertical-padding   = 0

    xthickness = 0
    ythickness = $subcell_size
}

# Can this all be moved in the menuitem style?
style "menu-child"
{
    color["bg_color"] = $black

    fg[NORMAL]   = $white
    fg[PRELIGHT] = $white

    bg[NORMAL] = $button_grey

    bg[INSENSITIVE] = $black
    base[INSENSITIVE] = $black

    engine "sugar" {
        parent_fg_color = $white
        parent_bg_color = $black
    }
}

#########################################################
#########################################################

style "textview"
{
    # We always use white here
    base[NORMAL]      = $white

    GtkTextView::interior-focus = 1
}

style "scrollbar"
{
    GtkScrollbar::slider-width = $subcell_size
    GtkScrollbar::trough-border = 0
    GtkScrollbar::stepper-spacing = 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

    # Hack for mozilla as it does not obey the above style properties ...
    # Really, mozilla should be fixed ...
    GtkScrollbar::stepper-size = 1
}

# hints for the trough padding hacks
# We cannot go trough GTK+, as we don't want a padding on all sides, but only one
# If we wanted padding on all sides, trough-border would work great.
style "hscrollbar" = "scrollbar"
{
    engine "sugar" {
        hint = "hscrollbar"
        scrollbar_border = $thickness
    }
}

style "vscrollbar" = "scrollbar"
{
    engine "sugar" {
        hint = "vscrollbar"
        scrollbar_border = $thickness
    }
}

style "hscrollbar-scrolled-window"
{
    GtkScrollbar::slider-width = $(subcell_size - thickness)
    GtkScrollbar::stepper-spacing = $thickness
    GtkScrollbar::has-forward-stepper = 1
    GtkScrollbar::has-backward-stepper = 1
    GtkScrollbar::stepper-size = 0

    engine "sugar" {
        hint = "hscrollbar-scrolled-window"
    }
}

style "vscrollbar-scrolled-window"
{
    GtkScrollbar::slider-width = $(subcell_size - thickness)
    GtkScrollbar::stepper-spacing = $thickness
    GtkScrollbar::has-forward-stepper = 1
    GtkScrollbar::has-backward-stepper = 1
    GtkScrollbar::stepper-size = 0

    engine "sugar" {
        hint = "vscrollbar-scrolled-window"
    }
}

style "scale"
{
    GtkWidget::focus-line-width = 0

    bg[NORMAL] = $button_grey

    # "square" slider (really round of course)
    # Same as indicator-size?
    GtkRange::slider-width = $scale_slider_width
    GtkScale::slider-length = $scale_slider_width
    GtkRange::trough-border = 0
    GtkRange::stepper-spacing = 0
    GtkRange::trough-side-details = 1
}

# hints for the trough sizing hacks
style "hscale" = "scale"
{
    engine "sugar" {
        hint = "hscale"
    }
}

style "vscale" = "scale"
{
    engine "sugar" {
        hint = "vscale"
    }
}

style "spinbutton"
{
    bg[NORMAL]  = $button_grey
    bg[ACTIVE]  = $white
    fg[NORMAL]  = $white
    fg[ACTIVE]  = $black

    ${ spin_ythickness = my_ceil(0.2 * (subcell_size*3.0/2.0 - thickness) + thickness) }
    ${ spin_xthickness = subcell_size }

    # small inner border and a large x/ythickness for entries
    # to reduce the number of hacks needed :-)
    xthickness = $spin_xthickness
    ythickness = $spin_ythickness
    GtkWidget::focus-line-width = 0

    # This tries to get a height of exactly 45 pixel for the entry.
    GtkEntry::inner-border = { $(max(subcell_size - spin_xthickness, 0)), $(max(subcell_size - spin_xthickness, 0)),
                               $(max(my_ceil((3*subcell_size - font_height - spin_ythickness*2)/2.0),0)), $(max(my_floor((3*subcell_size - font_height - spin_ythickness*2)/2.0), 0)) }

    engine "sugar" {
        hint = "spinbutton"
    }
}

style "frame"
{
    bg[NORMAL] = $toolbar_grey

    engine "sugar" {
        parent_bg_color = $toolbar_grey
        parent_fg_color = $white
    }
}

style "notebook-tab"
{
    bg[ACTIVE] = $button_grey
    fg[ACTIVE] = $white
}

style "notebook-panel"
{
    GtkNotebook::tab-overlap = -2
    GtkNotebook::tab-curvature = $default_padding

    xthickness = 0
    ythickness = 0

    bg[NORMAL] = $selection_grey
}

style "toolbox-notebook"
{
    GtkNotebook::tab-overlap = -2

    xthickness = 0
    ythickness = 0
}

style "toolbutton"
{
    xthickness = 0
    ythickness = 0
    GtkButton::inner-border = { $toolbutton_padding,
                                $toolbutton_padding,
                                $toolbutton_padding,
                                $toolbutton_padding }
    GtkWidget::focus-padding = 0

    bg[PRELIGHT] = $black
    bg[ACTIVE] = $button_grey
    bg[NORMAL] = $black

    fg[NORMAL] = $white
    fg[ACTIVE] = $white
    fg[PRELIGHT] = $white

    engine "sugar" {
        max_radius = 5.0
        fake_padding = $default_padding
    }
}

style "separatortoolbutton"
{
    # The sizing is handled in the engine.
    bg[NORMAL] = $button_grey
}

style "toolbar"
{
    xthickness = 0
    ythickness = 0

    bg[NORMAL] = $toolbar_grey
    bg[INSENSITIVE] = $toolbar_grey
    base[INSENSITIVE] = $toolbar_grey

    engine "sugar" {
        parent_fg_color = $white
        parent_bg_color = $toolbar_grey
    }
}

style "toolbox"
{
    fg[NORMAL] = $white
    fg[ACTIVE] = $white

    bg[NORMAL] = $toolbar_grey
    bg[INSENSITIVE] = $toolbar_grey
    base[INSENSITIVE] = $toolbar_grey

    engine "sugar" {
        parent_fg_color = $white
        parent_bg_color = $toolbar_grey
    }
}

style "panel"
{
    bg[NORMAL] = $panel_grey
    fg[NORMAL] = $black
}

style "entry"
{
    color["focus_line"] = $text_field_grey

    ${ 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) }

    # small inner border and a large x/ythickness for entries
    # to reduce the number of hacks needed :-)
    xthickness = $entry_xthickness
    ythickness = $entry_ythickness
    GtkWidget::focus-line-width = 0

    # Colors for the progress bar.
    bg[SELECTED] = $selection_grey
    fg[SELECTED] = $black

    # This tries to get a height of exactly 45 pixel for the entry.
    GtkEntry::inner-border = { $(max(subcell_size - entry_xthickness, 0)), $(max(subcell_size - entry_xthickness, 0)),
                               $(max(my_ceil((3*subcell_size - font_height - entry_ythickness*2)/2.0),0)), $(max(my_floor((3*subcell_size - font_height - entry_ythickness*2)/2.0), 0)) }
}

style "button"
{
    ${ border = max(0, my_ceil((3*subcell_size/2.0 - icon_small / 2.0))) }
    # It would be nicer to just set the inner-border, but that does not work
    # for combo boxes ... The combobox ignores it, so set it to 0px
    # See http://bugzilla.gnome.org/show_bug.cgi?id=485762

    GtkButton::inner-border = { 0, 0, 0, 0 }

    # So set the x/ythickness; y-padding is $subcell_size overall
    xthickness = $(border)
    ythickness = $(border)

    fg[NORMAL] = $white
    fg[ACTIVE] = $black

    bg[NORMAL] = $button_grey
}

style "combobox"
{
    # "Disable" the ACTIVE state of the button, it only creates problems :-(
    bg[ACTIVE] = $button_grey
    fg[ACTIVE] = $white

    # Work around GTK+ bug #382646
    text[NORMAL]      = $white
    text[ACTIVE]      = $white
    text[PRELIGHT]    = $white
}

style "comboboxentry"
{
    # Copied from the "default" style. Part of the workaround for bug #382646.
    text[NORMAL] = $black
    text[ACTIVE] = $black
    text[SELECTED] = $black
    text[PRELIGHT] = $black

    # It just happens that the entry xthickness works good for the button,
    # which simplefies matters a bit.
    xthickness = $entry_xthickness

    engine "sugar" {
        hint = "comboboxentry"
    }
}

style "checkbutton"
{
    base[NORMAL]      = $white
    base[PRELIGHT]    = $white
    base[ACTIVE]      = $text_field_grey

    text[PRELIGHT]    = $toolbar_grey
    text[NORMAL]      = $toolbar_grey
    text[ACTIVE]      = $toolbar_grey

    # I won't get the exact sizing, but this should be pretty close
    GtkCheckButton::indicator-size = $radio_size
    GtkCheckButton::indicator-spacing = 3
    GtkWidget::focus-padding = 3

    engine "sugar" {
        # Reset the colors to the normal color again
        # as they were overriden by the button style
        fg[NORMAL]        = parent_fg_color
        fg[PRELIGHT]      = parent_fg_color
        fg[ACTIVE]        = parent_fg_color
    }
}

style "progressbar"
{
    # text[PRELIGHT] is used for the bar in the sugar engine
    text[PRELIGHT] = $white

    # Text on top of the bar
    fg[PRELIGHT] = $black
    xthickness = $thickness
    ythickness = $thickness

    engine "sugar" {
        # Someone seems to have decided, that the background fill of a progress
        # bar should be done with bg[ACTIVE].
        bg[ACTIVE] = parent_bg_color

        # Color to fill the progress bar ("transparent" usually, except for the
        # white background case)
        bg[NORMAL] = parent_bg_color
        fg[NORMAL] = parent_fg_color
    }
}

style "progressbar-on-white" {
    bg[NORMAL] = $panel_grey
    fg[NORMAL] = $black
}

style "menuitem"
{
    GtkMenuItem::horizontal-padding = 0
    GtkMenuItem::arrow-spacing = $subcell_size
    GtkMenuItem::toggle-spacing = $subcell_size

    bg[PRELIGHT] = $button_grey

    # Work around bug #382646
    text[PRELIGHT]    = $white
    text[NORMAL]      = $white
    text[ACTIVE]      = $white

    xthickness = $subcell_size
    ythickness = $((subcell_size * 3 - font_height) / 2)
}

style "checkmenuitem"
{
    GtkCheckMenuItem::indicator-size = $radio_size
    GtkMenuItem::toggle-spacing = $(subcell_size * 2 / 3)

    ythickness = $((subcell_size * 3 - max(font_height, subcell_size * 2 / 3)) / 2)

    # This is only there because of bug #382646 ...
    base[NORMAL]      = $white
    base[PRELIGHT]    = $white
    base[ACTIVE]      = $text_field_grey

    text[PRELIGHT]    = $toolbar_grey
    text[NORMAL]      = $toolbar_grey
    text[ACTIVE]      = $toolbar_grey
}

style "imagemenuitem"
{
    # Assume a $(icon_small)px image and height of $(4*subcell_size)
    ythickness = $(max(0, (3*subcell_size - icon_small) / 2))
}

style "separatormenuitem"
{
    GtkWidget::wide-separators = 1
    GtkWidget::separator-height = $(2*subcell_size)

    ythickness = 0
    xthickness = 0
}

style "trayicon"
{
    bg[PRELIGHT] = $black
    bg[ACTIVE] = $button_grey
}

style "parent-bg"
{
    engine "sugar" {
        bg[NORMAL] = parent_bg_color
        bg[INSENSITIVE] = parent_bg_color
    }
}

####################################################################
# Default style, setting some generic options and style properties
####################################################################
class "GtkWidget" style "default"

####################################################################
# Styles that apply the different background (and foreground) colors
####################################################################

# This one should probably be the default (ie. no window-child style)
widget_class "<GtkWindow>"               style "window"
widget_class "<GtkWindow>*"              style "window-child"

widget_class "*<SugarAlert>"               style "black-bg"
widget_class "*<SugarAlert>*"              style "black-bg-child"

widget_class "*<SugarSectionView>"       style "white-bg"
widget_class "*<SugarSectionView>*"      style "white-bg-child"

# The notebook is very high, so that everything is overriden
# Only the color of the tab labels needs to be modified inside the
# notebooks in this style (and the widget itself).
# Assume that there is either just a normal label in the notebook tab, or
# it is inside a GtkBox (H- or VBox) together with eg. a close button.
widget_class "*<GtkNotebook>"                        style "notebook-tab"
widget_class "*<GtkNotebook>.<GtkLabel>"             style "notebook-tab"
widget_class "*<GtkNotebook>.<GtkBox>.<GtkLabel>"    style "notebook-tab"

# SugarPalette and Menu
widget_class "*<GtkMenu>"               style "menu"
widget_class "<SugarPaletteWindow>"           style "palette"
widget_class "<SugarPaletteWindow>.*"         style "menu-child"
widget_class "<SugarPaletteWindow>*<GtkMenu>" style "palette-menu"
widget_class "*<GtkMenuShell>.*"        style "menu-child"

# SugarFrameWindow
widget_class "*<SugarFrameWindow>*"    style "frame"

# SugarPanel
widget_class "*<SugarPanel>*"          style "panel"

# GroupBoxes (don't set bg[NORMAL] on the widget itself)
widget_class "<GtkWindow>*<SugarGroupBox>*"   style "groupbox-panel-child"

widget_class "<GtkWindow>*<GtkNotebook>*"   style "groupbox-panel-child"
widget_class "<GtkWindow>*<GtkNotebook>"    style "notebook-panel"

widget_class "<SugarPaletteWindow>*<SugarGroupBox>*"   style "groupbox-palette-child"

# SugarToolbox
#widget_class "*<SugarToolbox>" style "toolbox"
#widget_class "*<SugarToolbox>*" style "toolbox-child"
#widget_class "*<SugarToolbox>*<GtkEventBox>" style "toolbox"
#widget_class "*<SugarToolbox>*<GtkLayout>"   style "toolbox"
widget_class "*<SugarToolbox>*"        style "toolbox"
widget_class "*<GtkToolbar>*"          style "toolbox"
widget_class "*<SugarToolbox>.<GtkNotebook>" style "toolbox-notebook"

####################################################################
# Normal widget styles, using the above things
####################################################################
# The following only uses widget_class matches to get the priority
# right. An alternative would be to lower the priority of the
# background matches (which need widget_class) to eg. "application"

widget_class "*<GtkMenuItem>*"            style "menuitem"
widget_class "*<GtkCheckMenuItem>"        style "checkmenuitem"
widget_class "*<GtkImageMenuItem>"        style "imagemenuitem"
widget_class "*<GtkSeparatorMenuItem>*"   style "separatormenuitem"

# Buttons and Combos
widget_class "*<GtkButton>*"           style "button"
widget_class "*<GtkCheckButton>*"      style "checkbutton"
widget_class "*<GtkComboBox>*"         style "combobox"
widget_class "*<GtkComboBoxEntry>*"    style "comboboxentry"
widget_class "*<GtkCombo>*"            style "comboboxentry"

# Entries
widget_class "*<GtkEntry>"       style "entry"
widget_class "*<GtkSpinButton>"  style "spinbutton"
widget_class "*<GtkTextView>"    style "textview"

# Misc widgets
widget_class "*<GtkHScrollbar>"  style "hscrollbar"
widget_class "*<GtkVScrollbar>"  style "vscrollbar"
widget_class "*<GtkHScale>"      style "hscale"
widget_class "*<GtkVScale>"      style "vscale"
widget_class "*<GtkProgressBar>" style "progressbar"

widget_class "*<SugarSectionView>*<GtkProgress>"  style "progressbar-on-white"

# Scrolled window scrollbars
widget_class "*<GtkScrolledWindow>.<GtkVScrollbar>" style "vscrollbar-scrolled-window"
widget_class "*<GtkScrolledWindow>.<GtkHScrollbar>" style "hscrollbar-scrolled-window"

# Toolbar
widget_class "*<GtkToolButton>*"          style "toolbutton"
widget_class "*<SugarColorToolButton>*"   style "toolbutton"
widget_class "*<GtkSeparatorToolItem>*"   style "separatortoolbutton"
widget_class "*<GtkToolbar>"              style "toolbar"

# Tray
widget_class "*<SugarTrayIcon>*" style "trayicon"

# Widgets that should get the background color from the parent
widget_class "*<GtkEventBox>"        style "parent-bg"
widget_class "*<GtkLayout>"          style "parent-bg"
widget_class "*<GtkViewport>"        style "parent-bg"
widget_class "*<GtkDrawingArea>"     style "parent-bg"
widget_class "*<GtkScrolledWindow>"  style "parent-bg"

${
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 = 9
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 = 6      # 7.68

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

# Colors:

black = '"#000000"'
toolbar_grey = '"#282828"'
button_grey = '"#808080"'
selection_grey = '"#A6A6A6"'
panel_grey = '"#C0C0C0"'
text_field_grey = '"#E5E5E5"'
white = '"#FFFFFF"'

}
${
icon_small = icon_base * 3
icon_large = icon_base * 5

small_icons = [ "gtk-menu", "gtk-dnd", "gtk-small-toolbar", "gtk-button" ]
large_icons = [ "gtk-large-toolbar" ]

icon_sizes = []
for icon in small_icons:
    icon_sizes += [icon + "=" + str(icon_small) + ',' + str(icon_small)]
for icon in large_icons:
    icon_sizes += [icon + "=" + str(icon_large) + ',' + str(icon_large)]

icon_sizes = ":".join(icon_sizes)

}
gtk-icon-sizes="$icon_sizes"

style "default"
{
    # Will be overriden in some widgets
    color["focus_line"] = $white

    bg[NORMAL] = $button_grey
    bg[PRELIGHT] = $button_grey
    bg[ACTIVE] = $white
    bg[SELECTED] = $text_field_grey

    fg[NORMAL]        = $black
    fg[ACTIVE]        = $black
    fg[SELECTED]      = $white
    fg[PRELIGHT]      = $white

    text[NORMAL] = $black # button_grey? 1. need to patch GTK+ to use ACTIVE when focused, 2. what color should this really be?
    text[ACTIVE] = $black
    text[SELECTED] = $black
    text[PRELIGHT] = $black

    base[NORMAL] = $text_field_grey
    base[ACTIVE] = $white
    base[SELECTED] = $selection_grey
    base[PRELIGHT] = $white

    fg[INSENSITIVE] = $button_grey
    text[INSENSITIVE] = $button_grey

    xthickness = $thickness
    ythickness = $thickness

    # 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
    GtkWidget::focus-line-width = 1   # Prevents some drawing glitches
    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 = 0
    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 = 24
    GtkExpander::expander-spacing = 2     # XXX

    GtkTreeView::expander-size = 24

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

    GtkArrow::arrow-size = 1.0

    GtkToolbar::shadow-type = GTK_SHADOW_NONE
    GtkToolbar::space-size = $(2*subcell_size)

    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

    engine "sugar" {
        line_width = $line_width
        thick_line_width = $thick_line_width
        subcell_size = $subcell_size

        max_radius = $( 2*subcell_size )
    }
}

#############################################################
# Styles that set default background/foreground colors
#
#  We almost always need two styles because of the X windows.
#  For example bg[NORMAL] of a window (or eventbox) needs to
#  be set to be the correct window color so that X fills the
#  window correctly from the beginning, and it does not
#  flash in the wrong color.
#############################################################

style "window"
{
    bg[NORMAL] = $panel_grey
}

style "window-child"
{
    bg[INSENSITIVE] = $panel_grey
    base[INSENSITIVE] = $panel_grey

    fg[NORMAL] = $black

    engine "sugar" {
        parent_fg_color = $black
        parent_bg_color = $panel_grey
    }
}

style "white-bg"
{
    bg[NORMAL] = $white
}

style "white-bg-child"
{
    bg[INSENSITIVE] = $white
    base[INSENSITIVE] = $white

    fg[NORMAL] = $black

    engine "sugar" {
        parent_fg_color = $black
        parent_bg_color = $white
    }
}

style "black-bg"
{
    bg[NORMAL] = $black
}

style "black-bg-child"
{
    bg[INSENSITIVE] = $black
    base[INSENSITIVE] = $black

    fg[NORMAL] = $white

    engine "sugar" {
        parent_fg_color = $white
        parent_bg_color = $black
    }
}

style "groupbox-panel"
{
    bg[NORMAL] = $selection_grey
}

style "groupbox-panel-child"
{
    bg[INSENSITIVE] = $selection_grey
    base[INSENSITIVE] = $selection_grey

    fg[NORMAL] = $black

    engine "sugar" {
        parent_fg_color = $black
        parent_bg_color = $selection_grey
    }
}

style "groupbox-palette"
{
    bg[NORMAL] = $toolbar_grey
}

style "groupbox-palette-child"
{
    bg[INSENSITIVE] = $toolbar_grey
    base[INSENSITIVE] = $toolbar_grey

    fg[NORMAL] = $white

    engine "sugar" {
        parent_fg_color = $white
        parent_bg_color = $toolbar_grey
    }
}

style "menu"
{
    bg[NORMAL] = $black
    # Used for the arrow colour
    fg[NORMAL]   = $white
    fg[PRELIGHT] = $white
    bg[ACTIVE] = $button_grey

    ${# This is just the exact reverse of what is going on inside GTK+ ...}
    GtkMenu::scroll-arrow-vlength = $(my_floor(subcell_size/0.7 + 2*thickness))
    GtkMenu::horizontal-padding = 0
    GtkMenu::vertical-padding   = 0
    # 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  = 0
    GtkMenu::vertical-offset    = 0

    xthickness = $thickness
    ythickness = $thickness
}

style "palette-menu" = "menu"
{
    xthickness = 0
    ythickness = $subcell_size
}

# Can this all be moved in the menuitem style?
style "menu-child"
{
    color["bg_color"] = $black

    fg[NORMAL]   = $white
    fg[PRELIGHT] = $white

    bg[NORMAL] = $button_grey

    bg[INSENSITIVE] = $black
    base[INSENSITIVE] = $black

    engine "sugar" {
        parent_fg_color = $white
        parent_bg_color = $black
    }
}

#########################################################
#########################################################

style "textview"
{
    # We always use white here
    base[NORMAL]      = $white

    GtkTextView::interior-focus = 1
}

style "scrollbar"
{
    GtkScrollbar::slider-width = $subcell_size
    GtkScrollbar::trough-border = 0
    GtkScrollbar::stepper-spacing = 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

    # Hack for mozilla as it does not obey the above style properties ...
    # Really, mozilla should be fixed ...
    GtkScrollbar::stepper-size = 1
}

# hints for the trough padding hacks
# We cannot go trough GTK+, as we don't want a padding on all sides, but only one
# If we wanted padding on all sides, trough-border would work great.
style "hscrollbar" = "scrollbar"
{
    engine "sugar" {
        hint = "hscrollbar"
        scrollbar_border = $thickness
    }
}

style "vscrollbar" = "scrollbar"
{
    engine "sugar" {
        hint = "vscrollbar"
        scrollbar_border = $thickness
    }
}

style "hscrollbar-scrolled-window"
{
    GtkScrollbar::slider-width = $(subcell_size - thickness)
    GtkScrollbar::stepper-spacing = $thickness
    GtkScrollbar::has-forward-stepper = 1
    GtkScrollbar::has-backward-stepper = 1
    GtkScrollbar::stepper-size = 0

    engine "sugar" {
        hint = "hscrollbar-scrolled-window"
    }
}

style "vscrollbar-scrolled-window"
{
    GtkScrollbar::slider-width = $(subcell_size - thickness)
    GtkScrollbar::stepper-spacing = $thickness
    GtkScrollbar::has-forward-stepper = 1
    GtkScrollbar::has-backward-stepper = 1
    GtkScrollbar::stepper-size = 0

    engine "sugar" {
        hint = "vscrollbar-scrolled-window"
    }
}

style "scale"
{
    GtkWidget::focus-line-width = 0

    bg[NORMAL] = $button_grey

    # "square" slider (really round of course)
    # Same as indicator-size?
    GtkRange::slider-width = $scale_slider_width
    GtkScale::slider-length = $scale_slider_width
    GtkRange::trough-border = 0
    GtkRange::stepper-spacing = 0
    GtkRange::trough-side-details = 1
}

# hints for the trough sizing hacks
style "hscale" = "scale"
{
    engine "sugar" {
        hint = "hscale"
    }
}

style "vscale" = "scale"
{
    engine "sugar" {
        hint = "vscale"
    }
}

style "spinbutton"
{
    bg[NORMAL]  = $button_grey
    bg[ACTIVE]  = $white
    fg[NORMAL]  = $white
    fg[ACTIVE]  = $black

    ${ spin_ythickness = my_ceil(0.2 * (subcell_size*3.0/2.0 - thickness) + thickness) }
    ${ spin_xthickness = subcell_size }

    # small inner border and a large x/ythickness for entries
    # to reduce the number of hacks needed :-)
    xthickness = $spin_xthickness
    ythickness = $spin_ythickness
    GtkWidget::focus-line-width = 0

    # This tries to get a height of exactly 45 pixel for the entry.
    GtkEntry::inner-border = { $(max(subcell_size - spin_xthickness, 0)), $(max(subcell_size - spin_xthickness, 0)),
                               $(max(my_ceil((3*subcell_size - font_height - spin_ythickness*2)/2.0),0)), $(max(my_floor((3*subcell_size - font_height - spin_ythickness*2)/2.0), 0)) }

    engine "sugar" {
        hint = "spinbutton"
    }
}

style "frame"
{
    bg[NORMAL] = $toolbar_grey

    engine "sugar" {
        parent_bg_color = $toolbar_grey
        parent_fg_color = $white
    }
}

style "notebook-tab"
{
    bg[ACTIVE] = $button_grey
    fg[ACTIVE] = $white
}

style "notebook-panel"
{
    GtkNotebook::tab-overlap = -2
    GtkNotebook::tab-curvature = $default_padding

    xthickness = 0
    ythickness = 0

    bg[NORMAL] = $selection_grey
}

style "toolbox-notebook"
{
    GtkNotebook::tab-overlap = -2

    xthickness = 0
    ythickness = 0
}

style "toolbutton"
{
    xthickness = 0
    ythickness = 0
    GtkButton::inner-border = { $toolbutton_padding,
                                $toolbutton_padding,
                                $toolbutton_padding,
                                $toolbutton_padding }
    GtkWidget::focus-padding = 0

    bg[PRELIGHT] = $black
    bg[ACTIVE] = $button_grey
    bg[NORMAL] = $black

    fg[NORMAL] = $white
    fg[ACTIVE] = $white
    fg[PRELIGHT] = $white

    engine "sugar" {
        max_radius = 5.0
        fake_padding = $default_padding
    }
}

style "separatortoolbutton"
{
    # The sizing is handled in the engine.
    bg[NORMAL] = $button_grey
}

style "toolbar"
{
    xthickness = 0
    ythickness = 0

    bg[NORMAL] = $toolbar_grey
    bg[INSENSITIVE] = $toolbar_grey
    base[INSENSITIVE] = $toolbar_grey

    engine "sugar" {
        parent_fg_color = $white
        parent_bg_color = $toolbar_grey
    }
}

style "toolbox"
{
    fg[NORMAL] = $white
    fg[ACTIVE] = $white

    bg[NORMAL] = $toolbar_grey
    bg[INSENSITIVE] = $toolbar_grey
    base[INSENSITIVE] = $toolbar_grey

    engine "sugar" {
        parent_fg_color = $white
        parent_bg_color = $toolbar_grey
    }
}

style "panel"
{
    bg[NORMAL] = $panel_grey
    fg[NORMAL] = $black
}

style "entry"
{
    color["focus_line"] = $text_field_grey

    ${ 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) }

    # small inner border and a large x/ythickness for entries
    # to reduce the number of hacks needed :-)
    xthickness = $entry_xthickness
    ythickness = $entry_ythickness
    GtkWidget::focus-line-width = 0

    # Colors for the progress bar.
    bg[SELECTED] = $selection_grey
    fg[SELECTED] = $black

    # This tries to get a height of exactly 45 pixel for the entry.
    GtkEntry::inner-border = { $(max(subcell_size - entry_xthickness, 0)), $(max(subcell_size - entry_xthickness, 0)),
                               $(max(my_ceil((3*subcell_size - font_height - entry_ythickness*2)/2.0),0)), $(max(my_floor((3*subcell_size - font_height - entry_ythickness*2)/2.0), 0)) }
}

style "button"
{
    ${ border = max(0, my_ceil((3*subcell_size/2.0 - icon_small / 2.0))) }
    # It would be nicer to just set the inner-border, but that does not work
    # for combo boxes ... The combobox ignores it, so set it to 0px
    # See http://bugzilla.gnome.org/show_bug.cgi?id=485762

    GtkButton::inner-border = { 0, 0, 0, 0 }

    # So set the x/ythickness; y-padding is $subcell_size overall
    xthickness = $(border)
    ythickness = $(border)

    fg[NORMAL] = $white
    fg[ACTIVE] = $black

    bg[NORMAL] = $button_grey
}

style "combobox"
{
    # "Disable" the ACTIVE state of the button, it only creates problems :-(
    bg[ACTIVE] = $button_grey
    fg[ACTIVE] = $white

    # Work around GTK+ bug #382646
    text[NORMAL]      = $white
    text[ACTIVE]      = $white
    text[PRELIGHT]    = $white
}

style "comboboxentry"
{
    # Copied from the "default" style. Part of the workaround for bug #382646.
    text[NORMAL] = $black
    text[ACTIVE] = $black
    text[SELECTED] = $black
    text[PRELIGHT] = $black

    # It just happens that the entry xthickness works good for the button,
    # which simplefies matters a bit.
    xthickness = $entry_xthickness

    engine "sugar" {
        hint = "comboboxentry"
    }
}

style "checkbutton"
{
    base[NORMAL]      = $white
    base[PRELIGHT]    = $white
    base[ACTIVE]      = $text_field_grey

    text[PRELIGHT]    = $toolbar_grey
    text[NORMAL]      = $toolbar_grey
    text[ACTIVE]      = $toolbar_grey

    # I won't get the exact sizing, but this should be pretty close
    GtkCheckButton::indicator-size = $radio_size
    GtkCheckButton::indicator-spacing = 3
    GtkWidget::focus-padding = 3

    engine "sugar" {
        # Reset the colors to the normal color again
        # as they were overriden by the button style
        fg[NORMAL]        = parent_fg_color
        fg[PRELIGHT]      = parent_fg_color
        fg[ACTIVE]        = parent_fg_color
    }
}

style "progressbar"
{
    # text[PRELIGHT] is used for the bar in the sugar engine
    text[PRELIGHT] = $white

    # Text on top of the bar
    fg[PRELIGHT] = $black
    xthickness = $thickness
    ythickness = $thickness

    engine "sugar" {
        # Someone seems to have decided, that the background fill of a progress
        # bar should be done with bg[ACTIVE].
        bg[ACTIVE] = parent_bg_color

        # Color to fill the progress bar ("transparent" usually, except for the
        # white background case)
        bg[NORMAL] = parent_bg_color
        fg[NORMAL] = parent_fg_color
    }
}

style "progressbar-on-white" {
    bg[NORMAL] = $panel_grey
    fg[NORMAL] = $black
}

style "menuitem"
{
    GtkMenuItem::horizontal-padding = 0
    GtkMenuItem::arrow-spacing = $subcell_size
    GtkMenuItem::toggle-spacing = $subcell_size

    bg[PRELIGHT] = $button_grey

    # Work around bug #382646
    text[PRELIGHT]    = $white
    text[NORMAL]      = $white
    text[ACTIVE]      = $white

    xthickness = $subcell_size
    ythickness = $((subcell_size * 3 - font_height) / 2)
}

style "checkmenuitem"
{
    GtkCheckMenuItem::indicator-size = $radio_size
    GtkMenuItem::toggle-spacing = $(subcell_size * 2 / 3)

    ythickness = $((subcell_size * 3 - max(font_height, subcell_size * 2 / 3)) / 2)

    # This is only there because of bug #382646 ...
    base[NORMAL]      = $white
    base[PRELIGHT]    = $white
    base[ACTIVE]      = $text_field_grey

    text[PRELIGHT]    = $toolbar_grey
    text[NORMAL]      = $toolbar_grey
    text[ACTIVE]      = $toolbar_grey
}

style "imagemenuitem"
{
    # Assume a $(icon_small)px image and height of $(4*subcell_size)
    ythickness = $(max(0, (3*subcell_size - icon_small) / 2))
}

style "separatormenuitem"
{
    GtkWidget::wide-separators = 1
    GtkWidget::separator-height = $(2*subcell_size)

    ythickness = 0
    xthickness = 0
}

style "trayicon"
{
    bg[PRELIGHT] = $black
    bg[ACTIVE] = $button_grey
}

style "parent-bg"
{
    engine "sugar" {
        bg[NORMAL] = parent_bg_color
        bg[INSENSITIVE] = parent_bg_color
    }
}

####################################################################
# Default style, setting some generic options and style properties
####################################################################
class "GtkWidget" style "default"

####################################################################
# Styles that apply the different background (and foreground) colors
####################################################################

# This one should probably be the default (ie. no window-child style)
widget_class "<GtkWindow>"               style "window"
widget_class "<GtkWindow>*"              style "window-child"

widget_class "*<SugarAlert>"               style "black-bg"
widget_class "*<SugarAlert>*"              style "black-bg-child"

widget_class "*<SugarSectionView>"       style "white-bg"
widget_class "*<SugarSectionView>*"      style "white-bg-child"

# The notebook is very high, so that everything is overriden
# Only the color of the tab labels needs to be modified inside the
# notebooks in this style (and the widget itself).
# Assume that there is either just a normal label in the notebook tab, or
# it is inside a GtkBox (H- or VBox) together with eg. a close button.
widget_class "*<GtkNotebook>"                        style "notebook-tab"
widget_class "*<GtkNotebook>.<GtkLabel>"             style "notebook-tab"
widget_class "*<GtkNotebook>.<GtkBox>.<GtkLabel>"    style "notebook-tab"

# SugarPalette and Menu
widget_class "*<GtkMenu>"               style "menu"
widget_class "<SugarPalette>"           style "menu"
widget_class "<SugarPalette>.*"         style "menu-child"
widget_class "<SugarPalette>*<GtkMenu>" style "palette-menu"
widget_class "*<GtkMenuShell>.*"        style "menu-child"

# SugarFrameWindow
widget_class "*<SugarFrameWindow>*"    style "frame"

# SugarPanel
widget_class "*<SugarPanel>*"          style "panel"

# GroupBoxes (don't set bg[NORMAL] on the widget itself)
widget_class "<GtkWindow>*<SugarGroupBox>*"   style "groupbox-panel-child"

widget_class "<GtkWindow>*<GtkNotebook>*"   style "groupbox-panel-child"
widget_class "<GtkWindow>*<GtkNotebook>"    style "notebook-panel"

widget_class "<SugarPalette>*<SugarGroupBox>*"   style "groupbox-palette-child"

# SugarToolbox
#widget_class "*<SugarToolbox>" style "toolbox"
#widget_class "*<SugarToolbox>*" style "toolbox-child"
#widget_class "*<SugarToolbox>*<GtkEventBox>" style "toolbox"
#widget_class "*<SugarToolbox>*<GtkLayout>"   style "toolbox"
widget_class "*<SugarToolbox>*"        style "toolbox"
widget_class "*<GtkToolbar>*"          style "toolbox"
widget_class "*<SugarToolbox>.<GtkNotebook>" style "toolbox-notebook"

####################################################################
# Normal widget styles, using the above things
####################################################################
# The following only uses widget_class matches to get the priority
# right. An alternative would be to lower the priority of the
# background matches (which need widget_class) to eg. "application"

widget_class "*<GtkMenuItem>*"            style "menuitem"
widget_class "*<GtkCheckMenuItem>"        style "checkmenuitem"
widget_class "*<GtkImageMenuItem>"        style "imagemenuitem"
widget_class "*<GtkSeparatorMenuItem>*"   style "separatormenuitem"

# Buttons and Combos
widget_class "*<GtkButton>*"           style "button"
widget_class "*<GtkCheckButton>*"      style "checkbutton"
widget_class "*<GtkComboBox>*"         style "combobox"
widget_class "*<GtkComboBoxEntry>*"    style "comboboxentry"
widget_class "*<GtkCombo>*"            style "comboboxentry"

# Entries
widget_class "*<GtkEntry>"       style "entry"
widget_class "*<GtkSpinButton>"  style "spinbutton"
widget_class "*<GtkTextView>"    style "textview"

# Misc widgets
widget_class "*<GtkHScrollbar>"  style "hscrollbar"
widget_class "*<GtkVScrollbar>"  style "vscrollbar"
widget_class "*<GtkHScale>"      style "hscale"
widget_class "*<GtkVScale>"      style "vscale"
widget_class "*<GtkProgressBar>" style "progressbar"

widget_class "*<SugarSectionView>*<GtkProgress>"  style "progressbar-on-white"

# Scrolled window scrollbars
widget_class "*<GtkScrolledWindow>.<GtkVScrollbar>" style "vscrollbar-scrolled-window"
widget_class "*<GtkScrolledWindow>.<GtkHScrollbar>" style "hscrollbar-scrolled-window"

# Toolbar
widget_class "*<GtkToolButton>*"          style "toolbutton"
widget_class "*<SugarColorToolButton>*"   style "toolbutton"
widget_class "*<GtkSeparatorToolItem>*"   style "separatortoolbutton"
widget_class "*<GtkToolbar>"              style "toolbar"

# Tray
widget_class "*<SugarTrayIcon>*" style "trayicon"

# Widgets that should get the background color from the parent
widget_class "*<GtkEventBox>"        style "parent-bg"
widget_class "*<GtkLayout>"          style "parent-bg"
widget_class "*<GtkViewport>"        style "parent-bg"
widget_class "*<GtkDrawingArea>"     style "parent-bg"
widget_class "*<GtkScrolledWindow>"  style "parent-bg"


cherry-pick #182: suppress xacro warnings when determining dependencies

Instead of using xacro's stderr, use the process result to determine success.
On stderr all kinds of other warnings are published as well.

@[if DEVELSPACE]@
set(_xacro_py
  @(CMAKE_CURRENT_SOURCE_DIR)/scripts/xacro
  )
@[else]@
set(_xacro_py
  ${xacro_DIR}/../../../@(CATKIN_PACKAGE_BIN_DESTINATION)/xacro
  )
@[end if]@

add_custom_target(${PROJECT_NAME}_xacro_generated_to_devel_space_ ALL)

## xacro_add_xacro_file(<input> [<output>] [INORDER] [REMAP <arg> <arg> ...]
##                      [OUTPUT <variable>] DEPENDS <arg> <arg>)
##
## Creates a command run xacro on <input> like so:
## xacro [--inorder] -o <output> <input> [<remap args>]
##
## If <output> was not specified, it is determined from <input> removing the suffix .xacro
## The absolute output file name is returned in variable <output>, which defaults to
## XACRO_OUTPUT_FILE.
##
## In order to actually build and install, you need to provide a custom target:
## foreach(xacro_file ${MY_XACRO_FILES})
##   xacro_add_xacro_file(${xacro_file} INORDER REMAP bar:=foo foo:=bar)
##   list(APPEND xacro_outputs ${XACRO_OUTPUT_FILE})
## endforeach()
## xacro_install(xacro_target ${xacro_outputs} DESTINATION xml)
##
## Be aware, that xacro_install() is required to install into both, install and devel space.
## Normal install() only installs into install space!
##
## For conveniency, you might want to use xacro_add_files(), which does the same:
## xacro_add_files(${MY_XACRO_FILES} INORDER REMAP bar:=foo foo:=bar
##                 TARGET xacro_target INSTALL DESTINATION xml)
function(xacro_add_xacro_file input)
  # parse arguments
  set(options INORDER)
  set(oneValueArgs OUTPUT)
  set(multiValueArgs REMAP DEPENDS)
  cmake_parse_arguments(_XACRO "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  ## process arguments
  # retrieve output file name
  if(_XACRO_UNPARSED_ARGUMENTS)
    # output file explicitly specified
    list(GET _XACRO_UNPARSED_ARGUMENTS 0 output)
    list(REMOVE_AT _XACRO_UNPARSED_ARGUMENTS 0)
    # any remaining unparsed args?
    if(_XACRO_UNPARSED_ARGUMENTS)
      message(WARNING "unknown arguments: ${_XACRO_UNPARSED_ARGUMENTS}")
    endif(_XACRO_UNPARSED_ARGUMENTS)
  else()
    # implicitly determine output file from input
    if(${input} MATCHES "(.*)[.]xacro$")
      set(output ${CMAKE_MATCH_1})
    else()
      message(FATAL_ERROR "no <output> specified for: " ${input})
    endif()
  endif()
  # message(STATUS "output: ${output}")

  # transform _XACRO_INORDER's BOOL value
  if(_XACRO_INORDER)
    set(_XACRO_INORDER "--inorder")
  else()
    unset(_XACRO_INORDER)
  endif()

  ## determine absolute output target location
  if(IS_ABSOLUTE ${output})
    set(abs_output ${output})
  else()
    set(abs_output ${CMAKE_CURRENT_BINARY_DIR}/${output})
  endif()
  # message(STATUS "abs_output: ${abs_output}")

  ## export abs_output to parent scope in variable ${_XACRO_OUTPUT}
  if(NOT _XACRO_OUTPUT)
    set(_XACRO_OUTPUT XACRO_OUTPUT_FILE)
  endif()
  set(${_XACRO_OUTPUT} ${abs_output} PARENT_SCOPE)

  ## Call out to xacro to determine dependencies
  message(STATUS "xacro: determining deps for: " ${input} " ...")
  execute_process(COMMAND ${CATKIN_ENV} ${_xacro_py} ${_XACRO_INORDER} --deps ${input} ${_XACRO_REMAP}
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    RESULT_VARIABLE _xacro_result
    ERROR_VARIABLE _xacro_err
    OUTPUT_VARIABLE _xacro_deps_result
    OUTPUT_STRIP_TRAILING_WHITESPACE)
  if(_xacro_result)
    message(WARNING "failed to determine deps for: ${input}
${_xacro_err}")
  endif(_xacro_result)

  separate_arguments(_xacro_deps_result)

  ## command to actually call xacro
  add_custom_command(OUTPUT ${output}
    COMMAND ${CATKIN_ENV} ${_xacro_py} ${_XACRO_INORDER} -o ${abs_output} ${input} ${_XACRO_REMAP}
    DEPENDS ${input} ${_xacro_deps_result} ${_XACRO_DEPENDS}
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    COMMENT "xacro: generating ${output} from ${input}"
    )
endfunction(xacro_add_xacro_file)

## xacro_install(<target> <output> [<output> ...] DESTINATION <path>)
##
## installs xacro-generated files both to devel and input space
## into ${CATKIN_PACKAGE_SHARE_DESTINATION}/<path>
function(xacro_install target)
  # parse arguments
  set(oneValueArgs DESTINATION)
  cmake_parse_arguments(_XACRO "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  set(outputs ${_XACRO_UNPARSED_ARGUMENTS})

  ## rule to create target dir
  file(TO_CMAKE_PATH ${CATKIN_PACKAGE_SHARE_DESTINATION}/${_XACRO_DESTINATION} dest)
  file(TO_CMAKE_PATH ${CATKIN_DEVEL_PREFIX}/${dest} TARGET_DIR)
  add_custom_command(OUTPUT ${TARGET_DIR}
    COMMAND ${CMAKE_COMMAND} -E make_directory ${TARGET_DIR}
    COMMENT "creating dir ${TARGET_DIR}")

  ## process list of outputs
  foreach(output ${outputs})
    get_filename_component(tgt ${output} NAME)
    file(TO_CMAKE_PATH ${TARGET_DIR}/${tgt} tgt)
    list(APPEND tgts ${tgt})

    # rule to create devel space tgt
    add_custom_command(OUTPUT ${tgt}
      COMMAND ${CMAKE_COMMAND} -E copy_if_different ${output} ${tgt}
      DEPENDS ${TARGET_DIR} ${output}
      COMMENT "Copying to devel space: ${tgt}"
      )
  endforeach()

  ## rule to create devel space tgts
  add_custom_target(${PROJECT_NAME}_${target}_to_devel_space_ DEPENDS ${tgts})
  # add to main target _xacro_generated_to_devel_space_
  add_dependencies(${PROJECT_NAME}_xacro_generated_to_devel_space_
                   ${PROJECT_NAME}_${target}_to_devel_space_)

  ## normal install
  install(FILES ${outputs} DESTINATION ${dest})
endfunction(xacro_install)

## xacro_add_files(<file> [<file> ...] [INORDER] [REMAP <arg> <arg> ...] [DEPENDS <arg> <arg>]
##                 [TARGET <target>] [INSTALL [DESTINATION <path>]])
##
## create make <target> to generate xacro files
## and optionally install to ${CATKIN_PACKAGE_SHARE_DESTINATION}/<path>
## in devel and install space.
function(xacro_add_files)
  # parse arguments
  set(options INORDER INSTALL)
  set(oneValueArgs OUTPUT TARGET DESTINATION)
  set(multiValueArgs REMAP DEPENDS)
  cmake_parse_arguments(_XACRO "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  ## process arguments
  # transform _XACRO_INORDER's BOOL value
  if(_XACRO_INORDER)
    set(_XACRO_INORDER "INORDER")
  else()
    unset(_XACRO_INORDER)
  endif()

  # prepare REMAP args (prepending REMAP)
  if(_XACRO_REMAP)
    set(_XACRO_REMAP REMAP ${_XACRO_REMAP})
  endif()
  # prepare DEPENDS args (prepending DEPENDS)
  if(_XACRO_DEPENDS)
    set(_XACRO_DEPENDS DEPENDS ${_XACRO_DEPENDS})
  endif()

  # have INSTALL option, but no TARGET: fallback to default target
  if(_XACRO_INSTALL AND NOT _XACRO_TARGET)
    # message(STATUS "xacro: no TARGET specified, using default")
    set(_XACRO_TARGET _xacro_auto_generate)
  endif()

  foreach(input ${_XACRO_UNPARSED_ARGUMENTS})
    # call to main function
    xacro_add_xacro_file(${input} ${_XACRO_OUTPUT} ${_XACRO_INORDER} ${_XACRO_REMAP} ${_XACRO_DEPENDS})
    list(APPEND outputs ${XACRO_OUTPUT_FILE})
  endforeach()

  if(outputs)
    # link to target
    add_custom_target(${PROJECT_NAME}_${_XACRO_TARGET} ALL DEPENDS ${outputs})

    # install?
    if(_XACRO_INSTALL)
      xacro_install(${_XACRO_TARGET} ${outputs} DESTINATION ${_XACRO_DESTINATION})
    endif(_XACRO_INSTALL)
  endif()
endfunction(xacro_add_files)

@[if DEVELSPACE]@
set(_xacro_py
  @(CMAKE_CURRENT_SOURCE_DIR)/scripts/xacro
  )
@[else]@
set(_xacro_py
  ${xacro_DIR}/../../../@(CATKIN_PACKAGE_BIN_DESTINATION)/xacro
  )
@[end if]@

add_custom_target(${PROJECT_NAME}_xacro_generated_to_devel_space_ ALL)

## xacro_add_xacro_file(<input> [<output>] [INORDER] [REMAP <arg> <arg> ...]
##                      [OUTPUT <variable>] DEPENDS <arg> <arg>)
##
## Creates a command run xacro on <input> like so:
## xacro [--inorder] -o <output> <input> [<remap args>]
##
## If <output> was not specified, it is determined from <input> removing the suffix .xacro
## The absolute output file name is returned in variable <output>, which defaults to
## XACRO_OUTPUT_FILE.
##
## In order to actually build and install, you need to provide a custom target:
## foreach(xacro_file ${MY_XACRO_FILES})
##   xacro_add_xacro_file(${xacro_file} INORDER REMAP bar:=foo foo:=bar)
##   list(APPEND xacro_outputs ${XACRO_OUTPUT_FILE})
## endforeach()
## xacro_install(xacro_target ${xacro_outputs} DESTINATION xml)
##
## Be aware, that xacro_install() is required to install into both, install and devel space.
## Normal install() only installs into install space!
##
## For conveniency, you might want to use xacro_add_files(), which does the same:
## xacro_add_files(${MY_XACRO_FILES} INORDER REMAP bar:=foo foo:=bar
##                 TARGET xacro_target INSTALL DESTINATION xml)
function(xacro_add_xacro_file input)
  # parse arguments
  set(options INORDER)
  set(oneValueArgs OUTPUT)
  set(multiValueArgs REMAP DEPENDS)
  cmake_parse_arguments(_XACRO "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  ## process arguments
  # retrieve output file name
  if(_XACRO_UNPARSED_ARGUMENTS)
    # output file explicitly specified
    list(GET _XACRO_UNPARSED_ARGUMENTS 0 output)
    list(REMOVE_AT _XACRO_UNPARSED_ARGUMENTS 0)
    # any remaining unparsed args?
    if(_XACRO_UNPARSED_ARGUMENTS)
      message(WARNING "unknown arguments: ${_XACRO_UNPARSED_ARGUMENTS}")
    endif(_XACRO_UNPARSED_ARGUMENTS)
  else()
    # implicitly determine output file from input
    if(${input} MATCHES "(.*)[.]xacro$")
      set(output ${CMAKE_MATCH_1})
    else()
      message(FATAL_ERROR "no <output> specified for: " ${input})
    endif()
  endif()
  # message(STATUS "output: ${output}")

  # transform _XACRO_INORDER's BOOL value
  if(_XACRO_INORDER)
    set(_XACRO_INORDER "--inorder")
  else()
    unset(_XACRO_INORDER)
  endif()

  ## determine absolute output target location
  if(IS_ABSOLUTE ${output})
    set(abs_output ${output})
  else()
    set(abs_output ${CMAKE_CURRENT_BINARY_DIR}/${output})
  endif()
  # message(STATUS "abs_output: ${abs_output}")

  ## export abs_output to parent scope in variable ${_XACRO_OUTPUT}
  if(NOT _XACRO_OUTPUT)
    set(_XACRO_OUTPUT XACRO_OUTPUT_FILE)
  endif()
  set(${_XACRO_OUTPUT} ${abs_output} PARENT_SCOPE)

  ## Call out to xacro to determine dependencies
  message(STATUS "xacro: determining deps for: " ${input} " ...")
  execute_process(COMMAND ${CATKIN_ENV} ${_xacro_py} ${_XACRO_INORDER} --deps ${input} ${_XACRO_REMAP}
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    ERROR_VARIABLE _xacro_err
    OUTPUT_VARIABLE _xacro_deps_result
    OUTPUT_STRIP_TRAILING_WHITESPACE)
  if(_xacro_err)
    message(WARNING "failed to determine deps for: ${input}
${_xacro_err}")
  endif(_xacro_err)

  separate_arguments(_xacro_deps_result)

  ## command to actually call xacro
  add_custom_command(OUTPUT ${output}
    COMMAND ${CATKIN_ENV} ${_xacro_py} ${_XACRO_INORDER} -o ${abs_output} ${input} ${_XACRO_REMAP}
    DEPENDS ${input} ${_xacro_deps_result} ${_XACRO_DEPENDS}
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    COMMENT "xacro: generating ${output} from ${input}"
    )
endfunction(xacro_add_xacro_file)

## xacro_install(<target> <output> [<output> ...] DESTINATION <path>)
##
## installs xacro-generated files both to devel and input space
## into ${CATKIN_PACKAGE_SHARE_DESTINATION}/<path>
function(xacro_install target)
  # parse arguments
  set(oneValueArgs DESTINATION)
  cmake_parse_arguments(_XACRO "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  set(outputs ${_XACRO_UNPARSED_ARGUMENTS})

  ## rule to create target dir
  file(TO_CMAKE_PATH ${CATKIN_PACKAGE_SHARE_DESTINATION}/${_XACRO_DESTINATION} dest)
  file(TO_CMAKE_PATH ${CATKIN_DEVEL_PREFIX}/${dest} TARGET_DIR)
  add_custom_command(OUTPUT ${TARGET_DIR}
    COMMAND ${CMAKE_COMMAND} -E make_directory ${TARGET_DIR}
    COMMENT "creating dir ${TARGET_DIR}")

  ## process list of outputs
  foreach(output ${outputs})
    get_filename_component(tgt ${output} NAME)
    file(TO_CMAKE_PATH ${TARGET_DIR}/${tgt} tgt)
    list(APPEND tgts ${tgt})

    # rule to create devel space tgt
    add_custom_command(OUTPUT ${tgt}
      COMMAND ${CMAKE_COMMAND} -E copy_if_different ${output} ${tgt}
      DEPENDS ${TARGET_DIR} ${output}
      COMMENT "Copying to devel space: ${tgt}"
      )
  endforeach()

  ## rule to create devel space tgts
  add_custom_target(${PROJECT_NAME}_${target}_to_devel_space_ DEPENDS ${tgts})
  # add to main target _xacro_generated_to_devel_space_
  add_dependencies(${PROJECT_NAME}_xacro_generated_to_devel_space_
                   ${PROJECT_NAME}_${target}_to_devel_space_)

  ## normal install
  install(FILES ${outputs} DESTINATION ${dest})
endfunction(xacro_install)

## xacro_add_files(<file> [<file> ...] [INORDER] [REMAP <arg> <arg> ...] [DEPENDS <arg> <arg>]
##                 [TARGET <target>] [INSTALL [DESTINATION <path>]])
##
## create make <target> to generate xacro files
## and optionally install to ${CATKIN_PACKAGE_SHARE_DESTINATION}/<path>
## in devel and install space.
function(xacro_add_files)
  # parse arguments
  set(options INORDER INSTALL)
  set(oneValueArgs OUTPUT TARGET DESTINATION)
  set(multiValueArgs REMAP DEPENDS)
  cmake_parse_arguments(_XACRO "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  ## process arguments
  # transform _XACRO_INORDER's BOOL value
  if(_XACRO_INORDER)
    set(_XACRO_INORDER "INORDER")
  else()
    unset(_XACRO_INORDER)
  endif()

  # prepare REMAP args (prepending REMAP)
  if(_XACRO_REMAP)
    set(_XACRO_REMAP REMAP ${_XACRO_REMAP})
  endif()
  # prepare DEPENDS args (prepending DEPENDS)
  if(_XACRO_DEPENDS)
    set(_XACRO_DEPENDS DEPENDS ${_XACRO_DEPENDS})
  endif()

  # have INSTALL option, but no TARGET: fallback to default target
  if(_XACRO_INSTALL AND NOT _XACRO_TARGET)
    # message(STATUS "xacro: no TARGET specified, using default")
    set(_XACRO_TARGET _xacro_auto_generate)
  endif()

  foreach(input ${_XACRO_UNPARSED_ARGUMENTS})
    # call to main function
    xacro_add_xacro_file(${input} ${_XACRO_OUTPUT} ${_XACRO_INORDER} ${_XACRO_REMAP} ${_XACRO_DEPENDS})
    list(APPEND outputs ${XACRO_OUTPUT_FILE})
  endforeach()

  if(outputs)
    # link to target
    add_custom_target(${PROJECT_NAME}_${_XACRO_TARGET} ALL DEPENDS ${outputs})

    # install?
    if(_XACRO_INSTALL)
      xacro_install(${_XACRO_TARGET} ${outputs} DESTINATION ${_XACRO_DESTINATION})
    endif(_XACRO_INSTALL)
  endif()
endfunction(xacro_add_files)


test case for multiple belongs to on new model

describe "rest", ->

  adapter = null
  session = null

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

  afterEach ->
    delete Ep.__container__

  describe "managing groups with embedded members", ->

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

      class @Member extends Ep.Model
        role: Ep.attr('string')
        group: Ep.belongsTo(@Group)
      @App.Member = @Member

      class @User extends Ep.Model
        name: Ep.attr('string')
        groups: Ep.hasMany(@Group)
        members: Ep.hasMany(@Member)
      @App.User = @User

      @Group.reopen
        members: Ep.hasMany(@Member)
        user: Ep.belongsTo(@User)

      @Member.reopen
        user: Ep.belongsTo(@User)

      @container.register 'model:group', @Group, instantiate: false
      @container.register 'model:member', @Member, instantiate: false
      @container.register 'model:user', @User, instantiate: false

      GroupSerializer = Ep.RestSerializer.extend
        properties:
          members:
            embedded: 'always'

      @container.register 'serializer:group', GroupSerializer

    it 'creates new group and then deletes a member', ->
      adapter.r['POST:/users'] = -> users: {client_id: user.clientId, id: "1", name: "wes"}
      adapter.r['POST:/groups'] = (url, type, hash) ->
        expect(hash.data.group.members[0].role).to.eq('chief')
        return groups: {client_id: group.clientId, id: "2", name: "brogrammers", members: [{client_id: member.clientId, id: "3", role: "chief", post_id: "2", user_id: "1"}], user_id: "1"}

      childSession = session.newSession()
      user = childSession.create 'user', name: 'wes'
      group = null
      member = null
      childSession.flush().then ->
        expect(user.id).to.not.be.null
        expect(adapter.h).to.eql(['POST:/users'])
        childSession = session.newSession()
        user = childSession.add(user)
        group = childSession.create 'group', name: 'brogrammers', user: user
        member = childSession.create 'member', role: 'chief', user: user, group: group
        childSession.flush().then ->
          expect(adapter.h).to.eql(['POST:/users', 'POST:/groups'])
          expect(user.id).to.not.be.null
          expect(group.id).to.not.be.null
          expect(group.members.length).to.eq(1)
          expect(user.groups.length).to.eq(1)
          expect(user.members.length).to.eq(1)
          expect(member.id).to.not.be.null

          childSession = session.newSession()
          member = childSession.add(member)
          user = childSession.add(user)
          group = childSession.add(group)
          childSession.deleteModel(member)
          expect(user.members.length).to.eq(0)
          expect(group.members.length).to.eq(0)
          expect(user.groups.length).to.eq(1)

          adapter.r['PUT:/groups/2'] = -> groups: {client_id: group.clientId, id: "2", name: "brogrammers", members: [], user_id: "1"}
          childSession.flush().then ->
            expect(member.get('isDeleted')).to.be.true
            expect(group.members.length).to.eq(0)
            expect(user.members.length).to.eq(0)
            expect(user.groups.length).to.eq(1)
            expect(adapter.h).to.eql(['POST:/users', 'POST:/groups', 'PUT:/groups/2'])

    it "doesn't choke when loading a group without a members key", ->
      adapter.r['GET:/groups'] = groups: [{client_id: null, id: "1", name: "brogrammers", user_id: "1"}]

      session.query("group").then (result) ->
        expect(adapter.h).to.eql(['GET:/groups'])
        expect(result.content.length).to.eq(1)
        expect(result.content[0].name).to.eq("brogrammers")
        expect(result.content[0].groups).to.be.undefined

    it 'adds a member to an existing group', ->
      adapter.r['GET:/groups/1'] = -> groups: {id: "1", name: "employees", members: [{id: "2", name: "kinz", group_id: "1", user_id: "3"}]}, users: {id: "3", name: "wtf", member_id: "2"}

      session.load("group", 1).then (group) ->
        expect(adapter.h).to.eql(['GET:/groups/1'])

        childSession = session.newSession()
        childGroup = childSession.add(group)

        existingMember = childGroup.members.firstObject
        expect(existingMember.user).to.not.be.null
        expect(existingMember.user.isDetached).to.be.false

        member = childSession.create('member', {name: "mollie"})
        childGroup.members.addObject(member)

        expect(childGroup.members.length).to.eq(2)
        expect(group.members.length).to.eq(1)

        adapter.r['PUT:/groups/1'] = -> groups: {id: "1", name: "employees", members: [{id: "2", name: "kinz", group_id: "1"}, {id: 3, client_id: member.clientId, name: "mollie", group_id: "1"}]}
        promise = childSession.flush().then ->
          expect(childGroup.members.length).to.eq(2)
          expect(group.members.length).to.eq(2)
          expect(adapter.h).to.eql(['GET:/groups/1', 'PUT:/groups/1'])

        expect(group.members.length).to.eq(2)
        promise

  describe "managing comments", ->

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

      class @Comment extends Ep.Model
        message: Ep.attr('string')
        post: Ep.belongsTo(@Post)
      @App.Comment = @Comment

      @Post.reopen
        comments: Ep.hasMany(@Comment)

      @container.register 'model:post', @Post, instantiate: false
      @container.register 'model:comment', @Comment, instantiate: false

    it 'creates a new comment within a child session', ->
      adapter.r['POST:/comments'] = -> comment: {client_id: comment.clientId, id: "3", message: "#2", post_id: "1"}

      post = session.merge @Post.create(id: "1", title: "brogrammer's guide to beer pong")
      session.merge @Comment.create(id: "2", message: "yo", post: post)

      childSession = session.newSession()
      childPost = childSession.add(post)
      comment = childSession.create 'comment',
        message: '#2',
        post: childPost

      expect(childPost.comments.length).to.eq(2)

      promise = childSession.flush().then ->
        expect(childPost.comments.length).to.eq(2)
        expect(post.comments.length).to.eq(2)

      expect(childPost.comments.length).to.eq(2)
      expect(post.comments.length).to.eq(2)
      promise

  describe "two levels of embedded", ->

    beforeEach ->
      class @User extends Ep.Model
        name: Ep.attr('string')
        profile: Ep.belongsTo('profile')
      @App.User = @User

      class @Profile extends Ep.Model
        bio: Ep.attr('string')
        user: Ep.belongsTo('user')
        tags: Ep.hasMany('tag')
      @App.Profile = @Profile

      class @Tag extends Ep.Model
        name: Ep.attr('string')
        profile: Ep.belongsTo('profile')
      @App.User = @User

      @container.register 'model:user', @User, instantiate: false
      @container.register 'model:profile', @Profile, instantiate: false
      @container.register 'model:tag', @Tag, instantiate: false

      UserSerializer = Ep.RestSerializer.extend
        properties:
          profile:
            embedded: 'always'

      @container.register 'serializer:user', UserSerializer

      ProfileSerializer = Ep.RestSerializer.extend
        properties:
          tags:
            embedded: 'always'

      @container.register 'serializer:profile', ProfileSerializer

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

      user = session.merge @User.create
        id: '1'
        name: 'abby'
        profile: @Profile.create
          id: '2'
          bio: 'asd'
          tags: [@Tag.create(id: '3', name: 'java')]

      session.deleteModel(user)
      session.flush().then ->
        expect(adapter.h).to.eql(['DELETE:/users/1'])
        expect(user.isDeleted).to.be.true

  describe 'multiple belongsTo', ->
    beforeEach ->
      class @Foo extends Ep.Model
      @App.Foo = @Foo
      class @Bar extends Ep.Model
      @App.Bar = @Bar
      class @Baz extends Ep.Model
      @App.Baz = @Baz

      @Foo.reopen
        bar: Ep.belongsTo @Bar
        baz: Ep.belongsTo @Baz

      @Bar.reopen
        foos: Ep.hasMany @Foo

      @Baz.reopen
        foos: Ep.hasMany @Foo

      @container.register 'model:foo', @Foo, instantiate: false
      @container.register 'model:bar', @Bar, instantiate: false
      @container.register 'model:baz', @Baz, instantiate: false

    it 'sets ids properly', ->
      adapter.r['POST:/bars'] = -> bar: {client_id: bar.clientId, id: "1"}
      adapter.r['POST:/bazs'] = -> baz: {client_id: baz.clientId, id: "1"}
      adapter.r['POST:/foos'] = (url, type, hash) ->
        expect(hash.data.foo.bar_id).to.eq '1'
        expect(hash.data.foo.baz_id).to.eq '1'
        foo: {client_id: foo.clientId, id: "1", bar_id: "1", baz_id: "1"}

      childSession = session.newSession()
      foo = childSession.create 'foo'
      bar = childSession.create 'bar'
      baz = childSession.create 'baz'
      foo.set 'bar', bar
      foo.set 'baz', baz
      childSession.flush().thenn ->
        expect(adapter.h).to.eql ['POST:bars', 'POST:/bazs' 'POST:/foos']
        expect(foo.id).to.not.be.null
        expect(bar.id).to.not.be.null
        expect(baz.id).to.not.be.null
        expect(foo.bar).to.not.be.null
        expect(foo.baz).to.not.be.null
        expect(bar.foos.length).to.eq 1
        expect(baz.foos.length).to.eq 1


modify get() API in persistentMap.em

modify get() API in persistentMap.em

if (typeof(std) === "undefined") /** @namespace */ std = {};

/* Map data structure
 * Constructor:
 *   e.g. X = new std.persistentMap('mapTest', function(exist){...});
 *
 * APIs:
 *   set(key,value)     : Write element to memory
 *   copy(map)          : Write all elements in given associative array to this map (in memory)
 *                          e.g. M = {'a':'x','b':'y'};  X.copy(M);
 *   get(key)           : Read element with given key from memory, return the value
 *   get(key,cb)        : Read element with given key from memory first, if not exists, read
 *                        from backend storage then. Callback: cb(success, value)
 *   erase(key)         : Erase element from memory
 *   clear()            : Clear content in memory
 *   flush(cb)          : Flush changes to backend storage. Callback: cb(success)
 *   retrieve(keys,cb)  : Retrieve elements with given list of keys from backend to memory
 *                        Callback: cb(success)
 *   restore(cb)        : Restore all elements from backend to memory. Callback: cb(success)
 *   reset(cb)          : Clear content in both memory and backend. Callback: cb(success)
 *   name()             : Return name of the map
 *   data()             : Return all elements in memory, as an associative array
 *   keys()             : Return all keys in both memory and backend, as an array.
 *   size()             : Return number of elements in memory and backend
 *   empty()            : Return true if map is emty, false otherwise
 *   contains(key)      : Return true if the map contains the key, false otherwise
 *   isResident(key)    : Return true if the key is resident in memory, false otherwise
 */

std.persistentMap = function(name, cb)
{
    this._type = 'map';
    this._name = name;
    this._mapName = keyName(this._type,this._name);
    this._data = {};
    this._keys = {};
    this._dirtyKeys = {};
    this._keys_list = {};
    this._init(cb);
};

std.persistentMap.prototype._init = function(cb)
{
    system.storageRead(this._mapName, std.core.bind(this._initCommit, this, cb));
};

std.persistentMap.prototype._initCommit = function(cb, exist, val)
{
    if (!exist)
        system.print('This is a new map')
    else {
        this._keys = StringtoKeys(val[this._mapName]);
        this._keys_list = this._keys;
        system.print('Map exists, restore keys')
    }
    cb(exist);
};

std.persistentMap.prototype.set = function(key, value)
{
    this._data[key]=value;
    this._dirtyKeys[key] = 1;
    this._keys[key] = 1;
};

std.persistentMap.prototype.copy = function(map)
{
    for(key in map)
        this.set(key, map[key]);
}

std.persistentMap.prototype.get = function()
{
    if (arguments.length == 1) {
        key = arguments[0];
        return this._data[key];
    }
    else if (arguments.length == 2) {
        key = arguments[0];
        cb = arguments[1];
        if(key in this._data)
            cb(true, this._data[key]);
        else if(key in this._key)
            system.storageRead(keyName(this._mapName, key), std.core.bind(this._getCommit, this, key, cb));
        else
            cb(false, '');
    }
    else {
        system.print('API *get* usage: get(key) or get(key, cb)');
        return false;
    }
};

std.persistentMap.prototype._getCommit = function(key, cb, success, val)
{
    if(!success)
        system.print('key does not exist');
    else
        this._data[key]=val[keyName(this._mapName, key)];
    cb(success, this._data[key]);
};

std.persistentMap.prototype.erase = function(key)
{
    delete this._data[key];
    delete this._keys[key];
    if (key in this._keys_list)
        this._dirtyKeys[key] = 0;
    else
        delete this._dirtyKeys[key];
};

std.persistentMap.prototype.clear = function()
{
    for (key in this._data)
        this.erase(key);
};

std.persistentMap.prototype.flush = function(cb)
{
    system.storageBeginTransaction();
    for (key in this._dirtyKeys) {
        if(this._dirtyKeys[key]==1)
            system.storageWrite(keyName(this._mapName, key), this._data[key]);
        if(this._dirtyKeys[key]==0)
            system.storageErase(keyName(this._mapName, key));
    }
    system.storageWrite(this._mapName, KeystoString(this._keys));
    system.storageCommit(std.core.bind(this._flushCommit, this, cb));
};

std.persistentMap.prototype._flushCommit = function(cb, success)
{
    if (!success)
        system.print('Flush fails');
    else {
        this._dirtyKeys={};
        this._keys_list = this._keys;
    }
    cb(success);
};

std.persistentMap.prototype.retrieve = function(keys, cb)
{
    system.storageBeginTransaction();
    for(i=0; i<keys.length; i++)
        system.storageRead(keyName(this._mapName, keys[i]));
    system.storageCommit(std.core.bind(this._retrieveCommit, this, keys, cb));
};

std.persistentMap.prototype._retrieveCommit = function(keys, cb, success, val)
{
    if(!success)
        system.print('Retrieve fails');
    else
        for (i=0; i<keys.length; i++)
            this._data[keys[i]] = val[keyName(this._mapName, keys[i])];
    cb(success);
};

std.persistentMap.prototype.restore = function(cb)
{
    system.storageRead(this._mapName, std.core.bind(this._restore, this, cb));
};

std.persistentMap.prototype._restore = function(cb, success, val)
{
    if (!success) {
        system.print('Map does not exist');
        cb(success);
    }
    else {
        this._keys = {};
        list = val[this._mapName].split(',');
        if(list.length>0) {
            system.storageBeginTransaction();
            for (i=0; i<list.length; i++) {
                system.storageRead(keyName(this._mapName, list[i]));
                this._keys[list[i]] = 1;
            }
            system.storageCommit(std.core.bind(this._restoreCommit, this, cb));
        }
        else {
            system.print('Map is empty');
            cb(success)
        }
    }
};

std.persistentMap.prototype._restoreCommit = function(cb, success, val)
{
    if (!success)
        system.print('Restore fails');
    else {
        for (key in val)
            this._data[key.split(':')[2]]=val[key];
        this._dirtyKeys = {};
        this._keys_list = this._keys;
    }
    cb(success);
};

std.persistentMap.prototype.reset = function(cb)
{
    system.storageBeginTransaction();
    system.storageErase(this._mapName);
    for(key in this._keys)
        system.storageErase(keyName(this._mapName, key));
    for(key in this._dirtyKeys)
        if(this._dirtyKeys[key]==0)
            system.storageErase(keyName(this._mapName, key));
    system.storageCommit(std.core.bind(this._resetCommit, this, cb));
};

std.persistentMap.prototype._resetCommit = function(cb, success)
{
    if (!success)
        system.print('reset fails');
    else {
        this._data = {};
        this._keys = {};
        this._dirtyKeys = {};
        this._keys_list = {};
        system.print('map reset');
    }
    cb(success);
};

std.persistentMap.prototype.contains = function(key)
{
    if (key in this._keys)
        return true;
    else
        return false;
};

std.persistentMap.prototype.isResident = function(key)
{
    if (key in this._data)
        return true;
    else
        return false;
};

std.persistentMap.prototype.type = function()
{
    return this._type;
};

std.persistentMap.prototype.name = function()
{
    return this._name;
};

std.persistentMap.prototype.data = function()
{
    return this._data;
};

std.persistentMap.prototype.dirtyKeys = function()
{
    return KeystoList(this._dirtyKeys);
};

std.persistentMap.prototype.keys = function()
{
    return KeystoList(this._keys);
};

std.persistentMap.prototype.size = function()
{
    size = 0;
    for (key in this._keys)
        size++;
    return size;
};

std.persistentMap.prototype.empty = function()
{
    for (key in this._keys)
        return false;
    return true;
};

var keyName = function(name, subname) {
    return name + ':' + subname;
};

var KeystoString = function(keys) {
    str='';
    for (key in keys)
        str = str+','+key;
    return str.substr(1);
};

var KeystoList = function(keys) {
    list = [];
    for (key in keys)
        list.push(key);
    return list;
};

var StringtoKeys = function(str) {
    list = str.split(',');
    keys = {};
    for (i=0; i<list.length; i++)
        keys[list[i]] = 1;
    return keys;
};

if (typeof(std) === "undefined") /** @namespace */ std = {};

/* Map data structure
 * Constructor:
 *   e.g. X = new std.persistentMap('mapTest', function(exist){...});
 *
 * APIs:
 *   set(key,value)     : Write element to memory
 *   copy(map)          : Write all elements in given associative array to this map (in memory)
 *                          e.g. M = {'a':'x','b':'y'};  X.copy(M);
 *   get(key)           : Read element with given key from memory, return the value
 *   get(key,cb)        : Read element with given key from memory first, if not exists, read
 *                        from backend storage then. Callback: cb(success, value)
 *   erase(key)         : Erase element from memory
 *   clear()            : Clear content in memory
 *   flush(cb)          : Flush changes to backend storage. Callback: cb(success)
 *   retrieve(keys,cb)  : Retrieve elements with given list of keys from backend to memory
 *                        Callback: cb(success)
 *   restore(cb)        : Restore all elements from backend to memory. Callback: cb(success)
 *   reset(cb)          : Clear content in both memory and backend. Callback: cb(success)
 *   name()             : Return name of the map
 *   data()             : Return all elements in memory, as an associative array
 *   keys()             : Return all keys in both memory and backend, as an array.
 *   size()             : Return number of elements in memory and backend
 *   empty()            : Return true if map is emty, false otherwise
 *   contains(key)      : Return true if the map contains the key, false otherwise
 *   isResident(key)    : Return true if the key is resident in memory, false otherwise
 */

std.persistentMap = function(name, cb)
{
    this._type = 'map';
    this._name = name;
    this._mapName = keyName(this._type,this._name);
    this._data = {};
    this._keys = {};
    this._dirtyKeys = {};
    this._keys_list = {};
    this._init(cb);
};

std.persistentMap.prototype._init = function(cb)
{
    system.storageRead(this._mapName, std.core.bind(this._initCommit, this, cb));
};

std.persistentMap.prototype._initCommit = function(cb, exist, val)
{
    if (!exist)
        system.print('This is a new map')
    else {
        this._keys = StringtoKeys(val[this._mapName]);
        this._keys_list = this._keys;
        system.print('Map exists, restore keys')
    }
    cb(exist);
};

std.persistentMap.prototype.set = function(key, value)
{
    this._data[key]=value;
    this._dirtyKeys[key] = 1;
    this._keys[key] = 1;
};

std.persistentMap.prototype.copy = function(map)
{
    for(key in map)
        this.set(key, map[key]);
}

std.persistentMap.prototype.get = function()
{
    if (arguments.length == 1) {
        key = arguments[0];
        return this._data[key];
    }
    else if (arguments.length == 2) {
        key = arguments[0];
        cb = arguments[1];
        if(key in this._data)
            cb(true, this._data[key]);
        else
            system.storageRead(keyName(this._mapName, key), std.core.bind(this._getCommit, this, key, cb));
    }
    else {
        system.print('API *get* usage: get(key) or get(key, cb)');
        return false;
    }
};

std.persistentMap.prototype._getCommit = function(key, cb, success, val)
{
    if(!success)
        system.print('key does not exist');
    else
        this._data[key]=val[keyName(this._mapName, key)];
    cb(success, this._data[key]);
};

std.persistentMap.prototype.erase = function(key)
{
    delete this._data[key];
    delete this._keys[key];
    if (key in this._keys_list)
        this._dirtyKeys[key] = 0;
    else
        delete this._dirtyKeys[key];
};

std.persistentMap.prototype.clear = function()
{
    for (key in this._data)
        this.erase(key);
};

std.persistentMap.prototype.flush = function(cb)
{
    system.storageBeginTransaction();
    for (key in this._dirtyKeys) {
        if(this._dirtyKeys[key]==1)
            system.storageWrite(keyName(this._mapName, key), this._data[key]);
        if(this._dirtyKeys[key]==0)
            system.storageErase(keyName(this._mapName, key));
    }
    system.storageWrite(this._mapName, KeystoString(this._keys));
    system.storageCommit(std.core.bind(this._flushCommit, this, cb));
};

std.persistentMap.prototype._flushCommit = function(cb, success)
{
    if (!success)
        system.print('Flush fails');
    else {
        this._dirtyKeys={};
        this._keys_list = this._keys;
    }
    cb(success);
};

std.persistentMap.prototype.retrieve = function(keys, cb)
{
    system.storageBeginTransaction();
    for(i=0; i<keys.length; i++)
        system.storageRead(keyName(this._mapName, keys[i]));
    system.storageCommit(std.core.bind(this._retrieveCommit, this, keys, cb));
};

std.persistentMap.prototype._retrieveCommit = function(keys, cb, success, val)
{
    if(!success)
        system.print('Retrieve fails');
    else
        for (i=0; i<keys.length; i++)
            this._data[keys[i]] = val[keyName(this._mapName, keys[i])];
    cb(success);
};

std.persistentMap.prototype.restore = function(cb)
{
    system.storageRead(this._mapName, std.core.bind(this._restore, this, cb));
};

std.persistentMap.prototype._restore = function(cb, success, val)
{
    if (!success) {
        system.print('Map does not exist');
        cb(success);
    }
    else {
        this._keys = {};
        list = val[this._mapName].split(',');
        if(list.length>0) {
            system.storageBeginTransaction();
            for (i=0; i<list.length; i++) {
                system.storageRead(keyName(this._mapName, list[i]));
                this._keys[list[i]] = 1;
            }
            system.storageCommit(std.core.bind(this._restoreCommit, this, cb));
        }
        else {
            system.print('Map is empty');
            cb(success)
        }
    }
};

std.persistentMap.prototype._restoreCommit = function(cb, success, val)
{
    if (!success)
        system.print('Restore fails');
    else {
        for (key in val)
            this._data[key.split(':')[2]]=val[key];
        this._dirtyKeys = {};
        this._keys_list = this._keys;
    }
    cb(success);
};

std.persistentMap.prototype.reset = function(cb)
{
    system.storageBeginTransaction();
    system.storageErase(this._mapName);
    for(key in this._keys)
        system.storageErase(keyName(this._mapName, key));
    for(key in this._dirtyKeys)
        if(this._dirtyKeys[key]==0)
            system.storageErase(keyName(this._mapName, key));
    system.storageCommit(std.core.bind(this._resetCommit, this, cb));
};

std.persistentMap.prototype._resetCommit = function(cb, success)
{
    if (!success)
        system.print('reset fails');
    else {
        this._data = {};
        this._keys = {};
        this._dirtyKeys = {};
        this._keys_list = {};
        system.print('map reset');
    }
    cb(success);
};

std.persistentMap.prototype.contains = function(key)
{
    if (key in this._keys)
        return true;
    else
        return false;
};

std.persistentMap.prototype.isResident = function(key)
{
    if (key in this._data)
        return true;
    else
        return false;
};

std.persistentMap.prototype.type = function()
{
    return this._type;
};

std.persistentMap.prototype.name = function()
{
    return this._name;
};

std.persistentMap.prototype.data = function()
{
    return this._data;
};

std.persistentMap.prototype.dirtyKeys = function()
{
    return KeystoList(this._dirtyKeys);
};

std.persistentMap.prototype.keys = function()
{
    return KeystoList(this._keys);
};

std.persistentMap.prototype.size = function()
{
    size = 0;
    for (key in this._keys)
        size++;
    return size;
};

std.persistentMap.prototype.empty = function()
{
    for (key in this._keys)
        return false;
    return true;
};

var keyName = function(name, subname) {
    return name + ':' + subname;
};

var KeystoString = function(keys) {
    str='';
    for (key in keys)
        str = str+','+key;
    return str.substr(1);
};

var KeystoList = function(keys) {
    list = [];
    for (key in keys)
        list.push(key);
    return list;
};

var StringtoKeys = function(str) {
    list = str.split(',');
    keys = {};
    for (i=0; i<list.length; i++)
        keys[list[i]] = 1;
    return keys;
};


remove threshold for unstable results for skipped test

    <xunit plugin="xunit@@1.102">
      <types>
        <GoogleTestType>
          <pattern>@ESCAPE(pattern)</pattern>
          <skipNoTestFiles>true</skipNoTestFiles>
          <failIfNotNew>true</failIfNotNew>
          <deleteOutputFiles>true</deleteOutputFiles>
          <stopProcessingIfError>true</stopProcessingIfError>
        </GoogleTestType>
      </types>
      <thresholds>
        <org.jenkinsci.plugins.xunit.threshold.FailedThreshold>
          <unstableThreshold>0</unstableThreshold>
          <unstableNewThreshold/>
          <failureThreshold/>
          <failureNewThreshold/>
        </org.jenkinsci.plugins.xunit.threshold.FailedThreshold>
        <org.jenkinsci.plugins.xunit.threshold.SkippedThreshold>
          <unstableThreshold/>
          <unstableNewThreshold/>
          <failureThreshold/>
          <failureNewThreshold/>
        </org.jenkinsci.plugins.xunit.threshold.SkippedThreshold>
      </thresholds>
      <thresholdMode>1</thresholdMode>
      <extraConfiguration>
        <testTimeMargin>3000</testTimeMargin>
      </extraConfiguration>
    </xunit>

    <xunit plugin="xunit@@1.102">
      <types>
        <GoogleTestType>
          <pattern>@ESCAPE(pattern)</pattern>
          <skipNoTestFiles>true</skipNoTestFiles>
          <failIfNotNew>true</failIfNotNew>
          <deleteOutputFiles>true</deleteOutputFiles>
          <stopProcessingIfError>true</stopProcessingIfError>
        </GoogleTestType>
      </types>
      <thresholds>
        <org.jenkinsci.plugins.xunit.threshold.FailedThreshold>
          <unstableThreshold>0</unstableThreshold>
          <unstableNewThreshold/>
          <failureThreshold/>
          <failureNewThreshold/>
        </org.jenkinsci.plugins.xunit.threshold.FailedThreshold>
        <org.jenkinsci.plugins.xunit.threshold.SkippedThreshold>
          <unstableThreshold>0</unstableThreshold>
          <unstableNewThreshold/>
          <failureThreshold/>
          <failureNewThreshold/>
        </org.jenkinsci.plugins.xunit.threshold.SkippedThreshold>
      </thresholds>
      <thresholdMode>1</thresholdMode>
      <extraConfiguration>
        <testTimeMargin>3000</testTimeMargin>
      </extraConfiguration>
    </xunit>