/*
 * Copyright (C) 2008 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as 
 * published by the Free Software Foundation.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authored by Neil Jagdish Patel <neil.patel@canonical.com>
 *
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include "launcher-app.h"

#include <glib.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <string.h>
#include <stdlib.h>

#include <clutter-gtk/gtk-clutter-util.h>
#include <tidy/tidy.h>

#include <gconf/gconf.h>
#include <gconf/gconf-client.h>

#include "clutter-focus-manager.h"

#include "launcher-background.h"
#include "launcher-catbar.h"
#include "launcher-config.h"
#include "launcher-defines.h"
#include "launcher-iconview.h"
#include "launcher-link-editor.h"
#include "launcher-menu.h"
#include "launcher-shortcut.h"
#include "launcher-sidebar.h"
#include "launcher-startup.h"
#include "launcher-util.h"
#include "launcher-wm.h"

#define LINK_WATCH_DIR "/tmp/webfav"

G_DEFINE_TYPE (LauncherApp, launcher_app, CLUTTER_TYPE_GROUP);

#define LAUNCHER_APP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  LAUNCHER_TYPE_APP, \
  LauncherAppPrivate))

struct _LauncherAppPrivate
{
  gboolean windowed;

  LauncherWm   *wm;
  LauncherMenu *menu;
  
  ClutterActor *focus_manager;
  
  ClutterActor *bg;
  ClutterActor *shadow;
  ClutterActor *window;
  ClutterActor *ubuntu;
  ClutterActor *catbar;
  ClutterActor *iconviewbg;
  ClutterActor *iconview;
  ClutterActor *sidebar;
  ClutterActor *places;

  LauncherMenuCategory *current_cat;
  LauncherMenuCategory *places_cat;

  ClutterTimeline       *time;
  ClutterEffectTemplate *temp;

  GFile *watch_dir;
  GFileMonitor *dir_monitor;
};

/* Callbacks */
static void
on_effect_complete (ClutterActor *actor)
{
  if (CLUTTER_IS_ACTOR (actor))
    clutter_actor_destroy (actor);
}

static void
on_new_category (LauncherCatbar       *catbar,
                 LauncherMenuCategory *category,
                 LauncherApp          *app)
{
  LauncherAppPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();

  g_return_if_fail (LAUNCHER_IS_APP (app));
  g_return_if_fail (category);
  priv = app->priv;

  priv->current_cat = category;

  if (priv->places && priv->current_cat != priv->places_cat)
    launcher_shortcut_set_active (LAUNCHER_SHORTCUT (priv->places), FALSE);

  if (CLUTTER_IS_ACTOR (priv->iconview))
  {
    if (cfg->low_graphics)
      clutter_actor_destroy (priv->iconview);
    else
      clutter_effect_fade (priv->temp, priv->iconview, 0,
                         (ClutterEffectCompleteFunc)on_effect_complete, 
                         NULL);
  }

  priv->iconview = launcher_iconview_new ();
  clutter_container_add_actor (CLUTTER_CONTAINER (app),
                               priv->iconview);
  clutter_actor_set_position (priv->iconview, 
                         clutter_actor_get_x (priv->window)+cfg->bar_width-3,
                              PANEL_HEIGHT*2.3+PADDING);

  clutter_actor_show (priv->iconview);
  clutter_focus_manager_insert_group(CLUTTER_FOCUS_MANAGER(priv->focus_manager),
                                     CLUTTER_FOCUS_GROUP (priv->iconview),
                                     1);

  launcher_iconview_set_category (LAUNCHER_ICONVIEW (priv->iconview), category);
}

static gboolean
on_scroll_event (ClutterActor       *actor,
                 ClutterScrollEvent *event,
                 LauncherApp        *app)
{
  g_return_val_if_fail (LAUNCHER_IS_APP (app), FALSE);
  
  if (!LAUNCHER_IS_ICONVIEW (app->priv->iconview))
    return FALSE;

  return launcher_iconview_scroll_event (actor, event, 
                                         LAUNCHER_ICONVIEW (app->priv->iconview)
                                         );
}

static gboolean
eat_event (ClutterActor *stage, ClutterEvent *event, LauncherApp *app)
{
  gint opacity = clutter_actor_get_opacity (CLUTTER_ACTOR (app));

  if (opacity == 255)
  {
    g_signal_handlers_disconnect_by_func (stage, eat_event, app);
    clutter_ungrab_pointer ();
    return FALSE;
  }
  

  return TRUE;
}

static void
ungrab (ClutterActor *actor, LauncherApp *app)
{
  clutter_ungrab_pointer ();
  g_signal_handlers_disconnect_by_func (clutter_stage_get_default (), 
                                        eat_event, app);
}

static void
show_launcher (LauncherWm *wm, LauncherApp *app)
{ 
  LauncherAppPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();

  g_return_if_fail (LAUNCHER_IS_APP (app));
  priv = app->priv;

  if (cfg->low_graphics)
  {
    clutter_actor_set_opacity (CLUTTER_ACTOR (app), 255);
    ungrab (CLUTTER_ACTOR (app), app);
  }
  else
  {
    clutter_effect_fade (priv->temp, CLUTTER_ACTOR (app), 255, 
                         (ClutterEffectCompleteFunc)ungrab,
                         app);
  }
}

static void
hide_launcher (LauncherWm *wm, WnckWindow *window, LauncherApp *app)
{
  LauncherAppPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();

  g_return_if_fail (LAUNCHER_IS_APP (app));
  priv = app->priv;

  if (priv->windowed)
    return;

  if (cfg->low_graphics)
    clutter_actor_set_opacity (CLUTTER_ACTOR (app), 50);
  else
    clutter_effect_fade (priv->temp, CLUTTER_ACTOR (app), 50, NULL, NULL);

  clutter_grab_pointer (clutter_stage_get_default ());
  g_signal_connect (clutter_stage_get_default (),
                                    "captured-event", G_CALLBACK (eat_event), 
                                    app);
}

/*
 * External link watching 
 */
static gchar *
save_favion (const gchar *path)
{
#define ICON_DIR ".config/netbook-remix/icons"
  GFile *from;
  GFile *to;
  gchar *new_path, *temp, *leaf, *icon_dir;

  from = g_file_new_for_path (path);

  temp = g_filename_display_basename (path);
  leaf = g_strdup_printf ("%ld-%s", time (NULL), temp);

  /* make sure icon dir exists */
  icon_dir = g_build_filename (g_get_home_dir (), ICON_DIR, NULL);
  if (!g_file_test (icon_dir, G_FILE_TEST_EXISTS))
  {
    g_mkdir_with_parents (icon_dir, 0700);
    g_debug ("Created icon directory for webfav at %s", icon_dir);
  }

  new_path = g_build_filename (icon_dir, leaf, NULL);
  to = g_file_new_for_path (new_path);

  g_file_move (from, to, 0, NULL, NULL, NULL, NULL);

  g_free (temp);
  g_free (leaf);
  g_free (icon_dir);
  g_object_unref (from);
  g_object_unref (to);

  return new_path;
}

static void
on_link_reponse (GtkWidget *dialog, gint res, LauncherApp *app)
{
  if (!LAUNCHER_IS_APP (app))
  {
    gtk_widget_destroy (dialog);
    return;
  }

  if (res == GTK_RESPONSE_YES)
  {

     gchar *name = NULL, *exec = NULL, *icon_name = NULL, *real_icon;
     g_object_get (dialog, 
                   "link-name", &name,
                   "link-exec", &exec,
                   "link-icon", &icon_name,
                   NULL);
     real_icon = save_favion (icon_name);
     launcher_util_add_favorite (name, "Favorites", exec, real_icon);

     g_free (name);
     g_free (exec);
     g_free (icon_name);
     g_free (real_icon);
  }

  gtk_widget_destroy (dialog);
}

static void
on_watch_dir_changed (GFileMonitor      *monitor,
                      GFile             *file,
                      GFile             *other_file,
                      GFileMonitorEvent  event,
                      LauncherApp       *app)
{
  LauncherAppPrivate *priv;
  gchar *link_path;

  g_return_if_fail (LAUNCHER_IS_APP (app));
  priv = app->priv;

  if (event != G_FILE_MONITOR_EVENT_CREATED)
    return;

  link_path = g_file_get_path (file);
  if (link_path && strstr (link_path, ".desktop"))
  {
    GKeyFile *file;
    
    file = g_key_file_new ();
    if (g_key_file_load_from_file (file, link_path, 0, NULL))
    {
      GtkWidget *dialog;
      gchar *icon_path;
      
      icon_path = g_build_filename (LINK_WATCH_DIR, 
                                    g_key_file_get_string (file, 
                                                      G_KEY_FILE_DESKTOP_GROUP,
                                           G_KEY_FILE_DESKTOP_KEY_ICON, NULL),
                                    NULL);
      dialog = launcher_link_editor_new ();
      g_object_set (dialog, 
                "title", _("Add Shortcut"),
                "link-name", 
                  g_key_file_get_string (file, G_KEY_FILE_DESKTOP_GROUP,
                                         G_KEY_FILE_DESKTOP_KEY_NAME, NULL),
                "link-exec",
                  g_key_file_get_string (file, G_KEY_FILE_DESKTOP_GROUP,
                                         G_KEY_FILE_DESKTOP_KEY_URL, NULL),
                "link-icon", icon_path,
                NULL);
      gtk_dialog_add_buttons (GTK_DIALOG (dialog), 
                              "gtk-cancel", GTK_RESPONSE_CANCEL,
                              GTK_STOCK_APPLY, GTK_RESPONSE_YES,
                              NULL);
      g_signal_connect (dialog, "response",
                        G_CALLBACK (on_link_reponse), app);
                                   
      launcher_util_present_window (GTK_WINDOW (dialog));
      g_free (icon_path);
    }
    g_key_file_free (file);
  }
 

  g_free (link_path);
}

static void
watch_links (LauncherApp *app)
{
  LauncherAppPrivate *priv = app->priv;
  GFile *dir;

  g_mkdir_with_parents (LINK_WATCH_DIR, 0700);

  dir = priv->watch_dir = g_file_new_for_path (LINK_WATCH_DIR);

  priv->dir_monitor = g_file_monitor_directory (dir, 0, NULL, NULL);

  g_signal_connect (priv->dir_monitor, "changed",
                    G_CALLBACK (on_watch_dir_changed), app);
}

static gboolean
launch_dist_page (ClutterActor *actor, ClutterButtonEvent *event)
{
  const gchar *url;
  gchar *exec;

  if (event->button != 1)
    return FALSE;

  url = clutter_actor_get_name (actor);
  exec = g_strdup_printf ("%s %s", XDG_OPEN, url);

  gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
                                      exec, 
                                      NULL);
  g_free (exec);

  return FALSE;
}

static void
load_dist_logo (LauncherApp *app)
{
#define DIST_DIR "/apps/netbook-launcher"
#define DIST_DIR_LEGACY "/apps/ume-launcher"
#define DIST_IMG DIST_DIR"/dist_image"
#define DIST_IMG_LEGACY DIST_DIR_LEGACY"/dist_image"
#define DIST_URL DIST_DIR"/dist_url"
#define DIST_URL_LEGACY DIST_DIR_LEGACY"/dist_url"

  LauncherAppPrivate *priv = app->priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  GConfClient *client = gconf_client_get_default ();
  ClutterActor *tex;
  GdkPixbuf *logo;
  gint width, height;
  const gchar *imageurl;
  const gchar *url;

  width = cfg->bar_width;
  height = cfg->shortcut_height * 1.1;
  height *= 1;

  gconf_client_add_dir (client, DIST_DIR, GCONF_CLIENT_PRELOAD_NONE, NULL);
  imageurl = gconf_client_get_string (client, DIST_IMG, NULL);
  url = gconf_client_get_string (client, DIST_URL, NULL);

  /* if new gconf key exists, use it, otherwise try legacy key */
  if (!imageurl)
  {
    gconf_client_remove_dir (client, DIST_DIR, NULL);
    gconf_client_add_dir (client, DIST_DIR_LEGACY, GCONF_CLIENT_PRELOAD_NONE, NULL);
    imageurl = gconf_client_get_string (client, DIST_IMG_LEGACY, NULL);
    url = gconf_client_get_string (client, DIST_URL_LEGACY, NULL);

    if (!imageurl)
      return;
  }

  logo = gdk_pixbuf_new_from_file_at_scale (imageurl,
                                            width, height, TRUE, NULL);

  if (logo)
  {
    tex = launcher_util_texture_new_from_pixbuf (logo);
    clutter_container_add_actor (CLUTTER_CONTAINER (app), tex);
    clutter_actor_set_anchor_point_from_gravity (tex, CLUTTER_GRAVITY_CENTER);
    clutter_actor_set_position (tex, 
                                clutter_actor_get_x (priv->window) + (width/2),
                                CSH() - PADDING*2 - (height/2));
    clutter_actor_set_name (tex, url);
    clutter_actor_set_reactive (tex, TRUE);
    clutter_actor_show (tex);

    if (url)
    {
      g_signal_connect (tex, "button-release-event",
                      G_CALLBACK (launch_dist_page), NULL);
    }
    g_object_unref (logo);
  }

  g_object_unref (client);
}

static gboolean
deactivate_shortcut (LauncherShortcut *shortcut)
{
  launcher_shortcut_set_active (LAUNCHER_SHORTCUT (shortcut), FALSE);
  return FALSE;
}

static void
on_quit_clicked (LauncherShortcut *shortcut)
{
  gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
                                    "gnome-session-save --kill --gui",
                                    NULL);
  g_timeout_add_seconds (2, (GSourceFunc)deactivate_shortcut, shortcut);
}

static void
on_places_clicked (LauncherShortcut *shortcut, LauncherApp *app)
{
  static LauncherMenuCategory *cat = NULL;
  GList *places, *p;

  if (cat == app->priv->current_cat)
    return;

  if (cat)
  {
    GList *a;
    for (a = cat->applications; a; a = a->next)
    {
      LauncherMenuApplication *app = a->data;

      g_free (app->name);
      g_free (app->exec);
      g_slice_free (LauncherMenuApplication, app);
    }
    g_list_free (cat->applications);
    cat->applications = NULL;
  }
  else
  {
    cat = app->priv->places_cat = g_slice_new0 (LauncherMenuCategory);
  }

  places = launcher_sidebar_get_places (LAUNCHER_SIDEBAR 
                                          (launcher_sidebar_get_default ()));
  for (p = places; p; p = p->next)
  {
    LauncherShortcut *shortcut = p->data;
    LauncherMenuApplication *app;
    GVolume *volume;
    
    if (!LAUNCHER_IS_SHORTCUT (shortcut))
      continue;
    
    app = g_slice_new0 (LauncherMenuApplication);

    app->name = g_strdup (launcher_shortcut_get_label (shortcut));
    app->pixbuf = launcher_shortcut_get_icon (shortcut);
    app->exec = g_strdup (clutter_actor_get_name (CLUTTER_ACTOR (shortcut)));
    app->can_fav = FALSE;

    volume = g_object_get_data (G_OBJECT (shortcut), "volume");
    if (G_IS_VOLUME (volume))
      app->volume = volume;

    cat->applications = g_list_append (cat->applications, app);
  }
  
  launcher_catbar_deactivate (LAUNCHER_CATBAR (app->priv->catbar));
  on_new_category (NULL, cat, app);

  g_list_free (places);
}

static void
on_sidebar_shortcuts_changed (LauncherSidebar *sidebar, LauncherApp *app)
{
  app->priv->current_cat = NULL;
  on_places_clicked (LAUNCHER_SHORTCUT (app->priv->places), app);
}

/* GObject stuff */
static void
launcher_app_finalize (GObject *object)
{
  LauncherAppPrivate *priv;

  priv = LAUNCHER_APP_GET_PRIVATE (object);

  g_object_unref (priv->wm);
  g_object_unref (priv->menu);

  G_OBJECT_CLASS (launcher_app_parent_class)->finalize (object);
}

static void
launcher_app_class_init (LauncherAppClass *klass)
{
  GObjectClass        *obj_class = G_OBJECT_CLASS (klass);

  obj_class->finalize = launcher_app_finalize;

  g_type_class_add_private (obj_class, sizeof (LauncherAppPrivate));
}

static void
launcher_app_init (LauncherApp *app)
{
  LauncherAppPrivate *priv;
  ClutterActor *stage = clutter_stage_get_default ();
  LauncherConfig *cfg = launcher_config_get_default ();
  ClutterActor *shortcut;
  const gchar *winmode;
	
  priv = app->priv = LAUNCHER_APP_GET_PRIVATE (app);

  winmode = g_getenv ("LAUNCHER_WINDOWED");
  priv->windowed = winmode ? atoi (winmode) : FALSE;
  priv->places = NULL;
  priv->places_cat = NULL;
  
  /* The desktop background */
  priv->bg = launcher_background_new ();
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->bg);
  clutter_actor_set_size (priv->bg, CSW(), CSH());
  clutter_actor_set_position (priv->bg, 0, 0);
  clutter_actor_show (priv->bg);

  /* Main container group */
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), CLUTTER_ACTOR (app));
  clutter_actor_set_position (CLUTTER_ACTOR (app), 0, 0);
  clutter_actor_set_size (CLUTTER_ACTOR (app), CSW(), CSH());
  clutter_actor_show (CLUTTER_ACTOR (app));

  /* Focus server */
  priv->focus_manager = clutter_focus_manager_get_default ();
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->focus_manager);

  stage = CLUTTER_ACTOR (app);

  /* Shadow 
  priv->shadow = launcher_util_texture_new_from_file (PKGDATADIR"/shadow.png");
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->shadow);
  clutter_actor_set_position (priv->shadow, 0, PANEL_HEIGHT);
  if (!priv->windowed) clutter_actor_show (priv->shadow);
  */

  /* Window */
  priv->window = launcher_util_texture_new_from_file (PKGDATADIR"/window.svg");
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->window);
  clutter_actor_set_opacity (priv->window, 0);
  priv->window = tidy_texture_frame_new (CLUTTER_TEXTURE (priv->window),
                                         30, 30, 30, 30);
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->window);
  clutter_actor_set_size (priv->window, cfg->win_width, cfg->win_height);
  clutter_actor_set_position (priv->window, 
                              (CSW()-cfg->win_width)/2, 
                              PANEL_HEIGHT + 5);
  clutter_actor_show (priv->window);

  /* Ubuntu logo */
  priv->ubuntu = launcher_util_texture_new_from_file (PKGDATADIR"/ubuntu.png");
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->ubuntu);
  clutter_actor_set_anchor_point_from_gravity (priv->ubuntu, 
                                               CLUTTER_GRAVITY_NORTH);
  clutter_actor_set_position (priv->ubuntu, CSW()/2, 
                              PANEL_HEIGHT + PADDING + PANEL_HEIGHT/4);
  clutter_actor_show (priv->ubuntu);

  /* Init the WM interface */
  priv->wm = launcher_wm_get_default ();
  g_signal_connect (priv->wm, "show-windec", G_CALLBACK (hide_launcher), app);
  g_signal_connect (priv->wm, "hide-windec", G_CALLBACK (show_launcher), app);

  /* Application menu */
  priv->menu = launcher_menu_get_default ();

  /* Iconviewbg */
  priv->iconviewbg = launcher_util_texture_new_from_file 
                                                  (PKGDATADIR"/iconviewbg.png");
  clutter_actor_set_opacity (priv->iconviewbg, 0);
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->iconviewbg);
  priv->iconviewbg = tidy_texture_frame_new (CLUTTER_TEXTURE (priv->iconviewbg),
                                             30, 30, 30, 30);
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->iconviewbg);
  clutter_actor_set_size (priv->iconviewbg, cfg->iconview_width,
                          cfg->win_height-(PANEL_HEIGHT*1.3+PADDING));
  clutter_actor_set_position (priv->iconviewbg, 
                           clutter_actor_get_x (priv->window)+cfg->bar_width-3, 
                           PANEL_HEIGHT*2.3+PADDING+2);
  clutter_actor_show (priv->iconviewbg);
  clutter_actor_set_reactive (CLUTTER_ACTOR (priv->iconviewbg), TRUE);
  g_signal_connect (priv->iconviewbg, "scroll-event",
                    G_CALLBACK (on_scroll_event), app);
    
  /* Sidebar */
  if (!cfg->is_portrait)
  {
    priv->sidebar = launcher_sidebar_get_default ();
    clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->sidebar);
    clutter_actor_set_position (priv->sidebar, 
                                clutter_actor_get_x (priv->window)
                                  +CAW(priv->window)-cfg->bar_width,
                                PANEL_HEIGHT*2.3+PADDING);
    clutter_actor_show (priv->sidebar);
    clutter_focus_manager_insert_group 
                                    (CLUTTER_FOCUS_MANAGER(priv->focus_manager),
                                       CLUTTER_FOCUS_GROUP (priv->sidebar),
                                       1);
  }

  /* Category bar */
  priv->catbar = launcher_catbar_get_default ();
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->catbar);
  clutter_actor_set_position (priv->catbar, 
                              clutter_actor_get_x (priv->window),
                              PANEL_HEIGHT*2.3+PADDING);
  clutter_actor_show (priv->catbar);
  g_signal_connect (priv->catbar, "category-selected",
                    G_CALLBACK (on_new_category), app);
  clutter_focus_manager_insert_group(CLUTTER_FOCUS_MANAGER(priv->focus_manager),
                                     CLUTTER_FOCUS_GROUP (priv->catbar),
                                     0);
  /* dist logo */
  if (!cfg->is_portrait)
  {
    load_dist_logo (app);
  }
  else
  {
    shortcut = launcher_shortcut_new ("gtk-quit",
                                      _("Quit..."),
                                      FALSE);
    clutter_container_add_actor (CLUTTER_CONTAINER (stage), shortcut);
    clutter_actor_set_position (shortcut, 0, 
                               cfg->win_height - (cfg->shortcut_height));
    clutter_actor_show_all (shortcut);
    g_signal_connect (shortcut, "clicked", G_CALLBACK (on_quit_clicked), NULL);

    shortcut = priv->places = launcher_shortcut_new ("folder",
                                                     _("Places"),
                                                     FALSE);
    clutter_container_add_actor (CLUTTER_CONTAINER (stage), shortcut);
    clutter_actor_set_position (shortcut, 0, 
                               cfg->win_height - (cfg->shortcut_height*2));
    clutter_actor_show_all (shortcut);
    g_signal_connect (shortcut, "clicked", G_CALLBACK (on_places_clicked), app);

    g_signal_connect (launcher_sidebar_get_default (), "shortcuts-changed",
                      G_CALLBACK (on_sidebar_shortcuts_changed), app);
  }

  on_new_category (NULL, 
                   launcher_menu_get_categories (priv->menu)->data,
                   app);

  priv->time = clutter_timeline_new_for_duration (MID_TIME);
  priv->temp = clutter_effect_template_new (priv->time, 
                                            clutter_sine_inc_func);

  watch_links (app);
}

ClutterActor *
launcher_app_get_default (void)

{
  static ClutterActor *app = NULL;

  if (!app)
    app = g_object_new (LAUNCHER_TYPE_APP, 
                       NULL);

  return app;
}
