Logo Search packages:      
Sourcecode: galeon version File versions

bookmarks.c

/* -*- mode: c; c-style: k&r; c-basic-offset: 8 -*- */
/*
 *  Copyright (C) 2002  Ricardo Fernández Pascual
 *
 *  This program 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; either version 2, or (at your option)
 *  any later version.
 *
 *  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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "bookmarks.h"
#include "bookmarks-io.h"
#include "bookmarks-toolbar-widgets.h"
#include "bookmarks-icon-provider.h"
#include "bookmarks-util.h"
#include "bookmarks-iterator.h"
#include "galeon-auto-bookmarks-source.h"
#include "galeon-marshal.h"
#include "galeon-debug.h"
#include "pixbuf-cache.h"
#include "gul-general.h"
#include "gul-string.h"
#include <libgnomevfs/gnome-vfs-utils.h>
#include <string.h>
#include "galeon-autocompletion-source.h"
#include <time.h>

#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);

#define SEARCH_TIMEOUT 2000

struct _GbBookmarkCopyContext
{
      GHashTable *id_to_bookmark;
};

static void       gb_bookmark_set_autocompletion_source_init (GaleonAutocompletionSourceIface *iface);
static void       gb_bookmark_set_autocompletion_source_foreach 
                                          (GaleonAutocompletionSource *source,
                                           const gchar *basic_key,
                                           GaleonAutocompletionSourceForeachFunc func,
                                           gpointer data);
static void       gb_bookmark_set_autocompletion_source_set_basic_key
                                          (GaleonAutocompletionSource *source,
                                           const gchar *basic_key);
static void       gb_bookmark_set_emit_autocompletion_source_data_changed (GbBookmarkSet *gh);
static void       gb_bookmark_set_tree_changed  (GbBookmarkSet *set);
static void       gb_bookmark_set_set           (GbBookmark *b, GbBookmarkSet *set);
static GbBookmark *     gb_bookmark_copy_impl         (GbBookmark *b, GbBookmarkCopyContext *cc);
static void             gb_bookmark_copy_impl_do      (GbBookmark *b, GbBookmark *c, GbBookmarkCopyContext *cc);
static GbBookmark *     gb_site_copy_impl       (GbBookmark *b, GbBookmarkCopyContext *cc);
static void             gb_site_copy_impl_do          (GbSite *b, GbSite *c, GbBookmarkCopyContext *cc);
static GbBookmark *     gb_folder_copy_impl           (GbBookmark *b, GbBookmarkCopyContext *cc);
static void       gb_folder_copy_impl_do        (GbFolder *b, GbFolder *c, GbBookmarkCopyContext *cc, 
                                           gboolean children);
static GbBookmark *     gb_smart_site_copy_impl       (GbBookmark *b, GbBookmarkCopyContext *cc);
static void       gb_smart_site_copy_impl_do    (GbSmartSite *b, GbSmartSite *c, GbBookmarkCopyContext *cc);
static GbBookmark *     gb_alias_placeholder_copy_impl      (GbBookmark *b, GbBookmarkCopyContext *cc);
static void       gb_alias_placeholder_copy_impl_do (GbAliasPlaceholder *b, GbAliasPlaceholder *c, 
                                             GbBookmarkCopyContext *cc);
static GbBookmark *     gb_separator_copy_impl        (GbBookmark *b, GbBookmarkCopyContext *cc);
static GbBookmark *     gb_v_folder_copy_impl   (GbBookmark *b, GbBookmarkCopyContext *cc);
static void       gb_v_folder_copy_impl_do      (GbVFolder *b, GbVFolder *c, 
                                           GbBookmarkCopyContext *cc);
static GbBookmark *     gb_auto_folder_copy_impl      (GbBookmark *b, GbBookmarkCopyContext *cc);
static void       gb_auto_folder_copy_impl_do   (GbAutoFolder *b, GbAutoFolder *c, 
                                           GbBookmarkCopyContext *cc);
static void       gb_bookmark_set_set_impl      (GbBookmark *b, GbBookmarkSet *set);
static void       gb_site_set_set_impl          (GbBookmark *b, GbBookmarkSet *set);
static void       gb_folder_set_set_impl        (GbBookmark *b, GbBookmarkSet *set);
static void       gb_alias_placeholder_set_set_impl (GbBookmark *b, GbBookmarkSet *set);
static GbBookmark *     gb_bookmark_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap);
static void       gb_bookmark_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, 
                                            GbBookmark *alias);
static GbBookmark *     gb_site_alias_create_impl     (GbBookmark *b, GbAliasPlaceholder *ap);
static void       gb_site_alias_create_impl_do  (GbBookmark *b, GbAliasPlaceholder *ap, 
                                           GbSite *site);
static GbBookmark *     gb_folder_alias_create_impl   (GbBookmark *b, GbAliasPlaceholder *ap);
static void       gb_folder_alias_create_impl_do      (GbBookmark *b, GbAliasPlaceholder *ap, 
                                           GbFolder *alias);
static gboolean   gb_folder_is_autogenerated_impl     (GbFolder *f);
static gboolean   gb_v_folder_is_autogenerated_impl (GbFolder *f);
static GbBookmark *     gb_v_folder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap);
static void       gb_v_folder_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, 
                                               GbVFolder *alias);
static gboolean   gb_auto_folder_is_autogenerated_impl (GbFolder *f);
static GbBookmark *     gb_auto_folder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap);
static void       gb_auto_folder_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, 
                                               GbAutoFolder *alias);
static GbBookmark *     gb_smart_site_alias_create_impl     (GbBookmark *b, GbAliasPlaceholder *ap);
static GbBookmark *     gb_separator_alias_create_impl      (GbBookmark *b, GbAliasPlaceholder *ap);
static GbBookmark *     gb_alias_placeholder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap);
static void       gb_folder_emit_child_modified (GbFolder *f, GbBookmark *b);
static void       gb_folder_emit_child_removed  (GbFolder *f, GbBookmark *b, gint pos);
static void       gb_folder_emit_child_added    (GbFolder *f, GbBookmark *b, gint pos);
static void       gb_folder_emit_descendant_modified (GbFolder *f, GbBookmark *b);
static void       gb_folder_emit_descendant_added (GbFolder *f, GbFolder *p, GbBookmark *b,
                                           gint pos);
static void       gb_folder_emit_descendant_removed (GbFolder *f, GbFolder *p, GbBookmark *b,
                                             gint pos);
static void       gb_bookmark_unparent_internal (GbBookmark *b, gboolean emit_signals);


static void       gb_bookmark_set_finalize_impl (GObject *o);
static void       gb_bookmark_set_dispose_impl  (GObject *o);
static void       gb_bookmark_finalize_impl     (GObject *o);
static void       gb_site_finalize_impl         (GObject *o);
static void       gb_folder_finalize_impl       (GObject *o);
static void       gb_folder_dispose_impl        (GObject *o);
static void       gb_v_folder_finalize_impl     (GObject *o);
static void       gb_auto_folder_finalize_impl  (GObject *o);
static void       gb_smart_site_finalize_impl   (GObject *o);
static void       gb_alias_placeholder_finalize_impl (GObject *o);
static GbTbWidget *     gb_bookmark_create_toolbar_widget_impl (GbBookmark *b);
static GbTbWidget *     gb_folder_create_toolbar_widget_impl (GbBookmark *b);
static GbTbWidget *     gb_site_create_toolbar_widget_impl (GbBookmark *b);
static GbTbWidget *     gb_separator_create_toolbar_widget_impl (GbBookmark *b);
static GbTbWidget *     gb_smart_site_create_toolbar_widget_impl (GbBookmark *b);
static GbTbWidget *     gb_alias_placeholder_create_toolbar_widget_impl (GbBookmark *b);
static void       gb_bookmark_init_icons        (void);
static gboolean         gb_bookmark_set_set_auto_save_cb (gpointer data);
#define gb_bookmark_set_needs_saving(b) if (b && ((GbBookmark *) b)->set) \
                                    ((GbBookmark *) b)->set->needs_saving = TRUE;

static void       gb_bookmark_set_fix_galeon1_mess_recursive      (GbFolder *f);
static void             gb_bookmark_set_fix_galeon1_mess_item           (GbBookmark *b);
static char *           gb_bookmark_set_fix_galeon1_mess_string         (const gchar *s);
static gchar *          gb_smart_site_get_smarturl_only           (GbSmartSite *b);
static void       gb_smart_site_set_smarturl_full           (GbSmartSite *b, const gchar *url);
static gchar *          gb_smart_site_get_options           (GbSmartSite *b);
static void       gb_smart_site_set_options           (GbSmartSite *b, gchar *options);
static GbBookmark *     gb_folder_get_child_or_alias        (GbFolder *f, GbBookmark *c);
static void       gb_bookmark_set_refresh_autobookmarks     (GbBookmarkSet *set, gint delay);

#define BOOKMARKS_NUM_BACKUPS 5

#define AUTOBOOKMARKS_INITIAL_DELAY 3000
#define AUTOBOOKMARKS_UPDATE_DELAY (1 * 60 * 60 * 1000)

/**
 * Bookmark icons
 */

static GbIconProvider *icon_provider = NULL;

/* #define DEBUG_REF */
#ifdef DEBUG_REF
static int live_bookmarks = 0;
#endif

/**
 * Signals enums and ids
 */
enum GbBookmarkSetSignalsEnum {
      GB_BOOKMARK_SET_TOOLBAR,
      GB_BOOKMARK_SET_CONTEXT_MENU,
      GB_BOOKMARK_SET_LAST_SIGNAL
};
static gint GbBookmarkSetSignals[GB_BOOKMARK_SET_LAST_SIGNAL];

enum GbBookmarkSignalsEnum {
      GB_BOOKMARK_MODIFIED,
      GB_BOOKMARK_REPLACED,
      GB_BOOKMARK_LAST_SIGNAL
};
static gint GbBookmarkSignals[GB_BOOKMARK_LAST_SIGNAL];

enum GbSiteSignalsEnum {
      GB_SITE_URL_MODIFIED,
      GB_SITE_LAST_SIGNAL
};
static gint GbSiteSignals[GB_SITE_LAST_SIGNAL];

enum GbFolderSignalsEnum {
      GB_FOLDER_CHILD_MODIFIED,
      GB_FOLDER_CHILD_ADDED,
      GB_FOLDER_CHILD_REMOVED, 
      GB_FOLDER_CHILD_MOVED,
      GB_FOLDER_DESCENDANT_MODIFIED,
      GB_FOLDER_DESCENDANT_ADDED,
      GB_FOLDER_DESCENDANT_REMOVED,
      GB_FOLDER_LAST_SIGNAL
};
static gint GbFolderSignals[GB_FOLDER_LAST_SIGNAL];

enum GbSmartSiteSignalsEnum {
      GB_SMART_SITE_ENTRY_WIDTH_CHANGED,
      GB_SMART_SITE_VISIBILITY_CHANGED,
      GB_SMART_SITE_HISTORY_CHANGED,
      GB_SMART_SITE_LAST_SIGNAL
};
static gint GbSmartSiteSignals[GB_SMART_SITE_LAST_SIGNAL];

/**
 * Bookmark object
 */

G_DEFINE_TYPE (GbBookmark, gb_bookmark, G_TYPE_OBJECT);

static void
gb_bookmark_class_init (GbBookmarkClass *klass)
{
      klass->gb_bookmark_copy = gb_bookmark_copy_impl;
      klass->gb_bookmark_set_set = gb_bookmark_set_set_impl;
      klass->gb_bookmark_alias_create = gb_bookmark_alias_create_impl;
      klass->gb_bookmark_create_toolbar_widget = gb_bookmark_create_toolbar_widget_impl;
      G_OBJECT_CLASS (klass)->finalize = gb_bookmark_finalize_impl;

      GbBookmarkSignals[GB_BOOKMARK_MODIFIED] = g_signal_new (
            "modified", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbBookmarkClass, gb_bookmark_modified), 
            NULL, NULL, 
            galeon_marshal_VOID__VOID,
            G_TYPE_NONE, 0);
      GbBookmarkSignals[GB_BOOKMARK_REPLACED] = g_signal_new (
            "replaced", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbBookmarkClass, gb_bookmark_replaced), 
            NULL, NULL, 
            galeon_marshal_VOID__OBJECT,
            G_TYPE_NONE, 1, GB_TYPE_BOOKMARK);
      
      gb_bookmark_init_icons ();
}

static void
gb_bookmark_init_icons (void)
{
      if (icon_provider == NULL)
      {
            GbIconProvider *ip = gb_icon_provider_new ();
            gb_system_set_icon_provider (GB_ICON_PROVIDER (ip));
            g_object_unref (ip);
      }
}

void
gb_system_set_icon_provider (GbIconProvider *ip)
{
      if (icon_provider)
      {
            g_object_unref (icon_provider);
      }
      icon_provider = g_object_ref (ip);
}

GbIconProvider *
gb_system_get_icon_provider (void)
{
      if (!icon_provider)
      {
            gb_bookmark_init_icons ();
      }
      return icon_provider;
}

static void
gb_bookmark_finalize_impl (GObject *o)
{
      GbBookmark *b = (GbBookmark *) o;

#ifdef DEBUG_REF
      g_print ("live_bookmarks: %d- %-.20s\n", --live_bookmarks, b->name);
#endif
      
      g_assert (b->parent == NULL);

      if (!gb_bookmark_is_alias (b))
      {
            if (b->set && b->id && g_hash_table_lookup (b->set->id_to_bookmark, b->id) == b)
            {
                  g_hash_table_remove (b->set->id_to_bookmark, b->id);
                  if (b->alias)
                  {
                       g_assert (b->alias->id == b->id);
                       g_hash_table_insert (b->set->id_to_bookmark, b->alias->id, b->alias);
                  }
            }

            if (b->set)
            {
                  gb_bookmark_set_emit_autocompletion_source_data_changed (b->set);
            }

#ifdef NICK_HASHTABLE
            if (b->set && b->nick && g_hash_table_lookup (b->set->nick_to_bookmark, b->nick) == b)
            {
                  g_hash_table_remove (b->set->nick_to_bookmark, b->nick);
            }
#endif
            if (b->alias)
            {
                  b->alias->alias_of = NULL;
#ifdef NICK_HASHTABLE
                  if (b->alias->set)
                  {
                        g_hash_table_insert (b->alias->set->nick_to_bookmark, 
                                         b->alias->nick, b->alias);
                  }
#endif
            }
            else
            {
                  g_free (b->id);
                  g_free (b->name);
                  g_free (b->nick);
                  g_free (b->pixmap_file);
                  g_free (b->notes);
            }
      }
      else
      {
            b->alias_of->alias = b->alias;
            if (b->alias) 
            {
                  b->alias->alias_of = b->alias_of;
            }
      }
      
      G_OBJECT_CLASS (gb_bookmark_parent_class)->finalize (o);
}

static GbBookmarkCopyContext *
gb_bookmark_copy_context_new (void)
{
      GbBookmarkCopyContext *ret = g_new0 (GbBookmarkCopyContext, 1);
      ret->id_to_bookmark = g_hash_table_new (g_str_hash, g_str_equal);
      return ret;
}

static void
gb_bookmark_copy_context_free (GbBookmarkCopyContext *cc)
{
      if (cc)
      {
            g_hash_table_destroy (cc->id_to_bookmark);
            g_free (cc);
      }
}

static GbBookmark *
gb_bookmark_copy_internal (GbBookmark *b, GbBookmarkCopyContext *cc)
{
      GbBookmarkClass *klass = GB_BOOKMARK_GET_CLASS (b);
      return klass->gb_bookmark_copy (b, cc);
}

GbBookmark *
gb_bookmark_copy (GbBookmark *b)
{
      GbBookmarkCopyContext *cc = gb_bookmark_copy_context_new ();
      GbBookmark *ret = gb_bookmark_copy_internal (b, cc);
      gb_bookmark_copy_context_free (cc);
      return ret;
}

GbTbWidget *
gb_bookmark_create_toolbar_widget (GbBookmark *b)
{
      GbBookmarkClass *klass = GB_BOOKMARK_GET_CLASS (b);
      return klass->gb_bookmark_create_toolbar_widget (b);
}

static GbBookmark *
gb_bookmark_copy_impl (GbBookmark *b, GbBookmarkCopyContext *cc)
{
      g_warning ("Should not be reached!");
      return NULL;
}

static void 
gb_bookmark_copy_impl_do (GbBookmark *b, GbBookmark *c, GbBookmarkCopyContext *cc)
{
      if (!c->set)
      {
            c->set = b->set;
      }
      c->parent = NULL;
      c->next = NULL;
      c->prev = NULL;
      c->alias_of = NULL;
      c->alias = NULL;
      if (c->set != b->set)
      {
            g_free (c->id);
            c->id = g_strdup (b->id);
      }
      g_free (c->name);
      c->name = g_strdup (b->name);
      g_free (c->nick);
      c->nick = g_strdup (b->nick);
      g_free (c->pixmap_file);
      c->pixmap_file = g_strdup (b->pixmap_file);
      g_free (c->notes);
      c->notes = g_strdup (b->notes);
      c->add_to_context_menu = b->add_to_context_menu;
      c->time_added = b->time_added;
      c->time_modified = b->time_modified;

      if (b->id 
          && b->id[0] != '\0' 
          && g_hash_table_lookup (cc->id_to_bookmark, b->id) == NULL)
      {
            g_hash_table_insert (cc->id_to_bookmark, b->id, c);
      }
}

static GbTbWidget *
gb_bookmark_create_toolbar_widget_impl (GbBookmark *b)
{
      g_warning ("Should not be reached!");
      return NULL;
}

static GbBookmark *
gb_bookmark_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
      g_warning ("Should not be reached!");
      return NULL;
}

GbBookmark *
gb_bookmark_alias_create (GbBookmark *b, GbAliasPlaceholder *ap)
{
      GbBookmarkClass *klass = GB_BOOKMARK_GET_CLASS (b);
      return klass->gb_bookmark_alias_create (b, ap);
}

static void
gb_bookmark_ensure_has_id (GbBookmark *b)
{
      if (b->id == NULL || b->id[0] == '\0')
      {
            static gulong last_generated_id = 0;
            char id[32];
            do {
                  /* ID's have to begin with a Letter to be valid XML */
                  g_snprintf (id, sizeof(id), "id%lu", last_generated_id++);
                  if (!b->set 
                      || g_hash_table_lookup (b->set->id_to_bookmark, 
                                        id) == NULL)
                  {
                        break;
                  }
            } while (last_generated_id != 0);
            if (last_generated_id == 0)
            {
                  g_warning ("Could not find an unique id. You have a lot of bookmarks!");
            }
            gb_bookmark_set_id (b, id);
      }
}

const char *
gb_bookmark_get_id (GbBookmark *b)
{
        g_return_val_if_fail (GB_IS_BOOKMARK (b), NULL);

        gb_bookmark_ensure_has_id (b);

        return b->id;
}

static void
gb_bookmark_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, 
                          GbBookmark *alias)
{
      GbBookmark *alias_data = (GbBookmark *) ap;
      
      /* make sure the bookmark has an id */
      gb_bookmark_ensure_has_id (b);

      if (alias->set)
      {
            gb_bookmark_set_emit_autocompletion_source_data_changed (alias->set);
      }

      alias->set = b->set;
      alias->id = b->id;

      alias->alias_of = b;
      alias->alias = b->alias;
      if (b->alias)
      {
            b->alias->alias_of = alias;
      }
      b->alias = alias;

      g_free (alias->name);
      alias->name = b->name;
      
      g_free (alias->nick);
      alias->nick = b->nick;

      g_free (alias->pixmap_file);
      alias->pixmap_file = b->pixmap_file;

      g_free (alias->notes);
      alias->notes = b->notes;

      if (alias_data)
      {
            alias->add_to_context_menu = alias_data->add_to_context_menu;
      }
      else
      {
            alias->add_to_context_menu = b->add_to_context_menu;
      }

      alias->time_modified = b->time_modified;

      if (alias_data)
            alias->time_added = alias_data->time_added;
      else
            alias->time_added = b->time_added;
}


static void 
gb_bookmark_init (GbBookmark *b)
{
      b->parent = NULL;
      b->next = NULL;
      b->id = NULL;
      b->name = g_strdup ("");
      b->nick = g_strdup ("");
      b->notes = g_strdup ("");
      b->pixmap_file = g_strdup ("");
      b->set = NULL;
      b->xbel_node = NULL;
      
#ifdef DEBUG_REF
      g_print ("live_bookmarks: %d+\n", ++live_bookmarks);
#endif
}

static void
gb_bookmark_set_set (GbBookmark *b, GbBookmarkSet *set)
{
      GbBookmarkClass *klass = GB_BOOKMARK_GET_CLASS (b);
      klass->gb_bookmark_set_set (b, set);
}

static void
gb_bookmark_set_set_impl (GbBookmark *b, GbBookmarkSet *set)
{
      GbBookmarkSet *oldset;
      g_return_if_fail (GB_IS_BOOKMARK (b));
      g_return_if_fail (!set || GB_IS_BOOKMARK_SET (set));
      
      oldset = b->set;

      if (set 
        && gb_bookmark_is_alias (b)
          && gb_bookmark_real_bookmark (b)->set != set)
      {
           g_warning ("Setting the set of an alias to a differnt one than the real bookmark");
      }

      if (oldset && b->id && !gb_bookmark_is_alias (b))
      {
            g_hash_table_remove (oldset->id_to_bookmark, b->id);
            gb_bookmark_set_emit_autocompletion_source_data_changed (oldset);
      }

#ifdef NICK_HASHTABLE
      if (oldset && b->nick && !gb_bookmark_is_alias (b))
      {
            g_hash_table_remove (oldset->nick_to_bookmark, b->nick);
      }
#endif

      gb_bookmark_set_xbel_node (b, NULL);

      if (oldset && b->add_to_context_menu)
      {
            g_slist_free (oldset->context_bookmarks);
            oldset->context_bookmarks = NULL;
            g_signal_emit (oldset, GbBookmarkSetSignals[GB_BOOKMARK_SET_CONTEXT_MENU], 0);
      }

      b->set = set;

      if (set && b->add_to_context_menu)
      {
            g_slist_free (set->context_bookmarks);
            set->context_bookmarks = NULL;
            g_signal_emit (set, GbBookmarkSetSignals[GB_BOOKMARK_SET_CONTEXT_MENU], 0);
      }

      /* add to hashtables... */
      if (b->set && b->id && !gb_bookmark_is_alias (b))
      {
            /* FIXME: i should check that the id is unique... */
            g_hash_table_insert (set->id_to_bookmark, b->id, b);
            gb_bookmark_set_emit_autocompletion_source_data_changed (b->set);
      }

#ifdef NICK_HASHTABLE
      if (b->set && b->nick && !gb_bookmark_is_alias (b))
      {
            g_hash_table_insert (set->nick_to_bookmark, b->nick, b);
      }
#endif
}

void
gb_bookmark_set_id (GbBookmark *b, const gchar *val)
{
      gchar *newval;
      g_return_if_fail (GB_IS_BOOKMARK (b));
      
      b = gb_bookmark_real_bookmark (b);

      if (GB_IS_BOOKMARK_SET (b->set)) 
      {
            if (b->id && g_hash_table_lookup (b->set->id_to_bookmark, b->id) == b)
            {
                  g_hash_table_remove (b->set->id_to_bookmark, b->id);
            }
      }
      
      g_free (b->id);
      newval = val ? g_strdup (val) : NULL;
      b->id = newval;

      if (GB_IS_BOOKMARK_SET (b->set)) 
      {
            if (b->id)
            {
                  g_hash_table_insert (b->set->id_to_bookmark, b->id, b);
            }
      }

      gb_bookmark_set_needs_saving (b);

      do {
            b->id = newval;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((b = b->alias) != NULL);
}

void
gb_bookmark_set_xbel_node (GbBookmark *b, xmlNodePtr node)
{
      g_return_if_fail (GB_IS_BOOKMARK (b));
      g_return_if_fail (GB_IS_BOOKMARK_SET (b->set) || node == NULL);
      g_return_if_fail (node == NULL 
                    || node->doc == b->set->xbel_doc);

      b->xbel_node = node;
}

void
gb_bookmark_set_name (GbBookmark *b, const gchar *val)
{
      gchar *newval;

      g_return_if_fail (GB_IS_BOOKMARK (b));

      b = gb_bookmark_real_bookmark (b);

      g_free (b->name);
      newval = val ? g_strdup (val) : g_strdup ("");

      gb_bookmark_set_needs_saving (b);

      if (b->set)
      {
            gb_bookmark_set_emit_autocompletion_source_data_changed (b->set);
      }

      do {
            b->name = newval;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((b = b->alias) != NULL);
}

void
gb_bookmark_set_nick (GbBookmark *b, const gchar *val)
{
      gchar *newval;

      g_return_if_fail (GB_IS_BOOKMARK (b));

      b = gb_bookmark_real_bookmark (b);
      
#ifdef NICK_HASHTABLE
      if (GB_IS_BOOKMARK_SET (b->set)) 
      {
            if (b->nick && g_hash_table_lookup (b->set->nick_to_bookmark, b->nick) == b)
            {
                  g_hash_table_remove (b->set->nick_to_bookmark, b->nick);
            }
      }
#endif      
      g_free (b->nick);
      newval = val ? g_strdup (val) : g_strdup ("");

#ifdef NICK_HASHTABLE
      if (GB_IS_BOOKMARK_SET (b->set)) 
      {
            if (val)
            {
                  g_hash_table_insert (b->set->nick_to_bookmark, newval, b);
            }
      }
#endif
      gb_bookmark_set_needs_saving (b);

      do {
            b->nick = newval;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((b = b->alias) != NULL);


}

void
gb_bookmark_set_notes (GbBookmark *b, const gchar *val)
{
      gchar *newval;

      g_return_if_fail (GB_IS_BOOKMARK (b));

      b = gb_bookmark_real_bookmark (b);
      
      g_free (b->notes);
      newval = val ? g_strdup (val) : g_strdup ("");

      gb_bookmark_set_needs_saving (b);

      do {
            b->notes = newval;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((b = b->alias) != NULL);

}

void
gb_bookmark_set_pixmap (GbBookmark *b, const gchar *val)
{
      gchar *newval;

      g_return_if_fail (GB_IS_BOOKMARK (b));
      
      b = gb_bookmark_real_bookmark (b);
      
      g_free (b->pixmap_file);
      newval = val ? g_strdup (val) : g_strdup ("");

      gb_bookmark_set_needs_saving (b);

      do {
            b->pixmap_file = newval;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((b = b->alias) != NULL);

}

void
gb_bookmark_emit_changed (GbBookmark *b)
{
      b = gb_bookmark_real_bookmark (b);
      
      do {
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((b = b->alias) != NULL);
}

/**
 * Return the bookmark icon, maybe a favicon...
 */
GdkPixbuf *
gb_bookmark_get_icon (GbBookmark *b)
{
      return gb_icon_provider_get_icon (icon_provider, b);
}

GdkPixbuf *
gb_bookmark_get_image (GbBookmark *b)
{
      /* return the pixmap data, or null if no pixmap file found */
      if (b->pixmap_file && b->pixmap_file[0])
      {
            return gul_pixbuf_cache_get (b->pixmap_file);
      }
      else
      {
            return NULL;
      }
}


void
gb_bookmark_set_add_to_context_menu (GbBookmark *b, gboolean val)
{
      g_return_if_fail (GB_IS_BOOKMARK (b));

      val = !!val;

      if (b->add_to_context_menu != val)
      {
            b->add_to_context_menu = val;
            
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);

            if (b->set)
            {
                  g_slist_free (b->set->context_bookmarks);
                  b->set->context_bookmarks = NULL;
                  g_signal_emit (b->set, GbBookmarkSetSignals[GB_BOOKMARK_SET_CONTEXT_MENU], 0);
            }
            
            gb_bookmark_set_needs_saving (b);
      }
}

void
gb_bookmark_set_time_added (GbBookmark *b, GTime val)
{
      g_return_if_fail (GB_IS_BOOKMARK (b));
      
      b->time_added = val;

      g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
      gb_folder_emit_child_modified (b->parent, b);

      gb_bookmark_set_needs_saving (b);
}

static GTime 
gb_system_get_current_time (void)
{
      return time (NULL);
}

void
gb_bookmark_set_time_added_now (GbBookmark *b)
{
      gb_bookmark_set_time_added (b, gb_system_get_current_time ());
}

void
gb_bookmark_set_time_modified (GbBookmark *b, GTime val)
{
      g_return_if_fail (GB_IS_BOOKMARK (b));
      
      b = gb_bookmark_real_bookmark (b);

      gb_bookmark_set_needs_saving (b);
      
      do {
            b->time_modified = val;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((b = b->alias) != NULL);

}

void
gb_bookmark_set_time_modified_now (GbBookmark *b)
{
      gb_bookmark_set_time_modified (b, gb_system_get_current_time ());
}

void
gb_bookmark_unparent (GbBookmark *b)
{
      gb_bookmark_unparent_internal (b, TRUE);
}

static GbBookmark *
gb_bookmark_get_parented_alias (GbBookmark *b)
{
      GbBookmark *alias;
      g_return_val_if_fail (!gb_bookmark_is_alias (b), NULL);

      for (alias = b->alias; alias; alias = alias->alias)
      {
            if (alias->parent)
            {
                  return alias;
            }
      }
      return NULL;
}

static GbBookmark *
gb_bookmark_get_parented_alias_not_under (GbBookmark *b, GbFolder *f)
{
      GbBookmark *alias;
      g_return_val_if_fail (!gb_bookmark_is_alias (b), NULL);

      for (alias = b->alias; alias; alias = alias->alias)
      {
            if (alias->parent)
            {
                  if (!gb_folder_is_ancestor (f, alias))
                  {
                        return alias;
                  }
            }
      }
      return NULL;
}

static void
gb_folder_save_descendants (GbFolder *f, GbFolder *unsafe)
{
      GbBookmark *child;
      g_return_if_fail (!gb_bookmark_is_alias (f));

      LOG ("Trying to save descendants of %s not under %s", GB_BOOKMARK (f)->name, GB_BOOKMARK (unsafe)->name);
      
      for (child = f->child; child; child = child->next)
      {
            if (!gb_bookmark_is_alias (child) && gb_bookmark_has_alias (child))
            {
                  GbBookmark *safe_child = gb_bookmark_get_parented_alias_not_under (child, unsafe);
                  if (safe_child)
                  {
                        LOG ("Found a safe calias of %s under %s", 
                             child->name, GB_BOOKMARK (safe_child->parent)->name);
                        gb_bookmark_alias_make_real (safe_child);
                  }
                  else 
                  {
                        LOG ("No safe parent found for %s", child->name);
                        if (GB_IS_FOLDER (child))
                        {
                              gb_folder_save_descendants (GB_FOLDER (child), unsafe);
                        }
                  }
            }
            else if (!gb_bookmark_is_alias (child) && GB_IS_FOLDER (child))
            {
                  g_assert (!gb_bookmark_has_alias (child));
                  gb_folder_save_descendants (GB_FOLDER (child), unsafe);
            }
      }
}

void
gb_bookmark_unparent_safe (GbBookmark *b)
{
      if (!gb_bookmark_is_alias (b) && gb_bookmark_has_alias (b))
      {
            GbBookmark *parented_alias = NULL;
            if (GB_IS_FOLDER (b))
            {
                  parented_alias = gb_bookmark_get_parented_alias_not_under (b, GB_FOLDER (b));
            }
            else
            {
                  parented_alias = gb_bookmark_get_parented_alias (b);
            }

            if (parented_alias)
            {
                  LOG ("safe parent for unparented bm: %s", 
                       GB_BOOKMARK (parented_alias->parent)->name);

                  gb_bookmark_alias_make_real (parented_alias);
            }
            else
            {
                  LOG ("no safe parent found");
                  if (GB_IS_FOLDER (b))
                  {
                        gb_folder_save_descendants (GB_FOLDER (b), GB_FOLDER (b));
                  }
            }
      }

      g_assert (gb_bookmark_is_alias (b) 
              || !gb_bookmark_has_alias (b)
              || !b->alias->parent
              || (GB_IS_FOLDER (b) && gb_folder_is_ancestor (GB_FOLDER (b), b->alias)));

      gb_bookmark_unparent (b);
}

static void
gb_bookmark_unparent_internal (GbBookmark *b, gboolean emit_signals)
{
      g_return_if_fail (GB_IS_BOOKMARK (b));
      
      if (b->parent != NULL)
      {
            GbFolder *p = b->parent;
            GbFolder *pi;
            GbBookmark *first_child;
            gint pos;

            g_assert (GB_IS_FOLDER (p));
            g_assert (((GbBookmark *) p)->alias_of == NULL);
            
            gb_bookmark_set_needs_saving (p);
            if (GB_BOOKMARK (p)->set) gb_bookmark_set_tree_changed (GB_BOOKMARK (p)->set);
            
            /* it will be relinked when saving, if necessary */
            if (b->xbel_node)
            {
                  xmlUnlinkNode (b->xbel_node);
            }
            
            if (GB_IS_FOLDER (b))
            {
                  if (gb_folder_is_default_folder (GB_FOLDER (b)) 
                      && b->set && gb_folder_is_ancestor (b->set->root, b)
                      /* gb_bookmark_set_set_default_folder emits signals, we can't use
                         it always :( */
                      && emit_signals)
                  {
                        gb_bookmark_set_set_default_folder (b->set, b->set->root);
                  }
            }
            
            pos = gb_folder_get_child_index (p, b);

            first_child = p->child == b ? b->next : p->child;

            if (b->prev)
            {
                  b->prev->next = b->next;
            }

            if (b->next)
            {
                  b->next->prev = b->prev;
            }
            
            b->prev = NULL;
            b->next = NULL;
            b->parent = NULL;

            pi = p;
            do {
                  pi->child = first_child;
            } while ((pi = (GbFolder *) ((GbBookmark *) pi)->alias) != NULL);

            if (emit_signals)
            {
                  gb_folder_emit_child_removed (p, b, pos);
                  if (GB_BOOKMARK (p)->set)
                  {
                        gb_bookmark_set_emit_autocompletion_source_data_changed 
                              (GB_BOOKMARK (p)->set);
                  }
            }

            g_object_unref (G_OBJECT (b));
      }
}

GbBookmark *
gb_bookmark_real_bookmark (GbBookmark *b)
{
      while (b->alias_of)
      {
            b = b->alias_of;
      }
      return b;
}

static GbBookmark *
gb_bookmark_add_alias_under_internal (GbBookmark *b, GbFolder *newparent, gint position)
{
      GbBookmark *real = gb_bookmark_real_bookmark (b);
      if (real->parent || ((GbBookmark *) real->set->root) == real)
      {
            GbAliasPlaceholder *ap = gb_alias_placeholder_new (b->set, b->id);
            GbBookmark *new = gb_bookmark_alias_create (b, ap);
            gb_folder_add_child (newparent, new, position);
            g_object_unref (new);
            g_object_unref (ap);
            
            g_assert (GB_IS_BOOKMARK (new));

            return new;
      }
      else
      {
            gb_folder_add_child (newparent, real, position);
            return real;
      }
}

GbBookmark *
gb_bookmark_add_alias_under (GbBookmark *b, GbFolder *newparent)
{
      return gb_bookmark_add_alias_under_internal (b, newparent, -1);
}

GbBookmark *
gb_bookmark_ensure_alias_under (GbBookmark *b, GbFolder *newparent)
{
      GbBookmark *new = gb_folder_get_child_or_alias (newparent, b);
      if (!new)
      {
            new = gb_bookmark_add_alias_under (b, newparent);
      }
      return new;
}

GSList *
gb_bookmark_get_all_alias_parents (GbBookmark *b)
{
      GSList *ret = NULL;
      for (b = gb_bookmark_real_bookmark (b); b; b = b->alias)
      {
            if (b->parent)
            {
                  ret = g_slist_prepend (ret, b->parent);
            }
      }
      return ret;
}

/**
 * Makes an alias the real bookmark of its chain. To be called before
 * deleting the real bookmark.
 */
void
gb_bookmark_alias_make_real (GbBookmark *alias)
{
      GbBookmark *real;
      GSList *children_rev = NULL;
      GSList *li;
      GbBookmark *prevalias;
      GbBookmark *nextalias;
      xmlNodePtr alias_node;
      xmlNodePtr real_node;

      g_return_if_fail (gb_bookmark_is_alias (alias));
      
      real = gb_bookmark_real_bookmark (alias);

      alias_node = alias->xbel_node;
      if (alias_node)
      {
            xmlUnlinkNode (alias_node);
      }
      real_node = real->xbel_node;
      if (real_node)
      {
            xmlUnlinkNode (real_node);
      }

      if (GB_IS_FOLDER (real))
      {
            g_return_if_fail (!gb_folder_is_ancestor (GB_FOLDER (real), alias));

            children_rev = gb_folder_list_children_reversed (GB_FOLDER (real));
            for (li = children_rev; li; li = li->next)
            {
                  g_object_ref (li->data);
                  gb_bookmark_unparent (li->data);
            }
      }
      
      if (real->set && real->id && g_hash_table_lookup (real->set->id_to_bookmark, real->id) == real)
      {
            g_hash_table_remove (real->set->id_to_bookmark, real->id);
      }

#ifdef NICK_HASHTABLE
      if (real->set && real->nick && g_hash_table_lookup (real->set->nick_to_bookmark, real->nick) == real)
      {
            g_hash_table_remove (real->set->nick_to_bookmark, real->nick);
      }
#endif

      if (GB_IS_SITE (real) && real->set 
          && g_hash_table_lookup (real->set->url_to_bookmark, GB_SITE (real)->url) == real)
      {
            g_hash_table_remove (real->set->url_to_bookmark, GB_SITE (real)->url);
      }

      prevalias = alias->alias_of;
      nextalias = alias->alias;

      g_assert (prevalias->alias == alias);
      g_assert (!nextalias || nextalias->alias_of == alias);
      
      prevalias->alias = nextalias;
      if (nextalias)
      {
            nextalias->alias_of = prevalias;
      }
      
      alias->alias_of = NULL;
      alias->alias = real;
      g_assert (real->alias_of == NULL);
      real->alias_of = alias;

      if (alias->set && alias->id)
      {
            g_hash_table_insert (alias->set->id_to_bookmark, alias->id, alias);
      }

#ifdef NICK_HASHTABLE
      if (alias->set && alias->nick)
      {
            g_hash_table_insert (alias->set->nick_to_bookmark, alias->nick);
      }
#endif

      if (GB_IS_SITE (alias) && alias->set)
      {
            g_hash_table_insert (alias->set->url_to_bookmark, GB_SITE (alias)->url, alias);
      }

      if (GB_IS_FOLDER (alias))
      {
            for (li = children_rev; li; li = li->next)
            {
                  gb_folder_add_child (GB_FOLDER (alias), li->data, 0);
                  g_object_unref (li->data);
            }
      }
      g_slist_free (children_rev);

      gb_bookmark_set_xbel_node (alias, real_node);
      gb_bookmark_set_xbel_node (real, alias_node);

      gb_bookmark_set_needs_saving (alias);

      do {
            g_signal_emit (alias, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (alias->parent, alias);
      } while ((alias = alias->alias) != NULL);
}


void
gb_bookmark_replace (GbBookmark *b, GbBookmark *replacement)
{
      GSList *replaced = NULL;
      GSList *replacements = NULL;
      const GSList *li;
      const GSList *lj;
      b = gb_bookmark_real_bookmark (b);
      replacement = gb_bookmark_real_bookmark (replacement);

      while (b)
      {
            if (b->parent)
            {
                  int idx = gb_folder_get_child_index (b->parent, b);
                  replaced = g_slist_prepend (replaced, g_object_ref (b));
                  if (gb_bookmark_is_alias (b))
                  {
                        GbBookmark *r = gb_bookmark_add_alias_under_internal (replacement, b->parent, idx);
                        replacements = g_slist_prepend (replacements, g_object_ref (r));
                  }
                  else
                  {
                        gb_folder_add_child (b->parent, replacement, idx);
                        replacements = g_slist_prepend (replacements, replacement);
                  }
            }
            b = b->alias;
      }

      for (li = replaced, lj = replacements;
           li; 
           li = li->next, lj = lj->next)
      {
            GbBookmark *bi = li->data;
            g_assert (lj && GB_IS_BOOKMARK (lj->data));
            g_signal_emit (bi, GbBookmarkSignals[GB_BOOKMARK_REPLACED], 0, lj->data);
            gb_bookmark_unparent (bi);
            g_object_unref (bi);
            g_object_unref (lj->data);
      }
      g_slist_free (replaced);
      g_slist_free (replacements);
}

static gboolean
gb_bookmark_is_really_in_set (GbBookmark *b)
{
      while (b)
      {
            if (b == (GbBookmark *) b->set->root)
            {
                  return TRUE;
            }
            else
            {
                  b = (GbBookmark *) b->parent;
            }
      }
      return FALSE;
}

/**
 * Site object
 */ 

G_DEFINE_TYPE (GbSite, gb_site, GB_TYPE_BOOKMARK);

static void
gb_site_class_init (GbSiteClass *klass)
{
      klass->parent_class.gb_bookmark_copy = gb_site_copy_impl;
      klass->parent_class.gb_bookmark_set_set = gb_site_set_set_impl;
      klass->parent_class.gb_bookmark_alias_create = gb_site_alias_create_impl;
      klass->parent_class.gb_bookmark_create_toolbar_widget = gb_site_create_toolbar_widget_impl;
      G_OBJECT_CLASS (klass)->finalize = gb_site_finalize_impl;

      GbSiteSignals[GB_SITE_URL_MODIFIED] = g_signal_new (
            "url-modified", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbSiteClass, gb_site_url_modified), 
            NULL, NULL, 
            galeon_marshal_VOID__STRING,
            G_TYPE_NONE, 1, G_TYPE_STRING);
}

static void 
gb_site_init (GbSite *b)
{
      b->url = g_strdup ("");
}

static void
gb_site_finalize_impl (GObject *o)
{
      GbBookmark *b = (GbBookmark *) o;
      GbSite *s = (GbSite *) b;
      if (!gb_bookmark_is_alias (b))
      {
            if (b->set && g_hash_table_lookup (b->set->url_to_bookmark, s->url) == s)
            {
                  g_hash_table_remove (b->set->url_to_bookmark, s->url);
            }
            
            if (b->alias && b->alias->set)
            {
                  g_hash_table_insert (b->alias->set->url_to_bookmark, 
                                   GB_SITE (b->alias)->url, b->alias);
            }
            else if (!b->alias)
            {
                  g_free (s->url);
            }
      }

      G_OBJECT_CLASS (gb_site_parent_class)->finalize (o);
}

GbSite *
gb_site_new (GbBookmarkSet *set, const char *name, 
           const char *url)
{
      GbSite *b;
      if (name == NULL && url != NULL)
      {
            name = url;
      }

      b = g_object_new (GB_TYPE_SITE, NULL);
      gb_bookmark_set_name ((GbBookmark *) b, name);
      gb_site_set_url (b, url);
      gb_bookmark_set_set ((GbBookmark *) b, set);

      return b;
}

static void
gb_site_copy_impl_do (GbSite *b, GbSite *c, GbBookmarkCopyContext *cc)
{
      g_free (c->url);
      c->url = g_strdup (b->url);
      c->accel_key = b->accel_key;
      c->accel_mods = b->accel_mods;
      c->time_visited = b->time_visited;
}

GbBookmark *
gb_site_copy_impl (GbBookmark *b, GbBookmarkCopyContext *cc)
{
      GbBookmark *ret = g_object_new (GB_TYPE_SITE, NULL);
      gb_bookmark_copy_impl_do (b, ret, cc);
      gb_site_copy_impl_do (GB_SITE (b), GB_SITE (ret), cc);
      return ret;
}

static void
gb_site_set_set_impl (GbBookmark *b, GbBookmarkSet *set)
{
      GbSite *s = (GbSite *) b;
      GbBookmarkSet *oldset;

      g_return_if_fail (GB_IS_SITE (b));
      g_return_if_fail (!set || GB_IS_BOOKMARK_SET (set));

      oldset = b->set;

      if (oldset && s->url && !gb_bookmark_is_alias (b) && g_hash_table_lookup (oldset->url_to_bookmark, s->url) == s)
      {
            g_hash_table_remove (oldset->url_to_bookmark, s->url);
      }

      gb_bookmark_set_set_impl (b, set);

      if (b->set && s->url && !gb_bookmark_is_alias (b))
      {
            g_hash_table_insert (set->url_to_bookmark, s->url, s);
      }
}

static GbBookmark *
gb_site_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
      GbSite *alias;

      g_return_val_if_fail (GB_IS_SITE (b), NULL);
      g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

      alias = g_object_new (GB_TYPE_SITE, NULL);

      gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));
      gb_site_alias_create_impl_do (b, ap, alias);

      return (GbBookmark *) alias;
}

static void
gb_site_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, GbSite *alias)
{
      GbSite *s = (GbSite *) b;

      g_free (alias->url);
      alias->url = s->url;
      alias->accel_key = s->accel_key;
      alias->accel_mods = s->accel_mods;
      alias->time_visited = s->time_visited;
}

void
gb_site_set_url (GbSite *s, const char *val)
{
      gchar *newval;
      GbBookmark *b = (GbBookmark *) s;

      g_return_if_fail (GB_IS_SITE (s));

      b = gb_bookmark_real_bookmark (b);
      s = (GbSite *) b;

      if (b->set)
      {
            if (s->url && g_hash_table_lookup (b->set->url_to_bookmark, s->url) == s)
            {
                  g_hash_table_remove (b->set->url_to_bookmark, s->url);
            }
      }
      
      g_free (s->url);
      newval = val ? g_strdup (val) : g_strdup ("");

      if (b->set)
      {
            if (val)
            {
                  GbBookmark *old = g_hash_table_lookup (b->set->url_to_bookmark, newval);
                  if (!old || !old->parent)
                  {
                        g_hash_table_insert (b->set->url_to_bookmark, newval, s);
                  }
            }
      }

      gb_bookmark_set_needs_saving (b);

      if (b->set)
      {
            gb_bookmark_set_emit_autocompletion_source_data_changed (b->set);
      }

      do {
            s->url = newval;
            g_signal_emit (s, GbSiteSignals[GB_SITE_URL_MODIFIED], 0, s->url);
            g_signal_emit (s, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((s = (GbSite *) (b = b->alias)) != NULL);
}

void
gb_site_set_time_visited (GbSite *b, GTime val)
{
      g_return_if_fail (GB_IS_SITE (b));
      
      b = GB_SITE (gb_bookmark_real_bookmark ((GbBookmark *) b));

      gb_bookmark_set_needs_saving (b);

      do {
            b->time_visited = val;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (((GbBookmark *) b)->parent, (GbBookmark *) b);
      } while ((b = (GbSite *) ((GbBookmark *) b)->alias) != NULL);
}

void
gb_site_set_time_visited_now (GbSite *b)
{
      gb_site_set_time_visited (b, gb_system_get_current_time ());
}

void
gb_site_set_accel (GbSite *b, guint accel_key, guint accel_mods)
{
      g_return_if_fail (GB_IS_BOOKMARK (b));
      
      b = GB_SITE (gb_bookmark_real_bookmark ((GbBookmark *) b));

      gb_bookmark_set_needs_saving (b);

      do {
            b->accel_mods = accel_mods;
            b->accel_key = accel_key;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (((GbBookmark *)b)->parent, (GbBookmark *) b);
      } while ((b = (GbSite *) ((GbBookmark *) b)->alias) != NULL);
}

static GbTbWidget *
gb_site_create_toolbar_widget_impl (GbBookmark *b)
{
      g_return_val_if_fail (GB_IS_SITE (b), NULL);
      return gb_create_toolbar_widget_site (GB_SITE (b));
}

GbSmartSite *
gb_site_make_smart (GbSite *b, const gchar *smarturl)
{
      GbSmartSite *ret;
      g_return_val_if_fail (GB_IS_SITE (b), NULL);

      if (GB_IS_SMART_SITE (b))
      {
            return GB_SMART_SITE (b);
      }

      ret = gb_smart_site_new (GB_BOOKMARK (b)->set, GB_BOOKMARK (b)->name, b->url, smarturl);
      gb_bookmark_set_nick (GB_BOOKMARK (ret), GB_BOOKMARK (b)->nick);
      gb_bookmark_set_pixmap (GB_BOOKMARK (ret), GB_BOOKMARK (b)->pixmap_file);
      gb_bookmark_set_notes (GB_BOOKMARK (ret), GB_BOOKMARK (b)->notes);
      gb_bookmark_set_add_to_context_menu (GB_BOOKMARK (ret), GB_BOOKMARK (b)->add_to_context_menu);
      gb_bookmark_set_time_added (GB_BOOKMARK (ret), GB_BOOKMARK (b)->time_added);
      gb_bookmark_set_time_modified (GB_BOOKMARK (ret), GB_BOOKMARK (b)->time_modified);
      gb_site_set_time_visited (GB_SITE (ret), b->time_visited);
      gb_site_set_accel (GB_SITE (ret), b->accel_key, b->accel_mods);
      
      gb_bookmark_replace (GB_BOOKMARK (b), GB_BOOKMARK (ret));

      return ret;
}

GbSite *
gb_smart_site_make_dumb (GbSmartSite *b)
{
      GbSite *ret;
      g_return_val_if_fail (GB_IS_SMART_SITE (b), NULL);

      ret = gb_site_new (GB_BOOKMARK (b)->set, GB_BOOKMARK (b)->name, GB_SITE (b)->url);
      gb_bookmark_set_nick (GB_BOOKMARK (ret), GB_BOOKMARK (b)->nick);
      gb_bookmark_set_pixmap (GB_BOOKMARK (ret), GB_BOOKMARK (b)->pixmap_file);
      gb_bookmark_set_notes (GB_BOOKMARK (ret), GB_BOOKMARK (b)->notes);
      gb_bookmark_set_add_to_context_menu (GB_BOOKMARK (ret), GB_BOOKMARK (b)->add_to_context_menu);
      gb_bookmark_set_time_added (GB_BOOKMARK (ret), GB_BOOKMARK (b)->time_added);
      gb_bookmark_set_time_modified (GB_BOOKMARK (ret), GB_BOOKMARK (b)->time_modified);
      gb_site_set_time_visited (ret, GB_SITE (b)->time_visited);
      gb_site_set_accel (ret, GB_SITE (b)->accel_key, GB_SITE (b)->accel_mods);
      
      gb_bookmark_replace (GB_BOOKMARK (b), GB_BOOKMARK (ret));

      return ret;
}


/**
 * SmartSite object
 */

G_DEFINE_TYPE (GbSmartSite, gb_smart_site, GB_TYPE_SITE);

#define DEFAULT_SMART_SITE_ENTRY_SIZE 100

static void
gb_smart_site_class_init (GbSmartSiteClass *klass)
{
      klass->parent_class.parent_class.gb_bookmark_copy = gb_smart_site_copy_impl;
      klass->parent_class.parent_class.gb_bookmark_alias_create = gb_smart_site_alias_create_impl;
      klass->parent_class.parent_class.gb_bookmark_create_toolbar_widget = 
            gb_smart_site_create_toolbar_widget_impl;
      G_OBJECT_CLASS (klass)->finalize = gb_smart_site_finalize_impl;

      GbSmartSiteSignals[GB_SMART_SITE_ENTRY_WIDTH_CHANGED] = g_signal_new (
            "entry-width-changed", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbSmartSiteClass, gb_smart_site_entry_width_changed), 
            NULL, NULL, 
            galeon_marshal_VOID__INT_INT,
            G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
      GbSmartSiteSignals[GB_SMART_SITE_VISIBILITY_CHANGED] = g_signal_new (
            "visibility-changed", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbSmartSiteClass, gb_smart_site_visibility_changed), 
            NULL, NULL, 
            galeon_marshal_VOID__VOID,
            G_TYPE_NONE, 0);
      GbSmartSiteSignals[GB_SMART_SITE_HISTORY_CHANGED] = g_signal_new (
            "history-changed", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbSmartSiteClass, gb_smart_site_history_changed), 
            NULL, NULL, 
            galeon_marshal_VOID__VOID,
            G_TYPE_NONE, 0);
}

static GbBookmark *
gb_smart_site_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
      GbSmartSite *alias;
      GbSmartSite *s = (GbSmartSite *) b;

      g_return_val_if_fail (GB_IS_SMART_SITE (b), NULL);
      g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

      alias = g_object_new (GB_TYPE_SMART_SITE, NULL);

      gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));
      gb_site_alias_create_impl_do (b, ap, GB_SITE (alias));
      
      g_free (alias->smarturl);
      alias->smarturl = s->smarturl;

      return (GbBookmark *) alias;
}

GbBookmark *
gb_smart_site_copy_impl (GbBookmark *b, GbBookmarkCopyContext *cc)
{
      GbBookmark *ret = g_object_new (GB_TYPE_SMART_SITE, NULL);
      gb_bookmark_copy_impl_do (b, ret, cc);
      gb_site_copy_impl_do (GB_SITE (b), GB_SITE (ret), cc);
      gb_smart_site_copy_impl_do (GB_SMART_SITE (b), GB_SMART_SITE (ret), cc);
      return ret;
}

static void
gb_smart_site_copy_impl_do (GbSmartSite *b, GbSmartSite *c, GbBookmarkCopyContext *cc)
{
      int i, n;
      g_free (c->smarturl);
      c->smarturl = g_strdup (b->smarturl);
      c->folded = b->folded;
      n = gb_smart_site_get_num_fields (b);
      if (n > 0 && b->entries_sizes)
      {
            c->entries_sizes = g_new0 (int, n);
            for (i = 0; i < n; ++i)
            {
                  c->entries_sizes[i] = b->entries_sizes[i];
            }
      }
      else
      {
            c->entries_sizes = NULL;
      }
}

static void
gb_smart_site_init (GbSmartSite *b)
{
      b->smarturl = g_strdup ("");
      b->folded = TRUE;
      b->entries_sizes = NULL;
      b->history = NULL;
}

static void
gb_smart_site_finalize_impl (GObject *o)
{
      GbBookmark *b = (GbBookmark *) o;
      GbSmartSite *s = (GbSmartSite *) b;
      if (!gb_bookmark_is_alias (b) && !b->alias)
      {
            g_free (s->smarturl);
            g_slist_foreach (s->history, (GFunc) g_free, NULL);
            g_slist_free (s->history);
            g_free (s->entries_sizes);
      }
      G_OBJECT_CLASS (gb_smart_site_parent_class)->finalize (o);
}

GbSmartSite *
gb_smart_site_new (GbBookmarkSet *set, const char *name, 
               const char *url, const char *smarturl)
{
      GbSmartSite *b;
      
      b = g_object_new (GB_TYPE_SMART_SITE, NULL);
      gb_bookmark_set_name ((GbBookmark *) b, name);
      gb_site_set_url ((GbSite *) b, url);
      gb_smart_site_set_smarturl_full (b, smarturl);
      gb_bookmark_set_set ((GbBookmark *) b, set);
      return b;
}

GbSmartSite *
gb_smart_site_new_from_site (GbSite *site, const char *smarturl)
{
      NOT_IMPLEMENTED;
      return NULL;
}

static void
gb_smart_site_set_smarturl_full (GbSmartSite *s, const gchar *val)
{
      gchar *newval;
      GbBookmark *b = (GbBookmark *) s;

      g_return_if_fail (GB_IS_SMART_SITE (s));

      b = gb_bookmark_real_bookmark (b);
      s = (GbSmartSite *) b;

      g_free (s->smarturl);
      newval = val ? g_strdup (val) : g_strdup ("");

      gb_bookmark_set_needs_saving (b);

      do {
            s->smarturl = newval;
            g_signal_emit (s, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((s = (GbSmartSite *) (b = b->alias)) != NULL);
}

static GbTbWidget *
gb_smart_site_create_toolbar_widget_impl (GbBookmark *b)
{
      g_return_val_if_fail (GB_IS_SITE (b), NULL);
      return gb_create_toolbar_widget_smart_site (GB_SMART_SITE (b));
}

gint
gb_smart_site_get_num_fields (GbSmartSite *b)
{
      return 1;
}

gint
gb_smart_site_get_entry_size (GbSmartSite *b, gint index)
{
      if (b->entries_sizes)
      {
            return b->entries_sizes[index];
      }
      return DEFAULT_SMART_SITE_ENTRY_SIZE;
}

void
gb_smart_site_set_entry_size (GbSmartSite *b, gint index, gint width)
{
      gint num_entries = gb_smart_site_get_num_fields (b);
      if (index >= num_entries)
      {
            return;
      }
      if (!b->entries_sizes)
      {
            b->entries_sizes = g_new0 (int, num_entries);
      }
      b->entries_sizes[index] = width;
      g_signal_emit (b, GbSmartSiteSignals[GB_SMART_SITE_ENTRY_WIDTH_CHANGED], 0, index, width);
      g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
      gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));

      gb_bookmark_set_needs_saving (b);
}

void
gb_smart_site_set_folded (GbSmartSite *b, gboolean folded)
{
      if (b->folded != folded)
      {
            b->folded = folded;
            g_signal_emit (b, GbSmartSiteSignals[GB_SMART_SITE_VISIBILITY_CHANGED], 0);
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
            gb_bookmark_set_needs_saving (b);
      }
}

/*
 * Encodes a string for putting in a query string, ensures that spaces
 * are encoded as '+' rather than %20 to workaround servers that don't
 * like %20 in the query string
 */
static gchar *
encode_for_query_string (const char * string, const char* encoding)
{
      GString * encoded;
      gchar **split;
      int i;

      encoded = g_string_new ("");

      /* Split on spaces */
      split = g_strsplit (string, " ", -1);
      for (i = 0; split[i]; i++)
      {
            gchar *arg, *encarg;

            if (i) g_string_append_c (encoded, '+');

            arg = g_convert (split[i], strlen (split[i]),
                         encoding, "UTF-8", NULL, NULL, NULL);
            
            encarg = gnome_vfs_escape_string (arg ? arg : split[i]);
            g_string_append (encoded, encarg);
            
            g_free (encarg);
            g_free (arg);
      }
      g_strfreev (split);

      return g_string_free (encoded, FALSE);
}

/*
 * Encodes a string for putting in the path part of a url, this will escape
 * things that shouldn't be in the path, and will ensure that '?' is encoded
 * as well
 */
static gchar *
encode_for_path (const char * arg, const char* encoding)
{
      gchar * enc1, *enc2;

      enc2 = g_convert (arg, strlen (arg), 
                    encoding, "UTF-8", NULL, NULL, NULL);
            
      /* Escape everything but '/', '&', '=' and '?' */
      enc1 = gnome_vfs_escape_path_string (enc2 ? enc2 : arg);
      g_free (enc2);

      /* Escape '?' */
      enc2 = gnome_vfs_escape_set (enc1, "?");

      g_free (enc1);
      
      return enc2;
}

gchar *
gb_smart_site_subst_args (GbSmartSite *b, gchar **args)
{
      guint expected_args;
      guint i;
      GString *s;
      const gchar *t1, *t2;
      gchar *encoding;
      gchar *smarturl_only;
      const gchar *query_string_start;

      g_return_val_if_fail (GB_IS_SMART_SITE (b), NULL);
      g_return_val_if_fail (args != NULL, NULL);

      smarturl_only = gb_smart_site_get_smarturl_only (b);
      
      if (args[0] == NULL) 
      {
            return smarturl_only;
      }
      
      /* Work out where the start of the query string is */
      query_string_start = strchr (smarturl_only, '?');

      s = g_string_new ("");
      expected_args = gb_smart_site_get_num_fields (b);
      encoding = gb_smart_site_get_encoding (b);
      
      /* subst args */
      t1 = smarturl_only;
      for (i = 0; i < expected_args - 1; i++)
      {
            gchar *arg;

            t2 = strstr (t1, "%s");
            if (!t2) 
            {
                  break;
            }
            g_string_append_len (s, t1, t2 - t1);

            if (query_string_start && t2 > query_string_start)
            {
                  arg = encode_for_query_string (*args, encoding);
            }
            else
            {
                  arg = encode_for_path (*args, encoding);
            }

            g_string_append (s, arg);
            g_free (arg);

            args++;
            t1 = t2 + 2;

            /* repeat the last arg if there are not enough */
            if (*args == NULL)
            {
                  args--;
            }
      }

      /* there may (should) be still a %s, the last one. Put all the
         remaining args in it, separated with (url-encoded) spaces */
      t2 = strstr (t1, "%s");
      if (t2) 
      {
            g_string_append_len (s, t1, t2 - t1);
            while (*args != NULL)
            {
                  gchar *arg;
                  const gchar *sep;
                  if (query_string_start && t2 > query_string_start)
                  {
                        arg = encode_for_query_string (*args, encoding);
                        sep = "+";
                  }
                  else
                  {
                        arg = encode_for_path (*args, encoding);
                        sep = "%20";
                  }

                  g_string_append (s, arg);
                  g_free (arg);

                  args++;
                  if (*args != NULL)
                  {
                        g_string_append (s, sep);
                  }
            }
            t1 = t2 + 2;
      }
      g_string_append (s, t1);

      g_free (encoding);
      g_free (smarturl_only);

      return g_string_free (s, FALSE);
}

static gchar *
gb_smart_site_get_smarturl_only (GbSmartSite *b)
{
      const gchar *openbrace;
      const gchar *closebrace;
      const gchar *c;
      
      openbrace = strchr (b->smarturl, '{');
      if (!openbrace) return g_strdup (b->smarturl);
      for (c = b->smarturl; c < openbrace; ++c)
      {
            if (!strchr (" \t\n", *c)) return g_strdup (b->smarturl);
      }

      closebrace = strchr (openbrace + 1, '}');
      if (!closebrace) return g_strdup (b->smarturl);

      return g_strdup (closebrace + 1);
}

static gchar *
gb_smart_site_get_options (GbSmartSite *b)
{
      const gchar *openbrace;
      const gchar *closebrace;
      const gchar *c;
      
      openbrace = strchr (b->smarturl, '{');
      if (!openbrace) return g_strdup ("");
      for (c = b->smarturl; c < openbrace; ++c)
      {
            if (!strchr (" \t\n", *c)) return g_strdup ("");
      }

      closebrace = strchr (openbrace + 1, '}');
      if (!closebrace) return g_strdup ("");

      return g_strndup (openbrace + 1, closebrace - (openbrace + 1));
}

static void
gb_smart_site_set_options (GbSmartSite *b, gchar *options)
{
      const gchar *openbrace;
      const gchar *closebrace;
      const gchar *c;
      gchar *new_smarturl;
            
      openbrace = strchr (b->smarturl, '{');
      if (!openbrace) goto no_previous_options;
      for (c = b->smarturl; c < openbrace; ++c)
      {
            if (!strchr (" \t\n", *c)) goto no_previous_options;
      }

      closebrace = strchr (openbrace + 1, '}');
      if (!closebrace) goto no_previous_options;

      new_smarturl = g_strconcat ("{", options, "}", closebrace + 1, NULL);
      gb_smart_site_set_smarturl_full (b, new_smarturl);
      g_free (new_smarturl);
      return;

 no_previous_options:
      new_smarturl = g_strconcat ("{", options, "}", b->smarturl, NULL);
      gb_smart_site_set_smarturl_full (b, new_smarturl);
      g_free (new_smarturl);
}

gchar *
gb_smart_site_get_encoding (GbSmartSite *b)
{
      gchar *ret;
      gchar *options;

      g_return_val_if_fail (GB_IS_SMART_SITE (b), NULL);
      g_return_val_if_fail (b->smarturl, NULL);
      
      options = gb_smart_site_get_options (b);
      ret = gb_util_options_get (options, "encoding");
      g_free (options);
      
      if (!ret)
      {
            ret = g_strdup ("UTF-8");
      }
      return ret;
}

void
gb_smart_site_set_encoding (GbSmartSite *b, const gchar *encoding)
{
      gchar *newoptions;
      gchar *options;

      g_return_if_fail (GB_IS_SMART_SITE (b));
      g_return_if_fail (b->smarturl);
      
      options = gb_smart_site_get_options (b);
      newoptions = gb_util_options_set (options, "encoding", encoding);
      g_free (options);

      gb_smart_site_set_options (b, newoptions);
}

gchar *
gb_smart_site_get_smarturl (GbSmartSite *b)
{
      return gb_smart_site_get_smarturl_only (b);
}

void
gb_smart_site_set_smarturl (GbSmartSite *b, const gchar *url)
{
      gchar *options;
      gchar *new_smarturl;

      g_return_if_fail (GB_IS_SMART_SITE (b));
      options = gb_smart_site_get_options (b);

      new_smarturl = g_strconcat ("{", options, "}", url, NULL);
      gb_smart_site_set_smarturl_full (b, new_smarturl);
      g_free (new_smarturl);
      g_free (options);
}

GSList *
gb_smart_site_get_history (GbSmartSite *b, int entry_index)
{
      GSList *ret = NULL;
      GSList *li;

      g_return_val_if_fail (GB_IS_SMART_SITE (b), NULL);
      
      for (li = b->history; li; li = li->next)
      {
            ret = g_slist_prepend (ret, g_strdup (li->data));
      }

      return g_slist_reverse (ret);
}

void
gb_smart_site_set_history (GbSmartSite *s, int entry_index, const GSList *history)
{
      GSList *newval = NULL;
      const GSList *li;
      GbBookmark *b = (GbBookmark *) s;

      g_return_if_fail (GB_IS_SMART_SITE (s));

      b = gb_bookmark_real_bookmark (b);
      s = (GbSmartSite *) b;
      
      g_slist_foreach (s->history, (GFunc) g_free, NULL);
      g_slist_free (s->history);
      for (li = history; li; li = li->next)
      {
            newval = g_slist_prepend (newval, g_strdup (li->data));
      }
      newval = g_slist_reverse (newval);

      gb_bookmark_set_needs_saving (b);

      do {
            s->history = newval;
            g_signal_emit (s, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            g_signal_emit (s, GbSmartSiteSignals[GB_SMART_SITE_HISTORY_CHANGED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((s = (GbSmartSite *) (b = b->alias)) != NULL);
}

void
gb_smart_site_prepend_history (GbSmartSite *b, int entry_index, const gchar *item)
{
      GSList *newhistory = NULL;
      GSList *li;
      gint max_smart_site_history_items;
      int items = 0;

      g_return_if_fail (GB_IS_SMART_SITE (b));
      LOG ("in gb_smart_site_prepend_history");

      max_smart_site_history_items = (GB_BOOKMARK (b)->set) 
            ? GB_BOOKMARK (b)->set->max_smart_site_history_items
            : 15;
      
      for (li = b->history; li; li = li->next)
      {
            if (items < (max_smart_site_history_items - 1)
                && strcmp (li->data, item))
            {
                  newhistory = g_slist_prepend (newhistory, g_strdup (li->data));
                  items++;
            }
      }

      newhistory = g_slist_reverse (newhistory);
      newhistory = g_slist_prepend (newhistory, g_strdup (item));

      gb_smart_site_set_history (b, entry_index, newhistory);

      g_slist_foreach (newhistory, (GFunc) g_free, NULL);
      g_slist_free (newhistory);

      LOG ("%d history items", g_slist_length (b->history));
}

/**
 * Folder object
 */

G_DEFINE_TYPE (GbFolder, gb_folder, GB_TYPE_BOOKMARK)

static void
gb_folder_class_init (GbFolderClass *klass)
{
      klass->gb_folder_is_autogenerated = gb_folder_is_autogenerated_impl;
      klass->parent_class.gb_bookmark_copy = gb_folder_copy_impl;
      klass->parent_class.gb_bookmark_set_set = gb_folder_set_set_impl;
      klass->parent_class.gb_bookmark_alias_create = gb_folder_alias_create_impl;
      klass->parent_class.gb_bookmark_create_toolbar_widget = gb_folder_create_toolbar_widget_impl;
      G_OBJECT_CLASS (klass)->finalize = gb_folder_finalize_impl;
      G_OBJECT_CLASS (klass)->dispose = gb_folder_dispose_impl;

      GbFolderSignals[GB_FOLDER_CHILD_MODIFIED] = g_signal_new (
            "child-modified", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_descendant_modified), 
            NULL, NULL, 
            galeon_marshal_VOID__OBJECT,
            G_TYPE_NONE, 1, GB_TYPE_BOOKMARK);
      GbFolderSignals[GB_FOLDER_CHILD_ADDED] = g_signal_new (
            "child-added", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_child_added), 
            NULL, NULL, 
            galeon_marshal_VOID__OBJECT_INT,
            G_TYPE_NONE, 2, GB_TYPE_BOOKMARK, G_TYPE_INT);
      GbFolderSignals[GB_FOLDER_CHILD_REMOVED] = g_signal_new (
            "child-removed", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_child_removed), 
            NULL, NULL, 
            galeon_marshal_VOID__OBJECT_INT,
            G_TYPE_NONE, 2, GB_TYPE_BOOKMARK, G_TYPE_INT);
      GbFolderSignals[GB_FOLDER_DESCENDANT_MODIFIED] = g_signal_new (
            "descendant-modified", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_descendant_modified), 
            NULL, NULL, 
            galeon_marshal_VOID__OBJECT,
            G_TYPE_NONE, 1, GB_TYPE_BOOKMARK);
      GbFolderSignals[GB_FOLDER_DESCENDANT_ADDED] = g_signal_new (
            "descendant-added", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_descendant_added), 
            NULL, NULL, 
            galeon_marshal_VOID__OBJECT_OBJECT_INT,
            G_TYPE_NONE, 3, GB_TYPE_BOOKMARK, GB_TYPE_BOOKMARK, G_TYPE_INT);
      GbFolderSignals[GB_FOLDER_DESCENDANT_REMOVED] = g_signal_new (
            "descendant-removed", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_descendant_removed), 
            NULL, NULL, 
            galeon_marshal_VOID__OBJECT_OBJECT_INT,
            G_TYPE_NONE, 3, GB_TYPE_BOOKMARK, GB_TYPE_BOOKMARK, G_TYPE_INT);
}

static void 
gb_folder_init (GbFolder *b)
{
      b->child = NULL;
      b->create_toolbar = FALSE;
}

static void
gb_folder_set_set_impl (GbBookmark *b, GbBookmarkSet *set)
{
      GbFolder *f = (GbFolder *) b;
      GbBookmark *bi;
      GbBookmarkSet *oldset;

      g_return_if_fail (GB_IS_FOLDER (b));
      g_return_if_fail (!set || GB_IS_BOOKMARK_SET (set));

      oldset = b->set;
      
      if (oldset && f->create_toolbar)
      {
            oldset->toolbars = g_slist_remove (oldset->toolbars, f);
            g_signal_emit (oldset, GbBookmarkSetSignals[GB_BOOKMARK_SET_TOOLBAR], 0, b);
      }

      gb_bookmark_set_set_impl (b, set);

      if (set && f->create_toolbar)
      {
            set->toolbars = g_slist_prepend (set->toolbars, f);
            g_signal_emit (set, GbBookmarkSetSignals[GB_BOOKMARK_SET_TOOLBAR], 0, b);
      }

      /* set the set of the children too */
      if (!gb_bookmark_is_alias (b))
      {
            for (bi = f->child; bi; bi = bi->next)
            {
                  gb_bookmark_set_set (bi, set);
            }
      }
}

static GbBookmark *
gb_folder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
      GbFolder *alias;

      g_return_val_if_fail (GB_IS_FOLDER (b), NULL);
      g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

      alias = g_object_new (GB_TYPE_FOLDER, NULL);

      gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));
      gb_folder_alias_create_impl_do (b, ap, alias);

      return (GbBookmark *) alias;
}

static void
gb_folder_dispose_impl (GObject *o)
{
      GbBookmark *b = (GbBookmark *) o;
      GbFolder *f = (GbFolder *) b;

      LOG ("in gb_folder_dispose_impl");

      if (!gb_bookmark_is_alias (b))
      {
            /* when the real bookmark is destroyed, its children are unparented, even if 
               there was an alias of the parent. This is suboptimal. */
            while (f->child) 
            {
                  /* teorically, we can emit signals here. But I rather don't, 
                     because some objects might be already finalized */
                  gb_bookmark_unparent_internal (f->child, FALSE);
            }
      }
      G_OBJECT_CLASS (gb_folder_parent_class)->dispose (o);
}

static void
gb_folder_finalize_impl (GObject *o)
{
      LOG ("in gb_folder_finalize_impl");

      g_assert ((((GbFolder *) o)->child == NULL) || gb_bookmark_is_alias (o));
      
      G_OBJECT_CLASS (gb_folder_parent_class)->finalize (o);
}

static void
gb_folder_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, GbFolder *alias)
{
      GbFolder *f = (GbFolder *) b;
      if (ap)
            alias->create_toolbar = ap->create_toolbar;
      else
            alias->create_toolbar = f->create_toolbar;
      alias->child = f->child;
}

static void
gb_folder_copy_impl_do (GbFolder *b, GbFolder *c, GbBookmarkCopyContext *cc, gboolean children)
{
      GbBookmark *last_copied = NULL;
      GbBookmark *bi;
      c->create_toolbar = b->create_toolbar;
      c->expanded = b->expanded;
      c->child = NULL;

      if (children) for (bi = b->child; bi; bi = bi->next)
      {
            /* this is not correct, it should copy aliases... */
            if (!gb_bookmark_is_alias (bi) || !GB_IS_FOLDER (bi))
            {
                  GbBookmark *alias = NULL;
                  GbBookmark *ci = NULL;

                  if (bi->id && bi->id[0] != '\0')
                  {
                        alias = g_hash_table_lookup (cc->id_to_bookmark, bi->id);
                  }

                  if (alias)
                  {
                        GbAliasPlaceholder *ap;

                        LOG ("Aliases found while copying (%s).", bi->name);

                        /* they must be aliases */
                        g_assert (GB_IS_SITE (alias) == GB_IS_SITE (bi));
                        g_assert (GB_IS_FOLDER (alias) == GB_IS_FOLDER (bi));
                        g_assert (!strcmp (alias->name, bi->name));
                        g_assert (!GB_IS_SITE (alias) || !strcmp (GB_SITE (alias)->url, GB_SITE (bi)->url));

                        ap = gb_alias_placeholder_new (GB_BOOKMARK (b)->set, bi->id);
                        ci = gb_bookmark_alias_create (alias, ap);
                        g_object_unref (ap);
                  }
                  else
                  {
                        ci = gb_bookmark_copy_internal (bi, cc);
                  }

                  if (last_copied)
                  {
                        last_copied->next = ci;
                        ci->prev = last_copied;
                  }
                  else
                  {
                        c->child = ci;
                  }
                  ci->parent = GB_FOLDER (c);
                  last_copied = ci;
            }
      }
}

GbBookmark *
gb_folder_copy_impl (GbBookmark *b, GbBookmarkCopyContext *cc)
{
      GbBookmark *ret = g_object_new (GB_TYPE_FOLDER, NULL);
      gb_bookmark_copy_impl_do (b, ret, cc);
      gb_folder_copy_impl_do (GB_FOLDER (b), GB_FOLDER (ret), cc, TRUE);
      return ret;
}

GbFolder *
gb_folder_new (GbBookmarkSet *set, const gchar *name)
{
      GbFolder *b;

      b = g_object_new (GB_TYPE_FOLDER, NULL);
      gb_bookmark_set_name ((GbBookmark *) b, name);
      gb_bookmark_set_set ((GbBookmark *) b, set);

      if (!set->root)
      {
            gb_bookmark_set_set_root (set, b);
      }

      return b;
}

void
gb_folder_set_create_toolbar (GbFolder *b, gboolean val)
{
      g_return_if_fail (GB_IS_FOLDER (b));

      val = !!val;

      if (b->create_toolbar != val)
      {
            GbBookmarkSet *set = GB_BOOKMARK (b)->set;
            b->create_toolbar = val;
            
            if (val && set)
            {
                  set->toolbars = g_slist_prepend (set->toolbars, b);
            } 
            else if (set)
            {
                  set->toolbars = g_slist_remove (set->toolbars, b);
            }

            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (((GbBookmark *) b)->parent, (GbBookmark *) b);
            g_signal_emit (set, GbBookmarkSetSignals[GB_BOOKMARK_SET_TOOLBAR], 0, b);

            gb_bookmark_set_needs_saving (b);
      }
}

void
gb_folder_set_expanded (GbFolder *b, gboolean val)
{
      g_return_if_fail (GB_IS_FOLDER (b));

      b->expanded = !!val;

      g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
      gb_folder_emit_child_modified (((GbBookmark *) b)->parent, (GbBookmark *) b);
}

/*void
gb_folder_set_toolbar_style (BookmarkItem *b, GtkToolbarStyle val)
{
      NOT_IMPLEMENTED;
}
*/

void
gb_folder_add_child (GbFolder *p, GbBookmark *c, gint position)
{
      GbBookmark *cnext, *cprev;
      GbBookmark *first_child;
      gint i;
      GbFolder *pi;
      
      g_return_if_fail (GB_IS_FOLDER (p));
      g_return_if_fail (GB_IS_BOOKMARK (c));
      g_return_if_fail (position >= -1);
      g_return_if_fail (GB_BOOKMARK (p)->set);
      g_return_if_fail (GB_BOOKMARK (p) != c);

      if (gb_bookmark_is_alias (c)
          && gb_bookmark_real_bookmark (c)->parent == NULL 
          && gb_bookmark_real_bookmark (c) != GB_BOOKMARK (gb_bookmark_real_bookmark (c)->set->root))
      {
            g_warning ("Adding an alias whose real bookmark is unparented.");
      }
      
      p = GB_FOLDER (gb_bookmark_real_bookmark ((GbBookmark *) p));

      g_object_ref (G_OBJECT (c));

      gb_bookmark_unparent (c);

      g_assert (c->parent == NULL);
      g_assert (c->next == NULL);
      g_assert (c->prev == NULL);

      if (c->set != GB_BOOKMARK (p)->set)
      {
            gb_bookmark_set_set (c, GB_BOOKMARK (p)->set);
      }
      
      gb_bookmark_set_tree_changed (GB_BOOKMARK (p)->set);

      cprev = NULL;
      cnext = p->child;
      i = 0;
      while (i != position && cnext != NULL)
      {
            i++;
            cprev = cnext;
            cnext = cnext->next;
      }

      c->parent = p;
      if (cprev == NULL)
      {
            g_assert (cnext == p->child);
            p->child = c;
      }

      c->next = cnext;
      c->prev = cprev;
      if (cprev)
      {
            cprev->next = c;
      }
      if (cnext)
      {
            cnext->prev = c;
      }
      
      gb_bookmark_set_needs_saving (p);

      pi = p;
      first_child = p->child;
      do {
            pi->child = first_child;
      } while ((pi = (GbFolder *) ((GbBookmark *) pi)->alias) != NULL);

      gb_folder_emit_child_added (p, c, i);
      if (GB_BOOKMARK (p)->set)
      {
            gb_bookmark_set_emit_autocompletion_source_data_changed (GB_BOOKMARK (p)->set);
      }

      if (GB_IS_AUTO_FOLDER (c))
      {
            gb_bookmark_set_refresh_autobookmarks (c->set, AUTOBOOKMARKS_INITIAL_DELAY);
      }
}

gint
gb_folder_get_child_index (GbFolder *p, GbBookmark *b)
{
      gint index;
      GbBookmark *c;

      g_return_val_if_fail (GB_IS_FOLDER (p), -1);
      g_return_val_if_fail (GB_IS_BOOKMARK (b), -1);
      
      c = p->child;
      index = 0;
      while (c && c != b)
      {
            c = c->next;
            index++;
      }
      
      if (c == b)
      {
            g_assert (b->parent == (GbFolder *) gb_bookmark_real_bookmark ((GbBookmark *) p));
            return index;
      }
      else 
      {
            g_assert (b->parent != p);
            return -1;
      }
}

void
gb_folder_move_child (GbFolder *p, GbBookmark *c, gint position)
{
      gint old_position;

      g_return_if_fail (GB_IS_FOLDER (p));
      g_return_if_fail (GB_IS_BOOKMARK (c));
      g_return_if_fail (p == c->parent);

      old_position = gb_folder_get_child_index (p, c);

      if (old_position != position)
      {
            g_object_ref (c);
            gb_bookmark_unparent (c);
            gb_folder_add_child (p, c, position);
            g_object_unref (c);
            gb_bookmark_set_needs_saving (p);
      }
}

static void
gb_folder_emit_child_modified (GbFolder *f, GbBookmark *b)
{
      if (f)
      {
            GbFolder *fi;
            g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

            for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
            {
                  g_signal_emit (fi, GbFolderSignals[GB_FOLDER_CHILD_MODIFIED], 0, b);
            }
            gb_folder_emit_descendant_modified (f, b);
      }
}

static void
gb_folder_emit_child_added (GbFolder *f, GbBookmark *b, gint pos)
{
      if (f)
      {
            GbFolder *fi;
            g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

            for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
            {
                  g_signal_emit (fi, GbFolderSignals[GB_FOLDER_CHILD_ADDED], 0, b, pos);
            }
            gb_folder_emit_descendant_added (f, f, b, pos);
      }
}

static void
gb_folder_emit_child_removed (GbFolder *f, GbBookmark *b, gint pos)
{
      if (f)
      {
            GbFolder *fi;
            g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

            for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
            {
                  g_signal_emit (fi, GbFolderSignals[GB_FOLDER_CHILD_REMOVED], 0, b, pos);
            }
            gb_folder_emit_descendant_removed (f, f, b, pos);
      }
}

static void
gb_folder_emit_descendant_modified (GbFolder *f, GbBookmark *b)
{
      if (f)
      {
            GbFolder *fi;
            g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

            for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
            {
                  g_signal_emit (fi, GbFolderSignals[GB_FOLDER_DESCENDANT_MODIFIED], 0, b);
            }
            gb_folder_emit_descendant_modified (((GbBookmark *) f)->parent, b);
      }
}

static void
gb_folder_emit_descendant_added (GbFolder *f, GbFolder *p, GbBookmark *b, gint pos)
{
      if (f)
      {
            GbFolder *fi;
            g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

            for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
            {
                  g_signal_emit (fi, GbFolderSignals[GB_FOLDER_DESCENDANT_ADDED], 0, p, b, pos);
            }
            gb_folder_emit_descendant_added (((GbBookmark *) f)->parent, p, b, pos);
      }
}

static void
gb_folder_emit_descendant_removed (GbFolder *f, GbFolder *p, GbBookmark *b, gint pos)
{
      if (f)
      {
            GbFolder *fi;
            g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

            for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
            {
                  g_signal_emit (fi, GbFolderSignals[GB_FOLDER_DESCENDANT_REMOVED], 0, p, b, pos);
            }
            gb_folder_emit_descendant_removed (((GbBookmark *) f)->parent, p, b, pos);
      }
}

static GbTbWidget *
gb_folder_create_toolbar_widget_impl (GbBookmark *b)
{
      g_return_val_if_fail (GB_IS_FOLDER (b), NULL);
      return gb_create_toolbar_widget_folder (GB_FOLDER (b));
}

gboolean
gb_folder_is_ancestor (GbFolder *p, GbBookmark *b)
{
      if (p == b->parent)
      {
            return TRUE;
      }
      else if ((GbBookmark *) p == b || b->parent == NULL)
      {
            return FALSE;
      }
      else
      {
            return gb_folder_is_ancestor (p, (GbBookmark *) b->parent);
      }
}

gboolean
gb_folder_is_default_folder (GbFolder *f)
{
      GbFolder *rf = GB_FOLDER (gb_bookmark_real_bookmark (GB_BOOKMARK (f)));
      
      return (GB_BOOKMARK (rf)->set) && (rf == GB_BOOKMARK (rf)->set->default_folder);
}

GSList *
gb_folder_list_children_reversed (GbFolder *f)
{
      GSList *l = NULL;
      GbBookmark *bi;

      for (bi = f->child; bi; bi = bi->next)
      {
            l = g_slist_prepend (l, bi);
      }

      return l;
}

GSList *
gb_folder_list_children (GbFolder *f)
{
      return g_slist_reverse (gb_folder_list_children_reversed (f));
}

static gint 
gb_folder_sort_compare_func (gconstpointer a, gconstpointer b, gpointer user_data)
{
      GbBookmark *ba = GB_BOOKMARK (a);
      GbBookmark *bb = GB_BOOKMARK (b);
      gboolean folders_first = GPOINTER_TO_INT (user_data);
      if (folders_first)
      {
            if (GB_IS_FOLDER (ba) && !GB_IS_FOLDER (bb))
            {
                  return -1;
            } 
            else if (!GB_IS_FOLDER (ba) && GB_IS_FOLDER (bb))
            {
                  return 1;
            }
      }
      return g_utf8_collate (ba->name, bb->name);
}

void
gb_folder_sort (GbFolder *f, gboolean folders_first, gboolean recursive)
{
      GSList *l;
      GSList *li;
      gint i = 0;
      
      g_return_if_fail (GB_IS_FOLDER (f));

      l = gb_folder_list_children (f);
      l = g_slist_sort_with_data (l, gb_folder_sort_compare_func, GINT_TO_POINTER (folders_first));

      for (li = l; li; li = li->next)
      {
            gb_folder_move_child (f, li->data, i);
            ++i;
            if (recursive && !gb_bookmark_is_alias (li->data) && GB_IS_FOLDER (li->data))
            {
                  gb_folder_sort (li->data, folders_first, recursive);
            }
      }

      g_slist_free (l);
}

gboolean 
gb_folder_has_child_or_alias (GbFolder *f, GbBookmark *c)
{
      f = GB_FOLDER (gb_bookmark_real_bookmark (GB_BOOKMARK (f)));
      for (c = gb_bookmark_real_bookmark (c); c; c= c->alias)
      {
            if (c->parent == f)
            {
                  return TRUE;
            }
      }
      return FALSE;
}

static GbBookmark *
gb_folder_get_child_or_alias (GbFolder *f, GbBookmark *c)
{
      GbBookmark *i;
      f = GB_FOLDER (gb_bookmark_real_bookmark (GB_BOOKMARK (f)));
      c = gb_bookmark_real_bookmark (c);
      for (i = f->child; i; i = i->next)
      {
            if (gb_bookmark_real_bookmark (i) == c)
            {
                  return i;
            }
      }
      return NULL;
}

void
gb_folder_remove_child_or_aliases (GbFolder *f, GbBookmark *c)
{
      GbBookmark *v;
      g_object_ref (c);
      while ((v = gb_folder_get_child_or_alias (f, c)) != NULL)
      {
            gb_bookmark_unparent_safe (v);
      }
      g_object_unref (c);
}

static void
gb_folder_clear (GbFolder *f)
{
      g_return_if_fail (GB_IS_FOLDER (f));

      while (f->child)
      {
            gb_bookmark_unparent_safe (f->child);
      }
}

static gboolean
gb_folder_is_autogenerated_impl     (GbFolder *f)
{
      return FALSE;
}

gboolean
gb_folder_is_autogenerated (GbFolder *f)
{
      GbFolderClass *klass = GB_FOLDER_GET_CLASS (f);
      return klass->gb_folder_is_autogenerated (f);
}

/**
 * Separator object
 */

G_DEFINE_TYPE (GbSeparator, gb_separator, GB_TYPE_BOOKMARK)

GbSeparator *
gb_separator_new (GbBookmarkSet *set)
{
      GbSeparator *b = g_object_new (GB_TYPE_SEPARATOR, NULL);
      gb_bookmark_set_set ((GbBookmark *) b, set);
      return b;
}

static void 
gb_separator_init (GbSeparator *b)
{
}

static void
gb_separator_class_init (GbSeparatorClass *klass)
{
      klass->parent_class.gb_bookmark_copy = gb_separator_copy_impl;
      klass->parent_class.gb_bookmark_alias_create = gb_separator_alias_create_impl;
      klass->parent_class.gb_bookmark_create_toolbar_widget = gb_separator_create_toolbar_widget_impl;
}

static GbBookmark *
gb_separator_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
      GbSeparator *alias;

      g_return_val_if_fail (GB_IS_SEPARATOR (b), NULL);
      g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

      alias = g_object_new (GB_TYPE_SEPARATOR, NULL);

      gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));

      return (GbBookmark *) alias;
}

static GbBookmark *
gb_separator_copy_impl (GbBookmark *b, GbBookmarkCopyContext *cc)
{
      GbBookmark *ret = g_object_new (GB_TYPE_SEPARATOR, NULL);
      gb_bookmark_copy_impl_do (b, ret, cc);
      return ret;
}

static GbTbWidget *
gb_separator_create_toolbar_widget_impl (GbBookmark *b)
{
      g_return_val_if_fail (GB_IS_SEPARATOR (b), NULL);
      return gb_create_toolbar_widget_separator (GB_SEPARATOR (b));
}

/**
 * VFolder object
 */

G_DEFINE_TYPE (GbVFolder, gb_v_folder, GB_TYPE_FOLDER);

static void
gb_v_folder_class_init (GbVFolderClass *klass)
{
      klass->parent_class.gb_folder_is_autogenerated = gb_v_folder_is_autogenerated_impl;
      klass->parent_class.parent_class.gb_bookmark_copy = gb_v_folder_copy_impl;
      klass->parent_class.parent_class.gb_bookmark_alias_create = gb_v_folder_alias_create_impl;
      G_OBJECT_CLASS (klass)->finalize = gb_v_folder_finalize_impl;
}

static void 
gb_v_folder_init (GbVFolder *b)
{
}

GbVFolder *
gb_v_folder_new (GbBookmarkSet *set, const gchar *name)
{
      GbVFolder *b;

      b = g_object_new (GB_TYPE_V_FOLDER, NULL);
      gb_bookmark_set_name ((GbBookmark *) b, name);
      gb_bookmark_set_set ((GbBookmark *) b, set);
      gb_v_folder_set_search_text (b, "");
      gb_v_folder_set_include_sites (b, TRUE);
      gb_v_folder_set_look_in_name (b, TRUE);
      gb_v_folder_set_look_in_location (b, TRUE);
      gb_v_folder_set_look_in_notes (b, TRUE);
      gb_v_folder_set_match_type (b, GB_MATCH_TYPE_AND);
      gb_v_folder_set_rencently_visited (b, TRUE, 7);
      
      return b;
}

static void
gb_v_folder_finalize_impl (GObject *o)
{
      GbVFolder *vf = GB_V_FOLDER (o);
      LOG ("in gb_v_folder_finalize_impl");

      g_assert ((((GbFolder *) o)->child == NULL) || gb_bookmark_is_alias (o));
      
      if (!gb_bookmark_is_alias (vf) && !gb_bookmark_has_alias (vf))
      {
            g_free (vf->search_text);
            g_free (vf->search_casefold_text);
            g_strfreev (vf->search_text_split);
            g_strfreev (vf->search_casefold_text_split);
      }

      G_OBJECT_CLASS (gb_v_folder_parent_class)->finalize (o);
}

static GbBookmark *
gb_v_folder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
      GbVFolder *alias;

      g_return_val_if_fail (GB_IS_V_FOLDER (b), NULL);
      g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

      alias = g_object_new (GB_TYPE_V_FOLDER, NULL);

      gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));
      gb_folder_alias_create_impl_do (b, ap, GB_FOLDER (alias));
      gb_v_folder_alias_create_impl_do (b, ap, GB_V_FOLDER (alias));

      return (GbBookmark *) alias;
}

static void
gb_v_folder_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, 
                             GbVFolder *alias)
{
      GbVFolder *af = GB_V_FOLDER (b);
      alias->search_text = af->search_text;
      alias->search_casefold_text = af->search_casefold_text;
      alias->search_text_split = af->search_text_split;
      alias->search_casefold_text_split = af->search_casefold_text_split;
      alias->search_match_case = af->search_match_case;
      alias->search_include_folders = af->search_include_folders;
      alias->search_include_sites = af->search_include_sites;
      alias->search_look_in_name = af->search_look_in_name;
      alias->search_look_in_location = af->search_look_in_location;
      alias->search_look_in_notes = af->search_look_in_notes;
      alias->search_match_type = af->search_match_type;
      alias->search_recently_visited = af->search_recently_visited;
      alias->search_recently_visited_time = af->search_recently_visited_time;
      alias->search_recently_created = af->search_recently_created;
      alias->search_recently_created_time = af->search_recently_created_time;
      alias->dirty = af->dirty;
}

void
gb_v_folder_set_search_text (GbVFolder *b, const gchar *val)
{
      gchar *newval;
      gchar *newval_cf;
      gchar **newval_ts;
      gchar **newval_cf_ts;
      g_return_if_fail (GB_IS_V_FOLDER (b));

      b = GB_V_FOLDER (gb_bookmark_real_bookmark (GB_BOOKMARK (b)));

      g_free (b->search_text);
      g_strfreev (b->search_text_split);
      g_free (b->search_casefold_text);
      g_strfreev (b->search_casefold_text_split);
      
      newval = val ? g_strdup (val) : g_strdup ("");
      newval_cf = g_utf8_casefold (newval, -1);
      newval_ts = gul_strsplit_multiple_delimiters_with_quotes (newval, " ,", -1, NULL);
      newval_cf_ts = gul_strsplit_multiple_delimiters_with_quotes (newval_cf, " ,", -1, NULL);

      gb_bookmark_set_needs_saving (b);

      do {
            b->search_text = newval;
            b->search_casefold_text = newval_cf;
            b->search_text_split = newval_ts;
            b->search_casefold_text_split = newval_cf_ts;
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);

}

void
gb_v_folder_set_match_case (GbVFolder *b, gboolean v)
{
      g_return_if_fail (GB_IS_V_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      do {
            b->search_match_case = v;
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);
}

void
gb_v_folder_set_include_folders     (GbVFolder *b, gboolean v)
{
      g_return_if_fail (GB_IS_V_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      do {
            b->search_include_folders = v;
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);
}

void
gb_v_folder_set_include_sites (GbVFolder *b, gboolean v)
{
      g_return_if_fail (GB_IS_V_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      do {
            b->search_include_sites = v;
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);
}

void
gb_v_folder_set_look_in_name (GbVFolder *b, gboolean v)
{
      g_return_if_fail (GB_IS_V_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      do {
            b->search_look_in_name = v;
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);
}

void 
gb_v_folder_set_look_in_location (GbVFolder *b, gboolean v)
{
      g_return_if_fail (GB_IS_V_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      do {
            b->search_look_in_location = v;
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);
}

void
gb_v_folder_set_look_in_notes (GbVFolder *b, gboolean v)
{
      g_return_if_fail (GB_IS_V_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      do {
            b->search_look_in_notes = v;
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);
}

void
gb_v_folder_set_match_type (GbVFolder *b, GbMatchType v)
{
      g_return_if_fail (GB_IS_V_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      do {
            b->search_match_type = v;
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);
}

void
gb_v_folder_set_rencently_visited (GbVFolder *b, gboolean v, gint days)
{
      GTime tdays = days * 24 * 60 * 60;
      g_return_if_fail (GB_IS_V_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      do {
            b->search_recently_visited = v;
            b->search_recently_visited_time = tdays;
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);
}

void
gb_v_folder_set_rencently_created (GbVFolder *b, gboolean v, gint days)
{
      GTime tdays = days * 24 * 60 * 60;
      g_return_if_fail (GB_IS_V_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      do {
            b->search_recently_created = v;
            b->search_recently_created_time = tdays;
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);
}

const gchar *
gb_v_folder_get_search_text (GbVFolder *af)
{
      return af->search_text;
}

char *
gb_v_folder_get_search_options (GbVFolder *vf)
{

      GString *s = g_string_new ("");
      gchar *ret;
      
      if (vf->search_match_case)
      {
            g_string_append (s, "match_case,");
      }
      if (vf->search_include_folders)
      {
            g_string_append (s, "include_folders,");
      }
      if (vf->search_include_sites)
      {
            g_string_append (s, "include_sites,");
      }
      if (vf->search_look_in_name)
      {
            g_string_append (s, "look_in_name,");
      }
      if (vf->search_look_in_location)
      {
            g_string_append (s, "look_in_location,");
      }
      if (vf->search_look_in_notes)
      {
            g_string_append (s, "look_in_notes,");
      }

      switch (vf->search_match_type)
      {
      case GB_MATCH_TYPE_AND:
            g_string_append (s, "match_type_and");
            break;
      case GB_MATCH_TYPE_OR:
            g_string_append (s, "match_type_or");
            break;
      case GB_MATCH_TYPE_EXACT:
            g_string_append (s, "match_type_exact");
            break;
      }

      ret = s->str;
      g_string_free (s, FALSE);
      return ret;
}

static gboolean 
gb_options_search (gchar **opts, const gchar *opt)
{
      int i;
      for (i = 0; opts[i]; ++i)
      {
            if (!strcmp (opts[i], opt))
            {
                  return TRUE;
            }
      }
      return FALSE;
}

void
gb_v_folder_set_search_options (GbVFolder *vf, const gchar *options)
{
      gchar **opts = gul_strsplit_multiple_delimiters_with_quotes (options, " ,", -1, NULL);
      vf->search_match_case = gb_options_search (opts, "match_case");
      vf->search_include_folders = gb_options_search (opts, "include_folders");
      vf->search_include_sites = gb_options_search (opts, "include_sites");
      vf->search_look_in_name = gb_options_search (opts, "look_in_name");
      vf->search_look_in_location = gb_options_search (opts, "look_in_location");
      vf->search_look_in_notes = gb_options_search (opts, "look_in_notes");
      if (gb_options_search (opts, "match_type_and"))
      {
            vf->search_match_type = GB_MATCH_TYPE_AND;
      }
      else if (gb_options_search (opts, "match_type_or"))
      {
            vf->search_match_type = GB_MATCH_TYPE_OR;
      }
      else if (gb_options_search (opts, "match_type_exact"))
      {
            vf->search_match_type = GB_MATCH_TYPE_EXACT;
      }
      g_strfreev (opts);
}

gboolean
gb_v_folder_get_match_case (GbVFolder *af)
{
      return af->search_match_case;
}

gboolean
gb_v_folder_get_include_folders (GbVFolder *af)
{
      return af->search_include_folders;
}

gboolean
gb_v_folder_get_include_sites (GbVFolder *af)
{
      return af->search_include_sites;
}

gboolean
gb_v_folder_get_look_in_name (GbVFolder *af)
{
      return af->search_look_in_name;
}

gboolean
gb_v_folder_get_look_in_location (GbVFolder *af)
{
      return af->search_look_in_location;
}

gboolean
gb_v_folder_get_look_in_notes (GbVFolder *af)
{
      return af->search_look_in_notes;
}

GbMatchType
gb_v_folder_get_match_type (GbVFolder *af)
{
      return af->search_match_type;
}

gboolean
gb_v_folder_get_rencently_visited (GbVFolder *af, gint *days)
{
      if (days)
      {
            *days = af->search_recently_visited_time / (24 * 60 * 60);
      }
      return af->search_recently_visited;
}

gboolean
gb_v_folder_get_rencently_created (GbVFolder *af, gint *days)
{
      if (days)
      {
            *days = af->search_recently_created_time / (24 * 60 * 60);
      }
      return af->search_recently_created;
}

void
gb_v_folder_set_dirty (GbVFolder *b)
{
      g_return_if_fail (GB_IS_V_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      do {
            b->dirty = TRUE;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_V_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);
}

static gint 
gb_bookmarks_sort_bookmark_by_name_desc (gconstpointer a, gconstpointer b)
{
      const GbBookmark *ba = a;
      const GbBookmark *bb = b;
      return g_utf8_collate (bb->name, ba->name);
}

static gboolean
gb_v_folder_filter_search_in_text (const gchar *text, GbVFolder *vf)
{
      const gchar *whole = vf->search_match_case ? vf->search_text : vf->search_casefold_text;
      gchar **words = vf->search_match_case ? vf->search_text_split : vf->search_casefold_text_split;
      gchar *str = vf->search_match_case ? (gchar *) text : g_utf8_casefold (text, -1);
      int i;
      gboolean ret = FALSE;
      
      switch (vf->search_match_type)
      {
      case GB_MATCH_TYPE_AND:
            ret = TRUE;
            for (i = 0; words[i]; ++i)
            {
                  ret = ret && strstr (str, words[i]);
            }
            break;
      case GB_MATCH_TYPE_OR:
            ret = FALSE;
            for (i = 0; words[i]; ++i)
            {
                  ret = ret || strstr (str, words[i]);
            }
            break;
      case GB_MATCH_TYPE_EXACT:
            ret = strstr (str, whole) != NULL;
            break;
      }

      if (!vf->search_match_case) 
      {
            g_free (str);
      }
      return ret;
}

static GTime current_time; /* avoid a function call for each filtered bookmark */

static gboolean
gb_v_folder_filter_func (GbBookmark *b, GbVFolder *vf)
{
#define gb_v_folder_filter_func_search(x) \
      if (gb_v_folder_filter_search_in_text ((x), vf)) \
      { \
            return TRUE; \
      } \

      if (GB_IS_FOLDER (b) && !vf->search_include_folders)
      {
            return FALSE;
      }
      
      if (vf->search_recently_created)
      {
            if (gb_system_get_current_time () - b->time_added > vf->search_recently_created_time)
            {
                  return FALSE;
            }
      }

      if (GB_IS_SITE (b))
      {
            if (!vf->search_include_sites)
            {
                  return FALSE;
            }

            if (vf->search_recently_visited)
            {
                  if (current_time - GB_SITE (b)->time_visited > vf->search_recently_visited_time)
                  {
                        return FALSE;
                  }
            }

            if (vf->search_look_in_location)
            {
                  gb_v_folder_filter_func_search (GB_SITE (b)->url);

                  if (GB_IS_SMART_SITE (b))
                  {
                        gb_v_folder_filter_func_search (GB_SMART_SITE (b)->smarturl);
                  }
            }
      }
      else
      {
            if (GB_IS_SEPARATOR (b))
            {
                  return FALSE;
            }
      }

      if (vf->search_look_in_name)
      {
            gb_v_folder_filter_func_search (b->name);
      }

      if (vf->search_look_in_notes && b->notes[0])
      {
            gb_v_folder_filter_func_search (b->notes);
      }

#undef gb_v_folder_filter_func_search

      return FALSE;
}

void
gb_v_folder_refresh (GbVFolder *af)
{
      g_return_if_fail (GB_IS_V_FOLDER (af));
      START_PROFILER ("Search folder refresh");
      if (af->dirty)
      {
            GbFolder *f = GB_FOLDER (af);
            const GSList *l;
            const GSList *li;
            GSList *filtered;

            START_PROFILER ("sfr clear");
            gb_folder_clear (f);
            STOP_PROFILER ("sfr clear");
            
            if ((!af->search_text || af->search_text[0] == '\0')
                && !af->search_recently_visited 
                && !af->search_recently_created)
            {
                  STOP_PROFILER ("Search folder refresh");
                  return;
            }

            l  = gb_bookmark_set_get_all_noalias (GB_BOOKMARK (af)->set);

            START_PROFILER ("sfr filter+sort");
            current_time = gb_system_get_current_time ();
            filtered = gul_slist_filter (l, (GulFilterFunc) gb_v_folder_filter_func, af);
            filtered = g_slist_sort (filtered, gb_bookmarks_sort_bookmark_by_name_desc);
            STOP_PROFILER ("sfr filter+sort");

            START_PROFILER ("sfr add");
            for (li = filtered; li; li = li->next)
            {
                  gb_bookmark_add_alias_under_internal (li->data, f, 0);
            }
            STOP_PROFILER ("sfr add");

            g_slist_free (filtered);
      }
      STOP_PROFILER ("Search folder refresh");
}

static GbBookmark *
gb_v_folder_copy_impl (GbBookmark *b, GbBookmarkCopyContext *cc)
{
      GbBookmark *ret = g_object_new (GB_TYPE_V_FOLDER, NULL);
      gb_bookmark_copy_impl_do (b, ret, cc);
      gb_folder_copy_impl_do (GB_FOLDER (b), GB_FOLDER (ret), cc, FALSE);
      gb_v_folder_copy_impl_do (GB_V_FOLDER (b), GB_V_FOLDER (ret), cc);
      return ret;
}

static void
gb_v_folder_copy_impl_do (GbVFolder *b, GbVFolder *c, GbBookmarkCopyContext *cc)
{
      g_free (c->search_text);
      c->search_text = g_strdup (b->search_text);
      c->search_casefold_text = g_strdup (b->search_casefold_text);
      c->search_text_split = gul_strsplit_multiple_delimiters_with_quotes (c->search_text, " ,", -1, NULL);
      c->search_casefold_text_split = gul_strsplit_multiple_delimiters_with_quotes (c->search_casefold_text, " ,", -1, NULL);
      c->search_match_case = b->search_match_case;
      c->search_include_folders = b->search_include_folders;
      c->search_include_sites = b->search_include_sites;
      c->search_look_in_name = b->search_look_in_name;
      c->search_look_in_location = b->search_look_in_location;
      c->search_look_in_notes = b->search_look_in_notes;
      c->search_match_type = b->search_match_type;
      c->search_recently_visited = b->search_recently_visited;
      c->search_recently_visited_time = b->search_recently_visited_time;
      c->search_recently_created = b->search_recently_created;
      c->search_recently_created_time = b->search_recently_created_time;
      c->dirty = TRUE;
}

static gboolean
gb_v_folder_is_autogenerated_impl (GbFolder *f)
{
      return TRUE;
}

/**
 * AutoFolder object
 */

G_DEFINE_TYPE (GbAutoFolder, gb_auto_folder, GB_TYPE_FOLDER);

static void
gb_auto_folder_class_init (GbAutoFolderClass *klass)
{
      klass->parent_class.gb_folder_is_autogenerated = gb_auto_folder_is_autogenerated_impl;
      klass->parent_class.parent_class.gb_bookmark_copy = gb_auto_folder_copy_impl;
      klass->parent_class.parent_class.gb_bookmark_alias_create = gb_auto_folder_alias_create_impl;
      G_OBJECT_CLASS (klass)->finalize = gb_auto_folder_finalize_impl;
}

static void 
gb_auto_folder_init (GbAutoFolder *b)
{
}

GbAutoFolder *
gb_auto_folder_new (GbBookmarkSet *set, const gchar *name)
{
      GbAutoFolder *b;

      b = g_object_new (GB_TYPE_AUTO_FOLDER, NULL);
      gb_bookmark_set_name ((GbBookmark *) b, name);
      gb_bookmark_set_set ((GbBookmark *) b, set);

      gb_auto_folder_set_search_text (b, "");
      gb_auto_folder_set_look_in_title (b, TRUE);
      gb_auto_folder_set_look_in_url (b, TRUE);
      gb_auto_folder_set_match_type (b, GB_MATCH_TYPE_AND);
      gb_auto_folder_set_group_by_host (b, TRUE);
      gb_auto_folder_set_scoring_method (b, GALEON_AUTO_BOOKMARKS_SCORING_BOTH);
      
      b->max_matches = 20;
      
      return b;
}

static void
gb_auto_folder_finalize_impl (GObject *o)
{
      GbAutoFolder *vf = GB_AUTO_FOLDER (o);
      LOG ("in gb_auto_folder_finalize_impl");

      g_assert ((((GbFolder *) o)->child == NULL) || gb_bookmark_is_alias (o));
      
      if (!gb_bookmark_is_alias (vf) && !gb_bookmark_has_alias (vf))
      {
            g_free (vf->search_text);
            g_free (vf->search_casefold_text);
            g_strfreev (vf->search_text_split);
            g_strfreev (vf->search_casefold_text_split);
      }

      G_OBJECT_CLASS (gb_auto_folder_parent_class)->finalize (o);
}

static GbBookmark *
gb_auto_folder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
      GbAutoFolder *alias;

      g_return_val_if_fail (GB_IS_AUTO_FOLDER (b), NULL);
      g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

      alias = g_object_new (GB_TYPE_AUTO_FOLDER, NULL);

      gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));
      gb_folder_alias_create_impl_do (b, ap, GB_FOLDER (alias));
      gb_auto_folder_alias_create_impl_do (b, ap, GB_AUTO_FOLDER (alias));

      return (GbBookmark *) alias;
}

static void
gb_auto_folder_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, 
                             GbAutoFolder *alias)
{
      GbAutoFolder *af = GB_AUTO_FOLDER (b);
      alias->search_text = af->search_text;
      alias->search_casefold_text = af->search_casefold_text;
      alias->search_text_split = af->search_text_split;
      alias->search_casefold_text_split = af->search_casefold_text_split;
      alias->search_match_case = af->search_match_case;
      alias->search_look_in_title = af->search_look_in_title;
      alias->search_look_in_url = af->search_look_in_url;
      alias->search_match_type = af->search_match_type;
      alias->group_by_host = af->group_by_host;
      alias->scoring = af->scoring;
      alias->max_matches = af->max_matches;
}

typedef struct
{
      GbAutoFolder *folder;
      int count;
      GSList *list;
} GbAutoFolderRefreshData;

static gboolean
gb_auto_folder_refresh_iterator (const char *title, const char *url, gpointer data)
{
      GbAutoFolderRefreshData *c = data;
      GbSite *s = gb_site_new (GB_BOOKMARK (c->folder)->set, title && title[0] != '\0' ? title : url, url);
      c->list = g_slist_prepend (c->list, s);
      ++c->count;

      return c->count < c->folder->max_matches;
}

static gboolean
gb_auto_folder_refresh_search_in_text (const gchar *text, GbAutoFolder *vf)
{
      const gchar *whole = vf->search_match_case ? vf->search_text : vf->search_casefold_text;
      gchar **words = vf->search_match_case ? vf->search_text_split : vf->search_casefold_text_split;
      gchar *str = vf->search_match_case ? (gchar *) text : g_utf8_casefold (text, -1);
      int i;
      gboolean ret = FALSE;
      
      switch (vf->search_match_type)
      {
      case GB_MATCH_TYPE_AND:
            ret = TRUE;
            for (i = 0; words[i]; ++i)
            {
                  ret = ret && strstr (str, words[i]);
            }
            break;
      case GB_MATCH_TYPE_OR:
            ret = FALSE;
            for (i = 0; words[i]; ++i)
            {
                  ret = ret || strstr (str, words[i]);
            }
            break;
      case GB_MATCH_TYPE_EXACT:
            ret = strstr (str, whole) != NULL;
            break;
      }

      if (!vf->search_match_case) 
      {
            g_free (str);
      }
      return ret;
}

static gboolean
gb_auto_folder_refresh_filter (const char *title, const char *url, gpointer data)
{
      GbAutoFolder *af = data;
      GbBookmark *existing = g_hash_table_lookup (GB_BOOKMARK (af)->set->url_to_bookmark, url);
      if (!existing || !gb_bookmark_is_really_in_set (existing))
      {
#define gb_auto_folder_refresh_filter_search(x) \
            if (gb_auto_folder_refresh_search_in_text ((x), af)) \
            { \
                  return TRUE; \
            } \

            if (af->search_text[0] == '\0')
            {
                  return TRUE;
            }

            if (af->search_look_in_url)
            {
                  gb_auto_folder_refresh_filter_search (url);
            }

            if (af->search_look_in_title)
            {
                  gb_auto_folder_refresh_filter_search (title);
            }

#undef gb_v_folder_filter_func_search
            return FALSE;
      }
      else
      {
            return FALSE;
      }
}

static gint 
gb_bookmark_compare_reversed_func (gconstpointer a, gconstpointer b)
{
      const GbBookmark *ba = a;
      const GbBookmark *bb = b;
      return g_utf8_collate (bb->name, ba->name);
}

void
gb_auto_folder_refresh (GbAutoFolder *af)
{
      GbAutoFolderRefreshData closure;
      const GSList *li;

      g_return_if_fail (GB_IS_AUTO_FOLDER (af));

      gb_folder_clear (GB_FOLDER (af));

      if (!GB_BOOKMARK (af)->set->autobookmarks_source)
      {
            return;
      }
      
      closure.folder = af;
      closure.count = 0;
      closure.list = NULL;

      galeon_auto_bookmarks_source_get_autobookmarks (GB_BOOKMARK (af)->set->autobookmarks_source,
                                          gb_auto_folder_refresh_iterator, &closure, 
                                          gb_auto_folder_refresh_filter, af,
                                          af->scoring, af->group_by_host);

      closure.list = g_slist_sort (closure.list, gb_bookmark_compare_reversed_func);

      for (li = closure.list; li; li = li->next)
      {
            gb_folder_add_child (GB_FOLDER (af), GB_BOOKMARK (li->data), 0);
            g_object_unref (li->data);
      }

      g_slist_free (closure.list);
}

void
gb_auto_folder_set_search_text (GbAutoFolder *b, const gchar *val)
{
      GbBookmarkSet *set;
      gchar *newval;
      gchar *newval_cf;
      gchar **newval_ts;
      gchar **newval_cf_ts;
      g_return_if_fail (GB_IS_AUTO_FOLDER (b));

      b = GB_AUTO_FOLDER (gb_bookmark_real_bookmark (GB_BOOKMARK (b)));
      set = GB_BOOKMARK (b)->set;

      g_free (b->search_text);
      g_strfreev (b->search_text_split);
      g_free (b->search_casefold_text);
      g_strfreev (b->search_casefold_text_split);
      
      newval = val ? g_strdup (val) : g_strdup ("");
      newval_cf = g_utf8_casefold (newval, -1);
      newval_ts = gul_strsplit_multiple_delimiters_with_quotes (newval, " ,", -1, NULL);
      newval_cf_ts = gul_strsplit_multiple_delimiters_with_quotes (newval_cf, " ,", -1, NULL);

      gb_bookmark_set_needs_saving (b);

      do {
            b->search_text = newval;
            b->search_casefold_text = newval_cf;
            b->search_text_split = newval_ts;
            b->search_casefold_text_split = newval_cf_ts;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_AUTO_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);

      gb_bookmark_set_refresh_autobookmarks (set, AUTOBOOKMARKS_INITIAL_DELAY);
}

void
gb_auto_folder_set_match_case (GbAutoFolder *b, gboolean v)
{
      GbBookmarkSet *set;
      g_return_if_fail (GB_IS_AUTO_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      set = GB_BOOKMARK (b)->set;

      do {
            b->search_match_case = v;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_AUTO_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);

      gb_bookmark_set_refresh_autobookmarks (set, AUTOBOOKMARKS_INITIAL_DELAY);
}

void
gb_auto_folder_set_look_in_title (GbAutoFolder *b, gboolean v)
{
      GbBookmarkSet *set;
      g_return_if_fail (GB_IS_AUTO_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      set = GB_BOOKMARK (b)->set;

      do {
            b->search_look_in_title = v;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_AUTO_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);

      gb_bookmark_set_refresh_autobookmarks (set, AUTOBOOKMARKS_INITIAL_DELAY);
}

void 
gb_auto_folder_set_look_in_url (GbAutoFolder *b, gboolean v)
{
      GbBookmarkSet *set;
      g_return_if_fail (GB_IS_AUTO_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      set = GB_BOOKMARK (b)->set;

      do {
            b->search_look_in_url = v;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_AUTO_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);

      gb_bookmark_set_refresh_autobookmarks (set, AUTOBOOKMARKS_INITIAL_DELAY);
}

void
gb_auto_folder_set_match_type (GbAutoFolder *b, GbMatchType v)
{
      GbBookmarkSet *set;
      g_return_if_fail (GB_IS_AUTO_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      set = GB_BOOKMARK (b)->set;

      do {
            b->search_match_type = v;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_AUTO_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);

      gb_bookmark_set_refresh_autobookmarks (set, AUTOBOOKMARKS_INITIAL_DELAY);
}

void
gb_auto_folder_set_group_by_host (GbAutoFolder *b, gboolean v)
{
      GbBookmarkSet *set;
      g_return_if_fail (GB_IS_AUTO_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      set = GB_BOOKMARK (b)->set;

      do {
            b->group_by_host = v;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_AUTO_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);

      gb_bookmark_set_refresh_autobookmarks (set, AUTOBOOKMARKS_INITIAL_DELAY);
}

void
gb_auto_folder_set_scoring_method (GbAutoFolder *b, 
                           GaleonAutoBookmarksScoringMethod scoring)
{
      GbBookmarkSet *set;
      g_return_if_fail (GB_IS_AUTO_FOLDER (b));
      gb_bookmark_set_needs_saving (b);

      set = GB_BOOKMARK (b)->set;

      do {
            b->scoring = scoring;
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
      } while ((b = GB_AUTO_FOLDER (GB_BOOKMARK (b)->alias)) != NULL);

      gb_bookmark_set_refresh_autobookmarks (set, AUTOBOOKMARKS_INITIAL_DELAY);
}


const gchar *
gb_auto_folder_get_search_text (GbAutoFolder *af)
{
      return af->search_text;
}

gchar *
gb_auto_folder_get_options (GbAutoFolder *af)
{
      GString *s = g_string_new ("");
      gchar *ret;
      
      if (af->search_match_case)
      {
            g_string_append (s, "match_case,");
      }
      if (af->search_look_in_url)
      {
            g_string_append (s, "look_in_url,");
      }
      if (af->search_look_in_title)
      {
            g_string_append (s, "look_in_title,");
      }
      if (af->group_by_host)
      {
            g_string_append (s, "group_by_host,");
      }
      switch (af->scoring)
      {
      case GALEON_AUTO_BOOKMARKS_SCORING_RECENTLY_VISITED:
            g_string_append (s, "scoring_recently_visited");
            break;
      case GALEON_AUTO_BOOKMARKS_SCORING_FRECUENTLY_VISITED:
            g_string_append (s, "scoring_frecuently_visited");
            break;
      case GALEON_AUTO_BOOKMARKS_SCORING_BOTH:
            g_string_append (s, "scoring_both");
            break;
      }
      
      g_string_append_printf (s, "max_matches=%d,", af->max_matches);

      switch (af->search_match_type)
      {
      case GB_MATCH_TYPE_AND:
            g_string_append (s, "match_type_and");
            break;
      case GB_MATCH_TYPE_OR:
            g_string_append (s, "match_type_or");
            break;
      case GB_MATCH_TYPE_EXACT:
            g_string_append (s, "match_type_exact");
            break;
      }

      ret = s->str;
      g_string_free (s, FALSE);
      return ret;
}

void
gb_auto_folder_set_options (GbAutoFolder *vf, const gchar *options)
{
      gchar **opts = gul_strsplit_multiple_delimiters_with_quotes (options, " ,", -1, NULL);
      vf->search_match_case = gb_options_search (opts, "match_case");
      vf->search_look_in_title = gb_options_search (opts, "look_in_title");
      vf->search_look_in_url = gb_options_search (opts, "look_in_url");
      vf->group_by_host = gb_options_search (opts, "group_by_host");
      if (gb_options_search (opts, "match_type_and"))
      {
            vf->search_match_type = GB_MATCH_TYPE_AND;
      }
      else if (gb_options_search (opts, "match_type_or"))
      {
            vf->search_match_type = GB_MATCH_TYPE_OR;
      }
      else if (gb_options_search (opts, "match_type_exact"))
      {
            vf->search_match_type = GB_MATCH_TYPE_EXACT;
      }

      if (gb_options_search (opts, "scoring_recently_visited"))
      {
            vf->scoring = GALEON_AUTO_BOOKMARKS_SCORING_RECENTLY_VISITED;
      }
      else if (gb_options_search (opts, "scoring_frecuently_visited"))
      {
            vf->scoring = GALEON_AUTO_BOOKMARKS_SCORING_FRECUENTLY_VISITED;
      }
      else if (gb_options_search (opts, "scoring_both"))
      {
            vf->scoring = GALEON_AUTO_BOOKMARKS_SCORING_BOTH;
      }

      // TODO: max matches

      g_strfreev (opts);
}

gboolean
gb_auto_folder_get_match_case (GbAutoFolder *af)
{
      return af->search_match_case;
}

gboolean
gb_auto_folder_get_look_in_title (GbAutoFolder *af)
{
      return af->search_look_in_title;
}

gboolean
gb_auto_folder_get_look_in_url (GbAutoFolder *af)
{
      return af->search_look_in_url;
}

GbMatchType
gb_auto_folder_get_match_type (GbAutoFolder *af)
{
      return af->search_match_type;
}

gboolean
gb_auto_folder_get_group_by_host (GbAutoFolder *af)
{
      return af->group_by_host;
}

GaleonAutoBookmarksScoringMethod 
gb_auto_folder_get_scoring_method (GbAutoFolder *af)
{
      return af->scoring;
}

static GbBookmark *
gb_auto_folder_copy_impl (GbBookmark *b, GbBookmarkCopyContext *cc)
{
      GbBookmark *ret = g_object_new (GB_TYPE_AUTO_FOLDER, NULL);
      gb_bookmark_copy_impl_do (b, ret, cc);
      gb_folder_copy_impl_do (GB_FOLDER (b), GB_FOLDER (ret), cc, FALSE);
      gb_auto_folder_copy_impl_do (GB_AUTO_FOLDER (b), GB_AUTO_FOLDER (ret), cc);
      return ret;
}

static void
gb_auto_folder_copy_impl_do (GbAutoFolder *b, GbAutoFolder *c, GbBookmarkCopyContext *cc)
{
      g_free (c->search_text);
      c->search_text = g_strdup (b->search_text);
      c->search_casefold_text = g_strdup (b->search_casefold_text);
      c->search_text_split = gul_strsplit_multiple_delimiters_with_quotes (c->search_text, " ,", -1, NULL);
      c->search_casefold_text_split = gul_strsplit_multiple_delimiters_with_quotes (c->search_casefold_text, " ,", -1, NULL);
      c->search_match_case = b->search_match_case;
      c->search_look_in_title = b->search_look_in_title;
      c->search_look_in_url = b->search_look_in_url;
      c->search_match_type = b->search_match_type;
      c->scoring = b->scoring;
      c->max_matches = b->max_matches;
}

static gboolean
gb_auto_folder_is_autogenerated_impl (GbFolder *f)
{
      return TRUE;
}

/**
 * AliasPlaceholder object
 */

G_DEFINE_TYPE (GbAliasPlaceholder, gb_alias_placeholder, GB_TYPE_BOOKMARK);

GbAliasPlaceholder *
gb_alias_placeholder_new (GbBookmarkSet *set, const char *alias_of_id)
{
      GbAliasPlaceholder *b;
      b = g_object_new (GB_TYPE_ALIAS_PLACEHOLDER, NULL);
      gb_bookmark_set_set (GB_BOOKMARK (b), set);
      b->alias_of_id = g_strdup (alias_of_id);
      return b;
}

static void
gb_alias_placeholder_class_init (GbAliasPlaceholderClass *klass)
{
      klass->parent_class.gb_bookmark_copy = gb_alias_placeholder_copy_impl;
      klass->parent_class.gb_bookmark_set_set = gb_alias_placeholder_set_set_impl;
      klass->parent_class.gb_bookmark_alias_create = gb_alias_placeholder_alias_create_impl;
      klass->parent_class.gb_bookmark_create_toolbar_widget = gb_alias_placeholder_create_toolbar_widget_impl;
      G_OBJECT_CLASS (klass)->finalize = gb_alias_placeholder_finalize_impl;
}

GbBookmark *
gb_alias_placeholder_copy_impl (GbBookmark *b, GbBookmarkCopyContext *cc)
{
      GbBookmark *ret = g_object_new (GB_TYPE_ALIAS_PLACEHOLDER, NULL);
      gb_bookmark_copy_impl_do (b, ret, cc);
      gb_alias_placeholder_copy_impl_do (GB_ALIAS_PLACEHOLDER (b),
                                 GB_ALIAS_PLACEHOLDER (ret), 
                                 cc);
      return ret;
}

static void
gb_alias_placeholder_copy_impl_do (GbAliasPlaceholder *b, GbAliasPlaceholder *c, GbBookmarkCopyContext *cc)
{
      c->alias_of_id = g_strdup (b->alias_of_id);
      c->create_toolbar = b->create_toolbar;
}

static GbTbWidget *
gb_alias_placeholder_create_toolbar_widget_impl (GbBookmark *b)
{
      g_return_val_if_fail (GB_IS_ALIAS_PLACEHOLDER (b), NULL);
      return gb_create_toolbar_widget_alias_placeholder (GB_ALIAS_PLACEHOLDER (b));
}

static void
gb_alias_placeholder_set_set_impl (GbBookmark *b, GbBookmarkSet *set)
{
      GbAliasPlaceholder *ap = (GbAliasPlaceholder *) b;
      GbBookmarkSet *oldset;

      g_return_if_fail (GB_IS_ALIAS_PLACEHOLDER (ap));
      g_return_if_fail (!set || GB_IS_BOOKMARK_SET (set));

      oldset = b->set;
      if (oldset)
      {
            oldset->unresolved_aliases = g_slist_remove (oldset->unresolved_aliases, b);
      }

      gb_bookmark_set_set_impl ((GbBookmark *) b, set);

      if (set)
      {
            set->unresolved_aliases = g_slist_prepend (set->unresolved_aliases, b);
      }
}

static void
gb_alias_placeholder_finalize_impl (GObject *o)
{
      GbBookmark *b = (GbBookmark *) o;
      
      if (b->set)
      {
            b->set->unresolved_aliases = g_slist_remove (b->set->unresolved_aliases, b);
      }

        if (GB_ALIAS_PLACEHOLDER(b)->alias_of_id)
      {
            g_free (GB_ALIAS_PLACEHOLDER(b)->alias_of_id);
      }


      G_OBJECT_CLASS (gb_alias_placeholder_parent_class)->finalize (o);
}

static void 
gb_alias_placeholder_init (GbAliasPlaceholder *b)
{
}

static GbBookmark *
gb_alias_placeholder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
      GbSite *alias;

      g_return_val_if_fail (GB_IS_ALIAS_PLACEHOLDER (b), NULL);
      g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

      alias = g_object_new (GB_TYPE_ALIAS_PLACEHOLDER, NULL);

      gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));

      return (GbBookmark *) alias;
}

gboolean
gb_alias_placeholder_resolve (GbAliasPlaceholder *ap)
{
      GbBookmark *real;
      GbBookmark *alias;
      GbBookmarkSet *set;
      gint pos;

      g_return_val_if_fail (GB_IS_ALIAS_PLACEHOLDER (ap), FALSE);
      set = GB_BOOKMARK (ap)->set;
      g_return_val_if_fail (GB_IS_BOOKMARK_SET (set), FALSE);
      g_return_val_if_fail (GB_BOOKMARK (ap)->parent, FALSE);

      if (ap->alias_of_id)
      {
            real = g_hash_table_lookup (set->id_to_bookmark, ap->alias_of_id);
            if (!real) return FALSE;
      }
      else
      {
            return FALSE;
      }

      alias = gb_bookmark_alias_create (real, ap);
      if (!GB_IS_BOOKMARK (alias)) return FALSE;

      pos = gb_folder_get_child_index (((GbBookmark *) ap)->parent, (GbBookmark *) ap);
      gb_folder_add_child (((GbBookmark *) ap)->parent, alias, pos);
      g_object_unref (alias);
      
      gb_bookmark_set_xbel_node (alias, GB_BOOKMARK (ap)->xbel_node);
      gb_bookmark_set_xbel_node (GB_BOOKMARK (ap), NULL);

      gb_bookmark_unparent ((GbBookmark *) ap);
      set->unresolved_aliases = g_slist_remove (set->unresolved_aliases, ap);

      return TRUE;
}

void
gb_alias_placeholder_set_create_toolbar (GbAliasPlaceholder *ap, gboolean val)
{
      ap->create_toolbar = !!val;

      g_signal_emit (ap, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
      gb_folder_emit_child_modified (((GbBookmark *) ap)->parent, (GbBookmark *) ap);

      gb_bookmark_set_needs_saving (ap);
}

/**
 * BookmarkSet object
 */
G_DEFINE_TYPE_WITH_CODE (GbBookmarkSet, gb_bookmark_set, G_TYPE_OBJECT,
                   G_IMPLEMENT_INTERFACE (GALEON_TYPE_AUTOCOMPLETION_SOURCE,
                                    gb_bookmark_set_autocompletion_source_init));

static void
gb_bookmark_set_class_init (GbBookmarkSetClass *klass)
{
      G_OBJECT_CLASS (klass)->finalize = gb_bookmark_set_finalize_impl;
      G_OBJECT_CLASS (klass)->dispose = gb_bookmark_set_dispose_impl;

      GbBookmarkSetSignals[GB_BOOKMARK_SET_TOOLBAR] = g_signal_new (
            "toolbar", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbBookmarkSetClass, gb_bookmark_set_toolbar), 
            NULL, NULL, 
            galeon_marshal_VOID__OBJECT,
            G_TYPE_NONE, 1, GB_TYPE_FOLDER);

      GbBookmarkSetSignals[GB_BOOKMARK_SET_CONTEXT_MENU] = g_signal_new (
            "context-menu", G_OBJECT_CLASS_TYPE (klass),  
            G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbBookmarkSetClass, gb_bookmark_set_context_menu), 
            NULL, NULL, 
            galeon_marshal_VOID__VOID,
            G_TYPE_NONE, 0);
}

static void
gb_bookmark_set_autocompletion_source_init (GaleonAutocompletionSourceIface *iface)
{
      iface->foreach = gb_bookmark_set_autocompletion_source_foreach;
      iface->set_basic_key = gb_bookmark_set_autocompletion_source_set_basic_key;
}

static void
gb_bookmark_set_init (GbBookmarkSet *b)
{
      b->root = NULL;
      b->file_format_version = GB_FILE_FORMAT_VERSION_GALEON_2;
      b->filename = NULL;
      b->io = NULL;
      b->xbel_doc = NULL;
      b->needs_saving = FALSE;
      b->default_folder = b->root;
      b->id_to_bookmark = g_hash_table_new (g_str_hash, g_str_equal);
      b->url_to_bookmark = g_hash_table_new (g_str_hash, g_str_equal);
#ifdef NICK_HASHTABLE
      b->nick_to_bookmark = g_hash_table_new (g_str_hash, g_str_equal);
#endif
/*
      b->undo_queue = NULL;
      b->redo_queue  = NULL;
*/
      b->unresolved_aliases = NULL;
      b->toolbars = NULL;
      b->autosave_timeout_id = 0;
      b->max_smart_site_history_items = 15;     
}

static void
gb_bookmark_set_dispose_impl (GObject *o)
{
      GbBookmarkSet *set = GB_BOOKMARK_SET (o);

#ifdef DEBUG_REF
      g_print ("disposing the bookmarkset\n");
#endif

      LOG ("in gb_bookmark_set_dispose_impl");

      if (set->root)
      {
            gb_bookmark_set_set (GB_BOOKMARK (set->root), NULL);  
      }

      G_OBJECT_CLASS (gb_bookmark_set_parent_class)->dispose (o);
}

static void
gb_bookmark_set_finalize_impl (GObject *o)
{
      GbBookmarkSet *set = GB_BOOKMARK_SET (o);

#ifdef DEBUG_REF
      g_print ("finalizing the bookmarkset\n");
#endif

      LOG ("in gb_bookmark_set_finalize_impl");

      gb_bookmark_set_set_xbel_doc (set, NULL);
      
      if (set->autosave_timeout_id != 0)
      {
            g_source_remove (set->autosave_timeout_id);
      }

      if (set->search_timeout_id != 0)
      {
            g_source_remove (set->search_timeout_id);
      }

      if (set->autobookmarks_timeout_id != 0)
      {
            g_source_remove (set->autobookmarks_timeout_id);
      }

      if (set->io)
      {
            g_object_unref (G_OBJECT (set->io));
      }
      
      if (set->default_folder)
      {
            g_object_unref (G_OBJECT (set->default_folder));
      }

      if (set->root)
      {
            g_object_unref (G_OBJECT (set->root));
      }

      if (set->autobookmarks_source)
      {
            g_object_unref (G_OBJECT (set->autobookmarks_source));
      }

      /* if everything is 0, then things work quite well */
      LOG ("%d entries in url_to_bookmark", g_hash_table_size (set->url_to_bookmark));
      LOG ("%d entries in id_to_bookmark", g_hash_table_size (set->id_to_bookmark));

      g_hash_table_destroy (set->url_to_bookmark);
#ifdef NICK_HASHTABLE
      g_hash_table_destroy (set->nick_to_bookmark);
#endif
      g_hash_table_destroy (set->id_to_bookmark);

      g_free (set->filename);

      /* if everything is 0, then things work quite well */
      LOG ("%d unresolved_aliases", g_slist_length (set->unresolved_aliases));
      LOG ("%d toolbars\n", g_slist_length (set->toolbars));

      g_slist_free (set->unresolved_aliases);
      g_slist_free (set->toolbars);
      g_slist_free (set->all_bookmarks_noalias);
      g_slist_free (set->all_bookmarks);
      g_slist_free (set->v_folders);

      G_OBJECT_CLASS (gb_bookmark_set_parent_class)->finalize (o);
}

GbBookmarkSet *
gb_bookmark_set_new (void)
{
      return g_object_new (GB_TYPE_BOOKMARK_SET, NULL);
}

static gboolean
gb_bookmark_set_autobookmarks_timeout (gpointer data)
{
      GbBookmarkSet *set = data;
      GSList *afs;
      const GSList *li;
            set->autobookmarks_timeout_id = 0;

      afs = g_slist_copy ((GSList *) gb_bookmark_set_get_auto_folders (set));

      for (li = afs; li; li = li->next)
      {
            gb_auto_folder_refresh (li->data);
      }

      g_slist_free (afs);

      /* create another timeour because the interval is not constant */
      gb_bookmark_set_refresh_autobookmarks (set, AUTOBOOKMARKS_UPDATE_DELAY);

      return FALSE;
}

static gboolean
gb_bookmark_set_update_v_folders_timeout (gpointer data)
{
      GbBookmarkSet *set = data;
      GSList *afs;
      const GSList *li;
            set->search_timeout_id = 0;

      afs = g_slist_copy ((GSList *) gb_bookmark_set_get_v_folders (set));

      for (li = afs; li; li = li->next)
      {
            gb_v_folder_set_dirty (li->data);
            gb_v_folder_refresh (li->data);
      }

      if (set->search_timeout_id)
      {
            g_source_remove (set->search_timeout_id);
            set->search_timeout_id = 0;
      }

      g_slist_free (afs);

      return FALSE;
}

static void
gb_bookmark_set_update_v_folders_async (GbBookmarkSet *set)
{
      if (set->search_timeout_id)
      {
            g_source_remove (set->search_timeout_id);
      }
      set->search_timeout_id = g_timeout_add (SEARCH_TIMEOUT, gb_bookmark_set_update_v_folders_timeout, set);
}

static      void
gb_bookmark_set_root_descendant_modified_cb (GbFolder *f, GbBookmark *b, GbBookmarkSet *set)
{
      gb_bookmark_set_update_v_folders_async (set);
}


void
gb_bookmark_set_set_root (GbBookmarkSet *set, GbFolder *new_root)
{
      GbFolder *old = set->root;

      g_return_if_fail (GB_IS_FOLDER (new_root));
      g_return_if_fail (((GbBookmark *) new_root)->alias_of == NULL);
      set->root = new_root;

      if (new_root == old)
      {
            return;
      }

      if (GB_BOOKMARK (new_root)->set != set)
      {
            gb_bookmark_set_set (GB_BOOKMARK (new_root), set);
      }

      g_object_ref (G_OBJECT (new_root));

      if (old)
      {
            g_signal_handlers_disconnect_matched (old, G_SIGNAL_MATCH_DATA, 0, 0, 
                                          NULL, NULL, set);
            g_object_unref (old);
      }

      g_signal_connect (new_root, "descendant-modified", 
                    G_CALLBACK (gb_bookmark_set_root_descendant_modified_cb), set);
}

void
gb_bookmark_set_set_default_folder (GbBookmarkSet *set, GbFolder *default_folder)
{
      GbFolder *old;
      GbFolder *niu;
      GbBookmark *b;
      
      g_return_if_fail (GB_IS_BOOKMARK_SET (set));
      g_return_if_fail (GB_IS_FOLDER (default_folder));
      g_return_if_fail (GB_IS_FOLDER (set->root));
      g_return_if_fail (!gb_folder_is_autogenerated (default_folder));

      old = set->default_folder;
      niu = GB_FOLDER (gb_bookmark_real_bookmark (GB_BOOKMARK (default_folder)));

      g_assert (!old || !gb_bookmark_is_alias (old));
      g_assert (!gb_bookmark_is_alias (niu));

      set->default_folder = niu;
      g_object_ref (niu);

            if (old)
      {
            b = GB_BOOKMARK (old);
            do {
                  g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
                  gb_folder_emit_child_modified (b->parent, b);
            } while ((b = b->alias) != NULL);
            g_object_unref (old);
      }
            
      b = GB_BOOKMARK (niu);
            do {
            g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
            gb_folder_emit_child_modified (b->parent, b);
      } while ((b = b->alias) != NULL);

      gb_bookmark_set_needs_saving (niu);
}

void
gb_bookmark_set_resolve_aliases (GbBookmarkSet *set)
{
      GSList *l = g_slist_copy (set->unresolved_aliases);
      GSList *li;

      gint debug_len_before = g_slist_length (l);
      gint debug_len_after;

      for (li = l; li; li = li->next)
      {
            gb_alias_placeholder_resolve (li->data);
      }

      debug_len_after = g_slist_length (set->unresolved_aliases);
      if (debug_len_after > 0)
      {
            g_warning ("%d aliases were resolved, out of %d", 
                     debug_len_before - debug_len_after, debug_len_before);
            for (li = set->unresolved_aliases; li; li = li->next)
            {
                  g_warning ("unresolved \"%s\"", GB_BOOKMARK (li->data)->id);
            }
      }

      if (l) 
      {
            set->needs_saving = TRUE;
      }
      g_slist_free (l);
}

void
gb_bookmark_set_set_filename (GbBookmarkSet *set, const gchar *filename)
{
      g_free (set->filename);
      set->filename = g_strdup (filename);
      set->needs_saving = TRUE;
}

GSList *
gb_bookmark_set_get_toolbars (GbBookmarkSet *set)
{
      return g_slist_copy (set->toolbars);
}

void
gb_bookmark_set_set_io (GbBookmarkSet *set, GbIO *io)
{
      g_return_if_fail (GB_IS_IO (io));
      g_return_if_fail (GB_IS_BOOKMARK_SET (set));

      if (set->io)
      {
            g_object_unref (G_OBJECT (set->io));
      }
      if (io)
      {
            g_object_ref (G_OBJECT (io));
      }
      set->io = io;
}

void
gb_bookmark_set_discard_xml_nodes (GbBookmarkSet *set)
{
      GbIterator *i = gb_iterator_new (set);
      while (gb_iterator_has_next (i))
      {
            GbBookmark *b = gb_iterator_next (i);
            gb_bookmark_set_xbel_node (b, NULL);
      }
      g_object_unref (i);
}

void
gb_bookmark_set_set_xbel_doc (GbBookmarkSet *set, xmlDocPtr doc)
{
      g_return_if_fail (GB_IS_BOOKMARK_SET (set));

      if (set->xbel_doc && set->xbel_doc != doc)
      {
            gb_bookmark_set_discard_xml_nodes (set);
            xmlFreeDoc (set->xbel_doc);
      }

      set->xbel_doc = doc;
}

gboolean
gb_bookmark_set_check_save (GbBookmarkSet *set)
{
      if (set->needs_saving)
      {
            if (set->filename && set->io)
            {
                  gchar *tmpfilename = g_strconcat (set->filename, ".tmp", NULL);
                  gboolean ok = gb_io_save_to_file (set->io, set, tmpfilename);
                  if (ok)
                  {
                        gul_backup_file (set->filename, BOOKMARKS_NUM_BACKUPS, tmpfilename);
                        set->needs_saving = FALSE;
                  }
                  g_free (tmpfilename);
                  return ok;
            }
            else
            {
                  g_warning ("Not saving the bookmarks because either the filename or the IO has not been set.");
                  return FALSE;
            }
      }
      else
      {
            return TRUE;
      }
}

void
gb_bookmark_set_set_needs_saving (GbBookmarkSet *set, gboolean needs_saving)
{
      set->needs_saving = needs_saving;
}

void
gb_bookmark_set_set_auto_save (GbBookmarkSet *set, guint interval)
{
      if (set->autosave_timeout_id != 0)
      {
            g_source_remove (set->autosave_timeout_id);
      }

      set->autosave_timeout_id = g_timeout_add (interval, 
                                      gb_bookmark_set_set_auto_save_cb, set);
}

static gboolean
gb_bookmark_set_set_auto_save_cb (gpointer data)
{
      GbBookmarkSet *set = data;
      gb_bookmark_set_check_save (set);
      return TRUE;
}

void
gb_bookmark_set_add_default (GbBookmarkSet *set, GbBookmark *b)
{
      GbFolder *p;
      
      g_return_if_fail (GB_IS_BOOKMARK_SET (set));
      g_return_if_fail (GB_IS_BOOKMARK (b));
      
      p = set->default_folder ? set->default_folder : set->root;

      g_return_if_fail (GB_IS_FOLDER (p));

      gb_folder_add_child (p, b, -1);
}

static char *
gb_bookmark_set_fix_galeon1_mess_string (const gchar *s)
{
      char *sl = g_convert (s, -1, "ISO-8859-1", "UTF-8", NULL, NULL, NULL);
      if (sl) 
      {
            /* check if the result is valid */
            char *sll = g_locale_from_utf8 (sl, -1, NULL, NULL, NULL);
            if (sll)
            {
                  g_free (sll);
                  return sl;
            }
            else 
            {
                  g_free (sl);
                  return g_strdup (s);
            }
      }
      else
      {
            return g_strdup (s);
      }
}

static void
gb_bookmark_set_fix_galeon1_mess_item (GbBookmark *b)
{
      gchar *s;
      if (gb_bookmark_is_alias (b))
      {
            return;
      }

      s = gb_bookmark_set_fix_galeon1_mess_string (b->name);
      gb_bookmark_set_name (b, s);
      g_free (s);

      s = gb_bookmark_set_fix_galeon1_mess_string (b->notes);
      gb_bookmark_set_notes (b, s);
      g_free (s);
      
      if (GB_IS_FOLDER (b))
      {
            gb_bookmark_set_fix_galeon1_mess_recursive (GB_FOLDER (b));
      }
}

static void
gb_bookmark_set_fix_galeon1_mess_recursive (GbFolder *f)
{
      GbBookmark *bi;
      if (gb_bookmark_is_alias (f))
      {
            return;
      }
      for (bi = f->child; bi; bi = bi->next)
      {
            gb_bookmark_set_fix_galeon1_mess_item (bi);
      }
}

void
gb_bookmark_set_fix_galeon1_mess (GbBookmarkSet *set)
{
      gb_bookmark_set_fix_galeon1_mess_item (GB_BOOKMARK (set->root));
}

GbBookmark *
gb_bookmark_set_get_bookmark_by_url (GbBookmarkSet *set, const gchar *url)
{
      return g_hash_table_lookup (set->url_to_bookmark, url);
}

static GbBookmark *
gb_bookmark_set_get_bookmark_by_nick_rec (GbFolder *root, 
                                const gchar *nick)
{
      GbBookmark *bi;

      for (bi = root->child; bi; bi = bi->next)
      {
            if (GB_IS_FOLDER (bi) && !gb_bookmark_is_alias (bi))
            {
                  GbBookmark *bj = gb_bookmark_set_get_bookmark_by_nick_rec (GB_FOLDER (bi), nick);
                  if (bj) 
                  {
                        return bj;
                  }
            }

            if (bi->nick && bi->nick[0] && strstr (bi->nick, nick))
            {
                  /* possible match */
                  gchar **nicks = gul_strsplit_multiple_delimiters_with_quotes 
                        (bi->nick, " ,;", -1, "\"\'");
                  gint i;
                  gboolean matched = FALSE;
                  for (i = 0; nicks[i] && !matched; ++i)
                  {
                        matched = !strcmp (nicks[i], nick);
                  }
                  g_strfreev (nicks);

                  if (matched)
                  {
                        return bi;
                  }
            }
      }
      
      return NULL;
}

GbBookmark *
gb_bookmark_set_get_bookmark_by_nick (GbBookmarkSet *set, 
                              const gchar *nick)
{
#ifdef NICK_HASHTABLE
      return g_hash_table_lookup (set->nick_to_bookmark, nick);
#else
      return gb_bookmark_set_get_bookmark_by_nick_rec (set->root, nick);
#endif
}

gchar *
gb_bookmark_set_get_url_by_nick_and_args (GbBookmarkSet *set, 
                                const gchar *nick_and_args)
{
      GbBookmark *b;
      gchar **tokens;
      const gchar *nick;
      gchar *ret;

      tokens = gul_strsplit_with_quotes (nick_and_args, " ", -1, "\"\'");
      nick = tokens[0];

      if (!nick)
      {
            return NULL;
      }

      b = gb_bookmark_set_get_bookmark_by_nick (set, nick);

      if (!(b && GB_IS_SITE (b)))
      {
            g_strfreev (tokens);
            return NULL;
      }
      
      if (GB_IS_SMART_SITE (b) && tokens[1])
      {
            ret = gb_smart_site_subst_args (GB_SMART_SITE (b), tokens + 1);
      }
      else
      { 
            ret = g_strdup (GB_SITE (b)->url);
      }

      g_strfreev (tokens);

      return ret;
}

static void
gb_bookmark_set_autocompletion_source_foreach_rec (GaleonAutocompletionSource *source,
                                       GbFolder *f,
                                       GaleonAutocompletionSourceForeachFunc func,
                                       gpointer data)
{
      GbBookmark *bi;
      for (bi = f->child; bi; bi = bi->next)
      {
            if (!gb_bookmark_is_alias (bi))
            {
                  if (GB_IS_SITE (bi)) 
                  {
                        /* very simple scoring function. Most recent entries get higher scores.
                           Adds 100<<17 to the item because it's bookmarked... */

                        func (source, ((GbSite *) bi)->url, bi->name, 
                              ((GbSite *) bi)->time_visited + (100 << 17), data);
                  }
                  else if (GB_IS_FOLDER (bi))
                  {
                        gb_bookmark_set_autocompletion_source_foreach_rec 
                              (source, (GbFolder *) bi, func, data);
                  }
            }
      }
}

static void
gb_bookmark_set_autocompletion_source_set_basic_key (GaleonAutocompletionSource *source,
                                         const gchar *basic_key)
{
      /* nothing to do here */
}

static void
gb_bookmark_set_autocompletion_source_foreach (GaleonAutocompletionSource *source,
                                     const gchar *current_text,
                                     GaleonAutocompletionSourceForeachFunc func,
                                     gpointer data)
{
      GbBookmarkSet *set = GB_BOOKMARK_SET (source);
      gb_bookmark_set_autocompletion_source_foreach_rec (source, set->root, func, data);
}

static void
gb_bookmark_set_emit_autocompletion_source_data_changed (GbBookmarkSet *gh)
{
      g_signal_emit_by_name (gh, "data-changed");
}

static GSList *
gb_folder_prepend_preorder_noalias (GbFolder *f, GSList *l)
{
      GbBookmark *c;
      for (c = f->child; c; c = c->next)
      {
            if (!gb_bookmark_is_alias (c))
            {
                  l = g_slist_prepend (l, c);
                  if (GB_IS_FOLDER (c))
                  {
                        l = gb_folder_prepend_preorder_noalias (GB_FOLDER (c), l);
                  }
            }
      }
      return l;
}

static GSList *
gb_folder_prepend_preorder (GbFolder *f, GSList *l)
{
      GbBookmark *c;
      for (c = f->child; c; c = c->next)
      {
            l = g_slist_prepend (l, c);
            if (!gb_bookmark_is_alias (c) &&GB_IS_FOLDER (c))
            {
                  l = gb_folder_prepend_preorder (GB_FOLDER (c), l);
            }
      }
      return l;
}

const GSList *
gb_bookmark_set_get_all (GbBookmarkSet *set)
{
      if (!set->all_bookmarks)
      {
            set->all_bookmarks = g_slist_reverse 
                  (gb_folder_prepend_preorder (set->root, 
                                         g_slist_prepend (NULL, set->root)));
      }
      return set->all_bookmarks;
}

const GSList *
gb_bookmark_set_get_all_noalias (GbBookmarkSet *set)
{
      if (!set->all_bookmarks_noalias)
      {
            set->all_bookmarks_noalias = g_slist_reverse 
                  (gb_folder_prepend_preorder_noalias (set->root, 
                                               g_slist_prepend (NULL, set->root)));
      }
      return set->all_bookmarks_noalias;
}

const GSList *
gb_bookmark_set_get_context_bookmarks (GbBookmarkSet *set)
{
      if (!set->context_bookmarks)
      {
            const GSList *all = gb_bookmark_set_get_all (set);
            const GSList *li;
            GSList *t = NULL;
            
            for (li = all; li; li = li->next)
            {
                  GbBookmark *bi = li->data;
                  if (bi->add_to_context_menu)
                  {
                        t = g_slist_prepend (t, bi);
                  }
            }
            
            set->context_bookmarks = g_slist_reverse (t);
      }
      return set->context_bookmarks;
}

const GSList *
gb_bookmark_set_get_v_folders (GbBookmarkSet *set)
{
      if (!set->v_folders)
      {
            const GSList *all = gb_bookmark_set_get_all_noalias (set);
            const GSList *li;
            GSList *t = NULL;
            
            for (li = all; li; li = li->next)
            {
                  if (GB_IS_V_FOLDER (li->data))
                  {
                        t = g_slist_prepend (t, li->data);
                  }
            }
            
            set->v_folders = t;
      }
      return set->v_folders;
}

const GSList *
gb_bookmark_set_get_auto_folders (GbBookmarkSet *set)
{
      if (!set->auto_folders)
      {
            const GSList *all = gb_bookmark_set_get_all_noalias (set);
            const GSList *li;
            GSList *t = NULL;
            
            for (li = all; li; li = li->next)
            {
                  if (GB_IS_AUTO_FOLDER (li->data))
                  {
                        t = g_slist_prepend (t, li->data);
                  }
            }
            
            /* better to keep the order here */
            set->auto_folders = g_slist_reverse (t);
      }
      return set->auto_folders;
}

static void
gb_bookmark_set_tree_changed (GbBookmarkSet *set)
{
      if (set->all_bookmarks_noalias)
      {
            g_slist_free (set->all_bookmarks_noalias);
            set->all_bookmarks_noalias = NULL;
      }
      if (set->all_bookmarks)
      {
            g_slist_free (set->all_bookmarks);
            set->all_bookmarks = NULL;
      }
      if (set->context_bookmarks)
      {
            g_slist_free (set->context_bookmarks);
            set->context_bookmarks = NULL;
      }
      if (set->v_folders)
      {
            g_slist_free (set->v_folders);
            set->v_folders = NULL;
      }
      if (set->auto_folders)
      {
            g_slist_free (set->auto_folders);
            set->auto_folders = NULL;
      }

      gb_bookmark_set_update_v_folders_async (set);
}

void
gb_bookmark_set_set_autobookmarks_source (GbBookmarkSet *set, GaleonAutoBookmarksSource *src)
{
      if (set->autobookmarks_source)
      {
            g_object_unref (set->autobookmarks_source);
            set->autobookmarks_source = NULL;
      }
      if (src)
      {
            set->autobookmarks_source = g_object_ref (src);
      }
      gb_bookmark_set_refresh_autobookmarks (set, AUTOBOOKMARKS_INITIAL_DELAY);
}

static void
gb_bookmark_set_refresh_autobookmarks (GbBookmarkSet *set, gint delay)
{
      if (set->autobookmarks_timeout_id != 0)
      {
            g_source_remove (set->autobookmarks_timeout_id);
      }
      set->autobookmarks_timeout_id = g_timeout_add (delay, 
                                           gb_bookmark_set_autobookmarks_timeout, set);
}

GbBookmark *
gb_bookmark_set_get_bookmark_for_id (GbBookmarkSet *set, const char *id)
{
        GbBookmark *b;

      g_return_val_if_fail (GB_IS_BOOKMARK_SET (set), NULL);
      g_return_val_if_fail (id != NULL, NULL);

        b = g_hash_table_lookup (set->id_to_bookmark, id);

        return b;
}


Generated by  Doxygen 1.6.0   Back to index