medit/moo/mooedit/mooeditfilemgr.c

1269 lines
38 KiB
C

/*
* mooedit/mooeditfilemgr.c
*
* Copyright (C) 2004-2005 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* 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.
*
* See COPYING file that comes with this distribution.
*/
#define MOOEDIT_COMPILATION
#include "mooedit/mooeditfilemgr.h"
#include "mooedit/mooeditprefs.h"
#include "mooedit/mooedit-private.h"
#include "mooutils/moodialogs.h"
#include "mooutils/moomarshals.h"
#include <string.h>
#define PREFS_FILTERS "filters"
#define PREFS_LAST_FILTER PREFS_FILTERS "/last"
#define PREFS_USER "user"
#define PREFS_RECENT "recent_files"
#define PREFS_ENTRY "entry"
#define PREFS_SHOW_FULL_NAME PREFS_RECENT "/show_full_name"
#define NUM_USER_FILTERS 3
#define NUM_RECENT_FILES 5
typedef struct {
GtkFileFilter *filter;
GtkFileFilter *aux;
char *description;
char *glob;
gboolean user;
} Filter;
static Filter *filter_new (const char *description,
const char *glob,
gboolean user);
static void filter_free (Filter *filter);
static GtkFileFilter *filter_get_gtk_filter (Filter *filter);
static const char *filter_get_glob (Filter *filter);
static const char *filter_get_description (Filter *filter);
typedef struct _RecentStuff RecentStuff;
struct _MooEditFileMgrPrivate {
RecentStuff *recent;
GtkListStore *filters;
Filter *last_filter;
Filter *null_filter;
guint num_user_filters;
};
enum {
COLUMN_DESCRIPTION,
COLUMN_FILTER,
NUM_COLUMNS
};
static void moo_edit_file_mgr_class_init (MooEditFileMgrClass *klass);
static void moo_edit_file_mgr_init (MooEditFileMgr *mgr);
static void moo_edit_file_mgr_finalize (GObject *object);
static void mgr_recent_stuff_init (MooEditFileMgr *mgr);
static void mgr_recent_stuff_free (MooEditFileMgr *mgr);
static void mgr_load_filter_prefs (MooEditFileMgr *mgr);
static void mgr_save_filter_prefs (MooEditFileMgr *mgr);
static Filter *mgr_new_user_filter (MooEditFileMgr *mgr,
const char *glob);
static void mgr_set_last_filter (MooEditFileMgr *mgr,
Filter *filter);
static Filter *mgr_get_last_filter (MooEditFileMgr *mgr);
static Filter *mgr_get_null_filter (MooEditFileMgr *mgr);
static void mgr_set_filter (MooEditFileMgr *mgr,
GtkFileChooser *dialog,
Filter *filter);
static void list_store_init (MooEditFileMgr *mgr);
static void list_store_destroy (MooEditFileMgr *mgr);
static void list_store_append_filter (MooEditFileMgr *mgr,
Filter *filter);
static Filter *list_store_find_filter (MooEditFileMgr *mgr,
const char *text);
/* MOO_TYPE_EDIT_FILE_MGR */
G_DEFINE_TYPE (MooEditFileMgr, moo_edit_file_mgr, G_TYPE_OBJECT)
enum {
OPEN_RECENT,
NUM_SIGNALS
};
static guint signals[NUM_SIGNALS];
static void moo_edit_file_mgr_class_init (MooEditFileMgrClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = moo_edit_file_mgr_finalize;
signals[OPEN_RECENT] =
g_signal_new ("open-recent",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (MooEditFileMgrClass, open_recent),
NULL, NULL,
_moo_marshal_VOID__BOXED_POINTER_OBJECT,
G_TYPE_NONE, 3,
MOO_TYPE_EDIT_FILE_INFO | G_SIGNAL_TYPE_STATIC_SCOPE,
G_TYPE_POINTER,
GTK_TYPE_MENU_ITEM);
}
static void moo_edit_file_mgr_init (MooEditFileMgr *mgr)
{
mgr->priv = g_new0 (MooEditFileMgrPrivate, 1);
list_store_init (mgr);
mgr_recent_stuff_init (mgr);
}
static void moo_edit_file_mgr_finalize (GObject *object)
{
MooEditFileMgr *mgr = MOO_EDIT_FILE_MGR (object);
list_store_destroy (mgr);
mgr_recent_stuff_free (mgr);
G_OBJECT_CLASS (moo_edit_file_mgr_parent_class)->finalize (object);
}
MooEditFileMgr *moo_edit_file_mgr_new (void)
{
return MOO_EDIT_FILE_MGR (g_object_new (MOO_TYPE_EDIT_FILE_MGR, NULL));
}
static gboolean row_is_separator (GtkTreeModel *model,
GtkTreeIter *iter,
G_GNUC_UNUSED gpointer data)
{
Filter *filter;
gtk_tree_model_get (model, iter, COLUMN_FILTER, &filter, -1);
return filter == NULL;
}
static void filter_entry_activated (GtkEntry *entry,
GtkFileChooser *dialog);
static void combo_changed (GtkComboBox *combo,
GtkFileChooser *dialog);
static void setup_open_dialog (MooEditFileMgr *mgr,
GtkWidget *dialog)
{
GtkWidget *alignment;
GtkWidget *hbox;
GtkWidget *label;
GtkWidget *combo;
GtkWidget *entry;
Filter *filter;
alignment = gtk_alignment_new (1, 0.5, 0, 1);
gtk_widget_show (alignment);
gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog), alignment);
hbox = gtk_hbox_new (FALSE, 0);
gtk_widget_show (hbox);
gtk_container_add (GTK_CONTAINER (alignment), hbox);
label = gtk_label_new ("Filter:");
gtk_widget_show (label);
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
combo = gtk_combo_box_entry_new ();
moo_edit_file_mgr_init_filter_combo (mgr, GTK_COMBO_BOX (combo));
gtk_widget_show (combo);
gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0);
entry = GTK_WIDGET (GTK_BIN(combo)->child);
g_signal_connect (entry, "activate",
G_CALLBACK (filter_entry_activated),
dialog);
g_signal_connect (combo, "changed",
G_CALLBACK (combo_changed),
dialog);
g_object_set_data (G_OBJECT (dialog), "filter-combo", combo);
g_object_set_data (G_OBJECT (dialog), "filter-entry", entry);
g_object_set_data (G_OBJECT (dialog), "file-mgr", mgr);
filter = mgr_get_last_filter (mgr);
if (filter)
mgr_set_filter (mgr, GTK_FILE_CHOOSER (dialog), filter);
}
void moo_edit_file_mgr_init_filter_combo(MooEditFileMgr *mgr,
GtkComboBox *combo)
{
g_return_if_fail (MOO_IS_EDIT_FILE_MGR (mgr));
g_return_if_fail (GTK_IS_COMBO_BOX (combo));
gtk_combo_box_set_model (combo, GTK_TREE_MODEL (mgr->priv->filters));
gtk_combo_box_set_row_separator_func (combo, row_is_separator,
NULL, NULL);
gtk_combo_box_entry_set_text_column (GTK_COMBO_BOX_ENTRY (combo),
COLUMN_DESCRIPTION);
}
GSList *moo_edit_file_mgr_open_dialog (MooEditFileMgr *mgr,
GtkWidget *parent)
{
GSList *filenames;
GSList *result = NULL;
const char *title = "Open File";
const char *start = NULL;
GtkWidget *dialog;
g_return_val_if_fail (MOO_IS_EDIT_FILE_MGR (mgr), NULL);
start = moo_prefs_get_string (moo_edit_setting (MOO_EDIT_PREFS_DIALOGS_OPEN));
dialog = moo_file_dialog_create (parent,
MOO_DIALOG_FILE_OPEN_EXISTING,
TRUE,
title, start);
setup_open_dialog (mgr, dialog);
moo_file_dialog_run (dialog);
filenames = moo_file_dialog_get_filenames (dialog);
if (filenames)
{
GSList *l;
char *new_start = g_path_get_dirname (filenames->data);
moo_prefs_set_string (moo_edit_setting (MOO_EDIT_PREFS_DIALOGS_OPEN), new_start);
g_free (new_start);
for (l = filenames; l != NULL; l = l->next)
{
result = g_slist_prepend (result, moo_edit_file_info_new (l->data, NULL));
g_free (l->data);
}
g_slist_free (filenames);
}
gtk_widget_destroy (dialog);
return g_slist_reverse (result);
}
MooEditFileInfo *moo_edit_file_mgr_save_as_dialog (G_GNUC_UNUSED MooEditFileMgr *mgr,
MooEdit *edit)
{
const char *title = "Save File";
const char *start = NULL;
const char *filename = NULL;
MooEditFileInfo *file = NULL;
GtkWidget *dialog;
start = moo_prefs_get_string (moo_edit_setting (MOO_EDIT_PREFS_DIALOGS_SAVE));
if (!start)
start = moo_prefs_get_string (moo_edit_setting (MOO_EDIT_PREFS_DIALOGS_OPEN));
dialog = moo_file_dialog_create (GTK_WIDGET (edit), MOO_DIALOG_FILE_SAVE,
FALSE, title, start);
moo_file_dialog_run (dialog);
filename = moo_file_dialog_get_filename (dialog);
if (filename)
{
char *new_start = g_path_get_dirname (filename);
moo_prefs_set_string (moo_edit_setting (MOO_EDIT_PREFS_DIALOGS_SAVE), new_start);
g_free (new_start);
file = moo_edit_file_info_new (filename, NULL);
}
gtk_widget_destroy (dialog);
return file;
}
static Filter *mgr_get_null_filter (MooEditFileMgr *mgr)
{
if (!mgr->priv->null_filter)
{
Filter *filter = filter_new ("All Files", "*", FALSE);
list_store_append_filter (mgr, filter);
mgr->priv->null_filter = filter;
}
return mgr->priv->null_filter;
}
GtkFileFilter *moo_edit_file_mgr_get_null_filter (MooEditFileMgr *mgr)
{
Filter *filter;
g_return_val_if_fail (MOO_IS_EDIT_FILE_MGR (mgr), NULL);
filter = mgr_get_null_filter (mgr);
return filter->filter;
}
static Filter *mgr_get_last_filter (MooEditFileMgr *mgr)
{
mgr_load_filter_prefs (mgr);
if (!mgr->priv->last_filter)
mgr->priv->last_filter = mgr_get_null_filter (mgr);
return mgr->priv->last_filter;
}
GtkFileFilter *moo_edit_file_mgr_get_last_filter (MooEditFileMgr *mgr)
{
Filter *filter;
g_return_val_if_fail (MOO_IS_EDIT_FILE_MGR (mgr), NULL);
filter = mgr_get_last_filter (mgr);
if (filter)
return filter->filter;
else
return NULL;
}
static void list_store_init (MooEditFileMgr *mgr)
{
mgr->priv->filters = gtk_list_store_new (NUM_COLUMNS,
G_TYPE_STRING,
G_TYPE_POINTER);
mgr_get_null_filter (mgr);
}
static gboolean filter_free_func (GtkTreeModel *model,
G_GNUC_UNUSED GtkTreePath *path,
GtkTreeIter *iter,
G_GNUC_UNUSED gpointer data)
{
Filter *filter;
gtk_tree_model_get (model, iter, COLUMN_FILTER, &filter, -1);
if (filter)
filter_free (filter);
return FALSE;
}
static void list_store_destroy (MooEditFileMgr *mgr)
{
gtk_tree_model_foreach (GTK_TREE_MODEL (mgr->priv->filters),
filter_free_func, NULL);
g_object_unref (mgr->priv->filters);
mgr->priv->filters = NULL;
mgr->priv->last_filter = NULL;
mgr->priv->null_filter = NULL;
}
static void list_store_append_filter (MooEditFileMgr *mgr,
Filter *filter)
{
GtkTreeIter iter;
gtk_list_store_append (mgr->priv->filters, &iter);
if (filter)
gtk_list_store_set (mgr->priv->filters, &iter,
COLUMN_DESCRIPTION, filter_get_description (filter),
COLUMN_FILTER, filter, -1);
}
#define NEGATE_CHAR '!'
#define GLOB_SEPARATOR ";"
static gboolean neg_filter_func (const GtkFileFilterInfo *filter_info,
Filter *filter)
{
return !gtk_file_filter_filter (filter->aux, filter_info);
}
static Filter *filter_new (const char *description,
const char *glob,
gboolean user)
{
Filter *filter;
char **globs, **p;
gboolean negative;
g_return_val_if_fail (description != NULL, NULL);
g_return_val_if_fail (glob != NULL && glob[0] != 0, NULL);
g_return_val_if_fail (glob[0] != NEGATE_CHAR || glob[1] != 0, NULL);
if (glob[0] == NEGATE_CHAR)
{
negative = TRUE;
globs = g_strsplit (glob + 1, GLOB_SEPARATOR, 0);
}
else
{
negative = FALSE;
globs = g_strsplit (glob, GLOB_SEPARATOR, 0);
}
g_return_val_if_fail (globs != NULL, NULL);
filter = g_new0 (Filter, 1);
filter->description = g_strdup (description);
filter->glob = g_strdup (glob);
filter->user = user;
filter->filter = gtk_file_filter_new ();
gtk_object_sink (gtk_object_ref (GTK_OBJECT (filter->filter)));
gtk_file_filter_set_name (filter->filter, description);
if (negative)
{
filter->aux = gtk_file_filter_new ();
gtk_object_sink (gtk_object_ref (GTK_OBJECT (filter->aux)));
for (p = globs; *p != NULL; p++)
gtk_file_filter_add_pattern (filter->aux, *p);
gtk_file_filter_add_custom (filter->filter,
gtk_file_filter_get_needed (filter->aux),
(GtkFileFilterFunc) neg_filter_func,
filter, NULL);
}
else
{
for (p = globs; *p != NULL; p++)
gtk_file_filter_add_pattern (filter->filter, *p);
}
g_strfreev (globs);
return filter;
}
static void filter_free (Filter *filter)
{
if (filter)
{
if (filter->filter)
g_object_unref (filter->filter);
filter->filter = NULL;
if (filter->aux)
g_object_unref (filter->aux);
filter->aux = NULL;
g_free (filter->description);
filter->description = NULL;
g_free (filter->glob);
filter->glob = NULL;
g_free (filter);
}
}
static GtkFileFilter *filter_get_gtk_filter (Filter *filter)
{
return filter->filter;
}
static const char *filter_get_glob (Filter *filter)
{
return filter->glob;
}
static const char *filter_get_description (Filter *filter)
{
return filter->description;
}
static void filter_entry_activated (GtkEntry *entry,
GtkFileChooser *dialog)
{
const char *text;
Filter *filter = NULL;
MooEditFileMgr *mgr;
mgr = g_object_get_data (G_OBJECT (dialog), "file-mgr");
g_return_if_fail (mgr != NULL);
text = gtk_entry_get_text (entry);
if (text && text[0])
filter = mgr_new_user_filter (mgr, text);
if (!filter)
filter = mgr_get_null_filter (mgr);
mgr_set_filter (mgr, dialog, filter);
}
static void combo_changed (GtkComboBox *combo,
GtkFileChooser *dialog)
{
GtkTreeIter iter;
Filter *filter;
MooEditFileMgr *mgr;
if (!gtk_combo_box_get_active_iter (combo, &iter))
return;
mgr = g_object_get_data (G_OBJECT (dialog), "file-mgr");
g_return_if_fail (mgr != NULL);
gtk_tree_model_get (GTK_TREE_MODEL (mgr->priv->filters), &iter,
COLUMN_FILTER, &filter, -1);
g_return_if_fail (filter != NULL);
mgr_set_filter (mgr, dialog, filter);
}
static void mgr_set_filter (MooEditFileMgr *mgr,
GtkFileChooser *dialog,
Filter *filter)
{
GtkEntry *entry = g_object_get_data (G_OBJECT (dialog),
"filter-entry");
gtk_entry_set_text (entry, filter_get_description (filter));
gtk_file_chooser_set_filter (dialog, filter_get_gtk_filter (filter));
mgr_set_last_filter (mgr, filter);
}
static void mgr_set_last_filter (MooEditFileMgr *mgr,
Filter *filter)
{
mgr->priv->last_filter = filter;
if (filter == mgr->priv->null_filter)
moo_prefs_set_string (moo_edit_setting (PREFS_LAST_FILTER),
NULL);
else
moo_prefs_set_string (moo_edit_setting (PREFS_LAST_FILTER),
filter_get_glob (filter));
}
typedef gboolean (*ListFoundFunc) (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data);
typedef struct {
GtkTreeIter *iter;
ListFoundFunc func;
gpointer user_data;
gboolean found;
} ListStoreFindData;
static gboolean list_store_find_check_func (GtkTreeModel *model,
G_GNUC_UNUSED GtkTreePath *path,
GtkTreeIter *iter,
ListStoreFindData *data)
{
if (data->func (model, iter, data->user_data))
{
data->found = TRUE;
if (data->iter)
*data->iter = *iter;
return TRUE;
}
else
{
return FALSE;
}
}
static gboolean list_store_find (GtkListStore *store,
GtkTreeIter *iter,
ListFoundFunc func,
gpointer user_data)
{
ListStoreFindData data = {iter, func, user_data, FALSE};
gtk_tree_model_foreach (GTK_TREE_MODEL (store),
(GtkTreeModelForeachFunc) list_store_find_check_func,
&data);
return data.found;
}
static gboolean check_filter_match (GtkTreeModel *model,
GtkTreeIter *iter,
const char *text)
{
Filter *filter = NULL;
gtk_tree_model_get (model, iter, COLUMN_FILTER, &filter, -1);
if (!filter)
return FALSE;
if (!strcmp (text, filter_get_description (filter)) ||
!strcmp (text, filter_get_glob (filter)))
return TRUE;
return FALSE;
}
static Filter *list_store_find_filter (MooEditFileMgr *mgr,
const char *text)
{
GtkTreeIter iter;
g_return_val_if_fail (text != NULL, NULL);
if (list_store_find (mgr->priv->filters, &iter,
(ListFoundFunc)check_filter_match,
(gpointer)text))
{
Filter *filter;
gtk_tree_model_get (GTK_TREE_MODEL (mgr->priv->filters),
&iter, COLUMN_FILTER, &filter, -1);
return filter;
}
else
{
return NULL;
}
}
static void mgr_load_filter_prefs (MooEditFileMgr *mgr)
{
guint i;
char *key;
char *glob;
Filter *filter;
for (i = 0; i < NUM_USER_FILTERS; ++i)
{
key = g_strdup_printf (MOO_EDIT_PREFS_PREFIX "/"
PREFS_FILTERS "/" PREFS_USER "%d", i);
glob = g_strdup (moo_prefs_get_string (key));
if (glob && glob[0])
mgr_new_user_filter (mgr, glob);
g_free (key);
g_free (glob);
}
glob = g_strdup (moo_prefs_get_string (moo_edit_setting (PREFS_LAST_FILTER)));
if (glob && glob[0])
{
filter = mgr_new_user_filter (mgr, glob);
if (filter)
mgr_set_last_filter (mgr, filter);
}
g_free (glob);
}
static void set_user_filter_prefs (guint num,
const char *val)
{
char *key = g_strdup_printf (MOO_EDIT_PREFS_PREFIX "/"
PREFS_FILTERS "/" PREFS_USER "%d", num);
moo_prefs_set_string (key, val);
g_free (key);
}
static void mgr_save_filter_prefs (MooEditFileMgr *mgr)
{
GtkTreeIter iter;
gboolean user_present;
guint i = 0;
user_present = list_store_find (mgr->priv->filters, &iter,
row_is_separator, NULL);
if (user_present)
{
while (i < NUM_USER_FILTERS &&
gtk_tree_model_iter_next (GTK_TREE_MODEL (mgr->priv->filters), &iter))
{
Filter *filter = NULL;
gtk_tree_model_get (GTK_TREE_MODEL (mgr->priv->filters), &iter,
COLUMN_FILTER, &filter, -1);
g_assert (filter != NULL);
set_user_filter_prefs (i, filter_get_glob (filter));
i++;
}
}
for ( ; i < NUM_USER_FILTERS; ++i)
set_user_filter_prefs (i, NULL);
}
static Filter *mgr_new_user_filter (MooEditFileMgr *mgr,
const char *glob)
{
Filter *filter;
g_return_val_if_fail (glob && glob[0], NULL);
filter = list_store_find_filter (mgr, glob);
if (filter && !filter->user)
return filter;
if (filter)
{
GtkTreeIter iter;
gboolean user_present;
user_present = list_store_find (mgr->priv->filters, &iter,
row_is_separator, NULL);
g_return_val_if_fail (user_present, filter);
g_return_val_if_fail (gtk_tree_model_iter_next
(GTK_TREE_MODEL (mgr->priv->filters), &iter), filter);
gtk_list_store_move_before (mgr->priv->filters, &iter, NULL);
}
else
{
filter = filter_new (glob, glob, TRUE);
if (filter)
{
GtkTreeIter iter;
gboolean user_present;
user_present = list_store_find (mgr->priv->filters, &iter,
row_is_separator, NULL);
if (!user_present)
gtk_list_store_append (mgr->priv->filters, &iter);
if (mgr->priv->num_user_filters == NUM_USER_FILTERS)
{
Filter *old = NULL;
--mgr->priv->num_user_filters;
if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (mgr->priv->filters),
&iter))
{
filter_free (filter);
g_return_val_if_reached (NULL);
}
gtk_tree_model_get (GTK_TREE_MODEL (mgr->priv->filters),
&iter, COLUMN_FILTER, &old, -1);
g_assert (old != NULL && old != mgr->priv->last_filter);
gtk_list_store_remove (mgr->priv->filters, &iter);
filter_free (old);
}
gtk_list_store_append (mgr->priv->filters, &iter);
gtk_list_store_set (mgr->priv->filters, &iter,
COLUMN_FILTER, filter,
COLUMN_DESCRIPTION, filter_get_description (filter),
-1);
++mgr->priv->num_user_filters;
}
}
mgr_save_filter_prefs (mgr);
return filter;
}
GtkFileFilter *moo_edit_file_mgr_get_filter (MooEditFileMgr *mgr,
GtkTreeIter *iter)
{
Filter *filter = NULL;
g_return_val_if_fail (MOO_IS_EDIT_FILE_MGR (mgr), NULL);
g_return_val_if_fail (iter != NULL, NULL);
gtk_tree_model_get (GTK_TREE_MODEL (mgr->priv->filters), iter,
COLUMN_FILTER, &filter, -1);
g_return_val_if_fail (filter != NULL, NULL);
return filter->filter;
}
GtkFileFilter *moo_edit_file_mgr_new_user_filter (MooEditFileMgr *mgr,
const char *text)
{
Filter *filter;
g_return_val_if_fail (MOO_IS_EDIT_FILE_MGR (mgr), NULL);
g_return_val_if_fail (text && text[0], NULL);
filter = mgr_new_user_filter (mgr, text);
if (filter)
return filter->filter;
else
return NULL;
}
/************************************************************************/
/* Recent files
*/
struct _RecentStuff {
GSList *files; /* RecentEntry* */
GSList *menus; /* RecentMenu* */
gboolean prefs_loaded;
GtkTooltips *tooltips;
gboolean display_full_name;
};
typedef struct {
MooEditFileInfo *info;
} RecentEntry;
typedef struct {
GtkMenuItem *parent;
GtkMenu *menu;
gpointer data;
GSList *items; /* GtkMenuItem*, synchronized with recent files list */
} RecentMenu;
static RecentEntry *recent_entry_new (const MooEditFileInfo *info);
static void recent_entry_free (RecentEntry *entry);
static RecentEntry *recent_list_find (MooEditFileMgr *mgr,
MooEditFileInfo *info);
static void recent_list_delete_tail(MooEditFileMgr *mgr);
static GtkWidget *recent_menu_item_new (MooEditFileMgr *mgr,
const MooEditFileInfo *info,
gpointer data);
static void mgr_add_recent (MooEditFileMgr *mgr,
const MooEditFileInfo *info);
static void mgr_reorder_recent (MooEditFileMgr *mgr,
RecentEntry *top);
static void mgr_load_recent (MooEditFileMgr *mgr);
static void mgr_save_recent (MooEditFileMgr *mgr);
static void save_recent (guint n,
RecentEntry *entry);
static RecentEntry *load_recent (guint n);
static void menu_item_activated (GtkMenuItem *item,
MooEditFileMgr *mgr);
static void menu_destroyed (GtkMenuItem *parent,
MooEditFileMgr *mgr);
static void mgr_recent_stuff_init (MooEditFileMgr *mgr)
{
mgr->priv->recent = g_new0 (RecentStuff, 1);
mgr->priv->recent->tooltips = gtk_tooltips_new ();
gtk_object_sink (gtk_object_ref (GTK_OBJECT (mgr->priv->recent->tooltips)));
mgr->priv->recent->display_full_name = FALSE;
}
static void mgr_recent_stuff_free (MooEditFileMgr *mgr)
{
GSList *l;
for (l = mgr->priv->recent->files; l != NULL; l = l->next)
recent_entry_free (l->data);
g_slist_free (mgr->priv->recent->files);
mgr->priv->recent->files = NULL;
g_object_unref (mgr->priv->recent->tooltips);
mgr->priv->recent->tooltips = NULL;
for (l = mgr->priv->recent->menus; l != NULL; l = l->next)
{
RecentMenu *menu = l->data;
GSList *i;
g_signal_handlers_disconnect_by_func (menu->parent,
(gpointer) menu_destroyed,
mgr);
for (i = menu->items; i != NULL; i = i->next)
{
if (GTK_IS_MENU_ITEM (i->data))
g_signal_handlers_disconnect_by_func (i->data,
(gpointer) menu_item_activated, mgr);
}
g_slist_free (menu->items);
menu->items = NULL;
g_free (menu);
}
}
void moo_edit_file_mgr_add_recent (MooEditFileMgr *mgr,
MooEditFileInfo *info)
{
RecentEntry *entry;
g_return_if_fail (MOO_IS_EDIT_FILE_MGR (mgr));
g_return_if_fail (info != NULL);
g_return_if_fail (info->filename != NULL);
mgr_load_recent (mgr);
entry = recent_list_find (mgr, info);
if (!entry)
mgr_add_recent (mgr, info);
else
mgr_reorder_recent (mgr, entry);
mgr_save_recent (mgr);
}
static void mgr_add_recent (MooEditFileMgr *mgr,
const MooEditFileInfo *info)
{
GSList *l;
RecentEntry *entry = recent_entry_new (info);
gboolean delete_last = FALSE;
if (g_slist_length (mgr->priv->recent->files) == NUM_RECENT_FILES)
delete_last = TRUE;
mgr->priv->recent->files =
g_slist_prepend (mgr->priv->recent->files, entry);
if (delete_last)
recent_list_delete_tail (mgr);
for (l = mgr->priv->recent->menus; l != NULL; l = l->next)
{
RecentMenu *menu;
GtkWidget *item;
menu = l->data;
item = recent_menu_item_new (mgr, info, menu->data);
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu->menu), item);
menu->items = g_slist_prepend (menu->items, item);
if (delete_last)
{
GSList *tail;
tail = g_slist_last (menu->items);
g_signal_handlers_disconnect_by_func (tail->data,
(gpointer) menu_item_activated, mgr);
gtk_container_remove (GTK_CONTAINER (menu->menu), tail->data);
menu->items = g_slist_delete_link (menu->items, tail);
}
}
}
static void mgr_reorder_recent (MooEditFileMgr *mgr,
RecentEntry *top)
{
int index;
GSList *l;
index = g_slist_index (mgr->priv->recent->files, top);
g_return_if_fail (index >= 0);
if (index == 0)
return;
mgr->priv->recent->files =
g_slist_delete_link (mgr->priv->recent->files,
g_slist_nth (mgr->priv->recent->files, index));
mgr->priv->recent->files =
g_slist_prepend (mgr->priv->recent->files, top);
for (l = mgr->priv->recent->menus; l != NULL; l = l->next)
{
RecentMenu *menu;
GtkWidget *item;
menu = l->data;
item = g_slist_nth_data (menu->items, index);
g_assert (item != NULL);
g_object_ref (item);
gtk_container_remove (GTK_CONTAINER (menu->menu), item);
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu->menu), item);
g_object_unref (item);
menu->items = g_slist_delete_link (menu->items,
g_slist_nth (menu->items, index));
menu->items = g_slist_prepend (menu->items, item);
}
}
static void mgr_save_recent (MooEditFileMgr *mgr)
{
guint i, num;
num = g_slist_length (mgr->priv->recent->files);
for (i = 0; i < num; ++i)
save_recent (i, g_slist_nth_data (mgr->priv->recent->files, i));
for (; i < NUM_RECENT_FILES; ++i)
save_recent (i, NULL);
}
static void mgr_load_recent (MooEditFileMgr *mgr)
{
guint i;
if (mgr->priv->recent->prefs_loaded)
return;
mgr->priv->recent->prefs_loaded = TRUE;
mgr->priv->recent->display_full_name =
moo_prefs_get_bool (moo_edit_setting (PREFS_SHOW_FULL_NAME));
for (i = 0; i < NUM_RECENT_FILES; ++i)
{
RecentEntry *entry = load_recent (i);
if (entry)
mgr->priv->recent->files =
g_slist_append (mgr->priv->recent->files, entry);
}
}
static gboolean file_cmp (RecentEntry *entry,
MooEditFileInfo *info)
{
return strcmp (entry->info->filename, info->filename);
}
static RecentEntry *recent_list_find (MooEditFileMgr *mgr,
MooEditFileInfo *info)
{
GSList *l = g_slist_find_custom (mgr->priv->recent->files,
info, (GCompareFunc) file_cmp);
if (l)
return l->data;
else
return NULL;
}
static void recent_list_delete_tail(MooEditFileMgr *mgr)
{
GSList *l = g_slist_last (mgr->priv->recent->files);
g_return_if_fail (l != NULL);
recent_entry_free (l->data);
g_slist_delete_link (mgr->priv->recent->files, l);
}
static GtkWidget *recent_menu_item_new (MooEditFileMgr *mgr,
const MooEditFileInfo *info,
gpointer data)
{
GtkWidget *item;
if (mgr->priv->recent->display_full_name)
{
char *display_name = _moo_edit_filename_to_utf8 (info->filename);
item = gtk_menu_item_new_with_label (display_name);
g_free (display_name);
}
else
{
char *basename = g_path_get_basename (info->filename);
char *display_basename = _moo_edit_filename_to_utf8 (basename);
char *display_fullname = _moo_edit_filename_to_utf8 (info->filename);
item = gtk_menu_item_new_with_label (display_basename);
gtk_tooltips_set_tip (mgr->priv->recent->tooltips, item,
display_fullname, NULL);
g_free (display_basename);
g_free (display_fullname);
g_free (basename);
}
gtk_widget_show (item);
g_signal_connect (item, "activate",
G_CALLBACK (menu_item_activated), mgr);
g_object_set_data_full (G_OBJECT (item), "moo-edit-file-mgr-recent-file",
moo_edit_file_info_copy (info),
(GDestroyNotify) moo_edit_file_info_free);
g_object_set_data (G_OBJECT (item),
"moo-edit-file-mgr-recent-file-data", data);
return item;
}
static void menu_item_activated (GtkMenuItem *item,
MooEditFileMgr *mgr)
{
MooEditFileInfo *info = g_object_get_data (G_OBJECT (item),
"moo-edit-file-mgr-recent-file");
gpointer data = g_object_get_data (G_OBJECT (item),
"moo-edit-file-mgr-recent-file-data");
g_return_if_fail (info != NULL);
g_signal_emit (mgr, signals[OPEN_RECENT], 0, info, data, item);
}
static RecentEntry *recent_entry_new (const MooEditFileInfo *info)
{
RecentEntry *entry = g_new0 (RecentEntry, 1);
entry->info = moo_edit_file_info_copy (info);
return entry;
}
static void recent_entry_free (RecentEntry *entry)
{
if (entry)
{
moo_edit_file_info_free (entry->info);
g_free (entry);
}
}
#define RECENT_PREFIX MOO_EDIT_PREFS_PREFIX "/" PREFS_RECENT "/" PREFS_ENTRY
#define RECENT_FILENAME RECENT_PREFIX "%d/filename"
#define RECENT_ENCODING RECENT_PREFIX "%d/encoding"
static void save_recent (guint n,
RecentEntry *entry)
{
char *key;
const char *filename, *encoding;
filename = entry ? entry->info->filename : NULL;
encoding = entry ? entry->info->encoding : NULL;
key = g_strdup_printf (RECENT_FILENAME, n);
moo_prefs_set_string (key, filename);
g_free (key);
key = g_strdup_printf (RECENT_ENCODING, n);
moo_prefs_set_string (key, encoding);
g_free (key);
}
static RecentEntry *load_recent (guint n)
{
RecentEntry *entry = NULL;
char *filename, *encoding;
char *key;
key = g_strdup_printf (RECENT_FILENAME, n);
filename = g_strdup (moo_prefs_get_string (key));
g_free (key);
key = g_strdup_printf (RECENT_ENCODING, n);
encoding = g_strdup (moo_prefs_get_string (key));
g_free (key);
if (filename)
{
MooEditFileInfo *info = moo_edit_file_info_new (filename, encoding);
entry = recent_entry_new (info);
moo_edit_file_info_free (info);
}
g_free (filename);
g_free (encoding);
return entry;
}
#undef RECENT_PREFIX
#undef RECENT_FILENAME
#undef RECENT_ENCODING
GtkMenuItem *moo_edit_file_mgr_create_recent_files_menu (MooEditFileMgr *mgr,
gpointer data)
{
RecentMenu *menu;
GSList *l;
mgr_load_recent (mgr);
menu = g_new0 (RecentMenu, 1);
menu->data = data;
menu->parent = GTK_MENU_ITEM (gtk_image_menu_item_new_with_label ("Open Recent"));
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu->parent),
gtk_image_new_from_stock (GTK_STOCK_OPEN,
GTK_ICON_SIZE_MENU));
menu->menu = GTK_MENU (gtk_menu_new ());
gtk_menu_item_set_submenu (menu->parent, GTK_WIDGET (menu->menu));
gtk_widget_show (GTK_WIDGET (menu->parent));
gtk_widget_show (GTK_WIDGET (menu->menu));
g_signal_connect (menu->parent, "destroy",
G_CALLBACK (menu_destroyed), mgr);
for (l = mgr->priv->recent->files; l != NULL; l = l->next)
{
RecentEntry *entry = l->data;
GtkWidget *item = recent_menu_item_new (mgr, entry->info, data);
gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), item);
menu->items = g_slist_append (menu->items, item);
}
mgr->priv->recent->menus = g_slist_prepend (mgr->priv->recent->menus, menu);
return menu->parent;
}
static gboolean menu_cmp (RecentMenu *menu,
GtkMenuItem *parent)
{
return menu->parent != parent;
}
static void menu_destroyed (GtkMenuItem *parent,
MooEditFileMgr *mgr)
{
RecentMenu *menu;
GSList *l = g_slist_find_custom (mgr->priv->recent->menus,
parent,
(GCompareFunc) menu_cmp);
g_return_if_fail (l != NULL);
menu = l->data;
g_slist_free (menu->items);
g_free (menu);
mgr->priv->recent->menus =
g_slist_remove (mgr->priv->recent->menus, menu);
}