medit/moo/mooedit/mooeditfilemgr.c

715 lines
21 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.
*/
#include "mooedit/mooeditfilemgr.h"
#include "mooedit/mooeditprefs.h"
#include "mooutils/moodialogs.h"
#include <string.h>
#define PREFS_FILTERS "filters"
#define PREFS_LAST_FILTER PREFS_FILTERS "/last"
#define PREFS_USER "user"
#define NUM_USER_FILTERS 3
typedef struct {
MooEditFileInfo *info;
} RecentEntry;
static RecentEntry *recent_entry_new (const char *filename);
static RecentEntry *recent_entry_copy (RecentEntry *entry);
static void recent_entry_free (RecentEntry *entry);
typedef struct {
GtkFileFilter *filter;
GtkFileFilter *aux;
char *description;
char *glob;
} Filter;
static Filter *filter_new (const char *description,
const char *glob);
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);
struct _MooEditFileMgrPrivate {
GSList *recent_files; /* RecentEntry* */
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_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);
static void list_store_add_user (MooEditFileMgr *mgr,
Filter *filter);
/* MOO_TYPE_EDIT_FILE_MGR */
G_DEFINE_TYPE (MooEditFileMgr, moo_edit_file_mgr, G_TYPE_OBJECT)
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;
}
static void moo_edit_file_mgr_init (MooEditFileMgr *mgr)
{
mgr->priv = g_new0 (MooEditFileMgrPrivate, 1);
list_store_init (mgr);
}
static void moo_edit_file_mgr_finalize (GObject *object)
{
MooEditFileMgr *mgr = MOO_EDIT_FILE_MGR (object);
list_store_destroy (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_with_model (GTK_TREE_MODEL (mgr->priv->filters),
COLUMN_DESCRIPTION);
gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo),
row_is_separator,
NULL, NULL);
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);
}
MooEditFileInfo *moo_edit_file_mgr_open_dialog (MooEditFileMgr *mgr,
GtkWidget *parent)
{
const char *filename;
const char *title = "Open File";
const char *start = NULL;
MooEditFileInfo *file = 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,
title, start);
setup_open_dialog (mgr, dialog);
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_OPEN), new_start);
g_free (new_start);
file = moo_edit_file_info_new (filename, NULL);
}
gtk_widget_destroy (dialog);
return file;
}
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,
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", "*");
list_store_append_filter (mgr, filter);
mgr->priv->null_filter = filter;
}
return mgr->priv->null_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;
}
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)
{
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->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;
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)
{
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);
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;
}