/* * Copyright (C) 2001 Ximian, Inc. * Copyright (C) 2007 Vincent Geddes. * Copyright (C) 2012-2018 Juan Pablo Ugarte. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Authors: * Chema Celorio * Paolo Borelli * Vincent Geddes * Juan Pablo Ugarte */ #include #include "glade-window.h" #include "glade-resources.h" #include "glade-preferences.h" #include "glade-registration.h" #include "glade-intro.h" #include #include #include #include #include #include #include #include #include #include #ifdef MAC_INTEGRATION # include #endif #define ACTION_GROUP_STATIC "GladeStatic" #define ACTION_GROUP_PROJECT "GladeProject" #define ACTION_GROUP_PROJECTS_LIST_MENU "GladeProjectsList" #define READONLY_INDICATOR (_("[Read Only]")) #define URL_DEVELOPER_MANUAL "http://library.gnome.org/devel/gladeui/" #define CONFIG_GROUP_WINDOWS "Glade Windows" #define GLADE_WINDOW_DEFAULT_WIDTH 720 #define GLADE_WINDOW_DEFAULT_HEIGHT 540 #define CONFIG_KEY_X "x" #define CONFIG_KEY_Y "y" #define CONFIG_KEY_WIDTH "width" #define CONFIG_KEY_HEIGHT "height" #define CONFIG_KEY_MAXIMIZED "maximized" #define CONFIG_GROUP_LOAD_SAVE "Load and Save" #define CONFIG_KEY_BACKUP "backup" #define CONFIG_KEY_AUTOSAVE "autosave" #define CONFIG_KEY_AUTOSAVE_SECONDS "autosave-seconds" #define CONFIG_INTRO_GROUP "Intro" #define CONFIG_INTRO_DONE "intro-done" #define GLADE_WINDOW_ACTIVE_VIEW(w) ((GladeDesignView *) gtk_stack_get_visible_child (w->priv->view_stack)) struct _GladeWindowPrivate { GladeApp *app; GtkStack *stack; GtkStack *view_stack; GtkHeaderBar *headerbar; GtkWidget *project_switcher ; GtkWindow *about_dialog; GladePreferences *preferences; GtkWidget *start_page; GtkLabel *version_label; GtkWidget *intro_button; GladeAdaptorChooser *adaptor_chooser; GtkStack *inspectors_stack; /* Cached per project inspectors */ GladeEditor *editor; /* The editor */ GtkWidget *statusbar; /* A pointer to the status bar. */ guint statusbar_context_id; /* The context id of general messages */ guint statusbar_menu_context_id; /* The context id of the menu bar */ guint statusbar_actions_context_id; /* The context id of actions messages */ GActionGroup *actions; GtkRecentManager *recent_manager; GtkWidget *recent_menu; gchar *default_path; /* the default path for open/save operations */ GtkWidget *undo_button; /* undo/redo button, right click for history */ GtkWidget *redo_button; GtkWidget *toolbar; /* Actions are added to the toolbar */ gint actions_start; /* start of action items */ GtkWidget *center_paned; GtkWidget *left_paned; GtkWidget *open_button_box; /* gtk_button_box_set_layout() set homogeneous to TRUE, and we do not want that in this case */ GtkWidget *save_button_box; GtkWidget *registration; /* Registration and user survey dialog */ GladeIntro *intro; GType new_type; GdkRectangle position; }; static void check_reload_project (GladeWindow *window, GladeProject *project); G_DEFINE_TYPE_WITH_PRIVATE (GladeWindow, glade_window, GTK_TYPE_WINDOW) static void refresh_title (GladeWindow *window) { if (GLADE_WINDOW_ACTIVE_VIEW (window)) { GladeProject *project = glade_design_view_get_project (GLADE_WINDOW_ACTIVE_VIEW (window)); gchar *title, *name = NULL; GList *p; gtk_header_bar_set_custom_title (window->priv->headerbar, NULL); name = glade_project_get_name (project); if (glade_project_get_modified (project)) name = g_strdup_printf ("*%s", name); else name = g_strdup (name); if (glade_project_get_readonly (project) != FALSE) title = g_strdup_printf ("%s %s", name, READONLY_INDICATOR); else title = g_strdup_printf ("%s", name); gtk_header_bar_set_title (window->priv->headerbar, title); g_free (title); g_free (name); if ((p = glade_app_get_projects ()) && g_list_next (p)) gtk_header_bar_set_custom_title (window->priv->headerbar, window->priv->project_switcher); else { const gchar *path; /* Show path */ if (project && (path = glade_project_get_path (project))) { gchar *dirname = g_path_get_dirname (path); const gchar *home = g_get_home_dir (); if (g_str_has_prefix (dirname, home)) { char *subtitle = &dirname[g_utf8_strlen (home, -1) - 1]; subtitle[0] = '~'; gtk_header_bar_set_subtitle (window->priv->headerbar, subtitle); } else gtk_header_bar_set_subtitle (window->priv->headerbar, dirname); g_free (dirname); } else gtk_header_bar_set_subtitle (window->priv->headerbar, NULL); } } else { gtk_header_bar_set_custom_title (window->priv->headerbar, NULL); gtk_header_bar_set_title (window->priv->headerbar, _("User Interface Designer")); gtk_header_bar_set_subtitle (window->priv->headerbar, NULL); } } static const gchar * get_default_path (GladeWindow *window) { return window->priv->default_path; } static void update_default_path (GladeWindow *window, const gchar *filename) { gchar *path; g_return_if_fail (filename != NULL); path = g_path_get_dirname (filename); g_free (window->priv->default_path); window->priv->default_path = g_strdup (path); g_free (path); } static void activate_action (GtkToolButton *toolbutton, GladeWidgetAction *action) { GladeWidget *widget; GWActionClass *aclass = glade_widget_action_get_class (action); if ((widget = g_object_get_data (G_OBJECT (toolbutton), "glade-widget"))) glade_widget_adaptor_action_activate (glade_widget_get_adaptor (widget), glade_widget_get_object (widget), aclass->path); } static void action_notify_sensitive (GObject *gobject, GParamSpec *arg1, GtkWidget *item) { GladeWidgetAction *action = GLADE_WIDGET_ACTION (gobject); gtk_widget_set_sensitive (item, glade_widget_action_get_sensitive (action)); } static void action_disconnect (gpointer data, GClosure *closure) { g_signal_handlers_disconnect_matched (data, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, action_notify_sensitive, NULL); } static void clean_actions (GladeWindow *window) { GtkContainer *container = GTK_CONTAINER (window->priv->toolbar); GtkToolbar *bar = GTK_TOOLBAR (window->priv->toolbar); GtkToolItem *item; if (window->priv->actions_start) { while ((item = gtk_toolbar_get_nth_item (bar, window->priv->actions_start))) gtk_container_remove (container, GTK_WIDGET (item)); } } static void add_actions (GladeWindow *window, GladeWidget *widget, GList *actions) { GtkToolbar *bar = GTK_TOOLBAR (window->priv->toolbar); GtkToolItem *item = gtk_separator_tool_item_new (); gint n = 0; GList *l; gtk_toolbar_insert (bar, item, -1); gtk_widget_show (GTK_WIDGET (item)); if (window->priv->actions_start == 0) window->priv->actions_start = gtk_toolbar_get_item_index (bar, item); for (l = actions; l; l = g_list_next (l)) { GladeWidgetAction *action = l->data; GWActionClass *aclass = glade_widget_action_get_class (action); if (!aclass->important || !glade_widget_action_get_visible (action)) continue; if (glade_widget_action_get_children (action)) { g_warning ("Trying to add a group action to the toolbar is unsupported"); continue; } item = gtk_tool_button_new (NULL, aclass->label); gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (item), (aclass->stock) ? aclass->stock : "system-run"); if (aclass->label) gtk_widget_set_tooltip_text (GTK_WIDGET (item), aclass->label); g_object_set_data (G_OBJECT (item), "glade-widget", widget); /* We use destroy_data to keep track of notify::sensitive callbacks * on the action object and disconnect them when the toolbar item * gets destroyed. */ g_signal_connect_data (item, "clicked", G_CALLBACK (activate_action), action, action_disconnect, 0); gtk_widget_set_sensitive (GTK_WIDGET (item), glade_widget_action_get_sensitive (action)); g_signal_connect (action, "notify::sensitive", G_CALLBACK (activate_action), GTK_WIDGET (item)); gtk_toolbar_insert (bar, item, -1); gtk_tool_item_set_homogeneous (item, FALSE); gtk_widget_show_all (GTK_WIDGET (item)); n++; } if (n == 0) clean_actions (window); } static GladeProject * get_active_project (GladeWindow *window) { if (GLADE_WINDOW_ACTIVE_VIEW (window)) return glade_design_view_get_project (GLADE_WINDOW_ACTIVE_VIEW (window)); return NULL; } static void project_selection_changed_cb (GladeProject *project, GladeWindow *window) { GladeProject *active_project; GladeWidget *glade_widget = NULL; GList *list; gint num; active_project = get_active_project (window); /* This is sometimes called with a NULL project (to make the label * insensitive with no projects loaded) */ g_return_if_fail (GLADE_IS_WINDOW (window)); /* Only update the toolbar & workspace if the selection has changed on * the currently active project. */ if (project == active_project) { list = glade_project_selection_get (project); num = g_list_length (list); if (num == 1 && !GLADE_IS_PLACEHOLDER (list->data)) { glade_widget = glade_widget_get_from_gobject (G_OBJECT (list->data)); clean_actions (window); if (glade_widget_get_actions (glade_widget)) add_actions (window, glade_widget, glade_widget_get_actions (glade_widget)); } } glade_editor_load_widget (window->priv->editor, glade_widget); } static void refresh_stack_title_for_project (GladeWindow *window, GladeProject *project) { GList *children, *l; children = gtk_container_get_children (GTK_CONTAINER (window->priv->view_stack)); for (l = children; l; l = l->next) { GtkWidget *view = l->data; if (project == glade_design_view_get_project (GLADE_DESIGN_VIEW (view))) { gchar *name = glade_project_get_name (project); gchar *str; /* remove extension */ if ((str = g_utf8_strrchr (name, -1, '.'))) *str = '\0'; if (glade_project_get_modified (project)) str = g_strdup_printf ("*%s", name); else str = g_strdup (name); gtk_container_child_set (GTK_CONTAINER (window->priv->view_stack), view, "title", str, NULL); g_free (name); g_free (str); break; } } g_list_free (children); } static void project_targets_changed_cb (GladeProject *project, GladeWindow *window) { refresh_stack_title_for_project (window, project); } static void actions_set_enabled (GladeWindow *window, const gchar *name, gboolean enabled) { GAction *action = g_action_map_lookup_action (G_ACTION_MAP (window->priv->actions), name); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled); } static void project_actions_set_enabled (GladeWindow *window, gboolean enabled) { actions_set_enabled (window, "close", enabled); actions_set_enabled (window, "save", enabled); actions_set_enabled (window, "save_as", enabled); actions_set_enabled (window, "properties", enabled); actions_set_enabled (window, "undo", enabled); actions_set_enabled (window, "redo", enabled); actions_set_enabled (window, "cut", enabled); actions_set_enabled (window, "copy", enabled); actions_set_enabled (window, "delete", enabled); actions_set_enabled (window, "previous", enabled); actions_set_enabled (window, "next", enabled); } static void pointer_mode_actions_set_enabled (GladeWindow *window, gboolean enabled) { actions_set_enabled (window, "select", enabled); actions_set_enabled (window, "drag_resize", enabled); actions_set_enabled (window, "margin_edit", enabled); actions_set_enabled (window, "align_edit", enabled); } static void refresh_undo_redo (GladeWindow *window, GladeProject *project) { GladeCommand *undo = NULL, *redo = NULL; GladeWindowPrivate *priv = window->priv; const gchar *desc; gchar *tooltip; if (project != NULL) { undo = glade_project_next_undo_item (project); redo = glade_project_next_redo_item (project); } /* Refresh Undo */ actions_set_enabled (window, "undo", undo != NULL); desc = undo ? glade_command_description (undo) : _("the last action"); tooltip = g_strdup_printf (_("Undo: %s"), desc); gtk_widget_set_tooltip_text (priv->undo_button, tooltip); g_free (tooltip); /* Refresh Redo */ actions_set_enabled (window, "redo", redo != NULL); desc = redo ? glade_command_description (redo) : _("the last action"); tooltip = g_strdup_printf (_("Redo: %s"), desc); gtk_widget_set_tooltip_text (priv->redo_button, tooltip); g_free (tooltip); } static void cancel_autosave (gpointer data) { guint autosave_id = GPOINTER_TO_UINT (data); g_source_remove (autosave_id); } static gboolean autosave_project (gpointer data) { GladeProject *project = (GladeProject *)data; GladeWindow *window = GLADE_WINDOW (glade_app_get_window ()); gchar *display_name; display_name = glade_project_get_name (project); if (glade_project_autosave (project, NULL)) glade_util_flash_message (window->priv->statusbar, window->priv->statusbar_actions_context_id, _("Autosaving '%s'"), display_name); else /* This is problematic, should we be more intrusive and popup a dialog ? */ glade_util_flash_message (window->priv->statusbar, window->priv->statusbar_actions_context_id, _("Error autosaving '%s'"), display_name); g_free (display_name); /* This will remove the source id */ g_object_set_data (G_OBJECT (project), "glade-autosave-id", NULL); return FALSE; } static void project_queue_autosave (GladeWindow *window, GladeProject *project) { if (glade_project_get_path (project) != NULL && glade_project_get_modified (project) && glade_preferences_autosave (window->priv->preferences)) { guint autosave_id = g_timeout_add_seconds (glade_preferences_autosave_seconds (window->priv->preferences), autosave_project, project); g_object_set_data_full (G_OBJECT (project), "glade-autosave-id", GUINT_TO_POINTER (autosave_id), cancel_autosave); } else g_object_set_data (G_OBJECT (project), "glade-autosave-id", NULL); } static void project_cancel_autosave (GladeProject *project) { g_object_set_data (G_OBJECT (project), "glade-autosave-id", NULL); } static void project_changed_cb (GladeProject *project, GladeCommand *command, gboolean execute, GladeWindow *window) { GladeProject *active_project = get_active_project (window); if (project == active_project) refresh_undo_redo (window, project); project_queue_autosave (window, project); } static void project_notify_handler_cb (GladeProject *project, GParamSpec *spec, GladeWindow *window) { GladeProject *active_project = get_active_project (window); if (strcmp (spec->name, "path") == 0) { refresh_title (window); refresh_stack_title_for_project (window, project); } else if (strcmp (spec->name, "format") == 0) { refresh_stack_title_for_project (window, project); } else if (strcmp (spec->name, "modified") == 0) { refresh_title (window); refresh_stack_title_for_project (window, project); } else if (strcmp (spec->name, "read-only") == 0) { refresh_stack_title_for_project (window, project); actions_set_enabled (window, "save", !glade_project_get_readonly (project)); } else if (strcmp (spec->name, "has-selection") == 0 && (project == active_project)) { gboolean has_selection = glade_project_get_has_selection (project); actions_set_enabled (window, "cut", has_selection); actions_set_enabled (window, "copy", has_selection); actions_set_enabled (window, "delete", has_selection); } } static void clipboard_notify_handler_cb (GladeClipboard *clipboard, GParamSpec *spec, GladeWindow *window) { if (strcmp (spec->name, "has-selection") == 0) { actions_set_enabled (window, "paste", glade_clipboard_get_has_selection (clipboard)); } } static void on_pointer_mode_changed (GladeProject *project, GParamSpec *pspec, GladeWindow *window) { GladeProject *active_project = get_active_project (window); if (!active_project) { pointer_mode_actions_set_enabled (window, FALSE); return; } else if (active_project != project) return; if (glade_project_get_pointer_mode (project) == GLADE_POINTER_ADD_WIDGET) return; pointer_mode_actions_set_enabled (window, TRUE); } static void set_sensitivity_according_to_project (GladeWindow *window, GladeProject *project) { gboolean has_selection = glade_project_get_has_selection (project); actions_set_enabled (window, "cut", has_selection); actions_set_enabled (window, "copy", has_selection); actions_set_enabled (window, "delete", has_selection); actions_set_enabled (window, "save", !glade_project_get_readonly (project)); actions_set_enabled (window, "paste", glade_clipboard_get_has_selection (glade_app_get_clipboard ())); } static gchar * get_uri_from_project_path (const gchar *path) { GError *error = NULL; gchar *uri = NULL; if (g_path_is_absolute (path)) uri = g_filename_to_uri (path, NULL, &error); else { gchar *cwd = g_get_current_dir (); gchar *fullpath = g_build_filename (cwd, path, NULL); uri = g_filename_to_uri (fullpath, NULL, &error); g_free (cwd); g_free (fullpath); } if (error) { g_warning ("Could not convert local path \"%s\" to a uri: %s", path, error->message); g_error_free (error); } return uri; } static void recent_add (GladeWindow *window, const gchar *path) { gchar *uri = get_uri_from_project_path (path); GtkRecentData *recent_data; if (!uri) return; recent_data = g_slice_new (GtkRecentData); recent_data->display_name = NULL; recent_data->description = NULL; recent_data->mime_type = "application/x-glade"; recent_data->app_name = (gchar *) g_get_application_name (); recent_data->app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL); recent_data->groups = NULL; recent_data->is_private = FALSE; gtk_recent_manager_add_full (window->priv->recent_manager, uri, recent_data); g_free (uri); g_free (recent_data->app_exec); g_slice_free (GtkRecentData, recent_data); } static void recent_remove (GladeWindow * window, const gchar * path) { gchar *uri = get_uri_from_project_path (path); if (!uri) return; gtk_recent_manager_remove_item (window->priv->recent_manager, uri, NULL); g_free (uri); } /* switch to a project and check if we need to reload it. * */ static void switch_to_project (GladeWindow *window, GladeProject *project) { GladeWindowPrivate *priv = window->priv; GtkWidget *view; view = GTK_WIDGET (glade_design_view_get_from_project (project)); gtk_stack_set_visible_child (priv->view_stack, view); check_reload_project (window, project); } static void on_open_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GtkWidget *filechooser; gchar *path = NULL, *default_path; filechooser = glade_util_file_dialog_new (_("Open\342\200\246"), NULL, GTK_WINDOW (window), GLADE_FILE_DIALOG_ACTION_OPEN); default_path = g_strdup (get_default_path (window)); if (default_path != NULL) { gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filechooser), default_path); g_free (default_path); } if (gtk_dialog_run (GTK_DIALOG (filechooser)) == GTK_RESPONSE_OK) path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (filechooser)); gtk_widget_destroy (filechooser); if (!path) return; glade_window_open_project (window, path); g_free (path); } static gboolean check_loading_project_for_save (GladeProject *project) { if (glade_project_is_loading (project)) { gchar *name = glade_project_get_name (project); glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, _("Project %s is still loading."), name); g_free (name); return TRUE; } return FALSE; } static gboolean do_save (GladeWindow *window, GladeProject *project, const gchar *path) { GError *error = NULL; GladeVerifyFlags verify_flags = 0; gchar *display_path = g_strdup (path); if (glade_preferences_backup (window->priv->preferences) && !glade_project_backup (project, path, NULL)) { if (!glade_util_ui_message (GTK_WIDGET (window), GLADE_UI_ARE_YOU_SURE, NULL, _("Failed to backup existing file, continue saving?"))) { g_free (display_path); return FALSE; } } if (glade_preferences_warn_versioning (window->priv->preferences)) verify_flags |= GLADE_VERIFY_VERSIONS; if (glade_preferences_warn_deprecations (window->priv->preferences)) verify_flags |= GLADE_VERIFY_DEPRECATIONS; if (glade_preferences_warn_unrecognized (window->priv->preferences)) verify_flags |= GLADE_VERIFY_UNRECOGNIZED; if (!glade_project_save_verify (project, path, verify_flags, &error)) { if (error) { /* Reset path so future saves will prompt the file chooser */ glade_project_reset_path (project); glade_util_ui_message (GTK_WIDGET (window), GLADE_UI_ERROR, NULL, _("Failed to save %s: %s"), display_path, error->message); g_error_free (error); } g_free (display_path); return FALSE; } /* Cancel any queued autosave when explicitly saving */ project_cancel_autosave (project); g_free (display_path); return TRUE; } static void save (GladeWindow *window, GladeProject *project, const gchar *path) { gchar *display_name; time_t mtime; GtkWidget *dialog; GtkWidget *button; gint response; if (check_loading_project_for_save (project)) return; /* check for external modification to the project file */ if (glade_project_get_path (project)) { mtime = glade_util_get_file_mtime (glade_project_get_path (project), NULL); if (mtime > glade_project_get_file_mtime (project)) { dialog = gtk_message_dialog_new (GTK_WINDOW (window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, _("The file %s has been modified since reading it"), glade_project_get_path (project)); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("If you save it, all the external changes could be lost. " "Save it anyway?")); gtk_window_set_title (GTK_WINDOW (dialog), ""); button = gtk_button_new_with_mnemonic (_("_Save Anyway")); gtk_button_set_image (GTK_BUTTON (button), gtk_image_new_from_icon_name ("document-save", GTK_ICON_SIZE_BUTTON)); gtk_widget_show (button); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, GTK_RESPONSE_ACCEPT); gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Don't Save"), GTK_RESPONSE_REJECT); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_REJECT); response = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); if (response == GTK_RESPONSE_REJECT) return; } } /* Interestingly; we cannot use `path' after glade_project_reset_path * because we are getting called with glade_project_get_path (project) as an argument. */ if (!do_save (window, project, path)) return; /* Get display_name here, it could have changed with "Save As..." */ display_name = glade_project_get_name (project); recent_add (window, glade_project_get_path (project)); update_default_path (window, glade_project_get_path (project)); /* refresh names */ refresh_title (window); refresh_stack_title_for_project (window, project); glade_util_flash_message (window->priv->statusbar, window->priv->statusbar_actions_context_id, _("Project '%s' saved"), display_name); g_free (display_name); } static gboolean path_has_extension (const gchar *path) { gchar *basename = g_path_get_basename (path); gboolean retval = g_utf8_strrchr (basename, -1, '.') != NULL; g_free (basename); return retval; } static void save_as (GladeWindow *window) { GladeProject *project, *another_project; GtkWidget *filechooser; GtkWidget *dialog; gchar *path = NULL; gchar *project_name; project = glade_design_view_get_project (GLADE_WINDOW_ACTIVE_VIEW (window)); if (project == NULL) return; if (check_loading_project_for_save (project)) return; filechooser = glade_util_file_dialog_new (_("Save As\342\200\246"), project, GTK_WINDOW (window), GLADE_FILE_DIALOG_ACTION_SAVE); if (glade_project_get_path (project)) { gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (filechooser), glade_project_get_path (project)); } else { gchar *default_path = g_strdup (get_default_path (window)); if (default_path != NULL) { gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filechooser), default_path); g_free (default_path); } project_name = glade_project_get_name (project); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (filechooser), project_name); g_free (project_name); } while (gtk_dialog_run (GTK_DIALOG (filechooser)) == GTK_RESPONSE_OK) { path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (filechooser)); /* Check if selected filename has an extension or not */ if (!path_has_extension (path)) { gchar *real_path = g_strconcat (path, ".glade", NULL); g_free (path); path = real_path; /* We added .glade extension!, * check if file exist to avoid overwriting a file without asking */ if (g_file_test (path, G_FILE_TEST_EXISTS)) { /* Set existing filename and let filechooser ask about overwriting */ gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (filechooser), path); g_free (path); path = NULL; continue; } } break; } gtk_widget_destroy (filechooser); if (!path) return; /* checks if selected path is actually writable */ if (glade_util_file_is_writeable (path) == FALSE) { dialog = gtk_message_dialog_new (GTK_WINDOW (window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not save the file %s"), path); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("You do not have the permissions " "necessary to save the file.")); gtk_window_set_title (GTK_WINDOW (dialog), ""); g_signal_connect_swapped (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); gtk_widget_show (dialog); g_free (path); return; } /* checks if another open project is using selected path */ if ((another_project = glade_app_get_project_by_path (path)) != NULL) { if (project != another_project) { glade_util_ui_message (GTK_WIDGET (window), GLADE_UI_ERROR, NULL, _ ("Could not save file %s. Another project with that path is open."), path); g_free (path); return; } } save (window, project, path); g_free (path); } static void on_save_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GladeProject *project; project = glade_design_view_get_project (GLADE_WINDOW_ACTIVE_VIEW (window)); if (project == NULL) { /* Just in case the menu-item or button is not insensitive */ glade_util_ui_message (GTK_WIDGET (window), GLADE_UI_WARN, NULL, _("No open projects to save")); return; } if (glade_project_get_path (project) != NULL) { save (window, project, glade_project_get_path (project)); return; } /* If instead we dont have a path yet, fire up a file selector */ save_as (window); } static void on_save_as_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; save_as (window); } static gboolean confirm_close_project (GladeWindow *window, GladeProject *project) { GtkWidget *dialog; gboolean close = FALSE; gchar *msg, *project_name = NULL; gint ret; project_name = glade_project_get_name (project); msg = g_strdup_printf (_("Save changes to project \"%s\" before closing?"), project_name); dialog = gtk_message_dialog_new (GTK_WINDOW (window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "%s", msg); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", _("Your changes will be lost if you don't save them.")); gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("Close _without Saving"), GTK_RESPONSE_NO, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_YES, NULL); #ifdef G_OS_WIN32 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_YES, GTK_RESPONSE_CANCEL, GTK_RESPONSE_NO, -1); #endif gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES); ret = gtk_dialog_run (GTK_DIALOG (dialog)); switch (ret) { case GTK_RESPONSE_YES: /* if YES we save the project: note we cannot use save_cb * since it saves the current project, while the modified * project we are saving may be not the current one. */ if (glade_project_get_path (project) != NULL) { close = do_save (window, project, glade_project_get_path (project)); } else { GtkWidget *filechooser; gchar *path = NULL; gchar *default_path; filechooser = glade_util_file_dialog_new (_("Save\342\200\246"), project, GTK_WINDOW (window), GLADE_FILE_DIALOG_ACTION_SAVE); default_path = g_strdup (get_default_path (window)); if (default_path != NULL) { gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filechooser), default_path); g_free (default_path); } gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (filechooser), project_name); if (gtk_dialog_run (GTK_DIALOG (filechooser)) == GTK_RESPONSE_OK) path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (filechooser)); gtk_widget_destroy (filechooser); if (!path) break; save (window, project, path); g_free (path); close = FALSE; } break; case GTK_RESPONSE_NO: close = TRUE; break; case GTK_RESPONSE_CANCEL: case GTK_RESPONSE_DELETE_EVENT: close = FALSE; break; default: g_assert_not_reached (); close = FALSE; } g_free (msg); g_free (project_name); gtk_widget_destroy (dialog); return close; } static void close_project (GladeWindow *window, GladeProject *project) { GladeDesignView *view = glade_design_view_get_from_project (project); GladeWindowPrivate *priv = window->priv; /* Cancel any queued autosave activity */ project_cancel_autosave (project); if (glade_project_is_loading (project)) { glade_project_cancel_load (project); return; } g_signal_handlers_disconnect_by_func (project, project_notify_handler_cb, window); g_signal_handlers_disconnect_by_func (project, project_selection_changed_cb, window); g_signal_handlers_disconnect_by_func (project, project_targets_changed_cb, window); g_signal_handlers_disconnect_by_func (project, project_changed_cb, window); g_signal_handlers_disconnect_by_func (project, on_pointer_mode_changed, window); /* remove inspector first */ gtk_container_remove (GTK_CONTAINER (priv->inspectors_stack), g_object_get_data (G_OBJECT (view), "glade-window-view-inspector")); /* then the main view */ gtk_container_remove (GTK_CONTAINER (priv->view_stack), GTK_WIDGET (view)); clean_actions (window); /* Refresh the editor and some of the actions */ project_selection_changed_cb (project, window); on_pointer_mode_changed (project, NULL, window); glade_app_remove_project (project); refresh_title (window); if (!glade_app_get_projects ()) gtk_stack_set_visible_child (priv->stack, priv->start_page); if (GLADE_WINDOW_ACTIVE_VIEW (window)) set_sensitivity_according_to_project (window, glade_design_view_get_project (GLADE_WINDOW_ACTIVE_VIEW (window))); else project_actions_set_enabled (window, FALSE); } static void on_close_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GladeDesignView *view; GladeProject *project; gboolean close; view = GLADE_WINDOW_ACTIVE_VIEW (window); project = glade_design_view_get_project (view); if (view == NULL) return; if (glade_project_get_modified (project)) { close = confirm_close_project (window, project); if (!close) return; } close_project (window, project); } static void on_copy_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GladeProject *project; if (!GLADE_WINDOW_ACTIVE_VIEW (window)) return; project = glade_design_view_get_project (GLADE_WINDOW_ACTIVE_VIEW (window)); glade_project_copy_selection (project); } static void on_cut_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GladeProject *project; if (!GLADE_WINDOW_ACTIVE_VIEW (window)) return; project = glade_design_view_get_project (GLADE_WINDOW_ACTIVE_VIEW (window)); glade_project_command_cut (project); } static void on_paste_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GtkWidget *placeholder; GladeProject *project; if (!GLADE_WINDOW_ACTIVE_VIEW (window)) return; project = glade_design_view_get_project (GLADE_WINDOW_ACTIVE_VIEW (window)); placeholder = glade_util_get_placeholder_from_pointer (GTK_CONTAINER (window)); /* If this action is activated with a key binging (ctrl-v) the widget will be * pasted over the placeholder below the default pointer. */ glade_project_command_paste (project, placeholder ? GLADE_PLACEHOLDER (placeholder) : NULL); } static void on_delete_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GladeProject *project; if (!GLADE_WINDOW_ACTIVE_VIEW (window)) return; project = glade_design_view_get_project (GLADE_WINDOW_ACTIVE_VIEW (window)); glade_project_command_delete (project); } static void stack_visible_child_next_prev (GladeWindow *window, gboolean next) { GladeDesignView *view; GList *children, *node; if (!(view = GLADE_WINDOW_ACTIVE_VIEW (window))) return; children = gtk_container_get_children (GTK_CONTAINER (window->priv->view_stack)); if ((node = g_list_find (children, view)) && ((next && node->next) || (!next && node->prev))) gtk_stack_set_visible_child (window->priv->view_stack, (next) ? node->next->data : node->prev->data); g_list_free (children); } static void on_previous_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; stack_visible_child_next_prev (window, FALSE); } static void on_next_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; stack_visible_child_next_prev (window, TRUE); } static void on_properties_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GladeProject *project; if (!GLADE_WINDOW_ACTIVE_VIEW (window)) return; project = glade_design_view_get_project (GLADE_WINDOW_ACTIVE_VIEW (window)); glade_project_properties (project); } static void on_undo_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GladeProject *active_project = get_active_project (window); if (!active_project) { g_warning ("undo should not be sensitive: we don't have a project"); return; } glade_project_undo (active_project); } static void on_redo_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GladeProject *active_project = get_active_project (window); if (!active_project) { g_warning ("redo should not be sensitive: we don't have a project"); return; } glade_project_redo (active_project); } static void doc_search_cb (GladeEditor *editor, const gchar *book, const gchar *page, const gchar *search, GladeWindow *window) { glade_util_search_devhelp (book, page, search); } static void on_stack_visible_child_notify (GObject *gobject, GParamSpec *pspec, GladeWindow *window) { GladeDesignView *view = GLADE_WINDOW_ACTIVE_VIEW (window); GladeWindowPrivate *priv = window->priv; if (view) { GladeProject *project = glade_design_view_get_project (view); /* switch to the project's inspector */ gtk_stack_set_visible_child (priv->inspectors_stack, g_object_get_data (G_OBJECT (view), "glade-window-view-inspector")); glade_adaptor_chooser_set_project (priv->adaptor_chooser, project); set_sensitivity_according_to_project (window, project); refresh_undo_redo (window, project); /* Refresh the editor and some of the actions */ project_selection_changed_cb (project, window); on_pointer_mode_changed (project, NULL, window); } } static void on_open_recent_action_item_activated (GtkRecentChooser *chooser, GladeWindow *window) { gchar *uri, *path; GError *error = NULL; uri = gtk_recent_chooser_get_current_uri (chooser); path = g_filename_from_uri (uri, NULL, NULL); if (error) { g_warning ("Could not convert uri \"%s\" to a local path: %s", uri, error->message); g_error_free (error); return; } glade_window_open_project (window, path); g_free (uri); g_free (path); } static void on_reference_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { if (glade_util_have_devhelp ()) { glade_util_search_devhelp ("gladeui", NULL, NULL); return; } /* fallback to displaying online developer manual */ glade_util_url_show (URL_DEVELOPER_MANUAL); } static void on_preferences_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; gtk_widget_show (GTK_WIDGET (window->priv->preferences)); } static void on_about_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GladeWindowPrivate *priv = window->priv; gtk_about_dialog_set_version (GTK_ABOUT_DIALOG (priv->about_dialog), PACKAGE_VERSION); gtk_window_present (priv->about_dialog); } enum { TARGET_URI_LIST }; static GtkTargetEntry drop_types[] = { {"text/uri-list", 0, TARGET_URI_LIST} }; static void drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time, GladeWindow *window) { gchar **uris, **str; const guchar *data; if (info != TARGET_URI_LIST) return; data = gtk_selection_data_get_data (selection_data); uris = g_uri_list_extract_uris ((gchar *) data); for (str = uris; *str; str++) { GError *error = NULL; gchar *path = g_filename_from_uri (*str, NULL, &error); if (path) { glade_window_open_project (window, path); } else { g_warning ("Could not convert uri to local path: %s", error->message); g_error_free (error); } g_free (path); } g_strfreev (uris); } static gboolean delete_event (GtkWindow *w, GdkEvent *event, GladeWindow *window) { g_action_group_activate_action (window->priv->actions, "quit", NULL); /* return TRUE to stop other handlers */ return TRUE; } static void add_project (GladeWindow *window, GladeProject *project, gboolean for_file) { GladeWindowPrivate *priv = window->priv; GtkWidget *view, *inspector; g_return_if_fail (GLADE_IS_PROJECT (project)); /* Create a new view for project */ view = glade_design_view_new (project); gtk_stack_set_visible_child (priv->stack, priv->center_paned); g_signal_connect (G_OBJECT (project), "notify::modified", G_CALLBACK (project_notify_handler_cb), window); g_signal_connect (G_OBJECT (project), "notify::path", G_CALLBACK (project_notify_handler_cb), window); g_signal_connect (G_OBJECT (project), "notify::format", G_CALLBACK (project_notify_handler_cb), window); g_signal_connect (G_OBJECT (project), "notify::has-selection", G_CALLBACK (project_notify_handler_cb), window); g_signal_connect (G_OBJECT (project), "notify::read-only", G_CALLBACK (project_notify_handler_cb), window); g_signal_connect (G_OBJECT (project), "notify::pointer-mode", G_CALLBACK (on_pointer_mode_changed), window); g_signal_connect (G_OBJECT (project), "selection-changed", G_CALLBACK (project_selection_changed_cb), window); g_signal_connect (G_OBJECT (project), "targets-changed", G_CALLBACK (project_targets_changed_cb), window); g_signal_connect (G_OBJECT (project), "changed", G_CALLBACK (project_changed_cb), window); /* create inspector */ inspector = glade_inspector_new (); g_object_set_data (G_OBJECT (view), "glade-window-view-inspector", inspector); glade_inspector_set_project (GLADE_INSPECTOR (inspector), project); gtk_container_add (GTK_CONTAINER (priv->inspectors_stack), inspector); gtk_widget_show (inspector); set_sensitivity_according_to_project (window, project); project_actions_set_enabled (window, TRUE); /* Pass ownership of the project to the app */ glade_app_add_project (project); g_object_unref (project); /* Add view to stack */ gtk_container_add (GTK_CONTAINER (priv->view_stack), view); gtk_widget_show (view); gtk_stack_set_visible_child (priv->view_stack, view); refresh_stack_title_for_project (window, project); refresh_title (window); } static void on_registration_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; gtk_window_present (GTK_WINDOW (window->priv->registration)); } static gboolean on_undo_button_button_press_event (GtkWidget *widget, GdkEvent *event, GladeWindow *window) { GladeProject *project = get_active_project (window); if (project && event->button.button == 3) gtk_menu_popup_at_widget (GTK_MENU (glade_project_undo_items (project)), widget, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_SOUTH_WEST, event); return FALSE; } static gboolean on_redo_button_button_press_event (GtkWidget *widget, GdkEvent *event, GladeWindow *window) { GladeProject *project = get_active_project (window); if (project && event->button.button == 3) gtk_menu_popup_at_widget (GTK_MENU (glade_project_redo_items (project)), widget, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_SOUTH_WEST, event); return FALSE; } void glade_window_new_project (GladeWindow *window) { GladeProject *project; g_return_if_fail (GLADE_IS_WINDOW (window)); project = glade_project_new (); if (!project) { glade_util_ui_message (GTK_WIDGET (window), GLADE_UI_ERROR, NULL, _("Could not create a new project.")); return; } add_project (window, project, FALSE); } static void on_new_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { glade_window_new_project (data); } static gboolean open_project (GladeWindow *window, const gchar *path) { GladeProject *project; project = glade_project_new (); add_project (window, project, TRUE); update_default_path (window, path); if (!glade_project_load_from_file (project, path)) { close_project (window, project); recent_remove (window, path); return FALSE; } /* increase project popularity */ recent_add (window, glade_project_get_path (project)); return TRUE; } static void check_reload_project (GladeWindow *window, GladeProject *project) { gchar *path; GtkWidget *dialog; GtkWidget *button; gint response; /* Reopen the project if it has external modifications. * Prompt for permission to reopen. */ if ((glade_util_get_file_mtime (glade_project_get_path (project), NULL) <= glade_project_get_file_mtime (project))) { return; } if (glade_project_get_modified (project)) { dialog = gtk_message_dialog_new (GTK_WINDOW (window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, _("The project %s has unsaved changes"), glade_project_get_path (project)); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _ ("If you reload it, all unsaved changes " "could be lost. Reload it anyway?")); } else { dialog = gtk_message_dialog_new (GTK_WINDOW (window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, _ ("The project file %s has been externally modified"), glade_project_get_path (project)); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _ ("Do you want to reload the project?")); } gtk_window_set_title (GTK_WINDOW (dialog), ""); button = gtk_button_new_with_mnemonic (_("_Reload")); gtk_button_set_image (GTK_BUTTON (button), gtk_image_new_from_icon_name ("view-refresh", GTK_ICON_SIZE_BUTTON)); gtk_widget_show (button); gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_REJECT); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, GTK_RESPONSE_ACCEPT); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_REJECT); response = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); if (response == GTK_RESPONSE_REJECT) { return; } /* Reopen */ path = g_strdup (glade_project_get_path (project)); close_project (window, project); open_project (window, path); g_free (path); } /** * glade_window_open_project: * @window: a #GladeWindow * @path: the filesystem path of the project * * Opens a project file. If the project is already open, switch to that * project. * * Returns: #TRUE if the project was opened */ gboolean glade_window_open_project (GladeWindow *window, const gchar *path) { GladeProject *project; g_return_val_if_fail (GLADE_IS_WINDOW (window), FALSE); g_return_val_if_fail (path != NULL, FALSE); /* dont allow a project to be opened twice */ project = glade_app_get_project_by_path (path); if (project) { /* just switch to the project */ switch_to_project (window, project); return TRUE; } else { return open_project (window, path); } } static void glade_window_dispose (GObject *object) { GladeWindow *window = GLADE_WINDOW (object); g_clear_object (&window->priv->app); g_clear_object (&window->priv->registration); G_OBJECT_CLASS (glade_window_parent_class)->dispose (object); } static void glade_window_finalize (GObject *object) { g_free (GLADE_WINDOW (object)->priv->default_path); G_OBJECT_CLASS (glade_window_parent_class)->finalize (object); } static gboolean glade_window_configure_event (GtkWidget *widget, GdkEventConfigure *event) { GladeWindow *window = GLADE_WINDOW (widget); gboolean retval; gboolean is_maximized; GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); is_maximized = gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_MAXIMIZED; if (!is_maximized) { window->priv->position.width = event->width; window->priv->position.height = event->height; } retval = GTK_WIDGET_CLASS (glade_window_parent_class)->configure_event (widget, event); if (!is_maximized) { gtk_window_get_position (GTK_WINDOW (widget), &window->priv->position.x, &window->priv->position.y); } return retval; } static void key_file_set_window_position (GKeyFile *config, GdkRectangle *position, const char *id, gboolean maximized) { char *key_x, *key_y, *key_width, *key_height, *key_maximized; key_x = g_strdup_printf ("%s-" CONFIG_KEY_X, id); key_y = g_strdup_printf ("%s-" CONFIG_KEY_Y, id); key_width = g_strdup_printf ("%s-" CONFIG_KEY_WIDTH, id); key_height = g_strdup_printf ("%s-" CONFIG_KEY_HEIGHT, id); key_maximized = g_strdup_printf ("%s-" CONFIG_KEY_MAXIMIZED, id); if (position->x > G_MININT) g_key_file_set_integer (config, CONFIG_GROUP_WINDOWS, key_x, position->x); if (position->y > G_MININT) g_key_file_set_integer (config, CONFIG_GROUP_WINDOWS, key_y, position->y); g_key_file_set_integer (config, CONFIG_GROUP_WINDOWS, key_width, position->width); g_key_file_set_integer (config, CONFIG_GROUP_WINDOWS, key_height, position->height); g_key_file_set_boolean (config, CONFIG_GROUP_WINDOWS, key_maximized, maximized); g_free (key_maximized); g_free (key_height); g_free (key_width); g_free (key_y); g_free (key_x); } static void save_windows_config (GladeWindow *window, GKeyFile *config) { GladeWindowPrivate *priv = window->priv; GdkWindow *gdk_window; gboolean maximized; gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); maximized = gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_MAXIMIZED; key_file_set_window_position (config, &priv->position, "main", maximized); } static void save_paned_position (GKeyFile *config, GtkWidget *paned, const gchar *name) { g_key_file_set_integer (config, name, "position", gtk_paned_get_position (GTK_PANED (paned))); } static void glade_window_config_save (GladeWindow * window) { GKeyFile *config = glade_app_get_config (); save_windows_config (window, config); /* Save main window paned positions */ save_paned_position (config, window->priv->center_paned, "center_pane"); save_paned_position (config, window->priv->left_paned, "left_pane"); glade_preferences_save (window->priv->preferences, config); glade_app_config_save (); } static int key_file_get_int (GKeyFile *config, const char *group, const char *key, int default_value) { if (g_key_file_has_key (config, group, key, NULL)) return g_key_file_get_integer (config, group, key, NULL); else return default_value; } static void key_file_get_window_position (GKeyFile *config, const char *id, GdkRectangle *pos, gboolean *maximized) { char *key_x, *key_y, *key_width, *key_height, *key_maximized; key_x = g_strdup_printf ("%s-" CONFIG_KEY_X, id); key_y = g_strdup_printf ("%s-" CONFIG_KEY_Y, id); key_width = g_strdup_printf ("%s-" CONFIG_KEY_WIDTH, id); key_height = g_strdup_printf ("%s-" CONFIG_KEY_HEIGHT, id); key_maximized = g_strdup_printf ("%s-" CONFIG_KEY_MAXIMIZED, id); pos->x = key_file_get_int (config, CONFIG_GROUP_WINDOWS, key_x, pos->x); pos->y = key_file_get_int (config, CONFIG_GROUP_WINDOWS, key_y, pos->y); pos->width = key_file_get_int (config, CONFIG_GROUP_WINDOWS, key_width, pos->width); pos->height = key_file_get_int (config, CONFIG_GROUP_WINDOWS, key_height, pos->height); if (maximized) { if (g_key_file_has_key (config, CONFIG_GROUP_WINDOWS, key_maximized, NULL)) *maximized = g_key_file_get_boolean (config, CONFIG_GROUP_WINDOWS, key_maximized, NULL); else *maximized = FALSE; } g_free (key_x); g_free (key_y); g_free (key_width); g_free (key_height); g_free (key_maximized); } static void load_paned_position (GKeyFile *config, GtkWidget *pane, const gchar *name, gint default_position) { gtk_paned_set_position (GTK_PANED (pane), key_file_get_int (config, name, "position", default_position)); } static gboolean fix_paned_positions_idle (GladeWindow *window) { /* When initially maximized/fullscreened we need to deffer this operation */ GKeyFile *config = glade_app_get_config (); load_paned_position (config, window->priv->left_paned, "left_pane", 200); load_paned_position (config, window->priv->center_paned, "center_pane", 400); return FALSE; } static void glade_window_set_initial_size (GladeWindow *window, GKeyFile *config) { GdkRectangle position = { G_MININT, G_MININT, GLADE_WINDOW_DEFAULT_WIDTH, GLADE_WINDOW_DEFAULT_HEIGHT }; gboolean maximized; key_file_get_window_position (config, "main", &position, &maximized); if (maximized) { gtk_window_maximize (GTK_WINDOW (window)); g_timeout_add (200, (GSourceFunc) fix_paned_positions_idle, window); } if (position.width <= 0 || position.height <= 0) { position.width = GLADE_WINDOW_DEFAULT_WIDTH; position.height = GLADE_WINDOW_DEFAULT_HEIGHT; } gtk_window_set_default_size (GTK_WINDOW (window), position.width, position.height); if (position.x > G_MININT && position.y > G_MININT) gtk_window_move (GTK_WINDOW (window), position.x, position.y); } static void glade_window_config_load (GladeWindow *window) { GKeyFile *config = glade_app_get_config (); /* Initial main dimensions */ glade_window_set_initial_size (window, config); /* Paned positions */ load_paned_position (config, window->priv->left_paned, "left_pane", 200); load_paned_position (config, window->priv->center_paned, "center_pane", 400); /* Intro button */ if (g_key_file_get_boolean (config, CONFIG_INTRO_GROUP, CONFIG_INTRO_DONE, FALSE)) gtk_widget_hide (window->priv->intro_button); } static void on_quit_action_activate (GSimpleAction *action, GVariant *parameter, gpointer data) { GladeWindow *window = data; GList *list, *projects; projects = g_list_copy (glade_app_get_projects ()); for (list = projects; list; list = list->next) { GladeProject *project = GLADE_PROJECT (list->data); if (glade_project_get_modified (project)) { gboolean quit = confirm_close_project (window, project); if (!quit) { g_list_free (projects); return; } } } for (list = projects; list; list = list->next) { GladeProject *project = GLADE_PROJECT (glade_app_get_projects ()->data); close_project (window, project); } glade_window_config_save (window); g_list_free (projects); gtk_main_quit (); } static void on_pointer_select_action_activate (GSimpleAction *action, GVariant *p, gpointer data) { glade_project_set_pointer_mode (get_active_project (data), GLADE_POINTER_SELECT); } static void on_pointer_align_edit_action_activate (GSimpleAction *action, GVariant *p, gpointer data) { glade_project_set_pointer_mode (get_active_project (data), GLADE_POINTER_ALIGN_EDIT); } static void on_pointer_drag_resize_action_activate (GSimpleAction *action, GVariant *p, gpointer data) { glade_project_set_pointer_mode (get_active_project (data), GLADE_POINTER_DRAG_RESIZE); } static void on_pointer_margin_edit_action_activate (GSimpleAction *action, GVariant *p, gpointer data) { glade_project_set_pointer_mode (get_active_project (data), GLADE_POINTER_MARGIN_EDIT); } static void on_intro_action_activate (GSimpleAction *action, GVariant *p, gpointer data) { GladeWindow *window = data; gtk_widget_show (window->priv->intro_button); glade_intro_play (window->priv->intro); } static void glade_window_init (GladeWindow *window) { GladeWindowPrivate *priv; window->priv = priv = glade_window_get_instance_private (window); priv->default_path = NULL; /* Init preferences first, this has to be done before anything initializes * the real GladeApp, so that catalog paths are loaded correctly before we * continue. * * This should be fixed so that dynamic addition of catalogs at runtime * is supported. */ priv->preferences = (GladePreferences *)glade_preferences_new (); glade_preferences_load (window->priv->preferences, glade_app_get_config ()); /* We need this for the icons to be available */ glade_init (); gtk_widget_init_template (GTK_WIDGET (window)); gtk_box_set_homogeneous (GTK_BOX (priv->open_button_box), FALSE); gtk_box_set_homogeneous (GTK_BOX (priv->save_button_box), FALSE); priv->registration = glade_registration_new (); } static void glade_window_action_handler (GladeWindow *window, const gchar *name) { GladeWindowPrivate *priv = window->priv; GAction *action; if ((action = g_action_map_lookup_action (G_ACTION_MAP (priv->actions), name))) g_action_activate (action, NULL); } static void switch_foreach (GtkWidget *widget, gpointer data) { GtkWidget *parent = gtk_widget_get_parent (widget); gint pos; gtk_container_child_get (GTK_CONTAINER(parent), widget, "position", &pos, NULL); if (pos == GPOINTER_TO_INT (data)) gtk_stack_set_visible_child (GTK_STACK (parent), widget); } static void glade_window_switch_handler (GladeWindow *window, gint index) { gtk_container_foreach (GTK_CONTAINER (window->priv->view_stack), switch_foreach, GINT_TO_POINTER (index)); } static gboolean intro_continue (gpointer intro) { glade_intro_play (intro); return G_SOURCE_REMOVE; } static void on_intro_project_add_widget (GladeProject *project, GladeWidget *widget, GladeWindow *window) { GladeWidgetAdaptor *adaptor = glade_widget_get_adaptor (widget); if (glade_widget_adaptor_get_object_type (adaptor) == window->priv->new_type) { g_idle_add (intro_continue, window->priv->intro); if (window->priv->new_type == GTK_TYPE_BUTTON) g_signal_handlers_disconnect_by_func (project, on_intro_project_add_widget, window); } } static void on_user_new_action_activate (GSimpleAction *simple, GVariant *parameter, GladeWindow *window) { g_signal_connect (get_active_project (window), "add-widget", G_CALLBACK (on_intro_project_add_widget), window); glade_intro_play (window->priv->intro); g_signal_handlers_disconnect_by_func (simple, on_user_new_action_activate, window); } static void on_intro_show_node (GladeIntro *intro, const gchar *node, GtkWidget *widget, GladeWindow *window) { GladeWindowPrivate *priv = window->priv; if (!g_strcmp0 (node, "new-project")) { /* Create two new project to make the project switcher visible */ g_action_group_activate_action (window->priv->actions, "new", NULL); g_action_group_activate_action (window->priv->actions, "new", NULL); } else if (!g_strcmp0 (node, "add-project")) { GAction *new_action = g_action_map_lookup_action (G_ACTION_MAP (priv->actions), "new"); g_signal_connect (new_action, "activate", G_CALLBACK (on_user_new_action_activate), window); } else if (!g_strcmp0 (node, "add-window")) { window->priv->new_type = GTK_TYPE_WINDOW; } else if (!g_strcmp0 (node, "add-grid")) { window->priv->new_type = GTK_TYPE_GRID; } else if (!g_strcmp0 (node, "add-button")) { window->priv->new_type = GTK_TYPE_BUTTON; } else if (!g_strcmp0 (node, "search") || !g_strcmp0 (node, "others")) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); } else if (!g_strcmp0 (node, "gtk")) { GList *children; if ((children = gtk_container_get_children (GTK_CONTAINER (widget)))) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (children->data), TRUE); g_list_free (children); } } static void on_intro_hide_node (GladeIntro *intro, const gchar *node, GtkWidget *widget, GladeWindow *window) { if (!g_strcmp0 (node, "search") || !g_strcmp0 (node, "others")) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE); } else if (!g_strcmp0 (node, "gtk")) { GList *children; if ((children = gtk_container_get_children (GTK_CONTAINER (widget)))) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (children->data), FALSE); g_list_free (children); } else if (!g_strcmp0 (node, "done")) { gtk_widget_hide (window->priv->intro_button); g_key_file_set_boolean (glade_app_get_config (), CONFIG_INTRO_GROUP, CONFIG_INTRO_DONE, TRUE); } else if (!g_strcmp0 (node, "add-project") || !g_strcmp0 (node, "add-window") || !g_strcmp0 (node, "add-grid") || !g_strcmp0 (node, "add-button")) glade_intro_pause (window->priv->intro); } #define ADD_NODE(n,w,P,d,t) glade_intro_script_add (window->priv->intro, n, w, t, GLADE_INTRO_##P, d) static void glade_window_populate_intro (GladeWindow *window) { ADD_NODE (NULL, "intro-button", BOTTOM, 5, _("Hello, I will show you what's new in Glade")); ADD_NODE (NULL, "headerbar", BOTTOM, 6, _("The menubar and toolbar were merged in the headerbar")); ADD_NODE (NULL, "open-button", BOTTOM, 3, _("You can open a project")); ADD_NODE (NULL, "recent-button", BOTTOM, 2, _("find recently used")); ADD_NODE (NULL, "new-button", BOTTOM, 2, _("or create a new one")); ADD_NODE ("new-project", NULL, NONE, .75, NULL); ADD_NODE (NULL, "undo-button", BOTTOM, 2, _("Undo")); ADD_NODE (NULL, "redo-button", BOTTOM, 2, _("Redo")); ADD_NODE (NULL, "project-switcher", BOTTOM, 3, _("Project switcher")); ADD_NODE (NULL, "save-button", BOTTOM, 4, _("and Save button are directly accessible in the headerbar")); ADD_NODE (NULL, "save-as-button", BOTTOM, 2, _("just like Save As")); ADD_NODE (NULL, "properties-button", BOTTOM, 2, _("project properties")); ADD_NODE (NULL, "menu-button", BOTTOM, 3, _("and less commonly used actions")); ADD_NODE (NULL, "inspector", CENTER, 3, _("The object inspector took the palette's place")); ADD_NODE (NULL, "editor", CENTER, 3, _("To free up space for the property editor")); ADD_NODE (NULL, "adaptor-chooser", BOTTOM, 4, _("The palette was replaced with a new object chooser")); ADD_NODE ("search", "adaptor-search-button", RIGHT, 3, _("Where you can search all supported classes")); ADD_NODE ("gtk", "adaptor-gtk-buttonbox", BOTTOM, 2.5, _("investigate GTK+ object groups")); ADD_NODE ("others", "adaptor-others-button", RIGHT, 4, _("and find classes introduced by other libraries")); ADD_NODE (NULL, "intro-button", BOTTOM, 6, _("OK, now that we are done with the overview, let's start with the new workflow")); ADD_NODE ("add-project", "intro-button", BOTTOM, 4, _("First of all, create a new project")); ADD_NODE ("add-window", "intro-button", BOTTOM, 6, _("OK, now add a GtkWindow using the new widget chooser or by double clicking on the workspace")); ADD_NODE (NULL, "intro-button", BOTTOM, 2, _("Excellent!")); ADD_NODE (NULL, "intro-button", BOTTOM, 5, _("BTW, did you know you can double click on any placeholder to create widgets?")); ADD_NODE ("add-grid", "intro-button", BOTTOM, 3, _("Try adding a grid")); ADD_NODE ("add-button", "intro-button", BOTTOM, 3, _("and a button")); ADD_NODE (NULL, "intro-button", BOTTOM, 3, _("Quite easy! Isn't it?")); ADD_NODE ("done", "intro-button", BOTTOM, 2, _("Enjoy!")); g_signal_connect (window->priv->intro, "show-node", G_CALLBACK (on_intro_show_node), window); g_signal_connect (window->priv->intro, "hide-node", G_CALLBACK (on_intro_hide_node), window); } static void glade_window_constructed (GObject *object) { static GActionEntry actions[] = { { "open", on_open_action_activate, NULL, NULL, NULL }, { "new", on_new_action_activate, NULL, NULL, NULL }, { "registration", on_registration_action_activate, NULL, NULL, NULL }, { "intro", on_intro_action_activate, NULL, NULL, NULL }, { "reference", on_reference_action_activate, NULL, NULL, NULL }, { "preferences", on_preferences_action_activate, NULL, NULL, NULL }, { "about", on_about_action_activate, NULL, NULL, NULL }, { "quit", on_quit_action_activate, NULL, NULL, NULL }, /* Project actions */ { "close", on_close_action_activate, NULL, NULL, NULL }, { "save", on_save_action_activate, NULL, NULL, NULL }, { "save_as", on_save_as_action_activate, NULL, NULL, NULL }, { "properties", on_properties_action_activate, NULL, NULL, NULL }, { "undo", on_undo_action_activate, NULL, NULL, NULL }, { "redo", on_redo_action_activate, NULL, NULL, NULL }, { "cut", on_cut_action_activate, NULL, NULL, NULL }, { "copy", on_copy_action_activate, NULL, NULL, NULL }, { "paste", on_paste_action_activate, NULL, NULL, NULL }, { "delete", on_delete_action_activate, NULL, NULL, NULL }, { "previous", on_previous_action_activate, NULL, NULL, NULL }, { "next", on_next_action_activate, NULL, NULL, NULL }, /* Pointer mode actions */ { "select", on_pointer_select_action_activate, NULL, NULL, NULL }, { "drag_resize", on_pointer_drag_resize_action_activate, NULL, NULL, NULL }, { "margin_edit", on_pointer_margin_edit_action_activate, NULL, NULL, NULL }, { "align_edit", on_pointer_align_edit_action_activate, NULL, NULL, NULL }, }; GladeWindow *window = GLADE_WINDOW (object); GladeWindowPrivate *priv = window->priv; gchar *version; /* Chain up... */ G_OBJECT_CLASS (glade_window_parent_class)->constructed (object); /* Init Glade version */ version = g_strdup_printf ("%d.%d.%d", GLADE_MAJOR_VERSION, GLADE_MINOR_VERSION, GLADE_MICRO_VERSION); gtk_label_set_text (priv->version_label, version); g_free (version); /* recent files */ priv->recent_manager = gtk_recent_manager_get_default (); /* Setup Actions */ priv->actions = (GActionGroup *) g_simple_action_group_new (); g_action_map_add_action_entries (G_ACTION_MAP (priv->actions), actions, G_N_ELEMENTS (actions), window); gtk_widget_insert_action_group (GTK_WIDGET (window), "app", priv->actions); /* status bar */ priv->statusbar_context_id = gtk_statusbar_get_context_id (GTK_STATUSBAR (priv->statusbar), "general"); priv->statusbar_menu_context_id = gtk_statusbar_get_context_id (GTK_STATUSBAR (priv->statusbar), "menu"); priv->statusbar_actions_context_id = gtk_statusbar_get_context_id (GTK_STATUSBAR (priv->statusbar), "actions"); /* support for opening a file by dragging onto the project window */ gtk_drag_dest_set (GTK_WIDGET (window), GTK_DEST_DEFAULT_ALL, drop_types, G_N_ELEMENTS (drop_types), GDK_ACTION_COPY | GDK_ACTION_MOVE); g_signal_connect (G_OBJECT (window), "drag-data-received", G_CALLBACK (drag_data_received), window); g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (delete_event), window); /* GtkWindow events */ g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (glade_utils_hijack_key_press), window); /* Load configuration, we need the list of extra catalog paths before creating * the GladeApp */ glade_window_config_load (window); /* Create GladeApp singleton, this will load all catalogs */ priv->app = glade_app_new (); glade_app_set_window (GTK_WIDGET (window)); /* Clipboard signals */ g_signal_connect (G_OBJECT (glade_app_get_clipboard ()), "notify::has-selection", G_CALLBACK (clipboard_notify_handler_cb), window); priv->intro = glade_intro_new (GTK_WINDOW (window)); glade_window_populate_intro (window); refresh_title (window); project_actions_set_enabled (window, FALSE); } #define DEFINE_ACTION_SIGNAL(klass, name, handler,...) \ g_signal_new_class_handler (name, \ G_TYPE_FROM_CLASS (klass), \ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, \ G_CALLBACK (handler), \ NULL, NULL, NULL, \ G_TYPE_NONE, __VA_ARGS__) static void glade_window_class_init (GladeWindowClass *klass) { GObjectClass *object_class; GtkWidgetClass *widget_class; GtkCssProvider *provider; object_class = G_OBJECT_CLASS (klass); widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = glade_window_constructed; object_class->dispose = glade_window_dispose; object_class->finalize = glade_window_finalize; widget_class->configure_event = glade_window_configure_event; DEFINE_ACTION_SIGNAL (klass, "glade-action", glade_window_action_handler, 1, G_TYPE_STRING); DEFINE_ACTION_SIGNAL (klass, "glade-switch", glade_window_switch_handler, 1, G_TYPE_INT); gtk_widget_class_set_css_name (widget_class, "GladeWindow"); provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (provider, "/org/gnome/glade/glade-window.css"); gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref (provider); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/glade/glade.glade"); /* Internal children */ gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, adaptor_chooser); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, headerbar); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, project_switcher); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, about_dialog); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, start_page); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, version_label); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, intro_button); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, center_paned); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, left_paned); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, open_button_box); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, save_button_box); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, stack); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, view_stack); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, inspectors_stack); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, editor); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, statusbar); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, toolbar); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, undo_button); gtk_widget_class_bind_template_child_private (widget_class, GladeWindow, redo_button); /* Callbacks */ gtk_widget_class_bind_template_callback (widget_class, on_stack_visible_child_notify); gtk_widget_class_bind_template_callback (widget_class, on_open_recent_action_item_activated); gtk_widget_class_bind_template_callback (widget_class, on_undo_button_button_press_event); gtk_widget_class_bind_template_callback (widget_class, on_redo_button_button_press_event); } GtkWidget * glade_window_new (void) { return g_object_new (GLADE_TYPE_WINDOW, NULL); } void glade_window_check_devhelp (GladeWindow *window) { g_return_if_fail (GLADE_IS_WINDOW (window)); if (glade_util_have_devhelp ()) g_signal_connect (glade_app_get (), "doc-search", G_CALLBACK (doc_search_cb), window); } void glade_window_registration_notify_user (GladeWindow *window) { gboolean skip_reminder, completed; GladeWindowPrivate *priv; g_return_if_fail (GLADE_IS_WINDOW (window)); priv = window->priv; g_object_get (priv->registration, "completed", &completed, "skip-reminder", &skip_reminder, NULL); if (!completed && !skip_reminder) { GtkWidget *dialog, *check; dialog = gtk_message_dialog_new (GTK_WINDOW (glade_app_get_window ()), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", /* translators: Primary message of a dialog used to notify the user about the survey */ _("We are conducting a user survey\n would you like to take it now?")); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", /* translators: Secondary text of a dialog used to notify the user about the survey */ _("If not, you can always find it in the Help menu.")); check = gtk_check_button_new_with_mnemonic (_("_Do not show this dialog again")); gtk_box_pack_end (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), check, FALSE, FALSE, 0); gtk_widget_set_halign (check, GTK_ALIGN_START); gtk_widget_set_margin_start (check, 6); gtk_widget_show (check); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES) gtk_window_present (GTK_WINDOW (priv->registration)); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check))) { g_object_set (priv->registration, "skip-reminder", TRUE, NULL); glade_app_config_save (); } gtk_widget_destroy (dialog); } else if (!completed) glade_util_flash_message (priv->statusbar, priv->statusbar_context_id, "%s", /* translators: Text to show in the statusbar if the user did not completed the survey and choose not to show the notification dialog again */ _("Go to Help -> Registration & User Survey and complete our survey!")); }