/* -*- 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 <glib/gi18n.h>
#include <libnotify/notify.h>

static const char _EDJE_GROUP_ITEM[] = "e/netbook/launcher/main/apps/item";
static const char _EDJE_PART_ICON[] = "e.swallow.icon";
static const char _EDJE_PART_LABEL[] = "e.text.label";
static const char _EDJE_PART_COMMENT[] = "e.text.comment";
static const char _EDJE_PART_FAV_ACTION[] = "e.text.favorite_action";

static const char _EDJE_SIG_SRC[] = "e";
static const char _EDJE_SIG_SELECT[] = "gui,action,select";
static const char _EDJE_SIG_UNSELECT[] = "gui,action,unselect";
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_EXECUTED[] = "gui,action,executed";
static const char _EDJE_SIG_FAV_ADD[] = "gui,action,favorite,add";
static const char _EDJE_SIG_FAV_DEL[] = "gui,action,favorite,del";
static const char _EDJE_SIG_FAV_ADDED[] = "gui,state,favorite,added";
static const char _EDJE_SIG_FAV_CAN_ADD[] = "gui,state,favorite,can_add";
static const char _EDJE_SIG_FAV_CAN_DEL[] = "gui,state,favorite,can_del";
static const char _EDJE_SIG_WIN_CLOSE[] = "gui,action,win,close";
static const char _EDJE_SIG_WIN_OPEN[] = "gui,action,win,open";

struct app_item_context
{
  Evas_Object *item;
  Evas_Object *edje;
  const char *category;
  const char *name;
  const char *comment;
  const char *icon_name;
  const char *match_name;
  const char *uid;
  const char *path; // Just if it is a gadget
  const struct app_item_common *parent;
  Eina_Bool pending_launch;
  Eina_Bool is_favorite;

  struct
  {
    Ecore_Event_Handler *app_open;
    Ecore_Event_Handler *app_close;
    Ecore_Event_Handler *focus_in;
    Ecore_Event_Handler *focus_out;
  } handlers;
  struct
  {
    Ecore_Timer *stop_anim;
  } timer;
};

static void _app_item_data_select(void *user_data __UNUSED__, void *item_data);
static void _app_item_data_unselect(void *user_data __UNUSED__, void *item_data);

static void
_app_ecore_favs_changed(void)
{
  ecore_event_add(FAVORITES_EVENT_CHANGED, NULL, NULL, NULL);
}

static void
_app_ecore_favs_added(void *data)
{
  struct app_item_context *ctxt = data;
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_FAV_ADDED, _EDJE_SIG_SRC);
  _app_ecore_favs_changed();
}

static gboolean
_app_glib_notify_send(const char *title, const char *content, const char *icon)
{
  NotifyNotification *n = notify_notification_new(title, content, icon, NULL);
  GError *err = NULL;
  gboolean ret;

  if (!n)
    return 0;

  ret = notify_notification_show(n, &err);
  if (err)
  {
    g_warning("could not show notification "
              "title '%s', content '%s', icon '%s': %s",
              title, content, icon, err->message);
    g_error_free(err);
  }

  return ret;
}

static LauncherApplication *
_app_glib_item_find_app_from_desktop_file(const struct app_item_context *ctxt, const char *desktop_file)
{
  const GSList *n = launcher_menu_get_applications(ctxt->parent->menu);
  for (; n != NULL; n = n->next)
  {
    LauncherApplication *app = n->data;
    const char *cur_desktop_file = launcher_application_get_desktop_file(app);
    if (cur_desktop_file && (strcmp(cur_desktop_file, desktop_file) == 0))
      return app;
  }
  return NULL;
}

static gboolean
_app_glib_favorite_exists(const struct app_item_context *ctxt, const char *desktop_file)
{
  LauncherFavorites *favs = ctxt->parent->favs;
  GSList *lst = launcher_favorites_get_favorites(favs);
  gboolean ret = 0;

  while (lst)
  {
    char *uid = lst->data;

    if (!ret)
    {
      gchar *fav_file;

      fav_file = launcher_favorites_get_string(favs, uid, "desktop_file");
      if (fav_file)
      {
        if (strcmp(fav_file, desktop_file) == 0)
          ret = 1;
        g_free(fav_file);
      }
    }

    g_free(uid);
    lst = g_slist_delete_link(lst, lst);
  }

  return ret;
}

static void
_app_glib_item_load_gadget(LauncherFavorites *favorites, struct app_item_context *ctxt)
{
#ifdef HAVE_GOOGLE_GADGETS
  char *uid;

  uid = efl_gadget_uid_make();

  /* Set the properties of the favorites */
  launcher_favorites_set_string(favorites, uid, "type", "libgadget-ggadget");
  launcher_favorites_set_string(favorites, uid, "path", ctxt->path);
  launcher_favorites_add_favorite(favorites, uid);

  gstuff_ecore_run_simple(_app_ecore_favs_added, ctxt);
  g_free(uid);
#endif
}

static void
_app_glib_item_fav_add(void *data, const char *desktop_file)
{
  struct app_item_context *ctxt = data;
  LauncherFavorites *favs = ctxt->parent->favs;
  LauncherApplication *app;
  const char *name, *icon;
  char uid[128];
  time_t now = time(NULL);

  app = _app_glib_item_find_app_from_desktop_file(ctxt, desktop_file);
  if (!app)
  {
    g_debug("could not find application for desktop_file '%s'", desktop_file);
    return;
  }

  if (_app_glib_favorite_exists(ctxt, desktop_file))
  {
    g_debug("%s already exists in favorites, not adding duplicate.",
            desktop_file);
    goto end;
  }

  snprintf(uid, sizeof(uid), "application-%lld", (long long int)now);

  launcher_favorites_set_string(favs, uid, "type", "application");
  launcher_favorites_set_string(favs, uid, "desktop_file", desktop_file);
  launcher_favorites_add_favorite(favs, uid);

end:
  name = launcher_application_get_name(app);
  icon = launcher_application_get_icon_name(app);
  _app_glib_notify_send(name, _("Added to favorites"), icon);

  g_debug("add favorite '%s' (%s)", uid, desktop_file);
  gstuff_ecore_run_simple(_app_ecore_favs_added, ctxt);
}

static void
_app_glib_item_fav_del(void *data, const char *uid)
{
  struct app_item_context *ctxt = data;
  launcher_favorites_remove_favorite(ctxt->parent->favs, uid);
  g_debug("del favorite '%s'", uid);
  gstuff_ecore_run_noargs(_app_ecore_favs_changed);
}

static LauncherCategory *
_app_item_find_category(const struct app_item_context *ctxt, const char *category)
{
  const GSList *n = launcher_menu_get_categories(ctxt->parent->menu);
  for (; n != NULL; n = n->next)
  {
    LauncherCategory *cat = n->data;
    const char *cur = launcher_category_get_name(cat);
    if (cur && strcmp(cur, category) == 0)
      return cat;
  }
  return NULL;
}

static LauncherApplication *
_app_item_find_app(const struct app_item_context *ctxt, const char *category, const char *match_name)
{
  const GSList *n = NULL;
  LauncherCategory *cat = _app_item_find_category(ctxt, category);

  if (cat)
    n = launcher_category_get_applications(cat);

  if (!n)
    n = launcher_menu_get_applications(ctxt->parent->menu);

  for (; n != NULL; n = n->next)
  {
    LauncherApplication *app = n->data;
    const char *cur = launcher_application_get_unique_string(app);
    if (cur && strcmp(cur, match_name) == 0)
      return app;
  }
  return NULL;
}

static void
_app_ecore_item_error(void *data, unsigned int len, const char **strings)
{
  struct app_item_context *ctxt = data;
  const char *name, *message;
  if (len != 2)
  {
    ERR("2 strings expected, got %u\n", len);
    return;
  }
  name = strings[0];
  message = strings[1];
  if (ctxt->pending_launch)
    launchfeedback_hide();
  error_show(_("Could not execute '%s': %s"), name, message);
}

static void
_app_ecore_item_activated(void *data)
{
  struct app_item_context *ctxt = data;
  if (!ctxt->pending_launch)
    return;
  launchfeedback_hide();
}

static void
_app_glib_item_select(void *data, unsigned int len, const char **strings)
{
  struct app_item_context *ctxt = data;
  LauncherApplication *app;
  gboolean running;
  const char *category;
  const char *match_name;
  GError *err = NULL;

  if (len != 2)
  {
    g_warning("_app_glib_item_select: 2 strings expected, got %u", len);
    return;
  }

  category = strings[0];
  match_name = strings[1];
  app = _app_item_find_app(ctxt, category, match_name);
  if (!app)
  {
    gchar *type;
    type = launcher_favorites_get_string(ctxt->parent->favs, ctxt->uid, "type");
    if (g_strcmp0(type, "uri") == 0)
    {
      gchar *uri = launcher_favorites_get_string(ctxt->parent->favs, ctxt->uid, "uri");
      g_app_info_launch_default_for_uri(uri, NULL, NULL);
      g_free(uri);
    }
    else
      g_warning("could not find application '%s' in category '%s'.",
                match_name, category);
    g_free(type);
    return;
  }

  running = launcher_application_get_running(app);
  g_debug("app %s (%p, %s)\n",
          launcher_application_get_name(app), app,
          launcher_application_get_unique_string(app));
  if (running)
  {
    g_debug("application %s is already running",
            launcher_application_get_name(app));
    launcher_application_show(app);
    gstuff_ecore_run_simple(_app_ecore_item_activated, ctxt);
    return;
  }

  launcher_application_launch(app, &err);
  if (err)
  {
    const char *strings[3];

    g_warning("could not execute '%s': %s", match_name, err->message);

    strings[0] = launcher_application_get_name(app);
    strings[1] = err->message;
    strings[2] = NULL;

    if (!strings[0])
      strings[0] = match_name;

    gstuff_ecore_run_ptr_string_array(_app_ecore_item_error, ctxt, 2, strings);
    g_error_free(err);
  }
}

static void
_app_item_lanchfeedback_end(void *data)
{
  struct app_item_context *ctxt = data;
  ctxt->pending_launch = 0;
}

static void
_app_gadget_select(void *data, Evas_Object *obj __UNUSED__, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  struct app_item_context *ctxt = data;

  _app_glib_item_load_gadget(ctxt->parent->favs, ctxt);
}

static void
_app_item_select(void *data, Evas_Object *obj __UNUSED__, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  struct app_item_context *ctxt = data;
  const char *strings[3];

  launchfeedback_show_full
    (ctxt->icon_name, ctxt->name, 10.0, _app_item_lanchfeedback_end, ctxt);
  ctxt->pending_launch = 1;

  strings[0] = ctxt->category;
  strings[1] = ctxt->match_name;
  strings[2] = NULL;
  gstuff_glib_run_ptr_string_array(_app_glib_item_select, ctxt, 2, strings);

  sound_play(ctxt->item, "button-pressed");
  sound_play(ctxt->item, "button-released");
}

static void
_app_item_fav_add(void *data, Evas_Object *obj __UNUSED__, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  struct app_item_context *ctxt = data;
  DBG("add favorite '%s' '%s' '%s'\n", ctxt->name, ctxt->uid, ctxt->icon_name);
  gstuff_glib_run_ptr_string(_app_glib_item_fav_add, ctxt, ctxt->uid);
  sound_play(ctxt->item, "button-pressed");
  sound_play(ctxt->item, "button-released");
}

static void
_app_item_fav_del(void *data, Evas_Object *obj __UNUSED__, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  struct app_item_context *ctxt = data;
  DBG("del favorite '%s' '%s' '%s'\n", ctxt->name, ctxt->uid, ctxt->icon_name);
  gstuff_glib_run_ptr_string(_app_glib_item_fav_del, ctxt, ctxt->uid);
  sound_play(ctxt->item, "button-pressed");
  sound_play(ctxt->item, "button-released");
}

static Eina_Bool
_app_item_is_running(struct app_item_context *ctxt)
{
  const struct string_array *a = ctxt->parent->running_apps;
  unsigned int i;

  if (!a)
    return 0;

  for (i = 0; i < a->len; i++)
  {
    if (a->array[i] == ctxt->match_name)
      return 1;
  }

  return 0;
}

static int
_app_item_ecore_stop_anim_delayed(void *data)
{
  struct app_item_context *ctxt = data;
  edje_object_play_set(ctxt->edje, 0);
  ctxt->timer.stop_anim = NULL;
  return 0;
}

static void
_app_item_ecore_stop_anim_request(struct app_item_context *ctxt)
{
  if (ctxt->timer.stop_anim)
    ecore_timer_del(ctxt->timer.stop_anim);
  ctxt->timer.stop_anim = ecore_timer_add
    (2.0, _app_item_ecore_stop_anim_delayed, ctxt);
}

static int
_app_item_ecore_win_app_open(void *data, int type __UNUSED__, void *event)
{
  struct app_item_context *ctxt = data;
  const char *match_name = event;
  int play;

  if (match_name != ctxt->match_name) /* both are stringshared */
      return 1;

  play = edje_object_play_get(ctxt->edje);
  edje_object_play_set(ctxt->edje, 1);
  if (!play)
    _app_item_ecore_stop_anim_request(ctxt);
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_WIN_OPEN, _EDJE_SIG_SRC);

  if (ctxt->pending_launch)
    launchfeedback_hide();

  return 1;
}

static int
_app_item_ecore_win_app_close(void *data, int type __UNUSED__, void *event)
{
  struct app_item_context *ctxt = data;
  const char *match_name = event;
  int play;

  if (match_name != ctxt->match_name) /* both are stringshared */
      return 1;

  play = edje_object_play_get(ctxt->edje);
  edje_object_play_set(ctxt->edje, 1);
  if (!play)
    _app_item_ecore_stop_anim_request(ctxt);

  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_WIN_CLOSE, _EDJE_SIG_SRC);
  return 1;
}

static int
_app_item_ecore_win_apps_changed(void *data, int type __UNUSED__, void *event)
{
  struct app_item_common *common = data;
  const struct string_array *running_apps = event;
  common->running_apps = running_apps;
  return 1;
}

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

  if (ctxt->timer.stop_anim)
  {
    ecore_timer_del(ctxt->timer.stop_anim);
    ctxt->timer.stop_anim = NULL;
  }

  edje_object_play_set(ctxt->edje, 1);

  return 1;
}

static int
_app_item_ecore_win_focus_out(void *data, int type __UNUSED__, void *event __UNUSED__)
{
  struct app_item_context *ctxt = data;
  _app_item_ecore_stop_anim_request(ctxt);
  return 1;
}

static void
_app_item_in(void *data, Evas_Object *obj, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  struct app_item_context *ctxt = data;
  const char *str = edje_object_data_get(obj, "selectraise");
  if (str && (strcmp(str, "on") == 0))
    evas_object_raise(ctxt->item);
  _app_item_data_select(NULL, ctxt);
}

static void
_app_item_out(void *data, Evas_Object *obj __UNUSED__, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  struct app_item_context *ctxt = data;
  _app_item_data_unselect(NULL, ctxt);
}

static void
_app_item_del(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct app_item_context *ctxt = data;

  kbdnav_pool_item_del(ctxt->parent->nav_pool, ctxt);

  eina_stringshare_del(ctxt->category);
  eina_stringshare_del(ctxt->name);
  eina_stringshare_del(ctxt->comment);
  eina_stringshare_del(ctxt->icon_name);
  eina_stringshare_del(ctxt->match_name);
  eina_stringshare_del(ctxt->uid);
  eina_stringshare_del(ctxt->path);

  if (ctxt->handlers.app_open)
    ecore_event_handler_del(ctxt->handlers.app_open);
  if (ctxt->handlers.app_close)
    ecore_event_handler_del(ctxt->handlers.app_close);
  if (ctxt->handlers.focus_in)
    ecore_event_handler_del(ctxt->handlers.focus_in);
  if (ctxt->handlers.focus_out)
    ecore_event_handler_del(ctxt->handlers.focus_out);

  if (ctxt->timer.stop_anim)
    ecore_timer_del(ctxt->timer.stop_anim);

  if (ctxt->pending_launch)
    launchfeedback_hide();

  free(ctxt);
}

#ifdef HAVE_GOOGLE_GADGETS
static void
_on_remove_fav_gadget(void *data, Evas_Object *o, void *ev)
{
  struct app_item_context *ctxt = data;

  launcher_favorites_remove_favorite(ctxt->parent->favs, efl_gadget_uid_get(o));
  evas_object_del(o);
}
#endif

Evas_Object *
app_gadget_add(const struct app_item_common *parent, const char *category, const char *name, const char *comment, const char *icon_name, const char *path, const char *uid, Eina_Bool is_favorite)
{
  struct app_item_context *ctxt;
  Evas_Object *item, *ed, *icon;

  DBG("gadget add: '%s' '%s' '%s' '%s' '%s'\n",
      name, comment, icon_name, path, uid);

#ifdef HAVE_GOOGLE_GADGETS
    if (is_favorite)
        item = efl_gadget_new(evas_object_evas_get(parent->parent), path, uid);
    else
#endif
        item = elm_layout_add(parent->parent);
    if (!item)
    {
        ERR("could not create layout item.\n");
        return NULL;
    }

  ctxt = calloc(1, sizeof(*ctxt));
  if (!ctxt)
  {
    ERR("could not allocate app_item_context\n");
    evas_object_del(item);
    return NULL;
  }
  ctxt->item = item;
  ctxt->parent = parent;
  ctxt->category = eina_stringshare_add(category);
  ctxt->name = eina_stringshare_add(name);
  ctxt->comment = eina_stringshare_add(comment);
  ctxt->icon_name = eina_stringshare_add(icon_name);
  ctxt->match_name = eina_stringshare_add(uid);
  ctxt->path = eina_stringshare_add(path);
  ctxt->uid = eina_stringshare_add(uid);
  ctxt->is_favorite = is_favorite;

  evas_object_event_callback_add
    (item, EVAS_CALLBACK_FREE, _app_item_del, ctxt);

  if (!is_favorite)
  {
    ed = elm_layout_edje_get(item);
    if (!elm_layout_file_set(item, _nl_prefs.theme, _EDJE_GROUP_ITEM))
    {
      int err = edje_object_load_error_get(ed);
      const char *errmsg = edje_load_error_str(err);
      ERR("cannot load theme '%s', group '%s': %s\n",
          _nl_prefs.theme, _EDJE_GROUP_ITEM, errmsg);
      evas_object_del(item);
      return NULL;
    }
    ctxt->edje = ed;

    icon = _nl_icon_load_sized(parent->parent, icon_name,
        parent->item.icon_size);
    if (icon)
      elm_layout_content_set(item, _EDJE_PART_ICON, icon);

    edje_object_part_text_set(ed, _EDJE_PART_LABEL, name);
    edje_object_part_text_set(ed, _EDJE_PART_COMMENT, comment);

#define ES(sig, func)                                                   \
    edje_object_signal_callback_add(ed, sig, _EDJE_SIG_SRC, func, ctxt)
    ES(_EDJE_SIG_FOCUS_IN, _app_item_in);
    ES(_EDJE_SIG_FOCUS_OUT, _app_item_out);
    ES(_EDJE_SIG_EXECUTED, _app_gadget_select);
    ES(_EDJE_SIG_FAV_ADD, _app_item_fav_add);
    ES(_EDJE_SIG_FAV_DEL, _app_item_fav_del);
#undef ES

    edje_object_signal_emit(ed, _EDJE_SIG_FAV_CAN_ADD, _EDJE_SIG_SRC);
    edje_object_part_text_set(ed, _EDJE_PART_FAV_ACTION,
        _("add to favorites"));
  }
#ifdef HAVE_GOOGLE_GADGETS
  else
    evas_object_smart_callback_add(item, "remove-me", _on_remove_fav_gadget,
        ctxt);
#endif

  evas_object_resize(item, parent->item.w, parent->item.h);
  evas_object_show(item);

  kbdnav_pool_item_add(parent->nav_pool, ctxt);

  return item;
}

Evas_Object *
app_item_add(const struct app_item_common *parent, const char *category, const char *name, const char *comment, const char *icon_name, const char *match_name, const char *uid, Eina_Bool is_favorite)
{
  struct app_item_context *ctxt;
  Evas_Object *item, *ed, *icon;

  DBG("application add: '%s' '%s' '%s' '%s' '%s'\n",
      name, comment, icon_name, match_name, uid);

  item = elm_layout_add(parent->parent);
  if (!item)
  {
    ERR("could not create layout item.\n");
    return NULL;
  }

  ed = elm_layout_edje_get(item);
  if (!elm_layout_file_set(item, _nl_prefs.theme, _EDJE_GROUP_ITEM))
  {
    int err = edje_object_load_error_get(ed);
    const char *errmsg = edje_load_error_str(err);
    ERR("cannot load theme '%s', group '%s': %s\n",
        _nl_prefs.theme, _EDJE_GROUP_ITEM, errmsg);
    evas_object_del(item);
    return NULL;
  }

  ctxt = calloc(1, sizeof(*ctxt));
  if (!ctxt)
  {
    ERR("could not allocate app_item_context\n");
    evas_object_del(item);
    return NULL;
  }
  ctxt->item = item;
  ctxt->edje = ed;
  ctxt->category = eina_stringshare_add(category);
  ctxt->name = eina_stringshare_add(name);
  ctxt->comment = eina_stringshare_add(comment);
  ctxt->icon_name = eina_stringshare_add(icon_name);
  ctxt->match_name = eina_stringshare_add(match_name);
  ctxt->uid = eina_stringshare_add(uid);
  ctxt->path = NULL;
  ctxt->parent = parent;
  ctxt->is_favorite = is_favorite;

  evas_object_event_callback_add
    (item, EVAS_CALLBACK_FREE, _app_item_del, ctxt);

  icon = _nl_icon_load_sized(parent->parent, icon_name, parent->item.icon_size);
  if (icon)
    elm_layout_content_set(item, _EDJE_PART_ICON, icon);

  edje_object_part_text_set(ed, _EDJE_PART_LABEL, name);
  edje_object_part_text_set(ed, _EDJE_PART_COMMENT, comment);

#define ES(sig, func)                                                   \
  edje_object_signal_callback_add(ed, sig, _EDJE_SIG_SRC, func, ctxt)
  ES(_EDJE_SIG_FOCUS_IN, _app_item_in);
  ES(_EDJE_SIG_FOCUS_OUT,_app_item_out);
  ES(_EDJE_SIG_EXECUTED, _app_item_select);
  ES(_EDJE_SIG_FAV_ADD, _app_item_fav_add);
  ES(_EDJE_SIG_FAV_DEL, _app_item_fav_del);
#undef ES

#define WS(member, sig, func)                                           \
  ctxt->handlers.member = ecore_event_handler_add(sig, func, ctxt)
  WS(app_open, WIN_EVENT_APP_OPEN, _app_item_ecore_win_app_open);
  WS(app_close, WIN_EVENT_APP_CLOSE, _app_item_ecore_win_app_close);
  WS(focus_in, WIN_EVENT_FOCUS_IN, _app_item_ecore_win_focus_in);
  WS(focus_out, WIN_EVENT_FOCUS_OUT, _app_item_ecore_win_focus_out);
#undef WS

  if (!is_favorite)
  {
    edje_object_signal_emit(ed, _EDJE_SIG_FAV_CAN_ADD, _EDJE_SIG_SRC);
    edje_object_part_text_set(ed, _EDJE_PART_FAV_ACTION, _("add to favorites"));
  }
  else
  {
    edje_object_signal_emit(ed, _EDJE_SIG_FAV_CAN_DEL, _EDJE_SIG_SRC);
    edje_object_part_text_set
      (ed, _EDJE_PART_FAV_ACTION, _("remove from favorites"));
  }

  if (_app_item_is_running(ctxt))
    edje_object_signal_emit(ed, _EDJE_SIG_WIN_OPEN, _EDJE_SIG_SRC);

  evas_object_resize(item, parent->item.w, parent->item.h);
  evas_object_show(item);

  kbdnav_pool_item_add(parent->nav_pool, ctxt);

  return item;
}

static Evas_Object *
_app_item_data_object_get(void *user_data __UNUSED__, void *item_data)
{
  struct app_item_context *ctxt = item_data;
  return ctxt->edje;
}

static const char *
_app_item_data_id_get(void *user_data __UNUSED__, void *item_data)
{
  struct app_item_context *ctxt = item_data;
  return ctxt->uid;
}

static void
_app_item_data_select(void *user_data __UNUSED__, void *item_data)
{
  struct app_item_context *ctxt = item_data;
  Evas_Coord ix, iy, iw, ih, cx, cy;
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_SELECT, _EDJE_SIG_SRC);

  evas_object_geometry_get(ctxt->edje, &ix, &iy, &iw, &ih);
  evas_object_geometry_get(ctxt->parent->container, &cx, &cy, NULL, NULL);
  ix -= cx;
  iy -= cy;
  elm_scroller_region_show(ctxt->parent->scroller, ix, iy, iw, ih);
  sound_play(ctxt->edje, "item-selected");

  _nl_icon_info_event_enter(ctxt->name, ctxt->icon_name, ctxt->comment);
}

static void
_app_item_data_unselect(void *user_data __UNUSED__, void *item_data)
{
  struct app_item_context *ctxt = item_data;
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_UNSELECT, _EDJE_SIG_SRC);
  _nl_icon_info_event_leave();
}

static void
_app_item_common_focus_in(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct app_item_common *common = data;
  kbdnav_pool_current_select(common->nav_pool);
}

static void
_app_item_common_focus_out(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct app_item_common *common = data;
  kbdnav_pool_current_unselect(common->nav_pool);
}

static void
_app_item_common_selected_activate(struct app_item_common *common)
{
  struct app_item_context *ctxt;

  ctxt = kbdnav_pool_current_item_get(common->nav_pool);
  if (!ctxt)
    return;
  _app_item_select(ctxt, ctxt->edje, NULL, NULL);
}

static void
_app_item_common_key_down(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info)
{
  Evas_Event_Key_Down *ev = event_info;
  struct app_item_common *common = data;

  if (strcmp(ev->keyname, "Up") == 0)
    kbdnav_pool_navigate_up(common->nav_pool);
  else if (strcmp(ev->keyname, "Down") == 0)
    kbdnav_pool_navigate_down(common->nav_pool);
  else if (strcmp(ev->keyname, "Left") == 0)
    kbdnav_pool_navigate_left(common->nav_pool);
  else if (strcmp(ev->keyname, "Right") == 0)
    kbdnav_pool_navigate_right(common->nav_pool);
  else if (strcmp(ev->keyname, "Return") == 0)
    _app_item_common_selected_activate(common);
  else
  {
    struct app_item_context *ctxt;
    ctxt = kbdnav_pool_current_item_get(common->nav_pool);
    if (!ctxt)
      return;

    if (ctxt->is_favorite &&
        ((strcmp(ev->keyname, "minus") == 0) ||
         (strcmp(ev->keyname, "KP_Subtract") == 0) ||
         (strcmp(ev->keyname, "Delete") == 0)))
      _app_item_fav_del(ctxt, NULL, NULL, NULL);
    else if ((!ctxt->is_favorite) &&
             ((strcmp(ev->keyname, "equal") == 0) ||
              (strcmp(ev->keyname, "plus") == 0) ||
              (strcmp(ev->keyname, "KP_Add") == 0) ||
              (strcmp(ev->keyname, "Insert") == 0)))
      _app_item_fav_add(ctxt, NULL, NULL, NULL);
  }
}

static void
_app_item_common_calc_sizes(struct app_item_common *common)
{
  common->item.icon_size = 32;
  common->item.w = 50;
  common->item.h = 50;
  _nl_edje_item_size_calc(&common->item, common->parent, _EDJE_GROUP_ITEM);
}

Eina_Bool
app_item_common_ecore_start(struct app_item_common *common, Evas_Object *parent, Evas_Object *scroller, Evas_Object *container)
{
  common->parent = parent;
  common->scroller = scroller;
  common->container = container;
  _app_item_common_calc_sizes(common);

  evas_object_event_callback_add
    (parent, EVAS_CALLBACK_FOCUS_IN, _app_item_common_focus_in, common);
  evas_object_event_callback_add
    (parent, EVAS_CALLBACK_FOCUS_OUT, _app_item_common_focus_out, common);
  evas_object_event_callback_add
    (parent, EVAS_CALLBACK_KEY_DOWN, _app_item_common_key_down, common);

  common->_apps_changed = ecore_event_handler_add
    (WIN_EVENT_APPS_CHANGED, _app_item_ecore_win_apps_changed, common);

  common->nav_pool = kbdnav_pool_new
    (_app_item_data_object_get, _app_item_data_id_get,
     _app_item_data_select, _app_item_data_unselect, NULL);

  return 1;
}

Eina_Bool
app_item_common_ecore_stop(struct app_item_common *common)
{
  evas_object_event_callback_del
    (common->parent, EVAS_CALLBACK_FOCUS_IN, _app_item_common_focus_in);
  evas_object_event_callback_del
    (common->parent, EVAS_CALLBACK_FOCUS_OUT, _app_item_common_focus_out);
  evas_object_event_callback_del
    (common->parent, EVAS_CALLBACK_KEY_DOWN, _app_item_common_key_down);

  if (common->_apps_changed)
    ecore_event_handler_del(common->_apps_changed);
  if (common->nav_pool)
    kbdnav_pool_free(common->nav_pool);
  return 1;
}

Eina_Bool
app_item_common_navigation_id_set(struct app_item_common *common, const char *id)
{
  return kbdnav_pool_current_id_set(common->nav_pool, id);
}


static int _notify_init_count = 0;
static Eina_Bool _notify_init_here = 0;

Eina_Bool
app_item_common_glib_start(struct app_item_common *common)
{
  common->menu = launcher_menu_get_default();
  if (!common->menu)
  {
    g_critical("could not get launcher menu.");
    return 0;
  }

  common->favs = launcher_favorites_get_default();
  if (!common->favs)
  {
    g_critical("could not get launcher favorites.");
    return 0;
  }

  _notify_init_count++;
  if (_notify_init_count == 1)
  {
    if (!notify_is_initted())
    {
      _notify_init_here = 1;
      notify_init(PACKAGE);
    }
  }

  return 1;
}

Eina_Bool
app_item_common_glib_stop(struct app_item_common *common)
{
  if (common->menu)
    g_object_unref(common->menu);
  if (common->favs)
    g_object_unref(common->favs);

  _notify_init_count--;
  if (_notify_init_count == 0)
  {
    if (_notify_init_here)
    {
      _notify_init_here = 0;
      notify_uninit();
    }
  }

  return 1;
}
