/*
 * pluma-commands-file.c
 * This file is part of pluma
 *
 * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
 * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
 * Copyright (C) 2002-2005 Paolo Maggi
 * Copyright (C) 2012-2021 MATE Developers
 *
 * 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.
 */

/*
 * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
 * list of people on the pluma Team.
 * See the ChangeLog files for a list of changes.
 *
 * $Id$
 */

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

#include <sys/stat.h>

#include <string.h> /* For strlen and strcmp */

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

#include "pluma-commands.h"
#include "pluma-window.h"
#include "pluma-window-private.h"
#include "pluma-statusbar.h"
#include "pluma-debug.h"
#include "pluma-utils.h"
#include "pluma-file-chooser-dialog.h"
#include "dialogs/pluma-close-confirmation-dialog.h"


/* Defined constants */
#define PLUMA_OPEN_DIALOG_KEY 		"pluma-open-dialog-key"
#define PLUMA_TAB_TO_SAVE_AS  		"pluma-tab-to-save-as"
#define PLUMA_LIST_OF_TABS_TO_SAVE_AS   "pluma-list-of-tabs-to-save-as"
#define PLUMA_IS_CLOSING_ALL            "pluma-is-closing-all"
#define PLUMA_IS_QUITTING 	        "pluma-is-quitting"
#define PLUMA_IS_CLOSING_TAB		"pluma-is-closing-tab"
#define PLUMA_IS_QUITTING_ALL		"pluma-is-quitting-all"

/* 全局标志：记录大文件警告对话框是否正在显示
 * 用于防止在对话框显示期间加载新文件，并能激活已存在的对话框
 */
static gboolean large_file_dialog_showing = FALSE;
static GtkWidget *current_large_file_dialog = NULL;

static void tab_state_changed_while_saving (PlumaTab    *tab,
					    GParamSpec  *pspec,
					    PlumaWindow *window);

/* removed alignment helpers: replaced by inline minimal loops near dialog creation */

void
_pluma_cmd_file_new (GtkAction   *action,
		     PlumaWindow *window)
{
	pluma_debug (DEBUG_COMMANDS);

	pluma_window_create_tab (window, TRUE);
}

static PlumaTab *
get_tab_from_file (GList *docs, GFile *file)
{
	PlumaTab *tab = NULL;

	while (docs != NULL)
	{
		PlumaDocument *d;
		GFile *l;

		d = PLUMA_DOCUMENT (docs->data);

		l = pluma_document_get_location (d);
		if (l != NULL)
		{
			if (g_file_equal (l, file))
			{
				tab = pluma_tab_get_from_document (d);
				g_object_unref (l);
				break;
			}

			g_object_unref (l);
		}

		docs = g_list_next (docs);
	}

	return tab;
}

static gboolean
is_duplicated_file (GSList *files, GFile *file)
{
	while (files != NULL)
	{
		if (g_file_equal (files->data, file))
			return TRUE;

		files = g_slist_next (files);
	}

	return FALSE;
}

/* File loading */
static gint
load_file_list (PlumaWindow         *window,
		GSList              *files,
		const PlumaEncoding *encoding,
		gint                 line_pos,
		gboolean             create)
{
	PlumaTab      *tab;
	gint           loaded_files = 0; /* Number of files to load */
	gboolean       jump_to = TRUE; /* Whether to jump to the new tab */
	GList         *win_docs;
	GSList        *files_to_load = NULL;
	GSList        *l;

	pluma_debug (DEBUG_COMMANDS);

	/* 如果大文件警告对话框正在显示，拒绝加载新文件并激活已存在的对话框
	 * 这防止用户在未响应对话框时打开多个文件导致多个对话框弹出
	 */
	if (large_file_dialog_showing && current_large_file_dialog != NULL)
	{
		/* 激活已存在的对话框，提醒用户先处理它 */
		gtk_window_present (GTK_WINDOW (current_large_file_dialog));
		
		pluma_debug_message (DEBUG_COMMANDS, 
		                     "Large file dialog is showing, rejecting new file load request");
		
		/* 返回0表示没有加载任何文件 */
		return 0;
	}

	win_docs = pluma_window_get_documents (window);

	/* Remove the uris corresponding to documents already open
	 * in "window" and remove duplicates from "uris" list */
	for (l = files; l != NULL; l = l->next)
	{
		if (!is_duplicated_file (files_to_load, l->data))
		{
			tab = get_tab_from_file (win_docs, l->data);
			if (tab != NULL)
			{
				if (l == files)
				{
					pluma_window_set_active_tab (window, tab);
					jump_to = FALSE;

					if (line_pos > 0)
					{
						PlumaDocument *doc;
						PlumaView *view;

						doc = pluma_tab_get_document (tab);
						view = pluma_tab_get_view (tab);

						/* document counts lines starting from 0 */
						pluma_document_goto_line (doc, line_pos - 1);
						pluma_view_scroll_to_cursor (view);
					}
				}

				++loaded_files;
			}
			else
			{
				files_to_load = g_slist_prepend (files_to_load,
								 l->data);
			}
		}
	}

	g_list_free (win_docs);

	if (files_to_load == NULL)
		return loaded_files;

	files_to_load = g_slist_reverse (files_to_load);
	l = files_to_load;

	/* 在加载文件前检查是否有大文件（> 9.5MB）
	 * 如果有，提前警告用户，避免加载时卡死界面
	 */
	{
		GSList *current = files_to_load;
		gboolean has_large_file = FALSE;
		goffset size_threshold = 9.5 * 1024 * 1024; /* 9.5MB */

		while (current != NULL)
		{
			GFile *file = G_FILE (current->data);
			gchar *path = g_file_get_path (file);

			if (path != NULL)
			{
				struct stat file_info;
				if (stat (path, &file_info) == 0)
				{
					goffset file_size = file_info.st_size;
					if (file_size > size_threshold)
					{
						has_large_file = TRUE;
						g_free (path);
						break;
					}
				}
				g_free (path);
			}

			current = g_slist_next (current);
		}

		if (has_large_file)
		{
			GtkWidget *dialog;
			gint response;
			gchar *primary_text;
			gchar *secondary_text;

			/* 确保父窗口在对话框显示前已经可见和实现
			 * 这对于Pluma作为默认应用启动时非常重要
			 */
			if (!gtk_widget_get_visible (GTK_WIDGET (window)))
			{
				gtk_widget_show (GTK_WIDGET (window));
			}
			
			if (!gtk_widget_get_realized (GTK_WIDGET (window)))
			{
				gtk_widget_realize (GTK_WIDGET (window));
			}
			
			/* 处理所有待处理的事件，确保窗口完全显示 */
			while (gtk_events_pending ())
			{
				gtk_main_iteration ();
			}

		primary_text = g_strdup (_("The text editor is about to load an excessively large file, which may cause system lag."));
		secondary_text = g_strdup (
			_("Loading large files may cause the application to become unresponsive. "
			  "It is recommended to use a specialized editor for large files.\n\n"
			  "Do you want to continue loading this file?"));

			dialog = gtk_message_dialog_new (
				GTK_WINDOW (window),
				GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
				GTK_MESSAGE_WARNING,
				GTK_BUTTONS_NONE,
				"%s", primary_text);

			gtk_message_dialog_format_secondary_text (
				GTK_MESSAGE_DIALOG (dialog),
				"%s", secondary_text);

			gtk_dialog_add_buttons (GTK_DIALOG (dialog),
			                        _("_Cancel"), GTK_RESPONSE_CANCEL,
			                        _("_Load Anyway"), GTK_RESPONSE_OK,
			                        NULL);

			gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);

			/* 强制设置模态属性，确保锁住父窗口 */
			gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window));
			gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
			gtk_window_set_type_hint (GTK_WINDOW (dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
			
			/* 确保对话框置顶并获取焦点 */
			gtk_window_present (GTK_WINDOW (dialog));

			/* 设置全局标志，记录对话框正在显示 */
			large_file_dialog_showing = TRUE;
			current_large_file_dialog = dialog;

			response = gtk_dialog_run (GTK_DIALOG (dialog));
			
			/* 清除全局标志，对话框已关闭 */
			large_file_dialog_showing = FALSE;
			current_large_file_dialog = NULL;
			
			gtk_widget_destroy (dialog);

			g_free (primary_text);
			g_free (secondary_text);

			if (response != GTK_RESPONSE_OK)
			{
				/* 用户取消，释放资源并返回 */
				g_slist_free (files_to_load);
				return loaded_files;
			}
		}
	}

	tab = pluma_window_get_active_tab (window);
	if (tab != NULL)
	{
		PlumaDocument *doc;

		doc = pluma_tab_get_document (tab);

		if (pluma_document_is_untouched (doc) &&
		    (pluma_tab_get_state (tab) == PLUMA_TAB_STATE_NORMAL))
		{
			gchar *uri;

			// FIXME: pass the GFile to tab when api is there
			uri = g_file_get_uri (l->data);
			_pluma_tab_load (tab,
					 uri,
					 encoding,
					 line_pos,
					 create);
			g_free (uri);

			l = g_slist_next (l);
			jump_to = FALSE;

			++loaded_files;
		}
	}

	while (l != NULL)
	{
		gchar *uri;

		g_return_val_if_fail (l->data != NULL, 0);

		// FIXME: pass the GFile to tab when api is there
		uri = g_file_get_uri (l->data);
		tab = pluma_window_create_tab_from_uri (window,
							uri,
							encoding,
							line_pos,
							create,
							jump_to);
		g_free (uri);

		if (tab != NULL)
		{
			jump_to = FALSE;
			++loaded_files;
		}

		l = g_slist_next (l);
	}

	if (loaded_files == 1)
	{
		PlumaDocument *doc;
		gchar *uri_for_display;

		g_return_val_if_fail (tab != NULL, loaded_files);

		doc = pluma_tab_get_document (tab);
		uri_for_display = pluma_document_get_uri_for_display (doc);

		pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
					       window->priv->generic_message_cid,
					       _("Loading file '%s'\342\200\246"),
					       uri_for_display);

		g_free (uri_for_display);
	}
	else
	{
		pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
					       window->priv->generic_message_cid,
					       ngettext("Loading %d file\342\200\246",
							"Loading %d files\342\200\246",
							loaded_files),
					       loaded_files);
	}

	/* Free uris_to_load. Note that l points to the first element of uris_to_load */
	g_slist_free (files_to_load);

	return loaded_files;
}


// FIXME: we should expose API with GFile and just make the uri
// variants backward compat wrappers

static gint
load_uri_list (PlumaWindow         *window,
	       const GSList        *uris,
	       const PlumaEncoding *encoding,
	       gint                 line_pos,
	       gboolean             create)
{
	GSList *files = NULL;
	const GSList *u;
	gint ret;

	for (u = uris; u != NULL; u = u->next)
	{
		gchar *uri = u->data;

		if (pluma_utils_is_valid_uri (uri))
			files = g_slist_prepend (files, g_file_new_for_uri (uri));
		else
			g_warning ("invalid uri: %s", uri);
	}
	files = g_slist_reverse (files);

	ret = load_file_list (window, files, encoding, line_pos, create);

	g_slist_free_full (files, g_object_unref);

	return ret;
}

/**
 * pluma_commands_load_uri:
 * @window:
 * @uri:
 * @encoding: (allow-none):
 * @line_pos:
 *
 * Do nothing if uri does not exist
 */
void
pluma_commands_load_uri (PlumaWindow         *window,
			 const gchar         *uri,
			 const PlumaEncoding *encoding,
			 gint                 line_pos)
{
	GSList *uris = NULL;

	g_return_if_fail (PLUMA_IS_WINDOW (window));
	g_return_if_fail (uri != NULL);
	g_return_if_fail (pluma_utils_is_valid_uri (uri));

	pluma_debug_message (DEBUG_COMMANDS, "Loading URI '%s'", uri);

	uris = g_slist_prepend (uris, (gchar *)uri);

	load_uri_list (window, uris, encoding, line_pos, FALSE);

	g_slist_free (uris);
}

/**
 * pluma_commands_load_uris:
 * @window:
 * @uris:
 * @encoding:
 * @line_pos:
 *
 * Ignore non-existing URIs
 *
 * Returns:
 */
gint
pluma_commands_load_uris (PlumaWindow         *window,
			  const GSList        *uris,
			  const PlumaEncoding *encoding,
			  gint                 line_pos)
{
	g_return_val_if_fail (PLUMA_IS_WINDOW (window), 0);
	g_return_val_if_fail ((uris != NULL) && (uris->data != NULL), 0);

	pluma_debug (DEBUG_COMMANDS);

	return load_uri_list (window, uris, encoding, line_pos, FALSE);
}

/*
 * This should become public once we convert all api to GFile:
 */
static gint
pluma_commands_load_files (PlumaWindow         *window,
			   GSList              *files,
			   const PlumaEncoding *encoding,
			   gint                 line_pos)
{
	g_return_val_if_fail (PLUMA_IS_WINDOW (window), 0);
	g_return_val_if_fail ((files != NULL) && (files->data != NULL), 0);

	pluma_debug (DEBUG_COMMANDS);

	return load_file_list (window, files, encoding, line_pos, FALSE);
}

/*
 * From the command line we can specify a line position for the
 * first doc. Beside specifying a not existing uri creates a
 * titled document.
 */
gint
_pluma_cmd_load_files_from_prompt (PlumaWindow         *window,
				   GSList              *files,
				   const PlumaEncoding *encoding,
				   gint                 line_pos)
{
	pluma_debug (DEBUG_COMMANDS);

	return load_file_list (window, files, encoding, line_pos, TRUE);
}

static void
open_dialog_destroyed (PlumaWindow            *window,
		       PlumaFileChooserDialog *dialog)
{
	pluma_debug (DEBUG_COMMANDS);

	g_object_set_data (G_OBJECT (window),
			   PLUMA_OPEN_DIALOG_KEY,
			   NULL);
}

static void
open_dialog_response_cb (PlumaFileChooserDialog *dialog,
                         gint                    response_id,
                         PlumaWindow            *window)
{
	GSList *files;
	const PlumaEncoding *encoding;

	pluma_debug (DEBUG_COMMANDS);

	if (response_id != GTK_RESPONSE_OK)
	{
		gtk_widget_destroy (GTK_WIDGET (dialog));

		return;
	}

	files = gtk_file_chooser_get_files (GTK_FILE_CHOOSER (dialog));
	g_return_if_fail (files != NULL);

	encoding = pluma_file_chooser_dialog_get_encoding (dialog);

	gtk_widget_destroy (GTK_WIDGET (dialog));

	/* Remember the folder we navigated to */
	 _pluma_window_set_default_location (window, files->data);

	pluma_commands_load_files (window,
				   files,
				   encoding,
				   0);

	g_slist_free_full (files, g_object_unref);
}

void
_pluma_cmd_file_open (GtkAction   *action,
		      PlumaWindow *window)
{
	GtkWidget *open_dialog;
	gpointer data;
	PlumaDocument *doc;
	GFile *default_path = NULL;

	pluma_debug (DEBUG_COMMANDS);

	data = g_object_get_data (G_OBJECT (window), PLUMA_OPEN_DIALOG_KEY);

	if (data != NULL)
	{
		g_return_if_fail (PLUMA_IS_FILE_CHOOSER_DIALOG (data));

		gtk_window_present (GTK_WINDOW (data));

		return;
	}

	/* Translators: "Open Files" is the title of the file chooser window */
	open_dialog = pluma_file_chooser_dialog_new (_("Open Files"),
						     GTK_WINDOW (window),
						     GTK_FILE_CHOOSER_ACTION_OPEN,
						     NULL,
						     "process-stop", GTK_RESPONSE_CANCEL,
						     "document-open", GTK_RESPONSE_OK,
						     NULL);

	g_object_set_data (G_OBJECT (window),
			   PLUMA_OPEN_DIALOG_KEY,
			   open_dialog);

	g_object_weak_ref (G_OBJECT (open_dialog),
			   (GWeakNotify) open_dialog_destroyed,
			   window);

	/* Set the curret folder uri */
	doc = pluma_window_get_active_document (window);
	if (doc != NULL)
	{
		GFile *file;

		file = pluma_document_get_location (doc);

		if (file != NULL)
		{
			default_path = g_file_get_parent (file);
			g_object_unref (file);
		}
	}

	if (default_path == NULL)
		default_path = _pluma_window_get_default_location (window);

	if (default_path != NULL)
	{
		gchar *uri;

		uri = g_file_get_uri (default_path);
		gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (open_dialog),
							 uri);

		g_free (uri);
		g_object_unref (default_path);
	}

	g_signal_connect (open_dialog,
			  "response",
			  G_CALLBACK (open_dialog_response_cb),
			  window);

	gtk_widget_show (open_dialog);
}

/* File saving */
static void file_save_as (PlumaTab *tab, PlumaWindow *window);

static gboolean
is_read_only (GFile *location)
{
	gboolean ret = TRUE; /* default to read only */
	GFileInfo *info;

	pluma_debug (DEBUG_COMMANDS);

	info = g_file_query_info (location,
				  G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
				  G_FILE_QUERY_INFO_NONE,
				  NULL,
				  NULL);

	if (info != NULL)
	{
		if (g_file_info_has_attribute (info,
					       G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
		{
			ret = !g_file_info_get_attribute_boolean (info,
								  G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
		}

		g_object_unref (info);
	}

	return ret;
}

static gboolean
replace_existing_file (GtkWindow *parent, GFile *file)
{
	GtkWidget *dialog;
	gint ret;
	gchar *parse_name;
	gchar *name_for_display;

	pluma_debug (DEBUG_COMMANDS);

	parse_name = g_file_get_parse_name (file);

	/* 截断名称，使其不会变得太宽。注意，即使对话框使用换行文本，如果名称不包含空格，
	 * 文本换行代码也太愚蠢了，无法换行。 */
	name_for_display = pluma_utils_str_middle_truncate (parse_name, 50);
	g_free (parse_name);

	dialog = gtk_message_dialog_new (parent,
					 GTK_DIALOG_DESTROY_WITH_PARENT,
					 GTK_MESSAGE_QUESTION,
					 GTK_BUTTONS_NONE,
					 _("A file named \"%s\" already exists. Do you want to replace it?"),
			         name_for_display);
	g_free (name_for_display);

	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
						  _("The file already exists in this folder. Replacing it will overwrite its contents."));
{
	GtkWidget *area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog));
	GList *children = gtk_container_get_children (GTK_CONTAINER (area));
	for (GList *l = children; l != NULL; l = l->next)
	{
		if (GTK_IS_LABEL (l->data))
		{
			gtk_label_set_xalign (GTK_LABEL (l->data), 0.0);
			gtk_label_set_justify (GTK_LABEL (l->data), GTK_JUSTIFY_LEFT);
			gtk_widget_set_halign (GTK_WIDGET (l->data), GTK_ALIGN_FILL);
		}
	}
	g_list_free (children);
}

	pluma_dialog_add_button (GTK_DIALOG (dialog),
				 _("_Cancel"),
				 "process-stop",
				 GTK_RESPONSE_CANCEL);

	pluma_dialog_add_button (GTK_DIALOG (dialog),
				 _("_Replace"),
		  	 "document-save-as",
		  	 GTK_RESPONSE_YES);

	gtk_dialog_set_default_response	(GTK_DIALOG (dialog),
					 GTK_RESPONSE_CANCEL);

	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);

	ret = gtk_dialog_run (GTK_DIALOG (dialog));

	gtk_widget_destroy (dialog);

	return (ret == GTK_RESPONSE_YES);
}

/* FIXME: modify this dialog to be similar to the one provided by gtk+ for
 * already existing files - Paolo (Oct. 11, 2005) */
static gboolean
replace_read_only_file (GtkWindow *parent, GFile *file)
{
	GtkWidget *dialog;
	gint ret;
	gchar *parse_name;
	gchar *name_for_display;

	pluma_debug (DEBUG_COMMANDS);

	parse_name = g_file_get_parse_name (file);

	/* Truncate the name so it doesn't get insanely wide. Note that even
	 * though the dialog uses wrapped text, if the name doesn't contain
	 * white space then the text-wrapping code is too stupid to wrap it.
	 */
	name_for_display = pluma_utils_str_middle_truncate (parse_name, 50);
	g_free (parse_name);

	dialog = gtk_message_dialog_new (parent,
					 GTK_DIALOG_DESTROY_WITH_PARENT,
					 GTK_MESSAGE_QUESTION,
					 GTK_BUTTONS_NONE,
					 _("The file \"%s\" is read-only."),
				         name_for_display);
	g_free (name_for_display);

	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
						  _("Do you want to try to replace it "
						    "with the one you are saving?"));
{
	GtkWidget *area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog));
	GList *children = gtk_container_get_children (GTK_CONTAINER (area));
	for (GList *l = children; l != NULL; l = l->next)
	{
		if (GTK_IS_LABEL (l->data))
		{
			gtk_label_set_xalign (GTK_LABEL (l->data), 0.0);
			gtk_label_set_justify (GTK_LABEL (l->data), GTK_JUSTIFY_LEFT);
			gtk_widget_set_halign (GTK_WIDGET (l->data), GTK_ALIGN_FILL);
		}
	}
	g_list_free (children);
}

	pluma_dialog_add_button (GTK_DIALOG (dialog),
				 _("_Cancel"),
				 "process-stop",
				 GTK_RESPONSE_CANCEL);

	pluma_dialog_add_button (GTK_DIALOG (dialog),
				 _("_Replace"),
			  	 "document-save-as",
			  	 GTK_RESPONSE_YES);

	gtk_dialog_set_default_response	(GTK_DIALOG (dialog),
					 GTK_RESPONSE_CANCEL);

	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);

	ret = gtk_dialog_run (GTK_DIALOG (dialog));

	gtk_widget_destroy (dialog);

	return (ret == GTK_RESPONSE_YES);
}

static void
save_dialog_response_cb (PlumaFileChooserDialog *dialog,
                         gint                    response_id,
                         PlumaWindow            *window)
{
	GFile *file;
	const PlumaEncoding *encoding;
	PlumaTab *tab;
	gpointer data;
	GSList *tabs_to_save_as;
	PlumaDocumentNewlineType newline_type;

	pluma_debug (DEBUG_COMMANDS);

	tab = PLUMA_TAB (g_object_get_data (G_OBJECT (dialog),
					    PLUMA_TAB_TO_SAVE_AS));

	if (response_id != GTK_RESPONSE_OK)
	{
		gtk_widget_destroy (GTK_WIDGET (dialog));
        _pluma_cancel_save_as(window);
		goto save_next_tab;
	}

	file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
	g_return_if_fail (file != NULL);

	/* 记录：如果 GTK 在我们的手动处理之前已经请求了覆盖确认 */
	if (g_object_get_data (G_OBJECT (dialog), "pluma-gtk-confirm-overwrite-emitted") != NULL)
	{
		pluma_debug_message (DEBUG_COMMANDS,
				   "探针：GTK 在手动处理前请求过覆盖确认");
		/* 重置标记 */
		g_object_set_data (G_OBJECT (dialog), "pluma-gtk-confirm-overwrite-emitted", NULL);
	}

	/* 检查用户选择的是文件还是目录 */
	if (g_file_query_exists (file, NULL))
	{
		GFileType file_type;
		
		file_type = g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL);
		
		if (file_type == G_FILE_TYPE_DIRECTORY)
		{
			/* 用户选择了目录，需要在该目录下保存文件 */
			gchar *filename;
			GFile *target_file;
			
			pluma_debug_message (DEBUG_COMMANDS, "用户选择了目录，进入该目录保存");
			
			/* 获取用户在输入框中输入的文件名 */
			filename = gtk_file_chooser_get_current_name (GTK_FILE_CHOOSER (dialog));
			
			/* 如果输入框为空或无效，使用当前文档名 */
			if (filename == NULL || *filename == '\0')
			{
				PlumaDocument *doc;
				
				if (filename != NULL)
					g_free (filename);
				
				doc = pluma_tab_get_document (tab);
				filename = pluma_document_get_short_name_for_display (doc);
				
				pluma_debug_message (DEBUG_COMMANDS, "输入框为空，使用当前文档名");
			}
			else
			{
				pluma_debug_message (DEBUG_COMMANDS, "使用用户在输入框输入的文件名");
			}
			
			/* 构建完整路径：目录 + 文件名 */
			target_file = g_file_get_child (file, filename);
			g_free (filename);
			g_object_unref (file);
			
			file = target_file;
			
			pluma_debug_message (DEBUG_COMMANDS, "目标文件路径已更新");
		}
	}

	/* 检查文件是否存在并显示确认对话框（如果需要）。
	 * 如果 GTK 已经处理了 confirm-overwrite（探针标志已设置），则跳过手动对话框。
	 * 如果目标文件就是当前正在编辑的文件，也跳过确认对话框。 */
	if (g_file_query_exists (file, NULL) &&
	    g_object_get_data (G_OBJECT (dialog), "pluma-gtk-confirm-overwrite-emitted") == NULL)
	{
		PlumaDocument *doc;
		GFile *current_file;
		gboolean is_same_file = FALSE;
		
		/* 获取当前文档的位置 */
		doc = pluma_tab_get_document (tab);
		if (doc != NULL)
		{
			current_file = pluma_document_get_location (doc);
			if (current_file != NULL)
			{
				/* 检查目标文件是否是当前正在编辑的文件 */
				is_same_file = g_file_equal (file, current_file);
				g_object_unref (current_file);
			}
		}
		
		/* 如果不是同一个文件，才需要弹出替换确认对话框 */
		if (!is_same_file)
		{
			gboolean should_replace = FALSE;
			
			if (is_read_only (file))
			{
				should_replace = replace_read_only_file (GTK_WINDOW (dialog), file);
			}
			else
			{
				should_replace = replace_existing_file (GTK_WINDOW (dialog), file);
			}
			
			if (!should_replace)
			{
				/* 用户取消，不保存且不销毁对话框 */
				g_object_unref (file);
				return;
			}
		}
		else
		{
			pluma_debug_message (DEBUG_COMMANDS,
					   "目标文件是当前文档，跳过替换确认");
		}
	}

	encoding = pluma_file_chooser_dialog_get_encoding (dialog);
	newline_type = pluma_file_chooser_dialog_get_newline_type (dialog);

	gtk_widget_destroy (GTK_WIDGET (dialog));

	if (tab != NULL)
	{
		PlumaDocument *doc;
		gchar *parse_name;
		gchar *uri;

		doc = pluma_tab_get_document (tab);
		g_return_if_fail (PLUMA_IS_DOCUMENT (doc));

		parse_name = g_file_get_parse_name (file);

		pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
					        window->priv->generic_message_cid,
					       _("Saving file '%s'\342\200\246"),
					       parse_name);

		g_free (parse_name);

		/* let's remember the dir we navigated too,
		 * even if the saving fails... */
		 _pluma_window_set_default_location (window, file);

		// FIXME: pass the GFile to tab when api is there
		uri = g_file_get_uri (file);
		_pluma_tab_save_as (tab, uri, encoding, newline_type);
		g_free (uri);
	}

	g_object_unref (file);

save_next_tab:

	data = g_object_get_data (G_OBJECT (window),
				  PLUMA_LIST_OF_TABS_TO_SAVE_AS);
	if (data == NULL)
		return;

	/* Save As the next tab of the list (we are Saving All files) */
	tabs_to_save_as = (GSList *)data;
	g_return_if_fail (tab == PLUMA_TAB (tabs_to_save_as->data));

	/* Remove the first item of the list */
	tabs_to_save_as = g_slist_delete_link (tabs_to_save_as,
					       tabs_to_save_as);

	g_object_set_data (G_OBJECT (window),
			   PLUMA_LIST_OF_TABS_TO_SAVE_AS,
			   tabs_to_save_as);

	if (tabs_to_save_as != NULL)
	{
		tab = PLUMA_TAB (tabs_to_save_as->data);

		if (GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (tab),
							    PLUMA_IS_CLOSING_TAB)) == TRUE)
		{
			g_object_set_data (G_OBJECT (tab),
					   PLUMA_IS_CLOSING_TAB,
					   NULL);

			/* Trace tab state changes */
			g_signal_connect (tab,
					  "notify::state",
					  G_CALLBACK (tab_state_changed_while_saving),
					  window);
		}

		pluma_window_set_active_tab (window, tab);
		file_save_as (tab, window);
	}
}

static GtkFileChooserConfirmation
confirm_overwrite_callback (GtkFileChooser *dialog,
			    gpointer        data)
{
	gchar *uri;
	GFile *file;
	GtkFileChooserConfirmation res;

	pluma_debug (DEBUG_COMMANDS);

	uri = gtk_file_chooser_get_uri (dialog);
	file = g_file_new_for_uri (uri);
	g_free (uri);

	if (is_read_only (file))
	{
		if (replace_read_only_file (GTK_WINDOW (dialog), file))
			res = GTK_FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME;
		else
			res = GTK_FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN;
	}
	else
	{
		/* 显示我们的自定义覆盖确认对话框 */
		if (replace_existing_file (GTK_WINDOW (dialog), file))
			res = GTK_FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME;
		else
			res = GTK_FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN;
	}

	g_object_unref (file);

	return res;
}

/* 仅用于探测：判断 GTK 的默认覆盖确认流程是否会被触发。
 * 这里返回 SELECT_AGAIN 让 GTK 自己显示覆盖确认对话框；
 * 真实的用户提示在 save_dialog_response_cb 中视情况跳过/执行。
 */
static GtkFileChooserConfirmation
confirm_overwrite_probe_cb (GtkFileChooser *dialog,
			      gpointer        data)
{
	pluma_debug (DEBUG_COMMANDS);

	/* 标记：GTK 已尝试触发覆盖确认 */
	g_object_set_data (G_OBJECT (dialog),
		           "pluma-gtk-confirm-overwrite-emitted",
		           GINT_TO_POINTER (TRUE));

	pluma_debug_message (DEBUG_COMMANDS,
		          "探针：收到 GTK confirm-overwrite 信号，交由 GTK 显示其对话框");

	/* 请求 GtkFileChooser 显示其自带对话框 */
	return GTK_FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN;
}

static void
file_save_as (PlumaTab    *tab,
	      PlumaWindow *window)
{
	GtkWidget *save_dialog;
	GtkWindowGroup *wg;
	PlumaDocument *doc;
	GFile *file;
	gboolean uri_set = FALSE;
	const PlumaEncoding *encoding;
	PlumaDocumentNewlineType newline_type;

	g_return_if_fail (PLUMA_IS_TAB (tab));
	g_return_if_fail (PLUMA_IS_WINDOW (window));

	pluma_debug (DEBUG_COMMANDS);

	save_dialog = pluma_file_chooser_dialog_new (_("Save As\342\200\246"),
						     GTK_WINDOW (window),
						     GTK_FILE_CHOOSER_ACTION_SAVE,
						     NULL,
						     "process-stop", GTK_RESPONSE_CANCEL,
						     "document-save", GTK_RESPONSE_OK,
						     NULL);

	/* 开启 GTK 的覆盖确认以便触发 confirm-overwrite 信号，
	 * 同时通过探针回调拦截以避免我们手动逻辑重复弹框。 */
	gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (save_dialog),
						TRUE);

	g_signal_connect (save_dialog,
			  "confirm-overwrite",
			  G_CALLBACK (confirm_overwrite_probe_cb),
			  window);

	wg = pluma_window_get_group (window);

	gtk_window_group_add_window (wg,
				     GTK_WINDOW (save_dialog));

	/* Save As dialog is modal to its main window */
	gtk_window_set_modal (GTK_WINDOW (save_dialog), TRUE);

	/* Set the suggested file name */
	doc = pluma_tab_get_document (tab);
	file = pluma_document_get_location (doc);

	if (file != NULL)
	{
		uri_set = gtk_file_chooser_set_file (GTK_FILE_CHOOSER (save_dialog),
						     file,
						     NULL);

		g_object_unref (file);
	}


	if (!uri_set)
	{
		GFile *default_path;
		gchar *docname;

		default_path = _pluma_window_get_default_location (window);
		docname = pluma_document_get_short_name_for_display (doc);

		if (default_path != NULL)
		{
			gchar *uri;

			uri = g_file_get_uri (default_path);
			gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (save_dialog),
								 uri);

			g_free (uri);
			g_object_unref (default_path);
		}

		gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog),
						   docname);

		g_free (docname);
	}

	/* Set suggested encoding */
	encoding = pluma_document_get_encoding (doc);
	g_return_if_fail (encoding != NULL);

	newline_type = pluma_document_get_newline_type (doc);

	pluma_file_chooser_dialog_set_encoding (PLUMA_FILE_CHOOSER_DIALOG (save_dialog),
						encoding);

	pluma_file_chooser_dialog_set_newline_type (PLUMA_FILE_CHOOSER_DIALOG (save_dialog),
	                                            newline_type);

	g_object_set_data (G_OBJECT (save_dialog),
			   PLUMA_TAB_TO_SAVE_AS,
			   tab);

	g_signal_connect (save_dialog,
			  "response",
			  G_CALLBACK (save_dialog_response_cb),
			  window);

	gtk_widget_show (save_dialog);
}

static void
file_save (PlumaTab    *tab,
	   PlumaWindow *window)
{
	PlumaDocument *doc;
	gchar *uri_for_display;
	GFile *file;
	GFileInfo *info;
	GError *error = NULL;

	pluma_debug (DEBUG_COMMANDS);

	g_return_if_fail (PLUMA_IS_TAB (tab));
	g_return_if_fail (PLUMA_IS_WINDOW (window));

	doc = pluma_tab_get_document (tab);
	g_return_if_fail (PLUMA_IS_DOCUMENT (doc));

	if (pluma_document_is_untitled (doc) ||
	    pluma_document_get_readonly (doc))
	{
		pluma_debug_message (DEBUG_COMMANDS, "Untitled or Readonly");

		file_save_as (tab, window);

		return;
	}

	uri_for_display = pluma_document_get_uri_for_display (doc);
	/* 检测路径 , 如果路径不存在走另存为逻辑*/

	if(g_str_has_prefix(uri_for_display,"smb://")) {
		file = g_file_new_for_uri(uri_for_display);
	} else {
		file = g_file_new_for_path(uri_for_display);
	}
	info = g_file_query_info(file,
				"standard::name",
				G_FILE_QUERY_INFO_NONE,
				NULL,
				&error);

	if(!info) {
		printf("Waring : source path is not exist , used save as ... , error:%s\n", error->message);
		g_clear_error(&error);
		g_object_unref(file);
		file_save_as(tab , window);
		return;
	}
	g_object_unref(info);
	g_object_unref(file);

	pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
				        window->priv->generic_message_cid,
				       _("Saving file '%s'\342\200\246"),
				       uri_for_display);

	g_free (uri_for_display);

	_pluma_tab_save (tab);
}

void
_pluma_cmd_file_save (GtkAction   *action,
		     PlumaWindow *window)
{
	PlumaTab *tab;

	pluma_debug (DEBUG_COMMANDS);

	tab = pluma_window_get_active_tab (window);
	if (tab == NULL)
		return;

	file_save (tab, window);
}

void
_pluma_cmd_file_save_as (GtkAction   *action,
			PlumaWindow *window)
{
	PlumaTab *tab;

	pluma_debug (DEBUG_COMMANDS);

	tab = pluma_window_get_active_tab (window);
	if (tab == NULL)
		return;

	file_save_as (tab, window);
}

static gboolean
document_needs_saving (PlumaDocument *doc)
{
	if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (doc)))
		return TRUE;

	/* we check if it was deleted only for local files
	 * since for remote files it may hang */
	if (pluma_document_is_local (doc) && pluma_document_get_deleted (doc))
		return TRUE;

	return FALSE;
}

/*
 * The docs in the list must belong to the same PlumaWindow.
 */
void
_pluma_cmd_file_save_documents_list (PlumaWindow *window,
				     GList       *docs)
{
	GList *l;
	GSList *tabs_to_save_as = NULL;

	pluma_debug (DEBUG_COMMANDS);

	g_return_if_fail (!(pluma_window_get_state (window) &
			    (PLUMA_WINDOW_STATE_PRINTING |
			     PLUMA_WINDOW_STATE_SAVING_SESSION)));

	l = docs;
	while (l != NULL)
	{
		PlumaDocument *doc;
		PlumaTab *t;
		PlumaTabState state;

		g_return_if_fail (PLUMA_IS_DOCUMENT (l->data));

		doc = PLUMA_DOCUMENT (l->data);
		t = pluma_tab_get_from_document (doc);
		state = pluma_tab_get_state (t);

		g_return_if_fail (state != PLUMA_TAB_STATE_PRINTING);
		g_return_if_fail (state != PLUMA_TAB_STATE_PRINT_PREVIEWING);
		g_return_if_fail (state != PLUMA_TAB_STATE_CLOSING);

		if ((state == PLUMA_TAB_STATE_NORMAL) ||
		    (state == PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW) ||
		    (state == PLUMA_TAB_STATE_GENERIC_NOT_EDITABLE))
		{
			/* FIXME: manage the case of local readonly files owned by the
			   user is running pluma - Paolo (Dec. 8, 2005) */
			if (pluma_document_is_untitled (doc) ||
			    pluma_document_get_readonly (doc))
			{
				if (document_needs_saving (doc))
			     	{
				     	tabs_to_save_as = g_slist_prepend (tabs_to_save_as,
									   t);
			     	}
			}
			else
			{
				file_save (t, window);
			}
		}
		else
		{
			/* If the state is:
			   - PLUMA_TAB_STATE_LOADING: we do not save since we are sure the file is unmodified
			   - PLUMA_TAB_STATE_REVERTING: we do not save since the user wants
			     to return back to the version of the file she previously saved
			   - PLUMA_TAB_STATE_SAVING: well, we are already saving (no need to save again)
			   - PLUMA_TAB_STATE_PRINTING, PLUMA_TAB_STATE_PRINT_PREVIEWING: there is not a
			     real reason for not saving in this case, we do not save to avoid to run
			     two operations using the message area at the same time (may be we can remove
			     this limitation in the future). Note that SaveAll, ClosAll
			     and Quit are unsensitive if the window state is PRINTING.
			   - PLUMA_TAB_STATE_GENERIC_ERROR: we do not save since the document contains
			     errors (I don't think this is a very frequent case, we should probably remove
			     this state)
			   - PLUMA_TAB_STATE_LOADING_ERROR: there is nothing to save
			   - PLUMA_TAB_STATE_REVERTING_ERROR: there is nothing to save and saving the current
			     document will overwrite the copy of the file the user wants to go back to
			   - PLUMA_TAB_STATE_SAVING_ERROR: we do not save since we just failed to save, so there is
			     no reason to automatically retry... we wait for user intervention
			   - PLUMA_TAB_STATE_CLOSING: this state is invalid in this case
			*/

			gchar *uri_for_display;

			uri_for_display = pluma_document_get_uri_for_display (doc);
			pluma_debug_message (DEBUG_COMMANDS,
					     "File '%s' not saved. State: %d",
					     uri_for_display,
					     state);
			g_free (uri_for_display);
		}

		l = g_list_next (l);
	}

	if (tabs_to_save_as != NULL)
	{
		PlumaTab *tab;

		tabs_to_save_as = g_slist_reverse (tabs_to_save_as );

		g_return_if_fail (g_object_get_data (G_OBJECT (window),
						     PLUMA_LIST_OF_TABS_TO_SAVE_AS) == NULL);

		g_object_set_data (G_OBJECT (window),
				   PLUMA_LIST_OF_TABS_TO_SAVE_AS,
				   tabs_to_save_as);

		tab = PLUMA_TAB (tabs_to_save_as->data);

		pluma_window_set_active_tab (window, tab);
		file_save_as (tab, window);
	}
}

void
pluma_commands_save_all_documents (PlumaWindow *window)
{
	GList *docs;

	g_return_if_fail (PLUMA_IS_WINDOW (window));

	pluma_debug (DEBUG_COMMANDS);

	docs = pluma_window_get_documents (window);

	_pluma_cmd_file_save_documents_list (window, docs);

	g_list_free (docs);
}

void
_pluma_cmd_file_save_all (GtkAction   *action,
			 PlumaWindow *window)
{
	pluma_commands_save_all_documents (window);
}

void
pluma_commands_save_document (PlumaWindow   *window,
                              PlumaDocument *document)
{
	PlumaTab *tab;

	g_return_if_fail (PLUMA_IS_WINDOW (window));
	g_return_if_fail (PLUMA_IS_DOCUMENT (document));

	pluma_debug (DEBUG_COMMANDS);

	tab = pluma_tab_get_from_document (document);
	file_save (tab, window);
}

/* File revert */
static void
do_revert (PlumaWindow *window,
	   PlumaTab    *tab)
{
	PlumaDocument *doc;
	gchar *docname;

	pluma_debug (DEBUG_COMMANDS);

	doc = pluma_tab_get_document (tab);
	docname = pluma_document_get_short_name_for_display (doc);

	pluma_statusbar_flash_message (PLUMA_STATUSBAR (window->priv->statusbar),
				        window->priv->generic_message_cid,
				       _("Reverting the document '%s'\342\200\246"),
				       docname);

	g_free (docname);

	_pluma_tab_revert (tab);
}

static void
revert_dialog_response_cb (GtkDialog   *dialog,
			   gint         response_id,
			   PlumaWindow *window)
{
	PlumaTab *tab;

	pluma_debug (DEBUG_COMMANDS);

	/* FIXME: we are relying on the fact that the dialog is
	   modal so the active tab can't be changed...
	   not very nice - Paolo (Oct 11, 2005) */
	tab = pluma_window_get_active_tab (window);
	if (tab == NULL)
		return;

	gtk_widget_destroy (GTK_WIDGET (dialog));

	if (response_id == GTK_RESPONSE_OK)
	{
		do_revert (window, tab);
	}
}

static GtkWidget *
revert_dialog (PlumaWindow   *window,
	       PlumaDocument *doc)
{
	GtkWidget *dialog;
	gchar *docname;
	gchar *primary_msg;
	gchar *secondary_msg;
	glong seconds;

	pluma_debug (DEBUG_COMMANDS);

	docname = pluma_document_get_short_name_for_display (doc);
	primary_msg = g_strdup_printf (_("Revert unsaved changes to document '%s'?"),
	                               docname);
	g_free (docname);

	seconds = MAX (1, _pluma_document_get_seconds_since_last_save_or_load (doc));

	if (seconds < 55)
	{
		secondary_msg = g_strdup_printf (
					ngettext ("Changes made to the document in the last %ld second "
					    	  "will be permanently lost.",
						  "Changes made to the document in the last %ld seconds "
					    	  "will be permanently lost.",
						  seconds),
					seconds);
	}
	else if (seconds < 75) /* 55 <= seconds < 75 */
	{
		secondary_msg = g_strdup (_("Changes made to the document in the last minute "
					    "will be permanently lost."));
	}
	else if (seconds < 110) /* 75 <= seconds < 110 */
	{
		secondary_msg = g_strdup_printf (
					ngettext ("Changes made to the document in the last minute and "
						  "%ld second will be permanently lost.",
						  "Changes made to the document in the last minute and "
						  "%ld seconds will be permanently lost.",
						  seconds - 60 ),
					seconds - 60);
	}
	else if (seconds < 3600)
	{
		secondary_msg = g_strdup_printf (
					ngettext ("Changes made to the document in the last %ld minute "
					    	  "will be permanently lost.",
						  "Changes made to the document in the last %ld minutes "
					    	  "will be permanently lost.",
						  seconds / 60),
					seconds / 60);
	}
	else if (seconds < 7200)
	{
		gint minutes;
		seconds -= 3600;

		minutes = seconds / 60;
		if (minutes < 5)
		{
			secondary_msg = g_strdup (_("Changes made to the document in the last hour "
						    "will be permanently lost."));
		}
		else
		{
			secondary_msg = g_strdup_printf (
					ngettext ("Changes made to the document in the last hour and "
						  "%d minute will be permanently lost.",
						  "Changes made to the document in the last hour and "
						  "%d minutes will be permanently lost.",
						  minutes),
					minutes);
		}
	}
	else
	{
		gint hours;

		hours = seconds / 3600;

		secondary_msg = g_strdup_printf (
					ngettext ("Changes made to the document in the last %d hour "
					    	  "will be permanently lost.",
						  "Changes made to the document in the last %d hours "
					    	  "will be permanently lost.",
						  hours),
					hours);
	}

	dialog = gtk_message_dialog_new (GTK_WINDOW (window),
					 GTK_DIALOG_DESTROY_WITH_PARENT,
					 GTK_MESSAGE_QUESTION,
					 GTK_BUTTONS_NONE,
					 "%s", primary_msg);

	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
						  "%s", secondary_msg);
	g_free (primary_msg);
	g_free (secondary_msg);

	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);

	pluma_dialog_add_button (GTK_DIALOG (dialog),
				 _("_Cancel"),
				 "process-stop",
				 GTK_RESPONSE_CANCEL);

	pluma_dialog_add_button (GTK_DIALOG (dialog),
				 _("_Revert"),
				 "document-revert",
				 GTK_RESPONSE_OK);

	gtk_dialog_set_default_response	(GTK_DIALOG (dialog),
					 GTK_RESPONSE_CANCEL);

	return dialog;
}

void
_pluma_cmd_file_revert (GtkAction   *action,
		       PlumaWindow *window)
{
	PlumaTab       *tab;
	PlumaDocument  *doc;
	GtkWidget      *dialog;
	GtkWindowGroup *wg;

	pluma_debug (DEBUG_COMMANDS);

	tab = pluma_window_get_active_tab (window);
	g_return_if_fail (tab != NULL);

	/* If we are already displaying a notification
	 * reverting will drop local modifications, do
	 * not bug the user further */
	if (pluma_tab_get_state (tab) == PLUMA_TAB_STATE_EXTERNALLY_MODIFIED_NOTIFICATION)
	{
		do_revert (window, tab);
		return;
	}

	doc = pluma_tab_get_document (tab);
	g_return_if_fail (doc != NULL);
	g_return_if_fail (!pluma_document_is_untitled (doc));

	dialog = revert_dialog (window, doc);

	wg = pluma_window_get_group (window);

	gtk_window_group_add_window (wg, GTK_WINDOW (dialog));

	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);

	g_signal_connect (dialog,
			  "response",
			  G_CALLBACK (revert_dialog_response_cb),
			  window);

	gtk_widget_show (dialog);
}

/* Close tab */
static gboolean
really_close_tab (PlumaTab *tab)
{
	GtkWidget *toplevel;
	PlumaWindow *window;

	pluma_debug (DEBUG_COMMANDS);

	g_return_val_if_fail (pluma_tab_get_state (tab) == PLUMA_TAB_STATE_CLOSING,
			      FALSE);

	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tab));
	g_return_val_if_fail (PLUMA_IS_WINDOW (toplevel), FALSE);

	window = PLUMA_WINDOW (toplevel);

	pluma_window_close_tab (window, tab);

	if (pluma_window_get_active_tab (window) == NULL)
	{
		gboolean is_quitting;

		is_quitting = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window),
								      PLUMA_IS_QUITTING));

		if (is_quitting)
			gtk_widget_destroy (GTK_WIDGET (window));
	}

	return FALSE;
}

static void
tab_state_changed_while_saving (PlumaTab    *tab,
				GParamSpec  *pspec,
				PlumaWindow *window)
{
	PlumaTabState ts;

	ts = pluma_tab_get_state (tab);

	pluma_debug_message (DEBUG_COMMANDS, "State while saving: %d\n", ts);

	/* When the state become NORMAL, it means the saving operation is
	   finished */
	if (ts == PLUMA_TAB_STATE_NORMAL)
	{
		PlumaDocument *doc;

		g_signal_handlers_disconnect_by_func (tab,
						      G_CALLBACK (tab_state_changed_while_saving),
					      	      window);

		doc = pluma_tab_get_document (tab);
		g_return_if_fail (doc != NULL);

		/* If the saving operation failed or was interrupted, then the
		   document is still "modified" -> do not close the tab */
		if (document_needs_saving (doc))
			return;

		/* Close the document only if it has been succesfully saved.
		   Tab state is set to CLOSING (it is a state without exiting
		   transitions) and the tab is closed in a idle handler */
		_pluma_tab_mark_for_closing (tab);

		g_idle_add_full (G_PRIORITY_HIGH_IDLE,
				 (GSourceFunc)really_close_tab,
				 tab,
				 NULL);
	}
}

static void
save_and_close (PlumaTab    *tab,
		PlumaWindow *window)
{
	pluma_debug (DEBUG_COMMANDS);

	/* Trace tab state changes */
	g_signal_connect (tab,
			  "notify::state",
			  G_CALLBACK (tab_state_changed_while_saving),
			  window);

	file_save (tab, window);
}

static void
save_as_and_close (PlumaTab    *tab,
		   PlumaWindow *window)
{
	pluma_debug (DEBUG_COMMANDS);

	g_object_set_data (G_OBJECT (tab),
			   PLUMA_IS_CLOSING_TAB,
			   NULL);

	/* Trace tab state changes */
	g_signal_connect (tab,
			  "notify::state",
			  G_CALLBACK (tab_state_changed_while_saving),
			  window);

	pluma_window_set_active_tab (window, tab);
	file_save_as (tab, window);
}

static void
save_and_close_all_documents (const GList  *docs,
			      PlumaWindow  *window)
{
	GList  *tabs;
	GList  *l;
	GSList *sl;
	GSList *tabs_to_save_as;
	GSList *tabs_to_save_and_close;
	GList  *tabs_to_close;

	pluma_debug (DEBUG_COMMANDS);

	g_return_if_fail (!(pluma_window_get_state (window) & PLUMA_WINDOW_STATE_PRINTING));

	tabs = gtk_container_get_children (
			GTK_CONTAINER (_pluma_window_get_notebook (window)));

	tabs_to_save_as = NULL;
	tabs_to_save_and_close = NULL;
	tabs_to_close = NULL;

	l = tabs;
	while (l != NULL)
	{
		PlumaTab *t;
		PlumaTabState state;
		PlumaDocument *doc;

		t = PLUMA_TAB (l->data);

		state = pluma_tab_get_state (t);
		doc = pluma_tab_get_document (t);

		/* If the state is: ([*] invalid states)
		   - PLUMA_TAB_STATE_NORMAL: close (and if needed save)
		   - PLUMA_TAB_STATE_LOADING: close, we are sure the file is unmodified
		   - PLUMA_TAB_STATE_REVERTING: since the user wants
		     to return back to the version of the file she previously saved, we can close
		     without saving (CHECK: are we sure this is the right behavior, suppose the case
		     the original file has been deleted)
		   - [*] PLUMA_TAB_STATE_SAVING: invalid, ClosAll
		     and Quit are unsensitive if the window state is SAVING.
		   - [*] PLUMA_TAB_STATE_PRINTING, PLUMA_TAB_STATE_PRINT_PREVIEWING: there is not a
		     real reason for not closing in this case, we do not save to avoid to run
		     two operations using the message area at the same time (may be we can remove
		     this limitation in the future). Note that ClosAll
		     and Quit are unsensitive if the window state is PRINTING.
		   - PLUMA_TAB_STATE_SHOWING_PRINT_PREVIEW: close (and if needed save)
		   - PLUMA_TAB_STATE_LOADING_ERROR: close without saving (if the state is LOADING_ERROR then the
		     document is not modified)
		   - PLUMA_TAB_STATE_REVERTING_ERROR: we do not close since the document contains errors
		   - PLUMA_TAB_STATE_SAVING_ERROR: we do not close since the document contains errors
		   - PLUMA_TAB_STATE_GENERIC_ERROR: we do not close since the document contains
		     errors (CHECK: we should problably remove this state)
		   - [*] PLUMA_TAB_STATE_CLOSING: this state is invalid in this case
		*/

		g_return_if_fail (state != PLUMA_TAB_STATE_PRINTING);
		g_return_if_fail (state != PLUMA_TAB_STATE_PRINT_PREVIEWING);
		g_return_if_fail (state != PLUMA_TAB_STATE_CLOSING);
		g_return_if_fail (state != PLUMA_TAB_STATE_SAVING);

		if ((state != PLUMA_TAB_STATE_SAVING_ERROR) &&
		    (state != PLUMA_TAB_STATE_GENERIC_ERROR) &&
		    (state != PLUMA_TAB_STATE_REVERTING_ERROR))
		{
			if ((g_list_index ((GList *)docs, doc) >= 0) &&
			    (state != PLUMA_TAB_STATE_LOADING) &&
			    (state != PLUMA_TAB_STATE_LOADING_ERROR) &&
			    (state != PLUMA_TAB_STATE_REVERTING)) /* CHECK: is this the right behavior with REVERTING ?*/
			{
				/* The document must be saved before closing */
				g_return_if_fail (document_needs_saving (doc));

				/* FIXME: manage the case of local readonly files owned by the
				   user is running pluma - Paolo (Dec. 8, 2005) */
				if (pluma_document_is_untitled (doc) ||
				    pluma_document_get_readonly (doc))
				{
					g_object_set_data (G_OBJECT (t),
							   PLUMA_IS_CLOSING_TAB,
							   GBOOLEAN_TO_POINTER (TRUE));

					tabs_to_save_as = g_slist_prepend (tabs_to_save_as,
									   t);
				}
				else
				{
					tabs_to_save_and_close = g_slist_prepend (tabs_to_save_and_close,
										  t);
				}
			}
			else
			{
				/* The document must be closed without saving */
				tabs_to_close = g_list_prepend (tabs_to_close,
								t);
			}
		}

		l = g_list_next (l);
	}

	g_list_free (tabs);

	/* Close all tabs to close (in a sync way) */
	pluma_window_close_tabs (window, tabs_to_close);
	g_list_free (tabs_to_close);

	/* Save and close all the files in tabs_to_save_and_close */
	sl = tabs_to_save_and_close;
	while (sl != NULL)
	{
		save_and_close (PLUMA_TAB (sl->data),
				window);
		sl = g_slist_next (sl);
	}
	g_slist_free (tabs_to_save_and_close);

	/* Save As and close all the files in tabs_to_save_as  */
	if (tabs_to_save_as != NULL)
	{
		PlumaTab *tab;

		tabs_to_save_as = g_slist_reverse (tabs_to_save_as );

		g_return_if_fail (g_object_get_data (G_OBJECT (window),
						     PLUMA_LIST_OF_TABS_TO_SAVE_AS) == NULL);

		g_object_set_data (G_OBJECT (window),
				   PLUMA_LIST_OF_TABS_TO_SAVE_AS,
				   tabs_to_save_as);

		tab = PLUMA_TAB (tabs_to_save_as->data);

		save_as_and_close (tab, window);
	}
}

static void
save_and_close_document (const GList  *docs,
			 PlumaWindow  *window)
{
	PlumaTab *tab;

	pluma_debug (DEBUG_COMMANDS);

	g_return_if_fail (docs->next == NULL);

	tab = pluma_tab_get_from_document (PLUMA_DOCUMENT (docs->data));
	g_return_if_fail (tab != NULL);

	save_and_close (tab, window);
}

static void
close_all_tabs (PlumaWindow *window)
{
	gboolean is_quitting;

	pluma_debug (DEBUG_COMMANDS);

	/* There is no document to save -> close all tabs */
	pluma_window_close_all_tabs (window);

	is_quitting = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window),
							      PLUMA_IS_QUITTING));

	if (is_quitting)
		gtk_widget_destroy (GTK_WIDGET (window));

	return;
}

static void
close_document (PlumaWindow   *window,
		PlumaDocument *doc)
{
	PlumaTab *tab;

	pluma_debug (DEBUG_COMMANDS);

	tab = pluma_tab_get_from_document (doc);
	g_return_if_fail (tab != NULL);

	pluma_window_close_tab (window, tab);
}

static void
close_confirmation_dialog_response_handler (PlumaCloseConfirmationDialog *dlg,
					    gint                          response_id,
					    PlumaWindow                  *window)
{
	GList *selected_documents;
	gboolean is_closing_all;

	pluma_debug (DEBUG_COMMANDS);

	is_closing_all = GPOINTER_TO_BOOLEAN (g_object_get_data (G_OBJECT (window),
					    			 PLUMA_IS_CLOSING_ALL));

	gtk_widget_hide (GTK_WIDGET (dlg));

	switch (response_id)
	{
		case GTK_RESPONSE_YES: /* Save and Close */
			selected_documents = pluma_close_confirmation_dialog_get_selected_documents (dlg);
			if (selected_documents == NULL)
			{
				if (is_closing_all)
				{
					/* There is no document to save -> close all tabs */
					/* We call gtk_widget_destroy before close_all_tabs
					 * because close_all_tabs could destroy the pluma window */
					gtk_widget_destroy (GTK_WIDGET (dlg));

					close_all_tabs (window);

					return;
				}
				else
					g_return_if_reached ();
			}
			else
			{
				if (is_closing_all)
				{
					save_and_close_all_documents (selected_documents,
								      window);
				}
				else
				{
					save_and_close_document (selected_documents,
								 window);
				}
			}

			g_list_free (selected_documents);

			break;

		case GTK_RESPONSE_NO: /* Close without Saving */
			if (is_closing_all)
			{
				/* We call gtk_widget_destroy before close_all_tabs
				 * because close_all_tabs could destroy the pluma window */
				gtk_widget_destroy (GTK_WIDGET (dlg));

				close_all_tabs (window);

				return;
			}
			else
			{
				const GList *unsaved_documents;

				unsaved_documents = pluma_close_confirmation_dialog_get_unsaved_documents (dlg);
				g_return_if_fail (unsaved_documents->next == NULL);

				close_document (window,
						PLUMA_DOCUMENT (unsaved_documents->data));
			}

			break;
		default: /* Do not close */

			/* Reset is_quitting flag */
			g_object_set_data (G_OBJECT (window),
					   PLUMA_IS_QUITTING,
					   GBOOLEAN_TO_POINTER (FALSE));

			break;
	}

	gtk_widget_destroy (GTK_WIDGET (dlg));
}

/* Returns TRUE if the tab can be immediately closed */
static gboolean
tab_can_close (PlumaTab  *tab,
	       GtkWindow *window)
{
	PlumaDocument *doc;

	pluma_debug (DEBUG_COMMANDS);

	doc = pluma_tab_get_document (tab);

	if (!_pluma_tab_can_close (tab))
	{
		GtkWidget     *dlg;

		dlg = pluma_close_confirmation_dialog_new_single (
						window,
						doc,
						FALSE);

		g_signal_connect (dlg,
				  "response",
				  G_CALLBACK (close_confirmation_dialog_response_handler),
				  window);

		gtk_widget_show (dlg);

		return FALSE;
	}

	return TRUE;
}

/* CHECK: we probably need this one public for plugins...
 * maybe even a _list variant. Or maybe it's better make
 * pluma_window_close_tab always run the confirm dialog?
 * we should not allow closing a tab without resetting the
 * PLUMA_IS_CLOSING_ALL flag!
 */
void
_pluma_cmd_file_close_tab (PlumaTab    *tab,
			   PlumaWindow *window)
{
	pluma_debug (DEBUG_COMMANDS);

	g_return_if_fail (GTK_WIDGET (window) == gtk_widget_get_toplevel (GTK_WIDGET (tab)));

	g_object_set_data (G_OBJECT (window),
			   PLUMA_IS_CLOSING_ALL,
			   GBOOLEAN_TO_POINTER (FALSE));

	g_object_set_data (G_OBJECT (window),
			   PLUMA_IS_QUITTING,
			   GBOOLEAN_TO_POINTER (FALSE));

	g_object_set_data (G_OBJECT (window),
	                   PLUMA_IS_QUITTING_ALL,
	                   GINT_TO_POINTER (FALSE));


	if (tab_can_close (tab, GTK_WINDOW (window)))
		pluma_window_close_tab (window, tab);
}

void
_pluma_cmd_file_close (GtkAction   *action,
		      PlumaWindow *window)
{
	PlumaTab *active_tab;

	pluma_debug (DEBUG_COMMANDS);

	active_tab = pluma_window_get_active_tab (window);

	if (active_tab == NULL)
	{
		return;
	}

	_pluma_cmd_file_close_tab (active_tab, window);
}

/* Close all tabs */
static void
file_close_all (PlumaWindow *window,
		gboolean     is_quitting)
{
	GList     *unsaved_docs;
	GtkWidget *dlg;

	pluma_debug (DEBUG_COMMANDS);

	g_return_if_fail (!(pluma_window_get_state (window) &
	                    (PLUMA_WINDOW_STATE_SAVING |
	                     PLUMA_WINDOW_STATE_PRINTING |
	                     PLUMA_WINDOW_STATE_SAVING_SESSION)));

	g_object_set_data (G_OBJECT (window),
			   PLUMA_IS_CLOSING_ALL,
			   GBOOLEAN_TO_POINTER (TRUE));

	g_object_set_data (G_OBJECT (window),
			   PLUMA_IS_QUITTING,
			   GBOOLEAN_TO_POINTER (is_quitting));

	unsaved_docs = pluma_window_get_unsaved_documents (window);

	if (unsaved_docs == NULL)
	{
		/* There is no document to save -> close all tabs */
		pluma_window_close_all_tabs (window);

		if (is_quitting)
			gtk_widget_destroy (GTK_WIDGET (window));

		return;
	}

	if (unsaved_docs->next == NULL)
	{
		/* There is only one unsaved document */
		PlumaTab      *tab;
		PlumaDocument *doc;

		doc = PLUMA_DOCUMENT (unsaved_docs->data);

		tab = pluma_tab_get_from_document (doc);
		g_return_if_fail (tab != NULL);

		pluma_window_set_active_tab (window, tab);

		dlg = pluma_close_confirmation_dialog_new_single (
						GTK_WINDOW (window),
						doc,
						FALSE);
	}
	else
	{
		dlg = pluma_close_confirmation_dialog_new (GTK_WINDOW (window),
							   unsaved_docs,
							   FALSE);
	}

	g_list_free (unsaved_docs);

	g_signal_connect (dlg,
			  "response",
			  G_CALLBACK (close_confirmation_dialog_response_handler),
			  window);

	gtk_widget_show (dlg);
}

void
_pluma_cmd_file_close_all (GtkAction   *action,
			  PlumaWindow *window)
{
	pluma_debug (DEBUG_COMMANDS);

	g_return_if_fail (!(pluma_window_get_state (window) &
	                    (PLUMA_WINDOW_STATE_SAVING |
	                    PLUMA_WINDOW_STATE_PRINTING |
	                    PLUMA_WINDOW_STATE_SAVING_SESSION)));

	file_close_all (window, FALSE);
}

void
_pluma_cmd_file_quit (GtkAction   *action,
		     PlumaWindow *window)
{
	pluma_debug (DEBUG_COMMANDS);

	g_return_if_fail (!(pluma_window_get_state (window) &
	                    (PLUMA_WINDOW_STATE_SAVING |
	                     PLUMA_WINDOW_STATE_PRINTING |
	                     PLUMA_WINDOW_STATE_SAVING_SESSION)));

	file_close_all (window, TRUE);
}
