/* recipientdlg.c - A dialog to select a mail recipient.
   Copyright (C) 2008 g10 Code GmbH.

   This file is part of GPA

   GPA is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   GPA is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
   License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, see <http://www.gnu.org/licenses/>.  */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <gtk/gtk.h>

#include "gpa.h"
#include "i18n.h"

#include "gtktools.h"
#include "selectkeydlg.h"
#include "recipientdlg.h"


struct _RecipientDlg
{
  GtkDialog parent;

  GtkWidget *clist_keys;
  GtkWidget *statushint;
  GtkWidget *radio_pgp;
  GtkWidget *radio_x509;
  GtkWidget *radio_auto;

  GtkWidget *popup_menu;

  /* Flag to disable updates of the status hint.  This is actual a
     counter with updates only allowed if it is zero.  */
  int freeze_update_statushint;

  /* Flag to disable any key selection.  This is used while a key
     selection is active.  Implemented as a counter.  */
  int freeze_key_selection;

  /* Set if this dialog has usable key to be passed back to the
     caller.  You need to call update_statushint to set it.  */
  int usable;

  /* The selected protocol.  This is also set by update_statushint.  */
  gpgme_protocol_t selected_protocol;
};


struct _RecipientDlgClass
{
  GtkDialogClass parent_class;

};


/* The parent class.  */
static GObjectClass *parent_class;


/* Indentifiers for our properties. */
enum
  {
    PROP_0,
    PROP_WINDOW,
    PROP_FORCE_ARMOR
  };


/* For performance reasons we truncate the listing of ambiguous keys
   at a reasonable value.  */
#define TRUNCATE_KEYSEARCH_AT 40


/* An object to keep information about keys.  */
struct keyinfo_s
{
  /* An array with associated key(s) or NULL if none found/selected.  */
  gpgme_key_t *keys;

  /* The allocated size of the KEYS array.  This includes the
     terminating NULL entry.  */
  unsigned int dimof_keys;

  /* If set, indicates that the KEYS array has been truncated.  */
  int truncated:1;
};


/* Management information for each recipient.  This data is used per
   recipient.  */
struct userdata_s
{
  /* The recipient's address.  */
  char *mailbox;

  /* Information about PGP keys.  */
  struct keyinfo_s pgp;

  /* Information about X.509 keys.  */
  struct keyinfo_s x509;

  /* If the user has set this field, no encryption key will be
     required for the recipient.  */
  int ignore_recipient;

};


/* Identifiers for the columns of the RECPLIST.  */
enum
  {
    RECPLIST_MAILBOX,   /* The rfc822 mailbox to whom a key needs to
                           be associated.  */
    RECPLIST_HAS_PGP,   /* A PGP certificate is available.  */
    RECPLIST_HAS_X509,  /* An X.509 certificate is available.  */
    RECPLIST_KEYID,     /* The key ID of the associated key. */

    RECPLIST_USERDATA,  /* Pointer to management information (struct
                           userdata_s *).  */

    RECPLIST_N_COLUMNS
  };





/* Create the main list of this dialog.  */
static GtkWidget *
recplist_window_new (void)
{
  GtkListStore *store;
  GtkWidget *list;
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;

  /* Create a model and associate a view.  */
  store = gtk_list_store_new (RECPLIST_N_COLUMNS,
			      G_TYPE_STRING,
                              G_TYPE_BOOLEAN,
                              G_TYPE_BOOLEAN,
			      G_TYPE_STRING,
                              G_TYPE_POINTER);

  list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));

  /* Define the columns.  */
  renderer = gtk_cell_renderer_text_new ();
  column = gtk_tree_view_column_new_with_attributes
    (NULL, renderer, "markup", RECPLIST_MAILBOX, NULL);
  gpa_set_column_title (column, _("Recipient"),
                        _("Shows the recipients of the message."
                          " A key needs to be assigned to each recipient."));
  gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);

  renderer = gtk_cell_renderer_toggle_new ();
  column = gtk_tree_view_column_new_with_attributes
    (NULL, renderer, "active", RECPLIST_HAS_PGP, NULL);
  gpa_set_column_title (column, "PGP",
                        _("Checked if at least one matching"
                          " OpenPGP certificate has been found."));
  gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);

  renderer = gtk_cell_renderer_toggle_new ();
  column = gtk_tree_view_column_new_with_attributes
    (NULL, renderer, "active", RECPLIST_HAS_X509, NULL);
  gpa_set_column_title (column, "X.509",
                        _("Checked if at least one matching"
                          " X.509 certificate for use with S/MIME"
                          " has been found."));
  gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);

  renderer = gtk_cell_renderer_text_new ();
  column = gtk_tree_view_column_new_with_attributes
    (NULL, renderer, "text", RECPLIST_KEYID, NULL);
  gpa_set_column_title (column,
                        _("Key ID"),
                        _("Shows the key ID of the selected key or"
                          " an indication that a key needs to be selected."));
  gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);


  return list;
}


/* Get an interator for the selected row.  Store it in ITER and
   returns the mdeol.  if nothing is selected NULL is return and ITER
   is not valid.  */
static GtkTreeModel *
get_selected_row (RecipientDlg *dialog, GtkTreeIter *iter)
{
  GtkTreeSelection *selection;
  GtkTreeModel *model;

  g_return_val_if_fail (dialog, NULL);

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->clist_keys));
  if (gtk_tree_selection_count_selected_rows (selection) == 1
      && gtk_tree_selection_get_selected (selection, &model, iter))
    return model;
  return NULL;
}


/* Compute and display a new help text for the statushint.  */
static void
update_statushint (RecipientDlg *dialog)
{
  gpgme_protocol_t req_protocol, sel_protocol;
  GtkTreeModel *model;
  GtkTreeIter iter;
  int missing_keys = 0;
  int ambiguous_pgp_keys = 0;
  int ambiguous_x509_keys = 0;
  int n_pgp_keys = 0;
  int n_x509_keys = 0;
  int n_keys = 0;
  const char *hint;
  int okay = 0;

  if (dialog->freeze_update_statushint)
    return;

  model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->clist_keys));

  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radio_pgp)))
    req_protocol = GPGME_PROTOCOL_OpenPGP;
  else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON
                                         (dialog->radio_x509)))
    req_protocol = GPGME_PROTOCOL_CMS;
  else
    req_protocol = GPGME_PROTOCOL_UNKNOWN;

  sel_protocol = GPGME_PROTOCOL_UNKNOWN;

  if (gtk_tree_model_get_iter_first (model, &iter))
    {
      do
        {
          gboolean has_pgp, has_x509;
          struct userdata_s *info;

          gtk_tree_model_get (model, &iter,
                              RECPLIST_HAS_PGP, &has_pgp,
                              RECPLIST_HAS_X509, &has_x509,
                              RECPLIST_USERDATA, &info,
                              -1);
          if (!info)
            missing_keys++;  /* Oops */
          else if (info->ignore_recipient)
            ;
          else if (!info->pgp.keys && !info->x509.keys)
            missing_keys++;
          else if ((req_protocol == GPGME_PROTOCOL_OpenPGP && !has_pgp)
                   ||(req_protocol == GPGME_PROTOCOL_CMS && !has_x509))
            ; /* Not of the requested protocol.  */
          else
            {
              n_keys++;
              if (info->pgp.keys && info->pgp.keys[0])
                {
                  n_pgp_keys++;
                  if (info->pgp.keys[1])
                    ambiguous_pgp_keys++;
                }
              if (info->x509.keys && info->x509.keys[0])
                {
                  n_x509_keys++;
                  if (info->x509.keys[1])
                    ambiguous_x509_keys++;
                }
            }
        }
      while (gtk_tree_model_iter_next (model, &iter));
    }

  if (req_protocol == GPGME_PROTOCOL_UNKNOWN)
    {
      /* We select the protocol with the most available keys,
         preferring PGP.  */
      if (n_pgp_keys >= n_x509_keys)
        sel_protocol = GPGME_PROTOCOL_OpenPGP;
      else if (n_x509_keys)
        sel_protocol = GPGME_PROTOCOL_CMS;
    }
  else
    sel_protocol = req_protocol;


  if (missing_keys)
    hint = _("You need to select a key for each recipient.\n"
             "To select a key right-click on the respective line.");
  else if ((sel_protocol == GPGME_PROTOCOL_OpenPGP
            && ambiguous_pgp_keys)
           || (sel_protocol == GPGME_PROTOCOL_CMS
               && ambiguous_x509_keys)
           || (sel_protocol == GPGME_PROTOCOL_UNKNOWN
               && (ambiguous_pgp_keys || ambiguous_x509_keys )))
    hint = _("You need to select exactly one key for each recipient.\n"
             "To select a key right-click on the respective line.");
  else if ((sel_protocol == GPGME_PROTOCOL_OpenPGP
            && n_keys != n_pgp_keys)
           || (sel_protocol == GPGME_PROTOCOL_CMS
               && n_keys != n_x509_keys)
           || (sel_protocol == GPGME_PROTOCOL_UNKNOWN))
    hint = _("Although you selected keys for all recipients "
             "a common encryption protocol can't be used. "
             "Please decide on one protocol by clicking one "
             "of the above radio buttons.");
  else if (n_pgp_keys && sel_protocol == GPGME_PROTOCOL_OpenPGP)
    {
      hint = _("Using OpenPGP for encryption.");
      okay = 1;
    }
  else if (n_x509_keys && sel_protocol == GPGME_PROTOCOL_CMS)
    {
      hint = _("Using S/MIME for encryption.");
      okay = 1;
    }
  else
    hint = _("No recipients - encryption is not possible");

  gtk_label_set_text (GTK_LABEL (dialog->statushint), hint);
  gtk_label_set_line_wrap (GTK_LABEL (dialog->statushint), TRUE);

  dialog->usable = okay;
  dialog->selected_protocol = sel_protocol;
  gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK,
				     okay);
}


/* Add KEY to the keyarray of KEYINFO.  Ownership of KEY is moved to
   KEYARRAY.  Returns the number of keys in KEYINFO.  */
static unsigned int
append_key_to_keyinfo (struct keyinfo_s *keyinfo, gpgme_key_t key)
{
  unsigned int nkeys;

  if (!keyinfo->keys)
    {
      keyinfo->dimof_keys = 5; /* Space for 4 keys.  */
      keyinfo->keys = g_new (gpgme_key_t, keyinfo->dimof_keys);
      keyinfo->keys[0] = NULL;
    }
  for (nkeys=0; keyinfo->keys[nkeys]; nkeys++)
    ;
  /* Note that we silently skip a KEY of NULL because we can't store
     a NULL in the array.  */
  if (key)
    {
      if (nkeys+1 >= keyinfo->dimof_keys)
        {
          keyinfo->dimof_keys += 10;
          keyinfo->keys = g_renew (gpgme_key_t, keyinfo->keys,
                                   keyinfo->dimof_keys);
        }
      keyinfo->keys[nkeys++] = key;
      keyinfo->keys[nkeys] = NULL;
    }
  return nkeys;
}


/* Clear the content of a keyinfo object.  */
static void
clear_keyinfo (struct keyinfo_s *keyinfo)
{
  unsigned int nkeys;

  if (keyinfo)
    {
      if (keyinfo->keys)
        {
          for (nkeys=0; keyinfo->keys[nkeys]; nkeys++)
            gpgme_key_unref (keyinfo->keys[nkeys]);
          g_free (keyinfo->keys);
          keyinfo->keys = NULL;
        }
      keyinfo->dimof_keys = 0;
      keyinfo->truncated = 0;
    }
}


/* Update the row in the list described by by STORE and ITER.  The new
   data shall be taken from INFO.  */
static void
update_recplist_row (GtkListStore *store, GtkTreeIter *iter,
                     struct userdata_s *info)
{
  char *infostr = NULL;
  char *oldinfostr = NULL;
  int any_pgp = 0, any_x509 = 0;
  gpgme_key_t key;
  char *mailbox;
  char *oldmailbox;

  if (info->pgp.keys && info->pgp.keys[0])
    any_pgp = 1;
  if (info->x509.keys && info->x509.keys[0])
    any_x509 = 1;

  if (info->ignore_recipient)
    infostr = NULL;
  else if (any_pgp && any_x509 && info->pgp.keys[1] && info->x509.keys[1])
    infostr = g_strdup (_("[Ambiguous keys. Right-click to select]"));
  else if (any_pgp && info->pgp.keys[1])
    infostr = g_strdup (_("[Ambiguous PGP key.  Right-click to select]"));
  else if (any_x509 && info->x509.keys[1])
    infostr = g_strdup (_("[Ambiguous X.509 key. Right-click to select]"));
  else if (any_pgp && !info->pgp.keys[1])
    {
      /* Exactly one key found.  */
      key = info->pgp.keys[0];
      infostr = gpa_gpgme_key_get_userid (key->uids);
    }
  else if (any_x509 && !info->x509.keys[1])
    {
      key = info->x509.keys[0];
      infostr = gpa_gpgme_key_get_userid (key->uids);
    }
  else
    infostr = g_strdup (_("[Right-click to select]"));


  mailbox = g_markup_printf_escaped ("<span strikethrough='%s'>%s</span>",
                                     info->ignore_recipient? "true":"false",
                                     info->mailbox);

  g_print ("   mbox=`%s' fmt=`%s'\n", info->mailbox, mailbox);
  gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
                      RECPLIST_MAILBOX, &oldmailbox,
                      RECPLIST_KEYID, &oldinfostr,
                      -1);
  gtk_list_store_set (store, iter,
                      RECPLIST_MAILBOX, mailbox,
                      RECPLIST_HAS_PGP, any_pgp,
                      RECPLIST_HAS_X509, any_x509,
                      RECPLIST_KEYID, infostr,
                      -1);
  g_free (oldmailbox);
  g_free (mailbox);
  g_free (infostr);
  g_free (oldinfostr);
}


/* Parse one recipient, this is the working horse of parse_recipeints. */
static void
parse_one_recipient (gpgme_ctx_t ctx, GtkListStore *store, GtkTreeIter *iter,
                     struct userdata_s *info)
{
  static int have_locate = -1;
  gpgme_key_t key = NULL;
  gpgme_keylist_mode_t mode;

  if (have_locate == -1)
    have_locate = is_gpg_version_at_least ("2.0.10");

  g_return_if_fail (info);

  clear_keyinfo (&info->pgp);
  gpgme_set_protocol (ctx, GPGME_PROTOCOL_OpenPGP);
  mode = gpgme_get_keylist_mode (ctx);
  if (have_locate)
    gpgme_set_keylist_mode (ctx, (mode | (GPGME_KEYLIST_MODE_LOCAL
                                          | GPGME_KEYLIST_MODE_EXTERN)));
  if (!gpgme_op_keylist_start (ctx, info->mailbox, 0))
    {
      while (!gpgme_op_keylist_next (ctx, &key))
        {
          if (key->revoked || key->disabled || key->expired
              || !key->can_encrypt)
            gpgme_key_unref (key);
          else if (append_key_to_keyinfo (&info->pgp, key)
                   >= TRUNCATE_KEYSEARCH_AT)
            {
              /* Note that the truncation flag is not 100% correct.  In
                 case the next iteration would not yield a new key we
                 have not actually truncated the search.  */
              info->pgp.truncated = 1;
              break;
            }
        }
    }
  gpgme_op_keylist_end (ctx);
  gpgme_set_keylist_mode (ctx, mode);

  clear_keyinfo (&info->x509);
  gpgme_set_protocol (ctx, GPGME_PROTOCOL_CMS);
  if (!gpgme_op_keylist_start (ctx, info->mailbox, 0))
    {
      while (!gpgme_op_keylist_next (ctx, &key))
        {
          if (key->revoked || key->disabled || key->expired
              || !key->can_encrypt)
            gpgme_key_unref (key);
          else if (append_key_to_keyinfo (&info->x509,key)
                   >= TRUNCATE_KEYSEARCH_AT)
            {
              info->x509.truncated = 1;
              break;
            }
        }
    }
  gpgme_op_keylist_end (ctx);

  update_recplist_row (store, iter, info);
}


/* Parse the list of recipients, find possible keys and update the
   store.  */
static void
parse_recipients (GtkListStore *store)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  gpg_error_t err;
  gpgme_ctx_t ctx;

  err = gpgme_new (&ctx);
  if (err)
    gpa_gpgme_error (err);


  model = GTK_TREE_MODEL (store);
  /* Walk through the list, reading each row. */

  if (gtk_tree_model_get_iter_first (model, &iter))
    do
      {
        struct userdata_s *info;

        gtk_tree_model_get (model, &iter,
                            RECPLIST_USERDATA, &info,
                            -1);

        /* Do something with the data */
        /*g_print ("parsing mailbox `%s'\n", info? info->mailbox:"(null)");*/
        parse_one_recipient (ctx, store, &iter,info);
      }
    while (gtk_tree_model_iter_next (model, &iter));

  gpgme_release (ctx);
}


/* Callback for the row-activated signal.  */
static void
recplist_row_activated_cb (GtkTreeView       *tree_view,
                           GtkTreePath       *path,
                           GtkTreeViewColumn *column,
                           gpointer           user_data)
{
  /*RecipientDlg *dialog = user_data;*/
  GtkTreeIter iter;
  GtkTreeModel *model;
  char *mailbox;

  model = gtk_tree_view_get_model (tree_view);
  if (!gtk_tree_model_get_iter (model, &iter, path))
    return;

  gtk_tree_model_get (model, &iter,
                      RECPLIST_MAILBOX, &mailbox,
                      -1);
  g_free (mailbox);
}


static void
recplist_row_changed_cb (GtkTreeModel *model, GtkTreePath *path,
                         GtkTreeIter *changediter, gpointer user_data)
{
  RecipientDlg *dialog = user_data;

  g_return_if_fail (dialog);
  update_statushint (dialog);
}


static void
rbutton_toggled_cb (GtkToggleButton *button, gpointer user_data)
{
  RecipientDlg *dialog = user_data;
  GtkTreeViewColumn *column;
  int pgp = FALSE;
  int x509 = FALSE;

  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radio_pgp)))
    {
      pgp = TRUE;
    }
  else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON
                                         (dialog->radio_x509)))
    {
      x509 = TRUE;
    }
  else
    {
      pgp = TRUE;
      x509 = TRUE;
    }

  column = gtk_tree_view_get_column (GTK_TREE_VIEW (dialog->clist_keys),
                                     RECPLIST_HAS_PGP);
  gtk_tree_view_column_set_visible (column, pgp);
  column = gtk_tree_view_get_column (GTK_TREE_VIEW (dialog->clist_keys),
                                     RECPLIST_HAS_X509);
  gtk_tree_view_column_set_visible (column, x509);
  update_statushint (dialog);
}



/* The select key selection has returned.  */
static void
select_key_response_cb (SelectKeyDlg *seldlg, int response, void *user_data)
{
  RecipientDlg *dialog = user_data;
  gpgme_key_t key;

  if (response != GTK_RESPONSE_OK)
    {
      /* The dialog was canceled */
      gtk_widget_destroy (GTK_WIDGET (seldlg));
      dialog->freeze_key_selection--;
      return;
    }

  key = select_key_dlg_get_key (seldlg);
  if (key)
    {
      GtkTreeModel *model;
      GtkTreeIter iter;
      struct userdata_s *info = NULL;
      char *uidstr = gpa_gpgme_key_get_userid (key->uids);

      g_free (uidstr);
      if ((model = get_selected_row (dialog, &iter)))
        {
          gtk_tree_model_get (model, &iter, RECPLIST_USERDATA, &info, -1);
          if (info)
            {
              if (key->protocol == GPGME_PROTOCOL_OpenPGP)
                {
                  clear_keyinfo (&info->pgp);
                  gpgme_key_ref (key);
                  append_key_to_keyinfo (&info->pgp, key);
                }
              else if (key->protocol == GPGME_PROTOCOL_CMS)
                {
                  clear_keyinfo (&info->x509);
                  gpgme_key_ref (key);
                  append_key_to_keyinfo (&info->x509, key);
                }
              update_recplist_row (GTK_LIST_STORE (model), &iter, info);
              update_statushint (dialog);
            }
        }
      gpgme_key_unref (key);
    }

  gtk_widget_destroy (GTK_WIDGET (seldlg));
  dialog->freeze_key_selection--;
}



static void
do_select_key (RecipientDlg *dialog, gpgme_protocol_t protocol)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  struct userdata_s *info = NULL;
  SelectKeyDlg *seldlg;

  if ((model = get_selected_row (dialog, &iter)))
    {
      gtk_tree_model_get (model, &iter, RECPLIST_USERDATA, &info, -1);
      if (info)
        {
          gpgme_key_t *keys;

          if (protocol == GPGME_PROTOCOL_OpenPGP)
            keys = info->pgp.keys;
          else if (protocol == GPGME_PROTOCOL_CMS)
            keys = info->x509.keys;
          else
            keys = NULL;

          seldlg = select_key_dlg_new_with_keys (GTK_WIDGET (dialog),
                                                 protocol,
                                                 keys,
                                                 info->mailbox);
          g_signal_connect (G_OBJECT (seldlg), "response",
                            G_CALLBACK (select_key_response_cb), dialog);
          gtk_widget_show_all (GTK_WIDGET (seldlg));
        }
    }
  else
    dialog->freeze_key_selection--;
}


static void
recplist_popup_pgp (GSimpleAction *simple, GVariant *parameter, gpointer user_data)
{
  RecipientDlg *dialog = user_data;

  do_select_key (dialog, GPGME_PROTOCOL_OpenPGP);
}


static void
recplist_popup_x509 (GSimpleAction *simple, GVariant *parameter, gpointer user_data)
{
  RecipientDlg *dialog = user_data;

  do_select_key (dialog, GPGME_PROTOCOL_CMS);
}


static void
recplist_popup_ignore (GSimpleAction *simple, GVariant *parameter, gpointer user_data)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  struct userdata_s *info;

  RecipientDlg *dialog = user_data;

  if ((model = get_selected_row (dialog, &iter)))
    {
      gtk_tree_model_get (model, &iter, RECPLIST_USERDATA, &info, -1);
      info->ignore_recipient = !info->ignore_recipient;
      update_recplist_row (GTK_LIST_STORE (model), &iter, info);
      update_statushint (dialog);
    }
  dialog->freeze_key_selection--;
}


/* Left Click on the list auto selects an action.  */
static void
recplist_default_action (RecipientDlg *dialog)
{

  dialog->freeze_key_selection--;
}


static gint
recplist_display_popup_menu (RecipientDlg *dialog, GdkEvent *event,
                             GtkListStore *list)
{
  GtkMenu *menu;
  GdkEventButton *event_button;

  g_return_val_if_fail (dialog, FALSE);
  g_return_val_if_fail (event, FALSE);

  menu = GTK_MENU (dialog->popup_menu);

  if (event->type == GDK_BUTTON_PRESS)
    {
      event_button = (GdkEventButton *) event;
      if (event_button->button == 1
          || event_button->button == 3)
	{
	  GtkTreeSelection *selection;
	  GtkTreePath *path;
	  GtkTreeIter iter;

          selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
          /* Make sure the clicked key is selected.  */
	  if (!dialog->freeze_key_selection
              && gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list),
                                                event_button->x,
                                                event_button->y,
                                                &path, NULL,
                                                NULL, NULL))
	    {
              dialog->freeze_key_selection++;
	      gtk_tree_model_get_iter (gtk_tree_view_get_model
				       (GTK_TREE_VIEW (list)), &iter, path);
	      if (!gtk_tree_selection_iter_is_selected (selection, &iter))
		{
		  gtk_tree_selection_unselect_all (selection);
		  gtk_tree_selection_select_path (selection, path);
		}
              if (event_button->button == 1)
                recplist_default_action (dialog);
              else
                gtk_menu_popup_at_pointer (menu, NULL);
	    }
	  return TRUE;
	}
    }

  return FALSE;
}


/* Create the popup menu for the recipient list.  */
static GtkWidget *
recplist_popup_menu_new (GtkWidget *window, RecipientDlg *dialog)
{
  static const GActionEntry entries [] = {
    { "select_gpg_key", recplist_popup_pgp },
    { "select_cms_key", recplist_popup_x509 },
    { "toggle_ignore_flag", recplist_popup_ignore },
  };

  static const char *menu_string =
    "<interface>"
      "<menu id='popup_menu'>"
        "<section>"
          "<item>"
            "<attribute name='label' translatable='yes'>Select _PGP key</attribute>"
            "<attribute name='action'>app.select_pgp_key</attribute>"
          "</item>"
          "<item>"
            "<attribute name='label' translatable='yes'>Select _S\\/MIME key</attribute>"
            "<attribute name='action'>app.select_cms_key</attribute>"
          "</item>"
          "<item>"
            "<attribute name='label' translatable='yes'>Toggle _Ignore flag</attribute>"
            "<attribute name='action'>app.toggle_ignore_flag</attribute>"
          "</item>"
        "</section>"
      "</menu>"
    "</interface>";

  GError *err = NULL;

  GtkWidget *popup_menu_widget;

  GtkBuilder *gtk_builder = gtk_builder_new ();

  if (gtk_builder_add_from_string( gtk_builder, menu_string , -1, &err) == 0) {
    printf("ERROR menu: %s \n", err->message);
  }

  GMenuModel *popup_menu_model = G_MENU_MODEL (gtk_builder_get_object (GTK_BUILDER (gtk_builder), "popup_menu"));
  popup_menu_widget = gtk_menu_new_from_model (popup_menu_model);

  GApplication *gpa_app = G_APPLICATION (get_gpa_application ());

  g_action_map_add_action_entries (G_ACTION_MAP (gpa_app),
                                    entries,
                                    G_N_ELEMENTS (entries),
                                    window);


  return popup_menu_widget;
}



/************************************************************
 ******************   Object Management  ********************
 ************************************************************/

static void
recipient_dlg_get_property (GObject *object, guint prop_id,
                            GValue *value, GParamSpec *pspec)
{
  RecipientDlg *dialog = RECIPIENT_DLG (object);

  switch (prop_id)
    {
    case PROP_WINDOW:
      g_value_set_object (value,
			  gtk_window_get_transient_for (GTK_WINDOW (dialog)));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}


static void
recipient_dlg_set_property (GObject *object, guint prop_id,
                            const GValue *value, GParamSpec *pspec)
{
  RecipientDlg *dialog = RECIPIENT_DLG (object);

  switch (prop_id)
    {
    case PROP_WINDOW:
      gtk_window_set_transient_for (GTK_WINDOW (dialog),
				    g_value_get_object (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}


static void
recipient_dlg_finalize (GObject *object)
{
  /* Fixme:  Release the store.  */
  G_OBJECT_CLASS (parent_class)->finalize (object);
}


static void
recipient_dlg_init (RecipientDlg *dialog)
{
}


static GObject*
recipient_dlg_constructor (GType type, guint n_construct_properties,
                           GObjectConstructParam *construct_properties)
{
  GObject *object;
  RecipientDlg *dialog;
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *widget;
  GtkWidget *labelKeys;
  GtkWidget *scrollerKeys;
  GtkWidget *clistKeys;

  object = parent_class->constructor (type,
				      n_construct_properties,
				      construct_properties);
  dialog = RECIPIENT_DLG (object);

  gpa_window_set_title (GTK_WINDOW (dialog), _("Select keys for recipients"));
  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
			  _("_Cancel"), GTK_RESPONSE_CANCEL,
			  _("_OK"), GTK_RESPONSE_OK,
                          NULL);
  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
  gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK,
				     FALSE);
  gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);

  //vbox = GTK_DIALOG (dialog)->vbox;
  vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog));

  gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);

  labelKeys = gtk_label_new_with_mnemonic (_("_Recipient list"));
  gtk_widget_set_halign (GTK_WIDGET (labelKeys), GTK_ALIGN_START);
  gtk_widget_set_valign (GTK_WIDGET (labelKeys), GTK_ALIGN_CENTER);
  gtk_box_pack_start (GTK_BOX (vbox), labelKeys, FALSE, FALSE, 0);

  scrollerKeys = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy  (GTK_SCROLLED_WINDOW (scrollerKeys),
				   GTK_POLICY_AUTOMATIC,
				   GTK_POLICY_AUTOMATIC);
  gtk_box_pack_start (GTK_BOX (vbox), scrollerKeys, TRUE, TRUE, 0);
  gtk_widget_set_size_request (scrollerKeys, 400, 200);

  clistKeys = recplist_window_new ();
  dialog->clist_keys = clistKeys;
  gtk_container_add (GTK_CONTAINER (scrollerKeys), clistKeys);
  gtk_label_set_mnemonic_widget (GTK_LABEL (labelKeys), clistKeys);


  dialog->popup_menu = recplist_popup_menu_new (dialog->clist_keys, dialog);
  g_signal_connect_swapped (G_OBJECT (dialog->clist_keys),
                            "button_press_event",
                            G_CALLBACK (recplist_display_popup_menu), dialog);

  dialog->radio_pgp  = gtk_radio_button_new_with_mnemonic
    (NULL, _("Use _PGP"));
  dialog->radio_x509 = gtk_radio_button_new_with_mnemonic
    (gtk_radio_button_get_group (GTK_RADIO_BUTTON (dialog->radio_pgp)),
     _("Use _X.509"));
  dialog->radio_auto = gtk_radio_button_new_with_mnemonic
    (gtk_radio_button_get_group (GTK_RADIO_BUTTON (dialog->radio_pgp)),
     _("_Auto selection"));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->radio_auto), TRUE);

  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), dialog->radio_pgp,  FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), dialog->radio_x509, FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), dialog->radio_auto, FALSE, FALSE, 0);

  widget = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
  gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);

  dialog->statushint = gtk_label_new (NULL);
  gtk_box_pack_start (GTK_BOX (vbox), dialog->statushint, FALSE, FALSE, 0);


  g_signal_connect (G_OBJECT (GTK_TREE_VIEW (dialog->clist_keys)),
                    "row-activated",
                    G_CALLBACK (recplist_row_activated_cb), dialog);

  g_signal_connect (G_OBJECT (gtk_tree_view_get_model
                              (GTK_TREE_VIEW (dialog->clist_keys))),
                    "row-changed",
                    G_CALLBACK (recplist_row_changed_cb), dialog);

  g_signal_connect (G_OBJECT (dialog->radio_pgp),
                    "toggled",
                    G_CALLBACK (rbutton_toggled_cb), dialog);
  g_signal_connect (G_OBJECT (dialog->radio_x509),
                    "toggled",
                    G_CALLBACK (rbutton_toggled_cb), dialog);
  g_signal_connect (G_OBJECT (dialog->radio_auto),
                    "toggled",
                    G_CALLBACK (rbutton_toggled_cb), dialog);


  return object;
}


static void
recipient_dlg_class_init (RecipientDlgClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  object_class->constructor = recipient_dlg_constructor;
  object_class->finalize = recipient_dlg_finalize;
  object_class->set_property = recipient_dlg_set_property;
  object_class->get_property = recipient_dlg_get_property;

  g_object_class_install_property
    (object_class,
     PROP_WINDOW,
     g_param_spec_object
     ("window", "Parent window",
      "Parent window", GTK_TYPE_WIDGET,
      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));

}


GType
recipient_dlg_get_type (void)
{
  static GType this_type;

  if (!this_type)
    {
      static const GTypeInfo this_info =
	{
	  sizeof (RecipientDlgClass),
	  (GBaseInitFunc) NULL,
	  (GBaseFinalizeFunc) NULL,
	  (GClassInitFunc) recipient_dlg_class_init,
	  NULL, /* class_finalize */
	  NULL, /* class_data */

	  sizeof (RecipientDlg),
	  0,    /* n_preallocs */
	  (GInstanceInitFunc) recipient_dlg_init,
	};

      this_type = g_type_register_static (GTK_TYPE_DIALOG,
                                          "RecipientDlg",
                                          &this_info, 0);
    }

  return this_type;
}



/************************************************************
 **********************  Public API  ************************
 ************************************************************/

RecipientDlg *
recipient_dlg_new (GtkWidget *parent)
{
  RecipientDlg *dialog;

  dialog = g_object_new (RECIPIENT_DLG_TYPE,
			 "window", parent,
			 NULL);

  return dialog;
}


/* Put RECIPIENTS into the list.  PROTOCOL select the default protocol. */
void
recipient_dlg_set_recipients (RecipientDlg *dialog, GSList *recipients,
                              gpgme_protocol_t protocol)
{
  GtkListStore *store;
  GSList *recp;
  GtkTreeIter iter;
  const char *name;
  GtkWidget *widget;

  g_return_if_fail (dialog);

  dialog->freeze_update_statushint++;

  if (protocol == GPGME_PROTOCOL_OpenPGP)
    widget = dialog->radio_pgp;
  else if (protocol == GPGME_PROTOCOL_CMS)
    widget = dialog->radio_x509;
  else
    widget = dialog->radio_auto;
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);

  if (widget != dialog->radio_auto)
    {
      gtk_widget_set_sensitive (GTK_WIDGET (dialog->radio_pgp), FALSE);
      gtk_widget_set_sensitive (GTK_WIDGET (dialog->radio_x509), FALSE);
      gtk_widget_set_sensitive (GTK_WIDGET (dialog->radio_auto), FALSE);
    }

  store = GTK_LIST_STORE (gtk_tree_view_get_model
                          (GTK_TREE_VIEW (dialog->clist_keys)));

  gtk_list_store_clear (store);
  for (recp = recipients; recp; recp = g_slist_next (recp))
    {
      name = recp->data;
      if (name && *name)
        {
          struct userdata_s *info = g_malloc0 (sizeof *info);

          info->mailbox = g_strdup (name);
          gtk_list_store_append (store, &iter);
          gtk_list_store_set (store, &iter,
                              RECPLIST_MAILBOX, g_strdup (""),
                              RECPLIST_HAS_PGP, FALSE,
                              RECPLIST_HAS_X509, FALSE,
                              RECPLIST_KEYID,  NULL,
                              RECPLIST_USERDATA, info,
                              -1);
        }
    }

  parse_recipients (store);
  dialog->freeze_update_statushint--;
  update_statushint (dialog);
}


/* Return the selected keys as well as the selected protocol.  */
gpgme_key_t *
recipient_dlg_get_keys (RecipientDlg *dialog, gpgme_protocol_t *r_protocol)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  size_t idx, nkeys;
  gpgme_key_t key, *keyarray;
  gpgme_protocol_t protocol;

  g_return_val_if_fail (dialog, NULL);

  if (!dialog->usable)
    return NULL;  /* No valid keys available.  */
  protocol = dialog->selected_protocol;

  model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->clist_keys));

  /* Count the number of possible keys.  */
  nkeys = 0;
  if (gtk_tree_model_get_iter_first (model, &iter))
    {
      do
        nkeys++;
      while (gtk_tree_model_iter_next (model, &iter));
    }
  keyarray = g_new (gpgme_key_t, nkeys+1);
  idx = 0;
  if (gtk_tree_model_get_iter_first (model, &iter))
    {
      do
        {
          char *mailbox;
          struct userdata_s *info;

          if (idx >= nkeys)
            {
              g_debug ("key list grew unexpectedly\n");
              break;
            }

          gtk_tree_model_get (model, &iter,
                              RECPLIST_MAILBOX, &mailbox,
                              RECPLIST_USERDATA, &info,
                              -1);
          if (info && !info->ignore_recipient)
            {
              if (protocol == GPGME_PROTOCOL_OpenPGP && info->pgp.keys)
                key = info->pgp.keys[0];
              else if (protocol == GPGME_PROTOCOL_CMS && info->x509.keys)
                key = info->x509.keys[0];
              else
                key = NULL;
              if (key)
                {
                  gpgme_key_ref (key);
                  keyarray[idx++] = key;
                }
            }
          g_free (mailbox);
        }
      while (gtk_tree_model_iter_next (model, &iter));
    }
  g_assert (idx < nkeys+1);
  keyarray[idx] = NULL;

  if (r_protocol)
    *r_protocol = protocol;

  return keyarray;
}



