/* * mooeditfileops.c * * Copyright (C) 2004-2007 by Yevgen Muntyan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * See COPYING file that comes with this distribution. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #define MOOEDIT_COMPILATION #include "mooedit/mooedit-private.h" #include "mooedit/mooeditor-private.h" #include "mooedit/mooeditfileops.h" #include "mooedit/mooeditdialogs.h" #include "mooedit/mootextbuffer.h" #include "mooedit/mooeditprefs.h" #include "moofileview/moofile.h" #include "mooutils/moofilewatch.h" #include "mooutils/mooencodings.h" #include "mooutils/mooi18n.h" #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #if GLIB_CHECK_VERSION(2,6,0) # include #endif #undef LOAD_BINARY #define ENCODING_LOCALE "LOCALE" #ifndef O_BINARY #define O_BINARY 0 #endif static GSList *UNTITLED = NULL; static GHashTable *UNTITLED_NO = NULL; static void block_buffer_signals (MooEdit *edit); static void unblock_buffer_signals (MooEdit *edit); static gboolean focus_in_cb (MooEdit *edit); static void check_file_status (MooEdit *edit, gboolean in_focus_only); static void file_modified_on_disk (MooEdit *edit); static void file_deleted (MooEdit *edit); static void add_status (MooEdit *edit, MooEditStatus s); static gboolean moo_edit_load_local (MooEdit *edit, const char *file, const char *encoding, GError **error); static gboolean moo_edit_reload_local (MooEdit *edit, const char *encoding, GError **error); static gboolean moo_edit_save_local (MooEdit *edit, const char *filename, const char *encoding, MooEditSaveFlags flags, GError **error); static gboolean moo_edit_save_copy_local (MooEdit *edit, const char *filename, const char *encoding, GError **error); static char *_moo_edit_filename_to_utf8 (const char *filename); static void _moo_edit_start_file_watch (MooEdit *edit); GQuark _moo_edit_file_error_quark (void) { static GQuark q; if (!q) q = g_quark_from_static_string ("MooEditFileErrorQuark"); return q; } static const char * normalize_encoding (const char *encoding, gboolean for_save) { if (!encoding || !encoding[0] || !strcmp (encoding, MOO_ENCODING_AUTO)) encoding = for_save ? MOO_ENCODING_UTF8 : NULL; return encoding; } gboolean _moo_edit_load_file (MooEdit *edit, const char *filename, const char *encoding, GError **error) { char *filename_copy, *encoding_copy; gboolean result; GError *error_here = NULL; g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); g_return_val_if_fail (filename != NULL, FALSE); filename_copy = g_strdup (filename); encoding_copy = g_strdup (normalize_encoding (encoding, FALSE)); result = moo_edit_load_local (edit, filename_copy, encoding_copy, &error_here); if (error_here) g_propagate_error (error, error_here); g_free (filename_copy); g_free (encoding_copy); return result; } gboolean _moo_edit_reload_file (MooEdit *edit, const char *encoding, GError **error) { GError *error_here = NULL; gboolean result; g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); result = moo_edit_reload_local (edit, encoding, &error_here); if (error_here) g_propagate_error (error, error_here); return result; } gboolean _moo_edit_save_file (MooEdit *edit, const char *filename, const char *encoding, MooEditSaveFlags flags, GError **error) { GError *error_here = NULL; char *filename_copy, *encoding_copy; gboolean result; g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); g_return_val_if_fail (filename != NULL, FALSE); filename_copy = g_strdup (filename); encoding_copy = g_strdup (normalize_encoding (encoding, TRUE)); result = moo_edit_save_local (edit, filename_copy, encoding_copy, flags, &error_here); if (error_here) g_propagate_error (error, error_here); g_free (filename_copy); g_free (encoding_copy); return result; } gboolean _moo_edit_save_file_copy (MooEdit *edit, const char *filename, const char *encoding, GError **error) { char *filename_copy, *encoding_copy; gboolean result; g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); g_return_val_if_fail (filename != NULL, FALSE); filename_copy = g_strdup (filename); encoding_copy = g_strdup (normalize_encoding (encoding, TRUE)); result = moo_edit_save_copy_local (edit, filename_copy, encoding_copy, error); g_free (filename_copy); g_free (encoding_copy); return result; } static void set_encoding_error (GError **error) { GError *tmp_error = NULL; g_set_error (&tmp_error, MOO_EDIT_FILE_ERROR, MOO_EDIT_FILE_ERROR_ENCODING, "%s", *error ? (*error)->message : "ERROR"); g_clear_error (error); g_propagate_error (error, tmp_error); } /***************************************************************************/ /* File loading */ typedef enum { SUCCESS, ERROR_FILE, ERROR_ENCODING } LoadResult; static LoadResult do_load (MooEdit *edit, const char *file, const char *encoding, struct stat *statbuf, GError **error); #ifdef LOAD_BINARY static LoadResult load_binary (MooEdit *edit, const char *file, GError **error); #endif const char * _moo_get_default_encodings (void) { /* Translators: if translated, it should be a comma-separated list of encodings to try when opening files. Encodings names should be those understood by iconv, or "LOCALE" which means user's locale charset. For instance, the default value is "UTF-8,LOCALE,ISO_8859-15,ISO_8859-1". You want to add common preferred non-UTF8 encodings used in your locale. Do not remove ISO_8859-15 and ISO_8859-1, instead leave them at the end, these are common source files encodings. */ const char *to_translate = N_("encodings_list"); const char *encodings; encodings = _(to_translate); if (!strcmp (encodings, to_translate)) encodings = "UTF-8," ENCODING_LOCALE ",ISO_8859-1,ISO_8859-15"; return encodings; } static GSList * get_encodings (void) { const char *encodings; char **raw, **p; GSList *result; encodings = moo_prefs_get_string (moo_edit_setting (MOO_EDIT_PREFS_ENCODINGS)); if (!encodings || !encodings[0]) encodings = _moo_get_default_encodings (); result = NULL; raw = g_strsplit (encodings, ",", 0); for (p = raw; p && *p; ++p) { const char *enc; if (!g_ascii_strcasecmp (*p, ENCODING_LOCALE)) { if (g_get_charset (&enc)) enc = "UTF-8"; } else { enc = *p; } if (!g_slist_find_custom (result, enc, (GCompareFunc) g_ascii_strcasecmp)) result = g_slist_prepend (result, g_strdup (enc)); } if (!result) { g_critical ("%s: oops", G_STRLOC); result = g_slist_prepend (NULL, g_strdup ("UTF-8")); } g_strfreev (raw); return g_slist_reverse (result); } static LoadResult try_load (MooEdit *edit, const char *file, const char *encoding, struct stat *statbuf, GError **error) { GtkTextBuffer *buffer; gboolean enable_highlight; LoadResult result; g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); g_return_val_if_fail (file && file[0], FALSE); g_return_val_if_fail (encoding && encoding[0], FALSE); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (edit)); gtk_text_buffer_set_text (buffer, "", 0); g_object_get (edit, "enable-highlight", &enable_highlight, NULL); g_object_set (edit, "enable-highlight", FALSE, NULL); result = do_load (edit, file, encoding, statbuf, error); g_object_set (edit, "enable-highlight", enable_highlight, NULL); return result; } static gboolean moo_edit_load_local (MooEdit *edit, const char *file, const char *encoding, GError **error) { GtkTextIter start; GtkTextBuffer *buffer; MooTextView *view; gboolean undo; LoadResult result = ERROR_FILE; struct stat statbuf; char *freeme = NULL; g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); g_return_val_if_fail (file && file[0], FALSE); if (moo_edit_is_empty (edit)) undo = FALSE; else undo = TRUE; view = MOO_TEXT_VIEW (edit); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (edit)); block_buffer_signals (edit); if (undo) gtk_text_buffer_begin_user_action (buffer); else moo_text_view_begin_not_undoable_action (view); moo_text_buffer_begin_non_interactive_action (MOO_TEXT_BUFFER (buffer)); if (!encoding) { GSList *encodings; encodings = get_encodings (); result = ERROR_ENCODING; while (encodings) { char *enc = encodings->data; enc = encodings->data; encodings = g_slist_delete_link (encodings, encodings); g_clear_error (error); result = try_load (edit, file, enc, &statbuf, error); if (result != ERROR_ENCODING) { encoding = freeme = enc; break; } g_free (enc); } g_slist_foreach (encodings, (GFunc) g_free, NULL); g_slist_free (encodings); } else { result = try_load (edit, file, encoding, &statbuf, error); } #ifdef LOAD_BINARY if (result == ERROR_ENCODING) { g_clear_error (error); result = load_binary (edit, file, &statbuf, error); } #endif if (result == ERROR_ENCODING) set_encoding_error (error); if (result != SUCCESS) gtk_text_buffer_set_text (buffer, "", 0); unblock_buffer_signals (edit); if (result == SUCCESS) { /* XXX */ gtk_text_buffer_get_start_iter (buffer, &start); gtk_text_buffer_place_cursor (buffer, &start); edit->priv->status = 0; moo_edit_set_modified (edit, FALSE); _moo_edit_set_filename (edit, file, encoding); edit->priv->mode = statbuf.st_mode; edit->priv->mode_set = TRUE; _moo_edit_start_file_watch (edit); } else { _moo_edit_stop_file_watch (edit); } if (undo) gtk_text_buffer_end_user_action (buffer); else moo_text_view_end_not_undoable_action (view); moo_text_buffer_end_non_interactive_action (MOO_TEXT_BUFFER (buffer)); g_free (freeme); return result == SUCCESS; } static LoadResult do_load (MooEdit *edit, const char *filename, const char *encoding, struct stat *statbuf, GError **error) { GIOChannel *file = NULL; GIOStatus status; GtkTextBuffer *buffer; MooEditLineEndType le = MOO_EDIT_LINE_END_NONE; GString *text = NULL; char *line = NULL; LoadResult result = ERROR_FILE; g_return_val_if_fail (filename != NULL, ERROR_FILE); g_return_val_if_fail (encoding != NULL, ERROR_FILE); if (g_stat (filename, statbuf) == 0) { if ( #ifdef S_ISBLK S_ISBLK (statbuf->st_mode) || #endif #ifdef S_ISCHR S_ISCHR (statbuf->st_mode) || #endif #ifdef S_ISFIFO S_ISFIFO (statbuf->st_mode) || #endif #ifdef S_ISSOCK S_ISSOCK (statbuf->st_mode) || #endif 0) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Not a regular file."); return ERROR_FILE; } } file = g_io_channel_new_file (filename, "r", error); if (!file) return ERROR_FILE; if (g_io_channel_set_encoding (file, encoding, error) != G_IO_STATUS_NORMAL) { result = ERROR_ENCODING; goto error_out; } text = g_string_new (NULL); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (edit)); while (TRUE) { gboolean insert_line_term = FALSE; gsize len, line_term_pos; MooEditLineEndType le_here = 0; status = g_io_channel_read_line (file, &line, &len, &line_term_pos, error); if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_EOF) { if ((*error)->domain == G_CONVERT_ERROR) result = ERROR_ENCODING; else result = ERROR_FILE; goto error_out; } if (line) { if (!g_utf8_validate (line, len, NULL)) { result = ERROR_ENCODING; g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, "Invalid UTF8 data read from file"); goto error_out; } if (line_term_pos != len) { char *line_term = line + line_term_pos; insert_line_term = TRUE; len = line_term_pos; if (!strcmp ("\xe2\x80\xa9", line_term)) { insert_line_term = FALSE; len = len + 3; } else if (!strcmp (line_term, "\r")) { le_here = MOO_EDIT_LINE_END_MAC; } else if (!strcmp (line_term, "\n")) { le_here = MOO_EDIT_LINE_END_UNIX; } else if (!strcmp (line_term, "\r\n")) { le_here = MOO_EDIT_LINE_END_WIN32; } if (le_here) { if (le && le != le_here) le = MOO_EDIT_LINE_END_MIX; else le = le_here; } } g_string_append_len (text, line, len); if (insert_line_term) g_string_append_c (text, '\n'); #define MAX_TEXT_BUF_LEN 1000000 if (text->len > MAX_TEXT_BUF_LEN) { gtk_text_buffer_insert_at_cursor (buffer, text->str, text->len); g_string_truncate (text, 0); } #undef MAX_TEXT_BUF_LEN g_free (line); } if (status == G_IO_STATUS_EOF) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); break; } } if (text->len) gtk_text_buffer_insert_at_cursor (buffer, text->str, text->len); edit->priv->line_end_type = le; g_string_free (text, TRUE); g_clear_error (error); return SUCCESS; error_out: if (text) g_string_free (text, TRUE); if (file) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); } g_free (line); return result; } #ifdef LOAD_BINARY #define TEXT_BUF_LEN (1 << 16) #define REPLACEMENT_CHAR '\1' #define LINE_LEN 80 static LoadResult load_binary (MooEdit *edit, const char *filename, GError **error) { GIOChannel *file = NULL; GIOStatus status; GtkTextBuffer *buffer; g_return_val_if_fail (filename != NULL, FALSE); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (edit)); file = g_io_channel_new_file (filename, "r", error); if (!file) return ERROR_FILE; g_io_channel_set_encoding (file, NULL, NULL); while (TRUE) { gsize len, line_len; char buf[TEXT_BUF_LEN]; status = g_io_channel_read_chars (file, buf, TEXT_BUF_LEN, &len, error); if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_EOF) goto error_out; if (len) { guint i; for (i = 0, line_len = 0; i < len; ++i, ++line_len) { if (buf[i] && !(buf[i] & 0x80)) continue; if (line_len > LINE_LEN) { buf[i] = '\n'; line_len = 0; } else { buf[i] = REPLACEMENT_CHAR; } } gtk_text_buffer_insert_at_cursor (buffer, buf, len); } if (status == G_IO_STATUS_EOF) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); break; } } g_clear_error (error); return SUCCESS; error_out: if (file) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); } return ERROR_FILE; } #undef TEXT_BUF_LEN #undef REPLACEMENT_CHAR #undef LINE_LEN #endif /* XXX */ static gboolean moo_edit_reload_local (MooEdit *edit, const char *encoding, GError **error) { GtkTextIter start, end; GtkTextBuffer *buffer; gboolean result, enable_highlight; g_return_val_if_fail (edit->priv->filename != NULL, FALSE); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (edit)); block_buffer_signals (edit); gtk_text_buffer_begin_user_action (buffer); gtk_text_buffer_get_bounds (buffer, &start, &end); gtk_text_buffer_delete (buffer, &start, &end); g_object_get (edit, "enable-highlight", &enable_highlight, NULL); g_object_set (edit, "enable-highlight", FALSE, NULL); result = _moo_edit_load_file (edit, edit->priv->filename, encoding ? encoding : edit->priv->encoding, error); g_object_set (edit, "enable-highlight", enable_highlight, NULL); gtk_text_buffer_end_user_action (buffer); unblock_buffer_signals (edit); if (result) { edit->priv->status = 0; moo_edit_set_modified (edit, FALSE); _moo_edit_start_file_watch (edit); g_clear_error (error); } return result; } /***************************************************************************/ /* File saving */ #ifdef __WIN32__ #define BAK_SUFFIX ".bak" #define DEFAULT_LE_TYPE MOO_EDIT_LINE_END_WIN32 #else #define BAK_SUFFIX "~" #define DEFAULT_LE_TYPE MOO_EDIT_LINE_END_UNIX #endif static gboolean do_write (MooEdit *edit, const char *file, const char *encoding, GError **error); static gboolean do_save_local (MooEdit *edit, const char *filename, const char *encoding, GError **error, gboolean *retval) { *retval = TRUE; if (!do_write (edit, filename, encoding, error)) { if ((*error)->domain != G_CONVERT_ERROR || _moo_encodings_equal (encoding, MOO_ENCODING_UTF8)) { g_clear_error (error); if (!do_write (edit, filename, MOO_ENCODING_UTF8, error)) { *retval = FALSE; return FALSE; } } *retval = FALSE; set_encoding_error (error); } return TRUE; } static gboolean moo_edit_save_local (MooEdit *edit, const char *filename, const char *encoding, MooEditSaveFlags flags, GError **error) { gboolean result; g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); g_return_val_if_fail (filename && filename[0], FALSE); if (do_save_local (edit, filename, encoding, error, &result)) { edit->priv->status = 0; _moo_edit_set_filename (edit, filename, result ? encoding : MOO_ENCODING_UTF8); moo_edit_set_modified (edit, FALSE); _moo_edit_start_file_watch (edit); } return result; } static gboolean moo_edit_save_copy_local (MooEdit *edit, const char *filename, const char *encoding, GError **error) { gboolean result; g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); g_return_val_if_fail (filename && filename[0], FALSE); do_save_local (edit, filename, encoding, error, &result); return result; } static gboolean do_write (MooEdit *edit, const char *filename, const char *encoding, GError **error) { int fd = -1; GIOChannel *file; GIOStatus status; GtkTextIter line_start; GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (edit)); MooEditLineEndType le_type = DEFAULT_LE_TYPE; const char *le = "\n"; gssize le_len = 1; mode_t mode; g_return_val_if_fail (filename != NULL, FALSE); if (encoding && (!g_ascii_strcasecmp (encoding, "UTF-8") || !g_ascii_strcasecmp (encoding, "UTF8"))) encoding = NULL; #ifdef __WIN32__ mode = S_IRUSR | S_IWUSR; #else if (edit->priv->mode_set) mode = (edit->priv->mode & (S_IRWXU | S_IRWXG | S_IRWXO)) | S_IRUSR | S_IWUSR; else mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; #endif errno = 0; fd = g_open (filename, O_BINARY | O_WRONLY | O_CREAT | O_TRUNC, mode); if (fd == -1) { int err = errno; g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (err), "%s", g_strerror (err)); return FALSE; } #ifndef __WIN32__ file = g_io_channel_unix_new (fd); #else file = g_io_channel_win32_new_fd (fd); #endif g_io_channel_set_close_on_unref (file, TRUE); if (encoding && g_io_channel_set_encoding (file, encoding, error) != G_IO_STATUS_NORMAL) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); return FALSE; } switch (edit->priv->line_end_type) { case MOO_EDIT_LINE_END_NONE: le_type = DEFAULT_LE_TYPE; break; case MOO_EDIT_LINE_END_UNIX: case MOO_EDIT_LINE_END_WIN32: case MOO_EDIT_LINE_END_MAC: le_type = edit->priv->line_end_type; break; case MOO_EDIT_LINE_END_MIX: edit->priv->line_end_type = le_type = DEFAULT_LE_TYPE; break; } switch (le_type) { case MOO_EDIT_LINE_END_UNIX: le = "\n"; le_len = 1; break; case MOO_EDIT_LINE_END_WIN32: le = "\r\n"; le_len = 2; break; case MOO_EDIT_LINE_END_MAC: le = "\r"; le_len = 1; break; case MOO_EDIT_LINE_END_MIX: case MOO_EDIT_LINE_END_NONE: g_assert_not_reached (); } gtk_text_buffer_get_start_iter (buffer, &line_start); do { gsize written; GtkTextIter line_end = line_start; if (!gtk_text_iter_ends_line (&line_start)) { char *line; gssize len = -1; gtk_text_iter_forward_to_line_end (&line_end); line = gtk_text_buffer_get_slice (buffer, &line_start, &line_end, TRUE); status = g_io_channel_write_chars (file, line, len, &written, error); g_free (line); /* XXX */ if (status != G_IO_STATUS_NORMAL) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); return FALSE; } } if (!gtk_text_iter_is_end (&line_end)) { status = g_io_channel_write_chars (file, le, le_len, &written, error); if (written < (gsize)le_len || status != G_IO_STATUS_NORMAL) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); return FALSE; } } } while (gtk_text_iter_forward_line (&line_start)); /* glib #320668 */ status = g_io_channel_flush (file, error); if (status != G_IO_STATUS_NORMAL) { g_io_channel_shutdown (file, FALSE, NULL); g_io_channel_unref (file); return FALSE; } status = g_io_channel_shutdown (file, TRUE, error); if (status != G_IO_STATUS_NORMAL) { g_io_channel_unref (file); return FALSE; } g_io_channel_unref (file); g_clear_error (error); return TRUE; } /***************************************************************************/ /* Aux stuff */ static void block_buffer_signals (MooEdit *edit) { GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (edit)); g_signal_handler_block (buffer, edit->priv->modified_changed_handler_id); } static void unblock_buffer_signals (MooEdit *edit) { GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (edit)); g_signal_handler_unblock (buffer, edit->priv->modified_changed_handler_id); } static void file_watch_callback (G_GNUC_UNUSED MooFileWatch *watch, MooFileEvent *event, gpointer data) { MooEdit *edit = data; g_return_if_fail (MOO_IS_EDIT (data)); g_return_if_fail (event->monitor_id == edit->priv->file_monitor_id); g_return_if_fail (edit->priv->filename != NULL); g_return_if_fail (!(edit->priv->status & MOO_EDIT_CHANGED_ON_DISK)); switch (event->code) { case MOO_FILE_EVENT_CHANGED: edit->priv->modified_on_disk = TRUE; break; case MOO_FILE_EVENT_DELETED: edit->priv->deleted_from_disk = TRUE; edit->priv->file_monitor_id = 0; break; case MOO_FILE_EVENT_ERROR: /* XXX and what to do now? */ break; case MOO_FILE_EVENT_CREATED: g_critical ("%s: oops", G_STRLOC); break; } check_file_status (edit, FALSE); } static void _moo_edit_start_file_watch (MooEdit *edit) { MooFileWatch *watch; GError *error = NULL; watch = _moo_editor_get_file_watch (edit->priv->editor); g_return_if_fail (watch != NULL); if (edit->priv->file_monitor_id) moo_file_watch_cancel_monitor (watch, edit->priv->file_monitor_id); edit->priv->file_monitor_id = 0; g_return_if_fail ((edit->priv->status & MOO_EDIT_CHANGED_ON_DISK) == 0); g_return_if_fail (edit->priv->filename != NULL); edit->priv->file_monitor_id = moo_file_watch_create_monitor (watch, edit->priv->filename, file_watch_callback, edit, NULL, &error); if (!edit->priv->file_monitor_id) { g_warning ("%s: could not start watch for '%s'", G_STRLOC, edit->priv->filename); if (error) { g_warning ("%s: %s", G_STRLOC, error->message); g_error_free (error); } return; } if (!edit->priv->focus_in_handler_id) edit->priv->focus_in_handler_id = g_signal_connect (edit, "focus-in-event", G_CALLBACK (focus_in_cb), NULL); } void _moo_edit_stop_file_watch (MooEdit *edit) { MooFileWatch *watch; watch = _moo_editor_get_file_watch (edit->priv->editor); g_return_if_fail (watch != NULL); if (edit->priv->file_monitor_id) moo_file_watch_cancel_monitor (watch, edit->priv->file_monitor_id); edit->priv->file_monitor_id = 0; if (edit->priv->focus_in_handler_id) { g_signal_handler_disconnect (edit, edit->priv->focus_in_handler_id); edit->priv->focus_in_handler_id = 0; } } static gboolean focus_in_cb (MooEdit *edit) { check_file_status (edit, TRUE); return FALSE; } static void check_file_status (MooEdit *edit, gboolean in_focus_only) { if (in_focus_only && !GTK_WIDGET_HAS_FOCUS (edit)) return; g_return_if_fail (edit->priv->filename != NULL); g_return_if_fail (!(edit->priv->status & MOO_EDIT_CHANGED_ON_DISK)); if (edit->priv->deleted_from_disk) file_deleted (edit); else if (edit->priv->modified_on_disk) file_modified_on_disk (edit); } static void file_modified_on_disk (MooEdit *edit) { g_return_if_fail (edit->priv->filename != NULL); edit->priv->modified_on_disk = FALSE; edit->priv->deleted_from_disk = FALSE; _moo_edit_stop_file_watch (edit); add_status (edit, MOO_EDIT_MODIFIED_ON_DISK); } static void file_deleted (MooEdit *edit) { g_return_if_fail (edit->priv->filename != NULL); edit->priv->modified_on_disk = FALSE; edit->priv->deleted_from_disk = FALSE; _moo_edit_stop_file_watch (edit); add_status (edit, MOO_EDIT_DELETED); } static void add_status (MooEdit *edit, MooEditStatus s) { edit->priv->status |= s; g_signal_emit_by_name (edit, "doc-status-changed", NULL); } static void remove_untitled (gpointer destroyed, MooEdit *edit) { gpointer n = g_hash_table_lookup (UNTITLED_NO, edit); if (n) { UNTITLED = g_slist_remove (UNTITLED, n); g_hash_table_remove (UNTITLED_NO, edit); if (!destroyed) g_object_weak_unref (G_OBJECT (edit), (GWeakNotify) remove_untitled, GINT_TO_POINTER (TRUE)); } } static int add_untitled (MooEdit *edit) { int n; if (!(n = GPOINTER_TO_INT (g_hash_table_lookup (UNTITLED_NO, edit)))) { for (n = 1; ; ++n) { if (!g_slist_find (UNTITLED, GINT_TO_POINTER (n))) { UNTITLED = g_slist_prepend (UNTITLED, GINT_TO_POINTER (n)); break; } } g_hash_table_insert (UNTITLED_NO, edit, GINT_TO_POINTER (n)); g_object_weak_ref (G_OBJECT (edit), (GWeakNotify) remove_untitled, GINT_TO_POINTER (TRUE)); } return n; } const char * _moo_edit_get_default_encoding (void) { return moo_prefs_get_string (moo_edit_setting (MOO_EDIT_PREFS_ENCODING_SAVE)); } void _moo_edit_set_filename (MooEdit *edit, const char *file, const char *encoding) { char *tmp1, *tmp3, *tmp4; tmp1 = edit->priv->filename; tmp3 = edit->priv->display_filename; tmp4 = edit->priv->display_basename; if (!UNTITLED_NO) UNTITLED_NO = g_hash_table_new (g_direct_hash, g_direct_equal); if (!file) { int n = add_untitled (edit); edit->priv->filename = NULL; if (n == 1) edit->priv->display_filename = g_strdup (_("Untitled")); else edit->priv->display_filename = g_strdup_printf (_("Untitled %d"), n); edit->priv->display_basename = g_strdup (edit->priv->display_filename); } else { char *basename; remove_untitled (NULL, edit); edit->priv->filename = g_strdup (file); edit->priv->display_filename = _moo_edit_filename_to_utf8 (file); basename = g_path_get_basename (file); edit->priv->display_basename = _moo_edit_filename_to_utf8 (basename); g_free (basename); } if (!encoding) _moo_edit_set_encoding (edit, _moo_edit_get_default_encoding ()); else _moo_edit_set_encoding (edit, encoding); g_signal_emit_by_name (edit, "filename-changed", edit->priv->filename, NULL); moo_edit_status_changed (edit); g_free (tmp1); g_free (tmp3); g_free (tmp4); } static char * _moo_edit_filename_to_utf8 (const char *filename) { GError *err = NULL; char *utf_filename; g_return_val_if_fail (filename != NULL, NULL); utf_filename = g_filename_to_utf8 (filename, -1, NULL, NULL, &err); if (!utf_filename) { g_critical ("%s: could not convert filename to UTF8", G_STRLOC); if (err) { g_critical ("%s: %s", G_STRLOC, err->message); g_error_free (err); err = NULL; } utf_filename = g_strdup (""); } return utf_filename; } GdkPixbuf * _moo_edit_get_icon (MooEdit *doc, GtkWidget *widget, GtkIconSize size) { return _moo_get_icon_for_path (doc->priv->filename, widget, size); }