mail-notification

Fork of Jean-Yves Lefort's mail-notification, a tray icon to notify of new mail
git clone https://code.djc.id.au/git/mail-notification/

src/mn-util.c (43442B) - raw

      1 /*
      2  * Mail Notification
      3  * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be>
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License along
     16  * with this program; if not, write to the Free Software Foundation, Inc.,
     17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     18  */
     19 
     20 #include <stdio.h>
     21 #include <string.h>
     22 #include <stdarg.h>
     23 #include <stdlib.h>
     24 #include <time.h>
     25 #include <errno.h>
     26 #include <gmodule.h>
     27 #include <glib/gi18n.h>
     28 #include <gobject/gvaluecollector.h>
     29 #include <gnome.h>
     30 #include <glade/glade.h>
     31 #include "mn-util.h"
     32 #include "mn-mailboxes.h"
     33 #include "mn-shell.h"
     34 #include "mn-conf.h"
     35 
     36 typedef struct
     37 {
     38   GtkContainer	*container;
     39   const char	*callback_prefix;
     40 } ContainerCreateInterfaceConnectInfo;
     41 
     42 enum
     43 {
     44   TARGET_URI_LIST,
     45   TARGET_MOZ_URL
     46 };
     47 
     48 typedef struct
     49 {
     50   gpointer		instance;
     51   unsigned long		id;
     52 } SignalHandler;
     53 
     54 typedef struct
     55 {
     56   GMainLoop	*loop;
     57   int		response;
     58   gboolean	destroyed;
     59 } RunNonmodalInfo;
     60 
     61 void
     62 mn_info (const char *format, ...)
     63 {
     64   va_list args;
     65 
     66   g_return_if_fail(format != NULL);
     67 
     68   va_start(args, format);
     69   g_logv(NULL, G_LOG_LEVEL_INFO, format, args);
     70   va_end(args);
     71 }
     72 
     73 void
     74 mn_g_list_free_deep_custom (GList *list,
     75 			    GFunc element_free_func,
     76 			    gpointer user_data)
     77 {
     78   g_list_foreach(list, element_free_func, user_data);
     79   g_list_free(list);
     80 }
     81 
     82 GSList *
     83 mn_g_slist_append_elements (GSList *list, gpointer data, ...)
     84 {
     85   va_list args;
     86 
     87   va_start(args, data);
     88 
     89   while (data)
     90     {
     91       list = g_slist_append(list, data);
     92       data = va_arg(args, gpointer);
     93     }
     94 
     95   va_end(args);
     96 
     97   return list;
     98 }
     99 
    100 void
    101 mn_g_slist_free_deep (GSList *list)
    102 {
    103   mn_g_slist_free_deep_custom(list, (GFunc) g_free, NULL);
    104 }
    105 
    106 void
    107 mn_g_slist_free_deep_custom (GSList *list,
    108 			     GFunc element_free_func,
    109 			     gpointer user_data)
    110 {
    111   g_slist_foreach(list, element_free_func, user_data);
    112   g_slist_free(list);
    113 }
    114 
    115 void
    116 mn_g_slist_clear (GSList **list)
    117 {
    118   g_return_if_fail(list != NULL);
    119 
    120   g_slist_free(*list);
    121   *list = NULL;
    122 }
    123 
    124 void
    125 mn_g_slist_clear_deep (GSList **list)
    126 {
    127   g_return_if_fail(list != NULL);
    128 
    129   mn_g_slist_free_deep(*list);
    130   *list = NULL;
    131 }
    132 
    133 void
    134 mn_g_slist_clear_deep_custom (GSList **list,
    135 			      GFunc element_free_func,
    136 			      gpointer user_data)
    137 {
    138   g_return_if_fail(list != NULL);
    139   g_return_if_fail(element_free_func != NULL);
    140 
    141   mn_g_slist_free_deep_custom(*list, element_free_func, user_data);
    142   *list = NULL;
    143 }
    144 
    145 /**
    146  * mn_g_slist_delete_link_deep_custom:
    147  * @list: a #GSList of @element_free_func-freeable objects
    148  * @link_: an element in the #GSList
    149  * @element_free_func: a function to free @link_->data
    150  * @user_data: user data to pass to @element_free_func
    151  *
    152  * Equivalent of g_slist_delete_link() for a list of
    153  * @element_free_func-freeable objects.
    154  *
    155  * Return value: new head of @list.
    156  **/
    157 GSList *
    158 mn_g_slist_delete_link_deep_custom (GSList *list,
    159 				    GSList *link_,
    160 				    GFunc element_free_func,
    161 				    gpointer user_data)
    162 {
    163   g_return_val_if_fail(element_free_func != NULL, NULL);
    164 
    165   if (link_)
    166     element_free_func(link_->data, user_data);
    167 
    168   return g_slist_delete_link(list, link_);
    169 }
    170 
    171 static int
    172 str_slist_compare_func (gconstpointer a, gconstpointer b)
    173 {
    174   return strcmp(a, b);
    175 }
    176 
    177 GSList *
    178 mn_g_str_slist_find (GSList *list, const char *str)
    179 {
    180   g_return_val_if_fail(str != NULL, NULL);
    181 
    182   return g_slist_find_custom(list, str, str_slist_compare_func);
    183 }
    184 
    185 void
    186 mn_g_object_list_free (GList *list)
    187 {
    188   mn_g_list_free_deep_custom(list, (GFunc) g_object_unref, NULL);
    189 }
    190 
    191 GSList *
    192 mn_g_object_slist_ref (GSList *list)
    193 {
    194   g_slist_foreach(list, (GFunc) g_object_ref, NULL);
    195   return list;
    196 }
    197 
    198 GSList *
    199 mn_g_object_slist_copy (GSList *list)
    200 {
    201   return g_slist_copy(mn_g_object_slist_ref(list));
    202 }
    203 
    204 /**
    205  * mn_g_object_slist_free:
    206  * @list: a #GSList of #GObject instances
    207  *
    208  * Equivalent of mn_g_object_list_free() for a singly-linked list.
    209  **/
    210 void
    211 mn_g_object_slist_free (GSList *list)
    212 {
    213   mn_g_slist_free_deep_custom(list, (GFunc) g_object_unref, NULL);
    214 }
    215 
    216 void
    217 mn_g_object_slist_clear (GSList **list)
    218 {
    219   g_return_if_fail(list != NULL);
    220 
    221   mn_g_object_slist_free(*list);
    222   *list = NULL;
    223 }
    224 
    225 /**
    226  * mn_str_isnumeric:
    227  * @str: the ASCII string to test
    228  *
    229  * Tests if the ASCII string @str is numeric. Implemented by calling
    230  * g_ascii_isdigit() on each character of @str.
    231  *
    232  * Return value: %TRUE if the ASCII string @str only consists of digits
    233  **/
    234 gboolean
    235 mn_str_isnumeric (const char *str)
    236 {
    237   int i;
    238 
    239   g_return_val_if_fail(str != NULL, FALSE);
    240 
    241   for (i = 0; str[i]; i++)
    242     if (! g_ascii_isdigit(str[i]))
    243       return FALSE;
    244 
    245   return i > 0;
    246 }
    247 
    248 gboolean
    249 mn_str_ishex (const char *str)
    250 {
    251   int i;
    252 
    253   g_return_val_if_fail(str != NULL, FALSE);
    254 
    255   for (i = 0; str[i]; i++)
    256     if (! g_ascii_isxdigit(str[i]))
    257       return FALSE;
    258 
    259   return i > 0;
    260 }
    261 
    262 /**
    263  * mn_strstr_span:
    264  * @big: a string.
    265  * @little: a string to search for in @big.
    266  *
    267  * Locates the first occurrence of @little in @big.
    268  *
    269  * Return value: a pointer to the character following the first
    270  * occurrence of @little in @big, or %NULL if @little does not appear
    271  * in @big.
    272  **/
    273 char *
    274 mn_strstr_span (const char *big, const char *little)
    275 {
    276   char *s;
    277 
    278   g_return_val_if_fail(big != NULL, NULL);
    279   g_return_val_if_fail(little != NULL, NULL);
    280 
    281   s = strstr(big, little);
    282   if (s)
    283     s += strlen(little);
    284 
    285   return s;
    286 }
    287 
    288 GdkPixbuf *
    289 mn_pixbuf_new (const char *filename)
    290 {
    291   GdkPixbuf *pixbuf;
    292   GError *err = NULL;
    293 
    294   g_return_val_if_fail(filename != NULL, NULL);
    295 
    296   pixbuf = gdk_pixbuf_new_from_file(filename, &err);
    297   if (! pixbuf)
    298     {
    299       mn_show_fatal_error_dialog(NULL, "Unable to load image \"%s\" (%s).", filename, err->message);
    300       g_error_free(err);
    301     }
    302 
    303   return pixbuf;
    304 }
    305 
    306 static GladeXML *
    307 mn_glade_xml_new (const char *filename, const char *root, const char *domain)
    308 {
    309   GladeXML *xml;
    310 
    311   g_return_val_if_fail(filename != NULL, NULL);
    312 
    313   xml = glade_xml_new(filename, root, domain);
    314   if (! xml)
    315     mn_show_fatal_error_dialog(NULL, "Unable to load interface \"%s\".", filename);
    316 
    317   return xml;
    318 }
    319 
    320 static GtkWidget *
    321 mn_glade_xml_get_widget (GladeXML *xml, const char *widget_name)
    322 {
    323   GtkWidget *widget;
    324 
    325   g_return_val_if_fail(GLADE_IS_XML(xml), NULL);
    326   g_return_val_if_fail(widget_name != NULL, NULL);
    327 
    328   widget = glade_xml_get_widget(xml, widget_name);
    329   if (! widget)
    330     mn_show_fatal_error_dialog(NULL, "Widget \"%s\" not found in interface \"%s\".", widget_name, xml->filename);
    331 
    332   return widget;
    333 }
    334 
    335 static void
    336 create_interface_connect_cb (const char *handler_name,
    337 			     GObject *object,
    338 			     const char *signal_name,
    339 			     const char *signal_data,
    340 			     GObject *connect_object,
    341 			     gboolean after,
    342 			     gpointer user_data)
    343 {
    344   static GModule *module = NULL;
    345   ContainerCreateInterfaceConnectInfo *info = user_data;
    346   char *cb_name;
    347   GCallback cb;
    348   GConnectFlags flags;
    349 
    350   if (! module)
    351     {
    352       module = g_module_open(NULL, 0);
    353       if (! module)
    354 	mn_show_fatal_error_dialog(NULL, "Unable to open the program as a module (%s).", g_module_error());
    355     }
    356 
    357   cb_name = g_strconcat(info->callback_prefix, handler_name, NULL);
    358   if (! g_module_symbol(module, cb_name, (gpointer) &cb))
    359     mn_show_fatal_error_dialog(NULL, "Signal handler \"%s\" not found.", cb_name);
    360   g_free(cb_name);
    361 
    362   flags = G_CONNECT_SWAPPED;
    363   if (after)
    364     flags |= G_CONNECT_AFTER;
    365 
    366   g_signal_connect_data(object, signal_name, cb, info->container, NULL, flags);
    367 }
    368 
    369 void
    370 mn_container_create_interface (GtkContainer *container,
    371 			       const char *filename,
    372 			       const char *child_name,
    373 			       const char *callback_prefix,
    374 			       ...)
    375 {
    376   GladeXML *xml;
    377   GtkWidget *child;
    378   ContainerCreateInterfaceConnectInfo info;
    379   va_list args;
    380   const char *widget_name;
    381 
    382   g_return_if_fail(GTK_IS_CONTAINER(container));
    383   g_return_if_fail(filename != NULL);
    384   g_return_if_fail(child_name != NULL);
    385   g_return_if_fail(callback_prefix != NULL);
    386 
    387   xml = mn_glade_xml_new(filename, child_name, NULL);
    388   child = mn_glade_xml_get_widget(xml, child_name);
    389 
    390   if (GTK_IS_DIALOG(container))
    391     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(container)->vbox), child, TRUE, TRUE, 0);
    392   else
    393     gtk_container_add(container, child);
    394 
    395   info.container = container;
    396   info.callback_prefix = callback_prefix;
    397   glade_xml_signal_autoconnect_full(xml, create_interface_connect_cb, &info);
    398 
    399   va_start(args, callback_prefix);
    400 
    401   while ((widget_name = va_arg(args, const char *)))
    402     {
    403       GtkWidget **widget;
    404 
    405       widget = va_arg(args, GtkWidget **);
    406       g_return_if_fail(widget != NULL);
    407 
    408       *widget = mn_glade_xml_get_widget(xml, widget_name);
    409     }
    410 
    411   va_end(args);
    412 
    413   g_object_unref(xml);
    414 }
    415 
    416 GtkWindow *
    417 mn_widget_get_parent_window (GtkWidget *widget)
    418 {
    419   GtkWidget *toplevel;
    420 
    421   g_return_val_if_fail(GTK_IS_WIDGET(widget), NULL);
    422 
    423   toplevel = gtk_widget_get_toplevel(widget);
    424 
    425   return GTK_WIDGET_TOPLEVEL(toplevel) ? GTK_WINDOW(toplevel) : NULL;
    426 }
    427 
    428 static void
    429 file_chooser_dialog_file_activated_h (GtkFileChooser *chooser,
    430 				      gpointer user_data)
    431 {
    432   int accept_id = GPOINTER_TO_INT(user_data);
    433 
    434   gtk_dialog_response(GTK_DIALOG(chooser), accept_id);
    435 }
    436 
    437 static void
    438 file_chooser_dialog_response_h (GtkDialog *dialog,
    439 				int response_id,
    440 				gpointer user_data)
    441 {
    442   int accept_id = GPOINTER_TO_INT(user_data);
    443 
    444   if (response_id == accept_id)
    445     {
    446       char *uri;
    447 
    448       uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
    449       if (uri)
    450 	g_free(uri);
    451       else
    452 	g_signal_stop_emission_by_name(dialog, "response");
    453     }
    454 }
    455 
    456 /**
    457  * mn_file_chooser_dialog_allow_select_folder:
    458  * @dialog: a #GtkFileChooserDialog
    459  * @accept_id: the "accept" response ID (must not be
    460  *             GTK_RESPONSE_ACCEPT, GTK_RESPONSE_OK, GTK_RESPONSE_YES
    461  *             or GTK_RESPONSE_APPLY)
    462  *
    463  * Allows @dialog to pick a file (%GTK_FILE_CHOOSER_ACTION_OPEN) or
    464  * select a folder (%GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) at the
    465  * same time.
    466  *
    467  * Can be removed after
    468  * http://bugzilla.gnome.org/show_bug.cgi?id=136294 is fixed.
    469  **/
    470 void
    471 mn_file_chooser_dialog_allow_select_folder (GtkFileChooserDialog *dialog,
    472 					    int accept_id)
    473 {
    474   g_return_if_fail(GTK_IS_FILE_CHOOSER_DIALOG(dialog));
    475   g_return_if_fail(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)) == GTK_FILE_CHOOSER_ACTION_OPEN);
    476   g_return_if_fail(! (accept_id == GTK_RESPONSE_ACCEPT
    477 		      || accept_id == GTK_RESPONSE_OK
    478 		      || accept_id == GTK_RESPONSE_YES
    479 		      || accept_id == GTK_RESPONSE_APPLY));
    480 
    481   g_object_connect(dialog,
    482 		   "signal::file-activated", file_chooser_dialog_file_activated_h, GINT_TO_POINTER(accept_id),
    483 		   "signal::response", file_chooser_dialog_response_h, GINT_TO_POINTER(accept_id),
    484 		   NULL);
    485 }
    486 
    487 static gboolean
    488 scrolled_window_drag_motion_h (GtkWidget *widget,
    489 			       GdkDragContext *drag_context,
    490 			       int x,
    491 			       int y,
    492 			       unsigned int time_,
    493 			       gpointer user_data)
    494 {
    495   GtkAdjustment *adjustment;
    496 
    497   adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(widget));
    498   gtk_adjustment_set_value(adjustment, (double) y / (widget->allocation.height - 2) * (adjustment->upper - adjustment->page_size));
    499 
    500   return TRUE;			/* we're forcibly in a drop zone */
    501 }
    502 
    503 static void
    504 drag_data_received_h (GtkWidget *widget,
    505 		      GdkDragContext *drag_context,
    506 		      int x,
    507 		      int y,
    508 		      GtkSelectionData *selection_data,
    509 		      unsigned int info,
    510 		      unsigned int time_,
    511 		      gpointer user_data)
    512 {
    513   switch (info)
    514     {
    515     case TARGET_URI_LIST:
    516       {
    517 	char **uriv;
    518 	int i;
    519 	GSList *invalid_uri_list = NULL;
    520 
    521 	uriv = gtk_selection_data_get_uris(selection_data);
    522 	if (! uriv)
    523 	  {
    524 	    mn_show_error_dialog(mn_widget_get_parent_window(widget),
    525 				 _("A drag and drop error has occurred"),
    526 				 _("An invalid location list has been received."));
    527 	    return;
    528 	  }
    529 
    530 	for (i = 0; uriv[i]; i++)
    531 	  if (*uriv[i])
    532 	    {
    533 	      MNMailbox *mailbox;
    534 
    535 	      mailbox = mn_mailbox_new_from_uri(uriv[i]);
    536 	      if (mailbox)
    537 		{
    538 		  mn_mailbox_seal(mailbox);
    539 		  mn_mailboxes_queue_add(mn_shell->mailboxes, mailbox);
    540 		  g_object_unref(mailbox);
    541 		}
    542 	      else
    543 		invalid_uri_list = g_slist_append(invalid_uri_list, uriv[i]);
    544 	    }
    545 
    546 	if (invalid_uri_list)
    547 	  {
    548 	    mn_show_invalid_uri_list_dialog(mn_widget_get_parent_window(widget), _("A drag and drop error has occurred"), invalid_uri_list);
    549 	    g_slist_free(invalid_uri_list);
    550 	  }
    551 
    552 	g_strfreev(uriv);
    553       }
    554       break;
    555 
    556     case TARGET_MOZ_URL:
    557       {
    558 	GString *url;
    559 	const guint16 *char_data;
    560 	int char_len;
    561 	int i;
    562 	MNMailbox *mailbox;
    563 
    564 	/* text/x-moz-url is encoded in UCS-2 but in format 8: broken */
    565 	if (selection_data->format != 8 || selection_data->length <= 0 || (selection_data->length % 2) != 0)
    566 	  {
    567 	    mn_show_error_dialog(mn_widget_get_parent_window(widget),
    568 				 _("A drag and drop error has occurred"),
    569 				 _("An invalid Mozilla location has been received."));
    570 	    return;
    571 	  }
    572 
    573 	char_data = (const guint16 *) selection_data->data;
    574 	char_len = selection_data->length / 2;
    575 
    576 	url = g_string_new(NULL);
    577 	for (i = 0; i < char_len && char_data[i] != '\n'; i++)
    578 	  g_string_append_unichar(url, char_data[i]);
    579 
    580 	g_assert(mn_shell != NULL);
    581 
    582 	mailbox = mn_mailbox_new_from_uri(url->str);
    583 	if (mailbox)
    584 	  {
    585 	    mn_mailbox_seal(mailbox);
    586 	    mn_mailboxes_queue_add(mn_shell->mailboxes, mailbox);
    587 	    g_object_unref(mailbox);
    588 	  }
    589 	else
    590 	  mn_show_invalid_uri_dialog(mn_widget_get_parent_window(widget), _("A drag and drop error has occurred"), url->str);
    591 
    592 	g_string_free(url, TRUE);
    593       }
    594       break;
    595     }
    596 }
    597 
    598 /**
    599  * mn_setup_dnd:
    600  * @widget: a widget to setup mailbox drag-and-drop for
    601  *
    602  * Configures @widget so that when mailboxes are dropped on it, they
    603  * will be added to the Mail Notification mailbox list.
    604  **/
    605 void
    606 mn_setup_dnd (GtkWidget *widget)
    607 {
    608   static const GtkTargetEntry targets[] = {
    609     { "text/uri-list",	0, TARGET_URI_LIST },
    610     { "text/x-moz-url",	0, TARGET_MOZ_URL }
    611   };
    612 
    613   g_return_if_fail(GTK_IS_WIDGET(widget));
    614 
    615   gtk_drag_dest_set(widget,
    616 		    GTK_DEST_DEFAULT_ALL,
    617 		    targets,
    618 		    G_N_ELEMENTS(targets),
    619 		    GDK_ACTION_COPY);
    620 
    621   if (GTK_IS_SCROLLED_WINDOW(widget))
    622     g_signal_connect(widget,
    623 		     "drag-motion",
    624 		     G_CALLBACK(scrolled_window_drag_motion_h),
    625 		     NULL);
    626 
    627   g_signal_connect(widget,
    628 		   "drag-data-received",
    629 		   G_CALLBACK(drag_data_received_h),
    630 		   NULL);
    631 }
    632 
    633 gboolean
    634 mn_parse_gnome_copied_files (const char *gnome_copied_files,
    635 			     MNGnomeCopiedFilesType *type,
    636 			     GSList **uri_list)
    637 {
    638   char **strv;
    639   gboolean status = FALSE;
    640 
    641   g_return_val_if_fail(gnome_copied_files != NULL, FALSE);
    642   g_return_val_if_fail(type != NULL, FALSE);
    643   g_return_val_if_fail(uri_list != NULL, FALSE);
    644 
    645   strv = g_strsplit(gnome_copied_files, "\n", 0);
    646   if (strv[0])
    647     {
    648       int i;
    649 
    650       if (! strcmp(strv[0], "cut"))
    651 	{
    652 	  status = TRUE;
    653 	  *type = MN_GNOME_COPIED_FILES_CUT;
    654 	}
    655       else if (! strcmp(strv[0], "copy"))
    656 	{
    657 	  status = TRUE;
    658 	  *type = MN_GNOME_COPIED_FILES_COPY;
    659 	}
    660 
    661       if (status)
    662 	{
    663 	  *uri_list = NULL;
    664 	  for (i = 1; strv[i]; i++)
    665 	    *uri_list = g_slist_append(*uri_list, g_strdup(strv[i]));
    666 	}
    667     }
    668 
    669   g_strfreev(strv);
    670   return status;
    671 }
    672 
    673 void
    674 mn_show_help (GtkWindow *parent, const char *link_id)
    675 {
    676   GError *err = NULL;
    677 
    678   if (! gnome_help_display("mail-notification.xml", link_id, &err))
    679     {
    680       mn_show_error_dialog(parent, _("Unable to display help"), "%s", err->message);
    681       g_error_free(err);
    682     }
    683 }
    684 
    685 void
    686 mn_open_link (GtkWindow *parent, const char *url)
    687 {
    688   GError *err = NULL;
    689 
    690   if (! gnome_url_show(url, &err))
    691     {
    692       mn_show_error_dialog(parent, _("Unable to open link"), "%s", err->message);
    693       g_error_free(err);
    694     }
    695 }
    696 
    697 void
    698 mn_thread_create (GThreadFunc func, gpointer data)
    699 {
    700   GError *err = NULL;
    701 
    702   g_return_if_fail(func != NULL);
    703 
    704   if (! g_thread_create(func, data, FALSE, &err))
    705     {
    706       mn_show_fatal_error_dialog(NULL, "Unable to create a thread: %s.", err->message);
    707       g_error_free(err);
    708     }
    709 }
    710 
    711 static GtkWidget *
    712 menu_item_new (const char *stock_id, const char *mnemonic)
    713 {
    714   GtkWidget *item;
    715 
    716   if (stock_id && mnemonic)
    717     {
    718       GtkWidget *image;
    719 
    720       item = gtk_image_menu_item_new_with_mnemonic(mnemonic);
    721 
    722       image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU);
    723       gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
    724       gtk_widget_show(image);
    725     }
    726   else if (stock_id)
    727     item = gtk_image_menu_item_new_from_stock(stock_id, NULL);
    728   else if (mnemonic)
    729     item = gtk_menu_item_new_with_mnemonic(mnemonic);
    730   else
    731     item = gtk_separator_menu_item_new();
    732 
    733   return item;
    734 }
    735 
    736 /**
    737  * mn_menu_shell_append:
    738  * @shell: the #GtkMenuShell to append to
    739  * @stock_id: the stock ID of the item or %NULL
    740  * @mnemonic: the mnemonic of the item or %NULL
    741  *
    742  * Creates a new menu item, shows it and appends it to @shell.
    743  *
    744  * If both @stock_id and @mnemonic are provided, a #GtkImageMenuItem
    745  * will be created using the text of @mnemonic and the icon of
    746  * @stock_id.
    747  *
    748  * If only @stock_id is provided, a #GtkImageMenuitem will be created
    749  * using the text and icon of @stock_id.
    750  *
    751  * If only @mnemonic is provided, a #GtkMenuItem will be created using
    752  * the text of @mnemonic.
    753  *
    754  * If @stock_id and @mnemonic are both %NULL, a #GtkSeparatorMenuItem
    755  * will be created.
    756  *
    757  * Return value: the new menu item.
    758  **/
    759 GtkWidget *
    760 mn_menu_shell_append (GtkMenuShell *shell,
    761 		      const char *stock_id,
    762 		      const char *mnemonic)
    763 {
    764   GtkWidget *item;
    765 
    766   g_return_val_if_fail(GTK_IS_MENU_SHELL(shell), NULL);
    767 
    768   item = menu_item_new(stock_id, mnemonic);
    769   gtk_menu_shell_append(shell, item);
    770   gtk_widget_show(item);
    771 
    772   return item;
    773 }
    774 
    775 static void
    776 show_error_dialog_real (GtkWindow *parent,
    777 			MNDialogFlags flags,
    778 			const char *primary,
    779 			const char *format,
    780 			va_list args)
    781 {
    782   char *secondary;
    783   GtkWidget *dialog;
    784 
    785   g_return_if_fail(primary != NULL);
    786   g_return_if_fail(format != NULL);
    787 
    788   secondary = g_strdup_vprintf(format, args);
    789   dialog = mn_alert_dialog_new(parent, GTK_MESSAGE_ERROR, flags, primary, secondary);
    790   g_free(secondary);
    791 
    792   gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_OK, GTK_RESPONSE_OK);
    793 
    794   gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
    795 
    796   if ((flags & MN_DIALOG_BLOCKING) != 0)
    797     {
    798       gtk_dialog_run(GTK_DIALOG(dialog));
    799       gtk_widget_destroy(dialog);
    800     }
    801   else
    802     {
    803       g_signal_connect_swapped(dialog,
    804 			       "response",
    805 			       G_CALLBACK(gtk_widget_destroy),
    806 			       dialog);
    807       gtk_widget_show(dialog);
    808     }
    809 }
    810 
    811 void
    812 mn_show_error_dialog (GtkWindow *parent,
    813 		      const char *primary,
    814 		      const char *format,
    815 		      ...)
    816 {
    817   va_list args;
    818 
    819   g_return_if_fail(primary != NULL);
    820   g_return_if_fail(format != NULL);
    821 
    822   va_start(args, format);
    823   show_error_dialog_real(parent, 0, primary, format, args);
    824   va_end(args);
    825 }
    826 
    827 /* only the secondary text can have markup */
    828 void
    829 mn_show_error_dialog_with_markup (GtkWindow *parent,
    830 				  const char *primary,
    831 				  const char *format,
    832 				  ...)
    833 {
    834   va_list args;
    835 
    836   g_return_if_fail(primary != NULL);
    837   g_return_if_fail(format != NULL);
    838 
    839   va_start(args, format);
    840   show_error_dialog_real(parent, MN_DIALOG_MARKUP, primary, format, args);
    841   va_end(args);
    842 }
    843 
    844 void
    845 mn_show_invalid_uri_dialog (GtkWindow *parent,
    846 			    const char *primary,
    847 			    const char *invalid_uri)
    848 {
    849   GSList *list = NULL;
    850 
    851   g_return_if_fail(primary != NULL);
    852   g_return_if_fail(invalid_uri != NULL);
    853 
    854   list = g_slist_append(list, (gpointer) invalid_uri);
    855   mn_show_invalid_uri_list_dialog(parent, primary, list);
    856   g_slist_free(list);
    857 }
    858 
    859 void
    860 mn_show_invalid_uri_list_dialog (GtkWindow *parent,
    861 				 const char *primary,
    862 				 GSList *invalid_uri_list)
    863 {
    864   GString *string;
    865   GSList *l;
    866 
    867   g_return_if_fail(primary != NULL);
    868   g_return_if_fail(invalid_uri_list != NULL);
    869 
    870   string = g_string_new(NULL);
    871 
    872   MN_LIST_FOREACH(l, invalid_uri_list)
    873     {
    874       const char *uri = l->data;
    875 
    876       g_string_append(string, uri);
    877       if (l->next)
    878 	g_string_append_c(string, '\n');
    879     }
    880 
    881   mn_show_error_dialog(parent,
    882 		       primary,
    883 		       ngettext("The following location is invalid:\n\n%s",
    884 				"The following locations are invalid:\n\n%s",
    885 				g_slist_length((GSList *) invalid_uri_list)),
    886 		       string->str);
    887 
    888   g_string_free(string, TRUE);
    889 }
    890 
    891 void
    892 mn_show_fatal_error_dialog (GtkWindow *parent, const char *format, ...)
    893 {
    894   va_list args;
    895 
    896   g_assert(format != NULL);
    897 
    898   va_start(args, format);
    899   show_error_dialog_real(parent, MN_DIALOG_BLOCKING, _("A fatal error has occurred in Mail Notification"), format, args);
    900   va_end(args);
    901 
    902   exit(1);
    903 }
    904 
    905 GtkWidget *
    906 mn_alert_dialog_new (GtkWindow *parent,
    907 		     GtkMessageType type,
    908 		     MNDialogFlags flags,
    909 		     const char *primary,
    910 		     const char *secondary)
    911 {
    912   GtkWidget *dialog;
    913 
    914   g_return_val_if_fail(primary != NULL, NULL);
    915   g_return_val_if_fail(secondary != NULL, NULL);
    916 
    917   dialog = gtk_message_dialog_new(parent,
    918 				  GTK_DIALOG_DESTROY_WITH_PARENT,
    919 				  type,
    920 				  GTK_BUTTONS_NONE,
    921 				  "%s",
    922 				  primary);
    923 
    924   if ((flags & MN_DIALOG_MARKUP) != 0)
    925     gtk_message_dialog_format_secondary_markup(GTK_MESSAGE_DIALOG(dialog), "%s", secondary);
    926   else
    927     gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", secondary);
    928 
    929   gtk_window_set_title(GTK_WINDOW(dialog), ""); /* HIG */
    930 
    931   return dialog;
    932 }
    933 
    934 time_t
    935 mn_time (void)
    936 {
    937   time_t t;
    938 
    939   t = time(NULL);
    940   if (t < 0)
    941     {
    942       t = 0;
    943       g_warning("unable to get current time: %s", g_strerror(errno));
    944     }
    945 
    946   return t;
    947 }
    948 
    949 char *
    950 mn_strftime (const char *format, const struct tm *timeptr)
    951 {
    952   char *buf;
    953   size_t bufsize = 64;
    954 
    955   g_return_val_if_fail(format != NULL, NULL);
    956   g_return_val_if_fail(timeptr != NULL, NULL);
    957 
    958   buf = g_malloc(bufsize);
    959   while (strftime(buf, bufsize, format, timeptr) == 0)
    960     {
    961       bufsize *= 2;
    962       buf = g_realloc(buf, bufsize);
    963     }
    964 
    965   return buf;
    966 }
    967 
    968 char *
    969 mn_format_past_time (time_t past_time, time_t now)
    970 {
    971   time_t diff;
    972 
    973   g_return_val_if_fail(past_time > 0, NULL);
    974 
    975   diff = now - past_time;
    976   if (diff >= 0)
    977     {
    978       if (diff < 60)
    979 	return g_strdup_printf(ngettext("%i second ago", "%i seconds ago", (int) diff), (int) diff);
    980       else if (diff < 60 * 60)
    981 	{
    982 	  int minutes = diff / 60;
    983 	  return g_strdup_printf(ngettext("about %i minute ago", "about %i minutes ago", minutes), minutes);
    984 	}
    985       else if (diff < 60 * 60 * 24)
    986 	{
    987 	  int hours = diff / (60 * 60);
    988 	  return g_strdup_printf(ngettext("about %i hour ago", "about %i hours ago", hours), hours);
    989 	}
    990       else if (diff < 60 * 60 * 24 * 7)
    991 	{
    992 	  int days = diff / (60 * 60 * 24);
    993 	  return g_strdup_printf(ngettext("about %i day ago", "about %i days ago", days), days);
    994 	}
    995       else
    996 	{
    997 	  int weeks = diff / (60 * 60 * 24 * 7);
    998 	  return g_strdup_printf(ngettext("about %i week ago", "about %i weeks ago", weeks), weeks);
    999 	}
   1000     }
   1001   else				/* future time: simply format it */
   1002     {
   1003       struct tm *tm;
   1004 
   1005       tm = localtime(&past_time);
   1006       g_assert(tm != NULL);
   1007 
   1008       return mn_strftime("%c", tm);
   1009     }
   1010 }
   1011 
   1012 char *
   1013 mn_format_seconds (int seconds)
   1014 {
   1015   if (seconds < MN_MINS(1))
   1016     return g_strdup_printf(ngettext("%i second", "%i seconds", seconds), seconds);
   1017   else if (seconds < MN_HOURS(1))
   1018     {
   1019       int mins = seconds / MN_MINS(1);
   1020       return g_strdup_printf(ngettext("%i minute", "%i minutes", mins), mins);
   1021     }
   1022   else if (seconds < MN_DAYS(1))
   1023     {
   1024       int hours = seconds / MN_HOURS(1);
   1025       return g_strdup_printf(ngettext("%i hour", "%i hours", hours), hours);
   1026     }
   1027   else
   1028     {
   1029       g_return_val_if_fail(seconds == MN_DAYS(1), NULL);
   1030       return g_strdup(_("1 day"));
   1031     }
   1032 }
   1033 
   1034 void
   1035 mn_g_object_null_unref (gpointer object)
   1036 {
   1037   if (object)
   1038     g_object_unref(object);
   1039 }
   1040 
   1041 static void
   1042 object_connect_weak_notify_cb (gpointer data, GObject *former_object)
   1043 {
   1044   SignalHandler *handler = data;
   1045 
   1046   if (handler->instance)
   1047     {
   1048       g_signal_handler_disconnect(handler->instance, handler->id);
   1049       mn_remove_weak_pointer(&handler->instance);
   1050     }
   1051   g_free(handler);
   1052 }
   1053 
   1054 /**
   1055  * mn_g_object_connect:
   1056  * @object: the object to associate the handlers with
   1057  * @instance: the instance to connect to
   1058  * @signal_spec: the spec for the first signal
   1059  * @...: #GCallback for the first signal, followed by data for the
   1060  *       first signal, followed optionally by more signal spec/callback/data
   1061  *       triples, followed by NULL
   1062  *
   1063  * Connects to one or more signals of @instance, associating the
   1064  * handlers with @object. The handlers will be disconnected whenever
   1065  * @object is finalized.
   1066  *
   1067  * Note: this function is not thread-safe. If @object and @instance
   1068  * are finalized concurrently, the behaviour is undefined.
   1069  *
   1070  * The signals specs must be in the same format than those passed to
   1071  * g_object_connect(), except that object-signal,
   1072  * swapped-object-signal, object-signal-after and
   1073  * swapped-object-signal-after are not accepted.
   1074  *
   1075  * Note that this function is only useful because of
   1076  * http://bugzilla.gnome.org/show_bug.cgi?id=118536, otherwise
   1077  * g_signal_connect_object() and the object specs of
   1078  * g_object_connect() could be used.
   1079  *
   1080  * Return value: @object
   1081  **/
   1082 gpointer
   1083 mn_g_object_connect (gpointer object,
   1084 		     gpointer instance,
   1085 		     const char *signal_spec,
   1086 		     ...)
   1087 {
   1088   va_list args;
   1089 
   1090   g_return_val_if_fail(G_IS_OBJECT(object), NULL);
   1091   g_return_val_if_fail(G_IS_OBJECT(instance), NULL);
   1092 
   1093   va_start(args, signal_spec);
   1094 
   1095   while (signal_spec)
   1096     {
   1097       GCallback callback = va_arg(args, GCallback);
   1098       gpointer data = va_arg(args, gpointer);
   1099       SignalHandler *handler;
   1100 
   1101       handler = g_new(SignalHandler, 1);
   1102       handler->instance = instance;
   1103 
   1104       if (g_str_has_prefix(signal_spec, "signal::"))
   1105 	handler->id = g_signal_connect(instance, signal_spec + 8, callback, data);
   1106       else if (g_str_has_prefix(signal_spec, "swapped_signal::")
   1107 	       || g_str_has_prefix(signal_spec, "swapped-signal::"))
   1108 	handler->id = g_signal_connect_swapped(instance, signal_spec + 16, callback, data);
   1109       else if (g_str_has_prefix(signal_spec, "signal_after::")
   1110 	       || g_str_has_prefix(signal_spec, "signal-after::"))
   1111 	handler->id = g_signal_connect_after(instance, signal_spec + 14, callback, data);
   1112       else if (g_str_has_prefix(signal_spec, "swapped_signal_after::")
   1113 	       || g_str_has_prefix(signal_spec, "swapped-signal-after::"))
   1114 	handler->id = g_signal_connect_data(instance, signal_spec + 22, callback, data, NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
   1115       else
   1116 	g_critical("invalid signal specification \"%s\"", signal_spec);
   1117 
   1118       mn_add_weak_pointer(&handler->instance);
   1119       g_object_weak_ref(object, object_connect_weak_notify_cb, handler);
   1120 
   1121       signal_spec = va_arg(args, const char *);
   1122     }
   1123 
   1124   va_end(args);
   1125 
   1126   return object;
   1127 }
   1128 
   1129 static void
   1130 object_clone_parameter_free (GParameter *parameter)
   1131 {
   1132   g_return_if_fail(parameter != NULL);
   1133 
   1134   g_value_unset(&parameter->value);
   1135   g_free(parameter);
   1136 }
   1137 
   1138 static void
   1139 object_clone_parameters_foreach_cb (gpointer key,
   1140 				    GParameter *parameter,
   1141 				    gpointer user_data)
   1142 {
   1143   GArray *parameters = user_data;
   1144 
   1145   g_array_append_val(parameters, *parameter);
   1146 }
   1147 
   1148 gpointer
   1149 mn_g_object_clone (gpointer object, const char *property_name, ...)
   1150 {
   1151   GHashTable *parameters;
   1152   va_list args;
   1153   GParamSpec **properties;
   1154   unsigned int n_properties;
   1155   int i;
   1156   GArray *parameters_array;
   1157   GObject *new_object;
   1158 
   1159   g_return_val_if_fail(G_IS_OBJECT(object), NULL);
   1160 
   1161   parameters = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) object_clone_parameter_free);
   1162 
   1163   /* add provided properties */
   1164 
   1165   va_start(args, property_name);
   1166 
   1167   while (property_name)
   1168     {
   1169       GParamSpec *pspec;
   1170       GParameter *parameter;
   1171       char *error = NULL;
   1172 
   1173       pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(object), property_name);
   1174       g_assert(pspec != NULL);
   1175 
   1176       parameter = g_new0(GParameter, 1);
   1177       parameter->name = g_param_spec_get_name(pspec);
   1178 
   1179       g_value_init(&parameter->value, G_PARAM_SPEC_VALUE_TYPE(pspec));
   1180       G_VALUE_COLLECT(&parameter->value, args, 0, &error);
   1181       g_assert(error == NULL);
   1182 
   1183       g_hash_table_replace(parameters, (gpointer) parameter->name, parameter);
   1184 
   1185       property_name = va_arg(args, const char *);
   1186     }
   1187 
   1188   va_end(args);
   1189 
   1190   /* clone existing properties */
   1191 
   1192   properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(object), &n_properties);
   1193   for (i = 0; i < n_properties; i++)
   1194     if ((properties[i]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE)
   1195       {
   1196 	const char *name;
   1197 
   1198 	name = g_param_spec_get_name(properties[i]);
   1199 	if (! g_hash_table_lookup(parameters, name))
   1200 	  {
   1201 	    GParameter *parameter;
   1202 
   1203 	    parameter = g_new0(GParameter, 1);
   1204 	    parameter->name = name;
   1205 
   1206 	    g_value_init(&parameter->value, G_PARAM_SPEC_VALUE_TYPE(properties[i]));
   1207 	    g_object_get_property(object, parameter->name, &parameter->value);
   1208 
   1209 	    g_hash_table_insert(parameters, (gpointer) parameter->name, parameter);
   1210 	  }
   1211       }
   1212   g_free(properties);
   1213 
   1214   parameters_array = g_array_new(FALSE, FALSE, sizeof(GParameter));
   1215 
   1216   g_hash_table_foreach(parameters, (GHFunc) object_clone_parameters_foreach_cb, parameters_array);
   1217 
   1218   new_object = g_object_newv(G_OBJECT_TYPE(object), parameters_array->len, (GParameter *) parameters_array->data);
   1219 
   1220   g_hash_table_destroy(parameters);
   1221   g_array_free(parameters_array, TRUE);
   1222 
   1223   return new_object;
   1224 }
   1225 
   1226 int
   1227 mn_utf8_strcasecmp (const char *s1, const char *s2)
   1228 {
   1229   char *normalized_s1;
   1230   char *normalized_s2;
   1231   char *folded_s1;
   1232   char *folded_s2;
   1233   int cmp;
   1234 
   1235   g_return_val_if_fail(s1 != NULL, 0);
   1236   g_return_val_if_fail(s2 != NULL, 0);
   1237 
   1238   normalized_s1 = g_utf8_normalize(s1, -1, G_NORMALIZE_ALL);
   1239   normalized_s2 = g_utf8_normalize(s2, -1, G_NORMALIZE_ALL);
   1240   folded_s1 = g_utf8_casefold(normalized_s1, -1);
   1241   folded_s2 = g_utf8_casefold(normalized_s2, -1);
   1242 
   1243   cmp = strcmp(folded_s1, folded_s2);
   1244 
   1245   g_free(normalized_s1);
   1246   g_free(normalized_s2);
   1247   g_free(folded_s1);
   1248   g_free(folded_s2);
   1249 
   1250   return cmp;
   1251 }
   1252 
   1253 gboolean
   1254 mn_utf8_str_case_has_suffix (const char *str, const char *suffix)
   1255 {
   1256   char *normalized_str;
   1257   char *normalized_suffix;
   1258   char *folded_str;
   1259   char *folded_suffix;
   1260   int str_len;
   1261   int suffix_len;
   1262   gboolean has;
   1263 
   1264   g_return_val_if_fail(str != NULL, FALSE);
   1265   g_return_val_if_fail(suffix != NULL, FALSE);
   1266 
   1267   normalized_str = g_utf8_normalize(str, -1, G_NORMALIZE_ALL);
   1268   normalized_suffix = g_utf8_normalize(suffix, -1, G_NORMALIZE_ALL);
   1269   folded_str = g_utf8_casefold(normalized_str, -1);
   1270   folded_suffix = g_utf8_casefold(normalized_suffix, -1);
   1271 
   1272   str_len = strlen(folded_str);
   1273   suffix_len = strlen(folded_suffix);
   1274 
   1275   has = str_len >= suffix_len
   1276     && ! strcmp(folded_str + str_len - suffix_len, folded_suffix);
   1277 
   1278   g_free(normalized_str);
   1279   g_free(normalized_suffix);
   1280   g_free(folded_str);
   1281   g_free(folded_suffix);
   1282 
   1283   return has;
   1284 }
   1285 
   1286 char *
   1287 mn_utf8_escape (const char *str)
   1288 {
   1289   GString *escaped;
   1290 
   1291   g_return_val_if_fail(str != NULL, NULL);
   1292 
   1293   escaped = g_string_new(NULL);
   1294 
   1295   while (*str)
   1296     {
   1297       gunichar c;
   1298 
   1299       c = g_utf8_get_char_validated(str, -1);
   1300       if (c != (gunichar) -2 && c != (gunichar) -1)
   1301 	{
   1302 	  g_string_append_unichar(escaped, c);
   1303 	  str = g_utf8_next_char(str);
   1304 	}
   1305       else
   1306 	{
   1307 	  g_string_append_printf(escaped, "\\x%02x", (unsigned int) (unsigned char) *str);
   1308 	  str++;
   1309 	}
   1310     }
   1311 
   1312   return g_string_free(escaped, FALSE);
   1313 }
   1314 
   1315 static void
   1316 dialog_run_nonmodal_shutdown_loop (RunNonmodalInfo *info)
   1317 {
   1318   g_return_if_fail(info != NULL);
   1319 
   1320   if (g_main_loop_is_running(info->loop))
   1321     g_main_loop_quit(info->loop);
   1322 }
   1323 
   1324 static void
   1325 dialog_run_nonmodal_destroy_h (GtkObject *object, gpointer user_data)
   1326 {
   1327   RunNonmodalInfo *info = user_data;
   1328 
   1329   info->destroyed = TRUE;
   1330 
   1331   /*
   1332    * mn_dialog_run_nonmodal_shutdown_loop() will be called by
   1333    * mn_dialog_run_nonmodal_unmap_h()
   1334    */
   1335 }
   1336 
   1337 static void
   1338 dialog_run_nonmodal_unmap_h (GtkWidget *widget, gpointer user_data)
   1339 {
   1340   RunNonmodalInfo *info = user_data;
   1341 
   1342   dialog_run_nonmodal_shutdown_loop(info);
   1343 }
   1344 
   1345 static void
   1346 dialog_run_nonmodal_response_h (GtkDialog *dialog,
   1347 				int response,
   1348 				gpointer user_data)
   1349 {
   1350   RunNonmodalInfo *info = user_data;
   1351 
   1352   info->response = response;
   1353 
   1354   dialog_run_nonmodal_shutdown_loop(info);
   1355 }
   1356 
   1357 static gboolean
   1358 dialog_run_nonmodal_delete_event_h (GtkWidget *widget,
   1359 				    GdkEvent *event,
   1360 				    gpointer user_data)
   1361 {
   1362   RunNonmodalInfo *info = user_data;
   1363 
   1364   dialog_run_nonmodal_shutdown_loop(info);
   1365 
   1366   return TRUE;			/* do not destroy */
   1367 }
   1368 
   1369 int
   1370 mn_dialog_run_nonmodal (GtkDialog *dialog)
   1371 {
   1372   RunNonmodalInfo info = { NULL, GTK_RESPONSE_NONE, FALSE };
   1373 
   1374   g_return_val_if_fail(GTK_IS_DIALOG(dialog), -1);
   1375 
   1376   g_object_ref(dialog);
   1377 
   1378   if (! GTK_WIDGET_VISIBLE(dialog))
   1379     gtk_widget_show(GTK_WIDGET(dialog));
   1380 
   1381   g_object_connect(dialog,
   1382 		   "signal::destroy", dialog_run_nonmodal_destroy_h, &info,
   1383 		   "signal::unmap", dialog_run_nonmodal_unmap_h, &info,
   1384 		   "signal::response", dialog_run_nonmodal_response_h, &info,
   1385 		   "signal::delete-event", dialog_run_nonmodal_delete_event_h, &info,
   1386 		   NULL);
   1387 
   1388   info.loop = g_main_loop_new(NULL, FALSE);
   1389 
   1390   GDK_THREADS_LEAVE();
   1391   g_main_loop_run(info.loop);
   1392   GDK_THREADS_ENTER();
   1393 
   1394   g_main_loop_unref(info.loop);
   1395 
   1396   if (! info.destroyed)
   1397     g_object_disconnect(dialog,
   1398 			"any-signal", dialog_run_nonmodal_destroy_h, &info,
   1399 			"any-signal", dialog_run_nonmodal_unmap_h, &info,
   1400 			"any-signal", dialog_run_nonmodal_response_h, &info,
   1401 			"any-signal", dialog_run_nonmodal_delete_event_h, &info,
   1402 			NULL);
   1403 
   1404   g_object_unref(dialog);
   1405 
   1406   return info.response;
   1407 }
   1408 
   1409 void
   1410 mn_source_clear (unsigned int *tag)
   1411 {
   1412   g_return_if_fail(tag != NULL);
   1413 
   1414   if (*tag)
   1415     {
   1416       g_source_remove(*tag);
   1417       *tag = 0;
   1418     }
   1419 }
   1420 
   1421 gboolean
   1422 mn_ascii_str_case_has_prefix (const char *str, const char *prefix)
   1423 {
   1424   int str_len;
   1425   int prefix_len;
   1426 
   1427   g_return_val_if_fail(str != NULL, FALSE);
   1428   g_return_val_if_fail(prefix != NULL, FALSE);
   1429 
   1430   str_len = strlen(str);
   1431   prefix_len = strlen(prefix);
   1432 
   1433   if (str_len < prefix_len)
   1434     return FALSE;
   1435 
   1436   return g_ascii_strncasecmp(str, prefix, prefix_len) == 0;
   1437 }
   1438 
   1439 char *
   1440 mn_ascii_strcasestr (const char *big, const char *little)
   1441 {
   1442   char *lower_big;
   1443   char *lower_little;
   1444   char *s;
   1445 
   1446   g_return_val_if_fail(big != NULL, NULL);
   1447   g_return_val_if_fail(little != NULL, NULL);
   1448 
   1449   lower_big = g_ascii_strdown(big, -1);
   1450   lower_little = g_ascii_strdown(little, -1);
   1451 
   1452   s = strstr(lower_big, lower_little);
   1453 
   1454   g_free(lower_big);
   1455   g_free(lower_little);
   1456 
   1457   return s ? (char *) big + (s - lower_big) : NULL;
   1458 }
   1459 
   1460 char *
   1461 mn_ascii_strcasestr_span (const char *big, const char *little)
   1462 {
   1463   char *s;
   1464 
   1465   g_return_val_if_fail(big != NULL, NULL);
   1466   g_return_val_if_fail(little != NULL, NULL);
   1467 
   1468   s = mn_ascii_strcasestr(big, little);
   1469   if (s)
   1470     s += strlen(little);
   1471 
   1472   return s;
   1473 }
   1474 
   1475 char *
   1476 mn_subst_command (const char *command,
   1477 		  MNSubstCommandFunction subst,
   1478 		  gpointer data,
   1479 		  GError **err)
   1480 {
   1481   GString *result;
   1482   const char *p;
   1483 
   1484   g_return_val_if_fail(command != NULL, NULL);
   1485   g_return_val_if_fail(subst != NULL, NULL);
   1486 
   1487   result = g_string_new(NULL);
   1488 
   1489   for (p = command; *p;)
   1490     if (*p == '%')
   1491       {
   1492 	char *name = NULL;
   1493 
   1494 	if (p[1] == '%')
   1495 	  {
   1496 	    g_string_append_c(result, '%');
   1497 	    p += 2;
   1498 	  }
   1499 	else if (p[1] == '{')
   1500 	  {
   1501 	    char *end;
   1502 
   1503 	    end = strchr(p + 2, '}');
   1504 	    if (! end)
   1505 	      {
   1506 		g_set_error(err, 0, 0, _("unterminated substitution"));
   1507 		goto error;
   1508 	      }
   1509 
   1510 	    name = g_strndup(p + 2, end - p - 2);
   1511 	    p = end + 1;
   1512 	  }
   1513 	else
   1514 	  {
   1515 	    const char *end = p + 1;
   1516 
   1517 	    while (g_ascii_isalnum(*end) || *end == '-' || *end == '_')
   1518 	      end++;
   1519 
   1520 	    name = g_strndup(p + 1, end - p - 1);
   1521 	    p = end;
   1522 	  }
   1523 
   1524 	if (name)
   1525 	  {
   1526 	    gboolean ok = FALSE;
   1527 
   1528 	    if (*name)
   1529 	      {
   1530 		char *value;
   1531 
   1532 		if (subst(name, &value, data))
   1533 		  {
   1534 		    char *quoted;
   1535 
   1536 		    quoted = mn_shell_quote_safe(value ? value : "");
   1537 		    g_free(value);
   1538 
   1539 		    g_string_append(result, quoted);
   1540 		    g_free(quoted);
   1541 
   1542 		    ok = TRUE;
   1543 		  }
   1544 		else
   1545 		  g_set_error(err, 0, 0, _("unknown substitution \"%s\""), name);
   1546 	      }
   1547 	    else
   1548 	      g_set_error(err, 0, 0, _("empty substitution"));
   1549 
   1550 	    g_free(name);
   1551 	    if (! ok)
   1552 	      goto error;
   1553 	  }
   1554       }
   1555     else
   1556       {
   1557 	g_string_append_c(result, *p);
   1558 	p++;
   1559       }
   1560 
   1561   goto end;			/* success */
   1562 
   1563  error:
   1564   g_string_free(result, TRUE);
   1565   result = NULL;
   1566 
   1567  end:
   1568   return result ? g_string_free(result, FALSE) : NULL;
   1569 }
   1570 
   1571 static void
   1572 handle_execute_result (int status, const char *command)
   1573 {
   1574   if (status < 0)
   1575     mn_show_error_dialog(NULL,
   1576 			 _("A command error has occurred in Mail Notification"),
   1577 			 _("Unable to execute \"%s\": %s."),
   1578 			 command,
   1579 			 g_strerror(errno));
   1580 }
   1581 
   1582 void
   1583 mn_execute_command (const char *command)
   1584 {
   1585   g_return_if_fail(command != NULL);
   1586 
   1587   handle_execute_result(gnome_execute_shell(NULL, command), command);
   1588 }
   1589 
   1590 void
   1591 mn_execute_command_in_terminal (const char *command)
   1592 {
   1593   g_return_if_fail(command != NULL);
   1594 
   1595   handle_execute_result(gnome_execute_terminal_shell(NULL, command), command);
   1596 }
   1597 
   1598 /**
   1599  * mn_shell_quote_safe:
   1600  * @unquoted_string: a literal string
   1601  *
   1602  * Like g_shell_quote(), but guarantees that the string will be quoted
   1603  * using single quotes, therefore making sure that backticks will not
   1604  * be processed.
   1605  *
   1606  * Return value: the quoted string
   1607  **/
   1608 char *
   1609 mn_shell_quote_safe (const char *unquoted_string)
   1610 {
   1611   GString *result;
   1612   int i;
   1613 
   1614   g_return_val_if_fail(unquoted_string != NULL, NULL);
   1615 
   1616   result = g_string_new("'");
   1617 
   1618   for (i = 0; unquoted_string[i]; i++)
   1619     if (unquoted_string[i] == '\'')
   1620       g_string_append(result, "'\\''");
   1621     else
   1622       g_string_append_c(result, unquoted_string[i]);
   1623 
   1624   g_string_append_c(result, '\'');
   1625 
   1626   return g_string_free(result, FALSE);
   1627 }
   1628 
   1629 GtkWidget *
   1630 mn_hig_section_new (const char *title,
   1631 		    GtkWidget **label,
   1632 		    GtkWidget **alignment)
   1633 {
   1634   GtkWidget *section;
   1635   char *markup;
   1636   GtkWidget *_label;
   1637   GtkWidget *_alignment;
   1638 
   1639   g_return_val_if_fail(title != NULL, NULL);
   1640 
   1641   section = gtk_vbox_new(FALSE, 6);
   1642 
   1643   markup = g_markup_printf_escaped("<span weight=\"bold\">%s</span>", title);
   1644   _label = gtk_label_new(markup);
   1645   g_free(markup);
   1646 
   1647   gtk_misc_set_alignment(GTK_MISC(_label), 0.0, 0.5);
   1648   gtk_label_set_use_markup(GTK_LABEL(_label), TRUE);
   1649 
   1650   gtk_box_pack_start(GTK_BOX(section), _label, FALSE, FALSE, 0);
   1651 
   1652   _alignment = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
   1653   gtk_alignment_set_padding(GTK_ALIGNMENT(_alignment), 0, 0, 12, 0);
   1654 
   1655   gtk_box_pack_start(GTK_BOX(section), _alignment, TRUE, TRUE, 0);
   1656 
   1657   gtk_widget_show(_label);
   1658   gtk_widget_show(_alignment);
   1659 
   1660   if (label)
   1661     *label = _label;
   1662   if (alignment)
   1663     *alignment = _alignment;
   1664 
   1665   return section;
   1666 }
   1667 
   1668 GtkWidget *
   1669 mn_hig_section_new_with_box (const char *title,
   1670 			     GtkWidget **label,
   1671 			     GtkWidget **vbox)
   1672 {
   1673   GtkWidget *section;
   1674   GtkWidget *alignment;
   1675   GtkWidget *_vbox;
   1676 
   1677   g_return_val_if_fail(title != NULL, NULL);
   1678 
   1679   section = mn_hig_section_new(title, label, &alignment);
   1680 
   1681   _vbox = gtk_vbox_new(FALSE, 6);
   1682   gtk_container_add(GTK_CONTAINER(alignment), _vbox);
   1683   gtk_widget_show(_vbox);
   1684 
   1685   if (vbox)
   1686     *vbox = _vbox;
   1687 
   1688   return section;
   1689 }
   1690 
   1691 char *
   1692 mn_g_value_to_string (const GValue *value)
   1693 {
   1694   char *str;
   1695 
   1696   g_return_val_if_fail(G_IS_VALUE(value), NULL);
   1697 
   1698   /*
   1699    * We only handle types which we actually export (grep for
   1700    * MN_MESSAGE_PARAM_EXPORT and MN_MAILBOX_PARAM.*SAVE).
   1701    */
   1702 
   1703   if (G_VALUE_HOLDS_INT(value))
   1704     str = g_strdup_printf("%i", g_value_get_int(value));
   1705   else if (G_VALUE_HOLDS_ULONG(value))
   1706     str = g_strdup_printf("%lu", g_value_get_ulong(value));
   1707   else if (G_VALUE_HOLDS_STRING(value))
   1708     str = g_value_dup_string(value);
   1709   else if (G_VALUE_HOLDS_ENUM(value))
   1710     {
   1711       GEnumClass *enum_class;
   1712       GEnumValue *enum_value;
   1713 
   1714       enum_class = g_type_class_ref(G_VALUE_TYPE(value));
   1715       enum_value = g_enum_get_value(enum_class, g_value_get_enum(value));
   1716       g_assert(enum_value != NULL);
   1717 
   1718       str = g_strdup(enum_value->value_nick);
   1719       g_type_class_unref(enum_class);
   1720     }
   1721   else
   1722     g_return_val_if_reached(NULL);
   1723 
   1724   return str;
   1725 }
   1726 
   1727 gboolean
   1728 mn_g_value_from_string (GValue *value, const char *str)
   1729 {
   1730   g_return_val_if_fail(G_IS_VALUE(value), FALSE);
   1731   g_return_val_if_fail(str != NULL, FALSE);
   1732 
   1733   /*
   1734    * We only handle types which we actually import (grep for
   1735    * MN_MAILBOX_PARAM_LOAD).
   1736    */
   1737 
   1738   if (G_VALUE_HOLDS_INT(value))
   1739     {
   1740       int n;
   1741       char *endptr;
   1742 
   1743       n = strtol(str, &endptr, 10);
   1744       if (*endptr == '\0')	/* successful conversion */
   1745 	g_value_set_int(value, n);
   1746       else
   1747 	return FALSE;
   1748     }
   1749   else if (G_VALUE_HOLDS_STRING(value))
   1750     g_value_set_string(value, str);
   1751   else if (G_VALUE_HOLDS_ENUM(value))
   1752     {
   1753       GEnumClass *enum_class;
   1754       GEnumValue *enum_value;
   1755       gboolean found;
   1756 
   1757       enum_class = g_type_class_ref(G_VALUE_TYPE(value));
   1758       enum_value = g_enum_get_value_by_nick(enum_class, str);
   1759 
   1760       if (enum_value)
   1761 	{
   1762 	  g_value_set_enum(value, enum_value->value);
   1763 	  found = TRUE;
   1764 	}
   1765       else
   1766 	found = FALSE;
   1767 
   1768       g_type_class_unref(enum_class);
   1769 
   1770       if (! found)
   1771 	return FALSE;
   1772     }
   1773   else
   1774     g_return_val_if_reached(FALSE);
   1775 
   1776   return TRUE;
   1777 }
   1778 
   1779 void
   1780 mn_window_present_from_event (GtkWindow *window)
   1781 {
   1782   g_return_if_fail(GTK_IS_WINDOW(window));
   1783 
   1784   gtk_window_present_with_time(window, gtk_get_current_event_time());
   1785 }
   1786 
   1787 void
   1788 mn_add_weak_pointer (gpointer object_location)
   1789 {
   1790   gpointer *p;
   1791 
   1792   g_return_if_fail(object_location != NULL);
   1793 
   1794   p = (gpointer *) object_location;
   1795   g_return_if_fail(G_IS_OBJECT(*p));
   1796 
   1797   g_object_add_weak_pointer(G_OBJECT(*p), p);
   1798 }
   1799 
   1800 void
   1801 mn_remove_weak_pointer (gpointer object_location)
   1802 {
   1803   gpointer *p;
   1804 
   1805   g_return_if_fail(object_location != NULL);
   1806 
   1807   p = (gpointer *) object_location;
   1808   g_return_if_fail(G_IS_OBJECT(*p));
   1809 
   1810   g_object_remove_weak_pointer(G_OBJECT(*p), p);
   1811   *p = NULL;
   1812 }
   1813 
   1814 int
   1815 mn_strv_find (char **strv, const char *elem)
   1816 {
   1817   int i;
   1818 
   1819   g_return_val_if_fail(strv != NULL, -1);
   1820   g_return_val_if_fail(elem != NULL, -1);
   1821 
   1822   for (i = 0; strv[i]; i++)
   1823     if (! strcmp(strv[i], elem))
   1824       return i;
   1825 
   1826   return -1;
   1827 }
   1828 
   1829 GSList *
   1830 mn_g_ptr_array_to_slist (GPtrArray *array)
   1831 {
   1832   GSList *list = NULL;
   1833   int i;
   1834 
   1835   g_return_val_if_fail(array != NULL, NULL);
   1836 
   1837   for (i = array->len - 1; i >= 0; i--)
   1838     list = g_slist_prepend(list, g_ptr_array_index(array, i));
   1839 
   1840   return list;
   1841 }
   1842 
   1843 void
   1844 mn_g_ptr_array_free_deep_custom (GPtrArray *array,
   1845 				 GFunc element_free_func,
   1846 				 gpointer user_data)
   1847 {
   1848   g_return_if_fail(array != NULL);
   1849   g_return_if_fail(element_free_func != NULL);
   1850 
   1851   g_ptr_array_foreach(array, element_free_func, user_data);
   1852   g_ptr_array_free(array, TRUE);
   1853 }
   1854 
   1855 void
   1856 mn_g_object_ptr_array_free (GPtrArray *array)
   1857 {
   1858   g_return_if_fail(array != NULL);
   1859 
   1860   mn_g_ptr_array_free_deep_custom(array, (GFunc) g_object_unref, NULL);
   1861 }
   1862 
   1863 const char *
   1864 mn_enum_get_value_nick (GType type, int value)
   1865 {
   1866   GEnumClass *enum_class;
   1867   GEnumValue *enum_value;
   1868 
   1869   g_return_val_if_fail(G_TYPE_IS_ENUM(type), NULL);
   1870 
   1871   enum_class = g_type_class_ref(type);
   1872   g_return_val_if_fail(G_IS_ENUM_CLASS(enum_class), NULL);
   1873 
   1874   enum_value = g_enum_get_value(enum_class, value);
   1875   g_return_val_if_fail(enum_value != NULL, NULL);
   1876 
   1877   g_type_class_unref(enum_class);
   1878 
   1879   return enum_value->value_nick;
   1880 }