aboutsummaryrefslogtreecommitdiffstats
path: root/documentation/components/components-treegrid.asciidoc
blob: b0e28fee5fe420b05e9059f4604cb0845a718048 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
---
title: TreeGrid
order: 25
layout: page
---

[[components.treegrid]]
= TreeGrid

ifdef::web[]
[.sampler]
image:{live-demo-image}[alt="Live Demo", link="http://demo.vaadin.com/sampler/#ui/grids-and-trees/treegrid"]
endif::web[]

IMPORTANT: The [classname]#TreeGrid# component is currently being developed and only available in the Framework 8.1 prerelease versions, starting from 8.1.0.alpha1.

[[components.treegrid.overview]]
== Overview

[classname]#TreeGrid# is for displaying hierarchical tabular data laid out in rows and columns.
It is otherwise identical to the [classname]#Grid# component, but it adds the possibility to show
hierarchical data, allowing the user to expand and collapse nodes to show or hide data.

See the documentation for <<dummy/../../../framework/components/components-grid.asciidoc#components.grid,"Grid">> for all the shared features between [classname]#Grid# and [classname]#TreeGrid#.

[[figure.components.treegrid.basic]]
.A [classname]#TreeGrid#
image::img/tree-grid-basic.png[width=70%, scaledwidth=100%]

[[components.treegrid.data]]
== Binding to Data

[classname]#TreeGrid# is used by binding it to a hierarchical data provider. The data provider can be based on in-memory or back end data. For in-memory data, the [classname]#InMemoryHierarchicalDataProvider# can be used, and for loading data from a back end, you need to implement three methods from the [interfacename]#HierarchicalDataProvider# interface. Usage of both data providers is described in
<<dummy/../../../framework/datamodel/datamodel-hierarchical.asciidoc#datamodel.hierarchical,"Hierarchical Data">>.

Populating a [classname]#TreeGrid# with in-memory data can be done as follows

[source, java]
----
Project rootProject = getRootRroject();

HierarchyData<Project> data = new HierarchyData<>();
// add a root level item with null parent
data.addItem(null, rootProject);

// Add all children for root item
rootProject.flattened().forEach(
       project -> data.addItems(project, project.getSubProjects()));

TreeGrid<Project> treeGrid = new TreeGrid<>();
treeGrid.setDataProvider(new InMemoryHierarchicalDataProvider<>(data));

// the first column gets the hierarchy indicator by default
treeGrid.addColumn(Project::getName).setCaption("Project Name");
treeGrid.addColumn(Project::getHoursDone).setCaption("Hours Done");
treeGrid.addColumn(Project::getdLastModified).setCaption("Last Modified");
----

The [classname]#HierarchyData# class can be used to build the hierarchical data structure,
and it can then be passed on to [classname]#InMemoryHierarchicalDataProvider#. It is simply a hierarchical
collection, that the data provider uses to populate the [classname]#TreeGrid#.

The [methodname]#setItems# method in [classname]#TreeGrid# can be used to set the root level items. Internally
an [classname]#InMemoryHierarchicalDataProvider# with [classname]#HierarchyData# is used. If at any time you want to modify the in-memory data in the grid, you may do it as follows

[source, java]
----
InMemoryHierarchicalDataProvider<Project> dataProvider = (InMemoryHierarchicalDataProvider<Project>) treeGrid.getDataProvider();

HierarchyData<Project> data = dataProvider.getData();
// add new items
data.addItem(null, newProject);
data.addItems(newProject, newProject.getChildren());

// after adding / removing data, data provider needs to be refreshed
dataProvider.refreshAll();
----

Note that for adding or removing nodes, you always need to call the [methodname]#refreshAll# method in the data provider you are using. The [methodname]#refreshItem# method can only be used when just the data for that item is updated, but not for updates that add or remove items.

== Changing the Hierarchy Column

By default, the [classname]#TreeGrid# shows the hierarchy indicator by default in the first column of the grid.
You can change it with with the [methodname]#setHierarchyColumn#, method, that takes as a parameter the column's ID specified with the [methodname]#setId# method in [classname]#Column#.

[source, java]
----
// the first column gets the hierarchy indicator by default
treeGrid.addColumn(Project::getLastModified).setCaption("Last Modified");
treeGrid.addColumn(Project::getName).setCaption("Project Name").setId("name");
treeGrid.addColumn(Project::getHoursDone).setCaption("Hours Done");

treeGrid.setHierarchyColumn("name");
----
iteral.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/*
 * Copyright (c) 2009-2015, Vsevolod Stakhov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "fstring.h"
#include "str_util.h"

static const gsize default_initial_size = 48;
/* Maximum size when we double the size of new string */
static const gsize max_grow = 1024 * 1024;

#define fstravail(s) ((s)->allocated - (s)->len)
static rspamd_fstring_t * rspamd_fstring_grow (rspamd_fstring_t *str,
		gsize needed_len) G_GNUC_WARN_UNUSED_RESULT;

rspamd_fstring_t *
rspamd_fstring_new (void)
{
	rspamd_fstring_t *s;

	g_assert (posix_memalign ((void**)&s, 16, default_initial_size + sizeof (*s)) == 0);
	s->len = 0;
	s->allocated = default_initial_size;

	return s;
}

rspamd_fstring_t *
rspamd_fstring_sized_new (gsize initial_size)
{
	rspamd_fstring_t *s;
	gsize real_size = MAX (default_initial_size, initial_size);

	g_assert (posix_memalign ((void **)&s, 16, real_size + sizeof (*s)) == 0);
	s->len = 0;
	s->allocated = real_size;

	return s;
}

rspamd_fstring_t *
rspamd_fstring_new_init (const gchar *init, gsize len)
{
	rspamd_fstring_t *s;
	gsize real_size = MAX (default_initial_size, len);

	g_assert (posix_memalign ((void **) &s, 16, real_size + sizeof (*s)) == 0);
	s->len = len;
	s->allocated = real_size;
	memcpy (s->str, init, len);

	return s;
}

rspamd_fstring_t *
rspamd_fstring_assign (rspamd_fstring_t *str, const gchar *init, gsize len)
{
	gsize avail = str->allocated;

	if (avail < len) {
		str = rspamd_fstring_grow (str, len);
	}

	if (len > 0) {
		memcpy (str->str, init, len);
	}

	str->len = len;

	return str;
}

void
rspamd_fstring_free (rspamd_fstring_t *str)
{
	free (str);
}

static rspamd_fstring_t *
rspamd_fstring_grow (rspamd_fstring_t *str, gsize needed_len)
{
	gsize newlen;
	gpointer nptr;

	newlen = str->len + needed_len;

	/*
	 * Stop exponential grow at some point, since it might be slow for the
	 * vast majority of cases
	 */
	if (newlen < max_grow) {
		newlen *= 2;
	}
	else {
		newlen += max_grow;
	}

	nptr = realloc (str, newlen + sizeof (*str));

	if (nptr == NULL) {
		/* Avoid memory leak */
		free (str);
		g_assert (nptr);
	}

	str = nptr;
	str->allocated = newlen;

	return str;
}

rspamd_fstring_t *
rspamd_fstring_append (rspamd_fstring_t *str, const char *in, gsize len)
{
	gsize avail = fstravail (str);

	if (avail < len) {
		str = rspamd_fstring_grow (str, len);
	}

	memcpy (str->str + str->len, in, len);
	str->len += len;

	return str;
}

rspamd_fstring_t *
rspamd_fstring_append_chars (rspamd_fstring_t *str,
		char c, gsize len)
{
	gsize avail = fstravail (str);

	if (avail < len) {
		str = rspamd_fstring_grow (str, len);
	}

	memset (str->str + str->len, c, len);
	str->len += len;

	return str;
}

void
rspamd_fstring_erase (rspamd_fstring_t *str, gsize pos, gsize len)
{
	if (pos < str->len) {
		if (pos + len > str->len) {
			len = str->len - pos;
		}

		if (len == str->len - pos) {
			/* Fast path */
			str->len = pos;
		}
		else {
			memmove (str->str + pos, str->str + pos + len, str->len - pos);
			str->len -= pos;
		}
	}
	else {
		/* Do nothing */
	}
}

char *rspamd_fstring_cstr (const rspamd_fstring_t *str);

/* Compat code */
static guint32
fstrhash_c (gchar c, guint32 hval)
{
	guint32 tmp;
	/*
	 * xor in the current byte against each byte of hval
	 * (which alone gaurantees that every bit of input will have
	 * an effect on the output)
	 */
	tmp = c & 0xFF;
	tmp = tmp | (tmp << 8) | (tmp << 16) | (tmp << 24);
	hval ^= tmp;

	/* add some bits out of the middle as low order bits */
	hval = hval + ((hval >> 12) & 0x0000ffff);

	/* swap most and min significative bytes */
	tmp = (hval << 24) | ((hval >> 24) & 0xff);
	/* zero most and min significative bytes of hval */
	hval &= 0x00ffff00;
	hval |= tmp;
	/*
	 * rotate hval 3 bits to the left (thereby making the
	 * 3rd msb of the above mess the hsb of the output hash)
	 */
	return (hval << 3) + (hval >> 29);
}


/*
 * Return hash value for a string
 */
guint32
rspamd_fstrhash_lc (const rspamd_ftok_t * str, gboolean is_utf)
{
	gsize i;
	guint32 j, hval;
	const gchar *p, *end = NULL;
	gchar t;
	gunichar uc;

	if (str == NULL) {
		return 0;
	}

	p = str->begin;
	hval = str->len;

	if (is_utf) {
		while (end < str->begin + str->len) {
			if (!g_utf8_validate (p, str->len, &end)) {
				return rspamd_fstrhash_lc (str, FALSE);
			}
			while (p < end) {
				uc = g_unichar_tolower (g_utf8_get_char (p));
				for (j = 0; j < sizeof (gunichar); j++) {
					t = (uc >> (j * 8)) & 0xff;
					if (t != 0) {
						hval = fstrhash_c (t, hval);
					}
				}
				p = g_utf8_next_char (p);
			}
			p = end + 1;
		}

	}
	else {
		for (i = 0; i < str->len; i++, p++) {
			hval = fstrhash_c (g_ascii_tolower (*p), hval);
		}
	}

	return hval;
}

gboolean
rspamd_fstring_equal (const rspamd_fstring_t *s1,
		const rspamd_fstring_t *s2)
{
	g_assert (s1 != NULL && s2 != NULL);

	if (s1->len == s2->len) {
		return (memcmp (s1->str, s2->str, s1->len) == 0);
	}

	return FALSE;
}

gint
rspamd_fstring_casecmp (const rspamd_fstring_t *s1,
		const rspamd_fstring_t *s2)
{
	gint ret = 0;

	g_assert (s1 != NULL && s2 != NULL);

	if (s1->len == s2->len) {
		ret = rspamd_lc_cmp (s1->str, s2->str, s1->len);
	}
	else {
		ret = s1->len - s2->len;
	}

	return ret;
}

gint
rspamd_fstring_cmp (const rspamd_fstring_t *s1,
		const rspamd_fstring_t *s2)
{
	g_assert (s1 != NULL && s2 != NULL);

	if (s1->len == s2->len) {
		return memcmp (s1->str, s2->str, s1->len);
	}

	return s1->len - s2->len;
}

gint
rspamd_ftok_casecmp (const rspamd_ftok_t *s1,
		const rspamd_ftok_t *s2)
{
	gint ret = 0;

	g_assert (s1 != NULL && s2 != NULL);

	if (s1->len == s2->len) {
		ret = rspamd_lc_cmp (s1->begin, s2->begin, s1->len);
	}
	else {
		ret = s1->len - s2->len;
	}

	return ret;
}

gint
rspamd_ftok_cmp (const rspamd_ftok_t *s1,
		const rspamd_ftok_t *s2)
{
	g_assert (s1 != NULL && s2 != NULL);

	if (s1->len == s2->len) {
		return memcmp (s1->begin, s2->begin, s1->len);
	}

	return s1->len - s2->len;
}

void
rspamd_fstring_mapped_ftok_free (gpointer p)
{
	rspamd_ftok_t *tok = p;
	rspamd_fstring_t *storage;

	storage = (rspamd_fstring_t *) (tok->begin - 2 * sizeof (gsize));
	rspamd_fstring_free (storage);
	g_slice_free1 (sizeof (*tok), tok);
}

rspamd_ftok_t *
rspamd_ftok_map (const rspamd_fstring_t *s)
{
	rspamd_ftok_t *tok;

	g_assert (s != NULL);

	tok = g_slice_alloc (sizeof (*tok));
	tok->begin = s->str;
	tok->len = s->len;

	return tok;
}