diff options
Diffstat (limited to 'src/terminal-app.c')
-rw-r--r-- | src/terminal-app.c | 2116 |
1 files changed, 2116 insertions, 0 deletions
diff --git a/src/terminal-app.c b/src/terminal-app.c new file mode 100644 index 0000000..aa3ede7 --- /dev/null +++ b/src/terminal-app.c @@ -0,0 +1,2116 @@ +/* + * Copyright © 2001, 2002 Havoc Pennington + * Copyright © 2002 Red Hat, Inc. + * Copyright © 2002 Sun Microsystems + * Copyright © 2003 Mariano Suarez-Alvarez + * Copyright © 2008 Christian Persch + * + * Mate-terminal 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 3 of the License, or + * (at your option) any later version. + * + * Mate-terminal 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, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <errno.h> + +#include <glib.h> + +#include "terminal-intl.h" + +#include "terminal-debug.h" +#include "terminal-app.h" +#include "terminal-accels.h" +#include "terminal-screen.h" +#include "terminal-screen-container.h" +#include "terminal-window.h" +#include "terminal-util.h" +#include "profile-editor.h" +#include "terminal-encoding.h" +#include <mateconf/mateconf-client.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> + +#ifdef WITH_SMCLIENT +#include "eggsmclient.h" +#ifdef GDK_WINDOWING_X11 +#include "eggdesktopfile.h" +#endif +#endif + +#define FALLBACK_PROFILE_ID "Default" + +/* Settings storage works as follows: + * /apps/mate-terminal/global/ + * /apps/mate-terminal/profiles/Foo/ + * + * It's somewhat tricky to manage the profiles/ dir since we need to track + * the list of profiles, but mateconf doesn't have a concept of notifying that + * a directory has appeared or disappeared. + * + * Session state is stored entirely in the RestartCommand command line. + * + * The number one rule: all stored information is EITHER per-session, + * per-profile, or set from a command line option. THERE CAN BE NO + * OVERLAP. The UI and implementation totally break if you overlap + * these categories. See mate-terminal 1.x for why. + * + * Don't use this code as an example of how to use MateConf - it's hugely + * overcomplicated due to the profiles stuff. Most apps should not + * have to do scary things of this nature, and should not have + * a profiles feature. + * + */ + +struct _TerminalAppClass { + GObjectClass parent_class; + + void (* quit) (TerminalApp *app); + void (* profile_list_changed) (TerminalApp *app); + void (* encoding_list_changed) (TerminalApp *app); +}; + +struct _TerminalApp +{ + GObject parent_instance; + + GList *windows; + GtkWidget *new_profile_dialog; + GtkWidget *manage_profiles_dialog; + GtkWidget *manage_profiles_list; + GtkWidget *manage_profiles_new_button; + GtkWidget *manage_profiles_edit_button; + GtkWidget *manage_profiles_delete_button; + GtkWidget *manage_profiles_default_menu; + + MateConfClient *conf; + guint profile_list_notify_id; + guint default_profile_notify_id; + guint encoding_list_notify_id; + guint system_font_notify_id; + guint enable_mnemonics_notify_id; + guint enable_menu_accels_notify_id; + + GHashTable *profiles; + char* default_profile_id; + TerminalProfile *default_profile; + gboolean default_profile_locked; + + GHashTable *encodings; + gboolean encodings_locked; + + PangoFontDescription *system_font_desc; + gboolean enable_mnemonics; + gboolean enable_menu_accels; +}; + +enum +{ + PROP_0, + PROP_DEFAULT_PROFILE, + PROP_ENABLE_MENU_BAR_ACCEL, + PROP_ENABLE_MNEMONICS, + PROP_SYSTEM_FONT, +}; + +enum +{ + QUIT, + PROFILE_LIST_CHANGED, + ENCODING_LIST_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +enum +{ + COL_PROFILE, + NUM_COLUMNS +}; + +enum +{ + SOURCE_DEFAULT = 0, + SOURCE_SESSION = 1 +}; + +static TerminalApp *global_app = NULL; + +/* Evil hack alert: this is exported from libmateconf-2 but not in a public header */ +extern gboolean mateconf_spawn_daemon(GError** err); + +#define MONOSPACE_FONT_DIR "/desktop/mate/interface" +#define MONOSPACE_FONT_KEY MONOSPACE_FONT_DIR "/monospace_font_name" +#define DEFAULT_MONOSPACE_FONT ("Monospace 10") + +#define ENABLE_MNEMONICS_KEY CONF_GLOBAL_PREFIX "/use_mnemonics" +#define DEFAULT_ENABLE_MNEMONICS (TRUE) + +#define ENABLE_MENU_BAR_ACCEL_KEY CONF_GLOBAL_PREFIX"/use_menu_accelerators" +#define DEFAULT_ENABLE_MENU_BAR_ACCEL (TRUE) + +#define PROFILE_LIST_KEY CONF_GLOBAL_PREFIX "/profile_list" +#define DEFAULT_PROFILE_KEY CONF_GLOBAL_PREFIX "/default_profile" + +#define ENCODING_LIST_KEY CONF_GLOBAL_PREFIX "/active_encodings" + +/* Helper functions */ + +static GdkScreen* +terminal_app_get_screen_by_display_name (const char *display_name, + int screen_number) +{ + GdkDisplay *display = NULL; + GdkScreen *screen = NULL; + + /* --screen=screen_number overrides --display */ + + if (display_name == NULL) + display = gdk_display_get_default (); + else + { + GSList *displays, *l; + const char *period; + + period = strrchr (display_name, '.'); + if (period) + { + gulong n; + char *end; + + errno = 0; + end = NULL; + n = g_ascii_strtoull (period + 1, &end, 0); + if (errno == 0 && (period + 1) != end) + screen_number = n; + } + + displays = gdk_display_manager_list_displays (gdk_display_manager_get ()); + for (l = displays; l != NULL; l = l->next) + { + GdkDisplay *disp = l->data; + + /* compare without the screen number part, if present */ + if ((period && strncmp (gdk_display_get_name (disp), display_name, period - display_name) == 0) || + (period == NULL && strcmp (gdk_display_get_name (disp), display_name) == 0)) + { + display = disp; + break; + } + } + g_slist_free (displays); + + if (display == NULL) + display = gdk_display_open (display_name); /* FIXME we never close displays */ + } + + if (display == NULL) + return NULL; + if (screen_number >= 0) + screen = gdk_display_get_screen (display, screen_number); + if (screen == NULL) + screen = gdk_display_get_default_screen (display); + + return screen; +} + +/* Menubar mnemonics settings handling */ + +static int +profiles_alphabetic_cmp (gconstpointer pa, + gconstpointer pb) +{ + TerminalProfile *a = (TerminalProfile *) pa; + TerminalProfile *b = (TerminalProfile *) pb; + int result; + + result = g_utf8_collate (terminal_profile_get_property_string (a, TERMINAL_PROFILE_VISIBLE_NAME), + terminal_profile_get_property_string (b, TERMINAL_PROFILE_VISIBLE_NAME)); + if (result == 0) + result = strcmp (terminal_profile_get_property_string (a, TERMINAL_PROFILE_NAME), + terminal_profile_get_property_string (b, TERMINAL_PROFILE_NAME)); + + return result; +} + +typedef struct +{ + TerminalProfile *result; + const char *target; +} LookupInfo; + +static void +profiles_lookup_by_visible_name_foreach (gpointer key, + gpointer value, + gpointer data) +{ + LookupInfo *info = data; + const char *name; + + name = terminal_profile_get_property_string (value, TERMINAL_PROFILE_VISIBLE_NAME); + if (name && strcmp (info->target, name) == 0) + info->result = value; +} + +static void +terminal_window_destroyed (TerminalWindow *window, + TerminalApp *app) +{ + app->windows = g_list_remove (app->windows, window); + + if (app->windows == NULL) + g_signal_emit (app, signals[QUIT], 0); +} + +static TerminalProfile * +terminal_app_create_profile (TerminalApp *app, + const char *name) +{ + TerminalProfile *profile; + + g_assert (terminal_app_get_profile_by_name (app, name) == NULL); + + profile = _terminal_profile_new (name); + + g_hash_table_insert (app->profiles, + g_strdup (terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME)), + profile /* adopts the refcount */); + + if (app->default_profile == NULL && + app->default_profile_id != NULL && + strcmp (app->default_profile_id, + terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME)) == 0) + { + /* We are the default profile */ + app->default_profile = profile; + g_object_notify (G_OBJECT (app), TERMINAL_APP_DEFAULT_PROFILE); + } + + return profile; +} + +static void +terminal_app_delete_profile (TerminalApp *app, + TerminalProfile *profile) +{ + GHashTableIter iter; + GSList *name_list; + const char *name, *profile_name; + char *mateconf_dir; + GError *error = NULL; + const char **nameptr = &name; + + profile_name = terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME); + mateconf_dir = mateconf_concat_dir_and_key (CONF_PREFIX "/profiles", profile_name); + + name_list = NULL; + g_hash_table_iter_init (&iter, app->profiles); + while (g_hash_table_iter_next (&iter, (gpointer *) nameptr, NULL)) + { + if (strcmp (name, profile_name) == 0) + continue; + + name_list = g_slist_prepend (name_list, g_strdup (name)); + } + + mateconf_client_set_list (app->conf, + CONF_GLOBAL_PREFIX"/profile_list", + MATECONF_VALUE_STRING, + name_list, + NULL); + + g_slist_foreach (name_list, (GFunc) g_free, NULL); + g_slist_free (name_list); + + /* And remove the profile directory */ + if (!mateconf_client_recursive_unset (app->conf, mateconf_dir, MATECONF_UNSET_INCLUDING_SCHEMA_NAMES, &error)) + { + g_warning ("Failed to recursively unset %s: %s\n", mateconf_dir, error->message); + g_error_free (error); + } + + g_free (mateconf_dir); +} + +static void +terminal_app_profile_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + TerminalProfile *profile; + GValue value = { 0, }; + + gtk_tree_model_get (tree_model, iter, (int) COL_PROFILE, &profile, (int) -1); + + g_value_init (&value, G_TYPE_STRING); + g_object_get_property (G_OBJECT (profile), "visible-name", &value); + g_object_set_property (G_OBJECT (cell), "text", &value); + g_value_unset (&value); +} + +static int +terminal_app_profile_sort_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + TerminalProfile *profile_a, *profile_b; + int retval; + + gtk_tree_model_get (model, a, (int) COL_PROFILE, &profile_a, (int) -1); + gtk_tree_model_get (model, b, (int) COL_PROFILE, &profile_b, (int) -1); + + retval = profiles_alphabetic_cmp (profile_a, profile_b); + + g_object_unref (profile_a); + g_object_unref (profile_b); + + return retval; +} + +static /* ref */ GtkTreeModel * +terminal_app_get_profile_liststore (TerminalApp *app, + TerminalProfile *selected_profile, + GtkTreeIter *selected_profile_iter, + gboolean *selected_profile_iter_set) +{ + GtkListStore *store; + GtkTreeIter iter; + GList *profiles, *l; + TerminalProfile *default_profile; + + store = gtk_list_store_new (NUM_COLUMNS, TERMINAL_TYPE_PROFILE); + + *selected_profile_iter_set = FALSE; + + if (selected_profile && + _terminal_profile_get_forgotten (selected_profile)) + selected_profile = NULL; + + profiles = terminal_app_get_profile_list (app); + default_profile = terminal_app_get_default_profile (app); + + for (l = profiles; l != NULL; l = l->next) + { + TerminalProfile *profile = TERMINAL_PROFILE (l->data); + + gtk_list_store_insert_with_values (store, &iter, 0, + (int) COL_PROFILE, profile, + (int) -1); + + if (selected_profile_iter && profile == selected_profile) + { + *selected_profile_iter = iter; + *selected_profile_iter_set = TRUE; + } + } + g_list_free (profiles); + + /* Now turn on sorting */ + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), + COL_PROFILE, + terminal_app_profile_sort_func, + NULL, NULL); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + COL_PROFILE, GTK_SORT_ASCENDING); + + return GTK_TREE_MODEL (store); +} + +static /* ref */ TerminalProfile* +profile_combo_box_get_selected (GtkWidget *widget) +{ + GtkComboBox *combo = GTK_COMBO_BOX (widget); + TerminalProfile *profile = NULL; + GtkTreeIter iter; + + if (gtk_combo_box_get_active_iter (combo, &iter)) + gtk_tree_model_get (gtk_combo_box_get_model (combo), &iter, + (int) COL_PROFILE, &profile, (int) -1); + + return profile; +} + +static void +profile_combo_box_refill (TerminalApp *app, + GtkWidget *widget) +{ + GtkComboBox *combo = GTK_COMBO_BOX (widget); + GtkTreeIter iter; + gboolean iter_set; + TerminalProfile *selected_profile; + GtkTreeModel *model; + + selected_profile = profile_combo_box_get_selected (widget); + if (!selected_profile) + { + selected_profile = terminal_app_get_default_profile (app); + if (selected_profile) + g_object_ref (selected_profile); + } + + model = terminal_app_get_profile_liststore (app, + selected_profile, + &iter, + &iter_set); + gtk_combo_box_set_model (combo, model); + g_object_unref (model); + + if (iter_set) + gtk_combo_box_set_active_iter (combo, &iter); + + if (selected_profile) + g_object_unref (selected_profile); +} + +static GtkWidget* +profile_combo_box_new (TerminalApp *app) +{ + GtkWidget *combo; + GtkCellRenderer *renderer; + + combo = gtk_combo_box_new (); + terminal_util_set_atk_name_description (combo, NULL, _("Click button to choose profile")); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo), renderer, + (GtkCellLayoutDataFunc) terminal_app_profile_cell_data_func, + NULL, NULL); + + profile_combo_box_refill (app, combo); + g_signal_connect (app, "profile-list-changed", + G_CALLBACK (profile_combo_box_refill), combo); + + gtk_widget_show (combo); + return combo; +} + +static void +profile_combo_box_changed_cb (GtkWidget *widget, + TerminalApp *app) +{ + TerminalProfile *profile; + + profile = profile_combo_box_get_selected (widget); + if (!profile) + return; + + mateconf_client_set_string (app->conf, + CONF_GLOBAL_PREFIX "/default_profile", + terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME), + NULL); + + /* Even though the mateconf change notification does this, it happens too late. + * In some cases, the default profile changes twice in quick succession, + * and update_default_profile must be called in sync with those changes. + */ + app->default_profile = profile; + + g_object_notify (G_OBJECT (app), TERMINAL_APP_DEFAULT_PROFILE); + + g_object_unref (profile); +} + +static void +profile_list_treeview_refill (TerminalApp *app, + GtkWidget *widget) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GtkTreeIter iter; + gboolean iter_set; + GtkTreeSelection *selection; + GtkTreeModel *model; + TerminalProfile *selected_profile = NULL; + + model = gtk_tree_view_get_model (tree_view); + + selection = gtk_tree_view_get_selection (tree_view); + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + gtk_tree_model_get (model, &iter, (int) COL_PROFILE, &selected_profile, (int) -1); + + model = terminal_app_get_profile_liststore (terminal_app_get (), + selected_profile, + &iter, + &iter_set); + gtk_tree_view_set_model (tree_view, model); + g_object_unref (model); + + if (!iter_set) + iter_set = gtk_tree_model_get_iter_first (model, &iter); + + if (iter_set) + gtk_tree_selection_select_iter (selection, &iter); + + if (selected_profile) + g_object_unref (selected_profile); +} + +static GtkWidget* +profile_list_treeview_create (TerminalApp *app) +{ + GtkWidget *tree_view; + GtkTreeSelection *selection; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + tree_view = gtk_tree_view_new (); + terminal_util_set_atk_name_description (tree_view, _("Profile list"), NULL); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + gtk_tree_selection_set_mode (GTK_TREE_SELECTION (selection), + GTK_SELECTION_BROWSE); + + column = gtk_tree_view_column_new (); + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), renderer, TRUE); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer, + (GtkCellLayoutDataFunc) terminal_app_profile_cell_data_func, + NULL, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), + GTK_TREE_VIEW_COLUMN (column)); + + return tree_view; +} + +static void +profile_list_delete_confirm_response_cb (GtkWidget *dialog, + int response, + TerminalApp *app) +{ + TerminalProfile *profile; + + profile = TERMINAL_PROFILE (g_object_get_data (G_OBJECT (dialog), "profile")); + g_assert (profile != NULL); + + if (response == GTK_RESPONSE_ACCEPT) + terminal_app_delete_profile (app, profile); + + gtk_widget_destroy (dialog); +} + +static void +profile_list_delete_button_clicked_cb (GtkWidget *button, + GtkWidget *widget) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + TerminalApp *app = terminal_app_get (); + GtkTreeSelection *selection; + GtkWidget *dialog; + GtkTreeIter iter; + GtkTreeModel *model; + TerminalProfile *selected_profile; + GtkWidget *transient_parent; + + model = gtk_tree_view_get_model (tree_view); + selection = gtk_tree_view_get_selection (tree_view); + + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) + return; + + gtk_tree_model_get (model, &iter, (int) COL_PROFILE, &selected_profile, (int) -1); + + transient_parent = gtk_widget_get_toplevel (widget); + dialog = gtk_message_dialog_new (GTK_WINDOW (transient_parent), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _("Delete profile “%s”?"), + terminal_profile_get_property_string (selected_profile, TERMINAL_PROFILE_VISIBLE_NAME)); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, + GTK_RESPONSE_REJECT, + GTK_STOCK_DELETE, + GTK_RESPONSE_ACCEPT, + NULL); + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_ACCEPT, + GTK_RESPONSE_REJECT, + -1); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_ACCEPT); + + gtk_window_set_title (GTK_WINDOW (dialog), _("Delete Profile")); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + /* Transfer refcount of |selected_profile|, so no unref below */ + g_object_set_data_full (G_OBJECT (dialog), "profile", selected_profile, g_object_unref); + + g_signal_connect (dialog, "response", + G_CALLBACK (profile_list_delete_confirm_response_cb), + app); + + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +profile_list_new_button_clicked_cb (GtkWidget *button, + gpointer data) +{ + TerminalApp *app; + + app = terminal_app_get (); + terminal_app_new_profile (app, NULL, GTK_WINDOW (app->manage_profiles_dialog)); +} + +static void +profile_list_edit_button_clicked_cb (GtkWidget *button, + GtkWidget *widget) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GtkTreeSelection *selection; + GtkTreeIter iter; + GtkTreeModel *model; + TerminalProfile *selected_profile; + TerminalApp *app; + + app = terminal_app_get (); + + model = gtk_tree_view_get_model (tree_view); + selection = gtk_tree_view_get_selection (tree_view); + + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) + return; + + gtk_tree_model_get (model, &iter, (int) COL_PROFILE, &selected_profile, (int) -1); + + terminal_app_edit_profile (app, selected_profile, + GTK_WINDOW (app->manage_profiles_dialog), + NULL); + g_object_unref (selected_profile); +} + +static void +profile_list_row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer data) +{ + GtkTreeIter iter; + GtkTreeModel *model; + TerminalProfile *selected_profile; + TerminalApp *app; + + app = terminal_app_get (); + + model = gtk_tree_view_get_model (tree_view); + + if (!gtk_tree_model_get_iter (model, &iter, path)) + return; + + gtk_tree_model_get (model, &iter, (int) COL_PROFILE, &selected_profile, (int) -1); + + terminal_app_edit_profile (app, selected_profile, + GTK_WINDOW (app->manage_profiles_dialog), + NULL); + g_object_unref (selected_profile); +} + +static GList* +find_profile_link (GList *profiles, + const char *name) +{ + GList *l; + + for (l = profiles; l != NULL; l = l->next) + { + const char *profile_name; + + profile_name = terminal_profile_get_property_string (TERMINAL_PROFILE (l->data), TERMINAL_PROFILE_NAME); + if (profile_name && strcmp (profile_name, name) == 0) + break; + } + + return l; +} + +static void +terminal_app_profile_list_notify_cb (MateConfClient *conf, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + TerminalApp *app = TERMINAL_APP (user_data); + GObject *object = G_OBJECT (app); + MateConfValue *val; + GSList *value_list, *sl; + GList *profiles_to_delete, *l; + gboolean need_new_default; + TerminalProfile *fallback; + guint count; + + g_object_freeze_notify (object); + + profiles_to_delete = terminal_app_get_profile_list (app); + + val = mateconf_entry_get_value (entry); + if (val == NULL || + val->type != MATECONF_VALUE_LIST || + mateconf_value_get_list_type (val) != MATECONF_VALUE_STRING) + goto ensure_one_profile; + + value_list = mateconf_value_get_list (val); + + /* Add any new ones */ + for (sl = value_list; sl != NULL; sl = sl->next) + { + MateConfValue *listvalue = (MateConfValue *) (sl->data); + const char *profile_name; + GList *link; + + profile_name = mateconf_value_get_string (listvalue); + if (!profile_name) + continue; + + link = find_profile_link (profiles_to_delete, profile_name); + if (link) + /* make profiles_to_delete point to profiles we didn't find in the list */ + profiles_to_delete = g_list_delete_link (profiles_to_delete, link); + else + terminal_app_create_profile (app, profile_name); + } + +ensure_one_profile: + + fallback = NULL; + count = g_hash_table_size (app->profiles); + if (count == 0 || count <= g_list_length (profiles_to_delete)) + { + /* We are going to run out, so create the fallback + * to be sure we always have one. Must be done + * here before we emit "forgotten" signals so that + * screens have a profile to fall back to. + * + * If the profile with the FALLBACK_ID already exists, + * we aren't allowed to delete it, unless at least one + * other profile will still exist. And if you delete + * all profiles, the FALLBACK_ID profile returns as + * the living dead. + */ + fallback = terminal_app_get_profile_by_name (app, FALLBACK_PROFILE_ID); + if (fallback == NULL) + fallback = terminal_app_create_profile (app, FALLBACK_PROFILE_ID); + g_assert (fallback != NULL); + } + + /* Forget no-longer-existing profiles */ + need_new_default = FALSE; + for (l = profiles_to_delete; l != NULL; l = l->next) + { + TerminalProfile *profile = TERMINAL_PROFILE (l->data); + const char *name; + + name = terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME); + if (strcmp (name, FALLBACK_PROFILE_ID) == 0) + continue; + + if (profile == app->default_profile) + { + app->default_profile = NULL; + need_new_default = TRUE; + } + + _terminal_profile_forget (profile); + g_hash_table_remove (app->profiles, name); + + /* |profile| possibly isn't dead yet since the profiles dialogue's tree model holds a ref too... */ + } + g_list_free (profiles_to_delete); + + if (need_new_default) + { + TerminalProfile *new_default; + TerminalProfile **new_default_ptr = &new_default; + + new_default = terminal_app_get_profile_by_name (app, FALLBACK_PROFILE_ID); + if (new_default == NULL) + { + GHashTableIter iter; + + g_hash_table_iter_init (&iter, app->profiles); + if (!g_hash_table_iter_next (&iter, NULL, (gpointer *) new_default_ptr)) + /* shouldn't really happen ever, but just to be safe */ + new_default = terminal_app_create_profile (app, FALLBACK_PROFILE_ID); + } + g_assert (new_default != NULL); + + app->default_profile = new_default; + + g_object_notify (object, TERMINAL_APP_DEFAULT_PROFILE); + } + + g_assert (g_hash_table_size (app->profiles) > 0); + + g_signal_emit (app, signals[PROFILE_LIST_CHANGED], 0); + + g_object_thaw_notify (object); +} + +static void +terminal_app_default_profile_notify_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + TerminalApp *app = TERMINAL_APP (user_data); + MateConfValue *val; + const char *name = NULL; + + app->default_profile_locked = !mateconf_entry_get_is_writable (entry); + + val = mateconf_entry_get_value (entry); + if (val != NULL && + val->type == MATECONF_VALUE_STRING) + name = mateconf_value_get_string (val); + if (!name || !name[0]) + name = FALLBACK_PROFILE_ID; + g_assert (name != NULL); + + g_free (app->default_profile_id); + app->default_profile_id = g_strdup (name); + + app->default_profile = terminal_app_get_profile_by_name (app, name); + + g_object_notify (G_OBJECT (app), TERMINAL_APP_DEFAULT_PROFILE); +} + +static int +compare_encodings (TerminalEncoding *a, + TerminalEncoding *b) +{ + return g_utf8_collate (a->name, b->name); +} + +static void +encoding_mark_active (gpointer key, + gpointer value, + gpointer data) +{ + TerminalEncoding *encoding = (TerminalEncoding *) value; + guint active = GPOINTER_TO_UINT (data); + + encoding->is_active = active; +} + +static void +terminal_app_encoding_list_notify_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + TerminalApp *app = TERMINAL_APP (user_data); + MateConfValue *val; + GSList *strings, *tmp; + TerminalEncoding *encoding; + const char *charset; + + app->encodings_locked = !mateconf_entry_get_is_writable (entry); + + /* Mark all as non-active, then re-enable the active ones */ + g_hash_table_foreach (app->encodings, (GHFunc) encoding_mark_active, GUINT_TO_POINTER (FALSE)); + + /* First add the locale's charset */ + encoding = g_hash_table_lookup (app->encodings, "current"); + g_assert (encoding); + if (terminal_encoding_is_valid (encoding)) + encoding->is_active = TRUE; + + /* Also always make UTF-8 available */ + encoding = g_hash_table_lookup (app->encodings, "UTF-8"); + g_assert (encoding); + if (terminal_encoding_is_valid (encoding)) + encoding->is_active = TRUE; + + val = mateconf_entry_get_value (entry); + if (val != NULL && + val->type == MATECONF_VALUE_LIST && + mateconf_value_get_list_type (val) == MATECONF_VALUE_STRING) + strings = mateconf_value_get_list (val); + else + strings = NULL; + + for (tmp = strings; tmp != NULL; tmp = tmp->next) + { + MateConfValue *v = (MateConfValue *) tmp->data; + + charset = mateconf_value_get_string (v); + if (!charset) + continue; + + encoding = terminal_app_ensure_encoding (app, charset); + if (!terminal_encoding_is_valid (encoding)) + continue; + + encoding->is_active = TRUE; + } + + g_signal_emit (app, signals[ENCODING_LIST_CHANGED], 0); +} + +static void +terminal_app_system_font_notify_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + TerminalApp *app = TERMINAL_APP (user_data); + MateConfValue *mateconf_value; + const char *font = NULL; + PangoFontDescription *font_desc; + + if (strcmp (mateconf_entry_get_key (entry), MONOSPACE_FONT_KEY) != 0) + return; + + mateconf_value = mateconf_entry_get_value (entry); + if (mateconf_value && + mateconf_value->type == MATECONF_VALUE_STRING) + font = mateconf_value_get_string (mateconf_value); + if (!font) + font = DEFAULT_MONOSPACE_FONT; + g_assert (font != NULL); + + font_desc = pango_font_description_from_string (font); + if (app->system_font_desc && + pango_font_description_equal (app->system_font_desc, font_desc)) + { + pango_font_description_free (font_desc); + return; + } + + if (app->system_font_desc) + pango_font_description_free (app->system_font_desc); + + app->system_font_desc = font_desc; + + g_object_notify (G_OBJECT (app), TERMINAL_APP_SYSTEM_FONT); +} + +static void +terminal_app_enable_mnemonics_notify_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + TerminalApp *app = TERMINAL_APP (user_data); + MateConfValue *mateconf_value; + gboolean enable; + + if (strcmp (mateconf_entry_get_key (entry), ENABLE_MNEMONICS_KEY) != 0) + return; + + mateconf_value = mateconf_entry_get_value (entry); + if (mateconf_value && + mateconf_value->type == MATECONF_VALUE_BOOL) + enable = mateconf_value_get_bool (mateconf_value); + else + enable = TRUE; + + if (enable == app->enable_mnemonics) + return; + + app->enable_mnemonics = enable; + g_object_notify (G_OBJECT (app), TERMINAL_APP_ENABLE_MNEMONICS); +} + +static void +terminal_app_enable_menu_accels_notify_cb (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + TerminalApp *app = TERMINAL_APP (user_data); + MateConfValue *mateconf_value; + gboolean enable; + + if (strcmp (mateconf_entry_get_key (entry), ENABLE_MENU_BAR_ACCEL_KEY) != 0) + return; + + mateconf_value = mateconf_entry_get_value (entry); + if (mateconf_value && + mateconf_value->type == MATECONF_VALUE_BOOL) + enable = mateconf_value_get_bool (mateconf_value); + else + enable = TRUE; + + if (enable == app->enable_menu_accels) + return; + + app->enable_menu_accels = enable; + g_object_notify (G_OBJECT (app), TERMINAL_APP_ENABLE_MENU_BAR_ACCEL); +} + +static void +new_profile_response_cb (GtkWidget *new_profile_dialog, + int response_id, + TerminalApp *app) +{ + if (response_id == GTK_RESPONSE_ACCEPT) + { + GtkWidget *name_entry; + char *name; + const char *new_profile_name; + GtkWidget *base_option_menu; + TerminalProfile *base_profile = NULL; + TerminalProfile *new_profile; + GList *profiles; + GList *tmp; + GtkWindow *transient_parent; + GtkWidget *confirm_dialog; + gint retval; + GSList *list; + + base_option_menu = g_object_get_data (G_OBJECT (new_profile_dialog), "base_option_menu"); + base_profile = profile_combo_box_get_selected (base_option_menu); + if (!base_profile) + base_profile = terminal_app_get_default_profile (app); + if (!base_profile) + return; /* shouldn't happen ever though */ + + name_entry = g_object_get_data (G_OBJECT (new_profile_dialog), "name_entry"); + name = gtk_editable_get_chars (GTK_EDITABLE (name_entry), 0, -1); + g_strstrip (name); /* name will be non empty after stripping */ + + profiles = terminal_app_get_profile_list (app); + for (tmp = profiles; tmp != NULL; tmp = tmp->next) + { + TerminalProfile *profile = tmp->data; + const char *visible_name; + + visible_name = terminal_profile_get_property_string (profile, TERMINAL_PROFILE_VISIBLE_NAME); + + if (visible_name && strcmp (name, visible_name) == 0) + break; + } + if (tmp) + { + confirm_dialog = gtk_message_dialog_new (GTK_WINDOW (new_profile_dialog), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + _("You already have a profile called “%s”. Do you want to create another profile with the same name?"), name); + /* Alternative button order was set automatically by GtkMessageDialog */ + retval = gtk_dialog_run (GTK_DIALOG (confirm_dialog)); + gtk_widget_destroy (confirm_dialog); + if (retval == GTK_RESPONSE_NO) + goto cleanup; + } + g_list_free (profiles); + + transient_parent = gtk_window_get_transient_for (GTK_WINDOW (new_profile_dialog)); + + new_profile = _terminal_profile_clone (base_profile, name); + new_profile_name = terminal_profile_get_property_string (new_profile, TERMINAL_PROFILE_NAME); + g_hash_table_insert (app->profiles, + g_strdup (new_profile_name), + new_profile /* adopts the refcount */); + + /* And now save the list to mateconf */ + list = mateconf_client_get_list (app->conf, + CONF_GLOBAL_PREFIX"/profile_list", + MATECONF_VALUE_STRING, + NULL); + list = g_slist_append (list, g_strdup (new_profile_name)); + mateconf_client_set_list (app->conf, + CONF_GLOBAL_PREFIX"/profile_list", + MATECONF_VALUE_STRING, + list, + NULL); + + terminal_profile_edit (new_profile, transient_parent, NULL); + + cleanup: + g_free (name); + } + + gtk_widget_destroy (new_profile_dialog); +} + +static void +new_profile_dialog_destroy_cb (GtkWidget *new_profile_dialog, + TerminalApp *app) +{ + GtkWidget *combo; + + combo = g_object_get_data (G_OBJECT (new_profile_dialog), "base_option_menu"); + g_signal_handlers_disconnect_by_func (app, G_CALLBACK (profile_combo_box_refill), combo); + + app->new_profile_dialog = NULL; +} + +static void +new_profile_name_entry_changed_cb (GtkEntry *entry, + GtkDialog *dialog) +{ + const char *name; + + name = gtk_entry_get_text (entry); + + /* make the create button sensitive only if something other than space has been set */ + while (*name != '\0' && g_ascii_isspace (*name)) + ++name; + + gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_ACCEPT, name[0] != '\0'); +} + +void +terminal_app_new_profile (TerminalApp *app, + TerminalProfile *default_base_profile, + GtkWindow *transient_parent) +{ + if (app->new_profile_dialog == NULL) + { + GtkWidget *create_button, *table, *name_label, *name_entry, *base_label, *combo; + + if (!terminal_util_load_builder_file ("profile-new-dialog.ui", + "new-profile-dialog", &app->new_profile_dialog, + "new-profile-create-button", &create_button, + "new-profile-table", &table, + "new-profile-name-label", &name_label, + "new-profile-name-entry", &name_entry, + "new-profile-base-label", &base_label, + NULL)) + return; + + g_signal_connect (G_OBJECT (app->new_profile_dialog), "response", G_CALLBACK (new_profile_response_cb), app); + g_signal_connect (app->new_profile_dialog, "destroy", G_CALLBACK (new_profile_dialog_destroy_cb), app); + + g_object_set_data (G_OBJECT (app->new_profile_dialog), "create_button", create_button); + gtk_widget_set_sensitive (create_button, FALSE); + + /* the name entry */ + g_object_set_data (G_OBJECT (app->new_profile_dialog), "name_entry", name_entry); + g_signal_connect (name_entry, "changed", G_CALLBACK (new_profile_name_entry_changed_cb), app->new_profile_dialog); + gtk_entry_set_activates_default (GTK_ENTRY (name_entry), TRUE); + gtk_widget_grab_focus (name_entry); + + gtk_label_set_mnemonic_widget (GTK_LABEL (name_label), name_entry); + + /* the base profile option menu */ + combo = profile_combo_box_new (app); + gtk_table_attach_defaults (GTK_TABLE (table), combo, 1, 2, 1, 2); + g_object_set_data (G_OBJECT (app->new_profile_dialog), "base_option_menu", combo); + terminal_util_set_atk_name_description (combo, NULL, _("Choose base profile")); + + gtk_label_set_mnemonic_widget (GTK_LABEL (base_label), combo); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (app->new_profile_dialog), + GTK_RESPONSE_ACCEPT, + GTK_RESPONSE_CANCEL, + -1); + gtk_dialog_set_default_response (GTK_DIALOG (app->new_profile_dialog), GTK_RESPONSE_ACCEPT); + gtk_dialog_set_response_sensitive (GTK_DIALOG (app->new_profile_dialog), GTK_RESPONSE_ACCEPT, FALSE); + } + + gtk_window_set_transient_for (GTK_WINDOW (app->new_profile_dialog), + transient_parent); + + gtk_window_present (GTK_WINDOW (app->new_profile_dialog)); +} + +static void +profile_list_selection_changed_cb (GtkTreeSelection *selection, + TerminalApp *app) +{ + gboolean selected; + + selected = gtk_tree_selection_get_selected (selection, NULL, NULL); + + gtk_widget_set_sensitive (app->manage_profiles_edit_button, selected); + gtk_widget_set_sensitive (app->manage_profiles_delete_button, + selected && + g_hash_table_size (app->profiles) > 1); +} + +static void +profile_list_response_cb (GtkWidget *dialog, + int id, + TerminalApp *app) +{ + g_assert (app->manage_profiles_dialog == dialog); + + if (id == GTK_RESPONSE_HELP) + { + terminal_util_show_help ("mate-terminal-manage-profiles", GTK_WINDOW (dialog)); + return; + } + + gtk_widget_destroy (dialog); +} + +static void +profile_list_destroyed_cb (GtkWidget *manage_profiles_dialog, + TerminalApp *app) +{ + g_signal_handlers_disconnect_by_func (app, G_CALLBACK (profile_list_treeview_refill), app->manage_profiles_list); + g_signal_handlers_disconnect_by_func (app, G_CALLBACK (profile_combo_box_refill), app->manage_profiles_default_menu); + + app->manage_profiles_dialog = NULL; + app->manage_profiles_list = NULL; + app->manage_profiles_new_button = NULL; + app->manage_profiles_edit_button = NULL; + app->manage_profiles_delete_button = NULL; + app->manage_profiles_default_menu = NULL; +} + +void +terminal_app_manage_profiles (TerminalApp *app, + GtkWindow *transient_parent) +{ + GObject *dialog; + GObject *tree_view_container, *new_button, *edit_button, *remove_button; + GObject *default_hbox, *default_label; + GtkTreeSelection *selection; + + if (app->manage_profiles_dialog) + { + gtk_window_set_transient_for (GTK_WINDOW (app->manage_profiles_dialog), transient_parent); + gtk_window_present (GTK_WINDOW (app->manage_profiles_dialog)); + return; + } + + if (!terminal_util_load_builder_file ("profile-manager.ui", + "profile-manager", &dialog, + "profiles-treeview-container", &tree_view_container, + "new-profile-button", &new_button, + "edit-profile-button", &edit_button, + "delete-profile-button", &remove_button, + "default-profile-hbox", &default_hbox, + "default-profile-label", &default_label, + NULL)) + return; + + app->manage_profiles_dialog = GTK_WIDGET (dialog); + app->manage_profiles_new_button = GTK_WIDGET (new_button); + app->manage_profiles_edit_button = GTK_WIDGET (edit_button); + app->manage_profiles_delete_button = GTK_WIDGET (remove_button); + + g_signal_connect (dialog, "response", G_CALLBACK (profile_list_response_cb), app); + g_signal_connect (dialog, "destroy", G_CALLBACK (profile_list_destroyed_cb), app); + + app->manage_profiles_list = profile_list_treeview_create (app); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (app->manage_profiles_list)); + g_signal_connect (selection, "changed", G_CALLBACK (profile_list_selection_changed_cb), app); + + profile_list_treeview_refill (app, app->manage_profiles_list); + g_signal_connect (app, "profile-list-changed", + G_CALLBACK (profile_list_treeview_refill), app->manage_profiles_list); + + g_signal_connect (app->manage_profiles_list, "row-activated", + G_CALLBACK (profile_list_row_activated_cb), app); + + gtk_container_add (GTK_CONTAINER (tree_view_container), app->manage_profiles_list); + gtk_widget_show (app->manage_profiles_list); + + g_signal_connect (new_button, "clicked", + G_CALLBACK (profile_list_new_button_clicked_cb), + app->manage_profiles_list); + g_signal_connect (edit_button, "clicked", + G_CALLBACK (profile_list_edit_button_clicked_cb), + app->manage_profiles_list); + g_signal_connect (remove_button, "clicked", + G_CALLBACK (profile_list_delete_button_clicked_cb), + app->manage_profiles_list); + + app->manage_profiles_default_menu = profile_combo_box_new (app); + g_signal_connect (app->manage_profiles_default_menu, "changed", + G_CALLBACK (profile_combo_box_changed_cb), app); + + gtk_box_pack_start (GTK_BOX (default_hbox), app->manage_profiles_default_menu, FALSE, FALSE, 0); + gtk_widget_show (app->manage_profiles_default_menu); + + gtk_label_set_mnemonic_widget (GTK_LABEL (default_label), app->manage_profiles_default_menu); + + gtk_widget_grab_focus (app->manage_profiles_list); + + gtk_window_set_transient_for (GTK_WINDOW (app->manage_profiles_dialog), + transient_parent); + + gtk_window_present (GTK_WINDOW (app->manage_profiles_dialog)); +} + +#ifdef WITH_SMCLIENT + +static void +terminal_app_save_state_cb (EggSMClient *client, + GKeyFile *key_file, + TerminalApp *app) +{ + terminal_app_save_config (app, key_file); +} + +static void +terminal_app_client_quit_cb (EggSMClient *client, + TerminalApp *app) +{ + g_signal_emit (app, signals[QUIT], 0); +} + +#endif /* WITH_SMCLIENT */ + +/* Class implementation */ + +G_DEFINE_TYPE (TerminalApp, terminal_app, G_TYPE_OBJECT) + +static void +terminal_app_init (TerminalApp *app) +{ + GError *error = NULL; + + global_app = app; + + /* If the mateconf daemon isn't available (e.g. because there's no dbus + * session bus running), we'd crash later on. Tell the user about it + * now, and exit. See bug #561663. + * Don't use mateconf_ping_daemon() here since the server may just not + * be running yet, but able to be started. See comments on bug #564649. + */ + if (!mateconf_spawn_daemon (&error)) + { + g_printerr ("Failed to summon the MateConf demon; exiting. %s\n", error->message); + g_error_free (error); + + exit (EXIT_FAILURE); + } + + gtk_window_set_default_icon_name (MATE_TERMINAL_ICON_NAME); + + /* Initialise defaults */ + app->enable_mnemonics = DEFAULT_ENABLE_MNEMONICS; + app->enable_menu_accels = DEFAULT_ENABLE_MENU_BAR_ACCEL; + + app->profiles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + + app->encodings = terminal_encodings_get_builtins (); + + app->conf = mateconf_client_get_default (); + + mateconf_client_add_dir (app->conf, CONF_GLOBAL_PREFIX, + MATECONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + mateconf_client_add_dir (app->conf, MONOSPACE_FONT_DIR, + MATECONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + mateconf_client_add_dir (app->conf, CONF_PROXY_PREFIX, + MATECONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + mateconf_client_add_dir (app->conf, CONF_HTTP_PROXY_PREFIX, + MATECONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + + app->profile_list_notify_id = + mateconf_client_notify_add (app->conf, PROFILE_LIST_KEY, + terminal_app_profile_list_notify_cb, + app, NULL, NULL); + + app->default_profile_notify_id = + mateconf_client_notify_add (app->conf, + DEFAULT_PROFILE_KEY, + terminal_app_default_profile_notify_cb, + app, NULL, NULL); + + app->encoding_list_notify_id = + mateconf_client_notify_add (app->conf, + ENCODING_LIST_KEY, + terminal_app_encoding_list_notify_cb, + app, NULL, NULL); + + app->system_font_notify_id = + mateconf_client_notify_add (app->conf, + MONOSPACE_FONT_KEY, + terminal_app_system_font_notify_cb, + app, NULL, NULL); + + app->enable_mnemonics_notify_id = + mateconf_client_notify_add (app->conf, + ENABLE_MNEMONICS_KEY, + terminal_app_enable_mnemonics_notify_cb, + app, NULL, NULL); + + app->enable_menu_accels_notify_id = + mateconf_client_notify_add (app->conf, + ENABLE_MENU_BAR_ACCEL_KEY, + terminal_app_enable_menu_accels_notify_cb, + app, NULL, NULL); + + /* Load the settings */ + mateconf_client_notify (app->conf, PROFILE_LIST_KEY); + mateconf_client_notify (app->conf, DEFAULT_PROFILE_KEY); + mateconf_client_notify (app->conf, ENCODING_LIST_KEY); + mateconf_client_notify (app->conf, MONOSPACE_FONT_KEY); + mateconf_client_notify (app->conf, ENABLE_MENU_BAR_ACCEL_KEY); + mateconf_client_notify (app->conf, ENABLE_MNEMONICS_KEY); + + /* Ensure we have valid settings */ + g_assert (app->default_profile_id != NULL); + g_assert (app->system_font_desc != NULL); + + terminal_accels_init (); + +#ifdef WITH_SMCLIENT +{ + EggSMClient *sm_client; +#ifdef GDK_WINDOWING_X11 + char *desktop_file; + + desktop_file = g_build_filename (TERM_DATADIR, + "applications", + PACKAGE ".desktop", + NULL); + egg_set_desktop_file_without_defaults (desktop_file); + g_free (desktop_file); +#endif /* GDK_WINDOWING_X11 */ + + sm_client = egg_sm_client_get (); + g_signal_connect (sm_client, "save-state", + G_CALLBACK (terminal_app_save_state_cb), app); + g_signal_connect (sm_client, "quit", + G_CALLBACK (terminal_app_client_quit_cb), app); +} +#endif +} + +static void +terminal_app_finalize (GObject *object) +{ + TerminalApp *app = TERMINAL_APP (object); + +#ifdef WITH_SMCLIENT + EggSMClient *sm_client; + + sm_client = egg_sm_client_get (); + g_signal_handlers_disconnect_matched (sm_client, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, app); +#endif + + if (app->profile_list_notify_id != 0) + mateconf_client_notify_remove (app->conf, app->profile_list_notify_id); + if (app->default_profile_notify_id != 0) + mateconf_client_notify_remove (app->conf, app->default_profile_notify_id); + if (app->encoding_list_notify_id != 0) + mateconf_client_notify_remove (app->conf, app->encoding_list_notify_id); + if (app->system_font_notify_id != 0) + mateconf_client_notify_remove (app->conf, app->system_font_notify_id); + if (app->enable_menu_accels_notify_id != 0) + mateconf_client_notify_remove (app->conf, app->enable_menu_accels_notify_id); + if (app->enable_mnemonics_notify_id != 0) + mateconf_client_notify_remove (app->conf, app->enable_mnemonics_notify_id); + + mateconf_client_remove_dir (app->conf, CONF_GLOBAL_PREFIX, NULL); + mateconf_client_remove_dir (app->conf, MONOSPACE_FONT_DIR, NULL); + + g_object_unref (app->conf); + + g_free (app->default_profile_id); + + g_hash_table_destroy (app->profiles); + + g_hash_table_destroy (app->encodings); + + pango_font_description_free (app->system_font_desc); + + terminal_accels_shutdown (); + + G_OBJECT_CLASS (terminal_app_parent_class)->finalize (object); + + global_app = NULL; +} + +static void +terminal_app_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TerminalApp *app = TERMINAL_APP (object); + + switch (prop_id) + { + case PROP_SYSTEM_FONT: + if (app->system_font_desc) + g_value_set_boxed (value, app->system_font_desc); + else + g_value_take_boxed (value, pango_font_description_from_string (DEFAULT_MONOSPACE_FONT)); + break; + case PROP_ENABLE_MENU_BAR_ACCEL: + g_value_set_boolean (value, app->enable_menu_accels); + break; + case PROP_ENABLE_MNEMONICS: + g_value_set_boolean (value, app->enable_mnemonics); + break; + case PROP_DEFAULT_PROFILE: + g_value_set_object (value, app->default_profile); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +terminal_app_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TerminalApp *app = TERMINAL_APP (object); + + switch (prop_id) + { + case PROP_ENABLE_MENU_BAR_ACCEL: + app->enable_menu_accels = g_value_get_boolean (value); + mateconf_client_set_bool (app->conf, ENABLE_MENU_BAR_ACCEL_KEY, app->enable_menu_accels, NULL); + break; + case PROP_ENABLE_MNEMONICS: + app->enable_mnemonics = g_value_get_boolean (value); + mateconf_client_set_bool (app->conf, ENABLE_MNEMONICS_KEY, app->enable_mnemonics, NULL); + break; + case PROP_DEFAULT_PROFILE: + case PROP_SYSTEM_FONT: + /* not writable */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +terminal_app_real_quit (TerminalApp *app) +{ + gtk_main_quit(); +} + +static void +terminal_app_class_init (TerminalAppClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = terminal_app_finalize; + object_class->get_property = terminal_app_get_property; + object_class->set_property = terminal_app_set_property; + + klass->quit = terminal_app_real_quit; + + signals[QUIT] = + g_signal_new (I_("quit"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TerminalAppClass, quit), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[PROFILE_LIST_CHANGED] = + g_signal_new (I_("profile-list-changed"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TerminalAppClass, profile_list_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[ENCODING_LIST_CHANGED] = + g_signal_new (I_("encoding-list-changed"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TerminalAppClass, profile_list_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_object_class_install_property + (object_class, + PROP_ENABLE_MENU_BAR_ACCEL, + g_param_spec_boolean (TERMINAL_APP_ENABLE_MENU_BAR_ACCEL, NULL, NULL, + DEFAULT_ENABLE_MENU_BAR_ACCEL, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property + (object_class, + PROP_ENABLE_MNEMONICS, + g_param_spec_boolean (TERMINAL_APP_ENABLE_MNEMONICS, NULL, NULL, + DEFAULT_ENABLE_MNEMONICS, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property + (object_class, + PROP_SYSTEM_FONT, + g_param_spec_boxed (TERMINAL_APP_SYSTEM_FONT, NULL, NULL, + PANGO_TYPE_FONT_DESCRIPTION, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property + (object_class, + PROP_DEFAULT_PROFILE, + g_param_spec_object (TERMINAL_APP_DEFAULT_PROFILE, NULL, NULL, + TERMINAL_TYPE_PROFILE, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); +} + +/* Public API */ + +TerminalApp* +terminal_app_get (void) +{ + if (global_app == NULL) { + g_object_new (TERMINAL_TYPE_APP, NULL); + g_assert (global_app != NULL); + } + + return global_app; +} + +void +terminal_app_shutdown (void) +{ + if (global_app == NULL) + return; + + g_object_unref (global_app); + g_assert (global_app == NULL); +} + +/** + * terminal_app_handle_options: + * @app: + * @options: a #TerminalOptions + * @allow_resume: whether to merge the terminal configuration from the + * saved session on resume + * @error: a #GError to fill in + * + * Processes @options. It loads or saves the terminal configuration, or + * opens the specified windows and tabs. + * + * Returns: %TRUE if @options could be successfully handled, or %FALSE on + * error + */ +gboolean +terminal_app_handle_options (TerminalApp *app, + TerminalOptions *options, + gboolean allow_resume, + GError **error) +{ + GList *lw; + GdkScreen *gdk_screen; + + gdk_screen = terminal_app_get_screen_by_display_name (options->display_name, + options->screen_number); + + if (options->save_config) + { + if (options->remote_arguments) + return terminal_app_save_config_file (app, options->config_file, error); + + g_set_error_literal (error, TERMINAL_OPTION_ERROR, TERMINAL_OPTION_ERROR_NOT_IN_FACTORY, + "Cannot use \"--save-config\" when starting the factory process"); + return FALSE; + } + + if (options->load_config) + { + GKeyFile *key_file; + gboolean result; + + key_file = g_key_file_new (); + result = g_key_file_load_from_file (key_file, options->config_file, 0, error) && + terminal_options_merge_config (options, key_file, SOURCE_DEFAULT, error); + g_key_file_free (key_file); + + if (!result) + return FALSE; + + /* fall-through on success */ + } + +#ifdef WITH_SMCLIENT +{ + EggSMClient *sm_client; + + sm_client = egg_sm_client_get (); + + if (allow_resume && egg_sm_client_is_resumed (sm_client)) + { + GKeyFile *key_file; + + key_file = egg_sm_client_get_state_file (sm_client); + if (key_file != NULL && + !terminal_options_merge_config (options, key_file, SOURCE_SESSION, error)) + return FALSE; + } +} +#endif + + /* Make sure we open at least one window */ + terminal_options_ensure_window (options); + + if (options->startup_id != NULL) + _terminal_debug_print (TERMINAL_DEBUG_FACTORY, + "Startup ID is '%s'\n", + options->startup_id); + + for (lw = options->initial_windows; lw != NULL; lw = lw->next) + { + InitialWindow *iw = lw->data; + TerminalWindow *window; + GList *lt; + + g_assert (iw->tabs); + + /* Create & setup new window */ + window = terminal_app_new_window (app, gdk_screen); + + /* Restored windows shouldn't demand attention; see bug #586308. */ + if (iw->source_tag == SOURCE_SESSION) + terminal_window_set_is_restored (window); + + if (options->startup_id != NULL) + gtk_window_set_startup_id (GTK_WINDOW (window), options->startup_id); + + /* Overwrite the default, unique window role set in terminal_window_init */ + if (iw->role) + gtk_window_set_role (GTK_WINDOW (window), iw->role); + + if (iw->force_menubar_state) + terminal_window_set_menubar_visible (window, iw->menubar_state); + + if (iw->start_fullscreen) + gtk_window_fullscreen (GTK_WINDOW (window)); + if (iw->start_maximized) + gtk_window_maximize (GTK_WINDOW (window)); + + /* Now add the tabs */ + for (lt = iw->tabs; lt != NULL; lt = lt->next) + { + InitialTab *it = lt->data; + TerminalProfile *profile = NULL; + TerminalScreen *screen; + const char *profile_name; + gboolean profile_is_id; + + if (it->profile) + { + profile_name = it->profile; + profile_is_id = it->profile_is_id; + } + else + { + profile_name = options->default_profile; + profile_is_id = options->default_profile_is_id; + } + + if (profile_name) + { + if (profile_is_id) + profile = terminal_app_get_profile_by_name (app, profile_name); + else + profile = terminal_app_get_profile_by_visible_name (app, profile_name); + + if (profile == NULL) + g_printerr (_("No such profile \"%s\", using default profile\n"), it->profile); + } + if (profile == NULL) + profile = terminal_app_get_profile_for_new_term (app); + g_assert (profile); + + screen = terminal_app_new_terminal (app, window, profile, + it->exec_argv ? it->exec_argv : options->exec_argv, + it->title ? it->title : options->default_title, + it->working_dir ? it->working_dir : options->default_working_dir, + options->env, + it->zoom_set ? it->zoom : options->zoom); + + if (it->active) + terminal_window_switch_screen (window, screen); + } + + if (iw->geometry) + { + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "[window %p] applying geometry %s\n", + window, iw->geometry); + + if (!gtk_window_parse_geometry (GTK_WINDOW (window), iw->geometry)) + g_printerr (_("Invalid geometry string \"%s\"\n"), iw->geometry); + } + + gtk_window_present (GTK_WINDOW (window)); + } + + return TRUE; +} + +TerminalWindow * +terminal_app_new_window (TerminalApp *app, + GdkScreen *screen) +{ + TerminalWindow *window; + + window = terminal_window_new (); + + app->windows = g_list_append (app->windows, window); + g_signal_connect (window, "destroy", + G_CALLBACK (terminal_window_destroyed), app); + + if (screen) + gtk_window_set_screen (GTK_WINDOW (window), screen); + + return window; +} + +TerminalScreen * +terminal_app_new_terminal (TerminalApp *app, + TerminalWindow *window, + TerminalProfile *profile, + char **override_command, + const char *title, + const char *working_dir, + char **child_env, + double zoom) +{ + TerminalScreen *screen; + + g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); + g_return_val_if_fail (TERMINAL_IS_WINDOW (window), NULL); + + screen = terminal_screen_new (profile, override_command, title, + working_dir, child_env, zoom); + + terminal_window_add_screen (window, screen, -1); + terminal_window_switch_screen (window, screen); + gtk_widget_grab_focus (GTK_WIDGET (screen)); + + return screen; +} + +void +terminal_app_edit_profile (TerminalApp *app, + TerminalProfile *profile, + GtkWindow *transient_parent, + const char *widget_name) +{ + terminal_profile_edit (profile, transient_parent, widget_name); +} + +void +terminal_app_edit_keybindings (TerminalApp *app, + GtkWindow *transient_parent) +{ + terminal_edit_keys_dialog_show (transient_parent); +} + +void +terminal_app_edit_encodings (TerminalApp *app, + GtkWindow *transient_parent) +{ + terminal_encoding_dialog_show (transient_parent); +} + +TerminalWindow * +terminal_app_get_current_window (TerminalApp *app) +{ + if (app->windows == NULL) + return NULL; + + return g_list_last (app->windows)->data; +} + +/** + * terminal_profile_get_list: + * + * Returns: a #GList containing all #TerminalProfile objects. + * The content of the list is owned by the backend and + * should not be modified or freed. Use g_list_free() when done + * using the list. + */ +GList* +terminal_app_get_profile_list (TerminalApp *app) +{ + g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); + + return g_list_sort (g_hash_table_get_values (app->profiles), profiles_alphabetic_cmp); +} + +TerminalProfile* +terminal_app_get_profile_by_name (TerminalApp *app, + const char *name) +{ + g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); + g_return_val_if_fail (name != NULL, NULL); + + return g_hash_table_lookup (app->profiles, name); +} + +TerminalProfile* +terminal_app_get_profile_by_visible_name (TerminalApp *app, + const char *name) +{ + LookupInfo info; + + g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); + g_return_val_if_fail (name != NULL, NULL); + + info.result = NULL; + info.target = name; + + g_hash_table_foreach (app->profiles, + profiles_lookup_by_visible_name_foreach, + &info); + return info.result; +} + +TerminalProfile* +terminal_app_get_default_profile (TerminalApp *app) +{ + g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); + + return app->default_profile; +} + +TerminalProfile* +terminal_app_get_profile_for_new_term (TerminalApp *app) +{ + GHashTableIter iter; + TerminalProfile *profile = NULL; + TerminalProfile **profileptr = &profile; + + g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); + + if (app->default_profile) + return app->default_profile; + + g_hash_table_iter_init (&iter, app->profiles); + if (g_hash_table_iter_next (&iter, NULL, (gpointer *) profileptr)) + return profile; + + return NULL; +} + +GHashTable * +terminal_app_get_encodings (TerminalApp *app) +{ + return app->encodings; +} + +/** + * terminal_app_ensure_encoding: + * @app: + * @charset: + * + * Ensures there's a #TerminalEncoding for @charset available. + */ +TerminalEncoding * +terminal_app_ensure_encoding (TerminalApp *app, + const char *charset) +{ + TerminalEncoding *encoding; + + encoding = g_hash_table_lookup (app->encodings, charset); + if (encoding == NULL) + { + encoding = terminal_encoding_new (charset, + _("User Defined"), + TRUE, + TRUE /* scary! */); + g_hash_table_insert (app->encodings, + (gpointer) terminal_encoding_get_id (encoding), + encoding); + } + + return encoding; +} + +/** + * terminal_app_get_active_encodings: + * + * Returns: a newly allocated list of newly referenced #TerminalEncoding objects. + */ +GSList* +terminal_app_get_active_encodings (TerminalApp *app) +{ + GSList *list = NULL; + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, app->encodings); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + TerminalEncoding *encoding = (TerminalEncoding *) value; + + if (!encoding->is_active) + continue; + + list = g_slist_prepend (list, terminal_encoding_ref (encoding)); + } + + return g_slist_sort (list, (GCompareFunc) compare_encodings); +} + +void +terminal_app_save_config (TerminalApp *app, + GKeyFile *key_file) +{ + GList *lw; + guint n = 0; + GPtrArray *window_names_array; + char **window_names; + gsize len; + + g_key_file_set_comment (key_file, NULL, NULL, "Written by " PACKAGE_STRING, NULL); + + g_key_file_set_integer (key_file, TERMINAL_CONFIG_GROUP, TERMINAL_CONFIG_PROP_VERSION, TERMINAL_CONFIG_VERSION); + g_key_file_set_integer (key_file, TERMINAL_CONFIG_GROUP, TERMINAL_CONFIG_PROP_COMPAT_VERSION, TERMINAL_CONFIG_COMPAT_VERSION); + + window_names_array = g_ptr_array_sized_new (g_list_length (app->windows) + 1); + + for (lw = app->windows; lw != NULL; lw = lw->next) + { + TerminalWindow *window = TERMINAL_WINDOW (lw->data); + char *group; + + group = g_strdup_printf ("Window%u", n++); + g_ptr_array_add (window_names_array, group); + + terminal_window_save_state (window, key_file, group); + } + + len = window_names_array->len; + g_ptr_array_add (window_names_array, NULL); + window_names = (char **) g_ptr_array_free (window_names_array, FALSE); + g_key_file_set_string_list (key_file, TERMINAL_CONFIG_GROUP, TERMINAL_CONFIG_PROP_WINDOWS, (const char * const *) window_names, len); + g_strfreev (window_names); +} + +gboolean +terminal_app_save_config_file (TerminalApp *app, + const char *file_name, + GError **error) +{ + GKeyFile *key_file; + char *data; + gsize len; + gboolean result; + + key_file = g_key_file_new (); + terminal_app_save_config (app, key_file); + + data = g_key_file_to_data (key_file, &len, NULL); + result = g_file_set_contents (file_name, data, len, error); + g_free (data); + + return result; +} |