/*
 * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License (not later!)
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not,  see <http://www.gnu.org/licenses>
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */
#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "trace-cmd.h"
#include "trace-local.h"
#include "trace-view-store.h"
#include "trace-view.h"
#include "trace-gui.h"

#include "cpu.h"
#include "event-utils.h"
#include "list.h"

#define DIALOG_WIDTH	400
#define DIALOG_HEIGHT	600

#define TEXT_DIALOG_WIDTH	600
#define TEXT_DIALOG_HEIGHT	400

int str_cmp(const void *a, const void *b)
{
	char * const * sa = a;
	char * const * sb = b;

	return strcmp(*sa, *sb);
}

int id_cmp(const void *a, const void *b)
{
	const gint *ia = a;
	const gint *ib = b;

	if (*ia > *ib)
		return 1;
	if (*ia < *ib)
		return -1;
	return 0;
}

/**
 * trace_array_add - allocate and add an int to an array.
 * @array: address of array to allocate
 * @count: address of the current count of array data
 * @val:  the value to append to the array.
 *
 * The first call to this, @array should be uninitialized
 * and @count should be zero. @array will be malloced and
 * val will be added to it. If count is greater than zero,
 * then array will be realloced, and val will be appened
 * to it.
 *
 * This also always ends the array with -1, so the values are
 * assumed to always be positive.
 *
 * @array must be freed with free().
 */
void trace_array_add(gint **array, gint *count, gint val)
{
	if (*count)
		*array = realloc(*array, sizeof(val) * (*count + 2));
	else
		*array = malloc(sizeof(val) * 2);
	(*array)[(*count)++] = val;
	(*array)[*count] = -1;
}

/* --- event info box --- */

struct event_combo_info {
	struct pevent		*pevent;
	GtkWidget		*event_combo;
	GtkWidget		*op_combo;
	GtkWidget		*field_combo;
	GtkWidget		*entry;
};

static GtkTreeModel *create_event_combo_model(gpointer data)
{
	struct pevent *pevent = data;
	GtkTreeStore *tree;
	GtkTreeIter sys_iter;
	GtkTreeIter iter;
	struct event_format **events;
	struct event_format *event;
	const char *last_sys = NULL;
	int i;

	events = pevent_list_events(pevent, EVENT_SORT_SYSTEM);
	if (!events)
		return NULL;

	tree = gtk_tree_store_new(1, G_TYPE_STRING);


	for (i = 0; events[i]; i++) {
		event = events[i];
		if (!last_sys || strcmp(last_sys, event->system) != 0) {
			last_sys = event->system;

			gtk_tree_store_append(tree, &sys_iter, NULL);
			gtk_tree_store_set(tree, &sys_iter,
					   0, last_sys,
					   -1);
		}
		gtk_tree_store_append(tree, &iter, &sys_iter);
		gtk_tree_store_set(tree, &iter,
				   0, event->name,
				   -1);
	}

	return GTK_TREE_MODEL(tree);
}

static GtkTreeModel *create_op_combo_model(gpointer data)
{
	GtkListStore *list;
	GtkTreeIter iter;
	int i;
	const gchar *ops[] = {":", ",", "==", "!=", "<", ">", "<=",
			      ">=", "=~", "!~", "!", "(", ")", "+",
			      "-", "*", "/", "<<", ">>", "&&", "||",
			      "&", "|", NULL};

	list = gtk_list_store_new(1, G_TYPE_STRING);

	for (i = 0; ops[i]; i++) {
		gtk_list_store_append(list, &iter);
		gtk_list_store_set(list, &iter,
				   0, ops[i],
				   -1);
	}

	return GTK_TREE_MODEL(list);
}

static GtkTreeModel *create_field_combo_model(gpointer data)
{
	struct pevent *pevent = data;
	GtkListStore *list;
	GtkTreeIter iter;
	struct event_format **events;
	struct format_field **fields;
	struct format_field *field;
	int i;

	events = pevent_list_events(pevent, EVENT_SORT_SYSTEM);
	if (!events)
		return NULL;

	list = gtk_list_store_new(1, G_TYPE_STRING);

	/* use any event for common fields */
	fields = pevent_event_common_fields(events[0]);

	for (i = 0; fields[i]; i++) {
		field = fields[i];
		gtk_list_store_append(list, &iter);
		gtk_list_store_set(list, &iter,
				   0, field->name,
				   -1);
	}

	free(fields);

	return GTK_TREE_MODEL(list);
}

static void update_field_combo(struct pevent *pevent,
			       GtkWidget *combo,
			       const char *system,
			       const char *event_name)
{
	struct event_format **events;
	struct event_format *event;
	struct format_field **fields;
	struct format_field *field;
	GtkTreeModel *model;
	GtkListStore *list;
	GtkTreeIter iter;
	int i;

	model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
	if (!model)
		return;

	if (!gtk_tree_model_get_iter_first(model, &iter))
		return;

	if (event_name) {
		event = pevent_find_event_by_name(pevent, system, event_name);
		if (!event)
			return;
	} else {
		/* use any event */
		events = pevent_list_events(pevent, EVENT_SORT_SYSTEM);
		if (!events)
			return;
		event = events[0];
	}

	/* Remove all the objects and reset it */
	g_object_ref(model);
	gtk_combo_box_set_model(GTK_COMBO_BOX(combo), NULL);

	list = GTK_LIST_STORE(model);

	while (gtk_list_store_remove(list, &iter))
		;

	/* always load the common fields first */
	fields = pevent_event_common_fields(event);
	for (i = 0; fields[i]; i++) {
		field = fields[i];
		gtk_list_store_append(list, &iter);
		gtk_list_store_set(list, &iter,
				   0, field->name,
				   -1);
	}
	free(fields);

	/* Now add event specific events */
	if (event_name) {
		fields = pevent_event_fields(event);
		for (i = 0; fields[i]; i++) {
			field = fields[i];
			gtk_list_store_append(list, &iter);
			gtk_list_store_set(list, &iter,
					   0, field->name,
					   -1);
		}
		free(fields);
	}
	gtk_combo_box_set_model(GTK_COMBO_BOX(combo), model);
	g_object_unref(model);

	if (!gtk_tree_model_get_iter_first(model, &iter))
		return;
	gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
}

static void event_combo_changed(GtkComboBox *combo, gpointer data)
{
	struct event_combo_info *info = data;
	GtkWidget *field_combo = info->field_combo;
	GtkTreeIter parent_iter;
	GtkTreeIter iter;
	GtkTreeModel *model;
	GtkTreePath *path;
	gchar *system_name = NULL;
	gchar *event_name = NULL;
	gint depth;

	model = gtk_combo_box_get_model(combo);
	if (!model)
		return;

	if (!gtk_combo_box_get_active_iter(combo, &iter))
		return;

	path = gtk_tree_model_get_path(model, &iter);
	if (!path)
		return;

	depth = gtk_tree_path_get_depth(path);

	if (depth > 1) {
		gtk_tree_model_get(model, &iter,
				   0, &event_name,
				   -1);
		gtk_tree_model_iter_parent(model, &parent_iter,
					   &iter);
		gtk_tree_model_get(model, &parent_iter,
				   0, &system_name,
				   -1);
	} else
		gtk_tree_model_get(model, &iter,
				   0, &system_name,
				   -1);

	update_field_combo(info->pevent, field_combo,
			   system_name, event_name);

	g_free(system_name);
	g_free(event_name);
	gtk_tree_path_free(path);
}

static void insert_combo_text(struct event_combo_info *info,
			      GtkComboBox *combo)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	GtkWidget *entry;
	gchar *text;
	gint pos;

	model = gtk_combo_box_get_model(combo);
	if (!model)
		return;

	if (!gtk_combo_box_get_active_iter(combo, &iter))
		return;

	gtk_tree_model_get(model, &iter,
			   0, &text,
			   -1);

	entry = info->entry;

	pos = gtk_editable_get_position(GTK_EDITABLE(entry));
	gtk_editable_insert_text(GTK_EDITABLE(entry), text, strlen(text), &pos);
	gtk_editable_set_position(GTK_EDITABLE(entry), pos);
	gtk_editable_insert_text(GTK_EDITABLE(entry), " ", 1, &pos);
	gtk_editable_set_position(GTK_EDITABLE(entry), pos);

	g_free(text);
}

static void event_insert_pressed(GtkButton *button,
				 gpointer data)
{
	struct event_combo_info *info = data;

	insert_combo_text(info, GTK_COMBO_BOX(info->event_combo));
}

static void op_insert_pressed(GtkButton *button,
				 gpointer data)
{
	struct event_combo_info *info = data;

	insert_combo_text(info, GTK_COMBO_BOX(info->op_combo));
}

static void field_insert_pressed(GtkButton *button,
				 gpointer data)
{
	struct event_combo_info *info = data;

	insert_combo_text(info, GTK_COMBO_BOX(info->field_combo));
}

static GtkWidget *
create_combo_box(struct event_combo_info *info, GtkWidget *hbox, const gchar *text,
		 GtkTreeModel *(*combo_model_create)(gpointer data),
		 void (*insert_pressed)(GtkButton *button, gpointer data))
{
	GtkWidget *hbox2;
	GtkWidget *combo;
	GtkWidget *button;

	hbox2 = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), hbox2, TRUE, TRUE, 0);
	gtk_widget_show(hbox2);

	combo = trace_create_combo_box(hbox, text, combo_model_create, info->pevent);

	/* --- add insert button --- */

	button = gtk_button_new_with_label("Insert");
	gtk_box_pack_start(GTK_BOX(hbox2), button, FALSE, FALSE, 0);
	gtk_widget_show(button);

	g_signal_connect (button, "pressed",
			  G_CALLBACK (insert_pressed),
			  (gpointer) info);

	return combo;
}

static GtkWidget *event_info_box(struct event_combo_info *info)
{
	GtkWidget *hbox;
	GtkWidget *event_combo;
	GtkWidget *op_combo;
	GtkWidget *field_combo;

	hbox = gtk_hbox_new(FALSE, 0);

	event_combo = create_combo_box(info, hbox, "Event:",
				       create_event_combo_model,
				       event_insert_pressed);

	op_combo = create_combo_box(info, hbox, "Op:",
				    create_op_combo_model,
				    op_insert_pressed);

	field_combo = create_combo_box(info, hbox, "Field:",
				       create_field_combo_model,
				       field_insert_pressed);


	g_signal_connect (event_combo, "changed",
			  G_CALLBACK (event_combo_changed),
			  (gpointer) info);

	info->event_combo = event_combo;
	info->op_combo = op_combo;
	info->field_combo = field_combo;

	return hbox;
}


/* --- advance filter --- */

enum {
	ADV_COL_DELETE,
	ADV_COL_EVENT,
	ADV_COL_FILTER,
	ADV_COL_ID,
	NUM_ADV_FILTER_COLS,
};

static gint *get_event_ids(GtkTreeView *treeview)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	gboolean active;
	gint *ids = NULL;
	gint id;
	int count = 0;

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
	if (!model)
		return NULL;

	if (!gtk_tree_model_iter_children(model, &iter, NULL))
		return NULL;

	for (;;) {
		gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
				   ADV_COL_DELETE, &active,
				   ADV_COL_ID, &id,
				   -1);

		if (active)
			trace_array_add(&ids, &count, id);

		if (!gtk_tree_model_iter_next(model, &iter))
			break;
	}

	return ids;
}

static GtkTreeModel *
create_tree_filter_model(struct pevent *pevent,
		       struct event_filter *event_filter)
{
	GtkTreeStore *treestore;
	GtkTreeIter iter_events;
	struct event_format **events;
	char *str;
	gint i;

	treestore = gtk_tree_store_new(NUM_ADV_FILTER_COLS, G_TYPE_BOOLEAN,
				       G_TYPE_STRING, G_TYPE_STRING,
				       G_TYPE_INT);

	events = pevent_list_events(pevent, EVENT_SORT_SYSTEM);
	if (!events)
		return GTK_TREE_MODEL(treestore);

	for (i = 0; events[i]; i++) {
		str = pevent_filter_make_string(event_filter, events[i]->id);
		if (!str)
			continue;

		/* We only want to show advanced filters */
		if (strcmp(str, "TRUE") == 0 || strcmp(str, "FALSE") == 0) {
			free(str);
			continue;
		}

		gtk_tree_store_append(treestore, &iter_events, NULL);
		gtk_tree_store_set(treestore, &iter_events,
				   ADV_COL_DELETE, FALSE,
				   ADV_COL_EVENT, events[i]->name,
				   ADV_COL_FILTER, str,
				   ADV_COL_ID, events[i]->id,
				   -1);
		free(str);
	}

	return GTK_TREE_MODEL(treestore);
}

#define DELETE_FILTER "Delete Filter"

static void adv_filter_cursor_changed(GtkTreeView *treeview, gpointer data)
{
	GtkTreeViewColumn *col;
	GtkTreeModel *model;
	GtkTreePath *path;
	GtkTreeIter iter;
	gboolean active;
	const gchar *title;

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
	if (!model)
		return;

	gtk_tree_view_get_cursor(treeview, &path, &col);
	if (!path)
		return;

	if (!col)
		goto free;

	title = gtk_tree_view_column_get_title(col);

	if (strcmp(title, DELETE_FILTER) != 0)
		goto free;

	if (!gtk_tree_model_get_iter(model, &iter, path))
		goto free;

	gtk_tree_model_get(model, &iter,
			   ADV_COL_DELETE, &active,
			   -1);

	active = active ? FALSE : TRUE;

	gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
			   ADV_COL_DELETE, active,
			   -1);

 free:
	gtk_tree_path_free(path);
}

static GtkWidget *
create_adv_filter_view(struct pevent *pevent,
		       struct event_filter *event_filter)
{
	GtkTreeViewColumn *col;
	GtkCellRenderer *renderer;
	GtkCellRenderer *togrend;
	GtkWidget *view;
	GtkTreeModel *model;

	view = gtk_tree_view_new();

	renderer = gtk_cell_renderer_text_new();

	togrend = gtk_cell_renderer_toggle_new();

	/* --- delete column --- */

	col = gtk_tree_view_column_new();

	gtk_tree_view_column_set_title(col, DELETE_FILTER);

	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

	gtk_tree_view_column_pack_start(col, togrend, FALSE);
	gtk_tree_view_column_add_attribute(col, togrend, "active", ADV_COL_DELETE);

	/* --- events column --- */

	col = gtk_tree_view_column_new();

	gtk_tree_view_column_set_title(col, "Event");

	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

	gtk_tree_view_column_pack_start(col, renderer, FALSE);

	gtk_tree_view_column_add_attribute(col, renderer, "text", ADV_COL_EVENT);


	/* --- filter column --- */

	col = gtk_tree_view_column_new();

	gtk_tree_view_column_set_title(col, "Filter");

	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

	gtk_tree_view_column_pack_start(col, renderer, FALSE);

	gtk_tree_view_column_add_attribute(col, renderer, "text", ADV_COL_FILTER);


	model = create_tree_filter_model(pevent, event_filter);

	gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);

	g_object_unref(model);

	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
				    GTK_SELECTION_NONE);


	g_signal_connect_swapped (view, "cursor-changed",
				  G_CALLBACK (adv_filter_cursor_changed),
				  (gpointer) view);

	return view;
}

/**
 * trace_adv_filter_dialog - make dialog for text
 * @handle: the handle to the tracecmd data file
 * @event_filter: advanced filters
 * @func: The function to call when accept or cancel is pressed
 * @data: data to pass to the function @func
 */
void trace_adv_filter_dialog(struct tracecmd_input *handle,
			     struct event_filter *event_filter,
			       trace_adv_filter_cb_func func,
			       gpointer data)
{
	struct event_combo_info combo_info;
	struct pevent *pevent;
	GtkWidget *dialog;
	GtkWidget *hbox;
	GtkWidget *label;
	GtkWidget *entry;
	GtkWidget *scrollwin;
	GtkWidget *view;
	GtkWidget *event_box;
	const gchar *text;
	gint *event_ids;
	int result;

	if (!handle)
		return;

	pevent = tracecmd_get_pevent(handle);
	if (!pevent)
		return;

	/* --- Make dialog window --- */

	dialog = gtk_dialog_new_with_buttons("Advanced Filters",
					     NULL,
					     GTK_DIALOG_MODAL,
					     "Apply",
					     GTK_RESPONSE_ACCEPT,
					     GTK_STOCK_CANCEL,
					     GTK_RESPONSE_REJECT,
					     NULL);

	scrollwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
				       GTK_POLICY_AUTOMATIC,
				       GTK_POLICY_AUTOMATIC);
	view = create_adv_filter_view(pevent, event_filter);
	gtk_container_add(GTK_CONTAINER(scrollwin), view);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scrollwin, TRUE, TRUE, 0);

	label = gtk_label_new(" <event>[,<event>] : [!][(]<field><op><val>[)]"
			      "[&&/|| [(]<field><op><val>[)]]\n\n"
			      "Examples:\n"
			      "   sched_switch : next_prio < 100 && (prev_prio > 100"
			      "&& prev_pid != 0)\n"
			      "   irq.* : irq != 38\n"
			      "   .* : common_pid == 1234");
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, TRUE, FALSE, 0);
	gtk_widget_show(label);

	combo_info.pevent = pevent;

	event_box = event_info_box(&combo_info);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), event_box, FALSE, FALSE, 0);
	gtk_widget_show(event_box);

	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, FALSE, 0);
	gtk_widget_show(hbox);

	label = gtk_label_new("Filter:");
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_widget_show(label);

	entry = gtk_entry_new();
	gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
	gtk_widget_show(entry);

	combo_info.entry = entry;

	gtk_widget_set_size_request(GTK_WIDGET(dialog),
				    TEXT_DIALOG_WIDTH, TEXT_DIALOG_HEIGHT);

	gtk_widget_show_all(dialog);

	result = gtk_dialog_run(GTK_DIALOG(dialog));
	switch (result) {
	case GTK_RESPONSE_ACCEPT:
		text = gtk_entry_get_text(GTK_ENTRY(entry));
		event_ids = get_event_ids(GTK_TREE_VIEW(view));
		func(TRUE, text, event_ids, data);
		free(event_ids);
		break;
	case GTK_RESPONSE_REJECT:
		func(FALSE, NULL, NULL, data);
		break;
	default:
		break;
	};

	gtk_widget_destroy(dialog);
}

/* --- task list dialog --- */

enum {
	TASK_COL_SELECT,
	TASK_COL_PID,
	TASK_COL_COMM,
	NUM_TASK_COLS,
};

static void get_tasks(GtkTreeView *treeview,
		      gint **selected,
		      gint **non_selected)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	gboolean active;
	gint *pids = NULL;
	gint *non_pids = NULL;
	gint pid;
	int pid_count = 0;
	int non_count = 0;

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
	if (!model)
		return;

	if (!gtk_tree_model_iter_children(model, &iter, NULL))
		return;

	for (;;) {
		gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
				   TASK_COL_SELECT, &active,
				   TASK_COL_PID, &pid,
				   -1);

		if (active)
			trace_array_add(&pids, &pid_count, pid);
		else
			trace_array_add(&non_pids, &non_count, pid);

		if (!gtk_tree_model_iter_next(model, &iter))
			break;
	}

	*selected = pids;
	*non_selected = non_pids;
}

static GtkTreeModel *
create_task_model(struct pevent *pevent,
		  gint *tasks,
		  gint *selected)
{
	GtkTreeStore *treestore;
	GtkTreeIter iter;
	const char *comm;
	gboolean select;
	gint *ret;
	gint *list;
	gint count;
	gint i;

	if (!tasks)
		return NULL;

	treestore = gtk_tree_store_new(NUM_TASK_COLS, G_TYPE_BOOLEAN,
				       G_TYPE_INT, G_TYPE_STRING);

	/* sort our tasks */
	for (i = 0; tasks[i] >= 0; i++)
		;
	count = i;

	/* Don't assume we can modify the array passed in. */
	list = malloc_or_die(sizeof(gint) * (count + 1));
	memcpy(list, tasks, sizeof(gint) * (count + 1));
	qsort(list, count, sizeof(gint), id_cmp);

	/* Now that we have our own copy, use it instead */
	tasks = list;

	if (selected) {
		/* do the same for selected */
		for (i = 0; selected[i] >= 0; i++)
			;
		count = i;
		/* count is now the size of selected */

		list = malloc_or_die(sizeof(gint) * (count + 1));
		memcpy(list, selected, sizeof(gint) * (count + 1));
		qsort(list, count, sizeof(gint), id_cmp);

		selected = list;
	}


	for (i = 0; tasks[i] >= 0; i++) {

		comm = pevent_data_comm_from_pid(pevent, tasks[i]);

		gtk_tree_store_append(treestore, &iter, NULL);

		select = FALSE;

		if (selected) {
			ret = bsearch(&tasks[i], selected, count,
				      sizeof(gint), id_cmp);
			if (ret)
				select = TRUE;
		}

		gtk_tree_store_set(treestore, &iter,
				   TASK_COL_SELECT, select,
				   TASK_COL_PID, tasks[i],
				   TASK_COL_COMM, comm,
				   -1);
	}

	free(tasks);
	free(selected);

	return GTK_TREE_MODEL(treestore);
}

static void task_cursor_changed(gpointer data, GtkTreeView *treeview)
{
	gboolean *start = data;
	GtkTreeModel *model;
	GtkTreePath *path;
	GtkTreeIter iter;
	gboolean active;

	if (!*start) {
		*start = TRUE;
		return;
	}

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
	if (!model)
		return;

	gtk_tree_view_get_cursor(treeview, &path, NULL);
	if (!path)
		return;

	if (!gtk_tree_model_get_iter(model, &iter, path))
		goto free;

	gtk_tree_model_get(model, &iter,
			   TASK_COL_SELECT, &active,
			   -1);

	active = active ? FALSE : TRUE;

	gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
			   TASK_COL_SELECT, active,
			   -1);

 free:
	gtk_tree_path_free(path);
}

static GtkWidget *
create_task_view(struct pevent *pevent,
		 gint *tasks, gint *selected,
		 gboolean *start)
{
	GtkTreeViewColumn *col;
	GtkCellRenderer *renderer;
	GtkCellRenderer *togrend;
	GtkWidget *view;
	GtkTreeModel *model;

	view = gtk_tree_view_new();

	renderer  = gtk_cell_renderer_text_new();
	togrend  = gtk_cell_renderer_toggle_new();


	/* --- select column --- */

	col = gtk_tree_view_column_new();

	gtk_tree_view_column_set_title(col, "Plot");

	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

	gtk_tree_view_column_pack_start(col, togrend, FALSE);
	gtk_tree_view_column_add_attribute(col, togrend, "active", TASK_COL_SELECT);

	/* --- pid column --- */

	col = gtk_tree_view_column_new();

	gtk_tree_view_column_set_title(col, "PID");

	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

	gtk_tree_view_column_pack_start(col, renderer, FALSE);

	gtk_tree_view_column_add_attribute(col, renderer, "text", TASK_COL_PID);


	/* --- comm column --- */

	col = gtk_tree_view_column_new();

	gtk_tree_view_column_set_title(col, "Task");

	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

	gtk_tree_view_column_pack_start(col, renderer, FALSE);

	gtk_tree_view_column_add_attribute(col, renderer, "text", TASK_COL_COMM);


	model = create_task_model(pevent, tasks, selected);

	gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);

	g_object_unref(model);

	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
				    GTK_SELECTION_NONE);

	g_signal_connect_swapped (view, "cursor-changed",
				  G_CALLBACK (task_cursor_changed),
				  (gpointer)start);

	return view;
}

/**
 * trace_task_dialog - make dialog to select tasks
 * @handle: the handle to the tracecmd data file
 * @tasks: array of task pids ending with -1
 * @selected: tasks from @tasks that should be selected (can be NULL).
 *      Also an array of pids ending with -1
 * @func: callback function when accept or cancel is selected
 * @data: data to pass to the function @func
 */
void trace_task_dialog(struct tracecmd_input *handle,
		       gint *tasks, gint *selected,
		       trace_task_cb_func func,
		       gpointer data)
{
	struct pevent *pevent;
	GtkWidget *dialog;
	GtkWidget *scrollwin;
	GtkWidget *view;
	gboolean start = FALSE;
	gint *non_select;
	int result;

	if (!handle)
		return;

	pevent = tracecmd_get_pevent(handle);
	if (!pevent)
		return;

	/* --- Make dialog window --- */

	dialog = gtk_dialog_new_with_buttons("Select Tasks",
					     NULL,
					     GTK_DIALOG_MODAL,
					     "Apply",
					     GTK_RESPONSE_ACCEPT,
					     GTK_STOCK_CANCEL,
					     GTK_RESPONSE_REJECT,
					     NULL);

	scrollwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
				       GTK_POLICY_AUTOMATIC,
				       GTK_POLICY_AUTOMATIC);
	view = create_task_view(pevent, tasks, selected, &start);
	gtk_container_add(GTK_CONTAINER(scrollwin), view);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scrollwin, TRUE, TRUE, 0);

	gtk_widget_set_size_request(GTK_WIDGET(dialog),
				    DIALOG_WIDTH, DIALOG_HEIGHT);

	gtk_widget_show_all(dialog);

	selected = NULL;

	result = gtk_dialog_run(GTK_DIALOG(dialog));
	switch (result) {
	case GTK_RESPONSE_ACCEPT:
		get_tasks(GTK_TREE_VIEW(view), &selected, &non_select);
		func(TRUE, selected, non_select, data);
		free(selected);
		free(non_select);
		break;
	case GTK_RESPONSE_REJECT:
		func(FALSE, NULL, NULL, data);
		break;
	default:
		break;
	};

	gtk_widget_destroy(dialog);
}

enum {
	COL_EVENT,
	COL_ACTIVE,
	COL_NORMAL,
	COL_ACTIVE_START,
	COL_EVENT_ID,
	NUM_EVENT_COLS,
};

struct event_filter_helper {
	trace_filter_event_cb_func	func;
	GtkTreeView			*view;
	gpointer			data;
};

gboolean system_is_enabled(gchar **systems, gint systems_size, const gchar *system)
{
	const gchar **sys = &system;

	if (!systems)
		return FALSE;

	sys = bsearch(sys, systems, systems_size, sizeof(system), str_cmp);

	return sys != NULL;
}

gboolean event_is_enabled(gint *events, gint events_size, gint event)
{
	gint *ret;

	if (!events)
		return FALSE;

	ret = bsearch(&event, events, events_size, sizeof(gint), id_cmp);

	return ret != NULL;
}

static GtkTreeModel *
create_tree_event_model(struct pevent *pevent,
			struct event_filter *filter,
			gboolean all_events, gchar **systems_set,
			gint *event_ids_set)
{
	GtkTreeStore *treestore;
	GtkTreeIter iter_all, iter_sys, iter_events;
	struct event_format **events;
	struct event_format *event;
	char *last_system = NULL;
	gboolean sysactive;
	gboolean active, normal;
	gchar **systems = NULL;
	gint *event_ids = NULL;
	gint systems_size;
	gint event_ids_size;
	gint i;

	treestore = gtk_tree_store_new(NUM_EVENT_COLS, G_TYPE_STRING,
				       G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
				       G_TYPE_BOOLEAN, G_TYPE_INT);

	gtk_tree_store_append(treestore, &iter_all, NULL);
	gtk_tree_store_set(treestore, &iter_all,
			   COL_EVENT,	"All",
			   COL_ACTIVE, all_events,
			   COL_NORMAL, TRUE,
			   COL_ACTIVE_START, FALSE,
			   -1);

	events = pevent_list_events(pevent, EVENT_SORT_SYSTEM);
	if (!events)
		return GTK_TREE_MODEL(treestore);

	if (systems_set) {
		for (systems_size = 0; systems_set[systems_size]; systems_size++)
			;
		systems = g_new(typeof(*systems), systems_size + 1);
		memcpy(systems, systems_set, sizeof(*systems) * (systems_size + 1));
		qsort(systems, systems_size, sizeof(gchar *), str_cmp);
	}

	if (event_ids_set) {
		for (event_ids_size = 0; event_ids_set[event_ids_size] != -1; event_ids_size++)
			;
		event_ids = g_new(typeof(*event_ids), event_ids_size + 1);
		memcpy(event_ids, event_ids_set, sizeof(*event_ids) * (event_ids_size + 1));
		qsort(event_ids, event_ids_size, sizeof(gint), id_cmp);
	}

	for (i = 0; events[i]; i++) {
		event = events[i];
		if (!last_system || strcmp(last_system, event->system) != 0) {
			gtk_tree_store_append(treestore, &iter_sys, &iter_all);
			sysactive = all_events ||
				system_is_enabled(systems, systems_size, event->system);
			gtk_tree_store_set(treestore, &iter_sys,
					   COL_EVENT, event->system,
					   COL_ACTIVE, sysactive,
					   COL_NORMAL, TRUE,
					   -1);
			last_system = event->system;
		}

		active = all_events || sysactive ||
			event_is_enabled(event_ids, event_ids_size, event->id);

		normal = TRUE;
		if (active && filter) {
			if (pevent_event_filtered(filter, event->id) &&
			    !pevent_filter_event_has_trivial(filter, event->id,
							     FILTER_TRIVIAL_BOTH))
				normal = FALSE;
			/* Make trivial false not selected */
			else if (pevent_filter_event_has_trivial(filter, event->id,
								 FILTER_TRIVIAL_FALSE))
				active = FALSE;
		}
		gtk_tree_store_append(treestore, &iter_events, &iter_sys);
		gtk_tree_store_set(treestore, &iter_events,
				   COL_EVENT, event->name,
				   COL_ACTIVE, active,
				   COL_NORMAL, normal,
				   COL_EVENT_ID, event->id,
				   -1);
	}

	/* These are copies of the sets, using glib allocation */
	g_free(systems);
	g_free(event_ids);

	return GTK_TREE_MODEL(treestore);
}

static void update_active_events(GtkTreeModel *model, GtkTreeIter *parent,
				 gboolean active)
{
	GtkTreeIter event;

	if (!gtk_tree_model_iter_children(model, &event, parent))
		return;

	for (;;) {
		gtk_tree_store_set(GTK_TREE_STORE(model), &event,
				   COL_ACTIVE, active,
				   -1);

		if (!gtk_tree_model_iter_next(model, &event))
			break;
	}
}

static void update_active_systems(GtkTreeModel *model, GtkTreeIter *parent,
				  gboolean active)
{
	GtkTreeIter sys;

	if (!gtk_tree_model_iter_children(model, &sys, parent))
		return;

	for (;;) {
		gtk_tree_store_set(GTK_TREE_STORE(model), &sys,
				   COL_ACTIVE, active,
				   -1);

		update_active_events(model, &sys, active);

		if (!gtk_tree_model_iter_next(model, &sys))
			break;
	}
}

static void event_cursor_changed(GtkTreeView *treeview, gpointer data)
{
	GtkTreeModel *model;
	GtkTreePath *path;
	GtkTreeIter iter, parent, grandparent;
	gboolean active, start;
	gint depth;

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
	if (!model)
		return;

	gtk_tree_view_get_cursor(treeview, &path, NULL);
	if (!path)
		return;

	if (!gtk_tree_model_get_iter(model, &iter, path))
		goto free;

	depth = gtk_tree_path_get_depth(path);

	if (depth == 1) {
		/*
		 * The first time we start up, the cursor will
		 * select the "All Events" row, and call
		 * this routine. But we don't want to do anything.
		 * Check and activate.
		 */
		gtk_tree_model_get(model, &iter,
				   COL_ACTIVE_START, &start,
				   -1);
		if (!start) {
			gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
					   COL_ACTIVE_START, TRUE,
					   -1);
			goto free;
		}
	}

	gtk_tree_model_get(model, &iter,
			   COL_ACTIVE, &active,
			   -1);

	active = active ? FALSE : TRUE;

	gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
			   COL_ACTIVE, active,
			   -1);

	if (depth == 1) {

		/* Set all rows */
		update_active_systems(model, &iter, active);
			
	} else if (depth == 2) {

		/* set this system */
		update_active_events(model, &iter, active);

		if (!active) {
			/* disable the all events toggle */
			gtk_tree_model_iter_parent(model, &parent, &iter);
			gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
					   COL_ACTIVE, FALSE,
					   -1);
		}

	} else {
		if (!active) {
			/* disable system and all events toggles */
			gtk_tree_model_iter_parent(model, &parent, &iter);
			gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
					   COL_ACTIVE, FALSE,
					   -1);
			gtk_tree_model_iter_parent(model, &grandparent, &parent);
			gtk_tree_store_set(GTK_TREE_STORE(model), &grandparent,
					   COL_ACTIVE, FALSE,
					   -1);
		}
	}

 free:
	gtk_tree_path_free(path);
}

static gboolean child_set(GtkTreeModel *model, GtkTreeIter *parent)
{
	GtkTreeIter iter;
	gboolean active;

	if (!gtk_tree_model_iter_children(model, &iter, parent))
		return FALSE;

	for (;;) {

		gtk_tree_model_get(model, &iter,
				   COL_ACTIVE, &active,
				   -1);

		if (active)
			return TRUE;

		if (!gtk_tree_model_iter_next(model, &iter))
			break;
	}

	return FALSE;
}

static void expand_rows(GtkTreeView *tree, GtkTreeModel *model,
			gboolean all_events,
			gchar **systems, gint *events)
{
	GtkTreePath *path;
	GtkTreeIter all;
	GtkTreeIter sys;
	gboolean active;

	/* Expand the "All Events" row */
	path = gtk_tree_path_new_from_string("0");

	gtk_tree_view_expand_row(tree, path, FALSE);

	gtk_tree_path_free(path);

	if (all_events)
		return;

	/* Expand the system rows that are not full or empty */

	if (!gtk_tree_model_get_iter_first(model, &all))
		return;

	if (!gtk_tree_model_iter_children(model, &sys, &all))
		return;

	for (;;) {

		gtk_tree_model_get(model, &sys,
				   COL_ACTIVE, &active,
				   -1);

		if (!active && child_set(model, &sys)) {
			path = gtk_tree_model_get_path(model, &sys);
			gtk_tree_view_expand_row(tree, path, FALSE);
			gtk_tree_path_free(path);
		}

		if (!gtk_tree_model_iter_next(model, &sys))
			break;
	}
}

/**
 * trace_update_event_view - update the events of an existing event view
 * @event_view: event view to update
 * @pevent: The parse event descriptor
 * @filter: Event filter to determine what events have advanced filters
 *          May be NULL.
 * @all_events: True if all should be selected,
 * @systems: Array of system names of systems that should be selected.
 * @events: Array of event ids of events that should be selecetd.
 */
int trace_update_event_view(GtkWidget *event_view,
			    struct pevent *pevent,
			    struct event_filter *filter,
			    gboolean all_events,
			    gchar **systems, gint *events)
{
	GtkTreeView *view = GTK_TREE_VIEW(event_view);
	GtkTreeModel *model;

	model = create_tree_event_model(pevent, filter,
					all_events, systems, events);
	if (!model)
		return -1;

	gtk_tree_view_set_model(view, model);
	g_object_unref(model);

	expand_rows(view, model, all_events, systems, events);

	return 0;
}

/**
 * trace_create_event_list_view - create a list view of events in pevent
 * @pevent: The parse event descriptor
 * @filter: Event filter to determine what events have advanced filters
 *          May be NULL.
 * @all_events: True if all should be selected,
 * @systems: Array of system names of systems that should be selected.
 * @events: Array of event ids of events that should be selected.
 *
 * Returns a tree view widget of the events.
 */
GtkWidget *
trace_create_event_list_view(struct pevent *pevent,
			     struct event_filter *filter,
			     gboolean all_events, gchar **systems,
			     gint *events)
{
	GtkTreeViewColumn *col;
	GtkCellRenderer *renderer;
	GtkCellRenderer *togrend;
	GtkWidget *view;
	GtkTreeModel *model;

	view = gtk_tree_view_new();

	/* --- events column --- */

	col = gtk_tree_view_column_new();

	gtk_tree_view_column_set_title(col, "Events");

	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

	renderer  = gtk_cell_renderer_text_new();

	togrend  = gtk_cell_renderer_toggle_new();

	gtk_tree_view_column_pack_start(col, togrend, FALSE);
	gtk_tree_view_column_pack_start(col, renderer, FALSE);
	gtk_tree_view_column_add_attribute(col, togrend, "active", COL_ACTIVE);
	gtk_tree_view_column_add_attribute(col, togrend, "sensitive", COL_NORMAL);

	gtk_tree_view_column_add_attribute(col, renderer, "text", COL_EVENT);
	gtk_tree_view_column_add_attribute(col, renderer, "sensitive", COL_NORMAL);

	model = create_tree_event_model(pevent, filter, all_events, systems, events);

	gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);

	g_object_unref(model);

	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
				    GTK_SELECTION_NONE);

	expand_rows(GTK_TREE_VIEW(view), model, all_events, systems, events);

	g_signal_connect_swapped (view, "cursor-changed",
				  G_CALLBACK (event_cursor_changed),
				  (gpointer) view);

	return view;
}

static gint update_events(GtkTreeModel *model,
			  GtkTreeIter *parent,
			  gint **events, gint size)
{
	GtkTreeIter event;
	gboolean active;
	gint id;

	if (!gtk_tree_model_iter_children(model, &event, parent))
		return size;

	for (;;) {

		gtk_tree_model_get(model, &event,
				   COL_ACTIVE, &active,
				   COL_EVENT_ID, &id,
				   -1);

		if (active)
			*events = tracecmd_add_id(*events, id, size++);

		if (!gtk_tree_model_iter_next(model, &event))
			break;
	}

	return size;
}

static void update_system_events(GtkTreeModel *model,
				 GtkTreeIter *parent,
				 gchar ***systems,
				 gint **events)
{
	GtkTreeIter sys;
	gboolean active;
	gchar *system;
	gint sys_size = 0;
	gint event_size = 0;

	if (!gtk_tree_model_iter_children(model, &sys, parent))
		return;

	for (;;) {

		gtk_tree_model_get(model, &sys,
				   COL_ACTIVE, &active,
				   COL_EVENT, &system,
				   -1);

		if (active)
			*systems = tracecmd_add_list(*systems, system, sys_size++);
		else
			event_size = update_events(model, &sys, events, event_size);

		g_free(system);

		if (!gtk_tree_model_iter_next(model, &sys))
			break;
	}
}

/**
 * trace_extract_event_list_view - extract selected events from event view
 * @event_view: the event list view widget to extract from
 * @all_events: Set if all events are set (@systems and @events are left as is)
 * @systems: Set to the selected systems (if @all_events is FALSE)
 * @events: Set to the selected events (not in selected systems and if @all_events is FALSE)
 *
 * Returns 0 on success, -1 otherwise.
 */
gint trace_extract_event_list_view(GtkWidget *event_view,
				   gboolean *all_events,
				   gchar ***systems,
				   gint **events)
{
	GtkTreeView *view = GTK_TREE_VIEW(event_view);
	GtkTreeModel *model;
	GtkTreeIter iter;

	model = gtk_tree_view_get_model(view);
	if (!model)
		return -1;

	if (!gtk_tree_model_get_iter_first(model, &iter))
		return -1;

	gtk_tree_model_get(model, &iter,
			   COL_ACTIVE, all_events,
			   -1);

	if (!*all_events)
		update_system_events(model, &iter, systems, events);

	return 0;
}

static void accept_events(GtkWidget *view,
			  trace_filter_event_cb_func func, gpointer data)
{
	gboolean all_events;
	gchar **systems = NULL;
	gint *events = NULL;

	if (trace_extract_event_list_view(view, &all_events,
					  &systems, &events) < 0) {
		/* Failed to extract ? */
		func(FALSE, FALSE, NULL, NULL, data);
		return;
	}

	func(TRUE, all_events, systems, events, data);

	tracecmd_free_list(systems);
	free(events);
}

static void filter_event_dialog(struct pevent *pevent,
				struct event_filter *filter,
				gboolean all_events,
				gchar **systems, gint *events,
				trace_filter_event_cb_func func,
				gpointer data)
{
	GtkWidget *dialog;
	GtkWidget *scrollwin;
	GtkWidget *view;
	int result;

	/* --- Make dialog window --- */

	dialog = gtk_dialog_new_with_buttons("Filter Events",
					     NULL,
					     GTK_DIALOG_MODAL,
					     "Apply",
					     GTK_RESPONSE_ACCEPT,
					     GTK_STOCK_CANCEL,
					     GTK_RESPONSE_REJECT,
					     NULL);

	scrollwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
				       GTK_POLICY_AUTOMATIC,
				       GTK_POLICY_AUTOMATIC);
	view = trace_create_event_list_view(pevent, filter, all_events, systems, events);

	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scrollwin, TRUE, TRUE, 0);
	gtk_container_add(GTK_CONTAINER(scrollwin), view);

	gtk_widget_set_size_request(GTK_WIDGET(dialog),
				    DIALOG_WIDTH, DIALOG_HEIGHT);

	gtk_widget_show_all(dialog);

	result = gtk_dialog_run(GTK_DIALOG(dialog));
	switch (result) {
	case GTK_RESPONSE_ACCEPT:
		accept_events(view, func, data);
		break;
	case GTK_RESPONSE_REJECT:
		func(FALSE, FALSE, NULL, NULL, data);
		break;
	default:
		break;
	};

	gtk_widget_destroy(dialog);
}

/**
 * trace_filter_event_dialog - make dialog with event listing
 * @handle: the handle to the tracecmd data file
 * @all_events: if TRUE then select all events.
 * @systems: NULL or a string array of systems terminated with NULL
 * @events: NULL or a int array of event ids terminated with -1
 * @func: The function to call when accept or cancel is pressed
 * @data: data to pass to the function @func
 *
 * If @all_events is set, then @systems and @events are ignored.
 */
void trace_filter_event_dialog(struct tracecmd_input *handle,
			       gboolean all_events,
			       gchar **systems, gint *events,
			       trace_filter_event_cb_func func,
			       gpointer data)
{
	struct pevent *pevent;

	if (!handle)
		return;

	pevent = tracecmd_get_pevent(handle);
	if (!pevent)
		return;

	filter_event_dialog(pevent, NULL, all_events, systems,
			    events, func, data);
}

void trace_filter_pevent_dialog(struct pevent *pevent,
				gboolean all_events,
				gchar **systems, gint *events,
				trace_filter_event_cb_func func,
				gpointer data)
{
	if (!pevent)
		return;

	filter_event_dialog(pevent, NULL, all_events, systems,
			    events, func, data);
}


/**
 * trace_filter_event_filter_dialog - make dialog with event listing
 * @handle: the handle to the tracecmd data file
 * @filter: the event filter to determine what to display.
 * @all_events: if TRUE then select all events.
 * @func: The function to call when accept or cancel is pressed
 * @data: data to pass to the function @func
 *
 * If @all_events is set, then @systems and @events are ignored.
 */
void trace_filter_event_filter_dialog(struct tracecmd_input *handle,
				      struct event_filter *filter,
				      gboolean all_events,
				      trace_filter_event_cb_func func,
				      gpointer data)
{
	struct pevent *pevent;
	gchar **systems;
	gint *event_ids;

	if (!handle)
		return;

	pevent = tracecmd_get_pevent(handle);
	if (!pevent)
		return;

	trace_filter_convert_filter_to_names(filter, &systems, &event_ids);

	filter_event_dialog(pevent, filter, all_events, systems,
			    event_ids, func, data);
}

struct cpu_filter_helper {
	gboolean			allcpus;
	guint64				*cpu_mask;
	GtkWidget			**buttons;
	int				cpus;
};

static void destroy_cpu_helper(struct cpu_filter_helper *cpu_helper)
{
	g_free(cpu_helper->cpu_mask);
	g_free(cpu_helper->buttons);
	g_free(cpu_helper);
}

#define CPU_ALL_CPUS_STR "All CPUs"

void cpu_toggle(gpointer data, GtkWidget *widget)
{
	struct cpu_filter_helper *cpu_helper = data;
	const gchar *label;
	gboolean active;
	gint cpu;

	label = gtk_button_get_label(GTK_BUTTON(widget));
	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));

	if (strcmp(label, CPU_ALL_CPUS_STR) == 0) {
		cpu_helper->allcpus = active;
		/* enable/disable all toggles */
		for (cpu = 0; cpu < cpu_helper->cpus; cpu++)
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cpu_helper->buttons[cpu]),
						     active ? TRUE : FALSE);
		return;
	}

	/* Get the CPU # from the label. Pass "CPU " */
	cpu = atoi(label + 4);
	if (active)
		cpu_set(cpu_helper->cpu_mask, cpu);
	else
		cpu_clear(cpu_helper->cpu_mask, cpu);

	cpu_helper->allcpus = cpu_allset(cpu_helper->cpu_mask,
					 cpu_helper->cpus);
	/*
	 * Deactivate sending 'toggled' signal for "All CPUs"
	 * while we adjust its state
	 */
	g_signal_handlers_block_by_func(GTK_TOGGLE_BUTTON(cpu_helper->buttons[cpu_helper->cpus]),
				        G_CALLBACK (cpu_toggle),
				        (gpointer) cpu_helper);

	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cpu_helper->buttons[cpu_helper->cpus]),
				     cpu_helper->allcpus);

	g_signal_handlers_unblock_by_func(GTK_TOGGLE_BUTTON(cpu_helper->buttons[cpu_helper->cpus]),
					  G_CALLBACK (cpu_toggle),
					  (gpointer) cpu_helper);
}

/**
 * trace_filter_cpu_dialog - make dialog with cpu listing
 * @all_cpus: if TRUE then select all cpus.
 * @cpus_selected: NULL or a CPU mask with the CPUs to be select set.
 * @func: The function to call when accept or cancel is pressed
 * @data: data to pass to the function @func
 *
 * If @all_cpus is set, then @cpus_selected is ignored.
 */
void trace_filter_cpu_dialog(gboolean all_cpus, guint64 *cpus_selected, gint cpus,
			     trace_filter_cpu_cb_func func, gpointer data)
{
	struct cpu_filter_helper *cpu_helper;
	guint64 *cpu_mask = NULL;
	GtkWidget *dialog;
	GtkWidget *scrollwin;
	GtkWidget *viewport;
	GtkWidget *hbox;
	GtkWidget *vbox;
	GtkWidget *check;
	GtkRequisition req;
	gchar	counter[100];
	gint width, height;
	gint allset;
	gint cpu;
	int result;

	cpu_helper = g_new0(typeof(*cpu_helper), 1);
	g_assert(cpu_helper != NULL);

	/* --- Make dialog window --- */

	dialog = gtk_dialog_new_with_buttons("Filter CPUS",
					     NULL,
					     GTK_DIALOG_MODAL,
					     "Apply",
					     GTK_RESPONSE_ACCEPT,
					     GTK_STOCK_CANCEL,
					     GTK_RESPONSE_REJECT,
					     NULL);

	cpu_helper->cpus = cpus;
	cpu_helper->buttons = g_new0(GtkWidget *, cpus + 1);
	g_assert(cpu_helper->buttons);

	scrollwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
				       GTK_POLICY_AUTOMATIC,
				       GTK_POLICY_AUTOMATIC);

	viewport = gtk_viewport_new(NULL, NULL);
	gtk_widget_show(viewport);

	gtk_container_add(GTK_CONTAINER(scrollwin), viewport);

	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scrollwin, TRUE, TRUE, 0);

	/* Add hbox to center buttons. Is there a better way? */
	hbox = gtk_hbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(viewport), hbox);
	gtk_widget_show(hbox);

	vbox = gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, FALSE, 0);
	gtk_widget_show(vbox);

	check = gtk_check_button_new_with_label(CPU_ALL_CPUS_STR);
	gtk_box_pack_start(GTK_BOX(vbox), check, TRUE, TRUE, 0);

	/* The last button will be the all CPUs button */
	cpu_helper->buttons[cpus] = check;

	allset = cpus_selected ? 0 : 1;
	if (!allset) {
		/* check if the list is all set */
		for (cpu = 0; cpu < cpus; cpu++)
			if (!cpu_isset(cpus_selected, cpu))
				break;
		if (cpu == cpus)
			allset = 1;
	}

	if (allset)
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);

	g_signal_connect_swapped (check, "toggled",
				  G_CALLBACK (cpu_toggle),
				  (gpointer) cpu_helper);

	cpu_helper->allcpus = allset;
	cpu_helper->cpu_mask = g_new0(guint64, (cpus >> 6) + 1);
	g_assert(cpu_helper->cpu_mask != NULL);

	gtk_widget_show(check);

	for (cpu = 0; cpu < cpus; cpu++) {
		g_snprintf(counter, 100, "CPU %d", cpu);
		check = gtk_check_button_new_with_label(counter);
		cpu_helper->buttons[cpu] = check;
		gtk_box_pack_start(GTK_BOX(vbox), check, TRUE, FALSE, 0);
		if (cpus_selected && cpu_isset(cpus_selected, cpu)) {
			cpu_set(cpu_helper->cpu_mask, cpu);
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);
		} else
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), FALSE);

		g_signal_connect_swapped (check, "toggled",
					  G_CALLBACK (cpu_toggle),
					  (gpointer) cpu_helper);

		gtk_widget_show(check);
	}

	/* Figure out a good size to show */
	gtk_widget_size_request(hbox, &req);

	height = req.height;

	gtk_widget_size_request(scrollwin, &req);

	height += req.height;

	gtk_widget_size_request(dialog, &req);

	width = req.width;
	height += req.height;

	if (width > DIALOG_WIDTH)
		width = DIALOG_WIDTH;
	if (height > DIALOG_HEIGHT)
		height = DIALOG_HEIGHT;

	gtk_widget_set_size_request(GTK_WIDGET(dialog),
				    width, height);

	gtk_widget_show_all(dialog);

	result = gtk_dialog_run(GTK_DIALOG(dialog));
	switch (result) {
	case GTK_RESPONSE_ACCEPT:

		if (!cpu_helper->allcpus) {
			cpu_mask = cpu_helper->cpu_mask;
			cpu_helper->cpu_mask = NULL;
		}

		func(TRUE, cpu_helper->allcpus, cpu_mask, data);
		break;

	case GTK_RESPONSE_REJECT:
		func(FALSE, FALSE, NULL, data);
		break;
	default:
		break;
	};

	g_free(cpu_mask);

	gtk_widget_destroy(dialog);

	destroy_cpu_helper(cpu_helper);
}

/* -- Helper functions -- */

/**
 * trace_filter_convert_filter_to_names - convert a filter to names.
 * @filter: the filter to convert
 * @systems: array of systems that the filter selects (may be NULL)
 * @event_ids: array of event ids that the filter selects (may be NULL)
 *
 * @systems will be filled when the filter selects all events within
 * its system. (may return NULL)
 *
 * @event_ids will be all events selected (not including those selected
 *  by @systems)
 */
void trace_filter_convert_filter_to_names(struct event_filter *filter,
					  gchar ***systems,
					  gint **event_ids)
{
	struct pevent *pevent = filter->pevent;
	struct event_format **events;
	struct event_format *event;
	char *last_system = NULL;
	int all_selected = 1;
	int start_sys = 0;
	int sys_count = 0;
	int event_count = 0;
	int i, x;

	if (systems)
		*systems = NULL;
	if (event_ids)
		*event_ids = NULL;

	events = pevent_list_events(pevent, EVENT_SORT_SYSTEM);

	for (i = 0; events[i]; i++) {
		event = events[i];

		if (systems && last_system &&
		    strcmp(last_system, events[i]->system) != 0) {
			if (all_selected)
				*systems = tracecmd_add_list(*systems, last_system, sys_count++);
			start_sys = i;
			all_selected = 1;
		}

		if (pevent_filter_event_has_trivial(filter, event->id,
						    FILTER_TRIVIAL_TRUE)) {
			if (!all_selected || !systems)
				*event_ids = tracecmd_add_id(*event_ids, event->id, event_count++);
		} else {
			if (all_selected && event_ids) {
				for (x = start_sys; x < i; x++) {
					*event_ids = tracecmd_add_id(*event_ids,
						      events[x]->id, event_count++);
				}
			}
			all_selected = 0;

			/* If this event is filtered, still add it */
			if (pevent_event_filtered(filter, event->id))
				*event_ids = tracecmd_add_id(*event_ids, event->id, event_count++);
		}
		last_system = event->system;
	}

	if (systems && last_system && all_selected)
		*systems = tracecmd_add_list(*systems, last_system, sys_count++);
}

/**
 * trace_filter_convert_char_to_filter - convert the strings to the filter
 * @filter: the filter to convert
 * @systems: array of systems that will have its events selected in @filter
 * @events: array of event ids that will be selected in @filter
 */
void trace_filter_convert_char_to_filter(struct event_filter *filter,
					 gchar **systems,
					 gint *events)
{
	struct pevent *pevent;
	struct event_filter *copy;
	struct event_format *event;
	int i;

	pevent = filter->pevent;

	/* Make a copy to use later */
	copy = pevent_filter_alloc(pevent);
	pevent_filter_copy(copy, filter);
	pevent_filter_reset(filter);

	if (systems) {
		for (i = 0; systems[i]; i++)
			pevent_filter_add_filter_str(filter,
						     systems[i]);
	}

	if (events) {
		for (i = 0; events[i] >= 0; i++) {
			event = pevent_find_event(filter->pevent, events[i]);
			if (event)
				pevent_filter_add_filter_str(filter,
							     event->name);
		}
	}

	pevent_update_trivial(filter, copy, FILTER_TRIVIAL_BOTH);

	pevent_filter_free(copy);
}

int trace_filter_save_events(struct tracecmd_xml_handle *handle,
			     struct event_filter *filter)
{
	struct event_format *event;
	char **systems;
	gint *event_ids;
	char *str;
	int i;

	trace_filter_convert_filter_to_names(filter, &systems,
					     &event_ids);

	for (i = 0; systems && systems[i]; i++)
		tracecmd_xml_write_element(handle, "System", systems[i]);

	for (i = 0; event_ids && event_ids[i] > 0; i++) {
		str = pevent_filter_make_string(filter, event_ids[i]);
		if (!str)
			continue;

		event = pevent_find_event(filter->pevent, event_ids[i]);
		if (event) {

			/* skip not filtered items */
			if (strcmp(str, "FALSE") == 0) {
				free(str);
				continue;
			}

			tracecmd_xml_start_sub_system(handle, "Event");
			tracecmd_xml_write_element(handle, "System", event->system);
			tracecmd_xml_write_element(handle, "Name", event->name);
			/* If this is has an advanced filter, include that too */
			if (strcmp(str, "TRUE") != 0) {
				tracecmd_xml_write_element(handle, "Advanced",
							   str);
			}
			tracecmd_xml_end_sub_system(handle);
		}
		free(str);
	}

	return 0;
}

int trace_filter_save_tasks(struct tracecmd_xml_handle *handle,
			    struct filter_task *filter)
{
	char buffer[100];
	int *pids;
	int i;

	pids = filter_task_pids(filter);
	if (!pids)
		return -1;

	for (i = 0; pids[i] >= 0; i++) {
		snprintf(buffer, 100, "%d", pids[i]);
		tracecmd_xml_write_element(handle, "Task", buffer);
	}

	free(pids);

	return 0;
}

int trace_filter_load_events(struct event_filter *event_filter,
			     struct tracecmd_xml_handle *handle,
			     struct tracecmd_xml_system_node *node)
{
	struct tracecmd_xml_system_node *child;
	const char *name;
	const char *system;
	const char *event;
	const char *value;
	char *buffer;

	while (node) {
		name = tracecmd_xml_node_type(node);

		if (strcmp(name, "System") == 0) {
			system = tracecmd_xml_node_value(handle, node);
			pevent_filter_add_filter_str(event_filter,
						     system);
		} else if (strcmp(name, "Event") == 0) {
			system = NULL;
			event = NULL;
			value = NULL;
			child = tracecmd_xml_node_child(node);
			if (!child)
				return -1;
			do {
				name = tracecmd_xml_node_type(child);
				if (strcmp(name, "System") == 0)
					system = tracecmd_xml_node_value(handle, child);
				else if (strcmp(name, "Name") == 0)
					event = tracecmd_xml_node_value(handle, child);
				else if (strcmp(name, "Advanced") == 0)
					value = tracecmd_xml_node_value(handle, child);
				child = tracecmd_xml_node_next(child);
			} while (child);

			if (event || system) {
				if (event && system) {
					if (value) {
						buffer = malloc_or_die(strlen(event) +
								       strlen(system) +
								       strlen(value) + 3);
						sprintf(buffer, "%s/%s:%s",
							system, event, value);
					} else {
						buffer = malloc_or_die(strlen(event) +
								       strlen(system) + 2);
						sprintf(buffer, "%s/%s",
							system, event);
					}
				} else {
					if (!event)
						event = system;
					if (value) {
						buffer = malloc_or_die(strlen(event) +
								       strlen(value) + 2);
						sprintf(buffer, "%s:%s",
							event, value);
					} else {
						buffer = malloc_or_die(strlen(event) + 1);
						sprintf(buffer, "%s", event);
					}
				}
				pevent_filter_add_filter_str(event_filter,
							     buffer);
				free(buffer);
			}
		}

		node = tracecmd_xml_node_next(node);
	}

	return 0;
}

int trace_filter_load_task_filter(struct filter_task *filter,
				  struct tracecmd_xml_handle *handle,
				  struct tracecmd_xml_system_node *node)
{
	const char *name;
	const char *task;
	int pid;

	if (!filter)
		return 0;

	node = tracecmd_xml_node_child(node);

	while (node) {
		name = tracecmd_xml_node_type(node);

		if (strcmp(name, "Task") == 0) {
			task = tracecmd_xml_node_value(handle, node);
			pid = atoi(task);
			if (!filter_task_find_pid(filter, pid))
				filter_task_add_pid(filter, pid);
		}
		node = tracecmd_xml_node_next(node);
	}

	return 0;
}

int trace_filter_load_filters(struct tracecmd_xml_handle *handle,
			      const char *system_name,
			      struct filter_task *task_filter,
			      struct filter_task *hide_tasks)
{
	struct tracecmd_xml_system *system;
	struct tracecmd_xml_system_node *syschild;
	const char *name;

	system = tracecmd_xml_find_system(handle, system_name);
	if (!system)
		return -1;


	syschild = tracecmd_xml_system_node(system);
	if (!syschild)
		goto out_free_sys;

	do {
		name = tracecmd_xml_node_type(syschild);

		if (strcmp(name, "TaskFilter") == 0)
			trace_filter_load_task_filter(task_filter, handle, syschild);

		else if (strcmp(name, "HideTasks") == 0)
			trace_filter_load_task_filter(hide_tasks, handle, syschild);

		syschild = tracecmd_xml_node_next(syschild);
	} while (syschild);

	tracecmd_xml_free_system(system);

	return 0;

 out_free_sys:
	tracecmd_xml_free_system(system);
	return -1;
}

int trace_filter_save_filters(struct tracecmd_xml_handle *handle,
			      const char *system_name,
			      struct filter_task *task_filter,
			      struct filter_task *hide_tasks)
{

	tracecmd_xml_start_system(handle, system_name);

	if (task_filter && filter_task_count(task_filter)) {
		tracecmd_xml_start_sub_system(handle, "TaskFilter");
		trace_filter_save_tasks(handle, task_filter);
		tracecmd_xml_end_sub_system(handle);
	}

	if (hide_tasks && filter_task_count(hide_tasks)) {
		tracecmd_xml_start_sub_system(handle, "HideTasks");
		trace_filter_save_tasks(handle, hide_tasks);
		tracecmd_xml_end_sub_system(handle);
	}

	tracecmd_xml_end_system(handle);

	return 0;
}
