diff options
-rw-r--r-- | ChangeLog | 23 | ||||
-rw-r--r-- | embed/ephy-embed.c | 7 | ||||
-rw-r--r-- | embed/ephy-embed.h | 3 | ||||
-rw-r--r-- | embed/mozilla/EphyBrowser.cpp | 128 | ||||
-rw-r--r-- | embed/mozilla/EphyBrowser.h | 3 | ||||
-rw-r--r-- | embed/mozilla/mozilla-embed.cpp | 13 | ||||
-rw-r--r-- | src/ephy-notebook.c | 25 | ||||
-rw-r--r-- | src/ephy-notebook.h | 2 | ||||
-rw-r--r-- | src/ephy-window.c | 146 |
9 files changed, 342 insertions, 8 deletions
@@ -1,3 +1,26 @@ +2004-01-04 Christian Persch <chpe@cvs.gnome.org> + + * embed/ephy-embed.c: (ephy_embed_has_modified_forms): + * embed/ephy-embed.h: + * embed/mozilla/EphyBrowser.cpp: + * embed/mozilla/EphyBrowser.h: + * embed/mozilla/mozilla-embed.cpp: + + Add API to check if an EphyEmbed has forms with user input in them. + Currently it required one modified textarea, or two modified text + fields. + + * src/ephy-notebook.c: (ephy_notebook_class_init), + (close_button_clicked_cb): + * src/ephy-notebook.h: + * src/ephy-window.c: (confirm_close_with_modified_forms), + (ephy_window_delete_event_cb), (tab_delete_cb), (setup_notebook), + (ephy_window_init), (ephy_window_remove_tab): + + When closing a window or tab, check if there is unsubmitted user + input in form fields, and if so, warn the user before closing. + Fixes bug #119857. + 2004-01-03 Michael Terry <mterry@fastmail.fm> * data/ui/epiphany-ui.xml: diff --git a/embed/ephy-embed.c b/embed/ephy-embed.c index 3c51638ae..186f3ef5b 100644 --- a/embed/ephy-embed.c +++ b/embed/ephy-embed.c @@ -386,3 +386,10 @@ ephy_embed_print_preview_navigate (EphyEmbed *embed, EphyEmbedIFace *iface = EPHY_EMBED_GET_IFACE (embed); return iface->print_preview_navigate (embed, type, page); } + +gboolean +ephy_embed_has_modified_forms (EphyEmbed *embed) +{ + EphyEmbedIFace *iface = EPHY_EMBED_GET_IFACE (embed); + return iface->has_modified_forms (embed); +} diff --git a/embed/ephy-embed.h b/embed/ephy-embed.h index f047b4e4d..71ca925d8 100644 --- a/embed/ephy-embed.h +++ b/embed/ephy-embed.h @@ -228,6 +228,7 @@ struct EphyEmbedIFace EmbedPrintPreviewNavType type, int page); void (* activate) (EphyEmbed *embed); + gboolean (* has_modified_forms) (EphyEmbed *embed); }; GType ephy_embed_get_type (void); @@ -319,6 +320,8 @@ void ephy_embed_print_preview_navigate (EphyEmbed *embed, /* Misc. utility */ void ephy_embed_activate (EphyEmbed *embed); +gboolean ephy_embed_has_modified_forms (EphyEmbed *embed); + G_END_DECLS #endif diff --git a/embed/mozilla/EphyBrowser.cpp b/embed/mozilla/EphyBrowser.cpp index c8c82c62c..afed11d8a 100644 --- a/embed/mozilla/EphyBrowser.cpp +++ b/embed/mozilla/EphyBrowser.cpp @@ -74,6 +74,9 @@ #include "nsIDOMHTMLDocument.h" #include "nsIDOMHTMLCollection.h" #include "nsIDOMHTMLElement.h" +#include "nsIDOMHTMLFormElement.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMHTMLTextAreaElement.h" #include "nsIDeviceContext.h" #include "nsIPresContext.h" #include "nsIAtom.h" @@ -498,7 +501,6 @@ nsresult EphyBrowser::GetZoom (float *aZoom) nsresult EphyBrowser::GetDocument (nsIDOMDocument **aDOMDocument) { - nsCOMPtr<nsIDOMDocument> domDocument; return mDOMWindow->GetDocument (aDOMDocument); } @@ -867,3 +869,127 @@ nsresult EphyBrowser::GetCommandState (const char *command, PRBool *enabled) return cmdManager->IsCommandEnabled (command, nsnull, enabled); } + +#define NUM_MODIFIED_TEXTFIELDS_REQUIRED 2 + +nsresult EphyBrowser::GetDocumentHasModifiedForms (nsIDOMDocument *aDomDoc, PRUint32 *aNumTextFields, PRBool *aHasTextArea) +{ + nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(aDomDoc); + NS_ENSURE_TRUE (htmlDoc, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMHTMLCollection> forms; + htmlDoc->GetForms (getter_AddRefs (forms)); + if (!forms) return NS_OK; /* it's ok not to have any forms */ + + PRUint32 formNum; + forms->GetLength (&formNum); + + /* check all forms */ + for (PRUint32 formIndex = 0; formIndex < formNum; formIndex++) + { + nsCOMPtr<nsIDOMNode> formNode; + forms->Item (formIndex, getter_AddRefs (formNode)); + if (!formNode) continue; + + nsCOMPtr<nsIDOMHTMLFormElement> formElement = do_QueryInterface (formNode); + if (!formElement) continue; + + nsCOMPtr<nsIDOMHTMLCollection> formElements; + formElement->GetElements (getter_AddRefs (formElements)); + if (!formElements) continue; + + PRUint32 elementNum; + formElements->GetLength (&elementNum); + + /* check all input elements in the form for user input */ + for (PRUint32 elementIndex = 0; elementIndex < elementNum; elementIndex++) + { + nsCOMPtr<nsIDOMNode> domNode; + formElements->Item (elementIndex, getter_AddRefs (domNode)); + if (!domNode) continue; + + nsCOMPtr<nsIDOMHTMLTextAreaElement> areaElement = do_QueryInterface (domNode); + if (areaElement) + { + nsAutoString default_text, user_text; + areaElement->GetDefaultValue (default_text); + areaElement->GetValue (user_text); + if (Compare (user_text, default_text) != 0) + { + *aHasTextArea = PR_TRUE; + return NS_OK; + } + + continue; + } + + nsCOMPtr<nsIDOMHTMLInputElement> inputElement = do_QueryInterface(domNode); + if (!inputElement) continue; + + nsAutoString type; + inputElement->GetType(type); + + if (type.EqualsIgnoreCase("text")) + { + nsAutoString default_text, user_text; + inputElement->GetDefaultValue (default_text); + inputElement->GetValue (user_text); + if (Compare (user_text, default_text) != 0) + { + (*aNumTextFields)++; + if (*aNumTextFields >= NUM_MODIFIED_TEXTFIELDS_REQUIRED) + { + return NS_OK; + } + } + } + } + } + + return NS_OK; +} + +nsresult EphyBrowser::GetHasModifiedForms (PRBool *modified) +{ + *modified = PR_FALSE; + + nsCOMPtr<nsIDocShell> rootDocShell = do_GetInterface (mWebBrowser); + NS_ENSURE_TRUE (rootDocShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, + nsIDocShell::ENUMERATE_FORWARDS, + getter_AddRefs(enumerator)); + NS_ENSURE_TRUE (enumerator, NS_ERROR_FAILURE); + + PRBool hasMore; + PRBool hasTextArea = PR_FALSE; + PRUint32 numTextFields = 0; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> element; + enumerator->GetNext (getter_AddRefs(element)); + if (!element) continue; + + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface (element); + if (!docShell) continue; + + nsCOMPtr<nsIContentViewer> contentViewer; + docShell->GetContentViewer (getter_AddRefs(contentViewer)); + if (!contentViewer) continue; + + nsCOMPtr<nsIDOMDocument> domDoc; + contentViewer->GetDOMDocument (getter_AddRefs (domDoc)); + + nsresult result; + result = GetDocumentHasModifiedForms (domDoc, &numTextFields, &hasTextArea); + if (NS_SUCCEEDED (result) && + (numTextFields >= NUM_MODIFIED_TEXTFIELDS_REQUIRED || hasTextArea)) + { + *modified = PR_TRUE; + break; + } + } + + return NS_OK; +} diff --git a/embed/mozilla/EphyBrowser.h b/embed/mozilla/EphyBrowser.h index e5ed2e5f5..40101ce44 100644 --- a/embed/mozilla/EphyBrowser.h +++ b/embed/mozilla/EphyBrowser.h @@ -119,6 +119,8 @@ public: nsresult GetDocumentUrl (nsCString &url); nsresult GetTargetDocumentUrl (nsCString &url); + nsresult GetHasModifiedForms (PRBool *modified); + nsCOMPtr<nsIWebBrowser> mWebBrowser; private: @@ -135,6 +137,7 @@ private: nsresult SetZoomOnDocshell (float aZoom, nsIDocShell *DocShell); nsresult GetSHistory (nsISHistory **aSHistory); nsresult GetContentViewer (nsIContentViewer **aViewer); + nsresult GetDocumentHasModifiedForms (nsIDOMDocument *aDomDoc, PRUint32 *aNumTextFields, PRBool *aHasTextArea); }; #endif diff --git a/embed/mozilla/mozilla-embed.cpp b/embed/mozilla/mozilla-embed.cpp index f333c8768..15b88938b 100644 --- a/embed/mozilla/mozilla-embed.cpp +++ b/embed/mozilla/mozilla-embed.cpp @@ -786,6 +786,18 @@ impl_get_encoding_info (EphyEmbed *embed) return info; } +static gboolean +impl_has_modified_forms (EphyEmbed *embed) +{ + MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv; + nsresult result; + + PRBool modified; + result = mpriv->browser->GetHasModifiedForms (&modified); + + return modified == PR_TRUE ? TRUE : FALSE; +} + static void mozilla_embed_location_changed_cb (GtkMozEmbed *embed, MozillaEmbed *membed) @@ -1121,6 +1133,7 @@ ephy_embed_iface_init (EphyEmbedIFace *iface) iface->print_preview_close = impl_print_preview_close; iface->print_preview_n_pages = impl_print_preview_n_pages; iface->print_preview_navigate = impl_print_preview_navigate; + iface->has_modified_forms = impl_has_modified_forms; } void diff --git a/src/ephy-notebook.c b/src/ephy-notebook.c index 87bb719a6..799e27b74 100644 --- a/src/ephy-notebook.c +++ b/src/ephy-notebook.c @@ -20,7 +20,7 @@ */ #ifdef HAVE_CONFIG_H -#include <config.h> +#include "config.h" #endif #include "ephy-notebook.h" @@ -92,13 +92,14 @@ enum TAB_REMOVED, TABS_REORDERED, TAB_DETACHED, + TAB_DELETE, LAST_SIGNAL }; -static GObjectClass *parent_class = NULL; - static guint signals[LAST_SIGNAL] = { 0 }; +static GObjectClass *parent_class = NULL; + GType ephy_notebook_get_type (void) { @@ -175,6 +176,16 @@ ephy_notebook_class_init (EphyNotebookClass *klass) g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + signals[TAB_DELETE] = + g_signal_new ("tab_delete", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EphyNotebookClass, tab_delete), + g_signal_accumulator_true_handled, NULL, + ephy_marshal_BOOLEAN__OBJECT, + G_TYPE_BOOLEAN, + 1, + GTK_TYPE_WIDGET); g_type_class_add_private (object_class, sizeof(EphyNotebookPrivate)); } @@ -852,9 +863,15 @@ static void close_button_clicked_cb (GtkWidget *widget, GtkWidget *child) { EphyNotebook *notebook; + gboolean inhibited = FALSE; notebook = EPHY_NOTEBOOK (gtk_widget_get_parent (child)); - ephy_notebook_remove_page (notebook, child); + g_signal_emit (G_OBJECT (notebook), signals[TAB_DELETE], 0, child, &inhibited); + + if (inhibited == FALSE) + { + ephy_notebook_remove_page (notebook, child); + } } static GtkWidget * diff --git a/src/ephy-notebook.h b/src/ephy-notebook.h index ca93cdf55..314ad098c 100644 --- a/src/ephy-notebook.h +++ b/src/ephy-notebook.h @@ -70,6 +70,8 @@ struct EphyNotebookClass void (* tab_detached) (EphyNotebook *notebook, GtkWidget *child); void (* tabs_reordered) (EphyNotebook *notebook); + void (* tab_delete) (EphyNotebook *notebook, + GtkWidget *child); }; GType ephy_notebook_get_type (void); diff --git a/src/ephy-window.c b/src/ephy-window.c index f5013d9c8..a4f748aa5 100644 --- a/src/ephy-window.c +++ b/src/ephy-window.c @@ -57,6 +57,8 @@ #include <gtk/gtktoggleaction.h> #include <gtk/gtkuimanager.h> #include <gtk/gtktoggleaction.h> +#include <gtk/gtkdialog.h> +#include <gtk/gtkmessagedialog.h> static void ephy_window_class_init (EphyWindowClass *klass); static void ephy_window_init (EphyWindow *gs); @@ -636,6 +638,116 @@ ephy_window_state_event_cb (GtkWidget *widget, GdkEventWindowState *event, EphyW return FALSE; } +static gboolean +confirm_close_with_modified_forms (EphyWindow *window) +{ + GtkWidget *dialog; + GtkWidget *hbox, *vbox, *label, *image; + GtkWindowGroup *group; + char *text; + int response; + + dialog = gtk_dialog_new_with_buttons ("", + GTK_WINDOW (window), + GTK_DIALOG_NO_SEPARATOR | GTK_DIALOG_MODAL, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + _("_Close document"), GTK_RESPONSE_OK, + NULL); + + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 14); + + hbox = gtk_hbox_new (FALSE, 6); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, + TRUE, TRUE, 0); + + image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING, + GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0); + gtk_widget_show (image); + gtk_box_pack_start (GTK_BOX (hbox), image, TRUE, TRUE, 0); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + + text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s", + _("There are unsubmitted changes to form elements."), + _("If you close the document anyway, you will lose that information.")); + gtk_label_set_markup (GTK_LABEL (label), text); + g_free (text); + + gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + + group = GTK_WINDOW (window)->group; + if (group == NULL) + { + group = gtk_window_group_new (); + gtk_window_group_add_window (group, GTK_WINDOW (window)); + g_object_unref (group); + } + + gtk_window_group_add_window (group, GTK_WINDOW (dialog)); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + + return response == GTK_RESPONSE_OK; +} + +static gboolean +ephy_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, EphyWindow *window) +{ + EphyTab *modified_tab = NULL; + GList *tabs, *l; + gboolean modified = FALSE; + + tabs = ephy_window_get_tabs (window); + for (l = tabs; l != NULL; l = l->next) + { + EphyTab *tab = (EphyTab *) l->data; + EphyEmbed *embed; + + g_return_val_if_fail (EPHY_IS_TAB (tab), FALSE); + + embed = ephy_tab_get_embed (tab); + g_return_val_if_fail (EPHY_IS_EMBED (embed), FALSE); + + if (ephy_embed_has_modified_forms (embed)) + { + modified = TRUE; + modified_tab = tab; + break; + } + } + g_list_free (tabs); + + if (modified) + { + /* jump to the first tab with modified forms */ + ephy_window_jump_to_tab (window, modified_tab); + + if (confirm_close_with_modified_forms (window) == FALSE) + { + /* stop window close */ + return TRUE; + } + } + + /* See bug #114689 */ + gtk_widget_hide (widget); + + /* proceed with window close */ + return FALSE; +} static void update_edit_actions_sensitivity (EphyWindow *window, gboolean hide) @@ -1445,6 +1557,19 @@ tabs_reordered_cb (EphyNotebook *notebook, EphyWindow *window) ephy_tabs_menu_update (window->priv->tabs_menu); } +static gboolean +tab_delete_cb (EphyNotebook *notebook, GtkWidget *child, EphyWindow *window) +{ + g_return_val_if_fail (EPHY_IS_EMBED (child), FALSE); + + if (ephy_embed_has_modified_forms (EPHY_EMBED (child))) + { + return !confirm_close_with_modified_forms (window); + } + + return FALSE; +} + static GtkNotebook * setup_notebook (EphyWindow *window) { @@ -1468,6 +1593,8 @@ setup_notebook (EphyWindow *window) G_CALLBACK (tab_detached_cb), NULL); g_signal_connect (G_OBJECT (notebook), "tabs_reordered", G_CALLBACK (tabs_reordered_cb), window); + g_signal_connect (G_OBJECT (notebook), "tab_delete", + G_CALLBACK (tab_delete_cb), window); return notebook; } @@ -1662,6 +1789,9 @@ ephy_window_init (EphyWindow *window) g_signal_connect (window, "window-state-event", G_CALLBACK (ephy_window_state_event_cb), window); + g_signal_connect (window, "delete-event", + G_CALLBACK (ephy_window_delete_event_cb), + window); /* lockdown pref notifiers */ window->priv->disable_js_chrome_notifier_id = eel_gconf_notification_add @@ -1834,15 +1964,25 @@ void ephy_window_remove_tab (EphyWindow *window, EphyTab *tab) { - GtkWidget *embed; + EphyEmbed *embed; + gboolean modified; g_return_if_fail (EPHY_IS_WINDOW (window)); g_return_if_fail (EPHY_IS_TAB (tab)); - embed = GTK_WIDGET (ephy_tab_get_embed (tab)); + embed = ephy_tab_get_embed (tab); + g_return_if_fail (EPHY_IS_EMBED (embed)); + + modified = ephy_embed_has_modified_forms (embed); + if (ephy_embed_has_modified_forms (embed) + && confirm_close_with_modified_forms (window) == FALSE) + { + /* don't close the tab */ + return; + } ephy_notebook_remove_page (EPHY_NOTEBOOK (window->priv->notebook), - embed); + GTK_WIDGET (embed)); } /** |