/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 * vi:set noexpandtab tabstop=8 shiftwidth=8:
 *
 * Copyright (C) 2013 Matthias Clasen <mclasen@redhat.com>
 * Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: GPL-2.0+
 */

#include "config.h"

#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include "gs-feature-tile.h"
#include "gs-common.h"
#include "gs-css.h"

struct _GsFeatureTile
{
	GsAppTile	 parent_instance;
	GtkWidget	*stack;
	GtkWidget	*image;
	GtkWidget	*title;
	GtkWidget	*subtitle;
	const gchar	*markup_cache;  /* (unowned) (nullable) */
	GtkCssProvider	*tile_provider;  /* (owned) (nullable) */
	GtkCssProvider	*title_provider;  /* (owned) (nullable) */
	GtkCssProvider	*subtitle_provider;  /* (owned) (nullable) */
	GArray		*key_colors_cache;  /* (unowned) (nullable) */
	gboolean	 narrow_mode;
	guint		 refresh_id;
};

#define GS_TYPE_FEATURE_TILE_LAYOUT (gs_feature_tile_layout_get_type ())

G_DECLARE_FINAL_TYPE (GsFeatureTileLayout, gs_feature_tile_layout, GS, FEATURE_TILE_LAYOUT, GtkLayoutManager)

/* This is a copy of GtkBinLayout, because cannot derive from it */
struct _GsFeatureTileLayout
{
	GtkLayoutManager parent_instance;
};

G_DEFINE_TYPE (GsFeatureTileLayout, gs_feature_tile_layout, GTK_TYPE_LAYOUT_MANAGER)

static void
gs_feature_tile_layout_measure (GtkLayoutManager *layout_manager,
				GtkWidget        *widget,
				GtkOrientation    orientation,
				int               for_size,
				int              *minimum,
				int              *natural,
				int              *minimum_baseline,
				int              *natural_baseline)
{
	GtkWidget *child;

	for (child = gtk_widget_get_first_child (widget);
	     child != NULL;
	     child = gtk_widget_get_next_sibling (child)) {
		if (gtk_widget_should_layout (child)) {
			int child_min = 0;
			int child_nat = 0;
			int child_min_baseline = -1;
			int child_nat_baseline = -1;

			gtk_widget_measure (child, orientation, for_size,
					    &child_min, &child_nat,
					    &child_min_baseline, &child_nat_baseline);

			*minimum = MAX (*minimum, child_min);
			*natural = MAX (*natural, child_nat);

			if (child_min_baseline > -1)
				*minimum_baseline = MAX (*minimum_baseline, child_min_baseline);
			if (child_nat_baseline > -1)
				*natural_baseline = MAX (*natural_baseline, child_nat_baseline);
		}
	}
}

static void gs_feature_tile_refresh (GsAppTile *self);

static gboolean
gs_feature_tile_refresh_idle_cb (gpointer user_data)
{
	GsFeatureTile *tile = user_data;

	tile->refresh_id = 0;

	gs_feature_tile_refresh (GS_APP_TILE (tile));

	return G_SOURCE_REMOVE;
}

static void
gs_feature_tile_layout_allocate (GtkLayoutManager *layout_manager,
				 GtkWidget        *widget,
				 int               width,
				 int               height,
				 int               baseline)
{
	GsFeatureTile *tile = GS_FEATURE_TILE (widget);
	GtkWidget *child;
	gboolean narrow_mode;

	for (child = gtk_widget_get_first_child (widget);
	     child != NULL;
	     child = gtk_widget_get_next_sibling (child)) {
		if (child && gtk_widget_should_layout (child))
			gtk_widget_allocate (child, width, height, baseline, NULL);
	}

	/* Engage ‘narrow mode’ if the allocation becomes too narrow. The exact
	 * choice of width is arbitrary here. */
	narrow_mode = (width < 600);
	if (tile->narrow_mode != narrow_mode) {
		tile->narrow_mode = narrow_mode;
		if (!tile->refresh_id)
			tile->refresh_id = g_idle_add (gs_feature_tile_refresh_idle_cb, tile);
	}
}

static void
gs_feature_tile_layout_class_init (GsFeatureTileLayoutClass *klass)
{
	GtkLayoutManagerClass *layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (klass);

	layout_manager_class->measure = gs_feature_tile_layout_measure;
	layout_manager_class->allocate = gs_feature_tile_layout_allocate;
}

static void
gs_feature_tile_layout_init (GsFeatureTileLayout *layout)
{
}

/* A colour represented in hue, saturation, brightness form; with an additional
 * field for its contrast calculated with respect to some external colour.
 *
 * See https://en.wikipedia.org/wiki/HSL_and_HSV */
typedef struct
{
	gfloat hue;  /* [0.0, 1.0] */
	gfloat saturation;  /* [0.0, 1.0] */
	gfloat brightness;  /* [0.0, 1.0]; also known as lightness (HSL) or value (HSV) */
	gfloat contrast;  /* (0.047, 21] */
} GsHSBC;

G_DEFINE_TYPE (GsFeatureTile, gs_feature_tile, GS_TYPE_APP_TILE)

static void
gs_feature_tile_dispose (GObject *object)
{
	GsFeatureTile *tile = GS_FEATURE_TILE (object);

	if (tile->refresh_id) {
		g_source_remove (tile->refresh_id);
		tile->refresh_id = 0;
	}

	g_clear_object (&tile->tile_provider);
	g_clear_object (&tile->title_provider);
	g_clear_object (&tile->subtitle_provider);

	G_OBJECT_CLASS (gs_feature_tile_parent_class)->dispose (object);
}

/* These are subjectively chosen. See below. */
static const gfloat min_valid_saturation = 0.5;
static const gfloat max_valid_saturation = 0.85;

/* The minimum absolute contrast ratio between the foreground and background
 * colours, from WCAG:
 * https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html */
static const gfloat min_abs_contrast = 4.5;

/* Sort two candidate background colours for the feature tile, ranking them by
 * suitability for being chosen as the background colour, with the most suitable
 * first.
 *
 * There are several criteria being used here:
 *  1. First, colours are sorted by whether their saturation is in the range
 *     [0.5, 0.85], which is a subjectively-chosen range of ‘light, but not too
 *     saturated’ colours.
 *  2. Colours with saturation in that valid range are then sorted by contrast,
 *     with higher contrast being preferred. The contrast is calculated against
 *     an external colour by the caller.
 *  3. Colours with saturation outside that valid range are sorted by their
 *     absolute distance from the range, so that colours which are nearer to
 *     having a valid saturation are preferred. This is useful in the case where
 *     none of the key colours in this array have valid saturations; the caller
 *     will want the one which is closest to being valid.
 */
static gboolean
saturation_is_valid (const GsHSBC *hsbc,
                     gfloat       *distance_from_valid_range)
{
	*distance_from_valid_range = (hsbc->saturation > max_valid_saturation) ? hsbc->saturation - max_valid_saturation : min_valid_saturation - hsbc->saturation;
	return (hsbc->saturation >= min_valid_saturation && hsbc->saturation <= max_valid_saturation);
}

static gint
colors_sort_cb (gconstpointer a,
		gconstpointer b)
{
	const GsHSBC *hsbc_a = a;
	const GsHSBC *hsbc_b = b;
	gfloat hsbc_a_distance_from_range, hsbc_b_distance_from_range;
	gboolean hsbc_a_saturation_in_range = saturation_is_valid (hsbc_a, &hsbc_a_distance_from_range);
	gboolean hsbc_b_saturation_in_range = saturation_is_valid (hsbc_b, &hsbc_b_distance_from_range);

	if (hsbc_a_saturation_in_range && !hsbc_b_saturation_in_range)
		return -1;
	else if (!hsbc_a_saturation_in_range && hsbc_b_saturation_in_range)
		return 1;
	else if (!hsbc_a_saturation_in_range && !hsbc_b_saturation_in_range)
		return hsbc_a_distance_from_range - hsbc_b_distance_from_range;
	else
		return ABS (hsbc_b->contrast) - ABS (hsbc_a->contrast);
}

static gint
colors_sort_contrast_cb (gconstpointer a,
                         gconstpointer b)
{
	const GsHSBC *hsbc_a = a;
	const GsHSBC *hsbc_b = b;

	return hsbc_b->contrast - hsbc_a->contrast;
}

/* Calculate the relative luminance of @colour. This is [0.0, 1.0], where 0.0 is
 * the darkest black, and 1.0 is the lightest white.
 *
 * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef */
static gfloat
relative_luminance (const GsHSBC *colour)
{
	gfloat red, green, blue;
	gfloat r, g, b;
	gfloat luminance;

	/* Convert to sRGB */
	gtk_hsv_to_rgb (colour->hue, colour->saturation, colour->brightness,
			&red, &green, &blue);

	r = (red <= 0.03928) ? red / 12.92 : pow ((red + 0.055) / 1.055, 2.4);
	g = (green <= 0.03928) ? green / 12.92 : pow ((green + 0.055) / 1.055, 2.4);
	b = (blue <= 0.03928) ? blue / 12.92 : pow ((blue + 0.055) / 1.055, 2.4);

	luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
	g_assert (luminance >= 0.0 && luminance <= 1.0);
	return luminance;
}

/* Calculate the WCAG contrast ratio between the two colours. The returned ratio
 * is in the range (0.047, 21].
 *
 * https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html#contrast-ratiodef */
static gfloat
wcag_contrast (const GsHSBC *foreground,
               const GsHSBC *background)
{
	const GsHSBC *lighter, *darker;
	gfloat ratio;

	if (foreground->brightness >= background->brightness) {
		lighter = foreground;
		darker = background;
	} else {
		lighter = background;
		darker = foreground;
	}

	ratio = (relative_luminance (lighter) + 0.05) / (relative_luminance (darker) + 0.05);
	g_assert (ratio > 0.047 && ratio <= 21);
	return ratio;
}

/* Calculate a new brightness value for @background which improves its contrast
 * (as calculated using wcag_contrast()) with @foreground to at least
 * @desired_contrast.
 *
 * The return value is in the range [0.0, 1.0].
 */
static gfloat
wcag_contrast_find_brightness (const GsHSBC *foreground,
                               const GsHSBC *background,
                               gfloat        desired_contrast)
{
	GsHSBC modified_background;

	g_assert (desired_contrast > 0.047 && desired_contrast <= 21);

	/* This is an optimisation problem of modifying @background until
	 * the WCAG contrast is at least @desired_contrast. There might be a
	 * closed-form solution to this but I can’t be bothered to work it out
	 * right now. An optimisation loop should work.
	 *
	 * wcag_contrast() compares the lightest and darkest of the two colours,
	 * so ensure the background brightness is modified in the correct
	 * direction (increased or decreased) depending on whether the
	 * foreground colour is originally the brighter. This gives the largest
	 * search space for the background colour brightness, and ensures the
	 * optimisation works with dark and light themes. */
	for (modified_background = *background;
	     modified_background.brightness >= 0.0 &&
	     modified_background.brightness <= 1.0 &&
	     wcag_contrast (foreground, &modified_background) < desired_contrast;
	     modified_background.brightness += ((foreground->brightness > 0.5) ? -0.1 : 0.1)) {
		/* Nothing to do here */
	}

	return CLAMP (modified_background.brightness, 0.0, 1.0);
}

static void
gs_feature_tile_refresh (GsAppTile *self)
{
	GsFeatureTile *tile = GS_FEATURE_TILE (self);
	GsApp *app = gs_app_tile_get_app (self);
	const gchar *markup = NULL;
	g_autofree gchar *name = NULL;
	GtkStyleContext *context;
	g_autoptr(GIcon) icon = NULL;
	guint icon_size;

	if (app == NULL)
		return;

	gtk_stack_set_visible_child_name (GTK_STACK (tile->stack), "content");

	/* Set the narrow mode. */
	context = gtk_widget_get_style_context (GTK_WIDGET (self));
	if (tile->narrow_mode)
		gtk_style_context_add_class (context, "narrow");
	else
		gtk_style_context_remove_class (context, "narrow");

	/* Update the icon. Try a 160px version if not in narrow mode, and it’s
	 * available; otherwise use 128px. */
	if (!tile->narrow_mode) {
		icon = gs_app_get_icon_for_size (app,
						 160,
						 gtk_widget_get_scale_factor (tile->image),
						 NULL);
		icon_size = 160;
	}
	if (icon == NULL) {
		icon = gs_app_get_icon_for_size (app,
						 128,
						 gtk_widget_get_scale_factor (tile->image),
						 NULL);
		icon_size = 128;
	}

	if (icon != NULL) {
		gtk_image_set_from_gicon (GTK_IMAGE (tile->image), icon);
		gtk_image_set_pixel_size (GTK_IMAGE (tile->image), icon_size);
		gtk_widget_show (tile->image);
	} else {
		gtk_widget_hide (tile->image);
	}

	/* Update text and let it wrap if the widget is narrow. */
	gtk_label_set_label (GTK_LABEL (tile->title), gs_app_get_name (app));
	gtk_label_set_label (GTK_LABEL (tile->subtitle), gs_app_get_summary (app));

	gtk_label_set_wrap (GTK_LABEL (tile->subtitle), tile->narrow_mode);
	gtk_label_set_lines (GTK_LABEL (tile->subtitle), tile->narrow_mode ? 2 : 1);

	/* perhaps set custom css; cache it so that images don’t get reloaded
	 * unnecessarily. The custom CSS is direction-dependent, and will be
	 * reloaded when the direction changes. If RTL CSS isn’t set, fall back
	 * to the LTR CSS. */
	if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
		markup = gs_app_get_metadata_item (app, "GnomeSoftware::FeatureTile-css-rtl");
	if (markup == NULL)
		markup = gs_app_get_metadata_item (app, "GnomeSoftware::FeatureTile-css");

	if (tile->markup_cache != markup && markup != NULL) {
		g_autoptr(GsCss) css = gs_css_new ();
		g_autofree gchar *modified_markup = gs_utils_set_key_colors_in_css (markup, app);
		if (modified_markup != NULL)
			gs_css_parse (css, modified_markup, NULL);
		gs_utils_widget_set_css (GTK_WIDGET (tile), &tile->tile_provider, "feature-tile",
					 gs_css_get_markup_for_id (css, "tile"));
		gs_utils_widget_set_css (tile->title, &tile->title_provider, "feature-tile-name",
					 gs_css_get_markup_for_id (css, "name"));
		gs_utils_widget_set_css (tile->subtitle, &tile->subtitle_provider, "feature-tile-subtitle",
					 gs_css_get_markup_for_id (css, "summary"));
		tile->markup_cache = markup;
	} else if (markup == NULL) {
		GArray *key_colors = gs_app_get_key_colors (app);
		g_autofree gchar *css = NULL;

		/* If there is no override CSS for the app, default to a solid
		 * background colour based on the app’s key colors.
		 *
		 * Choose an arbitrary key color from the app’s key colors, and
		 * ensure that it’s:
		 *  - a light, not too saturated version of the dominant color
		 *    of the icon
		 *  - always light enough that grey text is visible on it
		 *
		 * Cache the result until the app’s key colours change, as the
		 * amount of calculation going on here is not entirely trivial.
		 */
		if (key_colors != tile->key_colors_cache) {
			g_autoptr(GArray) colors = NULL;
			GdkRGBA fg_rgba;
			gboolean fg_rgba_valid;
			GsHSBC fg_hsbc;
			const GsHSBC *chosen_hsbc;
			GsHSBC chosen_hsbc_modified;
			gboolean use_chosen_hsbc = FALSE;

			/* Look up the foreground colour for the feature tile,
			 * which is the colour of the text. This should always
			 * be provided as a named colour by the theme.
			 *
			 * Knowing the foreground colour allows calculation of
			 * the contrast between candidate background colours and
			 * the foreground which will be rendered on top of them.
			 *
			 * We want to choose a background colour with at least
			 * @min_abs_contrast contrast with the foreground, so
			 * that the text is legible.
			 */
			fg_rgba_valid = gtk_style_context_lookup_color (context, "theme_fg_color", &fg_rgba);
			g_assert (fg_rgba_valid);

			gtk_rgb_to_hsv (fg_rgba.red, fg_rgba.green, fg_rgba.blue,
					&fg_hsbc.hue, &fg_hsbc.saturation, &fg_hsbc.brightness);

			g_debug ("FG color: RGB: (%f, %f, %f), HSB: (%f, %f, %f)",
				 fg_rgba.red, fg_rgba.green, fg_rgba.blue,
				 fg_hsbc.hue, fg_hsbc.saturation, fg_hsbc.brightness);

			/* Convert all the RGBA key colours to HSB, and
			 * calculate their contrast against the foreground
			 * colour.
			 *
			 * The contrast is calculated as the Weber contrast,
			 * which is valid for small amounts of foreground colour
			 * (i.e. text) against larger background areas. Contrast
			 * is strictly calculated using luminance, but it’s OK
			 * to subjectively calculate it using brightness, as
			 * brightness is the subjective impression of luminance.
			 */
			if (key_colors != NULL)
				colors = g_array_sized_new (FALSE, FALSE, sizeof (GsHSBC), key_colors->len);

			g_debug ("Candidate background colors for %s:", gs_app_get_id (app));
			for (guint i = 0; key_colors != NULL && i < key_colors->len; i++) {
				const GdkRGBA *rgba = &g_array_index (key_colors, GdkRGBA, i);
				GsHSBC hsbc;

				gtk_rgb_to_hsv (rgba->red, rgba->green, rgba->blue,
						&hsbc.hue, &hsbc.saturation, &hsbc.brightness);
				hsbc.contrast = wcag_contrast (&fg_hsbc, &hsbc);
				g_array_append_val (colors, hsbc);

				g_debug (" • RGB: (%f, %f, %f), HSB: (%f, %f, %f), contrast: %f",
					 rgba->red, rgba->green, rgba->blue,
					 hsbc.hue, hsbc.saturation, hsbc.brightness,
					 hsbc.contrast);
			}

			/* Sort the candidate background colours to find the
			 * most appropriate one. */
			g_array_sort (colors, colors_sort_cb);

			/* If the developer/distro has provided override colours,
			 * use them. If there’s more than one override colour,
			 * use the one with the highest contrast with the
			 * foreground colour, unmodified. If there’s only one,
			 * modify it as below.
			 *
			 * If there are no override colours, take the top colour
			 * after sorting above. If it’s not good enough, modify
			 * its brightness to improve the contrast, and clamp its
			 * saturation to the valid range.
			 *
			 * If there are no colours, fall through and leave @css
			 * as %NULL. */
			if (gs_app_get_user_key_colors (app) &&
			    colors != NULL &&
			    colors->len > 1) {
				g_array_sort (colors, colors_sort_contrast_cb);

				chosen_hsbc = &g_array_index (colors, GsHSBC, 0);
				chosen_hsbc_modified = *chosen_hsbc;

				use_chosen_hsbc = TRUE;
			} else if (colors != NULL && colors->len > 0) {
				chosen_hsbc = &g_array_index (colors, GsHSBC, 0);
				chosen_hsbc_modified = *chosen_hsbc;

				chosen_hsbc_modified.saturation = CLAMP (chosen_hsbc->saturation, min_valid_saturation, max_valid_saturation);

				if (chosen_hsbc->contrast >= -min_abs_contrast &&
				    chosen_hsbc->contrast <= min_abs_contrast)
					chosen_hsbc_modified.brightness = wcag_contrast_find_brightness (&fg_hsbc, &chosen_hsbc_modified, min_abs_contrast);

				use_chosen_hsbc = TRUE;
			}

			if (use_chosen_hsbc) {
				GdkRGBA chosen_rgba;

				gtk_hsv_to_rgb (chosen_hsbc_modified.hue,
						chosen_hsbc_modified.saturation,
						chosen_hsbc_modified.brightness,
						&chosen_rgba.red, &chosen_rgba.green, &chosen_rgba.blue);

				g_debug ("Chosen background colour for %s (saturation %s, brightness %s): RGB: (%f, %f, %f), HSB: (%f, %f, %f)",
					 gs_app_get_id (app),
					 (chosen_hsbc_modified.saturation == chosen_hsbc->saturation) ? "not modified" : "modified",
					 (chosen_hsbc_modified.brightness == chosen_hsbc->brightness) ? "not modified" : "modified",
					 chosen_rgba.red, chosen_rgba.green, chosen_rgba.blue,
					 chosen_hsbc_modified.hue, chosen_hsbc_modified.saturation, chosen_hsbc_modified.brightness);

				css = g_strdup_printf ("background-color: rgb(%.0f,%.0f,%.0f);",
						       chosen_rgba.red * 255.f,
						       chosen_rgba.green * 255.f,
						       chosen_rgba.blue * 255.f);
			}

			gs_utils_widget_set_css (GTK_WIDGET (tile), &tile->tile_provider, "feature-tile", css);
			gs_utils_widget_set_css (tile->title, &tile->title_provider, "feature-tile-name", NULL);
			gs_utils_widget_set_css (tile->subtitle, &tile->subtitle_provider, "feature-tile-subtitle", NULL);

			tile->key_colors_cache = key_colors;
		}
	}

	switch (gs_app_get_state (app)) {
	case GS_APP_STATE_INSTALLED:
	case GS_APP_STATE_REMOVING:
	case GS_APP_STATE_UPDATABLE:
	case GS_APP_STATE_UPDATABLE_LIVE:
		name = g_strdup_printf ("%s (%s)",
					gs_app_get_name (app),
					_("Installed"));
		break;
	case GS_APP_STATE_AVAILABLE:
	case GS_APP_STATE_INSTALLING:
	default:
		name = g_strdup (gs_app_get_name (app));
		break;
	}

	if (name != NULL) {
		gtk_accessible_update_property (GTK_ACCESSIBLE (tile),
						GTK_ACCESSIBLE_PROPERTY_LABEL, name,
						GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, gs_app_get_summary (app),
						-1);
	}
}

static void
gs_feature_tile_direction_changed (GtkWidget *widget, GtkTextDirection previous_direction)
{
	GsFeatureTile *tile = GS_FEATURE_TILE (widget);

	gs_feature_tile_refresh (GS_APP_TILE (tile));
}

static void
gs_feature_tile_css_changed (GtkWidget         *widget,
                             GtkCssStyleChange *css_change)
{
	GsFeatureTile *tile = GS_FEATURE_TILE (widget);

	/* Clear the key colours cache, as the tile background colour will
	 * potentially need recalculating if the widget’s foreground colour has
	 * changed. */
	tile->key_colors_cache = NULL;

	gs_feature_tile_refresh (GS_APP_TILE (tile));

	GTK_WIDGET_CLASS (gs_feature_tile_parent_class)->css_changed (widget, css_change);
}

static void
gs_feature_tile_init (GsFeatureTile *tile)
{
	gtk_widget_init_template (GTK_WIDGET (tile));
}

static void
gs_feature_tile_class_init (GsFeatureTileClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
	GsAppTileClass *app_tile_class = GS_APP_TILE_CLASS (klass);

	object_class->dispose = gs_feature_tile_dispose;

	widget_class->css_changed = gs_feature_tile_css_changed;
	widget_class->direction_changed = gs_feature_tile_direction_changed;

	app_tile_class->refresh = gs_feature_tile_refresh;

	gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-feature-tile.ui");

	gtk_widget_class_bind_template_child (widget_class, GsFeatureTile, stack);
	gtk_widget_class_bind_template_child (widget_class, GsFeatureTile, image);
	gtk_widget_class_bind_template_child (widget_class, GsFeatureTile, title);
	gtk_widget_class_bind_template_child (widget_class, GsFeatureTile, subtitle);

	gtk_widget_class_set_css_name (widget_class, "feature-tile");
	gtk_widget_class_set_layout_manager_type (widget_class, GS_TYPE_FEATURE_TILE_LAYOUT);
}

GtkWidget *
gs_feature_tile_new (GsApp *app)
{
	return g_object_new (GS_TYPE_FEATURE_TILE,
			     "vexpand", FALSE,
			     "app", app,
			     NULL);
}
