/*
 * patch_funcs.c - General instrument patch functions
 *
 * Swami
 * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License only.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA or point your web browser to http://www.gnu.org.
 */
#include <stdio.h>
#include <gtk/gtk.h>
#include <string.h>

#include <libinstpatch/libinstpatch.h>
#include <libswami/libswami.h>

#include "SwamiguiMultiSave.h"
#include "SwamiguiPaste.h"
#include "SwamiguiProp.h"
#include "SwamiguiRoot.h"
#include "SwamiguiTree.h"
#include "util.h"
#include "i18n.h"

/* maximum notebook tab length (in characters).  Only used for item properties
   dialog currently. */
#define MAX_NOTEBOOK_TAB_LENGTH 20

/* Columns used in sample export file format combo box list store */
enum
{
    FILE_FORMAT_COL_TEXT,         /* Descriptive format label displayed in combo */
    FILE_FORMAT_COL_NAME,         /* Name identifier of format */
    FILE_FORMAT_COL_VALUE,        /* Enum value of format */
    FILE_FORMAT_COL_COUNT
};

/* Local Prototypes */

static void swamigui_cb_load_files_response(GtkWidget *dialog, gint response,
        gpointer user_data);
static gboolean swamigui_load_sample_helper(const char *fname, IpatchItem *parent_hint,
        IpatchPaste *paste, IpatchList *biglist,
        gboolean *paste_possible);
static void swamigui_cb_export_samples_response(GtkWidget *dialog,
        gint response,
        gpointer user_data);
/* global variables */

/* strings caches */
static char *path_patch_load = NULL; /* last loaded patch path */
//static char *path_patch_save = NULL; /* last saved patch path */
static char *path_sample_load = NULL; /* last sample load path */
static char *path_sample_export = NULL;	/* last sample export path */
static char *last_sample_format = NULL;	/* last sample export format */

/* clipboard for item selections */
static IpatchList *item_clipboard = NULL;

/* global Prototypes */
void
swamigui_copy_items (IpatchList *items);

/*--- Initialization / deinitialization -------------------------------------*/

/***
 * Initialization
 */
void _patch_funcs_init(void)
{
    /* string caches */
    path_patch_load = NULL;    /* last loaded patch path */
    path_sample_load = NULL;   /* last sample load path */
    path_sample_export = NULL; /* last sample export path */
    last_sample_format = NULL; /* last sample export format */

    /* clipboard for item selections */
    item_clipboard = NULL;
}

/***
 * Deinitialization: free caches and clipboard
 */
void _patch_funcs_deinit(void)
{
    /* free string caches */
    g_free(path_patch_load);    /* last loaded patch path */
    g_free(path_sample_load);   /* last sample load path */
    g_free(path_sample_export); /* last sample export path */
    g_free(last_sample_format); /* last sample export format */

    /* free clipboard */
    if(item_clipboard)
    {
        swamigui_copy_items(NULL);
    }
}
/*----- patch_funcs functions -----------------------------------------------*/

/**
 * swamigui_load_files:
 * @parent_hint: Parent of new samples, a child thereof or SwamiRoot object
 * @load_samples: TRUE to load audio files only, FALSE for patch and audio files
 *
 * Open files routine. Displays a file selection dialog to open patch
 * and sample files with.
 */
void
swamigui_load_files(GObject *parent_hint, gboolean load_samples)
{
    GtkWidget *dialog;
    GtkWindow *main_window;
    char *path;
    static const char *strkey = "Load files";

    /* Check if the dialog is already open and activate it.
       This ensures that the dialog is created only one time.
    */
    if(swamigui_util_activate_unique_dialog(strkey, 0))
    {
        return;
    }

    /* ++ ref main window */
    g_object_get(swamigui_root, "main-window", &main_window, NULL);

    dialog = gtk_file_chooser_dialog_new(_("Load files"), main_window,
                                         GTK_FILE_CHOOSER_ACTION_OPEN,
                                         GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL,
                                         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
                                         GTK_STOCK_ADD, GTK_RESPONSE_APPLY,
                                         NULL);

    g_object_unref(main_window);	   /* -- unref main window */

    /* register the dialog as unique. */
    swamigui_util_register_unique_dialog(dialog, strkey, 0);

    /* enable multiple selection mode */
    gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);

    /* set default response */
    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);

    g_object_set_data(G_OBJECT(dialog), "_load_samples", GUINT_TO_POINTER(load_samples));

    // Is file path set? If not use default from preferences
    if(load_samples)
    {
        if(!path_sample_load)
        {
            SwamiRoot *root = swami_get_root(G_OBJECT(parent_hint));
            g_object_get(G_OBJECT(root), "sample-path", &path_sample_load, NULL);     // ++ alloc sample path
        }

        path = path_sample_load;
    }
    else
    {
        if(!path_patch_load)
        {
            SwamiRoot *root = swami_get_root(G_OBJECT(parent_hint));
            g_object_get(root, "patch-path", &path_patch_load, NULL);         // ++ alloc patch path
        }

        path = path_patch_load;
    }

    if(path && strlen(path))
    {
        gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path);
    }

    if(parent_hint)
    {
        g_object_ref(parent_hint);    /* ++ ref for dialog */
    }

    g_signal_connect(G_OBJECT(dialog), "response",
                     G_CALLBACK(swamigui_cb_load_files_response),
                     parent_hint);

    gtk_widget_show(dialog);
}

/* loads the list of user selected files */
static void
swamigui_cb_load_files_response(GtkWidget *dialog, gint response,
                                gpointer user_data)
{
    GObject *parent_hint = G_OBJECT(user_data);
    SwamiRoot *root = swami_get_root(G_OBJECT(parent_hint));
    GSList *file_names, *p;
    gboolean patch_loaded = FALSE, samples_loaded = FALSE;
    char *fname;
    GError *err = NULL;
    GtkRecentManager *manager;
    char *file_uri;
    GType type;
    gboolean load_samples, paste_possible;
    IpatchPaste *paste;
    IpatchList *biglist;
    GtkRecentData recent_data;
    char *groups[2] = { SWAMIGUI_ROOT_INSTRUMENT_FILES_GROUP, NULL };
    IpatchItem *patch;

    if(response != GTK_RESPONSE_ACCEPT && response != GTK_RESPONSE_APPLY)
    {
        if(parent_hint)
        {
            g_object_unref(parent_hint);
        }

        gtk_widget_destroy(dialog);
        return;
    }

    load_samples = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(dialog), "_load_samples"));

    paste = ipatch_paste_new();	/* ++ ref paste object */
    biglist = ipatch_list_new();	/* ++ ref list */

    /* "Add" or "OK" button clicked */

    file_names = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));

    /* loop over file names */
    for(p = file_names; p; p = g_slist_next(p))
    {
        fname = (char *)(p->data);

        // Identify file type
        type = ipatch_file_identify_name(fname, &err);

        if(type == G_TYPE_NONE)
        {
            g_critical(_("Failed to identify file '%s': %s"), fname,
                       ipatch_gerror_message(err));
            g_clear_error(&err);
            continue;
        }

        if(!load_samples && ipatch_find_converter(type, IPATCH_TYPE_BASE) != 0)
        {
            patch_loaded = TRUE;      // Set patch path regardless if successful

            /* loading patch file and log error on Gtk message dialog */
            if(!swamigui_root_patch_load(root, fname, &patch, GTK_WINDOW(dialog)))
            {
                continue;
            }

            /* loading is success, ++ ref on patch object */

            if((file_uri = g_filename_to_uri(fname, NULL, NULL)))     // ++ alloc
            {
                manager = gtk_recent_manager_get_default();

                recent_data.display_name = NULL;
                recent_data.description = NULL;

                // ++ alloc mime type
                recent_data.mime_type = ipatch_base_type_get_mime_type(G_OBJECT_TYPE(patch));

                if(!recent_data.mime_type)
                {
                    recent_data.mime_type = g_strdup("application/octet-stream");
                }

                recent_data.app_name = g_strdup(g_get_application_name());              // ++ alloc
                recent_data.app_exec = g_strjoin(" ", g_get_prgname(), "%f", NULL);     // ++ alloc
                recent_data.groups = groups;
                recent_data.is_private = FALSE;

                // Add full info to set group of instrument files, to filter out sample files from the recent menu, etc.
                if(!gtk_recent_manager_add_full(manager, file_uri, &recent_data))
                {
                    g_warning("Error while adding file name to recent manager.");
                }

                g_free(recent_data.mime_type);  // -- free mime type
                g_free(recent_data.app_name);   // -- free application name
                g_free(recent_data.app_exec);   // -- free app exec command
                g_free(file_uri);               // -- free file uri
            }

            g_object_unref(patch);            // -- free
        }
        else if(g_type_is_a(type, IPATCH_TYPE_SND_FILE))
        {
            paste_possible = FALSE;

            if(!IPATCH_IS_ITEM(parent_hint)
                    || !swamigui_load_sample_helper(fname, IPATCH_ITEM(parent_hint),
                                                    paste, biglist, &paste_possible))
            {
                if(!paste_possible && !samples_loaded)
                {
                    GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(SWAMIGUI_ROOT(root)->main_window),
                                                            GTK_DIALOG_DESTROY_WITH_PARENT,
                                                            GTK_MESSAGE_ERROR,
                                                            GTK_BUTTONS_CLOSE,
                                                            _("Please select location in tree view to load samples into."));
                    gtk_dialog_run(GTK_DIALOG(msg));
                    gtk_widget_destroy(msg);
                }
            }

            samples_loaded = TRUE;       // Set sample path regardless if successful
        }
        else
        {
            if(load_samples)
            {
                g_critical(_("File '%s' is not a supported sample file"), fname);
            }
            else
            {
                g_critical(_("File '%s' is not a supported file type"), fname);
            }
        }
    }

    if(samples_loaded)
    {
        /* finish the paste operation */
        if(ipatch_paste_finish(paste, &err))
        {
            /* select all samples which were added */
            biglist->items = g_list_reverse(biglist->items);	/* put in right order */
            g_object_set(swamigui_root, "selection", biglist, NULL);
        }
        else
        {
            g_critical(_("Failed to finish load of samples (paste operation): %s"),
                       ipatch_gerror_message(err));
            g_clear_error(&err);
        }

        /* !! free old load path and take over allocation of new path */
        g_free(path_sample_load);
        path_sample_load = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog));
    }

    g_object_unref(biglist);	/* -- unref list */
    g_object_unref(paste);	/* -- unref paste object */

    if(patch_loaded)
    {
        /* !! free old load path and take over allocation of new path */
        g_free(path_patch_load);
        path_patch_load = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog));
    }

    /* free the file name list and strings */
    for(p = file_names; p; p = g_slist_delete_link(p, p))
    {
        g_free(p->data);
    }

    /* destroy dialog if "OK" button was clicked */
    if(response == GTK_RESPONSE_ACCEPT)
    {
        if(parent_hint)
        {
            g_object_unref(parent_hint);
        }

        gtk_widget_destroy(dialog);
    }
}

static gboolean
swamigui_load_sample_helper(const char *fname, IpatchItem *parent_hint,
                            IpatchPaste *paste, IpatchList *biglist, gboolean *paste_possible)
{
    IpatchFile *file;
    IpatchList *list;
    GList *lp;
    int size, max_size;
    GError *err = NULL;

    *paste_possible = TRUE;

    file = ipatch_file_identify_new(fname, &err);         // ++ ref file

    if(!file)
    {
        g_critical(_("Failed to identify and open file '%s': %s"), fname,
                   ipatch_gerror_message(err));
        g_clear_error(&err);
        return (FALSE);
    }

    g_object_get(swami_root, "sample-max-size", &max_size, NULL);

    if(max_size != 0)     // 0 means unlimited import size
    {
        size = ipatch_file_get_size(file, &err);

        if(size == -1)
        {
            g_warning(_("Failed to get sample file '%s' size: %s"), fname, ipatch_gerror_message(err));
            g_clear_error(&err);
        }
        else if(size > max_size * 1024 * 1024)
        {
            g_critical(_("Sample file '%s' of %d bytes exceeds max sample setting of %dMB"), fname, size, max_size);
            return (FALSE);
        }
    }

    /* determine if IpatchSampleFile can be pasted to destination.. */
    if(!ipatch_is_paste_possible(IPATCH_ITEM(parent_hint),
                                 IPATCH_ITEM(file)))
    {
        g_object_unref(file);       // -- unref file
        *paste_possible = FALSE;
        return (FALSE);
    }

    /* paste sample file to destination */
    if(!ipatch_paste_objects(paste, IPATCH_ITEM(parent_hint),
                             IPATCH_ITEM(file), &err))
    {
        /* object paste failed */
        g_critical(_("Failed to load object of type '%s' to '%s': %s"),
                   g_type_name(IPATCH_TYPE_SND_FILE),
                   g_type_name(G_OBJECT_TYPE(parent_hint)),
                   ipatch_gerror_message(err));
        g_clear_error(&err);
        g_object_unref(file);       // -- unref file
        return (FALSE);
    }

    g_object_unref(file);         // -- unref file

    list = ipatch_paste_get_add_list(paste);      /* ++ ref added object list */

    if(list)
    {
        /* biglist takes over items of list */
        for(lp = list->items; lp; lp = g_list_delete_link(lp, lp))
        {
            biglist->items = g_list_prepend(biglist->items, lp->data);
        }

        list->items = NULL;
        g_object_unref(list);	/* -- unref list */
    }

    return (TRUE);
}

/**
 * swamigui_close_files:
 * @item_list: List of items to close (usually only #IpatchBase derived
 *   objects make sense).
 *
 * User interface to close files.
 */
void
swamigui_close_files(IpatchList *item_list)
{
    GtkWidget *dialog;
    IpatchIter iter;
    IpatchItem *item;
    gboolean patch_found = FALSE;
    gboolean changed;
    GError *err = NULL;

    /* see if there are any patch items to close and if they have been changed */
    ipatch_list_init_iter(item_list, &iter);
    item = ipatch_item_first(&iter);

    while(item)
    {
        if(IPATCH_IS_BASE(item))   /* only IpatchBase derived patches */
        {
            patch_found = TRUE;
            g_object_get(item, "changed", &changed, NULL);

            if(changed)
            {
                break;
            }
        }

        item = ipatch_item_next(&iter);
    }

    if(!patch_found)
    {
        return;    /* no patches to close, return */
    }

    /* if no items changed, then go ahead and close files */
    if(!item)
    {
        if(!ipatch_close_base_list(item_list, &err))
        {
            g_warning(_("Failed to close file(s): %s"), ipatch_gerror_message(err));
            g_clear_error(&err);
        }

        return;
    }

    /* item(s) have been changed, pop user interactive dialog */
    dialog = swamigui_multi_save_new(_("Close files"),
                                     _("Save changed files before closing?"),
                                     SWAMIGUI_MULTI_SAVE_CLOSE_MODE);
    swamigui_multi_save_set_selection(SWAMIGUI_MULTI_SAVE(dialog), item_list);
    gtk_widget_show(dialog);
}

/**
 * swamigui_save_files:
 * @item_list: List of items to save.
 * @saveas: TRUE forces popup of dialog even if all files have previously
 *   been saved (forces "Save As").
 *
 * Save files user interface.  If @saveas is %FALSE and all selected files
 * have already been saved before, then they are saved.  If only one file has
 * not yet been saved then the normal save as file dialog is shown.  If
 * multiple files have not been saved or @saveas is %TRUE then the multi-file
 * save dialog is used.
 */
void
swamigui_save_files(IpatchList *item_list, gboolean saveas)
{
    GtkWidget *dialog;
    IpatchItem *item, *base;
    char *filename;
    gboolean popup = FALSE, match = FALSE;
    gboolean saved, changed;
    GError *err = NULL;
    int savecount = 0, failcount = 0;
    GList *p;

    /* see if any items have been changed */
    for(p = item_list->items; p; p = p->next)
    {
        item = (IpatchItem *)(p->data);
        base = ipatch_item_get_base(item);          /* ++ ref base object */

        if(base)            /* only save IpatchBase items or children thereof */
        {
            match = TRUE;		/* found a patch base object */
            g_object_get(base,
                         "saved", &saved,
                         "changed", &changed,
                         NULL);

            if(!saved)
            {
                popup = TRUE;    /* never been saved, force dialog */
            }

            g_object_unref(base);     /* -- unref base */
        }
    }

    if(!match)
    {
        return;    /* return, if there are no items to save */
    }

    popup |= saveas;		/* force dialog popup, "Save As"? */

    /* no dialog required? (all items previously saved and !saveas) */
    if(!popup)
    {
        for(p = item_list->items; p; p = p->next)
        {
            item = (IpatchItem *)(p->data);
            base = ipatch_item_get_base(item);          /* ++ ref base object */

            if(!base)
            {
                continue;
            }

            g_object_get(base, "file-name", &filename, NULL);         /* ++ alloc filename */

            if(!swami_root_patch_save(IPATCH_ITEM(base), filename, &err))
            {
                g_critical(_("Failed to save file '%s': %s"), filename,
                           ipatch_gerror_message(err));
                g_clear_error(&err);
                failcount++;
            }
            else
            {
                savecount++;
            }

            g_free(filename);         /* -- free filename */
            g_object_unref(base);     /* -- unref base object */
        }

        if(!failcount)
            swamigui_statusbar_printf(swamigui_root->statusbar,
                                      _("Saved %d file(s)"), savecount);
        else
            swamigui_statusbar_printf(swamigui_root->statusbar,
                                      _("Saved %d file(s), %d FAILED"), savecount,
                                      failcount);

        return;
    }

    /* save-as was requested or a file has not yet been saved */
    dialog = swamigui_multi_save_new(_("Save files"),
                                     _("Select files to save"), 0);
    swamigui_multi_save_set_selection(SWAMIGUI_MULTI_SAVE(dialog), item_list);

    /* if "save as" is requested and the selection is simple opens the
       "File save as" dialog directly otherwise the "Save files dialog" is show.
    */
    if(saveas && (item_list && item_list->items  && !item_list->items->next))
    {
        /* opens "file save as" dialog */
        swamigui_save_as_browser(NULL, SWAMIGUI_MULTI_SAVE(dialog));
        gtk_widget_destroy(dialog);
    }
    else
    {
        gtk_widget_show(dialog); /* show "Save files" dialog */
    }
}

/**
 * swamigui_delete_items:
 * @item_list: List of items to delete
 *
 * Delete patch items
 */
void
swamigui_delete_items(IpatchList *item_list)
{
    IpatchItem *parent = NULL;
    gboolean same_parent = TRUE;
    IpatchItem *item;
    IpatchIter iter;
    IpatchList *list;

    ipatch_list_init_iter(item_list, &iter);

    for(item = ipatch_item_first(&iter); item; item = ipatch_item_next(&iter))
    {
        if(IPATCH_IS_ITEM(item) && !IPATCH_IS_BASE(item))
        {
            if(same_parent)
            {
                if(parent)
                {
                    if(parent != ipatch_item_peek_parent(item))
                    {
                        same_parent = FALSE;
                    }
                }
                else
                {
                    parent = ipatch_item_get_parent(item);    /* ++ ref parent */
                }
            }

            ipatch_item_remove(item);
        }
    }

    /* If all items had same parent and it wasn't the patch object, make it the
     * new selection */
    if(same_parent && parent && !IPATCH_IS_BASE(parent))
    {
        list = ipatch_list_new();   /* ++ ref list */
        list->items = g_list_append(list->items, g_object_ref(parent));
        swamigui_tree_set_selection(SWAMIGUI_TREE(swamigui_root->tree), list);
        g_object_unref(list);       /* -- unref list */
    }

    if(parent)
    {
        g_object_unref(parent);    /* -- unref parent */
    }
}

/**
 * swamigui_wtbl_load_patch:
 * @item: Patch to load into wavetable.
 *
 * Load a patch item
 */
void
swamigui_wtbl_load_patch(IpatchItem *item)
{
    SwamiRoot *root;
    GObject *wavetbl;
    GError *err = NULL;

    /* IpatchBase derived objects only */
    if(!IPATCH_IS_BASE(item))
    {
        return;
    }

    root = swami_get_root(G_OBJECT(item));

    if(!root)
    {
        return;
    }

    wavetbl = swami_object_get_by_type(G_OBJECT(root), "SwamiWavetbl");

    if(wavetbl)
    {
        if(!swami_wavetbl_load_patch(SWAMI_WAVETBL(wavetbl), item, &err))
        {
            g_critical(_("Patch load failed: %s"), ipatch_gerror_message(err));
            g_clear_error(&err);
        }
    }
}

/**
 * swamigui_new_item:
 * @parent_hint: The parent of the new item or a hint item. An example of
 *   a hint item is a SWAMIGUI_TREE_PRESET_MELODIC item which would allow the
 *   real IpatchSF2Preset parent to be found, and would also indicate that
 *   the new zone should be in the melodic branch. Can (and should be) NULL for
 *   toplevel patch objects (IpatchSF2, etc).
 * @type: GType of an #IpatchItem derived type to create.
 *
 * Create a new patch item.
 */
void
swamigui_new_item(IpatchItem *parent_hint, GType type)
{
    IpatchItem *new_item, *real_parent = NULL;
    IpatchVirtualContainerConformFunc conform_func;
    IpatchList *list;

    g_return_if_fail(!parent_hint || IPATCH_IS_ITEM(parent_hint));

    if(!parent_hint)
    {
        parent_hint = IPATCH_ITEM(swami_root->patch_root);
    }

    new_item = g_object_new(type, NULL);	/* ++ ref new item */

    /* parent hint is a virtual container? */
    if(IPATCH_IS_VIRTUAL_CONTAINER(parent_hint))
    {
        /* get virtual container conform function if any */
        ipatch_type_get(G_OBJECT_TYPE(parent_hint), "virtual-child-conform-func",
                        &conform_func, NULL);

        real_parent = ipatch_item_get_parent(parent_hint);  /* ++ ref parent */
        g_return_if_fail(real_parent != NULL);

        parent_hint = real_parent;

        /* force the new item to conform to virtual container parent_hint */
        if(conform_func)
        {
            conform_func((GObject *)new_item);
        }
    }

    /* add and make unique (if appropriate) */
    ipatch_container_add_unique(IPATCH_CONTAINER(parent_hint), new_item);

    /* -- unref real parent object if parent_hint was a virtual container */
    if(real_parent)
    {
        g_object_unref(real_parent);
    }

    /* update selection to be new item */
    list = ipatch_list_new();	/* ++ ref new list */
    list->items = g_list_append(list->items, new_item);   /* !! list takes over ref */

    g_object_set(swamigui_root, "selection", list, NULL);
    g_object_unref(list);	/* -- unref new list */
}

/**
 * swamigui_goto_link_item:
 * @item: Patch item
 * @tree: Swami tree object
 *
 * Goto an item's linked item in a #SwamiguiTree object.
 * Moves the view and selects the item in a #SwamiguiTree that is linked
 * by @item.
 */
void
swamigui_goto_link_item(IpatchItem *item, SwamiguiTree *tree)
{
    GObject *link = NULL;

    g_return_if_fail(IPATCH_IS_ITEM(item));
    g_return_if_fail(SWAMIGUI_IS_TREE(tree));

    g_object_get(item, "link-item", &link, NULL);  /* ++ ref link */

    if(link)
    {
        swamigui_tree_spotlight_item(tree, link);
        g_object_unref(link);	/* -- unref from g_object_get */
    }
}

/**
 * swamigui_export_samples:
 * @samples: List of objects (non #IpatchSample interface items are ignored)
 *
 * Export one or more samples (object with #IpatchSample interface) to a file
 * or directory.
 */
void
swamigui_export_samples(IpatchList *samples)
{
    gboolean found_sample = FALSE;
    GtkWindow *main_window;
    GtkWidget *dialog;
    GtkWidget *hbox;
    GtkWidget *label;
    GtkWidget *combo;
    GEnumClass *format_enum;
    GtkListStore *format_store;
    GtkCellRenderer *renderer;
    gboolean multi;
    GtkTreeIter iter;
    IpatchSample *sample = NULL;
    int sel, def_index = 0;
    guint i;
    GList *p;

    g_return_if_fail(IPATCH_IS_LIST(samples));

    for(p = samples->items; p; p = p->next)
    {
        if(IPATCH_IS_SAMPLE(p->data))
        {
            if(found_sample)
            {
                break;
            }

            sample = p->data;
            found_sample = TRUE;
        }
    }

    if(!found_sample)
    {
        return;
    }

    multi = (p != NULL);  /* Multi sample selection? */

    /* ++ ref main window */
    g_object_get(swamigui_root, "main-window", &main_window, NULL);

    /* if only 1 item found, create a file save dialog, otherwise create a
       folder selection dialog */
    dialog = gtk_file_chooser_dialog_new(_("Export samples"), main_window,
                                         multi ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
                                         : GTK_FILE_CHOOSER_ACTION_SAVE,
                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                         GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
                                         NULL);
    g_object_unref(main_window);		/* -- unref main window */

    /* set default response */
    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);

    /* if sample load path isn't set, use default from config */
    if(!path_sample_export)
    {
        g_object_get(swamigui_root, "sample-path", &path_sample_export, NULL);
    }

    if(path_sample_export && strlen(path_sample_export))
    {
        gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
                                            path_sample_export);

        if(!multi)          /* Single sample export? */
        {
            char *name, *filename;

            /* Set the file name to be that of the sample's title */
            g_object_get(sample, "title", &name, NULL);       /* ++ alloc name */
            filename = g_strconcat(name, ".wav", NULL);      /* ++ alloc filename */
            g_free(name);     /* -- free name */

            gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), filename);
            g_free(filename);         /* -- free file name */
        }
    }

    g_object_set_data_full(G_OBJECT(dialog), "samples", g_object_ref(samples),
                           (GDestroyNotify)g_object_unref);
    g_object_set_data(G_OBJECT(dialog), "multi", GINT_TO_POINTER(multi));

    g_signal_connect(G_OBJECT(dialog), "response",
                     G_CALLBACK(swamigui_cb_export_samples_response), NULL);

    /* Create file format selector combo */

    hbox = gtk_hbox_new(FALSE, 4);
    gtk_widget_show(hbox);
    gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, FALSE, 0);

    label = gtk_label_new(_("File format"));
    gtk_widget_show(label);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);

    /* ++ ref new store for file format combo box */
    format_store = gtk_list_store_new(FILE_FORMAT_COL_COUNT, G_TYPE_STRING,
                                      G_TYPE_STRING, G_TYPE_INT);
    combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(format_store));

    renderer = gtk_cell_renderer_text_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
                                   "text", FILE_FORMAT_COL_TEXT, NULL);

    if(!last_sample_format)
    {
        g_object_get(swamigui_root, "sample-format", &last_sample_format, NULL);

        if(!last_sample_format)
        {
            last_sample_format = g_strdup("wav");
        }
    }

    sel = -1;	/* selected index */

    /* Populate file formats */
    format_enum = g_type_class_ref(g_type_from_name("IpatchSndFileFormat"));      /* ++ ref class */

    if(format_enum)
    {
        for(i = 0; i < format_enum->n_values; i++)
        {
            gtk_list_store_append(format_store, &iter);
            gtk_list_store_set(format_store, &iter,
                               FILE_FORMAT_COL_TEXT, format_enum->values[i].value_nick,
                               FILE_FORMAT_COL_NAME, format_enum->values[i].value_name,
                               FILE_FORMAT_COL_VALUE, format_enum->values[i].value,
                               -1);

            if(last_sample_format
                    && strcmp(last_sample_format, format_enum->values[i].value_name) == 0)
            {
                sel = i;
            }

            if(format_enum->values[i].value == IPATCH_SND_FILE_DEFAULT_FORMAT)
            {
                def_index = i;
            }
        }

        g_type_class_unref(format_enum);    /* -- unref enum class */
    }

    gtk_combo_box_set_active(GTK_COMBO_BOX(combo), sel != -1 ? sel : def_index);

    g_object_unref(format_store);         /* -- unref the list store */

    gtk_widget_show(combo);
    gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
    g_object_set_data(G_OBJECT(dialog), "combo", combo);

    gtk_widget_show(dialog);
}

static void
swamigui_cb_export_samples_response(GtkWidget *dialog, gint response,
                                    gpointer user_data)
{
    IpatchList *samples;
    IpatchSample *sample;
    GtkWidget *combo;
    gboolean multi;
    char *filepath;	/* file name or directory name (single/multi) */
    char *filename;
    GError *err = NULL;
    GtkTreeModel *format_model;
    GtkTreeIter iter;
    char *format_name;
    int format_value = IPATCH_SND_FILE_DEFAULT_FORMAT;
    GList *p;

    if(response != GTK_RESPONSE_ACCEPT)
    {
        gtk_widget_destroy(dialog);
        return;
    }

    samples = IPATCH_LIST(g_object_get_data(G_OBJECT(dialog), "samples"));
    multi = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(dialog), "multi"));
    combo = GTK_WIDGET(g_object_get_data(G_OBJECT(dialog), "combo"));

    filepath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));

    if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter))
    {
        format_model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));

        gtk_tree_model_get(format_model, &iter,
                           FILE_FORMAT_COL_NAME, &format_name,     /* ++ alloc */
                           FILE_FORMAT_COL_VALUE, &format_value,
                           -1);
    }

    /* update last sample format */
    if(last_sample_format)
    {
        g_free(last_sample_format);
    }

    last_sample_format = format_name;  /* !! last_sample_format takes over alloc */

    for(p = samples->items; p; p = p->next)
    {
        if(!IPATCH_IS_SAMPLE(p->data))
        {
            continue;
        }

        sample = IPATCH_SAMPLE(p->data);

        /* compose file name */
        if(multi)
        {
            char *name, *temp;

            g_object_get(sample, "title", &name, NULL);
            temp = g_strconcat(name, ".", format_name, NULL);
            g_free(name);

            filename = g_build_filename(filepath, temp, NULL);
            g_free(temp);
        }
        else
        {
            filename = g_strdup(filepath);
        }

        if(!ipatch_sample_save_to_file(sample, filename, format_value, -1, &err))
        {
            g_critical(_("Failed to save sample '%s': %s"), filename,
                       ipatch_gerror_message(err));
            continue;
        }

        g_free(filename);
    }

    g_free(filepath);

    gtk_widget_destroy(dialog);
}

/**
 * swamigui_copy_items:
 * @items: List of items to copy or %NULL to clear
 *
 * Set the item clipboard to a given list of items.
 */
void
swamigui_copy_items(IpatchList *items)
{
    g_return_if_fail(!items || IPATCH_IS_LIST(items));

    if(item_clipboard)
    {
        g_object_unref(item_clipboard);
        item_clipboard = NULL;
    }

    if(items)
    {
        item_clipboard = ipatch_list_duplicate(items);
    }
}

/* structure used to remember user decisions */
typedef struct
{
    SwamiguiPasteDecision all;   /* choice for all items or 0 for none */
    GList *types;		   /* per type choices (RememberTypeChoice) */
} RememberChoices;

/* per item type choice structure */
typedef struct
{
    GType type;			/* GType of this choice */
    SwamiguiPasteDecision choice;	/* choice */
} RememberTypeChoice;

/**
 * swamigui_paste_items:
 * @dstitem: Destination item for paste
 * @items: List of source items to paste to destination item or %NULL
 *   to use item clipboard
 *
 * Paste items user interface routine
 */
void
swamigui_paste_items(IpatchItem *dstitem, GList *items)
{
    IpatchPaste *paste;		/* paste instance */
    IpatchItem *src;
    GError *err = NULL;
    GList *p;

    /* use clipboard if no items given */
    if(!items && item_clipboard)
    {
        items = item_clipboard->items;
    }

    paste = ipatch_paste_new();		/* ++ ref new paste instance */

    for(p = items; p; p = p->next)	/* loop on source items */
    {
        src = IPATCH_ITEM(p->data);

        if(ipatch_is_paste_possible(dstitem, src))	/* paste possible? */
        {
            /* add paste operation to instance */
            if(!ipatch_paste_objects(paste, dstitem, src, &err))
            {
                g_critical(_("Failed to paste item of type %s to %s: %s"),
                           G_OBJECT_TYPE_NAME(src),
                           G_OBJECT_TYPE_NAME(dstitem),
                           ipatch_gerror_message(err));
                g_clear_error(&err);
            }
        }
    }

    /* complete the paste operations */
    if(!ipatch_paste_finish(paste, &err))
    {
        g_critical(_("Failed to execute paste operation: %s"),
                   ipatch_gerror_message(err));
        g_clear_error(&err);
    }

    g_object_unref(paste);	/* -- unref paste instance */
}

/**
 * swamigui_get_clipboard_items:
 *
 * Get the current item clipboard list used for copy/paste operations.
 *
 * Returns: Current clipboard item list or NULL.  Caller should not modify the list
 *   but owns a reference and should unref the list object when finished using it.
 */
IpatchList *
swamigui_get_clipboard_items(void)
{
    if(!item_clipboard)
    {
        return (NULL);
    }

    return (g_object_ref(item_clipboard));
}

