/**
 *  @file
 *  Implementation for sediffx's main toplevel window.
 *
 *  @author Jeremy A. Mowery jmowery@tresys.com
 *  @author Jason Tang jtang@tresys.com
 *  @author Brandon Whalen bwhalen@tresys.com
 *  @author Randy Wicks rwicks@tresys.com
 *
 *  Copyright (C) 2005-2007 Tresys Technology, LLC
 *
 *  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; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <config.h>

#include "find_dialog.h"
#include "open_policies_dialog.h"
#include "policy_view.h"
#include "remap_types_dialog.h"
#include "sediffx.h"
#include "select_diff_dialog.h"
#include "toplevel.h"
#include "utilgui.h"

#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <apol/util.h>
#include <gtk/gtk.h>
#include <glade/glade.h>

struct toplevel
{
	sediffx_t *s;
	progress_t *progress;
	find_dialog_t *find;
	results_t *results;
	policy_view_t *views[SEDIFFX_POLICY_NUM];
	GladeXML *xml;
	/** filename for glade file */
	char *xml_filename;
	/** toplevel window widget */
	GtkWindow *w;
	/** toplevel notebook widget */
	GtkNotebook *notebook;
};

/**
 * Enable/disable all items (menus and buttons) that depend upon if a
 * policy is loaded.
 *
 * @param top Toplevel object containing menu widgets.
 * @param TRUE to enable items, FALSE to disable.
 */
static void toplevel_enable_policy_items(toplevel_t * top, gboolean sens)
{
	static const char *items[] = {
		"Copy", "Select All",
		"Find", "Run Diff", "Remap Types",
		"run diff button", "remap types button",
		NULL
	};
	size_t i;
	const char *s;
	for (i = 0, s = items[0]; s != NULL; s = items[++i]) {
		GtkWidget *w = glade_xml_get_widget(top->xml, s);
		assert(w != NULL);
		gtk_widget_set_sensitive(w, sens);
	}
}

/**
 * Update the toplevel's title bar to list the policies currently
 * opened.
 *
 * @param top Toplevel to modify.
 */
static void toplevel_update_title_bar(toplevel_t * top)
{
	const apol_policy_path_t *paths[SEDIFFX_POLICY_NUM];
	char *types[SEDIFFX_POLICY_NUM] = { "Policy", "Policy" }, *s;
	const char *primaries[SEDIFFX_POLICY_NUM] = { NULL, NULL };
	sediffx_policy_e i;

	paths[SEDIFFX_POLICY_ORIG] = sediffx_get_policy_path(top->s, SEDIFFX_POLICY_ORIG);
	paths[SEDIFFX_POLICY_MOD] = sediffx_get_policy_path(top->s, SEDIFFX_POLICY_MOD);

	for (i = SEDIFFX_POLICY_ORIG; i < SEDIFFX_POLICY_NUM; i++) {
		if (paths[i] == NULL) {
			primaries[i] = "No Policy";
		} else {
			if (apol_policy_path_get_type(paths[i]) == APOL_POLICY_PATH_TYPE_MODULAR) {
				types[i] = "Base";
			}
			primaries[i] = apol_policy_path_get_primary(paths[i]);
		}
	}
	if (asprintf(&s, "sediffx - [%s file: %s] [%s file: %s]",
		     types[SEDIFFX_POLICY_ORIG], primaries[SEDIFFX_POLICY_ORIG],
		     types[SEDIFFX_POLICY_MOD], primaries[SEDIFFX_POLICY_MOD]) < 0) {
		toplevel_ERR(top, "%s", strerror(errno));
		return;
	}
	gtk_window_set_title(top->w, s);
	free(s);
}

/**
 * Initialize the application icons for the program.  These icons are
 * the ones shown by the window manager within title bars and pagers.
 * The last icon listed in the array will be displayed in the About
 * dialog.
 *
 * @param top Toplevel whose icon to set.  All child windows will
 * inherit these icons.
 */
static void init_icons(toplevel_t * top)
{
	static const char *icon_names[] = { "sediffx-small.png", "sediffx.png" };
	GdkPixbuf *icon;
	char *path;
	GList *icon_list = NULL;
	size_t i;
	for (i = 0; i < sizeof(icon_names) / sizeof(icon_names[0]); i++) {
		if ((path = apol_file_find_path(icon_names[i])) == NULL) {
			continue;
		}
		icon = gdk_pixbuf_new_from_file(path, NULL);
		free(path);
		if (icon == NULL) {
			continue;
		}
		icon_list = g_list_append(icon_list, icon);
	}
	gtk_window_set_default_icon_list(icon_list);
	gtk_window_set_icon_list(top->w, icon_list);
}

static void toplevel_on_switch_page(GtkNotebook * notebook __attribute__ ((unused)), GtkNotebookPage * page
				    __attribute__ ((unused)), guint page_num, gpointer user_data)
{
	toplevel_t *top = (toplevel_t *) user_data;
	if (page_num != 0) {
		toplevel_set_sort_menu_sensitivity(top, FALSE);
	} else {
		results_switch_to_page(top->results);
	}
}

toplevel_t *toplevel_create(sediffx_t * s)
{
	toplevel_t *top;
	int error = 0;
	sediffx_policy_e i;
	if ((top = calloc(1, sizeof(*top))) == NULL) {
		error = errno;
		goto cleanup;
	}
	top->s = s;
	if ((top->xml_filename = apol_file_find_path("sediffx.glade")) == NULL ||
	    (top->xml = glade_xml_new(top->xml_filename, "toplevel", NULL)) == NULL) {
		fprintf(stderr, "Could not open sediffx.glade.\n");
		error = EIO;
		goto cleanup;
	}
	top->w = GTK_WINDOW(glade_xml_get_widget(top->xml, "toplevel"));
	top->notebook = GTK_NOTEBOOK(glade_xml_get_widget(top->xml, "toplevel main notebook"));
	assert(top->w != NULL && top->notebook != NULL);
	init_icons(top);
	g_object_set_data(G_OBJECT(top->w), "toplevel", top);
	gtk_widget_show(GTK_WIDGET(top->w));
	g_signal_connect(G_OBJECT(top->notebook), "switch-page", G_CALLBACK(toplevel_on_switch_page), top);

	/* initialize sub-windows, now that glade XML file has been
	 * read */
	if ((top->find = find_dialog_create(top)) == NULL ||
	    (top->progress = progress_create(top)) == NULL || (top->results = results_create(top)) == NULL) {
		error = errno;
		goto cleanup;
	}
	for (i = SEDIFFX_POLICY_ORIG; i < SEDIFFX_POLICY_NUM; i++) {
		if ((top->views[i] = policy_view_create(top, i)) == NULL) {
			fprintf(stderr, "%s\n", strerror(errno));
			error = errno;
			goto cleanup;
		}
	}

	glade_xml_signal_autoconnect(top->xml);

      cleanup:
	if (error != 0) {
		toplevel_destroy(&top);
		errno = error;
		return NULL;
	}
	return top;
}

void toplevel_destroy(toplevel_t ** top)
{
	if (top != NULL && *top != NULL) {
		sediffx_policy_e i;
		find_dialog_destroy(&(*top)->find);
		progress_destroy(&(*top)->progress);
		results_destroy(&(*top)->results);
		for (i = SEDIFFX_POLICY_ORIG; i < SEDIFFX_POLICY_NUM; i++) {
			policy_view_destroy(&(*top)->views[i]);
		}
		free((*top)->xml_filename);
		free(*top);
		*top = NULL;
	}
}

struct policy_run_datum
{
	toplevel_t *top;
	apol_policy_path_t *paths[2];
	apol_policy_t *policies[2];
	int result;
};

/**
 * Thread that loads and parses a policy file.  It will write to
 * progress_seaudit_handle_func() its status during the load.
 *
 * @param data Pointer to a struct policy_run_datum, for control
 * information.
 */
static gpointer toplevel_open_policy_runner(gpointer data)
{
	struct policy_run_datum *run = (struct policy_run_datum *)data;
	sediffx_policy_e i;
	for (i = SEDIFFX_POLICY_ORIG; i < SEDIFFX_POLICY_NUM; i++) {
		apol_policy_path_t *path = run->paths[i];
		char *title = util_policy_path_to_string(path);
		if (title == NULL) {
			run->result = -1;
			progress_abort(run->top->progress, "%s", strerror(errno));
			return NULL;
		}
		progress_update(run->top->progress, "Opening %s", title);
		free(title);
		run->policies[i] =
			apol_policy_create_from_policy_path(path, QPOL_POLICY_OPTION_NO_RULES, progress_apol_handle_func,
							    run->top->progress);
		// poldiff_run() will rebuild the policies as needed
		if (run->policies[i] == NULL) {
			run->result = -1;
			progress_abort(run->top->progress, NULL);
			return NULL;
		}
	}
	run->result = 0;
	progress_done(run->top->progress);
	return NULL;
}

int toplevel_open_policies(toplevel_t * top, apol_policy_path_t * orig_path, apol_policy_path_t * mod_path)
{
	struct policy_run_datum run;
	memset(&run, 0, sizeof(run));
	run.top = top;
	run.paths[0] = orig_path;
	run.paths[1] = mod_path;
	sediffx_policy_e i;

	util_cursor_wait(GTK_WIDGET(top->w));
	progress_show(top->progress, "Loading Policies");
	g_thread_create(toplevel_open_policy_runner, &run, FALSE, NULL);
	progress_wait(top->progress);
	progress_hide(top->progress);
	util_cursor_clear(GTK_WIDGET(top->w));
	if (run.result < 0) {
		apol_policy_path_destroy(&run.paths[0]);
		apol_policy_path_destroy(&run.paths[1]);
		return run.result;
	}
	if (remap_types_update(run.policies[SEDIFFX_POLICY_ORIG], run.policies[SEDIFFX_POLICY_MOD]) < 0) {
		toplevel_ERR(top, "%s", strerror(errno));
		apol_policy_path_destroy(&run.paths[0]);
		apol_policy_path_destroy(&run.paths[1]);
		return -1;
	}
	results_open_policies(top->results, run.policies[SEDIFFX_POLICY_ORIG], run.policies[SEDIFFX_POLICY_MOD]);
	for (i = SEDIFFX_POLICY_ORIG; i < SEDIFFX_POLICY_NUM; i++) {
		policy_view_update(top->views[i], run.policies[i], run.paths[i]);
		sediffx_set_policy(top->s, i, run.policies[i], run.paths[i]);
	}
	results_update(top->results);
	toplevel_enable_policy_items(top, TRUE);
	toplevel_update_title_bar(top);
	return 0;
}

struct run_datum
{
	toplevel_t *top;
	uint32_t run_flags;
	int result;
};

static gpointer toplevel_run_diff_runner(gpointer data)
{
	struct run_datum *run = data;
	poldiff_t *diff = sediffx_get_poldiff(run->top->s, progress_poldiff_handle_func, run->top->progress);
	if (diff == NULL) {
		run->result = -1;
		progress_abort(run->top->progress, "Could not get a poldiff object: %s", strerror(errno));
		return NULL;
	}
	run->result = poldiff_run(diff, run->run_flags);
	sediffx_set_poldiff_run_flags(run->top->s, run->run_flags);
	if (run->run_flags & (POLDIFF_DIFF_AVRULES | POLDIFF_DIFF_TERULES)) {
		poldiff_enable_line_numbers(diff);
	}
	if (run->result < 0) {
		progress_abort(run->top->progress, NULL);
	} else {
		progress_done(run->top->progress);
	}
	return NULL;
}

void toplevel_run_diff(toplevel_t * top)
{
	struct run_datum r;

	r.run_flags = select_diff_dialog_run(top);
	if (r.run_flags == 0) {
		return;
	}
	r.top = top;
	r.result = 0;

	results_clear(top->results);
	util_cursor_wait(GTK_WIDGET(top->w));
	progress_show(top->progress, "Running Diff");
	g_thread_create(toplevel_run_diff_runner, &r, FALSE, NULL);
	progress_wait(top->progress);
	progress_hide(top->progress);
	util_cursor_clear(GTK_WIDGET(top->w));
	if (r.result == 0) {
		results_update(top->results);
	}
}

void toplevel_show_policy_line(toplevel_t * top, sediffx_policy_e which, unsigned long line)
{
	gtk_notebook_set_current_page(top->notebook, 1 + which);
	policy_view_show_policy_line(top->views[which], line);
}

void toplevel_set_sort_menu_sensitivity(toplevel_t * top, gboolean sens)
{
	GtkWidget *w = glade_xml_get_widget(top->xml, "sort menu item");
	assert(w != NULL);
	gtk_widget_set_sensitive(w, sens);
}

void toplevel_set_sort_menu_selection(toplevel_t * top, results_sort_e field, results_sort_dir_e dir)
{
	static const char *menu_items[][2] = {
		{"Default Sort", "Default Sort"},
		{"Ascending source type", "Descending source type"},
		{"Ascending target type", "Descending target type"},
		{"Ascending object class", "Descending object class"},
		{"Ascending conditional", "Descending conditonal"}
	};
	int direction = 1;
	if (dir == RESULTS_SORT_ASCEND) {
		direction = 0;
	}
	GtkWidget *w = glade_xml_get_widget(top->xml, menu_items[field][direction]);
	assert(w != NULL);
	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), TRUE);
}

char *toplevel_get_glade_xml(toplevel_t * top)
{
	return top->xml_filename;
}

gint toplevel_get_notebook_page(toplevel_t * top)
{
	return gtk_notebook_get_current_page(top->notebook);
}

progress_t *toplevel_get_progress(toplevel_t * top)
{
	return top->progress;
}

GtkWindow *toplevel_get_window(toplevel_t * top)
{
	return top->w;
}

GtkTextView *toplevel_get_text_view(toplevel_t * top)
{
	gint pagenum = gtk_notebook_get_current_page(top->notebook);
	switch (pagenum) {
	case 0:
		return results_get_text_view(top->results);
	case 1:
		return policy_view_get_text_view(top->views[SEDIFFX_POLICY_ORIG]);
	case 2:
		return policy_view_get_text_view(top->views[SEDIFFX_POLICY_MOD]);
	}
	/* should never get here */
	assert(0);
	return NULL;
}

poldiff_t *toplevel_get_poldiff(toplevel_t * top)
{
	return sediffx_get_poldiff(top->s, progress_poldiff_handle_func, top->progress);
}

uint32_t toplevel_get_poldiff_run_flags(toplevel_t * top)
{
	return sediffx_get_poldiff_run_flags(top->s);
}

/**
 * Pop-up a dialog with a line of text and wait for the user to
 * dismiss the dialog.
 *
 * @param top Toplevel window; this message dialog will be centered
 * upon it.
 * @param msg_type Type of message being displayed.
 * @param fmt Format string to print, using syntax of printf(3).
 */
static void toplevel_message(toplevel_t * top, GtkMessageType msg_type, const char *fmt, va_list ap)
{
	GtkWidget *dialog;
	char *msg;
	if (vasprintf(&msg, fmt, ap) < 0) {
		ERR(NULL, "%s", strerror(errno));
		return;
	}
	dialog = gtk_message_dialog_new(top->w, GTK_DIALOG_DESTROY_WITH_PARENT, msg_type, GTK_BUTTONS_CLOSE, msg);
	free(msg);
	gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
}

void toplevel_ERR(toplevel_t * top, const char *format, ...)
{
	va_list(ap);
	va_start(ap, format);
	toplevel_message(top, GTK_MESSAGE_ERROR, format, ap);
	va_end(ap);
}

void toplevel_WARN(toplevel_t * top, const char *format, ...)
{
	va_list(ap);
	va_start(ap, format);
	toplevel_message(top, GTK_MESSAGE_WARNING, format, ap);
	va_end(ap);
}

/******************** menu callbacks below ********************/

void toplevel_on_open_activate(gpointer user_data, GtkMenuItem * widget __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	const apol_policy_path_t *orig_path = sediffx_get_policy_path(top->s, SEDIFFX_POLICY_ORIG);
	const apol_policy_path_t *mod_path = sediffx_get_policy_path(top->s, SEDIFFX_POLICY_MOD);
	open_policies_dialog_run(top, orig_path, mod_path);
}

void toplevel_on_quit_activate(gpointer user_data, GtkMenuItem * widget __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	top->w = NULL;
	gtk_main_quit();
}

void toplevel_on_edit_menu_activate(gpointer user_data, GtkMenuItem * widget __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	GtkTextView *view = toplevel_get_text_view(top);
	GtkTextBuffer *txt = gtk_text_view_get_buffer(view);
	GtkWidget *copy = glade_xml_get_widget(top->xml, "Copy");
	GtkWidget *select_all = glade_xml_get_widget(top->xml, "Select All");
	GtkTextIter start, end;
	assert(copy != NULL && select_all != NULL);

	/* check to see if anything has been selected and set copy
	 * button up */
	if (gtk_text_buffer_get_selection_bounds(txt, &start, &end)) {
		gtk_widget_set_sensitive(copy, TRUE);
	} else {
		gtk_widget_set_sensitive(copy, FALSE);
	}
	/* check to see if there is anything currently in this buffer
	 * that can be selected */
	gtk_text_buffer_get_start_iter(txt, &start);
	gtk_text_buffer_get_end_iter(txt, &end);
	if (gtk_text_iter_get_offset(&start) == gtk_text_iter_get_offset(&end)) {
		gtk_widget_set_sensitive(select_all, FALSE);
	} else {
		gtk_widget_set_sensitive(select_all, TRUE);
	}
}

void toplevel_on_copy_activate(gpointer user_data, GtkMenuItem * widget __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	GtkClipboard *clipboard = gtk_clipboard_get(NULL);
	GtkTextView *view = toplevel_get_text_view(top);
	GtkTextBuffer *txt = gtk_text_view_get_buffer(view);
	if (gtk_text_buffer_get_selection_bounds(txt, NULL, NULL)) {
		gtk_text_buffer_copy_clipboard(txt, clipboard);
	}
}

void toplevel_on_select_all_activate(gpointer user_data, GtkMenuItem * widget __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	GtkTextView *view = toplevel_get_text_view(top);
	GtkTextBuffer *txt = gtk_text_view_get_buffer(view);
	GtkTextIter start, end;
	gtk_text_buffer_get_start_iter(txt, &start);
	gtk_text_buffer_get_end_iter(txt, &end);
	gtk_text_buffer_select_range(txt, &start, &end);
}

void toplevel_on_find_activate(gpointer user_data, GtkMenuItem * widget __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	find_dialog_show(top->find);
}

void toplevel_on_run_diff_activate(gpointer user_data, GtkMenuItem * widget __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	toplevel_run_diff(top);
}

void toplevel_on_remap_types_activate(gpointer user_data, GtkMenuItem * widget __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	remap_types_run(top);
}

void toplevel_on_default_sort_activate(gpointer user_data, GtkMenuItem * menuitem)
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
		results_sort(top->results, RESULTS_SORT_DEFAULT, RESULTS_SORT_ASCEND);
	}
}

void toplevel_on_source_type_asc_activate(gpointer user_data, GtkMenuItem * menuitem)
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
		results_sort(top->results, RESULTS_SORT_SOURCE, RESULTS_SORT_ASCEND);
	}
}

void toplevel_on_source_type_des_activate(gpointer user_data, GtkMenuItem * menuitem)
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
		results_sort(top->results, RESULTS_SORT_SOURCE, RESULTS_SORT_DESCEND);
	}
}

void toplevel_on_target_type_asc_activate(gpointer user_data, GtkMenuItem * menuitem)
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
		results_sort(top->results, RESULTS_SORT_TARGET, RESULTS_SORT_ASCEND);
	}
}

void toplevel_on_target_type_des_activate(gpointer user_data, GtkMenuItem * menuitem)
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
		results_sort(top->results, RESULTS_SORT_TARGET, RESULTS_SORT_DESCEND);
	}
}

void toplevel_on_class_asc_activate(gpointer user_data, GtkMenuItem * menuitem)
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
		results_sort(top->results, RESULTS_SORT_CLASS, RESULTS_SORT_ASCEND);
	}
}

void toplevel_on_class_des_activate(gpointer user_data, GtkMenuItem * menuitem)
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
		results_sort(top->results, RESULTS_SORT_CLASS, RESULTS_SORT_DESCEND);
	}
}

void toplevel_on_conditional_asc_activate(gpointer user_data, GtkMenuItem * menuitem)
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
		results_sort(top->results, RESULTS_SORT_COND, RESULTS_SORT_ASCEND);
	}
}

void toplevel_on_conditional_des_activate(gpointer user_data, GtkMenuItem * menuitem)
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
		results_sort(top->results, RESULTS_SORT_COND, RESULTS_SORT_DESCEND);
	}
}

void toplevel_on_help_activate(gpointer user_data, GtkMenuItem * widget __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	GtkWidget *window;
	GtkWidget *scroll;
	GtkWidget *text_view;
	GtkTextBuffer *buffer;
	char *help_text = NULL;
	size_t len;
	int rt;
	char *dir;

	window = gtk_dialog_new_with_buttons("sediffx Help",
					     GTK_WINDOW(top->w),
					     GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
	gtk_dialog_set_default_response(GTK_DIALOG(window), GTK_RESPONSE_CLOSE);
	g_signal_connect_swapped(window, "response", G_CALLBACK(gtk_widget_destroy), window);
	scroll = gtk_scrolled_window_new(NULL, NULL);
	text_view = gtk_text_view_new();
	gtk_window_set_default_size(GTK_WINDOW(window), 520, 300);
	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(window)->vbox), scroll);
	gtk_container_add(GTK_CONTAINER(scroll), text_view);
	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view), GTK_WRAP_NONE);
	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));
	if ((dir = apol_file_find_path("sediff_help.txt")) == NULL) {
		toplevel_ERR(top, "Cannot find help file.");
		return;
	}
	rt = apol_file_read_to_buffer(dir, &help_text, &len);
	free(dir);
	if (rt != 0) {
		free(help_text);
		return;
	}
	gtk_text_buffer_set_text(buffer, help_text, len);
	gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view), FALSE);
	gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ON_PARENT);
	gtk_widget_show(text_view);
	gtk_widget_show(scroll);
	gtk_widget_show(window);
}

void toplevel_on_about_sediffx_activate(gpointer user_data, GtkMenuItem * widget __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
#ifdef GTK_2_8
	gtk_show_about_dialog(top->w,
			      "comments", "Policy Semantic Difference Tool for Security Enhanced Linux",
			      "copyright", COPYRIGHT_INFO,
			      "name", "sediffx", "version", VERSION, "website", "http://oss.tresys.com/projects/setools", NULL);
#else
	GtkWidget *w = gtk_message_dialog_new(top->w,
					      GTK_DIALOG_DESTROY_WITH_PARENT,
					      GTK_MESSAGE_INFO,
					      GTK_BUTTONS_CLOSE,
					      "%s %s\n%s\n%s\n%s",
					      "sediffx", VERSION,
					      "Policy Semantic Difference Tool for Security Enhanced Linux",
					      COPYRIGHT_INFO,
					      "http://oss.tresys.com/projects/setools");
	gtk_dialog_run(GTK_DIALOG(w));
	gtk_widget_destroy(w);
#endif
}

void toplevel_on_open_policies_button_click(gpointer user_data, GtkWidget * widget __attribute__ ((unused)), GdkEvent * event
					    __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	const apol_policy_path_t *orig_path = sediffx_get_policy_path(top->s, SEDIFFX_POLICY_ORIG);
	const apol_policy_path_t *mod_path = sediffx_get_policy_path(top->s, SEDIFFX_POLICY_MOD);
	open_policies_dialog_run(top, orig_path, mod_path);
}

void toplevel_on_run_diff_button_click(gpointer user_data, GtkWidget * widget __attribute__ ((unused)), GdkEvent * event
				       __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	toplevel_run_diff(top);
}

void toplevel_on_remap_types_button_click(gpointer user_data, GtkWidget * widget __attribute__ ((unused)), GdkEvent * event
					  __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	remap_types_run(top);
}

void toplevel_on_destroy(gpointer user_data, GtkObject * object __attribute__ ((unused)))
{
	toplevel_t *top = g_object_get_data(G_OBJECT(user_data), "toplevel");
	top->w = NULL;
	gtk_main_quit();
}
