<commit_before>/* this file is part of libccc, criawips' cairo-based canvas
 *
 * AUTHORS
 *       <NAME>        <<EMAIL>>
 *
 * Copyright (C) 2005 <NAME>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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
 */

#include <ccc/cc-item.h>

#include <string.h>
#include <gdk/gdk.h>
#include <gdk/gdkenumtypes.h>
#include <gtk/gtktypebuiltins.h>
#include <ccc/cc-marshallers.h>
#include <ccc/cc-item-view.h>
#include <ccc/cc-utils.h>
#define CDEBUG_TYPE cc_item_get_type
#include <cdebug/cdebug.h>
#include "glib-helpers.h"

enum {
	ALL_BOUNDS_CHANGED,
	BUTTON_PRESS_EVENT,
	BUTTON_RELEASE_EVENT,
	ENTER_NOTIFY_EVENT,
	EVENT,
	FOCUS,
	FOCUS_ENTER,
	FOCUS_LEAVE,
	ITEM_DIRTY,
	KEY_PRESS_EVENT,
	KEY_RELEASE_EVENT,
	LEAVE_NOTIFY_EVENT,
	MOTION_NOTIFY_EVENT,

	ITEM_ADDED,
	ITEM_REMOVED,

	VIEW_REGISTER,
	VIEW_UNREGISTER,

	N_SIGNALS
};

guint cc_item_signals[N_SIGNALS] = {0};

static void ci_init_item_view(CcItemViewIface* iface);

G_DEFINE_TYPE_WITH_CODE(CcItem, cc_item, G_TYPE_INITIALLY_UNOWNED,
			G_IMPLEMENT_INTERFACE(CC_TYPE_ITEM_VIEW, ci_init_item_view));

/**
 * CcItemFlags:
 * @CC_DISPOSED: specifies whether dispose has already been run on an item.
 * @CC_GRID_ALIGNED: specifies whether an item should be rendered to match an
 * existing grid.
 */

/**
 * CC_ITEM_FLAGS:
 * @i: a #CcItem
 *
 * Get the flags which are set on the #CcItem @i.
 *
 * Returns the mask of flags that are set on @i.
 */

/**
 * CC_ITEM_SET_FLAGS:
 * @i: a #CcItem
 * @m: a CcItemFlags mask
 *
 * Sets on @i the #CcItemFlags that have been specified in @m.
 */

/**
 * CC_ITEM_UNSET_FLAGS:
 * @i: a #CcItem
 * @m: a #CcItemFlags mask
 *
 * Unsets from @i the #CcItemFlags that have been specified in @m.
 */

/**
 * CC_ITEM_DISPOSED:
 * @i: a #CcItem
 *
 * Queries the #CcItem @i whether it has the %CC_DISPOSED flag set. This is
 * very useful in the dispose handler for the GObject derived types. This flag
 * gets set automatically if you chain up your dispose calls correctly.
 *
 * Returns %TRUE is the flag was set, %FALSE if not.
 */

/**
 * CC_ITEM_GRID_ALIGNED:
 * @i: a #CcItem
 *
 * Queries the #CcItem @i whether it has the %CC_GRID_ALIGNED flag set.
 *
 * Returns %TRUE if the flag was set, %FALSE if not.
 */

/**
 * cc_item_new:
 *
 * Create a new #CcItem that can be used to group several items together.
 *
 * Returns a new #CcItem.
 */
CcItem*
cc_item_new(void) {
	return g_object_new(CC_TYPE_ITEM, NULL);
}

/**
 * cc_item_append:
 * @self: a #CcItem
 * @child: another #CcItem
 *
 * Insert an item at the end of this item. A #CcItem can serve as a container
 * for several items.
 */
void
cc_item_append(CcItem* self, CcItem* child) {
	cc_item_insert(self, child, -1);
}

/**
 * cc_item_add_view:
 * @self: a #CcItem
 * @view: a #CcView
 *
 * Registers a view at the item. The item and its children can now set up data
 * for this view.
 *
 * This function should not be called by application code. Use
 * cc_item_view_register() instead.
 */
void
cc_item_add_view(CcItem* self, CcView* view) {
	g_return_if_fail(CC_IS_ITEM(self));
	g_return_if_fail(CC_IS_VIEW(view));

	self->views = g_list_prepend(self->views, view);
	// FIXME: add a weak reference on the view, and then add a FIXME for
	// removal in _remove_view()
	g_signal_emit(self, cc_item_signals[VIEW_REGISTER], 0, view);
}

/**
 * cc_item_remove_view:
 * @self: a #CcItem
 * @view: a #CcView
 *
 * Unregisters a view at the item. The item and the children can now free data
 * kept for this view.
 *
 * This function should not be called by application code. Use
 * cc_item_view_unregister() instead.
 */
void
cc_item_remove_view(CcItem* self, CcView* view) {
	g_return_if_fail(CC_IS_ITEM(self));
	g_return_if_fail(CC_IS_VIEW(view));

	self->views = g_list_remove(self->views, view);
	// FIXME: add an extra check to make sure that the view is definitely
	// not in the list anymore (and disable it with G_DISABLE_CHECKS)
	g_signal_emit(self, cc_item_signals[VIEW_UNREGISTER], 0, view);
}

static void
ci_update_all_bounds(CcView const* view, CcItem* self) {
	CcDRect const* all_bounds      = cc_item_get_all_bounds(self, view);
	CcDRect const* bounds          = cc_hash_map_lookup(self->bounds, view);
	CcDRect const* children_bounds = cc_hash_map_lookup(self->children_bounds, view);

	g_return_if_fail(CC_IS_VIEW(view));

	if(G_UNLIKELY(!bounds && !children_bounds && all_bounds)) {
		cc_hash_map_remove(self->all_bounds, self);
		g_signal_emit(self, cc_item_signals[ALL_BOUNDS_CHANGED], 0, view, NULL);
	} else if(G_LIKELY(bounds || children_bounds)) {
		CcDRect new_bounds;
		if(bounds && children_bounds) {
			cc_d_rect_union(*bounds, *children_bounds, &new_bounds);
		} else if(bounds) {
			new_bounds = *bounds;
		} else {
			new_bounds = *children_bounds;
		}

		if(!all_bounds || !cc_d_rect_equal(*all_bounds, new_bounds)) {
			cc_hash_map_remove(self->all_bounds, view);
			cc_hash_map_insert(self->all_bounds, (gpointer)view, cc_d_rect_copy(&new_bounds));
			g_signal_emit(self, cc_item_signals[ALL_BOUNDS_CHANGED], 0, view, &new_bounds);
		}
	}
}

void
cc_item_bounds_changed(CcItem* self, CcView const* view) {
	g_return_if_fail(CC_IS_ITEM(self));
	g_return_if_fail(CC_IS_VIEW(view));

	ci_update_all_bounds(view, self);
}

/**
 * cc_item_dirty:
 * @self: a #CcItem
 * @view: a #CcView
 * @dirty_region: the region that needs to be redrawn
 *
 * Let emit a signal to tell @view that it needs to be redrawn.
 */
void
cc_item_dirty(CcItem* self, CcView const* view, CcDRect dirty_region) {
	g_signal_emit(self, cc_item_signals[ITEM_DIRTY], 0, view, &dirty_region);
}

/**
 * cc_item_distance:
 * @self: a #CcItem
 * @x: the x coordinate to query for
 * @y: the y coordinate to query for
 * @found: the return location for a found #CcItem
 *
 * Calculates the distance from @self to (@x,@y). If (@x,@y) is within @self or
 * one of its children @found is set to the matching element.
 *
 * Returns the distance between @self (or one of its children) to (@x,@y).
 */
gdouble
cc_item_distance(CcItem* self, gdouble x, gdouble y, CcItem** found) {
	gdouble distance = 0.0;
	g_return_val_if_fail(CC_IS_ITEM(self), G_MAXDOUBLE);
	g_return_val_if_fail(CC_ITEM_GET_CLASS(self)->distance, G_MAXDOUBLE);
	g_return_val_if_fail(found, G_MAXDOUBLE);
	g_return_val_if_fail(!CC_IS_ITEM(*found), G_MAXDOUBLE);

	distance = CC_ITEM_GET_CLASS(self)->distance(self, x, y, found);

#ifndef G_DISABLE_CHECKS
	// enable this on experimental builds, but not on performance builds
	if(distance <= 0.0 && !CC_IS_ITEM(*found)) {
		g_warning("%sClass->distance() should set *found if the returned distance is <= 0.0",
			  G_OBJECT_TYPE_NAME(self));
		*found = self;
	} else if(distance > 0 && CC_IS_ITEM(*found)) {
		g_warning("%sClass->distance() should not set *found if distance is larger than 0.0",
			  G_OBJECT_TYPE_NAME(self));
		*found = NULL;
	}
#endif

	return distance;
}

static void
ci_foreach_view(CcView* view, gpointer self_func_and_data[3]) {
	CcItem   * self = self_func_and_data[0];
	CcItemFunc func = self_func_and_data[1];
	gpointer   data = self_func_and_data[2];

	func(self, view, data);
}

void
cc_item_foreach_view(CcItem* self, CcItemFunc func, gpointer data) {
	CcItem* item;
	gpointer self_func_and_data[] = {self, func, data};
	for(item = self; item; item = item->parent) {
		g_list_foreach(item->views, G_FUNC(ci_foreach_view), self_func_and_data);
	}
}

/**
 * cc_item_get_all_bounds:
 * @self: a #CcItem
 * @view: a #CcView
 *
 * Get the bounds of @self as it would e displayed by @view (including all of its children).
 *
 * Returns the bounds of the item, may be %NULL (in this case the item is not
 * tied visible area).
 */
CcDRect const*
cc_item_get_all_bounds(CcItem const* self, CcView const* view) {
	g_return_val_if_fail(CC_IS_ITEM(self), NULL);
	g_return_val_if_fail(CC_IS_VIEW(view), NULL);

	return cc_hash_map_lookup(self->all_bounds, view);
}

void
cc_item_grab_focus(CcItem* self, CcView* view) {
	GdkEventFocus ev = {
		GDK_FOCUS_CHANGE,
		NULL,
		FALSE,
		FALSE
	};
	gboolean retval;
	CcItem* focus = cc_view_get_focus(view);

	if(focus) {
		g_signal_emit(focus, cc_item_signals[FOCUS_LEAVE], 0, view, &ev, &retval);
	}

	cc_view_set_focus(view, self);
	ev.in = TRUE;
	g_signal_emit(self, cc_item_signals[FOCUS_ENTER], 0, view, &ev, &retval);
}

static void
ci_add_parent_view_to_child(CcItem* self, CcView* view, gpointer data) {
	cc_item_view_register(CC_ITEM_VIEW(view), CC_ITEM(data));
}

/**
 * cc_item_insert:
 * @self: a #CcItem
 * @child: another #CcItem
 * @position: the position to insert the child at (0 means at the bottom,
 * bigger numbers mean higher layers, -1 means top layer)
 *
 * Inserts @child at @position into @self.
 */
void
cc_item_insert(CcItem* self, CcItem* child, gint position) {
	g_return_if_fail(CC_IS_ITEM(self));
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(!CC_IS_ITEM(child->parent));

	self->children = g_list_insert(self->children, g_object_ref_sink(child), position);
	child->parent = self;
	cc_item_view_register(CC_ITEM_VIEW(self), child);
	position = g_list_index(self->children, child);
	cc_item_foreach_view(self, ci_add_parent_view_to_child, child);

	g_signal_emit(self, cc_item_signals[ITEM_ADDED], 0, position, child);
}

/**
 * cc_item_is_child_of:
 * @child: a #CcItem
 * @parent: another #CcItem
 *
 * Find out whether @child is a child of @parent.
 *
 * Returns %TRUE is @child is a child of @parent.
 */
gboolean
cc_item_is_child_of(CcItem const* child, CcItem const* parent) {
	gboolean retval = FALSE;

	g_return_val_if_fail(CC_IS_ITEM(child), FALSE);
	g_return_val_if_fail(CC_IS_ITEM(parent), FALSE);

	for(child = child->parent; CC_IS_ITEM(child) ; child = child->parent) {
		if(child == parent) {
			retval = TRUE;
			break;
		}
	}

	return retval;
}

static void
ci_remove_parent_view_from_child(CcItem* self, CcView* view, gpointer data) {
	CcItem* child = CC_ITEM(data);

	cc_item_view_unregister(CC_ITEM_VIEW(view), child);
}

/**
 * cc_item_remove:
 * @self: a #CcItem
 * @child: another #CcItem
 *
 * Removes @child from @self. Does nothing if @child was not a child of @self
 * or if @child is a child of one of @self's children.
 */
void
cc_item_remove(CcItem* self, CcItem* child) {
	gint position;
	g_return_if_fail(CC_IS_ITEM(self) && CC_IS_ITEM(child));

	position = g_list_index(self->children, child);
	self->children = g_list_remove(self->children, child);
	cc_item_foreach_view(self, ci_remove_parent_view_from_child, child);
	cc_item_view_unregister(CC_ITEM_VIEW(self), child);

	g_signal_emit(self, cc_item_signals[ITEM_REMOVED], 0, position, child);
	g_object_unref(child);
}

/**
 * cc_item_render:
 * @self: a #CcItem
 * @view: a #Ccview
 * @cr: a cairo context (#cairo_t)
 *
 * Renders @self to @cr which is supposed to be a cairo context of @view as
 * the transformation matrix of @view will be used for rendering.
 */
void
cc_item_render(CcItem* self, CcView* view, cairo_t* cr) {
	g_return_if_fail(CC_IS_ITEM(self));
	g_return_if_fail(CC_IS_VIEW(view));
	g_return_if_fail(cr);

	g_return_if_fail(CC_ITEM_GET_CLASS(self)->render);

	CC_ITEM_GET_CLASS(self)->render(self, view, cr);
}

/**
 * cc_item_set_grid_aligned:
 * @self: a #CcItem
 * @grid_aligned: a gboolean
 *
 * Sets @self to be rendered grid-aligned if @grid_aligned is %TRUE, sets it to
 * be allowed to render fuzzyly if %FALSE.
 */
// FIXME: link to the grid align section of the reference
void
cc_item_set_grid_aligned(CcItem* self, gboolean grid_aligned) {
	if(grid_aligned) {
		CC_ITEM_SET_FLAGS(self, CC_GRID_ALIGNED);
	} else {
		CC_ITEM_UNSET_FLAGS(self, CC_GRID_ALIGNED);
	}
}

static void
ci_update_bounds_per_view(CcView* view, gpointer* self_and_data) {
	g_return_if_fail(CC_IS_VIEW(view));
	g_return_if_fail(CC_IS_ITEM(self_and_data[0]));

	if(CC_ITEM_GET_CLASS(self_and_data[0])->update_bounds) {
		CC_ITEM_GET_CLASS(self_and_data[0])->update_bounds(self_and_data[0], view, self_and_data[1]);
	}
}

void
cc_item_update_bounds(CcItem* self, gpointer data) {
	CcItem* item;
	gpointer self_and_data[2];

	g_return_if_fail(CC_IS_ITEM(self));

	self_and_data[0] = self;
	self_and_data[1] = data;
	for(item = self; item; item = item->parent) {
		g_list_foreach(item->views, G_FUNC(ci_update_bounds_per_view), self_and_data);
	}
}

void
cc_item_update_bounds_for_view(CcItem* self, CcView* view) {
	gpointer self_and_data[] = {
		self, NULL
	};
	ci_update_bounds_per_view(view, self_and_data);
}

static void
ci_update_view(CcItem* self, CcView* view, gpointer data) {
	CcDRect const *dirty_region = cc_item_get_all_bounds(self, view);
	g_return_if_fail(dirty_region != NULL);
	cc_item_dirty(self, view, *dirty_region);
}

/**
 * cc_item_set_position:
 * @child: a #CcItem
 * @parent: another #CcItem, parent of @child
 * @position: the new position of the #CcItem (0 means at the bottom,
 * bigger numbers mean higher layers, -1 means top layer)
 *
 * Changes the position of @child relative to @parent.
 */
void
cc_item_set_position(CcItem* child, CcItem* parent, gint position)
{
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(CC_IS_ITEM(parent));
	g_return_if_fail(cc_item_is_child_of(child, parent));

	parent->children = g_list_remove(parent->children, child);
	parent->children = g_list_insert(parent->children, child, position);
	cc_item_foreach_view(child, ci_update_view, NULL);
}

/**
 * cc_item_raise_to_top:
 * @child: a #CcItem
 * @parent: another #CcItem, parent of @child
 *
 * Changes the position of @child relative to @parent to the top layer.
 */
void
cc_item_raise_to_top(CcItem* child, CcItem* parent)
{
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(CC_IS_ITEM(parent));
	g_return_if_fail(cc_item_is_child_of(child, parent));

	parent->children = g_list_remove(parent->children, child);
	parent->children = g_list_append(parent->children, child);
	cc_item_foreach_view(child, ci_update_view, NULL);
}

/**
 * cc_item_raise:
 * @child: a #CcItem
 * @parent: another #CcItem, parent of @child
 *
 * Changes the position of @child relative to @parent by 1.
 */
void
cc_item_raise(CcItem* child, CcItem* parent)
{
	gint position;
	
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(CC_IS_ITEM(parent));
	g_return_if_fail(cc_item_is_child_of(child, parent));

	position = g_list_index(parent->children, child);
	parent->children = g_list_remove(parent->children, child);
	parent->children = g_list_insert(parent->children, child, position+1);
	cc_item_foreach_view(child, ci_update_view, NULL);
}

/**
 * cc_item_lower:
 * @child: a #CcItem
 * @parent: another #CcItem, parent of @child
 *
 * Changes the position of @child relative to @parent by -1.
 */
void
cc_item_lower(CcItem* child, CcItem* parent)
{
	gint position;
	
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(CC_IS_ITEM(parent));
	g_return_if_fail(cc_item_is_child_of(child, parent));

	position = g_list_index(parent->children, child);
	parent->children = g_list_remove(parent->children, child);
	parent->children = g_list_insert(parent->children, child, position-1);
	cc_item_foreach_view(child, ci_update_view, NULL);
}

/**
 * cc_item_lower_to_bottom:
 * @child: a #CcItem
 * @parent: another #CcItem, parent of @child
 *
 * Changes the position of @child relative to @parent to the bottom layer.
 */
void
cc_item_lower_to_bottom(CcItem* child, CcItem* parent)
{
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(CC_IS_ITEM(parent));
	g_return_if_fail(cc_item_is_child_of(child, parent));
<commit_msg>Updated the API reference for the new functions<commit_after>/* this file is part of libccc, criawips' cairo-based canvas
 *
 * AUTHORS
 *       <NAME>        <<EMAIL>>
 *
 * Copyright (C) 2005 <NAME>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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
 */

#include <ccc/cc-item.h>

#include <string.h>
#include <gdk/gdk.h>
#include <gdk/gdkenumtypes.h>
#include <gtk/gtktypebuiltins.h>
#include <ccc/cc-marshallers.h>
#include <ccc/cc-item-view.h>
#include <ccc/cc-utils.h>
#define CDEBUG_TYPE cc_item_get_type
#include <cdebug/cdebug.h>
#include "glib-helpers.h"

enum {
	ALL_BOUNDS_CHANGED,
	BUTTON_PRESS_EVENT,
	BUTTON_RELEASE_EVENT,
	ENTER_NOTIFY_EVENT,
	EVENT,
	FOCUS,
	FOCUS_ENTER,
	FOCUS_LEAVE,
	ITEM_DIRTY,
	KEY_PRESS_EVENT,
	KEY_RELEASE_EVENT,
	LEAVE_NOTIFY_EVENT,
	MOTION_NOTIFY_EVENT,

	ITEM_ADDED,
	ITEM_REMOVED,

	VIEW_REGISTER,
	VIEW_UNREGISTER,

	N_SIGNALS
};

guint cc_item_signals[N_SIGNALS] = {0};

static void ci_init_item_view(CcItemViewIface* iface);

G_DEFINE_TYPE_WITH_CODE(CcItem, cc_item, G_TYPE_INITIALLY_UNOWNED,
			G_IMPLEMENT_INTERFACE(CC_TYPE_ITEM_VIEW, ci_init_item_view));

/**
 * CcItemFlags:
 * @CC_DISPOSED: specifies whether dispose has already been run on an item.
 * @CC_GRID_ALIGNED: specifies whether an item should be rendered to match an
 * existing grid.
 */

/**
 * CC_ITEM_FLAGS:
 * @i: a #CcItem
 *
 * Get the flags which are set on the #CcItem @i.
 *
 * Returns the mask of flags that are set on @i.
 */

/**
 * CC_ITEM_SET_FLAGS:
 * @i: a #CcItem
 * @m: a CcItemFlags mask
 *
 * Sets on @i the #CcItemFlags that have been specified in @m.
 */

/**
 * CC_ITEM_UNSET_FLAGS:
 * @i: a #CcItem
 * @m: a #CcItemFlags mask
 *
 * Unsets from @i the #CcItemFlags that have been specified in @m.
 */

/**
 * CC_ITEM_DISPOSED:
 * @i: a #CcItem
 *
 * Queries the #CcItem @i whether it has the %CC_DISPOSED flag set. This is
 * very useful in the dispose handler for the GObject derived types. This flag
 * gets set automatically if you chain up your dispose calls correctly.
 *
 * Returns %TRUE is the flag was set, %FALSE if not.
 */

/**
 * CC_ITEM_GRID_ALIGNED:
 * @i: a #CcItem
 *
 * Queries the #CcItem @i whether it has the %CC_GRID_ALIGNED flag set.
 *
 * Returns %TRUE if the flag was set, %FALSE if not.
 */

/**
 * cc_item_new:
 *
 * Create a new #CcItem that can be used to group several items together.
 *
 * Returns a new #CcItem.
 */
CcItem*
cc_item_new(void) {
	return g_object_new(CC_TYPE_ITEM, NULL);
}

/**
 * cc_item_append:
 * @self: a #CcItem
 * @child: another #CcItem
 *
 * Insert an item at the end of this item. A #CcItem can serve as a container
 * for several items.
 */
void
cc_item_append(CcItem* self, CcItem* child) {
	cc_item_insert(self, child, -1);
}

/**
 * cc_item_add_view:
 * @self: a #CcItem
 * @view: a #CcView
 *
 * Registers a view at the item. The item and its children can now set up data
 * for this view.
 *
 * This function should not be called by application code. Use
 * cc_item_view_register() instead.
 */
void
cc_item_add_view(CcItem* self, CcView* view) {
	g_return_if_fail(CC_IS_ITEM(self));
	g_return_if_fail(CC_IS_VIEW(view));

	self->views = g_list_prepend(self->views, view);
	// FIXME: add a weak reference on the view, and then add a FIXME for
	// removal in _remove_view()
	g_signal_emit(self, cc_item_signals[VIEW_REGISTER], 0, view);
}

/**
 * cc_item_remove_view:
 * @self: a #CcItem
 * @view: a #CcView
 *
 * Unregisters a view at the item. The item and the children can now free data
 * kept for this view.
 *
 * This function should not be called by application code. Use
 * cc_item_view_unregister() instead.
 */
void
cc_item_remove_view(CcItem* self, CcView* view) {
	g_return_if_fail(CC_IS_ITEM(self));
	g_return_if_fail(CC_IS_VIEW(view));

	self->views = g_list_remove(self->views, view);
	// FIXME: add an extra check to make sure that the view is definitely
	// not in the list anymore (and disable it with G_DISABLE_CHECKS)
	g_signal_emit(self, cc_item_signals[VIEW_UNREGISTER], 0, view);
}

static void
ci_update_all_bounds(CcView const* view, CcItem* self) {
	CcDRect const* all_bounds      = cc_item_get_all_bounds(self, view);
	CcDRect const* bounds          = cc_hash_map_lookup(self->bounds, view);
	CcDRect const* children_bounds = cc_hash_map_lookup(self->children_bounds, view);

	g_return_if_fail(CC_IS_VIEW(view));

	if(G_UNLIKELY(!bounds && !children_bounds && all_bounds)) {
		cc_hash_map_remove(self->all_bounds, self);
		g_signal_emit(self, cc_item_signals[ALL_BOUNDS_CHANGED], 0, view, NULL);
	} else if(G_LIKELY(bounds || children_bounds)) {
		CcDRect new_bounds;
		if(bounds && children_bounds) {
			cc_d_rect_union(*bounds, *children_bounds, &new_bounds);
		} else if(bounds) {
			new_bounds = *bounds;
		} else {
			new_bounds = *children_bounds;
		}

		if(!all_bounds || !cc_d_rect_equal(*all_bounds, new_bounds)) {
			cc_hash_map_remove(self->all_bounds, view);
			cc_hash_map_insert(self->all_bounds, (gpointer)view, cc_d_rect_copy(&new_bounds));
			g_signal_emit(self, cc_item_signals[ALL_BOUNDS_CHANGED], 0, view, &new_bounds);
		}
	}
}

void
cc_item_bounds_changed(CcItem* self, CcView const* view) {
	g_return_if_fail(CC_IS_ITEM(self));
	g_return_if_fail(CC_IS_VIEW(view));

	ci_update_all_bounds(view, self);
}

/**
 * cc_item_dirty:
 * @self: a #CcItem
 * @view: a #CcView
 * @dirty_region: the region that needs to be redrawn
 *
 * Let emit a signal to tell @view that it needs to be redrawn.
 */
void
cc_item_dirty(CcItem* self, CcView const* view, CcDRect dirty_region) {
	g_signal_emit(self, cc_item_signals[ITEM_DIRTY], 0, view, &dirty_region);
}

/**
 * cc_item_distance:
 * @self: a #CcItem
 * @x: the x coordinate to query for
 * @y: the y coordinate to query for
 * @found: the return location for a found #CcItem
 *
 * Calculates the distance from @self to (@x,@y). If (@x,@y) is within @self or
 * one of its children @found is set to the matching element.
 *
 * Returns the distance between @self (or one of its children) to (@x,@y).
 */
gdouble
cc_item_distance(CcItem* self, gdouble x, gdouble y, CcItem** found) {
	gdouble distance = 0.0;
	g_return_val_if_fail(CC_IS_ITEM(self), G_MAXDOUBLE);
	g_return_val_if_fail(CC_ITEM_GET_CLASS(self)->distance, G_MAXDOUBLE);
	g_return_val_if_fail(found, G_MAXDOUBLE);
	g_return_val_if_fail(!CC_IS_ITEM(*found), G_MAXDOUBLE);

	distance = CC_ITEM_GET_CLASS(self)->distance(self, x, y, found);

#ifndef G_DISABLE_CHECKS
	// enable this on experimental builds, but not on performance builds
	if(distance <= 0.0 && !CC_IS_ITEM(*found)) {
		g_warning("%sClass->distance() should set *found if the returned distance is <= 0.0",
			  G_OBJECT_TYPE_NAME(self));
		*found = self;
	} else if(distance > 0 && CC_IS_ITEM(*found)) {
		g_warning("%sClass->distance() should not set *found if distance is larger than 0.0",
			  G_OBJECT_TYPE_NAME(self));
		*found = NULL;
	}
#endif

	return distance;
}

static void
ci_foreach_view(CcView* view, gpointer self_func_and_data[3]) {
	CcItem   * self = self_func_and_data[0];
	CcItemFunc func = self_func_and_data[1];
	gpointer   data = self_func_and_data[2];

	func(self, view, data);
}

void
cc_item_foreach_view(CcItem* self, CcItemFunc func, gpointer data) {
	CcItem* item;
	gpointer self_func_and_data[] = {self, func, data};
	for(item = self; item; item = item->parent) {
		g_list_foreach(item->views, G_FUNC(ci_foreach_view), self_func_and_data);
	}
}

/**
 * cc_item_get_all_bounds:
 * @self: a #CcItem
 * @view: a #CcView
 *
 * Get the bounds of @self as it would e displayed by @view (including all of its children).
 *
 * Returns the bounds of the item, may be %NULL (in this case the item is not
 * tied visible area).
 */
CcDRect const*
cc_item_get_all_bounds(CcItem const* self, CcView const* view) {
	g_return_val_if_fail(CC_IS_ITEM(self), NULL);
	g_return_val_if_fail(CC_IS_VIEW(view), NULL);

	return cc_hash_map_lookup(self->all_bounds, view);
}

void
cc_item_grab_focus(CcItem* self, CcView* view) {
	GdkEventFocus ev = {
		GDK_FOCUS_CHANGE,
		NULL,
		FALSE,
		FALSE
	};
	gboolean retval;
	CcItem* focus = cc_view_get_focus(view);

	if(focus) {
		g_signal_emit(focus, cc_item_signals[FOCUS_LEAVE], 0, view, &ev, &retval);
	}

	cc_view_set_focus(view, self);
	ev.in = TRUE;
	g_signal_emit(self, cc_item_signals[FOCUS_ENTER], 0, view, &ev, &retval);
}

static void
ci_add_parent_view_to_child(CcItem* self, CcView* view, gpointer data) {
	cc_item_view_register(CC_ITEM_VIEW(view), CC_ITEM(data));
}

/**
 * cc_item_insert:
 * @self: a #CcItem
 * @child: another #CcItem
 * @position: the position to insert the child at (0 means at the bottom,
 * bigger numbers mean higher layers, -1 means top layer)
 *
 * Inserts @child at @position into @self.
 */
void
cc_item_insert(CcItem* self, CcItem* child, gint position) {
	g_return_if_fail(CC_IS_ITEM(self));
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(!CC_IS_ITEM(child->parent));

	self->children = g_list_insert(self->children, g_object_ref_sink(child), position);
	child->parent = self;
	cc_item_view_register(CC_ITEM_VIEW(self), child);
	position = g_list_index(self->children, child);
	cc_item_foreach_view(self, ci_add_parent_view_to_child, child);

	g_signal_emit(self, cc_item_signals[ITEM_ADDED], 0, position, child);
}

/**
 * cc_item_is_child_of:
 * @child: a #CcItem
 * @parent: another #CcItem
 *
 * Find out whether @child is a child of @parent.
 *
 * Returns %TRUE is @child is a child of @parent.
 */
gboolean
cc_item_is_child_of(CcItem const* child, CcItem const* parent) {
	gboolean retval = FALSE;

	g_return_val_if_fail(CC_IS_ITEM(child), FALSE);
	g_return_val_if_fail(CC_IS_ITEM(parent), FALSE);

	for(child = child->parent; CC_IS_ITEM(child) ; child = child->parent) {
		if(child == parent) {
			retval = TRUE;
			break;
		}
	}

	return retval;
}

static void
ci_remove_parent_view_from_child(CcItem* self, CcView* view, gpointer data) {
	CcItem* child = CC_ITEM(data);

	cc_item_view_unregister(CC_ITEM_VIEW(view), child);
}

/**
 * cc_item_remove:
 * @self: a #CcItem
 * @child: another #CcItem
 *
 * Removes @child from @self. Does nothing if @child was not a child of @self
 * or if @child is a child of one of @self's children.
 */
void
cc_item_remove(CcItem* self, CcItem* child) {
	gint position;
	g_return_if_fail(CC_IS_ITEM(self) && CC_IS_ITEM(child));

	position = g_list_index(self->children, child);
	self->children = g_list_remove(self->children, child);
	cc_item_foreach_view(self, ci_remove_parent_view_from_child, child);
	cc_item_view_unregister(CC_ITEM_VIEW(self), child);

	g_signal_emit(self, cc_item_signals[ITEM_REMOVED], 0, position, child);
	g_object_unref(child);
}

/**
 * cc_item_render:
 * @self: a #CcItem
 * @view: a #Ccview
 * @cr: a cairo context (#cairo_t)
 *
 * Renders @self to @cr which is supposed to be a cairo context of @view as
 * the transformation matrix of @view will be used for rendering.
 */
void
cc_item_render(CcItem* self, CcView* view, cairo_t* cr) {
	g_return_if_fail(CC_IS_ITEM(self));
	g_return_if_fail(CC_IS_VIEW(view));
	g_return_if_fail(cr);

	g_return_if_fail(CC_ITEM_GET_CLASS(self)->render);

	CC_ITEM_GET_CLASS(self)->render(self, view, cr);
}

/**
 * cc_item_set_grid_aligned:
 * @self: a #CcItem
 * @grid_aligned: a gboolean
 *
 * Sets @self to be rendered grid-aligned if @grid_aligned is %TRUE, sets it to
 * be allowed to render fuzzyly if %FALSE.
 */
// FIXME: link to the grid align section of the reference
void
cc_item_set_grid_aligned(CcItem* self, gboolean grid_aligned) {
	if(grid_aligned) {
		CC_ITEM_SET_FLAGS(self, CC_GRID_ALIGNED);
	} else {
		CC_ITEM_UNSET_FLAGS(self, CC_GRID_ALIGNED);
	}
}

static void
ci_update_bounds_per_view(CcView* view, gpointer* self_and_data) {
	g_return_if_fail(CC_IS_VIEW(view));
	g_return_if_fail(CC_IS_ITEM(self_and_data[0]));

	if(CC_ITEM_GET_CLASS(self_and_data[0])->update_bounds) {
		CC_ITEM_GET_CLASS(self_and_data[0])->update_bounds(self_and_data[0], view, self_and_data[1]);
	}
}

void
cc_item_update_bounds(CcItem* self, gpointer data) {
	CcItem* item;
	gpointer self_and_data[2];

	g_return_if_fail(CC_IS_ITEM(self));

	self_and_data[0] = self;
	self_and_data[1] = data;
	for(item = self; item; item = item->parent) {
		g_list_foreach(item->views, G_FUNC(ci_update_bounds_per_view), self_and_data);
	}
}

void
cc_item_update_bounds_for_view(CcItem* self, CcView* view) {
	gpointer self_and_data[] = {
		self, NULL
	};
	ci_update_bounds_per_view(view, self_and_data);
}

static void
ci_update_view(CcItem* self, CcView* view, gpointer data) {
	CcDRect const *dirty_region = cc_item_get_all_bounds(self, view);
	g_return_if_fail(dirty_region != NULL);
	cc_item_dirty(self, view, *dirty_region);
}

/**
 * cc_item_set_position:
 * @child: a #CcItem
 * @parent: another #CcItem, parent of @child
 * @position: the new position of the #CcItem (0 means at the bottom,
 * bigger numbers mean higher layers, -1 means top layer)
 *
 * Changes the position of @child relative to @parent.
 */
void
cc_item_set_position(CcItem* child, CcItem* parent, gint position)
{
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(CC_IS_ITEM(parent));
	g_return_if_fail(cc_item_is_child_of(child, parent));

	parent->children = g_list_remove(parent->children, child);
	parent->children = g_list_insert(parent->children, child, position);
	cc_item_foreach_view(child, ci_update_view, NULL);
}

/**
 * cc_item_raise_to_top:
 * @child: a #CcItem
 * @parent: another #CcItem, parent of @child
 *
 * Raises an @child to the top. It will be displayed over all of the other
 * children of @parent.
 */
void
cc_item_raise_to_top(CcItem* child, CcItem* parent)
{
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(CC_IS_ITEM(parent));
	g_return_if_fail(cc_item_is_child_of(child, parent));

	parent->children = g_list_remove(parent->children, child);
	parent->children = g_list_append(parent->children, child);
	cc_item_foreach_view(child, ci_update_view, NULL);
}

/**
 * cc_item_raise:
 * @child: a #CcItem
 * @parent: another #CcItem, parent of @child
 *
 * Raises @child by one level.
 */
void
cc_item_raise(CcItem* child, CcItem* parent)
{
	gint position;
	
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(CC_IS_ITEM(parent));
	g_return_if_fail(cc_item_is_child_of(child, parent));

	position = g_list_index(parent->children, child);
	parent->children = g_list_remove(parent->children, child);
	parent->children = g_list_insert(parent->children, child, position+1);
	cc_item_foreach_view(child, ci_update_view, NULL);
}

/**
 * cc_item_lower:
 * @child: a #CcItem
 * @parent: another #CcItem, parent of @child
 *
 * Lowers @child by one level.
 */
void
cc_item_lower(CcItem* child, CcItem* parent)
{
	gint position;
	
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(CC_IS_ITEM(parent));
	g_return_if_fail(cc_item_is_child_of(child, parent));

	position = g_list_index(parent->children, child);
	parent->children = g_list_remove(parent->children, child);
	parent->children = g_list_insert(parent->children, child, position-1);
	cc_item_foreach_view(child, ci_update_view, NULL);
}

/**
 * cc_item_lower_to_bottom:
 * @child: a #CcItem
 * @parent: another #CcItem, parent of @child
 *
 * Lowers @child to be displayed behind all the other children of @parent.
 */
void
cc_item_lower_to_bottom(CcItem* child, CcItem* parent)
{
	g_return_if_fail(CC_IS_ITEM(child));
	g_return_if_fail(CC_IS_ITEM(parent));
	g_return_if_fail(cc_item_is_child_of(child, parent));

{'fraction_non_alphanumeric': 0.11779518746502518, 'fraction_numerical': 0.003668469812845862, 'mean_word_length': 5.122382946326608, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 131, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4281342', 'n_tokens_mistral': 12463, 'n_tokens_neox': 12223, 'n_words': 3021}