/* Copyright (C) 2005-2006 Fabio Marzocca <thesaltydog@gmail.com>
 * Copyright (C) 2012-2021 MATE Developers
 *
 * This file is part of MATE Utils.
 *
 * MATE Utils 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.
 *
 * MATE Utils 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 MATE Utils.  If not, see <https://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <glibtop.h>

#include "baobab.h"
#include "baobab-scan.h"
#include "baobab-treeview.h"
#include "baobab-utils.h"
#include "callbacks.h"
#include "baobab-prefs.h"

#include "baobab-treemap.h"
#include "baobab-ringschart.h"

#define BAOBAB_UI_RESOURCE "/org/mate/disk-usage-analyzer/baobab-main-window.ui"
#define GET_WIDGET(x) (GTK_WIDGET (gtk_builder_get_object (baobab.main_ui, (x))))
#define GET_ACTION(x) (GTK_ACTION (gtk_builder_get_object (baobab.main_ui, (x))))
#define GET_TOGGLE_ACTION(x) (GTK_TOGGLE_ACTION (gtk_builder_get_object (baobab.main_ui, (x))))

static void push_iter_in_stack (GtkTreeIter *);
static GtkTreeIter pop_iter_from_stack (void);

BaobabApplication baobab;

static gint currentdepth = 0;
static GtkTreeIter currentiter;
static GtkTreeIter firstiter;
static GQueue *iterstack = NULL;

enum {
	DND_TARGET_URI_LIST
};

static GtkTargetEntry dnd_target_list[] = {
	{ "text/uri-list", 0, DND_TARGET_URI_LIST },
};

static gboolean
scan_is_local (GFile	*file)
{
	gchar *uri_scheme;
	gboolean ret = FALSE;

	uri_scheme = g_file_get_uri_scheme (file);
	if (g_ascii_strcasecmp(uri_scheme,"file") == 0)
		ret = TRUE;

	g_free (uri_scheme);

	return ret;
}

void
baobab_set_busy (gboolean busy)
{
	static GdkCursor *busy_cursor = NULL;
	GdkCursor *cursor = NULL;
	GdkWindow *window;
	GdkDisplay *display;

	window = gtk_widget_get_window (baobab.window);

	if (busy == TRUE) {
		if (!busy_cursor && window) {
			display = gtk_widget_get_display (baobab.window);
			busy_cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
		}
		cursor = busy_cursor;

		gtk_widget_show (baobab.spinner);
		gtk_spinner_start (GTK_SPINNER (baobab.spinner));

		baobab_chart_freeze_updates (baobab.rings_chart);
		baobab_chart_freeze_updates (baobab.treemap_chart);

		gtk_widget_set_sensitive (baobab.chart_type_combo, FALSE);
	}
	else {
		gtk_widget_hide (baobab.spinner);
		gtk_spinner_stop (GTK_SPINNER (baobab.spinner));

		baobab_chart_thaw_updates (baobab.rings_chart);
		baobab_chart_thaw_updates (baobab.treemap_chart);

		gtk_widget_set_sensitive (baobab.chart_type_combo, TRUE);
	}

	/* change the cursor */
	if (window) {
		gdk_window_set_cursor (window, cursor);
	}
}

static void
set_drop_target (GtkWidget *target, gboolean active) {
	if (active) {
		gtk_drag_dest_set (GTK_WIDGET (target),
				   GTK_DEST_DEFAULT_DROP | GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT,
				   dnd_target_list,
				   G_N_ELEMENTS (dnd_target_list),
				   GDK_ACTION_COPY);
	} else {
		gtk_drag_dest_unset (target);
	}
}

/* menu & toolbar sensitivity */
static void
check_menu_sens (gboolean scanning)
{
	gboolean has_current_location;

	if (scanning) {
		while (gtk_events_pending ())
			gtk_main_iteration ();

		baobab_set_statusbar (_("Scanning..."));
		gtk_action_set_sensitive (GET_ACTION ("expand_all"), TRUE);
		gtk_action_set_sensitive (GET_ACTION ("collapse_all"), TRUE);
	}

	has_current_location = baobab.current_location != NULL;

	gtk_action_set_sensitive (GET_ACTION ("menuscanhome"), !scanning);
	gtk_action_set_sensitive (GET_ACTION ("menuallfs"), !scanning);
	gtk_action_set_sensitive (GET_ACTION ("menuscandir"), !scanning);
	gtk_action_set_sensitive (GET_ACTION ("menustop"), scanning);
	gtk_action_set_sensitive (GET_ACTION ("menurescan"), !scanning && has_current_location);
	gtk_action_set_sensitive (GET_ACTION ("preferenze1"), !scanning);
	gtk_action_set_sensitive (GET_ACTION ("menu_scan_rem"), !scanning);
	gtk_action_set_sensitive (GET_ACTION ("ck_allocated"), !scanning && baobab.is_local);

	gtk_widget_set_sensitive (GET_WIDGET ("tbscanhome"), !scanning);
	gtk_widget_set_sensitive (GET_WIDGET ("tbscanall"), !scanning);
	gtk_widget_set_sensitive (GET_WIDGET ("tbscandir"), !scanning);
	gtk_widget_set_sensitive (GET_WIDGET ("tbstop"), scanning);
	gtk_widget_set_sensitive (GET_WIDGET ("tbrescan"), !scanning && has_current_location);
	gtk_widget_set_sensitive (GET_WIDGET ("tb_scan_remote"), !scanning);
}

static void
check_drop_targets (gboolean scanning)
{
	set_drop_target (baobab.rings_chart, !scanning);
	set_drop_target (baobab.treemap_chart, !scanning);
}

static void
update_scan_label (void)
{
	gchar *markup;
	gchar *total;
	gchar *used;
	gchar *available;

		total = g_format_size (baobab.fs.total);
		used = g_format_size (baobab.fs.used);
		available = g_format_size (baobab.fs.avail);

	/* Translators: these are labels for disk space */
	markup = g_markup_printf_escaped  ("<small>%s <b>%s</b> (%s %s %s %s)</small>",
					   _("Total filesystem capacity:"), total,
					   _("used:"), used,
					   _("available:"), available);

	g_free (total);
	g_free (used);
	g_free (available);

	gtk_label_set_markup (GTK_LABEL (GET_WIDGET ("label1")), markup);
	g_free (markup);
}

void
baobab_update_filesystem (void)
{
	baobab_get_filesystem (&baobab.fs);
	update_scan_label ();
}

void
baobab_scan_location (GFile *file)
{
	GtkToggleAction *ck_allocated;

	if (!baobab_check_dir (file))
		return;

	if (iterstack !=NULL)
		return;

	if (baobab.current_location)
		g_object_unref (baobab.current_location);
	baobab.current_location = g_object_ref (file);

	baobab.STOP_SCANNING = FALSE;
	baobab_set_busy (TRUE);
	check_menu_sens (TRUE);
	check_drop_targets (TRUE);
	gtk_tree_store_clear (baobab.model);
	currentdepth = -1;	/* flag */
	iterstack = g_queue_new ();

	/* check if the file system is local or remote */
	baobab.is_local = scan_is_local (file);
	ck_allocated = GET_TOGGLE_ACTION ("ck_allocated");
	if (!baobab.is_local) {
		gtk_toggle_action_set_active (ck_allocated, FALSE);
		gtk_action_set_sensitive (GTK_ACTION (ck_allocated), FALSE);
		baobab.show_allocated = FALSE;
	}
	else {
		gtk_action_set_sensitive (GTK_ACTION (ck_allocated), TRUE);
	}

	baobab_scan_execute (file);

	/* set statusbar, percentage and allocated/normal size */
	baobab_set_statusbar (_("Calculating percentage bars..."));
	gtk_tree_model_foreach (GTK_TREE_MODEL (baobab.model),
				show_bars,
				NULL);

	baobab_chart_set_max_depth (baobab.rings_chart, baobab.model_max_depth);
	baobab_chart_set_max_depth (baobab.treemap_chart, baobab.model_max_depth);

	baobab_set_busy (FALSE);
	check_menu_sens (FALSE);
	check_drop_targets (FALSE);
	baobab_set_statusbar (_("Ready"));

	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (baobab.tree_view));
	baobab.STOP_SCANNING = TRUE;
	g_queue_free (iterstack);
	iterstack = NULL;
	baobab.CONTENTS_CHANGED_DELAYED = FALSE;
}

void
baobab_scan_home (void)
{
	GFile *file;

	file = g_file_new_for_path (g_get_home_dir ());
	baobab_scan_location (file);
	g_object_unref (file);
}

void
baobab_scan_root (void)
{
	GFile *file;

	file = g_file_new_for_uri ("file:///");
	baobab_scan_location (file);
	g_object_unref (file);
}

void
baobab_rescan_current_dir (void)
{
	g_return_if_fail (baobab.current_location != NULL);

	baobab_update_filesystem ();

	g_object_ref (baobab.current_location);
	baobab_scan_location (baobab.current_location);
	g_object_unref (baobab.current_location);
}

void
baobab_stop_scan (void)
{
	baobab.STOP_SCANNING = TRUE;

	baobab_set_statusbar (_("Calculating percentage bars..."));
	gtk_tree_model_foreach (GTK_TREE_MODEL (baobab.model),
				show_bars, NULL);
	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (baobab.tree_view));
}

/*
 * pre-fills model during scanning
 */
static void
prefill_model (struct chan_data *data)
{
	GtkTreeIter iter, iterparent;
	char *name;
	char *str;

	if (currentdepth == -1) {
		gtk_tree_store_append (baobab.model, &iter, NULL);
		firstiter = iter;
	}
	else if (data->depth == 1) {
		GtkTreePath *path;

		gtk_tree_store_append (baobab.model, &iter, &firstiter);
		path = gtk_tree_model_get_path (GTK_TREE_MODEL (baobab.model),
						&firstiter);
		gtk_tree_view_expand_row (GTK_TREE_VIEW (baobab.tree_view),
					  path, FALSE);
		gtk_tree_path_free (path);
	}
	else if (data->depth > currentdepth) {
		gtk_tree_store_append (baobab.model, &iter, &currentiter);
	}
	else if (data->depth == currentdepth) {
		gtk_tree_model_iter_parent ((GtkTreeModel *) baobab.model,
					    &iterparent, &currentiter);
		gtk_tree_store_append (baobab.model, &iter, &iterparent);
	}
	else if (data->depth < currentdepth) {
		GtkTreeIter tempiter;
		gint i;
		iter = currentiter;
		for (i = 0; i <= (currentdepth - data->depth); i++) {
			gtk_tree_model_iter_parent ((GtkTreeModel *)
						    baobab.model,
						    &tempiter, &iter);
			iter = tempiter;
		}
		gtk_tree_store_append (baobab.model, &iter, &tempiter);
	}

	currentdepth = data->depth;
	push_iter_in_stack (&iter);
	currentiter = iter;

	/* in case filenames contains gmarkup */
	name = g_markup_escape_text (data->display_name, -1);

	str = g_strdup_printf ("<small><i>%s</i></small>", _("Scanning..."));

	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (baobab.tree_view), TRUE);
	gtk_tree_store_set (baobab.model, &iter,
			    COL_DIR_NAME, name,
			    COL_H_PARSENAME, "",
			    COL_H_ELEMENTS, -1,
			    COL_H_PERC, -1.0,
			    COL_DIR_SIZE, str,
			    COL_ELEMENTS, str, -1);

	g_free (name);
	g_free (str);

	while (gtk_events_pending ()) {
		gtk_main_iteration ();
	}
}

static void
first_row (void)
{
	char *size;
	gdouble perc;
	char *label;

	GtkTreeIter root_iter;

	gchar *capacity_label, *capacity_size;

	gtk_tree_store_append (baobab.model, &root_iter, NULL);

	capacity_size = g_format_size (baobab.fs.total);

	capacity_label = g_strdup (_("Total filesystem capacity"));
	gtk_tree_store_set (baobab.model, &root_iter,
			    COL_DIR_NAME, capacity_label,
			    COL_H_PARSENAME, "",
			    COL_H_PERC, 100.0,
			    COL_DIR_SIZE, capacity_size,
			    COL_H_SIZE, baobab.fs.total,
			    COL_H_ALLOCSIZE, baobab.fs.total,
			    COL_H_ELEMENTS, -1, -1);
	g_free (capacity_label);
	g_free (capacity_size);

	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (baobab.tree_view), FALSE);
	gtk_tree_store_append (baobab.model, &firstiter, &root_iter);

		size = g_format_size (baobab.fs.used);

	if (baobab.fs.total == 0 && baobab.fs.used == 0) {
		perc = 100.0;
	} else {
		g_assert (baobab.fs.total != 0);
		perc = ((gdouble) baobab.fs.used * 100) / (gdouble) baobab.fs.total;
	}

	label = g_strdup (_("Total filesystem usage"));
	gtk_tree_store_set (baobab.model, &firstiter,
			    COL_DIR_NAME, label,
			    COL_H_PARSENAME, "",
			    COL_H_PERC, perc,
			    COL_DIR_SIZE, size,
			    COL_H_SIZE, baobab.fs.used,
			    COL_H_ALLOCSIZE, baobab.fs.used,
			    COL_H_ELEMENTS, -1, -1);

	g_free (size);
	g_free (label);

	gtk_tree_view_expand_all (GTK_TREE_VIEW (baobab.tree_view));
}

/* fills model during scanning */
void
baobab_fill_model (struct chan_data *data)
{
	GtkTreeIter iter;
	GString *hardlinks;
	GString *elements;
	char *name;
	char *size;
	char *alloc_size;

	if (data->elements == -1) {
		prefill_model (data);
		return;
	}

	iter = pop_iter_from_stack ();

	/* in case filenames contains gmarkup */
	name = g_markup_escape_text (data->display_name, -1);

	hardlinks = g_string_new ("");
	if (data->tempHLsize > 0) {
		size = g_format_size (data->tempHLsize);

		g_string_assign (hardlinks, "<i>(");
		g_string_append (hardlinks, _("contains hardlinks for:"));
		g_string_append (hardlinks, " ");
		g_string_append (hardlinks, size);
		g_string_append (hardlinks, ")</i>");
		g_free (size);
	}

	elements = g_string_new ("");
	g_string_printf (elements,
			 ngettext ("%5d item", "%5d items",
				   data->elements), data->elements);

	size = g_format_size (data->size);
	alloc_size = g_format_size (data->alloc_size);

	gtk_tree_store_set (baobab.model, &iter,
			    COL_DIR_NAME, name,
			    COL_H_PARSENAME, data->parse_name,
			    COL_H_PERC, -1.0,
			    COL_DIR_SIZE,
			    baobab.show_allocated ? alloc_size : size,
			    COL_H_SIZE, data->size,
			    COL_ELEMENTS, elements->str,
			    COL_H_ELEMENTS, data->elements,
			    COL_HARDLINK, hardlinks->str,
			    COL_H_HARDLINK, data->tempHLsize,
			    COL_H_ALLOCSIZE, data->alloc_size, -1);

	while (gtk_events_pending ()) {
		gtk_main_iteration ();
	}

	g_string_free (hardlinks, TRUE);
	g_string_free (elements, TRUE);
	g_free (name);
	g_free (size);
	g_free (alloc_size);
}

void
push_iter_in_stack (GtkTreeIter *iter)
{
	g_queue_push_head (iterstack, iter->user_data3);
	g_queue_push_head (iterstack, iter->user_data2);
	g_queue_push_head (iterstack, iter->user_data);
	g_queue_push_head (iterstack, GINT_TO_POINTER (iter->stamp));
}

GtkTreeIter
pop_iter_from_stack (void)
{
	GtkTreeIter iter;

	iter.stamp = GPOINTER_TO_INT (g_queue_pop_head (iterstack));
	iter.user_data = g_queue_pop_head (iterstack);
	iter.user_data2 = g_queue_pop_head (iterstack);
	iter.user_data3 = g_queue_pop_head (iterstack);

	return iter;
}

gboolean
baobab_is_excluded_location (GFile *file)
{
	gboolean ret = FALSE;
	GSList *l;

	g_return_val_if_fail (file != NULL, FALSE);

	for (l = baobab.excluded_locations; l != NULL; l = l->next) {
		if (g_file_equal (l->data, file)) {
			ret = TRUE;
			break;
		}
	}

	return ret;
}

static void
volume_changed (GVolumeMonitor *volume_monitor,
                GVolume        *volume,
                gpointer        user_data)
{
	/* filesystem has changed (mounted or unmounted device) */
	baobab_update_filesystem ();
}

static void
home_contents_changed (GFileMonitor      *file_monitor,
		       GFile             *child,
		       GFile             *other_file,
		       GFileMonitorEvent  event_type,
		       gpointer           user_data)

{
	gchar *excluding;

	if (baobab.CONTENTS_CHANGED_DELAYED)
		return;

	excluding = g_file_get_basename (child);
	if (strcmp (excluding, ".recently-used") == 0   ||
	    strcmp (excluding, ".mate2_private") == 0  ||
	    strcmp (excluding, ".xsession-errors") == 0 ||
	    strcmp (excluding, ".bash_history") == 0) {
		g_free (excluding);
		return;
	}
	g_free (excluding);

	baobab.CONTENTS_CHANGED_DELAYED = TRUE;
}

static void
monitor_volume (void)
{
	baobab.monitor_vol = g_volume_monitor_get ();

	g_signal_connect (baobab.monitor_vol, "volume_changed",
			  G_CALLBACK (volume_changed), NULL);
}

static void
monitor_home (gboolean enable)
{
	if (enable && baobab.monitor_home == NULL) {
		GFile *file;
		GError *error = NULL;

		file = g_file_new_for_path (g_get_home_dir ());
		baobab.monitor_home = g_file_monitor_directory (file, 0, NULL, &error);
		g_object_unref (file);

		if (!baobab.monitor_home) {
			message (_("Could not initialize monitoring"),
				 _("Changes to your home folder will not be monitored."),
				 GTK_MESSAGE_WARNING, NULL);
			g_print ("homedir:%s\n", error->message);
			g_error_free (error);
		}
		else {
			g_signal_connect (baobab.monitor_home,
					  "changed",
					  G_CALLBACK (home_contents_changed),
					  NULL);
		}
	}
	else if (!enable && baobab.monitor_home != NULL) {
		g_file_monitor_cancel (baobab.monitor_home);
		g_object_unref (baobab.monitor_home);
		baobab.monitor_home = NULL;
	}
}

void
baobab_set_statusbar (const gchar *text)
{
	gtk_statusbar_pop (GTK_STATUSBAR (baobab.statusbar), 1);
	gtk_statusbar_push (GTK_STATUSBAR (baobab.statusbar), 1, text);

	while (gtk_events_pending ())
		gtk_main_iteration ();
}

static void
toolbar_reconfigured_cb (GtkToolItem *item,
			 GtkWidget   *spinner)
{
	GtkToolbarStyle style;
	GtkIconSize size;

	style = gtk_tool_item_get_toolbar_style (item);

	if (style == GTK_TOOLBAR_BOTH)
	{
		size = GTK_ICON_SIZE_DIALOG;
	}
	else
	{
		size = GTK_ICON_SIZE_LARGE_TOOLBAR;
	}

	gtk_widget_set_size_request (spinner, size, size);
}

static void
baobab_create_toolbar (void)
{
	GtkWidget *toolbar;
	GtkToolItem *item;
	GtkToolItem *separator;

	toolbar = GET_WIDGET ("toolbar1");
	if (toolbar == NULL) {
		g_printerr ("Could not build toolbar\n");
		return;
	}

	baobab.toolbar = toolbar;

	separator = gtk_separator_tool_item_new ();
	gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (separator), FALSE);
	gtk_tool_item_set_expand (GTK_TOOL_ITEM (separator), TRUE);
	gtk_container_add (GTK_CONTAINER (toolbar), GTK_WIDGET (separator));
	gtk_widget_show (GTK_WIDGET (separator));

	baobab.spinner = gtk_spinner_new ();
	item = gtk_tool_item_new ();
	gtk_container_add (GTK_CONTAINER (item), baobab.spinner);
	gtk_container_add (GTK_CONTAINER (toolbar), GTK_WIDGET (item));
	gtk_widget_show (GTK_WIDGET (item));

	g_signal_connect (item, "toolbar-reconfigured",
			  G_CALLBACK (toolbar_reconfigured_cb), baobab.spinner);
	toolbar_reconfigured_cb (item, baobab.spinner);

	g_settings_bind (baobab.ui_settings,
			 BAOBAB_SETTINGS_TOOLBAR_VISIBLE,
			 baobab.toolbar, "visible",
			 G_SETTINGS_BIND_DEFAULT);
	g_settings_bind (baobab.ui_settings,
			 BAOBAB_SETTINGS_TOOLBAR_VISIBLE,
			 GET_TOGGLE_ACTION ("view_tb"), "active",
			 G_SETTINGS_BIND_DEFAULT);

}

static void
baobab_create_statusbar (void)
{
	baobab.statusbar = GET_WIDGET ("statusbar1");
	if (baobab.statusbar == NULL) {
		g_printerr ("Could not build statusbar\n");
		return;
	}

	g_settings_bind (baobab.ui_settings,
			 BAOBAB_SETTINGS_STATUSBAR_VISIBLE,
			 baobab.statusbar, "visible",
			 G_SETTINGS_BIND_DEFAULT);
	g_settings_bind (baobab.ui_settings,
			 BAOBAB_SETTINGS_STATUSBAR_VISIBLE,
			 GET_TOGGLE_ACTION ("view_sb"), "active",
			 G_SETTINGS_BIND_DEFAULT);
}

static void
baobab_settings_subfoldertips_changed (GSettings *settings,
									   const gchar *key,
									   gpointer user_data)
{
	gboolean visible;

	visible = g_settings_get_boolean (settings, key);
	baobab_ringschart_set_subfoldertips_enabled (baobab.rings_chart, visible);
}

static void
baobab_set_excluded_locations (gchar **uris)
{
	gint i;

	g_slist_free_full (baobab.excluded_locations, g_object_unref);
	baobab.excluded_locations = NULL;
	for (i = 0; uris[i] != NULL; ++i) {
		baobab.excluded_locations = g_slist_prepend (baobab.excluded_locations,
						g_file_new_for_uri (uris[i]));
	}
}

static void
store_excluded_locations (void)
{
	GSList *l;
	GPtrArray *uris;

	uris = g_ptr_array_new ();

	for (l = baobab.excluded_locations; l != NULL; l = l->next) {
		g_ptr_array_add (uris, g_file_get_uri (l->data));
	}

	g_ptr_array_add (uris, NULL);

	g_settings_set_strv (baobab.prefs_settings,
			     BAOBAB_SETTINGS_EXCLUDED_URIS,
			     (const gchar *const *) uris->pdata);

	g_ptr_array_free (uris, TRUE);
}

static void
sanity_check_excluded_locations (void)
{
	GFile *root;
	GSList *l;

	/* Make sure the root dir is not excluded */
	root = g_file_new_for_uri ("file:///");

	for (l = baobab.excluded_locations; l != NULL; l = l->next) {
		if (g_file_equal (l->data, root)) {
			baobab.excluded_locations = g_slist_delete_link (baobab.excluded_locations, l);
			store_excluded_locations ();
			break;
		}
	}

	g_object_unref (root);
}

static void
excluded_uris_changed (GSettings   *settings,
		       const gchar *key,
		       gpointer     user_data)
{
	gchar **uris;

	uris = g_settings_get_strv (settings, key);
	baobab_set_excluded_locations (uris);
	g_strfreev (uris);

	baobab_update_filesystem ();

	gtk_tree_store_clear (baobab.model);
	first_row ();
}

static void
baobab_setup_excluded_locations (void)
{
	gchar **uris;

	g_signal_connect (baobab.prefs_settings,
			 "changed::" BAOBAB_SETTINGS_EXCLUDED_URIS,
			  G_CALLBACK (excluded_uris_changed),
			  NULL);

	uris = g_settings_get_strv (baobab.prefs_settings,
				    BAOBAB_SETTINGS_EXCLUDED_URIS);
	baobab_set_excluded_locations (uris);
	g_strfreev (uris);

	sanity_check_excluded_locations ();
}

static void
baobab_settings_monitor_home_changed (GSettings *settings,
				      const gchar *key,
				      gpointer user_data)
{
	gboolean enable;

	enable = g_settings_get_boolean (settings, key);

	monitor_home (enable);
}

static void
baobab_setup_monitors (void)
{
	gboolean enable;

	monitor_volume ();

	g_signal_connect (baobab.prefs_settings,
			  "changed::" BAOBAB_SETTINGS_MONITOR_HOME,
			  G_CALLBACK (baobab_settings_monitor_home_changed),
			  NULL);

	enable = g_settings_get_boolean (baobab.prefs_settings,
					 BAOBAB_SETTINGS_MONITOR_HOME);

	monitor_home (enable);
}

static void
baobab_init (void)
{
	GError *error = NULL;

	/* FileSystem usage */
	baobab_get_filesystem (&baobab.fs);

	/* Load the UI */
	baobab.main_ui = gtk_builder_new ();
	if (gtk_builder_add_from_resource (baobab.main_ui, BAOBAB_UI_RESOURCE, &error) == 0) {
		g_object_unref (baobab.main_ui);
		g_critical ("Unable to load the user interface file: %s", error->message);
		g_error_free (error);
		exit (1);
	}

	gtk_builder_connect_signals (baobab.main_ui, NULL);

	/* Settings */
	baobab.ui_settings = g_settings_new (BAOBAB_UI_SETTINGS_SCHEMA);
	baobab.prefs_settings = g_settings_new (BAOBAB_PREFS_SETTINGS_SCHEMA);

	/* Misc */
	baobab.CONTENTS_CHANGED_DELAYED = FALSE;
	baobab.STOP_SCANNING = TRUE;
	baobab.show_allocated = TRUE;
	baobab.is_local = TRUE;

	baobab_setup_excluded_locations ();

	baobab_create_toolbar ();
	baobab_create_statusbar ();
	baobab_setup_monitors ();

	g_signal_connect (baobab.ui_settings, "changed::" BAOBAB_SETTINGS_SUBFLSTIPS_VISIBLE,
					  (GCallback) baobab_settings_subfoldertips_changed, NULL);
}

static void
baobab_shutdown (void)
{
	if (baobab.current_location) {
		g_object_unref (baobab.current_location);
	}

	if (baobab.monitor_vol) {
		g_object_unref (baobab.monitor_vol);
	}

	if (baobab.monitor_home) {
		g_file_monitor_cancel (baobab.monitor_home);
		g_object_unref (baobab.monitor_home);
	}

	g_free (baobab.selected_path);

	g_slist_free_full (baobab.excluded_locations, g_object_unref);

	if (baobab.ui_settings) {
		g_object_unref (baobab.ui_settings);
	}

	if (baobab.prefs_settings) {
		g_object_unref (baobab.prefs_settings);
	}
}

static BaobabChartMenu *
create_context_menu (void)
{
	BaobabChartMenu *menu = NULL;

	baobab.chart_menu = g_new0 (BaobabChartMenu, 1);
	menu = baobab.chart_menu;

	menu->widget = gtk_menu_new ();

	menu->up_item = gtk_image_menu_item_new_with_label (_("Move to parent folder"));
	gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu->up_item),
				       gtk_image_new_from_icon_name("go-up", GTK_ICON_SIZE_MENU));

	menu->zoom_in_item = gtk_image_menu_item_new_with_label (_("Zoom in")) ;
	gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu->zoom_in_item),
				       gtk_image_new_from_icon_name("list-add", GTK_ICON_SIZE_MENU));

	menu->zoom_out_item = gtk_image_menu_item_new_with_label (_("Zoom out"));
	gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu->zoom_out_item),
				       gtk_image_new_from_icon_name("list-remove", GTK_ICON_SIZE_MENU));

	menu->snapshot_item = gtk_image_menu_item_new_with_label (_("Save screenshot"));
	gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu->snapshot_item),
				       gtk_image_new_from_icon_name ("applets-screenshooter", GTK_ICON_SIZE_MENU));

	gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget),
			       menu->up_item);
	gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget),
			       gtk_separator_menu_item_new ());
	gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget),
			       menu->zoom_in_item);
	gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget),
			       menu->zoom_out_item);
	gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget),
			       gtk_separator_menu_item_new ());
	gtk_menu_shell_append (GTK_MENU_SHELL (menu->widget),
			       menu->snapshot_item);

	/* connect signals */
	g_signal_connect (menu->up_item, "activate",
			  G_CALLBACK (on_move_upwards_cb), NULL);
	g_signal_connect (menu->zoom_in_item, "activate",
			  G_CALLBACK (on_zoom_in_cb), NULL);
	g_signal_connect (menu->zoom_out_item, "activate",
			  G_CALLBACK (on_zoom_out_cb), NULL);
	g_signal_connect (menu->snapshot_item, "activate",
					  G_CALLBACK (on_chart_snapshot_cb), NULL);

	gtk_widget_show_all (menu->widget);

	return menu;
}

static void
on_chart_item_activated (BaobabChart *chart, GtkTreeIter *iter)
{
	GtkTreePath *path;

	path = gtk_tree_model_get_path (GTK_TREE_MODEL (baobab.model), iter);

	if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (baobab.tree_view), path))
		gtk_tree_view_expand_to_path (GTK_TREE_VIEW (baobab.tree_view), path);

	gtk_tree_view_set_cursor (GTK_TREE_VIEW (baobab.tree_view),
				  path, NULL, FALSE);
	gtk_tree_path_free (path);
}

static gboolean
on_chart_button_release (BaobabChart *chart,
			 GdkEventButton *event,
			 gpointer data)
{
	if (baobab_chart_is_frozen (baobab.current_chart))
		return FALSE;

	if (event->button== 3) /* right button */
	{
		GtkTreePath *root_path;
		BaobabChartMenu *menu;

		root_path = baobab_chart_get_root (baobab.current_chart);

		menu = baobab.chart_menu;
		gtk_widget_set_sensitive (menu->up_item,
					  ((root_path != NULL) &&
					  (gtk_tree_path_get_depth (root_path) > 1)));
		gtk_widget_set_sensitive (menu->zoom_in_item,
					  baobab_chart_can_zoom_in (baobab.current_chart));
		gtk_widget_set_sensitive (menu->zoom_out_item,
					  baobab_chart_can_zoom_out (baobab.current_chart));

		/* show the menu */
		gtk_menu_popup_at_pointer (GTK_MENU (menu->widget),
		                           (const GdkEvent*) event);

		gtk_tree_path_free (root_path);
	}

	return FALSE;
}

static void
drag_data_received_handl (GtkWidget *widget,
			 GdkDragContext *context,
			 gint x,
			 gint y,
			 GtkSelectionData *selection_data,
			 guint target_type,
			 guint time,
			 gpointer data)
{
	GFile *gf = NULL;

	/* set "gf" if we got some valid data */
	if ((selection_data != NULL) &&
	    (gtk_selection_data_get_length (selection_data) >= 0) &&
	    (target_type == DND_TARGET_URI_LIST)) {
		gchar **uri_list;
		uri_list = g_uri_list_extract_uris ((const gchar *) gtk_selection_data_get_data (selection_data));
		/* check list is 1 item long */
		if (uri_list != NULL && uri_list[0] != NULL && uri_list[1] == NULL) {
			gf = g_file_new_for_uri (uri_list[0]);
		}
		g_strfreev (uri_list);
	}

	/* success if "gf" has been set */
	if (gf != NULL) {
		/* finish drop before beginning scan, as the drag-drop can
		   probably time out */
		gtk_drag_finish (context, TRUE, FALSE, time);
		baobab_scan_location (gf);
		g_object_unref (gf);
	} else {
		gtk_drag_finish (context, FALSE, FALSE, time);
	}
}

static void
set_active_chart (GtkWidget *chart)
{
	if (baobab.current_chart != chart) {
		if (baobab.current_chart) {
			baobab_chart_freeze_updates (baobab.current_chart);

			g_object_ref (baobab.current_chart);
			gtk_container_remove (GTK_CONTAINER (baobab.chart_frame),
					      baobab.current_chart);
		}

		gtk_container_add (GTK_CONTAINER (baobab.chart_frame), chart);
		g_object_unref (chart);

		baobab_chart_thaw_updates (chart);

		baobab.current_chart = chart;

		gtk_widget_show_all (baobab.chart_frame);

		g_settings_set_string (baobab.ui_settings,
				       BAOBAB_SETTINGS_ACTIVE_CHART,
				       baobab.current_chart == baobab.rings_chart ? "rings" : "treemap");
	}
}

static void
on_chart_type_change (GtkWidget *combo, gpointer user_data)
{
	GtkWidget *chart;
	gint active;

	active = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));

	switch (active) {
	case 0:
		chart = baobab.rings_chart;
		break;
	case 1:
		chart = baobab.treemap_chart;
		break;
	default:
		g_return_if_reached ();
	}

	set_active_chart (chart);
}

static void
initialize_charts (void)
{
	GtkWidget *hpaned_main;
	GtkWidget *hbox1;
	char *saved_chart;
	gboolean visible;

	baobab.chart_frame = gtk_frame_new (NULL);
	gtk_frame_set_label_align (GTK_FRAME (baobab.chart_frame), 0.0, 0.0);
	gtk_frame_set_shadow_type (GTK_FRAME (baobab.chart_frame), GTK_SHADOW_IN);

	hpaned_main = GET_WIDGET ("hpaned_main");
	gtk_paned_pack2 (GTK_PANED (hpaned_main),
			 baobab.chart_frame, TRUE, TRUE);
	gtk_paned_set_position (GTK_PANED (hpaned_main), 480);

	baobab.chart_type_combo = gtk_combo_box_text_new ();
	gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (baobab.chart_type_combo),
				   _("View as Rings Chart"));
	gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (baobab.chart_type_combo),
				   _("View as Treemap Chart"));
	gtk_widget_show (baobab.chart_type_combo);
	g_signal_connect (baobab.chart_type_combo,
			  "changed",
			  G_CALLBACK (on_chart_type_change), NULL);

	hbox1 = GET_WIDGET ("hbox1");
	gtk_container_add (GTK_CONTAINER (hbox1), baobab.chart_type_combo);
	gtk_box_set_spacing (GTK_BOX (hbox1), 50);
	gtk_box_set_child_packing (GTK_BOX (hbox1),
				   baobab.chart_type_combo,
				   FALSE,
				   TRUE,
				   0, GTK_PACK_END);

	baobab.chart_menu = create_context_menu ();

	/* Baobab's Treemap Chart */
	baobab.treemap_chart = baobab_treemap_new ();
	baobab_chart_set_model_with_columns (baobab.treemap_chart,
					     GTK_TREE_MODEL (baobab.model),
					     COL_DIR_NAME,
					     COL_DIR_SIZE,
					     COL_H_PARSENAME,
					     COL_H_PERC,
					     COL_H_ELEMENTS,
					     NULL);
	baobab_chart_set_max_depth (baobab.treemap_chart, 1);
	g_signal_connect (baobab.treemap_chart, "item_activated",
			  G_CALLBACK (on_chart_item_activated), NULL);
	g_signal_connect (baobab.treemap_chart, "button-release-event",
			  G_CALLBACK (on_chart_button_release), NULL);
	g_signal_connect (baobab.treemap_chart, "drag-data-received",
			  G_CALLBACK (drag_data_received_handl), NULL);
	gtk_widget_show (baobab.treemap_chart);
	g_object_ref_sink (baobab.treemap_chart);
	baobab_chart_freeze_updates (baobab.treemap_chart);

	/* Baobab's Rings Chart */
	baobab.rings_chart = (GtkWidget *) baobab_ringschart_new ();
	baobab_chart_set_model_with_columns (baobab.rings_chart,
					     GTK_TREE_MODEL (baobab.model),
					     COL_DIR_NAME,
					     COL_DIR_SIZE,
					     COL_H_PARSENAME,
					     COL_H_PERC,
					     COL_H_ELEMENTS,
					     NULL);

	visible = g_settings_get_boolean (baobab.ui_settings,
									  BAOBAB_SETTINGS_SUBFLSTIPS_VISIBLE);
	baobab_ringschart_set_subfoldertips_enabled (baobab.rings_chart, visible);

	baobab_chart_set_max_depth (baobab.rings_chart, 1);
	g_signal_connect (baobab.rings_chart, "item_activated",
			  G_CALLBACK (on_chart_item_activated), NULL);
	g_signal_connect (baobab.rings_chart, "button-release-event",
			  G_CALLBACK (on_chart_button_release), NULL);
	g_signal_connect (baobab.rings_chart, "drag-data-received",
			  G_CALLBACK (drag_data_received_handl), NULL);
	gtk_widget_show (baobab.rings_chart);
	g_object_ref_sink (baobab.rings_chart);
	baobab_chart_freeze_updates (baobab.rings_chart);

	saved_chart = g_settings_get_string (baobab.ui_settings,
					     BAOBAB_SETTINGS_ACTIVE_CHART);

	if (0 == g_ascii_strcasecmp (saved_chart, "treemap")) {
		set_active_chart (baobab.treemap_chart);
		gtk_combo_box_set_active (GTK_COMBO_BOX (baobab.chart_type_combo), 1);
	} else {
		set_active_chart (baobab.rings_chart);
		gtk_combo_box_set_active (GTK_COMBO_BOX (baobab.chart_type_combo), 0);
	}

	g_free (saved_chart);

	check_drop_targets (FALSE);
}

void
baobab_quit ()
{
	baobab_stop_scan ();
	gtk_main_quit ();
}

static gboolean
start_proc_on_command_line (GFile *file)
{
	baobab_scan_location (file);

	return FALSE;
}

static gboolean
show_version (const gchar *option_name,
              const gchar *value,
              gpointer data,
              GError **error)
{
	g_print("%s %s\n", g_get_application_name (), VERSION);
	exit (0);
	return TRUE; /* It's just good form */
}

int
main (int argc, char *argv[])
{
	gchar **directories = NULL;
	const GOptionEntry options[] = {
		{"version", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_version, N_("Show version"), NULL},
		{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &directories, NULL, N_("[DIRECTORY]")},
		{NULL}
	};
	GOptionContext *context;
	GError *error = NULL;

#ifdef ENABLE_NLS
	bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
	textdomain (GETTEXT_PACKAGE);
#endif /* ENABLE_NLS */

	g_set_application_name (_("Disk Usage Analyzer"));

	context = g_option_context_new (NULL);
	g_option_context_set_ignore_unknown_options (context, FALSE);
	g_option_context_set_help_enabled (context, TRUE);
	g_option_context_add_main_entries(context, options, GETTEXT_PACKAGE);
	g_option_context_add_group (context, gtk_get_option_group (TRUE));

	g_option_context_parse (context, &argc, &argv, &error);

	if (error) {
		g_critical ("Unable to parse option: %s", error->message);
		g_error_free (error);
		g_option_context_free (context);

		exit (1);
	}
	g_option_context_free (context);

	if (directories && directories[0] && directories[1]) {
		g_critical (_("Too many arguments. Only one directory can be specified."));
		exit (1);
	}

	glibtop_init ();

	gtk_window_set_default_icon_name ("mate-disk-usage-analyzer");

	baobab_init ();

	if (baobab.fs.total == 0) {
		GtkWidget *dialog;

		dialog = gtk_message_dialog_new (NULL,
				GTK_DIALOG_DESTROY_WITH_PARENT,
				GTK_MESSAGE_ERROR,
				GTK_BUTTONS_CLOSE,
				_("Could not detect any mount point."));
		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog),
				_("Without mount points disk usage cannot be analyzed."));
		gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (dialog);
		goto closing;
	}

	check_menu_sens (FALSE);
	update_scan_label ();

	baobab.window = GET_WIDGET ("baobab_window");
	gtk_window_set_position (GTK_WINDOW (baobab.window),
				 GTK_WIN_POS_CENTER);

	baobab.tree_view = create_directory_treeview ();

	gtk_action_set_sensitive (GET_ACTION ("menurescan"), FALSE);

	/* set allocated space checkbox */
	gtk_toggle_action_set_active (GET_TOGGLE_ACTION ("ck_allocated"),
				      baobab.show_allocated);

	gtk_widget_show (baobab.window);

	first_row ();
	baobab_set_statusbar (_("Ready"));

	initialize_charts ();

	/* commandline */
	if (directories && directories[0]) {
		GFile *file;

		file = g_file_new_for_commandline_arg (directories[0]);

		/* start processing the dir specified on the
		 * command line as soon as we enter the main loop */
		g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
		                 (GSourceFunc) start_proc_on_command_line,
		                 file,
		                 g_object_unref);
	}
	g_strfreev (directories);

	gtk_main ();

 closing:
	baobab_shutdown ();

	glibtop_close ();

	return 0;
}
