/* -*- Mode: C; indent-tabs-mode: nil; tab-width: 2 -*-
 *
 * Copyright (C) 2009,2010 Canonical Ltd.
 * Authors:
 *  Gustavo Sverzut Barbieri <gustavo.barbieri@canonical.com>
 *  Michael Terry <michael.terry@canonical.com>
 *
 * This file is part of Netbook Launcher EFL.
 *
 * Netbook Launcher EFL is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * Netbook Launcher EFL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Netbook Launcher EFL.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "netbook-launcher.h"

#include <Elementary.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gconf/gconf.h>
#include <gconf/gconf-client.h>
#include <launcher/launcher.h>

/* uncomment to set window layer and stacking manually */
//#define WINDOW_LAYER_CHANGE

static const char _win_context_key[] = "_nl_ctxt";

static const char _EDJE_GROUP_MAIN[] = "e/netbook/launcher/main";
static const char _EDJE_PART_SIDEBAR[] = "e.swallow.sidebar";
static const char _EDJE_PART_APPS[] = "e.swallow.apps";
static const char _EDJE_PART_PLACES[] = "e.swallow.places";
static const char _EDJE_PART_PREFS[] = "e.swallow.preferences";
static const char _EDJE_PART_BG[] = "e.swallow.background";
static const char _EDJE_PART_ICON_NAME[] = "e.text.icon_name";
static const char _EDJE_PART_ICON_COMMENT[] = "e.text.icon_comment";
static const char _EDJE_PART_ADD_ITEM[] = "e.swallow.add_item";
static const char _EDJE_SIG_SRC[] = "e";
static const char _EDJE_SIG_FOCUS_OUT[] = "gui,action,focus,out";
static const char _EDJE_SIG_FOCUS_IN[] = "gui,action,focus,in";
static const char _EDJE_SIG_ANIM_END[] = "gui,action,anim,end";
static const char _EDJE_SIG_APPS_ENTER[] = "gui,action,apps,enter";
static const char _EDJE_SIG_PLACES_ENTER[] = "gui,action,places,enter";
static const char _EDJE_SIG_PREFS_ENTER[] = "gui,action,preferences,enter";
static const char _EDJE_SIG_ADD_ITEM_ENTER[] = "gui,action,add_item,enter";
static const char _EDJE_SIG_APPS_SET[] = "gui,action,apps,set";
static const char _EDJE_SIG_PLACES_SET[] = "gui,action,places,set";
static const char _EDJE_SIG_PREFS_SET[] = "gui,action,preferences,set";
static const char _EDJE_SIG_ADD_ITEM_SET[] = "gui,action,add_item,set";
static const char _EDJE_SIG_ICON_ENTER[] = "gui,action,icon_info,enter";
static const char _EDJE_SIG_ICON_LEAVE[] = "gui,action,icon_info,leave";

static const char FONT_PATH[] = "/desktop/gnome/font_rendering";
static const char FONT_DPI[] = "/desktop/gnome/font_rendering/dpi";

static const char NL_DIR[] = "/apps/netbook-launcher-efl";
static const char THEME_PATH[] = "/apps/netbook-launcher-efl/theme";

int WIN_EVENT_FOCUS_IN = 0;
int WIN_EVENT_FOCUS_OUT = 0;
int WIN_EVENT_APP_OPEN = 0;
int WIN_EVENT_APP_CLOSE = 0;
int WIN_EVENT_APPS_CHANGED = 0;

struct win_context
{
  Evas_Object *win;
  Evas_Object *layout;
  Evas_Object *edje;
  Evas_Object *sidebar;
  Evas_Object *apps;
  Evas_Object *places;
  Evas_Object *prefs;
  Evas_Object *add_item;
  struct string_array strings;
  Ecore_Event *changed_event;
  Eina_Bool contents_anim;
  Eina_Bool is_animating;
  Eina_Bool sidebar_focus;
  enum
  {
    WIN_CONTENT_NONE,
    WIN_CONTENT_APPS,
    WIN_CONTENT_PLACES,
    WIN_CONTENT_PREFS,
    WIN_CONTENT_ADD_ITEM
  } content;
  GtkSettings *settings;
  GConfClient *client;
  LauncherSession *session;
  const char *font;
  struct
  {
    unsigned int size;
    double dpi;
  } cur, prev;
  struct
  {
    gint theme;
    gint font_dpi;
    gulong font;
    gulong sound_theme;
    gulong sound_enable;
    gulong app_open;
  } notify;
  struct
  {
    Ecore_Event_Handler *icon_info_enter;
    Ecore_Event_Handler *icon_info_leave;
  } handler;
};

static inline void
_win_context_set(Evas_Object *win, const struct win_context *ctxt)
{
  evas_object_data_set(win, _win_context_key, ctxt);
}

static inline struct win_context *
_win_context_get(const Evas_Object *win)
{
  return evas_object_data_get(win, _win_context_key);
}

Evas_Object *
win_sidebar_get(const Evas_Object *win)
{
  struct win_context * ctxt = _win_context_get(win);
  return ctxt->sidebar;
}

static void
_win_ecore_font_apply(const struct win_context *ctxt)
{
  int size = ctxt->cur.size * ctxt->cur.dpi / 72.0;

  edje_text_class_set("list_font", ctxt->font, size);
  edje_text_class_set("grid_font", ctxt->font, size);
  edje_text_class_set("frame_font", ctxt->font, size);
  edje_text_class_set("dialog_font", ctxt->font, size);
}

static void
_win_conf_load(struct win_context *ctxt)
{
  if (!conf_dpi_get(&ctxt->cur.dpi))
    ctxt->cur.dpi = 96;
  ctxt->prev.dpi = ctxt->cur.dpi;

  if (conf_font_get("main", &ctxt->font, &ctxt->cur.size))
  {
    ctxt->prev.size = ctxt->cur.size;
    _win_ecore_font_apply(ctxt);
  }
}

static void
_win_ecore_font_changed(void *data, const char *font)
{
  struct win_context *ctxt = data;

  if ((ctxt->cur.dpi == ctxt->prev.dpi) &&
      (ctxt->cur.size == ctxt->prev.size) &&
      (strcmp(ctxt->font, font) == 0))
  {
    DBG("font is still the same, ignore.\n");
    return;
  }

  eina_stringshare_replace(&ctxt->font, font);
  _win_ecore_font_apply(ctxt);
  conf_font_set("main", ctxt->font, ctxt->cur.size);
  conf_dpi_set(ctxt->cur.dpi);
}

static void
_win_glib_font_update(struct win_context *ctxt)
{
  PangoFontDescription *fd;
  gchar *fontspec;

  g_object_get(ctxt->settings, "gtk-font-name", &fontspec, NULL);
  fd = pango_font_description_from_string(fontspec);
  if (!fd)
    g_warning("could not parse pango font description: %s", fontspec);
  else
  { /* TODO: less brain dead way to convert from pango description to
     * font config description? Or at least get a FcPattern from it?
     */
    gint size;
    GString *str;
    PangoStyle style;
    PangoWeight weight;

    style = pango_font_description_get_style(fd);
    weight = pango_font_description_get_weight(fd);

    str = g_string_new(NULL);

    /* WARNING: this is far from sane or correct */
    if (weight <= PANGO_WEIGHT_THIN)
      str = g_string_append(str, "Thin");
    else if (weight <= PANGO_WEIGHT_LIGHT)
      str = g_string_append(str, "Light");
    else if (weight <= PANGO_WEIGHT_BOOK)
      str = g_string_append(str, "Book");
    else if (weight <= PANGO_WEIGHT_NORMAL)
      ; /* do nothing */
    else if (weight <= PANGO_WEIGHT_MEDIUM)
      str = g_string_append(str, "Medium");
    else if (weight <= PANGO_WEIGHT_SEMIBOLD)
      str = g_string_append(str, "Demi");
    else if (weight <= PANGO_WEIGHT_BOLD)
      str = g_string_append(str, "Bold");
    else if (weight >= PANGO_WEIGHT_HEAVY)
      str = g_string_append(str, "Heavy");

    if (style == PANGO_STYLE_OBLIQUE)
      str = g_string_append(str, "Oblique");
    else if (style == PANGO_STYLE_ITALIC)
      str = g_string_append(str, "Italic");

    if (str->len > 0)
      str = g_string_prepend(str, ":style=");

    str = g_string_prepend(str, pango_font_description_get_family(fd));

    size = pango_font_description_get_size(fd);
    if (!pango_font_description_get_size_is_absolute(fd))
      size /= PANGO_SCALE;
    if (size > 0)
    {
      ctxt->prev.size = ctxt->cur.size;
      ctxt->cur.size = size;
    }

    pango_font_description_free(fd);

    g_debug("font: '%s', size: %d. Original. '%s'",
            str->str, ctxt->cur.size, fontspec);

    gstuff_ecore_run_ptr_string(_win_ecore_font_changed, ctxt, str->str);
    g_string_free(str, 1);
  }

  g_free(fontspec);
}

static void
_win_glib_font_changed(GtkSettings *settings __UNUSED__, GParamSpec *spec __UNUSED__, struct win_context *ctxt)
{
  _win_glib_font_update(ctxt);
}

static void
_win_ecore_sound_theme_changed(const char *theme_name)
{
  sound_theme_set(theme_name);
}

static void
_win_glib_sound_theme_changed(GtkSettings *settings __UNUSED__, GParamSpec *spec __UNUSED__, struct win_context *ctxt)
{
  gchar *theme_name = NULL;
  g_object_get(ctxt->settings, "gtk-sound-theme-name", &theme_name, NULL);
  if (!theme_name)
    return;
  gstuff_ecore_run_string(_win_ecore_sound_theme_changed, theme_name);
  g_free(theme_name);
}

static void
_win_ecore_sound_enable_changed(void *data)
{
  Eina_Bool enabled = (Eina_Bool)(long)data;
  sound_enable_set(enabled);
}

static void
_win_glib_sound_enable_changed(GtkSettings *settings __UNUSED__, GParamSpec *spec __UNUSED__, struct win_context *ctxt)
{
  gboolean enabled = FALSE;
  g_object_get(ctxt->settings, "gtk-enable-event-sounds", &enabled, NULL);
  gstuff_ecore_run_simple
    (_win_ecore_sound_enable_changed, (void *)(long)enabled);
}

static void
_win_ecore_theme_changed(const char *theme)
{
  static char path[1024];

  DBG("theme changed '%s'\n", theme);
  if (!_nl_theme_resolve(path, theme))
  {
    ERR("invalid theme ignored: '%s'\n", theme);
    return;
  }

  DBG("theme changed new='%s', old='%s'\n", path, _nl_prefs.theme);
  if (strcmp(_nl_prefs.theme, path) == 0)
  {
    INF("new theme is the same as the old one: '%s'\n", path);
    return;
  }

  _nl_prefs.theme = path;
  conf_theme_set(path);
  DBG("config theme set, restart...\n");
  _nl_restart();
}

static void
_win_glib_theme_changed(GConfClient *client __UNUSED__, guint id __UNUSED__, GConfEntry *entry, gpointer data __UNUSED__)
{
  GConfValue *value;
  const char *theme;

  value = gconf_entry_get_value(entry);
  theme = gconf_value_get_string(value);
  g_debug("theme changed: '%s'", theme);
  gstuff_ecore_run_string(_win_ecore_theme_changed, theme);
}

static void
_win_glib_dpi_changed(GConfClient *client __UNUSED__, guint id __UNUSED__, GConfEntry *entry, gpointer data)
{
  struct win_context *ctxt = data;
  GConfValue *value;

  value = gconf_entry_get_value(entry);
  ctxt->prev.dpi = ctxt->cur.dpi;
  ctxt->cur.dpi = gconf_value_get_float(value);
  _win_glib_font_update(ctxt);
}

static void
_ecore_event_stringshared_del(void *data __UNUSED__, void *ev)
{
  eina_stringshare_del(ev);
}

static void
_win_ecore_app_open(const char *match_name)
{
  match_name = eina_stringshare_add(match_name);
  ecore_event_add
    (WIN_EVENT_APP_OPEN, (void *)match_name,
     _ecore_event_stringshared_del, NULL);
}

static void
_win_ecore_app_close(const char *match_name)
{
  match_name = eina_stringshare_add(match_name);
  ecore_event_add
    (WIN_EVENT_APP_CLOSE, (void *)match_name,
     _ecore_event_stringshared_del, NULL);
}

static void
_ecore_event_apps_changed_del(void *data, void *ev __UNUSED__)
{
  struct win_context *ctxt = data;
  ctxt->changed_event = NULL;
}

static void
_win_ecore_apps_changed(void *data, unsigned int len, const char **strings)
{
  struct win_context *ctxt = data;
  const char **new_array = malloc((len + 1) * sizeof(char *));
  unsigned int i;

  if (!new_array)
  {
    ERR("could not allocate new strings array.\n");
    len = 0;
    goto end;
  }

  for (i = 0; i < len; i++)
    new_array[i] = eina_stringshare_add(strings[i]);
  new_array[i] = NULL;

end:
  for (i = 0; i < ctxt->strings.len; i++)
    eina_stringshare_del(ctxt->strings.array[i]);
  free(ctxt->strings.array);

  ctxt->strings.array = new_array;
  ctxt->strings.len = len;

  if (ctxt->changed_event)
    ecore_event_del(ctxt->changed_event);

  ctxt->changed_event = ecore_event_add
    (WIN_EVENT_APPS_CHANGED, &ctxt->strings,
     _ecore_event_apps_changed_del, ctxt);
}

static void
_win_glib_apps_changed(struct win_context *ctxt)
{
  GSList *n, *lst;
  unsigned int i, len;
  const char **strings;

  lst = launcher_session_get_running_applications(ctxt->session);
  len = g_slist_length(lst);

  strings = alloca((len + 1) * sizeof(char *));
  if (!strings)
  {
    g_warning("could not allocate strings on stack.");
    return;
  }

  for (i = 0, n = lst; n != NULL; n = n->next)
  {
    LauncherApplication *app = n->data;
    const char *match_name = launcher_application_get_unique_string(app);
    if (!match_name)
      continue;

    strings[i++] = match_name;
  }
  strings[i] = NULL;

  gstuff_ecore_run_ptr_string_array(_win_ecore_apps_changed, ctxt, i, strings);
}

static void
_win_glib_app_close(LauncherApplication *app, WnckApplication *wnckapp __UNUSED__, struct win_context *ctxt)
{
  const char *match_name = launcher_application_get_unique_string(app);
  if (!match_name)
  {
    g_debug("application closed, but no desktop file: %s, ignored.",
            launcher_application_get_name(app));
    return;
  }

  _win_glib_apps_changed(ctxt);
  gstuff_ecore_run_string(_win_ecore_app_close, match_name);
}

static void
_win_glib_app_open(LauncherSession *session __UNUSED__, LauncherApplication *app, struct win_context *ctxt)
{
  const char *match_name = launcher_application_get_unique_string(app);
  if (!match_name)
  {
    g_debug("application opened, but no desktop file: %s, ignored.",
            launcher_application_get_name(app));
    return;
  }

  g_signal_connect(app, "closed", G_CALLBACK(_win_glib_app_close), ctxt);

  _win_glib_apps_changed(ctxt);
  gstuff_ecore_run_string(_win_ecore_app_open, match_name);
}

static void
_win_glib_stop(void *data)
{
  struct win_context *ctxt = data;

  icon_path_shutdown();

  if (ctxt->notify.app_open)
    g_signal_handler_disconnect(ctxt->session, ctxt->notify.app_open);

  if (ctxt->session)
    g_object_unref(ctxt->session);

  if (ctxt->notify.font)
    g_signal_handler_disconnect(ctxt->settings, ctxt->notify.font);

  if (ctxt->notify.sound_theme)
    g_signal_handler_disconnect(ctxt->settings, ctxt->notify.sound_theme);

  if (ctxt->notify.sound_enable)
    g_signal_handler_disconnect(ctxt->settings, ctxt->notify.sound_enable);

  // seems gtksettings should not be unref'ed. nothing on its docs says so...
/*   if (ctxt->settings) */
/*     g_object_unref(ctxt->settings); */

  if (ctxt->notify.font_dpi)
    gconf_client_notify_remove(ctxt->client, ctxt->notify.font_dpi);

  if (ctxt->notify.theme)
    gconf_client_notify_remove(ctxt->client, ctxt->notify.theme);

  if (ctxt->client)
  {
    gconf_client_remove_dir(ctxt->client, FONT_PATH, NULL);
    g_object_unref(ctxt->client);
  }

  gstuff_ecore_run_simple(free, ctxt);
}

static void
_win_glib_start(void *data)
{
  struct win_context *ctxt = data;
  GError *err = NULL;
  char *str;

  gtk_init(NULL, NULL);

  ctxt->settings = gtk_settings_get_default();
  if (!ctxt->settings)
  {
    g_critical("could not get GTK settings.");
    return;
  }

  ctxt->notify.font = g_signal_connect
    (ctxt->settings, "notify::gtk-font-name",
     G_CALLBACK(_win_glib_font_changed), ctxt);
  _win_glib_font_update(ctxt);

  ctxt->notify.sound_theme = g_signal_connect
    (ctxt->settings, "notify::gtk-sound-theme-name",
     G_CALLBACK(_win_glib_sound_theme_changed), ctxt);
  _win_glib_sound_theme_changed(ctxt->settings, NULL, ctxt);

  ctxt->notify.sound_enable = g_signal_connect
    (ctxt->settings, "notify::gtk-enable-event-sounds",
     G_CALLBACK(_win_glib_sound_enable_changed), ctxt);
  _win_glib_sound_enable_changed(ctxt->settings, NULL, ctxt);

  ctxt->client = gconf_client_get_default();
  if (!ctxt->client)
  {
    g_critical("could not get default gconf client.");
    return;
  }

  gconf_client_add_dir
    (ctxt->client, NL_DIR, GCONF_CLIENT_PRELOAD_NONE, &err);
  if (err)
  {
    g_critical("could not add gconf dir to be monitored '%s': %s'",
               NL_DIR, err->message);
    g_error_free(err);
  }

  ctxt->notify.theme = gconf_client_notify_add
    (ctxt->client, THEME_PATH, _win_glib_theme_changed, ctxt, NULL, &err);
  if (err)
  {
    g_critical("could not add gconf notify path '%s': %s",
               THEME_PATH, err->message);
    g_error_free(err);
    return;
  }

  str = gconf_client_get_string(ctxt->client, THEME_PATH, &err);
  if (err)
  {
    g_warning("could not get gconf key as string '%s': %s",
               THEME_PATH, err->message);
    g_error_free(err);
  }
  gstuff_ecore_run_string(_win_ecore_theme_changed, str);
  g_free(str);

  gconf_client_add_dir
    (ctxt->client, FONT_PATH, GCONF_CLIENT_PRELOAD_NONE, &err);
  if (err)
  {
    g_critical("could not add gconf dir to be monitored '%s': %s",
               FONT_PATH, err->message);
    g_error_free(err);
    return;
  }

  ctxt->notify.font_dpi = gconf_client_notify_add
    (ctxt->client, FONT_DPI, _win_glib_dpi_changed, ctxt, NULL, &err);
  if (err)
  {
    g_critical("could not add gconf notify path '%s': %s",
               FONT_DPI, err->message);
    g_error_free(err);
    return;
  }

  ctxt->session = launcher_session_get_default();
  if (!ctxt->session)
  {
    g_critical("could not get default launcher session.");
    return;
  }

  ctxt->notify.app_open = g_signal_connect
    (ctxt->session, "application-opened",
     G_CALLBACK(_win_glib_app_open), ctxt);

  if (!icon_path_init())
  {
    g_critical("could not init icon path cache.");
    return;
  }
}

static void
_win_del(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *ev __UNUSED__)
{
  struct win_context *ctxt = data;

  if (ctxt->changed_event)
    ecore_event_del(ctxt->changed_event);

  while (ctxt->strings.len > 0)
    eina_stringshare_del(ctxt->strings.array[--ctxt->strings.len]);
  free(ctxt->strings.array);

  eina_stringshare_del(ctxt->font);

  if (ctxt->handler.icon_info_enter)
    ecore_event_handler_del(ctxt->handler.icon_info_enter);
  if (ctxt->handler.icon_info_leave)
    ecore_event_handler_del(ctxt->handler.icon_info_leave);

  evas_object_data_del(o, _win_context_key);
  conf_shutdown();
}

static void
_win_del_request(void *data, Evas_Object *o __UNUSED__, void *ev __UNUSED__)
{
  struct win_context *ctxt = data;
  //DBG("launcher window closed, quit.\n");
  gstuff_glib_run_simple(_win_glib_stop, ctxt);
  elm_exit();
}

static void
_win_focus_out(Evas_Object *edje)
{
  DBG("launcher loses focus\n");
  edje_object_signal_emit(edje, _EDJE_SIG_FOCUS_OUT, _EDJE_SIG_SRC);
#ifdef WINDOW_LAYER_CHANGE
  elm_win_lower(o);
  elm_win_layer_set(o, 0);
#endif
  ecore_event_add(WIN_EVENT_FOCUS_OUT, NULL, NULL, NULL);
}

static void
_win_focus_in(Evas_Object *edje)
{
  DBG("launcher gets focus\n");
  edje_object_signal_emit(edje, _EDJE_SIG_FOCUS_IN, _EDJE_SIG_SRC);
#ifdef WINDOW_LAYER_CHANGE
  elm_win_raise(o);
  elm_win_layer_set(o, 9999);
#endif
  ecore_event_add(WIN_EVENT_FOCUS_IN, NULL, NULL, NULL);
}

static void
_win_focus_changed(WnckScreen *screen, WnckWindow *prev, Evas_Object *edje)
{
  WnckWindow *focus;

  /* Check who took our focus.  If it's a transient window, ignore it. */
  focus = wnck_screen_get_active_window(screen);
  if (!focus)
    return;

  switch (wnck_window_get_window_type(focus))
  {
  case WNCK_WINDOW_DESKTOP:
    /* This is us! */
    _win_focus_in(edje);
    break;
  case WNCK_WINDOW_DOCK:
  case WNCK_WINDOW_MENU:
  case WNCK_WINDOW_SPLASHSCREEN:
    return; /* ignore focus-out */
  default:
    /* Only trigger focus-out if we were previous focus */
    if (!prev || wnck_window_get_window_type(prev) == WNCK_WINDOW_DESKTOP)
      _win_focus_out(edje);
    break;
  }
}

static void
_win_focus_apply(struct win_context *ctxt)
{
  if (ctxt->sidebar_focus)
  {
    DBG("focus sidebar\n");
    evas_object_focus_set(ctxt->sidebar, 1);
  }
  else
  {
    /* TODO: this is getting awkward already, keep a pointer to
     * current and handle this and transitions the same way?
     */
    switch (ctxt->content)
    {
    case WIN_CONTENT_APPS:
      DBG("focus apps\n");
      evas_object_focus_set(ctxt->apps, 1);
      break;
    case WIN_CONTENT_PLACES:
      DBG("focus places\n");
      evas_object_focus_set(ctxt->places, 1);
      break;
    case WIN_CONTENT_PREFS:
      DBG("focus prefs\n");
      evas_object_focus_set(ctxt->prefs, 1);
      break;
    default:
      WRN("could not give focus to content=%d\n", ctxt->content);
    }
  }
}

static void
_win_focus_change(struct win_context *ctxt, int direction __UNUSED__)
{
  ctxt->sidebar_focus = !ctxt->sidebar_focus;
  _win_focus_apply(ctxt);
}

static void
_win_focus_next(struct win_context *ctxt)
{
  _win_focus_change(ctxt, 1);
}

static void
_win_focus_prev(struct win_context *ctxt)
{
  _win_focus_change(ctxt, -1);
}

static void
_win_key_down(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info)
{
  Evas_Event_Key_Down *ev = event_info;
  struct win_context *ctxt = data;

if (strcmp(ev->keyname, "Tab") == 0)
  {
    if (evas_key_modifier_is_set(ev->modifiers, "Shift"))
      _win_focus_prev(ctxt);
    else
      _win_focus_next(ctxt);
  }
}

static void
_win_evas_focus_in(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct win_context *ctxt = data;
  _win_focus_apply(ctxt);
}

static void
_win_edje_animation_end(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
{
  struct win_context *ctxt = data;
  ctxt->is_animating = 0;
  DBG("finished animation, ready!\n");
}

static void
_win_apps_animation_end(void *data, Evas_Object *o __UNUSED__)
{
  struct win_context *ctxt = data;
  ctxt->is_animating = 0;
  DBG("finished animation, ready!\n");
}

static void
_win_glib_quit(void)
{
#if USE_QUIT == 1
  g_spawn_command_line_async("netbook-launcher-efl_quit", NULL);
#elif USE_QUIT == 2
  g_spawn_command_line_async("xfce4-session-logout", NULL);
#endif
}

static int
_win_icon_info_enter(void *data, int type __UNUSED__, void *event)
{
  struct win_context *ctxt = data;
  struct icon_info_event_enter *ev = event;
  Edje_Message_String_Set *msg;

  edje_object_part_text_set(ctxt->edje, _EDJE_PART_ICON_NAME, ev->name);
  edje_object_part_text_set(ctxt->edje, _EDJE_PART_ICON_COMMENT, ev->comment);
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_ICON_ENTER, _EDJE_SIG_SRC);

  msg = alloca(sizeof(*msg) + sizeof(char *));
  msg->count = 2;
  msg->str[0] = (char *)ev->name;
  msg->str[1] = (char *)ev->comment;
  edje_object_message_send(ctxt->edje, EDJE_MESSAGE_STRING_SET, 0, msg);

  return 1;
}

static int
_win_icon_info_leave(void *data, int type __UNUSED__, void *event __UNUSED__)
{
  struct win_context *ctxt = data;

  edje_object_part_text_set(ctxt->edje, _EDJE_PART_ICON_NAME, "");
  edje_object_part_text_set(ctxt->edje, _EDJE_PART_ICON_COMMENT, "");
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_ICON_LEAVE, _EDJE_SIG_SRC);
  return 1;
}

static void
_win_edje_debug(void *data __UNUSED__, Evas_Object *o __UNUSED__, const char *signal __UNUSED__, const char *source)
{
  fprintf(stderr, "EDJE-SIGNAL-DEBUG: '%s'\n", source);
}

static void
_time_set(Evas_Object *win, unsigned int timestamp)
{
  Ecore_X_Window xwin;
  GdkWindow *gdkwin, *topwin;
  xwin = elm_win_xwindow_get(win);
  if (!xwin)
    return;
  gdkwin = gdk_window_foreign_new(xwin);
  if (!gdkwin)
    return;
  topwin = gdkwin;
  while (gdk_window_get_parent(topwin))
  {
    topwin = gdk_window_get_parent(topwin);
    topwin = gdk_window_get_toplevel(topwin);
  }
  gdk_x11_window_set_user_time(topwin, timestamp);
  g_object_unref(gdkwin);
}

static int
_time_kdown(void *data, int type, void *ev)
{
  Evas_Event_Key_Down *event = (Evas_Event_Key_Down *)ev;
  struct win_context *ctxt = (struct win_context *)data;
  _time_set(ctxt->win, event->timestamp);
  return ECORE_CALLBACK_RENEW;
}

static int
_time_kup(void *data, int type, void *ev)
{
  Evas_Event_Key_Up *event = (Evas_Event_Key_Up *)ev;
  struct win_context *ctxt = (struct win_context *)data;
  _time_set(ctxt->win, event->timestamp);
  return ECORE_CALLBACK_RENEW;
}

static int
_time_mdown(void *data, int type, void *ev)
{
  Evas_Event_Mouse_Down *event = (Evas_Event_Mouse_Down *)ev;
  struct win_context *ctxt = (struct win_context *)data;
  _time_set(ctxt->win, event->timestamp);
  return ECORE_CALLBACK_RENEW;
}

static int
_time_mup(void *data, int type, void *ev)
{
  Evas_Event_Mouse_Up *event = (Evas_Event_Mouse_Up *)ev;
  struct win_context *ctxt = (struct win_context *)data;
  _time_set(ctxt->win, event->timestamp);
  return ECORE_CALLBACK_RENEW;
}

static int
_time_mmove(void *data, int type, void *ev)
{
  Evas_Event_Mouse_Move *event = (Evas_Event_Mouse_Move *)ev;
  struct win_context *ctxt = (struct win_context *)data;
  _time_set(ctxt->win, event->timestamp);
  return ECORE_CALLBACK_RENEW;
}

static int
_time_min(void *data, int type, void *ev)
{
  Evas_Event_Mouse_In *event = (Evas_Event_Mouse_In *)ev;
  struct win_context *ctxt = (struct win_context *)data;
  _time_set(ctxt->win, event->timestamp);
  return ECORE_CALLBACK_RENEW;
}

static int
_time_mout(void *data, int type, void *ev)
{
  Evas_Event_Mouse_Out *event = (Evas_Event_Mouse_Out *)ev;
  struct win_context *ctxt = (struct win_context *)data;
  _time_set(ctxt->win, event->timestamp);
  return ECORE_CALLBACK_RENEW;
}

static int
_time_mwheel(void *data, int type, void *ev)
{
  Evas_Event_Mouse_Wheel *event = (Evas_Event_Mouse_Wheel *)ev;
  struct win_context *ctxt = (struct win_context *)data;
  _time_set(ctxt->win, event->timestamp);
  return ECORE_CALLBACK_RENEW;
}

Evas_Object *
win_add(void)
{
  Evas_Object *win, *layout, *edje;
  const char *key;
  struct win_context *ctxt;

  if (!WIN_EVENT_FOCUS_IN)
    WIN_EVENT_FOCUS_IN = ecore_event_type_new();
  if (!WIN_EVENT_FOCUS_OUT)
    WIN_EVENT_FOCUS_OUT = ecore_event_type_new();
  if (!WIN_EVENT_APP_OPEN)
    WIN_EVENT_APP_OPEN = ecore_event_type_new();
  if (!WIN_EVENT_APP_CLOSE)
    WIN_EVENT_APP_CLOSE = ecore_event_type_new();
  if (!WIN_EVENT_APPS_CHANGED)
    WIN_EVENT_APPS_CHANGED = ecore_event_type_new();

  win = elm_win_add(NULL, "e-netbook-launcher", ELM_WIN_DESKTOP);
  if (!win)
  {
    ERR("could not create window.\n");
    return NULL;
  }

  layout = elm_layout_add(win);
  if (!layout)
  {
    ERR("could not create layout.\n");
    evas_object_del(win);
    return NULL;
  }

  edje = elm_layout_edje_get(layout);
  if (!elm_layout_file_set(layout, _nl_prefs.theme, _EDJE_GROUP_MAIN))
  {
    int err = edje_object_load_error_get(edje);
    const char *errmsg = edje_load_error_str(err);
    ERR("cannot load theme '%s', group '%s': %s\n",
        _nl_prefs.theme, _EDJE_GROUP_MAIN, errmsg);
    evas_object_del(win);
    return 0;
  }

  evas_object_size_hint_weight_set(layout, 1.0, 1.0);
  elm_win_resize_object_add(win, layout);
  evas_object_show(layout);

  key = edje_object_data_get(edje, "alpha");
  if (key && atoi(key))
    elm_win_alpha_set(win, 1);
  else
  {
    key = edje_object_data_get(edje, "shaped");
    if (key && atoi(key))
      elm_win_shaped_set(win, 1);
  }

  elm_win_title_set(win, "Netbook Launcher EFL");
  elm_win_autodel_set(win, 0);
  elm_win_borderless_set(win, 1);
#ifdef WINDOW_LAYER_CHANGE
  elm_win_layer_set(win, 0);
  elm_win_lower(win);
#endif

  ctxt = calloc(1, sizeof(*ctxt));
  if (!ctxt)
  {
    ERR("could not create window context.\n");
    evas_object_del(win);
    return 0;
  }
  ctxt->win = win;
  ctxt->layout = layout;
  ctxt->edje = edje;
  ctxt->content = WIN_CONTENT_NONE;
  ctxt->sidebar_focus = 1;

  evas_object_smart_callback_add(win, "delete-request", _win_del_request, ctxt);
  g_signal_connect(wnck_screen_get_default(), "active-window-changed",
                   G_CALLBACK(_win_focus_changed), edje);

  evas_object_event_callback_add(win, EVAS_CALLBACK_DEL, _win_del, ctxt);
  evas_object_event_callback_add
    (win, EVAS_CALLBACK_FOCUS_IN, _win_evas_focus_in, ctxt);
  evas_object_event_callback_add
    (layout, EVAS_CALLBACK_KEY_DOWN, _win_key_down, ctxt);

  /* Tell the window manager the timestamp of any user events.
     This fixes focus issues when launching apps. */
  ecore_event_handler_add(ECORE_EVENT_KEY_DOWN, _time_kdown, ctxt);
  ecore_event_handler_add(ECORE_EVENT_KEY_UP, _time_kup, ctxt);
  ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_DOWN, _time_mdown, ctxt);
  ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_UP, _time_mup, ctxt);
  ecore_event_handler_add(ECORE_EVENT_MOUSE_MOVE, _time_mmove, ctxt);
  ecore_event_handler_add(ECORE_EVENT_MOUSE_IN, _time_min, ctxt);
  ecore_event_handler_add(ECORE_EVENT_MOUSE_OUT, _time_mout, ctxt);
  ecore_event_handler_add(ECORE_EVENT_MOUSE_WHEEL, _time_mwheel, ctxt);

  key = edje_object_data_get(edje, "contents_anim");
  if (key && atoi(key))
    ctxt->contents_anim = 1;

  edje_object_signal_callback_add
    (edje, _EDJE_SIG_ANIM_END, _EDJE_SIG_SRC, _win_edje_animation_end, ctxt);

  edje_object_signal_callback_add(edje, "DBG", "*", _win_edje_debug, ctxt);

  _win_context_set(win, ctxt);

  if (conf_init())
    _win_conf_load(ctxt);

  ctxt->handler.icon_info_enter = ecore_event_handler_add
    (EVENT_ICON_INFO_ENTER, _win_icon_info_enter, ctxt);
  ctxt->handler.icon_info_leave = ecore_event_handler_add
    (EVENT_ICON_INFO_LEAVE, _win_icon_info_leave, ctxt);

  gstuff_glib_run_simple(_win_glib_start, ctxt);

  return win;
}

Eina_Bool
win_sidebar_set(Evas_Object *win, Evas_Object *sidebar)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  elm_layout_content_set(ctxt->layout, _EDJE_PART_SIDEBAR, sidebar);
  ctxt->sidebar = sidebar;

  if (ctxt->sidebar_focus && evas_object_focus_get(win))
    evas_object_focus_set(sidebar, 1);

  return 1;
}

Eina_Bool
win_apps_set(Evas_Object *win, Evas_Object *apps)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  elm_layout_content_set(ctxt->layout, _EDJE_PART_APPS, apps);
  ctxt->apps = apps;

  if ((!ctxt->sidebar_focus) && (ctxt->content == WIN_CONTENT_APPS) &&
      evas_object_focus_get(win))
    evas_object_focus_set(apps, 1);

  return 1;
}

Eina_Bool
win_apps_show_category(Evas_Object *win, const char *category)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  if (!ctxt->apps)
  {
    ERR("no apps object.\n");
    return 0;
  }

  ctxt->sidebar_focus = 0;
  evas_object_focus_set(ctxt->apps, 1);

  if (!ctxt->contents_anim || (ctxt->content == WIN_CONTENT_NONE))
  {
    DBG("immediate loading of apps: contents_anim=%hhu, first=%hhu\n",
        ctxt->contents_anim, (ctxt->content == WIN_CONTENT_NONE));

    if (ctxt->content != WIN_CONTENT_APPS) {
      edje_object_signal_emit(ctxt->edje, _EDJE_SIG_APPS_SET, _EDJE_SIG_SRC);
      ctxt->content = WIN_CONTENT_APPS;
    }

    apps_load_category(ctxt->apps, category, 0, NULL, NULL);
    return 1;
  }

  if (ctxt->is_animating)
    WRN("loading new item while another one was pending!\n");

  if (ctxt->content == WIN_CONTENT_APPS)
    apps_load_category(ctxt->apps, category, 1, _win_apps_animation_end, win);
  else {
    ctxt->is_animating = 1;
    ctxt->content = WIN_CONTENT_APPS;
    apps_load_category(ctxt->apps, category, 0, NULL, NULL);
    DBG("animate enter of apps\n");
    edje_object_signal_emit(ctxt->edje, _EDJE_SIG_APPS_ENTER, _EDJE_SIG_SRC);
  }

  return 1;
}

Eina_Bool
win_apps_show_favorites(Evas_Object *win)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  if (!ctxt->apps)
  {
    ERR("no apps object.\n");
    return 0;
  }

  ctxt->sidebar_focus = 0;
  evas_object_focus_set(ctxt->apps, 1);

  if (!ctxt->contents_anim || (ctxt->content == WIN_CONTENT_NONE))
  {
    DBG("immediate loading of apps: contents_anim=%hhu, first=%hhu\n",
        ctxt->contents_anim, (ctxt->content == WIN_CONTENT_NONE));

    if (ctxt->content != WIN_CONTENT_APPS) {
      edje_object_signal_emit(ctxt->edje, _EDJE_SIG_APPS_SET, _EDJE_SIG_SRC);
      ctxt->content = WIN_CONTENT_APPS;
    }

    apps_load_favorites(ctxt->apps, 0, NULL, NULL);
    return 1;
  }

  if (ctxt->is_animating)
    WRN("loading new item while another one was pending!\n");

  if (ctxt->content == WIN_CONTENT_APPS)
    apps_load_favorites(ctxt->apps, 1, _win_apps_animation_end, win);
  else {
    ctxt->is_animating = 1;
    ctxt->content = WIN_CONTENT_APPS;
    apps_load_favorites(ctxt->apps, 0, NULL, NULL);
    DBG("animate enter of apps\n");
    edje_object_signal_emit(ctxt->edje, _EDJE_SIG_APPS_ENTER, _EDJE_SIG_SRC);
  }

  return 1;
}

Eina_Bool
win_places_set(Evas_Object *win, Evas_Object *places)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  elm_layout_content_set(ctxt->layout, _EDJE_PART_PLACES, places);
  ctxt->places = places;

  if ((!ctxt->sidebar_focus) && (ctxt->content == WIN_CONTENT_PLACES) &&
      evas_object_focus_get(win))
    evas_object_focus_set(places, 1);

  return 1;
}

Eina_Bool
win_places_show(Evas_Object *win)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  if (!ctxt->places)
  {
    ERR("no places object.\n");
    return 0;
  }

  ctxt->sidebar_focus = 0;
  evas_object_focus_set(ctxt->places, 1);

  if (ctxt->content == WIN_CONTENT_PLACES)
    return 1;

  if (!ctxt->contents_anim || (ctxt->content == WIN_CONTENT_NONE))
  {
    DBG("immediate loading of places: contents_anim=%hhu, first=%hhu\n",
        ctxt->contents_anim, (ctxt->content == WIN_CONTENT_NONE));

    ctxt->content = WIN_CONTENT_PLACES;
    places_load(ctxt->places);
    edje_object_signal_emit(ctxt->edje, _EDJE_SIG_PLACES_SET, _EDJE_SIG_SRC);
    return 1;
  }
  ctxt->content = WIN_CONTENT_PLACES;

  if (!ctxt->is_animating)
    ctxt->is_animating = 1;
  else
    WRN("loading new item while another one was pending!\n");

  DBG("animate enter of places\n");
  places_load(ctxt->places);
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_PLACES_ENTER, _EDJE_SIG_SRC);

  return 1;
}

Eina_Bool
win_preferences_set(Evas_Object *win, Evas_Object *prefs)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  elm_layout_content_set(ctxt->layout, _EDJE_PART_PREFS, prefs);
  ctxt->prefs = prefs;

  if ((!ctxt->sidebar_focus) && (ctxt->content == WIN_CONTENT_PREFS) &&
      evas_object_focus_get(win))
    evas_object_focus_set(prefs, 1);

  return 1;
}

Eina_Bool
win_preferences_show(Evas_Object *win)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  if (!ctxt->prefs)
  {
    ERR("no preferences object.\n");
    return 0;
  }

  ctxt->sidebar_focus = 0;
  evas_object_focus_set(ctxt->prefs, 1);

  if (ctxt->content == WIN_CONTENT_PREFS)
    return 1;

  if (!ctxt->contents_anim || (ctxt->content == WIN_CONTENT_NONE))
  {
    DBG("immediate loading of prefs: contents_anim=%hhu, first=%hhu\n",
        ctxt->contents_anim, (ctxt->content == WIN_CONTENT_NONE));

    ctxt->content = WIN_CONTENT_PREFS;
    preferences_load(ctxt->prefs);
    edje_object_signal_emit(ctxt->edje, _EDJE_SIG_PREFS_SET, _EDJE_SIG_SRC);
    return 1;
  }
  ctxt->content = WIN_CONTENT_PREFS;

  if (!ctxt->is_animating)
    ctxt->is_animating = 1;
  else
    WRN("loading new item while another one was pending!\n");

  DBG("animate enter of prefs\n");
  preferences_load(ctxt->prefs);
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_PREFS_ENTER, _EDJE_SIG_SRC);

  return 1;
}

Eina_Bool
win_bg_set(Evas_Object *win, Evas_Object *bg)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  elm_layout_content_set(ctxt->layout, _EDJE_PART_BG, bg);
  return 1;
}

Eina_Bool
win_quit_show(Evas_Object *win __UNUSED__)
{
  return gstuff_glib_run_noargs(_win_glib_quit);
}

Eina_Bool
win_add_item_set(Evas_Object *win, Evas_Object *add_item)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  elm_layout_content_set(ctxt->layout, _EDJE_PART_ADD_ITEM, add_item);
  ctxt->add_item = add_item;

  if ((!ctxt->sidebar_focus) && (ctxt->content == WIN_CONTENT_ADD_ITEM) &&
      evas_object_focus_get(win))
    evas_object_focus_set(add_item, 1);

  return 1;
}

Eina_Bool
win_add_item_show(Evas_Object *win __UNUSED__)
{
  struct win_context *ctxt = _win_context_get(win);

  if (!ctxt)
    return 0;

  if (!ctxt->add_item)
  {
    ERR("no add_item object.\n");
    return 0;
  }

  ctxt->sidebar_focus = 0;
  evas_object_focus_set(ctxt->add_item, 1);

  if (ctxt->content == WIN_CONTENT_ADD_ITEM)
    return 1;

  if (!ctxt->contents_anim || (ctxt->content == WIN_CONTENT_NONE))
  {
    DBG("immediate loading of add_item: contents_anim=%hhu, first=%hhu\n",
        ctxt->contents_anim, (ctxt->content == WIN_CONTENT_NONE));

    if (ctxt->content != WIN_CONTENT_ADD_ITEM) {
      edje_object_signal_emit(ctxt->edje, _EDJE_SIG_ADD_ITEM_SET, _EDJE_SIG_SRC);
      ctxt->content = WIN_CONTENT_ADD_ITEM;
    }

    return 1;
  }

  if (ctxt->is_animating)
    WRN("loading new item while another one was pending!\n");

  if (ctxt->content != WIN_CONTENT_ADD_ITEM)
  {
    ctxt->is_animating = 1;
    ctxt->content = WIN_CONTENT_ADD_ITEM;
    DBG("animate enter of add_item\n");
    edje_object_signal_emit(ctxt->edje, _EDJE_SIG_ADD_ITEM_ENTER, _EDJE_SIG_SRC);
  }

  return 1;
}


Eina_Bool
win_hide_sidebar_and_current_section(Evas_Object *win)
{
  struct win_context *ctxt = _win_context_get(win);
  edje_object_signal_emit(ctxt->edje, "gui,action,hide,all", _EDJE_SIG_SRC);
  edje_object_signal_emit(ctxt->edje, "gui,action,sidebar,hide", _EDJE_SIG_SRC);
}

Eina_Bool
win_show_sidebar_and_current_section(Evas_Object *win)
{
  struct win_context *ctxt = _win_context_get(win);

  if (ctxt->content == WIN_CONTENT_APPS)
    edje_object_signal_emit(ctxt->edje, _EDJE_SIG_APPS_ENTER, _EDJE_SIG_SRC);
  else if (ctxt->content == WIN_CONTENT_PLACES)
    edje_object_signal_emit(ctxt->edje, _EDJE_SIG_PLACES_ENTER, _EDJE_SIG_SRC);
  else if (ctxt->content == WIN_CONTENT_PREFS)
    edje_object_signal_emit(ctxt->edje, _EDJE_SIG_PREFS_ENTER, _EDJE_SIG_SRC);
  else if (ctxt->content == WIN_CONTENT_ADD_ITEM)
    edje_object_signal_emit(ctxt->edje, _EDJE_SIG_ADD_ITEM_ENTER, _EDJE_SIG_SRC);
}
