When using a custom column, make it possible to select which occurrence to show if the field has multiple occurrences.
svn path=/trunk/; revision=34186
This commit is contained in:
parent
8e278e7f90
commit
7364bef1b3
|
@ -59,6 +59,7 @@ col_setup(column_info *cinfo, const gint num_cols)
|
|||
cinfo->col_last = g_new(int, NUM_COL_FMTS);
|
||||
cinfo->col_title = g_new(gchar*, num_cols);
|
||||
cinfo->col_custom_field = g_new(gchar*, num_cols);
|
||||
cinfo->col_custom_occurrence = g_new(gint, num_cols);
|
||||
cinfo->col_custom_field_id = g_new(int, num_cols);
|
||||
cinfo->col_custom_dfilter = g_new(dfilter_t*, num_cols);
|
||||
cinfo->col_data = (const gchar **)g_new(gchar*, num_cols);
|
||||
|
@ -222,6 +223,7 @@ void col_custom_set_edt(epan_dissect_t *edt, column_info *cinfo)
|
|||
cinfo->col_custom_field_id[i] != -1) {
|
||||
cinfo->col_data[i] = cinfo->col_buf[i];
|
||||
cinfo->col_expr.col_expr[i] = epan_custom_set(edt, cinfo->col_custom_field_id[i],
|
||||
cinfo->col_custom_occurrence[i],
|
||||
cinfo->col_buf[i],
|
||||
cinfo->col_expr.col_expr_val[i],
|
||||
COL_MAX_LEN);
|
||||
|
|
|
@ -725,6 +725,20 @@ get_column_custom_field(const gint col)
|
|||
return(cfmt->custom_field);
|
||||
}
|
||||
|
||||
gint
|
||||
get_column_custom_occurrence(const gint col)
|
||||
{
|
||||
GList *clp = g_list_nth(prefs.col_list, col);
|
||||
fmt_data *cfmt;
|
||||
|
||||
if (!clp) /* Invalid column requested */
|
||||
return 0;
|
||||
|
||||
cfmt = (fmt_data *) clp->data;
|
||||
|
||||
return(cfmt->custom_occurrence);
|
||||
}
|
||||
|
||||
void
|
||||
build_column_format_array(column_info *cinfo, const gint num_cols, const gboolean reset_fences)
|
||||
{
|
||||
|
@ -739,14 +753,17 @@ build_column_format_array(column_info *cinfo, const gint num_cols, const gboolea
|
|||
|
||||
if (cinfo->col_fmt[i] == COL_CUSTOM) {
|
||||
cinfo->col_custom_field[i] = g_strdup(get_column_custom_field(i));
|
||||
cinfo->col_custom_occurrence[i] = get_column_custom_occurrence(i);
|
||||
if(!dfilter_compile(cinfo->col_custom_field[i], &cinfo->col_custom_dfilter[i])) {
|
||||
/* XXX: Should we issue a warning? */
|
||||
g_free(cinfo->col_custom_field[i]);
|
||||
cinfo->col_custom_field[i] = NULL;
|
||||
cinfo->col_custom_occurrence[i] = 0;
|
||||
cinfo->col_custom_dfilter[i] = NULL;
|
||||
}
|
||||
} else {
|
||||
cinfo->col_custom_field[i] = NULL;
|
||||
cinfo->col_custom_occurrence[i] = 0;
|
||||
cinfo->col_custom_dfilter[i] = NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ typedef struct _fmt_data {
|
|||
gchar *title;
|
||||
gchar *fmt;
|
||||
gchar *custom_field;
|
||||
gint custom_occurrence;
|
||||
gboolean visible;
|
||||
gboolean resolved;
|
||||
} fmt_data;
|
||||
|
@ -48,6 +49,7 @@ void set_column_visible(const gint, gboolean);
|
|||
gboolean get_column_resolved(const gint);
|
||||
void set_column_resolved(const gint, gboolean);
|
||||
const gchar *get_column_custom_field(const gint);
|
||||
gint get_column_custom_occurrence(const gint);
|
||||
const gchar *get_column_width_string(const gint, const gint);
|
||||
const char *get_column_longest_string(const gint);
|
||||
gint get_column_char_width(const gint format);
|
||||
|
|
|
@ -44,7 +44,7 @@ typedef struct {
|
|||
gchar **col_expr_val; /**< Value for filter expression */
|
||||
} col_expr_t;
|
||||
|
||||
/** Coulmn info */
|
||||
/** Column info */
|
||||
typedef struct _column_info {
|
||||
gint num_cols; /**< Number of columns */
|
||||
gint *col_fmt; /**< Format of column */
|
||||
|
@ -53,6 +53,7 @@ typedef struct _column_info {
|
|||
gint *col_last; /**< Last column number with a given format */
|
||||
gchar **col_title; /**< Column titles */
|
||||
gchar **col_custom_field; /**< Custom column field */
|
||||
gint *col_custom_occurrence;/**< Custom column field id*/
|
||||
gint *col_custom_field_id; /**< Custom column field id*/
|
||||
struct _dfilter_t **col_custom_dfilter; /**< Compiled custom column field */
|
||||
const gchar **col_data; /**< Column data */
|
||||
|
|
|
@ -235,10 +235,11 @@ epan_dissect_prime_dfilter(epan_dissect_t *edt, const dfilter_t* dfcode)
|
|||
/* ----------------------- */
|
||||
const gchar *
|
||||
epan_custom_set(epan_dissect_t *edt, int field_id,
|
||||
gint occurrence,
|
||||
gchar *result,
|
||||
gchar *expr, const int size )
|
||||
{
|
||||
return proto_custom_set(edt->tree, field_id, result, expr, size);
|
||||
return proto_custom_set(edt->tree, field_id, occurrence, result, expr, size);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -124,7 +124,7 @@ epan_dissect_free(epan_dissect_t* edt);
|
|||
|
||||
/** Sets custom column */
|
||||
const gchar *
|
||||
epan_custom_set(epan_dissect_t *edt, int id,
|
||||
epan_custom_set(epan_dissect_t *edt, int id, gint occurrence,
|
||||
gchar *result, gchar *expr, const int size);
|
||||
|
||||
/**
|
||||
|
|
18
epan/prefs.c
18
epan/prefs.c
|
@ -1139,6 +1139,7 @@ init_prefs(void) {
|
|||
cfmt->visible = TRUE;
|
||||
cfmt->resolved = TRUE;
|
||||
cfmt->custom_field = NULL;
|
||||
cfmt->custom_occurrence = 0;
|
||||
prefs.col_list = g_list_append(prefs.col_list, cfmt);
|
||||
}
|
||||
prefs.num_cols = DEF_NUM_COLS;
|
||||
|
@ -1933,7 +1934,7 @@ try_convert_to_custom_column(gpointer *el_data)
|
|||
|
||||
haystack_fmt = col_format_to_string(migrated_columns[haystack_idx].el);
|
||||
if (strcmp(haystack_fmt, *fmt) == 0) {
|
||||
gchar *cust_col = g_strdup_printf("%%Cus:%s",
|
||||
gchar *cust_col = g_strdup_printf("%%Cus:%s:0",
|
||||
migrated_columns[haystack_idx].col_expr);
|
||||
|
||||
g_free(*fmt);
|
||||
|
@ -1957,6 +1958,7 @@ set_pref(gchar *pref_name, gchar *value, void *private_data _U_)
|
|||
module_t *module;
|
||||
pref_t *pref;
|
||||
gboolean had_a_dot;
|
||||
gchar **cust_format_info;
|
||||
const gchar *cust_format = col_format_to_string(COL_CUSTOM);
|
||||
size_t cust_format_len = strlen(cust_format);
|
||||
|
||||
|
@ -2035,11 +2037,19 @@ set_pref(gchar *pref_name, gchar *value, void *private_data _U_)
|
|||
if (strncmp(col_l_elt->data, cust_format, cust_format_len) == 0) {
|
||||
cfmt->fmt = g_strdup(cust_format);
|
||||
prefs_fmt = g_strdup(col_l_elt->data);
|
||||
cfmt->custom_field = g_strdup(&prefs_fmt[cust_format_len+1]); /* add 1 for ':' */
|
||||
cust_format_info = g_strsplit(&prefs_fmt[cust_format_len+1],":",2); /* add 1 for ':' */
|
||||
cfmt->custom_field = g_strdup(cust_format_info[0]);
|
||||
if (cfmt->custom_field && cust_format_info[1]) {
|
||||
cfmt->custom_occurrence = (int)strtol(cust_format_info[1],NULL,10);
|
||||
} else {
|
||||
cfmt->custom_occurrence = 0;
|
||||
}
|
||||
g_strfreev(cust_format_info);
|
||||
} else {
|
||||
cfmt->fmt = g_strdup(col_l_elt->data);
|
||||
prefs_fmt = g_strdup(cfmt->fmt);
|
||||
cfmt->custom_field = NULL;
|
||||
cfmt->custom_occurrence = 0;
|
||||
}
|
||||
cfmt->visible = prefs_is_column_hidden (cols_hidden_list, prefs_fmt) ? FALSE : TRUE;
|
||||
cfmt->resolved = TRUE;
|
||||
|
@ -3067,7 +3077,7 @@ write_prefs(char **pf_path_return)
|
|||
cfmt = (fmt_data *) clp->data;
|
||||
col_l = g_list_append(col_l, g_strdup(cfmt->title));
|
||||
if ((strcmp(cfmt->fmt, cust_format) == 0) && (cfmt->custom_field)) {
|
||||
prefs_fmt = g_strdup_printf("%s:%s", cfmt->fmt, cfmt->custom_field);
|
||||
prefs_fmt = g_strdup_printf("%s:%s:%d", cfmt->fmt, cfmt->custom_field, cfmt->custom_occurrence);
|
||||
col_l = g_list_append(col_l, prefs_fmt);
|
||||
} else {
|
||||
prefs_fmt = cfmt->fmt;
|
||||
|
@ -3311,8 +3321,10 @@ copy_prefs(e_prefs *dest, e_prefs *src)
|
|||
dest_cfmt->fmt = g_strdup(src_cfmt->fmt);
|
||||
if (src_cfmt->custom_field) {
|
||||
dest_cfmt->custom_field = g_strdup(src_cfmt->custom_field);
|
||||
dest_cfmt->custom_occurrence = src_cfmt->custom_occurrence;
|
||||
} else {
|
||||
dest_cfmt->custom_field = NULL;
|
||||
dest_cfmt->custom_occurrence = 0;
|
||||
}
|
||||
dest_cfmt->visible = src_cfmt->visible;
|
||||
dest_cfmt->resolved = src_cfmt->resolved;
|
||||
|
|
323
epan/proto.c
323
epan/proto.c
|
@ -3418,8 +3418,8 @@ proto_tree_set_representation(proto_item *pi, const char *format, va_list ap)
|
|||
|
||||
/* -------------------------- */
|
||||
const gchar *
|
||||
proto_custom_set(proto_tree* tree, const int field_id, gchar *result,
|
||||
gchar *expr, const int size)
|
||||
proto_custom_set(proto_tree* tree, const int field_id, gint occurrence,
|
||||
gchar *result, gchar *expr, const int size)
|
||||
{
|
||||
guint32 u_integer;
|
||||
gint32 integer;
|
||||
|
@ -3430,7 +3430,7 @@ proto_custom_set(proto_tree* tree, const int field_id, gchar *result,
|
|||
guint32 n_addr; /* network-order IPv4 address */
|
||||
|
||||
const true_false_string *tfstring;
|
||||
int len;
|
||||
int len, last, i, offset=0;
|
||||
GPtrArray *finfos;
|
||||
field_info *finfo;
|
||||
header_field_info* hfinfo;
|
||||
|
@ -3450,172 +3450,207 @@ proto_custom_set(proto_tree* tree, const int field_id, gchar *result,
|
|||
hfinfo = hfinfo->same_name_next;
|
||||
continue;
|
||||
}
|
||||
/* get the last one */
|
||||
finfo = g_ptr_array_index(finfos, len -1);
|
||||
|
||||
switch(hfinfo->type) {
|
||||
/* Are there enough occurrences of the field? */
|
||||
if ((occurrence > len) || (occurrence < -len) )
|
||||
return "";
|
||||
|
||||
case FT_NONE: /* Nothing to add */
|
||||
result[0] = '\0';
|
||||
break;
|
||||
/* calculate single index or set outer bounderies */
|
||||
if (occurrence < 0) {
|
||||
i = occurrence + len;
|
||||
last = i;
|
||||
} else if (occurrence > 0) {
|
||||
i = occurrence - 1;
|
||||
last = i;
|
||||
} else {
|
||||
i = 0;
|
||||
last = len - 1;
|
||||
}
|
||||
|
||||
case FT_PROTOCOL:
|
||||
g_strlcpy(result, "Yes", size);
|
||||
break;
|
||||
while (i <= last) {
|
||||
finfo = g_ptr_array_index(finfos, i);
|
||||
|
||||
case FT_UINT_BYTES:
|
||||
case FT_BYTES:
|
||||
bytes = fvalue_get(&finfo->value);
|
||||
g_strlcpy(result, bytes_to_str(bytes, fvalue_length(&finfo->value)), size);
|
||||
break;
|
||||
if (offset && (offset < size-2))
|
||||
result[offset++]=',';
|
||||
|
||||
case FT_ABSOLUTE_TIME:
|
||||
g_strlcpy(result,
|
||||
abs_time_to_str(fvalue_get(&finfo->value), hfinfo->display, TRUE),
|
||||
size);
|
||||
break;
|
||||
switch(hfinfo->type) {
|
||||
|
||||
case FT_RELATIVE_TIME:
|
||||
g_strlcpy(result, rel_time_to_secs_str(fvalue_get(&finfo->value)), size);
|
||||
break;
|
||||
case FT_NONE: /* Nothing to add */
|
||||
result[0] = '\0';
|
||||
break;
|
||||
|
||||
case FT_BOOLEAN:
|
||||
u_integer = fvalue_get_uinteger(&finfo->value);
|
||||
tfstring = (const true_false_string *)&tfs_true_false;
|
||||
if (hfinfo->strings) {
|
||||
tfstring = (const struct true_false_string*) hfinfo->strings;
|
||||
}
|
||||
g_strlcpy(result, u_integer ? tfstring->true_string : tfstring->false_string, size);
|
||||
break;
|
||||
case FT_PROTOCOL:
|
||||
/* prevent multiple "yes" entries by setting result directly */
|
||||
g_strlcpy(result, "Yes", size);
|
||||
break;
|
||||
|
||||
case FT_UINT8:
|
||||
case FT_UINT16:
|
||||
case FT_UINT24:
|
||||
case FT_UINT32:
|
||||
case FT_FRAMENUM:
|
||||
u_integer = fvalue_get_uinteger(&finfo->value);
|
||||
if (hfinfo->strings) {
|
||||
if (hfinfo->display & BASE_RANGE_STRING) {
|
||||
g_strlcpy(result, rval_to_str(u_integer, hfinfo->strings, "%u"), size);
|
||||
} else if (hfinfo->display & BASE_EXT_STRING) {
|
||||
g_strlcpy(result, val_to_str_ext(u_integer, (value_string_ext *) (hfinfo->strings), "%u"), size);
|
||||
} else {
|
||||
g_strlcpy(result, val_to_str(u_integer, cVALS(hfinfo->strings), "%u"), size);
|
||||
}
|
||||
} else if (IS_BASE_DUAL(hfinfo->display)) {
|
||||
g_snprintf(result, size, hfinfo_uint_value_format(hfinfo), u_integer, u_integer);
|
||||
} else {
|
||||
g_snprintf(result, size, hfinfo_uint_value_format(hfinfo), u_integer);
|
||||
}
|
||||
break;
|
||||
case FT_UINT_BYTES:
|
||||
case FT_BYTES:
|
||||
bytes = fvalue_get(&finfo->value);
|
||||
offset += g_strlcpy(result+offset, bytes_to_str(bytes, fvalue_length(&finfo->value)), size-offset);
|
||||
break;
|
||||
|
||||
case FT_INT64:
|
||||
case FT_UINT64:
|
||||
g_snprintf(result, size, "%" G_GINT64_MODIFIER "u", fvalue_get_integer64(&finfo->value));
|
||||
break;
|
||||
case FT_ABSOLUTE_TIME:
|
||||
offset += g_strlcpy(result+offset,
|
||||
abs_time_to_str(fvalue_get(&finfo->value), hfinfo->display, TRUE),
|
||||
size-offset);
|
||||
break;
|
||||
|
||||
/* XXX - make these just FT_INT? */
|
||||
case FT_INT8:
|
||||
case FT_INT16:
|
||||
case FT_INT24:
|
||||
case FT_INT32:
|
||||
integer = fvalue_get_sinteger(&finfo->value);
|
||||
if (hfinfo->strings) {
|
||||
if (hfinfo->display & BASE_RANGE_STRING) {
|
||||
g_strlcpy(result, rval_to_str(integer, hfinfo->strings, "%d"), size);
|
||||
} else if (hfinfo->display & BASE_EXT_STRING) {
|
||||
g_strlcpy(result, val_to_str_ext(integer, (value_string_ext *) (hfinfo->strings), "%d"), size);
|
||||
} else {
|
||||
g_strlcpy(result, val_to_str(integer, cVALS(hfinfo->strings), "%d"), size);
|
||||
}
|
||||
} else if (IS_BASE_DUAL(hfinfo->display)) {
|
||||
g_snprintf(result, size, hfinfo_int_value_format(hfinfo), integer, integer);
|
||||
} else {
|
||||
g_snprintf(result, size, hfinfo_int_value_format(hfinfo), integer);
|
||||
}
|
||||
break;
|
||||
case FT_RELATIVE_TIME:
|
||||
offset += g_strlcpy(result+offset, rel_time_to_secs_str(fvalue_get(&finfo->value)), size-offset);
|
||||
break;
|
||||
|
||||
case FT_IPv4:
|
||||
ipv4 = fvalue_get(&finfo->value);
|
||||
n_addr = ipv4_get_net_order_addr(ipv4);
|
||||
g_strlcpy(result, ip_to_str((guint8 *)&n_addr), size);
|
||||
break;
|
||||
case FT_BOOLEAN:
|
||||
u_integer = fvalue_get_uinteger(&finfo->value);
|
||||
tfstring = (const true_false_string *)&tfs_true_false;
|
||||
if (hfinfo->strings) {
|
||||
tfstring = (const struct true_false_string*) hfinfo->strings;
|
||||
}
|
||||
offset += g_strlcpy(result+offset, u_integer ? tfstring->true_string : tfstring->false_string, size-offset);
|
||||
break;
|
||||
|
||||
case FT_IPv6:
|
||||
ipv6 = fvalue_get(&finfo->value);
|
||||
SET_ADDRESS (&addr, AT_IPv6, sizeof(struct e_in6_addr), ipv6);
|
||||
address_to_str_buf(&addr, result, size);
|
||||
break;
|
||||
case FT_UINT8:
|
||||
case FT_UINT16:
|
||||
case FT_UINT24:
|
||||
case FT_UINT32:
|
||||
case FT_FRAMENUM:
|
||||
u_integer = fvalue_get_uinteger(&finfo->value);
|
||||
if (hfinfo->strings) {
|
||||
if (hfinfo->display & BASE_RANGE_STRING) {
|
||||
offset += g_strlcpy(result+offset, rval_to_str(u_integer, hfinfo->strings, "%u"), size-offset);
|
||||
} else if (hfinfo->display & BASE_EXT_STRING) {
|
||||
offset += g_strlcpy(result+offset, val_to_str_ext(u_integer, (value_string_ext *) (hfinfo->strings), "%u"), size-offset);
|
||||
} else {
|
||||
offset += g_strlcpy(result+offset, val_to_str(u_integer, cVALS(hfinfo->strings), "%u"), size-offset);
|
||||
}
|
||||
} else if (IS_BASE_DUAL(hfinfo->display)) {
|
||||
g_snprintf(result+offset, size-offset, hfinfo_uint_value_format(hfinfo), u_integer, u_integer);
|
||||
offset = strlen(result);
|
||||
} else {
|
||||
g_snprintf(result+offset, size-offset, hfinfo_uint_value_format(hfinfo), u_integer);
|
||||
offset = strlen(result);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_ETHER:
|
||||
g_strlcpy(result, bytes_to_str_punct(fvalue_get(&finfo->value), 6, ':'), size);
|
||||
break;
|
||||
case FT_INT64:
|
||||
case FT_UINT64:
|
||||
g_snprintf(result+offset, size-offset, "%" G_GINT64_MODIFIER "u", fvalue_get_integer64(&finfo->value));
|
||||
offset = strlen(result);
|
||||
break;
|
||||
|
||||
case FT_GUID:
|
||||
g_strlcpy(result, guid_to_str((e_guid_t *)fvalue_get(&finfo->value)), size);
|
||||
break;
|
||||
/* XXX - make these just FT_INT? */
|
||||
case FT_INT8:
|
||||
case FT_INT16:
|
||||
case FT_INT24:
|
||||
case FT_INT32:
|
||||
integer = fvalue_get_sinteger(&finfo->value);
|
||||
if (hfinfo->strings) {
|
||||
if (hfinfo->display & BASE_RANGE_STRING) {
|
||||
offset += g_strlcpy(result+offset, rval_to_str(integer, hfinfo->strings, "%d"), size-offset);
|
||||
} else if (hfinfo->display & BASE_EXT_STRING) {
|
||||
offset += g_strlcpy(result+offset, val_to_str_ext(integer, (value_string_ext *) (hfinfo->strings), "%d"), size-offset);
|
||||
} else {
|
||||
offset += g_strlcpy(result+offset, val_to_str(integer, cVALS(hfinfo->strings), "%d"), size-offset);
|
||||
}
|
||||
} else if (IS_BASE_DUAL(hfinfo->display)) {
|
||||
g_snprintf(result+offset, size-offset, hfinfo_int_value_format(hfinfo), integer, integer);
|
||||
offset = strlen(result);
|
||||
} else {
|
||||
g_snprintf(result+offset, size-offset, hfinfo_int_value_format(hfinfo), integer);
|
||||
offset = strlen(result);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_OID:
|
||||
bytes = fvalue_get(&finfo->value);
|
||||
g_strlcpy(result, oid_resolved_from_encoded(bytes, fvalue_length(&finfo->value)), size);
|
||||
break;
|
||||
case FT_IPv4:
|
||||
ipv4 = fvalue_get(&finfo->value);
|
||||
n_addr = ipv4_get_net_order_addr(ipv4);
|
||||
offset += g_strlcpy(result+offset, ip_to_str((guint8 *)&n_addr), size-offset);
|
||||
break;
|
||||
|
||||
case FT_FLOAT:
|
||||
g_snprintf(result, size, "%." STRINGIFY(FLT_DIG) "f", fvalue_get_floating(&finfo->value));
|
||||
break;
|
||||
case FT_IPv6:
|
||||
ipv6 = fvalue_get(&finfo->value);
|
||||
SET_ADDRESS (&addr, AT_IPv6, sizeof(struct e_in6_addr), ipv6);
|
||||
address_to_str_buf(&addr, result+offset, size-offset);
|
||||
offset = strlen(result);
|
||||
break;
|
||||
|
||||
case FT_DOUBLE:
|
||||
g_snprintf(result, size, "%." STRINGIFY(DBL_DIG) "g", fvalue_get_floating(&finfo->value));
|
||||
break;
|
||||
case FT_ETHER:
|
||||
offset += g_strlcpy(result+offset, bytes_to_str_punct(fvalue_get(&finfo->value), 6, ':'), size-offset);
|
||||
break;
|
||||
|
||||
case FT_EBCDIC:
|
||||
case FT_STRING:
|
||||
case FT_STRINGZ:
|
||||
case FT_UINT_STRING:
|
||||
bytes = fvalue_get(&finfo->value);
|
||||
g_strlcpy(result, format_text(bytes, strlen(bytes)), size);
|
||||
break;
|
||||
case FT_GUID:
|
||||
offset += g_strlcpy(result+offset, guid_to_str((e_guid_t *)fvalue_get(&finfo->value)), size-offset);
|
||||
break;
|
||||
|
||||
case FT_IPXNET: /*XXX really No column custom ?*/
|
||||
case FT_PCRE:
|
||||
default:
|
||||
g_error("hfinfo->type %d (%s) not handled\n",
|
||||
hfinfo->type,
|
||||
ftype_name(hfinfo->type));
|
||||
DISSECTOR_ASSERT_NOT_REACHED();
|
||||
break;
|
||||
}
|
||||
case FT_OID:
|
||||
bytes = fvalue_get(&finfo->value);
|
||||
offset += g_strlcpy(result+offset, oid_resolved_from_encoded(bytes, fvalue_length(&finfo->value)), size-offset);
|
||||
break;
|
||||
|
||||
switch(hfinfo->type) {
|
||||
case FT_FLOAT:
|
||||
g_snprintf(result+offset, size-offset, "%." STRINGIFY(FLT_DIG) "f", fvalue_get_floating(&finfo->value));
|
||||
offset = strlen(result);
|
||||
break;
|
||||
|
||||
case FT_BOOLEAN:
|
||||
g_snprintf(expr, size, "%u", fvalue_get_uinteger(&finfo->value) ? 1 : 0);
|
||||
break;
|
||||
case FT_DOUBLE:
|
||||
g_snprintf(result+offset, size-offset, "%." STRINGIFY(DBL_DIG) "g", fvalue_get_floating(&finfo->value));
|
||||
offset = strlen(result);
|
||||
break;
|
||||
|
||||
case FT_UINT8:
|
||||
case FT_UINT16:
|
||||
case FT_UINT24:
|
||||
case FT_UINT32:
|
||||
case FT_FRAMENUM:
|
||||
g_snprintf(expr, size, hfinfo_numeric_value_format(hfinfo), fvalue_get_uinteger(&finfo->value));
|
||||
break;
|
||||
case FT_EBCDIC:
|
||||
case FT_STRING:
|
||||
case FT_STRINGZ:
|
||||
case FT_UINT_STRING:
|
||||
bytes = fvalue_get(&finfo->value);
|
||||
offset += g_strlcpy(result+offset, format_text(bytes, strlen(bytes)), size-offset);
|
||||
break;
|
||||
|
||||
case FT_INT8:
|
||||
case FT_INT16:
|
||||
case FT_INT24:
|
||||
case FT_INT32:
|
||||
g_snprintf(expr, size, hfinfo_numeric_value_format(hfinfo), fvalue_get_sinteger(&finfo->value));
|
||||
break;
|
||||
case FT_IPXNET: /*XXX really No column custom ?*/
|
||||
case FT_PCRE:
|
||||
default:
|
||||
g_error("hfinfo->type %d (%s) not handled\n",
|
||||
hfinfo->type,
|
||||
ftype_name(hfinfo->type));
|
||||
DISSECTOR_ASSERT_NOT_REACHED();
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
case FT_OID:
|
||||
bytes = fvalue_get(&finfo->value);
|
||||
g_strlcpy(expr, oid_encoded2string(bytes, fvalue_length(&finfo->value)), size);
|
||||
break;
|
||||
if(occurrence) {
|
||||
switch(hfinfo->type) {
|
||||
|
||||
default:
|
||||
g_strlcpy(expr, result, size);
|
||||
break;
|
||||
}
|
||||
case FT_BOOLEAN:
|
||||
g_snprintf(expr, size, "%u", fvalue_get_uinteger(&finfo->value) ? 1 : 0);
|
||||
break;
|
||||
|
||||
case FT_UINT8:
|
||||
case FT_UINT16:
|
||||
case FT_UINT24:
|
||||
case FT_UINT32:
|
||||
case FT_FRAMENUM:
|
||||
g_snprintf(expr, size, hfinfo_numeric_value_format(hfinfo), fvalue_get_uinteger(&finfo->value));
|
||||
break;
|
||||
|
||||
case FT_INT8:
|
||||
case FT_INT16:
|
||||
case FT_INT24:
|
||||
case FT_INT32:
|
||||
g_snprintf(expr, size, hfinfo_numeric_value_format(hfinfo), fvalue_get_sinteger(&finfo->value));
|
||||
break;
|
||||
|
||||
case FT_OID:
|
||||
bytes = fvalue_get(&finfo->value);
|
||||
g_strlcpy(expr, oid_encoded2string(bytes, fvalue_length(&finfo->value)), size);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_strlcpy(expr, result, size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*XXSLBXX*/
|
||||
|
||||
return hfinfo->abbrev;
|
||||
}
|
||||
return "";
|
||||
|
|
|
@ -1822,11 +1822,13 @@ proto_check_field_name(const gchar *field_name);
|
|||
/** Check if given string is a valid field name
|
||||
@param tree the tree to append this item to
|
||||
@param field_id the field id used for custom column
|
||||
@param occurrence the occurrence of the field used for custom column
|
||||
@param result the buffer to fill with the field string
|
||||
@param expr the filter expression
|
||||
@param size the size of the string buffer */
|
||||
const gchar *
|
||||
proto_custom_set(proto_tree* tree, const int field_id,
|
||||
gint occurrence,
|
||||
gchar *result,
|
||||
gchar *expr, const int size );
|
||||
|
||||
|
|
|
@ -897,7 +897,7 @@ void apply_as_custom_column_cb (GtkWidget *widget _U_, gpointer data _U_)
|
|||
{
|
||||
if (cfile.finfo_selected) {
|
||||
column_prefs_add_custom(COL_CUSTOM, cfile.finfo_selected->hfinfo->name,
|
||||
cfile.finfo_selected->hfinfo->abbrev);
|
||||
cfile.finfo_selected->hfinfo->abbrev,0);
|
||||
/* Recreate the packet list according to new preferences */
|
||||
#ifdef NEW_PACKET_LIST
|
||||
new_packet_list_recreate ();
|
||||
|
|
|
@ -1244,7 +1244,7 @@ packet_list_recent_write_all(FILE *rf)
|
|||
fprintf (rf, "%s:", RECENT_KEY_COL_WIDTH);
|
||||
for (col = 0; col < cfile.cinfo.num_cols; col++) {
|
||||
if (cfile.cinfo.col_fmt[col] == COL_CUSTOM) {
|
||||
fprintf (rf, " %%Cus:%s,", get_column_custom_field(col));
|
||||
fprintf (rf, " %%Cus:%s:%d,", get_column_custom_field(col),get_column_custom_occurrence(col));
|
||||
} else {
|
||||
fprintf (rf, " %s,", col_format_to_string(cfile.cinfo.col_fmt[col]));
|
||||
}
|
||||
|
|
|
@ -1493,7 +1493,7 @@ new_packet_list_recent_write_all(FILE *rf)
|
|||
for (col = 0; col < num_cols; col++) {
|
||||
col_fmt = get_column_format(col);
|
||||
if (col_fmt == COL_CUSTOM) {
|
||||
fprintf (rf, " %%Cus:%s,", get_column_custom_field(col));
|
||||
fprintf (rf, " %%Cus:%s:%d,", get_column_custom_field(col),get_column_custom_occurrence(col));
|
||||
} else {
|
||||
fprintf (rf, " %s,", col_format_to_string(col_fmt));
|
||||
}
|
||||
|
|
|
@ -49,9 +49,10 @@
|
|||
#include "gtk/filter_autocomplete.h"
|
||||
|
||||
|
||||
static GtkWidget *remove_bt, *field_te, *field_lb, *fmt_cmb;
|
||||
static GtkWidget *remove_bt, *field_te, *field_lb, *occurrence_te, *occurrence_lb, *fmt_cmb;
|
||||
static gulong column_menu_changed_handler_id;
|
||||
static gulong column_field_changed_handler_id;
|
||||
static gulong column_occurrence_changed_handler_id;
|
||||
static gulong column_row_deleted_handler_id;
|
||||
|
||||
static void column_list_new_cb(GtkWidget *, gpointer);
|
||||
|
@ -59,9 +60,12 @@ static void column_list_delete_cb(GtkWidget *, gpointer);
|
|||
static void column_list_select_cb(GtkTreeSelection *, gpointer);
|
||||
static void column_menu_changed_cb(GtkWidget *, gpointer);
|
||||
static void column_field_changed_cb(GtkEditable *, gpointer);
|
||||
static void column_occurrence_changed_cb(GtkEditable *, gpointer);
|
||||
static void column_dnd_row_deleted_cb(GtkTreeModel *, GtkTreePath *, gpointer);
|
||||
static gboolean column_title_changed_cb(GtkCellRendererText *, const gchar *, const gchar *, gpointer);
|
||||
|
||||
static char custom_occurrence_str[8] = "";
|
||||
|
||||
enum {
|
||||
#ifdef NEW_PACKET_LIST
|
||||
VISIBLE_COLUMN,
|
||||
|
@ -196,12 +200,17 @@ column_prefs_show(GtkWidget *prefs_window) {
|
|||
cfmt = (fmt_data *) clp->data;
|
||||
cur_fmt = get_column_format_from_str(cfmt->fmt);
|
||||
if (cur_fmt == COL_CUSTOM) {
|
||||
fmt = g_strdup_printf("%s (%s)", col_format_desc(cur_fmt), cfmt->custom_field);
|
||||
if (cfmt->custom_occurrence) {
|
||||
fmt = g_strdup_printf("%s (%s#%d)", col_format_desc(cur_fmt), cfmt->custom_field, cfmt->custom_occurrence);
|
||||
} else {
|
||||
fmt = g_strdup_printf("%s (%s)", col_format_desc(cur_fmt), cfmt->custom_field);
|
||||
}
|
||||
} else {
|
||||
if (cfmt->custom_field) {
|
||||
/* Delete custom_field from previous changes */
|
||||
g_free (cfmt->custom_field);
|
||||
cfmt->custom_field = NULL;
|
||||
cfmt->custom_occurrence = 0;
|
||||
}
|
||||
fmt = g_strdup_printf("%s", col_format_desc(cur_fmt));
|
||||
}
|
||||
|
@ -257,7 +266,7 @@ column_prefs_show(GtkWidget *prefs_window) {
|
|||
gtk_widget_show(props_fr);
|
||||
|
||||
/* Column name entry and format selection */
|
||||
tb = gtk_table_new(2, 2, FALSE);
|
||||
tb = gtk_table_new(2, 4, FALSE);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(tb), 5);
|
||||
gtk_container_add(GTK_CONTAINER(props_fr), tb);
|
||||
gtk_table_set_row_spacings(GTK_TABLE(tb), 10);
|
||||
|
@ -265,7 +274,7 @@ column_prefs_show(GtkWidget *prefs_window) {
|
|||
gtk_widget_show(tb);
|
||||
|
||||
lb = gtk_label_new("Field type:");
|
||||
gtk_misc_set_alignment(GTK_MISC(lb), 1.0f, 0.5f);
|
||||
gtk_misc_set_alignment(GTK_MISC(lb), 0.0f, 0.5f);
|
||||
gtk_table_attach_defaults(GTK_TABLE(tb), lb, 0, 1, 0, 1);
|
||||
gtk_tooltips_set_tip (tooltips, lb,
|
||||
"Select which packet information to present in the column.", NULL);
|
||||
|
@ -278,7 +287,7 @@ column_prefs_show(GtkWidget *prefs_window) {
|
|||
gtk_widget_show(props_hb);
|
||||
|
||||
field_lb = gtk_label_new("Field name:");
|
||||
gtk_misc_set_alignment(GTK_MISC(field_lb), 1.0f, 0.5f);
|
||||
gtk_misc_set_alignment(GTK_MISC(field_lb), 0.0f, 0.5f);
|
||||
gtk_table_attach_defaults(GTK_TABLE(tb), field_lb, 0, 1, 1, 2);
|
||||
gtk_widget_set_sensitive(field_lb, FALSE);
|
||||
gtk_tooltips_set_tip (tooltips, field_lb,
|
||||
|
@ -307,6 +316,30 @@ column_prefs_show(GtkWidget *prefs_window) {
|
|||
"This string has the same syntax as a display filter string.", NULL);
|
||||
gtk_widget_show(field_te);
|
||||
|
||||
occurrence_lb = gtk_label_new("Field occurrence:");
|
||||
gtk_misc_set_alignment(GTK_MISC(occurrence_lb), 0.0f, 0.5f);
|
||||
gtk_table_attach_defaults(GTK_TABLE(tb), occurrence_lb, 2, 3, 1, 2);
|
||||
gtk_widget_set_sensitive(occurrence_lb, FALSE);
|
||||
gtk_tooltips_set_tip (tooltips, occurrence_lb,
|
||||
"Field occurence to use. "
|
||||
"0=all (default), 1=first, 2=second, ..., -1=last.", NULL);
|
||||
gtk_widget_show(occurrence_lb);
|
||||
|
||||
occurrence_te = gtk_entry_new();
|
||||
g_object_set_data (G_OBJECT(occurrence_te), "occurrence", "");
|
||||
|
||||
/* XXX: column_occurrence_changed_cb will be called for every character entered in the entry box. */
|
||||
/* Consider Changing logic so that the field is "accepted" only when a return is entered ?? */
|
||||
column_occurrence_changed_handler_id =
|
||||
g_signal_connect(occurrence_te, "changed", G_CALLBACK(column_occurrence_changed_cb), column_l);
|
||||
|
||||
gtk_table_attach_defaults(GTK_TABLE(tb), occurrence_te, 3, 4, 1, 2);
|
||||
gtk_widget_set_sensitive(occurrence_te, FALSE);
|
||||
gtk_tooltips_set_tip (tooltips, occurrence_te,
|
||||
"Field occurence to use. "
|
||||
"0=all (default), 1=first, 2=second, ..., -1=last.", NULL);
|
||||
gtk_widget_show(occurrence_te);
|
||||
|
||||
fmt_cmb = gtk_combo_box_new_text();
|
||||
|
||||
for (i = 0; i < NUM_COL_FMTS; i++)
|
||||
|
@ -326,7 +359,7 @@ column_prefs_show(GtkWidget *prefs_window) {
|
|||
}
|
||||
|
||||
void
|
||||
column_prefs_add_custom(gint fmt, const gchar *title, const gchar *custom_field)
|
||||
column_prefs_add_custom(gint fmt, const gchar *title, const gchar *custom_field, gint custom_occurrence)
|
||||
{
|
||||
GList *clp;
|
||||
fmt_data *cfmt, *last_cfmt;
|
||||
|
@ -340,6 +373,7 @@ column_prefs_add_custom(gint fmt, const gchar *title, const gchar *custom_field)
|
|||
cfmt->title = g_strdup(title);
|
||||
cfmt->fmt = g_strdup(col_format_to_string(fmt));
|
||||
cfmt->custom_field = g_strdup(custom_field);
|
||||
cfmt->custom_occurrence = custom_occurrence;
|
||||
cfmt->resolved = TRUE;
|
||||
|
||||
if (custom_field) {
|
||||
|
@ -394,7 +428,7 @@ column_list_new_cb(GtkWidget *w _U_, gpointer data) {
|
|||
GtkTreeViewColumn *title_column;
|
||||
|
||||
cur_fmt = COL_NUMBER; /* Set the default new column type */
|
||||
column_prefs_add_custom (cur_fmt, title, NULL);
|
||||
column_prefs_add_custom (cur_fmt, title, NULL, 0);
|
||||
|
||||
model = gtk_tree_view_get_model(column_l);
|
||||
#if GTK_CHECK_VERSION(2,6,0)
|
||||
|
@ -546,15 +580,24 @@ column_list_select_cb(GtkTreeSelection *sel, gpointer data _U_)
|
|||
g_signal_handler_unblock(fmt_cmb, column_menu_changed_handler_id);
|
||||
|
||||
g_signal_handler_block (field_te, column_field_changed_handler_id);
|
||||
g_signal_handler_block (occurrence_te, column_occurrence_changed_handler_id);
|
||||
if (cur_fmt == COL_CUSTOM) {
|
||||
gtk_entry_set_text(GTK_ENTRY(field_te), cfmt->custom_field);
|
||||
gtk_widget_set_sensitive(field_lb, TRUE);
|
||||
gtk_widget_set_sensitive(field_te, TRUE);
|
||||
g_snprintf(custom_occurrence_str, sizeof(custom_occurrence_str), "%d", cfmt->custom_occurrence);
|
||||
gtk_entry_set_text(GTK_ENTRY(occurrence_te), custom_occurrence_str);
|
||||
gtk_widget_set_sensitive(occurrence_lb, TRUE);
|
||||
gtk_widget_set_sensitive(occurrence_te, TRUE);
|
||||
} else {
|
||||
gtk_editable_delete_text(GTK_EDITABLE(field_te), 0, -1);
|
||||
gtk_widget_set_sensitive(field_lb, FALSE);
|
||||
gtk_widget_set_sensitive(field_te, FALSE);
|
||||
gtk_editable_delete_text(GTK_EDITABLE(occurrence_te), 0, -1);
|
||||
gtk_widget_set_sensitive(occurrence_lb, FALSE);
|
||||
gtk_widget_set_sensitive(occurrence_te, FALSE);
|
||||
}
|
||||
g_signal_handler_unblock(occurrence_te, column_occurrence_changed_handler_id);
|
||||
g_signal_handler_unblock(field_te, column_field_changed_handler_id);
|
||||
|
||||
gtk_widget_set_sensitive(remove_bt, TRUE);
|
||||
|
@ -563,9 +606,11 @@ column_list_select_cb(GtkTreeSelection *sel, gpointer data _U_)
|
|||
else
|
||||
{
|
||||
gtk_editable_delete_text(GTK_EDITABLE(field_te), 0, -1);
|
||||
gtk_editable_delete_text(GTK_EDITABLE(occurrence_te), 0, -1);
|
||||
|
||||
gtk_widget_set_sensitive(remove_bt, FALSE);
|
||||
gtk_widget_set_sensitive(field_te, FALSE);
|
||||
gtk_widget_set_sensitive(occurrence_te, FALSE);
|
||||
gtk_widget_set_sensitive(fmt_cmb, FALSE);
|
||||
}
|
||||
}
|
||||
|
@ -615,12 +660,16 @@ column_menu_changed_cb(GtkWidget *w, gpointer data) {
|
|||
/* Update field widgets, list_store, column format array */
|
||||
/* entry as appropriate. */
|
||||
g_signal_handler_block (field_te, column_field_changed_handler_id);
|
||||
g_signal_handler_block (occurrence_te, column_occurrence_changed_handler_id);
|
||||
if (cur_fmt == COL_CUSTOM) {
|
||||
/* Changing from custom to non-custom */
|
||||
gtk_editable_delete_text(GTK_EDITABLE(field_te), 0, -1);
|
||||
gtk_editable_delete_text(GTK_EDITABLE(occurrence_te), 0, -1);
|
||||
fmt = g_strdup_printf("%s", col_format_desc(cur_cb_fmt));
|
||||
gtk_widget_set_sensitive(field_lb, FALSE);
|
||||
gtk_widget_set_sensitive(field_te, FALSE);
|
||||
gtk_widget_set_sensitive(occurrence_lb, FALSE);
|
||||
gtk_widget_set_sensitive(occurrence_te, FALSE);
|
||||
|
||||
} else if (cur_cb_fmt == COL_CUSTOM) {
|
||||
/* Changing from non-custom to custom */
|
||||
|
@ -628,14 +677,24 @@ column_menu_changed_cb(GtkWidget *w, gpointer data) {
|
|||
cfmt->custom_field = g_strdup("");
|
||||
/* The following doesn't trigger a call to menu_field_changed_cb() */
|
||||
gtk_entry_set_text(GTK_ENTRY(field_te), cfmt->custom_field);
|
||||
fmt = g_strdup_printf("%s (%s)", col_format_desc(cur_cb_fmt), cfmt->custom_field);
|
||||
g_snprintf(custom_occurrence_str, sizeof(custom_occurrence_str), "%d", cfmt->custom_occurrence);
|
||||
gtk_entry_set_text(GTK_ENTRY(occurrence_te), custom_occurrence_str);
|
||||
|
||||
if (cfmt->custom_occurrence) {
|
||||
fmt = g_strdup_printf("%s (%s#%d)", col_format_desc(cur_cb_fmt), cfmt->custom_field, cfmt->custom_occurrence);
|
||||
} else {
|
||||
fmt = g_strdup_printf("%s (%s)", col_format_desc(cur_cb_fmt), cfmt->custom_field);
|
||||
}
|
||||
gtk_widget_set_sensitive(field_lb, TRUE);
|
||||
gtk_widget_set_sensitive(field_te, TRUE);
|
||||
gtk_widget_set_sensitive(occurrence_lb, TRUE);
|
||||
gtk_widget_set_sensitive(occurrence_te, TRUE);
|
||||
|
||||
} else {
|
||||
/* Changing from non-custom to non-custom */
|
||||
fmt = g_strdup_printf("%s", col_format_desc(cur_cb_fmt));
|
||||
}
|
||||
g_signal_handler_unblock(occurrence_te, column_occurrence_changed_handler_id);
|
||||
g_signal_handler_unblock(field_te, column_field_changed_handler_id);
|
||||
|
||||
gtk_list_store_set(GTK_LIST_STORE(model), &iter, FORMAT_COLUMN, fmt, -1);
|
||||
|
@ -680,7 +739,11 @@ column_field_changed_cb(GtkEditable *te, gpointer data) {
|
|||
|
||||
/* The user has entered a new value in the field entry box: make the req'd changes */
|
||||
cur_fmt = get_column_format_from_str(cfmt->fmt);
|
||||
fmt = g_strdup_printf("%s (%s)", col_format_desc(cur_fmt), field);
|
||||
if (cfmt->custom_occurrence) {
|
||||
fmt = g_strdup_printf("%s (%s#%d)", col_format_desc(cur_fmt), field, cfmt->custom_occurrence);
|
||||
} else {
|
||||
fmt = g_strdup_printf("%s (%s)", col_format_desc(cur_fmt), field);
|
||||
}
|
||||
|
||||
gtk_list_store_set(GTK_LIST_STORE(model), &iter, FORMAT_COLUMN, fmt, -1);
|
||||
g_free(fmt);
|
||||
|
@ -690,6 +753,54 @@ column_field_changed_cb(GtkEditable *te, gpointer data) {
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* The user changed the custom field occurrence entry box or
|
||||
* the field occurrece entry box has been updated because a new
|
||||
* column row with custom format has been selected.
|
||||
* If the current field entry matches that of the current
|
||||
* column row, this is just an update because a new
|
||||
* column row has been selected. Do nothing.
|
||||
* If the two are different, then update the column row & etc.
|
||||
*/
|
||||
static void
|
||||
column_occurrence_changed_cb(GtkEditable *te, gpointer data) {
|
||||
fmt_data *cfmt;
|
||||
gint cur_fmt;
|
||||
gint occurrence;
|
||||
GList *clp;
|
||||
gchar *fmt;
|
||||
GtkTreeView *tree = (GtkTreeView *)data;
|
||||
GtkTreeSelection *sel;
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter;
|
||||
|
||||
sel = gtk_tree_view_get_selection(tree);
|
||||
if ( ! (gtk_tree_selection_get_selected(sel, &model, &iter))) {
|
||||
return;
|
||||
}
|
||||
|
||||
occurrence = (gint)strtol(gtk_editable_get_chars(te, 0, -1), NULL, 10);
|
||||
gtk_tree_model_get(model, &iter, DATA_COLUMN, &clp, -1);
|
||||
cfmt = (fmt_data *) clp->data;
|
||||
if (cfmt->custom_occurrence == occurrence) {
|
||||
return; /* no action req'd */
|
||||
}
|
||||
|
||||
/* The user has entered a new value in the field occurrence entry box: make the req'd changes */
|
||||
cur_fmt = get_column_format_from_str(cfmt->fmt);
|
||||
if (occurrence) {
|
||||
fmt = g_strdup_printf("%s (%s#%d)", col_format_desc(cur_fmt), cfmt->custom_field, occurrence);
|
||||
} else {
|
||||
fmt = g_strdup_printf("%s (%s)", col_format_desc(cur_fmt), cfmt->custom_field);
|
||||
}
|
||||
|
||||
gtk_list_store_set(GTK_LIST_STORE(model), &iter, FORMAT_COLUMN, fmt, -1);
|
||||
g_free(fmt);
|
||||
cfmt->custom_occurrence = occurrence;
|
||||
cfile.cinfo.columns_changed = TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Callback for the "row-deleted" signal emitted when a list item is dragged.
|
||||
* http://library.gnome.org/devel/gtk/stable/GtkTreeModel.html#GtkTreeModel-rows-reordered
|
||||
|
|
|
@ -62,7 +62,8 @@ void column_prefs_destroy(GtkWidget *widget);
|
|||
* @param custom_field column custom field
|
||||
*/
|
||||
void column_prefs_add_custom(gint fmt, const gchar *title,
|
||||
const gchar *custom_field);
|
||||
const gchar *custom_field,
|
||||
gint custom_occurrence);
|
||||
|
||||
/** Rename a column title.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue