/* vim: set sw=8 ts=8 sts=8 noexpandtab: */
#include "ewl_text_fmt.h"
#include "ewl_private.h"
#include "ewl_macros.h"
#include "ewl_debug.h"
#define FMT_PRINT(fmt) \
{ \
printf("Set current format %d\n" \
"\tindex: %d chars %d bytes\n" \
"\tlength: %d chars %d bytes\n", \
__LINE__, \
fmt->current_node.char_idx, \
fmt->current_node.byte_idx, \
fmt->length.char_len, \
fmt->length.byte_len); \
}
static void ewl_text_fmt_node_free(void *node);
Ewl_Text_Fmt *
ewl_text_fmt_new(Ewl_Text *t)
{
Ewl_Text_Fmt *fmt;
DENTER_FUNCTION(DLEVEL_STABLE);
fmt = NEW(Ewl_Text_Fmt, 1);
if (!fmt) DRETURN_PTR(NULL, DLEVEL_STABLE);
fmt->nodes = ecore_dlist_new();
if (!fmt->nodes)
{
ewl_text_fmt_destroy(fmt);
DRETURN_PTR(NULL, DLEVEL_STABLE);
}
ecore_dlist_set_free_cb(fmt->nodes, ewl_text_fmt_node_free);
fmt->text = t;
DRETURN_PTR(fmt, DLEVEL_STABLE);
}
void
ewl_text_fmt_destroy(Ewl_Text_Fmt *fmt)
{
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
if (fmt->nodes) ecore_dlist_destroy(fmt->nodes);
FREE(fmt);
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
void
ewl_text_fmt_clear(Ewl_Text_Fmt *fmt)
{
Ewl_Text_Fmt_Node *node;
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
while ((node = ecore_dlist_remove_first(fmt->nodes)))
{
if (node->tx) ewl_text_context_release(node->tx);
FREE(node);
}
fmt->current_node.char_idx = 0;
fmt->current_node.byte_idx = 0;
FMT_PRINT(fmt);
fmt->length.char_len = 0;
fmt->length.byte_len = 0;
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
void
ewl_text_fmt_node_prepend(Ewl_Text_Fmt *fmt, Ewl_Text_Fmt_Node *node)
{
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
DCHECK_PARAM_PTR("node", node);
ecore_dlist_prepend(fmt->nodes, node);
/* we prepended so we need to add the nodes lengths to the current
* format position length so we're still in the right spot */
fmt->current_node.char_idx += node->char_len;
fmt->current_node.byte_idx += node->byte_len;
fmt->length.char_len += node->char_len;
fmt->length.byte_len += node->byte_len;
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
void
ewl_text_fmt_node_append(Ewl_Text_Fmt *fmt, Ewl_Text_Fmt_Node *node)
{
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
DCHECK_PARAM_PTR("node", node);
ecore_dlist_append(fmt->nodes, node);
fmt->length.char_len += node->char_len;
fmt->length.byte_len += node->byte_len;
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
void
ewl_text_fmt_node_insert(Ewl_Text_Fmt *fmt, Ewl_Text_Fmt_Node *node,
unsigned int idx)
{
Ewl_Text_Fmt_Node *cur_node, *left, *next;
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
DCHECK_PARAM_PTR("node", node);
cur_node = ecore_dlist_current(fmt->nodes);
/* insert at the current location */
if (fmt->current_node.char_idx == idx)
ecore_dlist_insert(fmt->nodes, node);
/* insert at the end of the current node */
else if ((fmt->current_node.char_idx + cur_node->char_len) == idx)
{
ecore_dlist_next(fmt->nodes);
next = ecore_dlist_current(fmt->nodes);
if (next)
{
ecore_dlist_insert(fmt->nodes, node);
/* update our index as we're currently on the next node */
fmt->current_node.char_idx += cur_node->char_len;
fmt->current_node.byte_idx += cur_node->byte_len;
}
else
{
ecore_dlist_append(fmt->nodes, node);
ecore_dlist_goto_last(fmt->nodes);
/* we haven't incremented the sizes yet so we're at
* the end of the last set of nodes */
fmt->current_node.char_idx = fmt->length.char_len;
fmt->current_node.byte_idx = fmt->length.byte_len;
FMT_PRINT(fmt);
}
}
else
{
/* insert into the middle of the current node */
left = ewl_text_fmt_node_new(node->tx,
idx - fmt->current_node.char_idx, 0);
ewl_text_fmt_char_to_byte(fmt, idx,
idx - fmt->current_node.char_idx,
NULL, &(left->byte_len));
cur_node->char_len -= left->char_len;
cur_node->byte_len -= left->byte_len;
/* insert inserts to the right, so move past the current node so we
* can insert the two new ones. If we have a node to the left we
* insert the left node then the new node so the new one is in the
* middle. If there is no node to the left we insert the new node
* then the left node. */
ecore_dlist_next(fmt->nodes);
next = ecore_dlist_current(fmt->nodes);
if (next)
{
ecore_dlist_insert(fmt->nodes, left);
ecore_dlist_insert(fmt->nodes, node);
}
else
{
ecore_dlist_append(fmt->nodes, node);
ecore_dlist_goto_last(fmt->nodes);
ecore_dlist_append(fmt->nodes, left);
}
fmt->current_node.char_idx += cur_node->char_len;
fmt->current_node.byte_idx += cur_node->byte_len;
}
fmt->length.char_len += node->char_len;
fmt->length.byte_len += node->byte_len;
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
void
ewl_text_fmt_node_delete(Ewl_Text_Fmt *fmt, unsigned int idx,
unsigned int char_len)
{
Ewl_Text_Fmt_Node *node;
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
node = ecore_dlist_current(fmt->nodes);
while (char_len > 0)
{
unsigned int available;
/* how much space is available in this node to be removed */
available = node->char_len - (idx - fmt->current_node.char_idx);
if (available > char_len) available = char_len;
node->char_len -= available;
if (node->char_len > 0)
{
node->byte_len = 0;
ewl_text_fmt_char_to_byte(fmt, idx,
node->char_len,
NULL, &(node->byte_len));
ecore_dlist_next(fmt->nodes);
fmt->current_node.char_idx += node->char_len;
fmt->current_node.byte_idx += node->byte_len;
node = ecore_dlist_current(fmt->nodes);
}
else
{
ecore_dlist_remove(fmt->nodes);
ewl_text_fmt_node_free(node);
node = ecore_dlist_current(fmt->nodes);
}
char_len -= available;
}
/* make sure we've still got a current node. if not reset to the
* first node in the list */
node = ecore_dlist_current(fmt->nodes);
if (!node)
{
ecore_dlist_goto_first(fmt->nodes);
fmt->current_node.char_idx = 0;
fmt->current_node.byte_idx = 0;
FMT_PRINT(fmt);
}
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
unsigned int
ewl_text_fmt_node_count_get(Ewl_Text_Fmt *fmt)
{
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR_RET("fmt", fmt, 0);
DRETURN_INT(ecore_dlist_nodes(fmt->nodes), DLEVEL_STABLE);
}
Ewl_Text_Fmt_Node *
ewl_text_fmt_get_last(Ewl_Text_Fmt *fmt)
{
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR_RET("fmt", fmt, NULL);
DRETURN_PTR(ecore_dlist_goto_last(fmt->nodes),DLEVEL_STABLE);
}
Ewl_Text_Fmt_Node *
ewl_text_fmt_get_first(Ewl_Text_Fmt *fmt)
{
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR_RET("fmt", fmt, NULL);
DRETURN_PTR(ecore_dlist_goto_first(fmt->nodes),DLEVEL_STABLE);
}
Ewl_Text_Fmt_Node *
ewl_text_fmt_get_current(Ewl_Text_Fmt *fmt)
{
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR_RET("fmt", fmt, NULL);
DRETURN_PTR(ecore_dlist_current(fmt->nodes),DLEVEL_STABLE);
}
Ewl_Text_Fmt_Node *
ewl_text_fmt_get(Ewl_Text_Fmt *fmt, unsigned int idx)
{
Ewl_Text_Fmt_Node *node, *current_node;
unsigned int cur_byte, cur_char;
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR_RET("fmt", fmt, NULL);
cur_char = fmt->current_node.char_idx;
cur_byte = fmt->current_node.byte_idx;
current_node = ecore_dlist_current(fmt->nodes);
ewl_text_fmt_goto(fmt, idx);
node = ecore_dlist_current(fmt->nodes);
ecore_dlist_goto(fmt->nodes, current_node);
fmt->current_node.char_idx = cur_char;
fmt->current_node.byte_idx = cur_byte;
FMT_PRINT(fmt);
DRETURN_PTR(node, DLEVEL_STABLE);
}
void
ewl_text_fmt_goto(Ewl_Text_Fmt *fmt, unsigned int idx)
{
Ewl_Text_Fmt_Node *node;
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
/* set to start of text */
if (idx == 0)
{
fmt->current_node.char_idx = 0;
fmt->current_node.byte_idx = 0;
FMT_PRINT(fmt);
ecore_dlist_goto_first(fmt->nodes);
}
/* set to end of text */
else if (idx >= fmt->length.char_len)
{
node = ecore_dlist_goto_last(fmt->nodes);
fmt->current_node.char_idx =
fmt->length.char_len - node->char_len;
fmt->current_node.byte_idx =
fmt->length.byte_len - node->byte_len;
printf("Node len: %d chars %d bytes\n", node->char_len,
node->byte_len);
FMT_PRINT(fmt);
}
/* set to after our current position
* walk forward until we cover the given position */
else if (fmt->current_node.char_idx < idx)
{
node = ecore_dlist_current(fmt->nodes);
while ((node->char_len + fmt->current_node.char_idx) < idx)
{
fmt->current_node.char_idx += node->char_len;
fmt->current_node.byte_idx += node->byte_len;
ecore_dlist_next(fmt->nodes);
node = ecore_dlist_current(fmt->nodes);
if (!node) break;
}
}
/* set before our position
* walk back until we're less then the given position */
else
{
while (fmt->current_node.char_idx > idx)
{
ecore_dlist_previous(fmt->nodes);
node = ecore_dlist_current(fmt->nodes);
if (!node) break;
fmt->current_node.char_idx -= node->char_len;
fmt->current_node.byte_idx -= node->byte_len;
}
}
node = ecore_dlist_current(fmt->nodes);
if (!node)
{
DWARNING("Unable to place cursor at %d", idx);
ecore_dlist_goto_first(fmt->nodes);
fmt->current_node.char_idx = 0;
fmt->current_node.byte_idx = 0;
FMT_PRINT(fmt);
}
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
/**
* @param t: The text to dump the formatting from
* @return Returns no value
* @brief Prints out the formatting information for the given text object
*/
void
ewl_text_fmt_dump(Ewl_Text_Fmt *fmt)
{
Ewl_Text_Fmt_Node *node, *cur_node;
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
cur_node = ecore_dlist_current(fmt->nodes);
ecore_dlist_goto_first(fmt->nodes);
while ((node = ecore_dlist_next(fmt->nodes)))
{
printf("%d chars, %d bytes\n", node->char_len, node->byte_len);
ewl_text_context_print(node->tx, " ");
printf("\n\n");
}
ecore_dlist_goto(fmt->nodes, cur_node);
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
/* This will determine the number of bytes to get to char_pos in the text
* and, if needed will get the number of bytes between char_pos and
* char_pos + char_len
*
* This _HAS_ to leave the list with the same current pointer is when it
* started */
void
ewl_text_fmt_char_to_byte(Ewl_Text_Fmt *fmt,
unsigned int char_idx, unsigned int char_len,
unsigned int *byte_idx, unsigned int *byte_len)
{
Ewl_Text *t;
Ewl_Text_Fmt_Node *current_node, *node = NULL;
unsigned int cur_char_idx = 0, bidx = 0;
void *(*move)(Ecore_DList *list);
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
t = fmt->text;
current_node = ecore_dlist_current(fmt->nodes);
cur_char_idx = fmt->current_node.char_idx;
bidx = fmt->current_node.byte_idx;
printf("cur_node %p char_idx %u cur_char_idx %u\n", current_node, char_idx, cur_char_idx);
if (cur_char_idx > 10000)
ewl_text_fmt_dump(fmt);
/* first step is to find the actual node that this text represents */
if (fmt->current_node.char_idx < char_idx)
{
/* walk forward until we cover the given position */
node = ecore_dlist_current(fmt->nodes);
while ((node->char_len + cur_char_idx) < char_idx)
{
cur_char_idx += node->char_len;
bidx += node->byte_len;
ecore_dlist_next(fmt->nodes);
node = ecore_dlist_current(fmt->nodes);
if (!node) break;
}
move = ecore_dlist_previous;
if (!node)
{
node = ecore_dlist_goto_last(fmt->nodes);
fmt->current_node.char_idx = fmt->length.char_len - node->char_len;
fmt->current_node.byte_idx = fmt->length.byte_len - node->byte_len;
FMT_PRINT(fmt);
}
}
else
{
/* walk back until we're less then the given position */
while (cur_char_idx > char_idx)
{
ecore_dlist_previous(fmt->nodes);
node = ecore_dlist_current(fmt->nodes);
if (!node) break;
cur_char_idx -= node->char_len;
bidx -= node->byte_len;
}
move = ecore_dlist_next;
if (!node)
{
node = ecore_dlist_goto_first(fmt->nodes);
fmt->current_node.char_idx = 0;
fmt->current_node.byte_idx = 0;
FMT_PRINT(fmt);
}
}
/* we still need to count within this node */
while (cur_char_idx < char_idx)
{
unsigned int bytes;
ewl_text_text_next_char(t->text + bidx, &bytes);
bidx += bytes;
cur_char_idx ++;
}
if (byte_len)
{
if (char_len == 0) *byte_len = 0;
else
{
char *txt;
txt = t->text + bidx;
cur_char_idx = 0;
while (cur_char_idx < char_len)
{
unsigned int bytes;
txt = ewl_text_text_next_char(txt, &bytes);
*byte_len += bytes;
cur_char_idx ++;
}
}
}
if (byte_idx) *byte_idx = bidx;
node = ecore_dlist_current(fmt->nodes);
while (node != current_node)
{
move(fmt->nodes);
node = ecore_dlist_current(fmt->nodes);
}
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
/* This will determine the number of chars to get to byte_idx in the text
* and, if needed will get the number of chars between byte_idx and
* byte_idx + byte_len */
void
ewl_text_fmt_byte_to_char(Ewl_Text_Fmt *fmt,
unsigned int byte_idx, unsigned int byte_len,
unsigned int *char_idx, unsigned int *char_len)
{
Ewl_Text *t;
Ewl_Text_Fmt_Node *current_node, *node = NULL;
unsigned int byte_count = 0, cidx = 0;
void *(*move)(Ecore_DList *list);
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
t = fmt->text;
current_node = ecore_dlist_current(fmt->nodes);
if (fmt->current_node.byte_idx < byte_idx)
{
byte_count = fmt->current_node.byte_idx;
cidx = fmt->current_node.char_idx;
/* walk forward until we cover the given position */
node = ecore_dlist_current(fmt->nodes);
while ((node->byte_len + byte_count) < byte_idx)
{
byte_count += node->byte_len;
cidx += node->char_len;
ecore_dlist_next(fmt->nodes);
node = ecore_dlist_current(fmt->nodes);
if (!node) break;
}
if (!node)
{
ecore_dlist_goto_last(fmt->nodes);
fmt->current_node.char_idx = fmt->length.char_len - node->char_len;
fmt->current_node.byte_idx = fmt->length.byte_len - node->byte_len;
FMT_PRINT(fmt);
}
move = ecore_dlist_previous;
}
else
{
/* walk back until we're less then the given position */
node = ecore_dlist_current(fmt->nodes);
while (byte_count > byte_idx)
{
ecore_dlist_previous(fmt->nodes);
node = ecore_dlist_current(fmt->nodes);
if (!node) break;
byte_count -= node->byte_len;
cidx -= node->char_len;
}
if (!node)
{
ecore_dlist_goto_first(fmt->nodes);
fmt->current_node.char_idx = 0;
fmt->current_node.byte_idx = 0;
FMT_PRINT(fmt);
}
move = ecore_dlist_next;
}
/* we still need to count within this node */
while (byte_count < byte_idx)
{
unsigned int bytes;
ewl_text_text_next_char(t->text + byte_count, &bytes);
byte_count += bytes;
cidx ++;
}
if (char_len)
{
if (byte_len == 0) *char_len = 0;
else
{
char *txt;
txt = t->text + byte_idx;
byte_count = 0;
while (byte_count < byte_len)
{
unsigned int bytes;
txt = ewl_text_text_next_char(txt, &bytes);
byte_count += bytes;
(*char_len) ++;
}
}
}
if (char_idx) *char_idx = cidx;
node = ecore_dlist_current(fmt->nodes);
while (node != current_node)
{
move(fmt->nodes);
node = ecore_dlist_current(fmt->nodes);
}
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
void
ewl_text_fmt_walk(Ewl_Text_Fmt *fmt,
void (*cb)(Ewl_Text_Fmt_Node *, Ewl_Text *, unsigned int byte_idx))
{
Ewl_Text_Fmt_Node *current_node, *node;
unsigned int byte_idx = 0;
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("fmt", fmt);
DCHECK_PARAM_PTR("cb", cb);
current_node = ecore_dlist_current(fmt->nodes);
ecore_dlist_goto_first(fmt->nodes);
while ((node = ecore_dlist_next(fmt->nodes)))
{
cb(node, fmt->text, byte_idx);
byte_idx += node->byte_len;
}
ecore_dlist_goto(fmt->nodes, current_node);
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
void
ewl_text_fmt_apply(Ewl_Text_Fmt *fmt, unsigned int context_mask,
Ewl_Text_Context *change, unsigned int char_idx,
unsigned int char_len)
{
Ewl_Text *t;
Ewl_Text_Fmt_Node *node;
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("change", change);
if ((char_len == 0) || (context_mask == EWL_TEXT_CONTEXT_MASK_NONE))
DRETURN(DLEVEL_STABLE);
t = fmt->text;
node = ecore_dlist_current(fmt->nodes);
while (char_len > 0)
{
/* we've walked off the end of the list */
if (!node) break;
/* covers entire node */
if ((char_idx == fmt->current_node.char_idx)
&& (node->char_len <= char_len))
{
Ewl_Text_Context *new_tx;
new_tx = ewl_text_context_find(node->tx,
context_mask, change);
ewl_text_context_release(node->tx);
node->tx = new_tx;
char_idx += node->char_len;
char_len -= node->char_len;
ecore_dlist_next(fmt->nodes);
fmt->current_node.char_idx += node->char_len;
fmt->current_node.byte_idx += node->byte_len;
}
/* start is the same, node is longer then needed */
else if (char_idx == fmt->current_node.char_idx)
{
unsigned int blen = 0;
Ewl_Text_Fmt_Node *new;
ewl_text_fmt_char_to_byte(fmt,
char_idx, char_len, NULL, &blen);
new = ewl_text_fmt_node_new(
ewl_text_context_find(node->tx,
context_mask, change),
char_len, blen);
node->char_len -= new->char_len;
node->byte_len -= new->byte_len;
ecore_dlist_insert(fmt->nodes, new);
ecore_dlist_next(fmt->nodes);
fmt->current_node.char_idx += new->char_len;
fmt->current_node.byte_idx += new->byte_len;
char_idx += new->char_len;
char_len -= new->char_len;
}
/* starts are different, need to set context on end of node */
else
{
Ewl_Text_Fmt_Node *new;
unsigned int blen = 0, right_skip;
right_skip = char_idx - fmt->current_node.char_idx;
ewl_text_fmt_char_to_byte(fmt, fmt->current_node.char_idx,
right_skip, NULL, &blen);
new = ewl_text_fmt_node_new(node->tx, right_skip, blen);
ecore_dlist_insert(fmt->nodes, new);
ecore_dlist_next(fmt->nodes);
if (!ecore_dlist_current(fmt->nodes))
ecore_dlist_goto_last(fmt->nodes);
fmt->current_node.char_idx += new->char_len;
fmt->current_node.byte_idx += new->byte_len;
/* update node to be the new formatting piece */
node->tx = ewl_text_context_find(node->tx,
context_mask, change);
node->char_len -= new->char_len;
node->byte_len -= new->byte_len;
/* the rest of the node is covered */
if (node->char_len <= char_len)
{
char_len -= node->char_len;
char_idx += node->char_len;
ecore_dlist_next(fmt->nodes);
fmt->current_node.char_idx += node->char_len;
fmt->current_node.byte_idx += node->byte_len;
}
/* we need to split the node */
else
{
Ewl_Text_Context *tx;
unsigned int blen = 0;
tx = new->tx;
ewl_text_fmt_char_to_byte(fmt,
char_idx, char_len,
NULL, &blen);
/* we create a new node which will be our
* formatting and reset node to be original
* formatting */
new = ewl_text_fmt_node_new(node->tx,
char_len, blen);
ecore_dlist_insert(fmt->nodes, new);
ecore_dlist_next(fmt->nodes);
if (!ecore_dlist_current(fmt->nodes))
ecore_dlist_goto_last(fmt->nodes);
fmt->current_node.char_idx += new->char_len;
fmt->current_node.byte_idx += new->byte_len;
node->tx = tx;
node->char_len -= new->char_len;
node->byte_len -= new->byte_len;
char_len -= new->char_len;
char_idx += new->char_len;
}
}
node = ecore_dlist_current(fmt->nodes);
}
/* make sure we're still pointing at a node */
if (!node) node = ecore_dlist_goto_last(fmt->nodes);
DLEAVE_FUNCTION(DLEVEL_STABLE);
}
/*
* Ewl_Text_Fmt_Node stuff
*/
Ewl_Text_Fmt_Node *
ewl_text_fmt_node_new(Ewl_Text_Context *tx, unsigned int char_len,
unsigned int byte_len)
{
Ewl_Text_Fmt_Node *node;
DENTER_FUNCTION(DLEVEL_STABLE);
node = NEW(Ewl_Text_Fmt_Node, 1);
if (!node) DRETURN_PTR(NULL, DLEVEL_STABLE);
node->tx = tx;
ewl_text_context_acquire(node->tx);
node->char_len = char_len;
node->byte_len = byte_len;
DRETURN_PTR(node, DLEVEL_STABLE);
}
static void
ewl_text_fmt_node_free(void *node)
{
Ewl_Text_Fmt_Node *n;
DENTER_FUNCTION(DLEVEL_STABLE);
DCHECK_PARAM_PTR("node", node);
n = node;
if (n->tx) ewl_text_context_release(n->tx);
n->tx = NULL;
FREE(node);
DLEAVE_FUNCTION(DLEVEL_STABLE);
}