static gboolean
gd_tagged_entry_button_release_event (GtkWidget *widget,
                                      GdkEventButton *event)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
    GdTaggedEntryTag *tag;

    tag = gd_tagged_entry_find_tag_by_window (self, event->window);

    if (tag != NULL)
    {
        self->in_child_active = FALSE;

        if (gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y))
        {
            self->in_child_button_active = FALSE;
            g_signal_emit (self, signals[SIGNAL_TAG_BUTTON_CLICKED], 0, tag);
        }
        else
        {
            g_signal_emit (self, signals[SIGNAL_TAG_CLICKED], 0, tag);
        }

        gtk_widget_queue_draw (widget);

        return TRUE;
    }

    return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->button_release_event (widget, event);
}
static void
gd_tagged_entry_size_allocate (GtkWidget *widget,
                               GtkAllocation *allocation)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
    gint x, y, width, height;
    GdTaggedEntryTag *tag;
    GList *l;

    gtk_widget_set_allocation (widget, allocation);
    GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->size_allocate (widget, allocation);

    if (gtk_widget_get_realized (widget))
    {
        gd_tagged_entry_tag_panel_get_position (self, &x, &y);

        for (l = self->tags; l != NULL; l = l->next)
        {
            GtkBorder margin;

            tag = l->data;
            gd_tagged_entry_tag_get_size (tag, self, &width, &height);
            gd_tagged_entry_tag_get_margin (tag, self, &margin);
            gdk_window_move_resize (tag->window, x, y + margin.top, width, height);

            x += width;
        }

        gtk_widget_queue_draw (widget);
    }
}
void
xplayer_search_entry_add_source (XplayerSearchEntry *self,
			       const gchar      *id,
			       const gchar      *label,
			       int               priority)
{
	GtkWidget *item;

	g_return_if_fail (XPLAYER_IS_SEARCH_ENTRY (self));

	if (self->priv->menu == NULL) {
		self->priv->menu = gtk_menu_new ();
		gtk_menu_button_set_popup (GTK_MENU_BUTTON (self->priv->button),
					   self->priv->menu);
		gd_tagged_entry_add_tag (GD_TAGGED_ENTRY (self->priv->entry),
					 SOURCE_ID, label);
	}

	item = gtk_radio_menu_item_new_with_label (self->priv->group, label);
	self->priv->group = g_slist_prepend (self->priv->group, item);

	g_object_set_data_full (G_OBJECT (item), "id", g_strdup (id), g_free);
	g_object_set_data_full (G_OBJECT (item), "label", g_strdup (label), g_free);
	g_object_set_data (G_OBJECT (item), "priority", GINT_TO_POINTER (priority));

	g_signal_connect (item, "toggled",
			  G_CALLBACK (item_toggled), self);

	insert_item_sorted (self, priority, item);
}
static void
xplayer_search_entry_init (XplayerSearchEntry *self)
{
	GtkWidget *entry;
	GtkWidget *button;

	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, XPLAYER_TYPE_SEARCH_ENTRY, XplayerSearchEntryPrivate);

	/* Entry */
	entry = GTK_WIDGET (gd_tagged_entry_new ());
	gd_tagged_entry_set_tag_button_visible (GD_TAGGED_ENTRY (entry), FALSE);
	gtk_box_pack_start (GTK_BOX (self),
			    entry,
			    TRUE, TRUE, 0);
	gtk_widget_show (entry);

	self->priv->entry = entry;

	/* Button */
	button = gtk_menu_button_new ();
	gtk_box_pack_start (GTK_BOX (self),
			    button,
			    FALSE, TRUE, 0);
	gtk_widget_show (button);

	self->priv->button = button;

	/* Connect signals */
	g_signal_connect (self->priv->entry, "activate",
			  G_CALLBACK (entry_activate_cb), self);
}
static void
item_toggled (GtkCheckMenuItem *item,
	      XplayerSearchEntry *self)
{
	const char *label;

	if (gtk_check_menu_item_get_active (item)) {
		label = g_object_get_data (G_OBJECT (item), "label");
		gd_tagged_entry_set_tag_label (GD_TAGGED_ENTRY (self->priv->entry),
					       SOURCE_ID, label);
		g_object_notify (G_OBJECT (self), "selected-id");
	}
}
static void
gd_tagged_entry_finalize (GObject *obj)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (obj);

    if (self->tags != NULL)
    {
        g_list_free_full (self->tags, g_object_unref);
        self->tags = NULL;
    }

    G_OBJECT_CLASS (gd_tagged_entry_parent_class)->finalize (obj);
}
static gint
gd_tagged_entry_leave_notify (GtkWidget        *widget,
                              GdkEventCrossing *event)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);

    if (self->in_child != NULL)
    {
        self->in_child = NULL;
        gtk_widget_queue_draw (widget);
    }

    return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->leave_notify_event (widget, event);
}
static void
gd_tagged_entry_unrealize (GtkWidget *widget)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
    GdTaggedEntryTag *tag;
    GList *l;

    GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->unrealize (widget);

    for (l = self->tags; l != NULL; l = l->next)
    {
        tag = l->data;
        gd_tagged_entry_tag_unrealize (tag);
    }
}
static void
gd_tagged_entry_set_property (GObject      *object,
                              guint         property_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (object);

    switch (property_id)
    {
    case PROP_TAG_BUTTON_VISIBLE:
        gd_tagged_entry_set_tag_button_visible (self, g_value_get_boolean (value));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}
static void
gd_tagged_entry_get_text_area_size (GtkEntry *entry,
                                    gint *x,
                                    gint *y,
                                    gint *width,
                                    gint *height)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (entry);
    gint tag_panel_width;

    GTK_ENTRY_CLASS (gd_tagged_entry_parent_class)->get_text_area_size (entry, x, y, width, height);

    tag_panel_width = gd_tagged_entry_tag_panel_get_width (self);

    if (width)
        *width -= tag_panel_width;
}
static void
gd_tagged_entry_get_preferred_width (GtkWidget *widget,
                                     gint *minimum,
                                     gint *natural)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
    gint tag_panel_width;

    GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->get_preferred_width (widget, minimum, natural);

    tag_panel_width = gd_tagged_entry_tag_panel_get_width (self);

    if (minimum)
        *minimum += tag_panel_width;
    if (natural)
        *natural += tag_panel_width;
}
static gint
gd_tagged_entry_enter_notify (GtkWidget        *widget,
                              GdkEventCrossing *event)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
    GdTaggedEntryTag *tag;

    tag = gd_tagged_entry_find_tag_by_window (self, event->window);

    if (tag != NULL)
    {
        self->in_child = tag;
        gtk_widget_queue_draw (widget);
    }

    return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->enter_notify_event (widget, event);
}
static gboolean
gd_tagged_entry_draw (GtkWidget *widget,
                      cairo_t *cr)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
    GdTaggedEntryTag *tag;
    GList *l;

    GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->draw (widget, cr);

    for (l = self->tags; l != NULL; l = l->next)
    {
        tag = l->data;
        gd_tagged_entry_tag_draw (tag, cr, self);
    }

    return FALSE;
}
static void
gd_tagged_entry_unmap (GtkWidget *widget)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
    GdTaggedEntryTag *tag;
    GList *l;

    if (gtk_widget_get_mapped (widget))
    {
        for (l = self->tags; l != NULL; l = l->next)
        {
            tag = l->data;
            gdk_window_hide (tag->window);
        }

        GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->unmap (widget);
    }
}
void
xplayer_search_entry_remove_source (XplayerSearchEntry *self,
				  const gchar *id)
{
	guint num_items;

	g_return_if_fail (XPLAYER_IS_SEARCH_ENTRY (self));

	/* FIXME
	 * - implement
	 * - don't forget to remove tag
	 * - check if it's the currently selected source and notify of the change if so */

	num_items = 1;

	if (num_items == 0) {
		gtk_menu_button_set_popup (GTK_MENU_BUTTON (self->priv->button), NULL);
		g_clear_object (&self->priv->menu);
		gd_tagged_entry_remove_tag (GD_TAGGED_ENTRY (self->priv->entry), SOURCE_ID);
	}
}
static gint
gd_tagged_entry_motion_notify (GtkWidget      *widget,
                               GdkEventMotion *event)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
    GdTaggedEntryTag *tag;

    tag = gd_tagged_entry_find_tag_by_window (self, event->window);

    if (tag != NULL)
    {
        gdk_event_request_motions (event);

        self->in_child = tag;
        self->in_child_button = gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y);
        gtk_widget_queue_draw (widget);

        return FALSE;
    }

    return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->motion_notify_event (widget, event);
}
static gboolean
gd_tagged_entry_button_press_event (GtkWidget *widget,
                                    GdkEventButton *event)
{
    GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
    GdTaggedEntryTag *tag;

    tag = gd_tagged_entry_find_tag_by_window (self, event->window);

    if (tag != NULL)
    {
        if (gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y))
            self->in_child_button_active = TRUE;
        else
            self->in_child_active = TRUE;

        gtk_widget_queue_draw (widget);

        return TRUE;
    }

    return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->button_press_event (widget, event);
}