/* title-add.c
 *
 * Copyright (C) 2008-2026 John Stebbins <stebbins@stebbins>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * 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, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

#include "title-add.h"

#include "application.h"
#include "audiohandler.h"
#include "callbacks.h"
#include "ghb-file-button.h"
#include "handbrake/handbrake.h"
#include "hb-dvd.h"
#include "jobdict.h"
#include "presets.h"
#include "queuehandler.h"
#include "titledict.h"
#include "values.h"

G_MODULE_EXPORT void
title_selected_cb (GtkWidget *widget, signal_user_data_t *ud);
G_MODULE_EXPORT void
title_dest_file_cb (GtkWidget *widget, signal_user_data_t *ud);
G_MODULE_EXPORT void
title_dest_dir_cb (GtkWidget *widget, GParamSpec *spec, signal_user_data_t *ud);


static GtkWidget *
find_widget (GtkWidget *widget, const char *name)
{
    const char *wname;
    GtkWidget *result = NULL;

    if (widget == NULL || name == NULL)
        return NULL;

    wname = gtk_widget_get_name(widget);
    if (wname != NULL && !strncmp(wname, name, 80))
    {
        return widget;
    }
    GtkWidget *child = gtk_widget_get_first_child(widget);
    while (child != NULL)
    {
        result = find_widget(child, name);
        if (result != NULL) break;
        child = gtk_widget_get_next_sibling(child);
    }
    return result;
}

static gboolean
validate_settings (signal_user_data_t *ud, GhbValue *settings, gint batch)
{
    // Check to see if the dest file exists or is
    // already in the queue
    const gchar *dest;
    gint count, ii;
    gint title_id, titleindex;
    const hb_title_t *title;
    GtkWindow *hb_window;
    gboolean dest_in_queue = FALSE;
    gboolean dest_exists = FALSE;

    hb_window = GTK_WINDOW(ghb_builder_widget("hb_window"));

    title_id = ghb_dict_get_int(settings, "title");
    title = ghb_lookup_title(title_id, &titleindex);
    if (title == NULL) return FALSE;
    dest = ghb_dict_get_string(settings, "destination");
    count = ghb_array_len(ud->queue);
    for (ii = 0; ii < count; ii++)
    {
        GhbValue *queueDict, *uiDict;
        const gchar *filename;

        queueDict = ghb_array_get(ud->queue, ii);
        uiDict = ghb_dict_get(queueDict, "uiSettings");
        filename = ghb_dict_get_string(uiDict, "destination");
        if (g_strcmp0(dest, filename) == 0)
        {
            dest_in_queue = TRUE;
            break;
        }
    }
    gchar *destdir = g_path_get_dirname(dest);
    if (!g_file_test(destdir, G_FILE_TEST_IS_DIR))
    {
        ghb_alert_dialog_show(GTK_MESSAGE_ERROR, _("Invalid Destination"),
                              _("“%s” is is not a valid directory."), destdir);
        g_free(destdir);
        return FALSE;
    }
#if !defined(_WIN32)
    // This doesn't work properly on windows
    if (g_access(destdir, R_OK|W_OK) != 0)
    {
        ghb_alert_dialog_show(GTK_MESSAGE_ERROR, _("Invalid Destination"),
                              _("“%s” is not a writable directory."), destdir);
        g_free(destdir);
        return FALSE;
    }
#endif
    g_free(destdir);
    dest_exists = g_file_test(dest, G_FILE_TEST_EXISTS);

    if (dest_exists)
    {
        if (!ghb_question_dialog_run(hb_window, GHB_ACTION_DESTRUCTIVE,
            _("Overwrite"), _("Cancel"), _("Overwrite File?"),
            _("The file “%s” already exists.\n"
              "Do you want to overwrite it?"), dest))
        {
            return FALSE;
        }
    }
    else if (dest_in_queue)
    {
        if (!ghb_question_dialog_run(hb_window, GHB_ACTION_NORMAL,
            _("Overwrite"), _("Cancel"), _("Overwrite File?"),
            _("Destination: %s\n\n"
              "Another queued job has specified the same destination.\n"
              "Do you want to overwrite?"), dest))
        {
            return FALSE;
        }
    }
    // Validate audio settings
    if (!ghb_validate_audio(settings, hb_window))
    {
        return FALSE;
    }
    // Validate audio settings
    if (!ghb_validate_subtitles(settings, hb_window))
    {
        return FALSE;
    }
    // Validate video settings
    if (!ghb_validate_video(settings, hb_window))
    {
        return FALSE;
    }
    // Validate filter settings
    if (!ghb_validate_filters(settings, hb_window))
    {
        return FALSE;
    }
    return TRUE;
}

void ghb_finalize_job (GhbValue *settings)
{
    GhbValue *preset, *job;

    preset = ghb_settings_to_preset(settings);
    job    = ghb_dict_get(settings, "Job");

    // Add scale filter since the above does not
    GhbValue *filter_settings, *filter_list, *filter_dict;
    gint width, height, crop[4];

    filter_settings = ghb_get_job_filter_settings(settings);
    filter_list = ghb_array_new();
    ghb_dict_set(filter_settings, "FilterList", filter_list);

    width = ghb_dict_get_int(settings, "scale_width");
    height = ghb_dict_get_int(settings, "scale_height");

    crop[0] = ghb_dict_get_int(settings, "PictureTopCrop");
    crop[1] = ghb_dict_get_int(settings, "PictureBottomCrop");
    crop[2] = ghb_dict_get_int(settings, "PictureLeftCrop");
    crop[3] = ghb_dict_get_int(settings, "PictureRightCrop");

    hb_dict_t * dict = ghb_dict_new();
    ghb_dict_set_int(dict, "width", width);
    ghb_dict_set_int(dict, "height", height);
    ghb_dict_set_int(dict, "crop-top", crop[0]);
    ghb_dict_set_int(dict, "crop-bottom", crop[1]);
    ghb_dict_set_int(dict, "crop-left", crop[2]);
    ghb_dict_set_int(dict, "crop-right", crop[3]);

    filter_dict = ghb_dict_new();
    ghb_dict_set_int(filter_dict, "ID", HB_FILTER_CROP_SCALE);
    ghb_dict_set(filter_dict, "Settings", dict);
    hb_add_filter2(filter_list, filter_dict);

    // Apply selected preset settings
    hb_preset_apply_mux(preset, job);
    hb_preset_apply_video(preset, job);
    hb_preset_apply_filters(preset, job);

    ghb_value_free(&preset);
}

gboolean
ghb_add_title_to_queue (signal_user_data_t *ud, GhbValue *settings, gint batch)
{
    // Add settings to the queue
    if (!validate_settings(ud, settings, batch))
    {
        return FALSE;
    }

    if (ud->queue == NULL)
        ud->queue = ghb_array_new();

    ghb_finalize_job(settings);

    GhbValue *titleDict  = ghb_get_title_settings(settings);
    GhbValue *jobDict    = ghb_get_job_settings(settings);
    GhbValue *destDict   = ghb_get_job_dest_settings(settings);
    GhbValue *uiDict     = ghb_value_dup(settings);

    if (ghb_dict_get_string(destDict, "File") == NULL)
    {
        const char *dest = ghb_dict_get_string(uiDict, "destination");
        ghb_dict_set_string(destDict, "File", dest);
    }

    ghb_dict_remove(uiDict, "Job");
    ghb_dict_remove(uiDict, "Title");

    GhbValue *queueDict  = ghb_dict_new();
    ghb_dict_set(queueDict, "uiSettings", uiDict);
    ghb_dict_set(queueDict, "Job", ghb_value_dup(jobDict));
    ghb_dict_set(queueDict, "Title", ghb_value_dup(titleDict));

    // Copy current prefs into settings
    // The job should run with the preferences that existed
    // when the job was added to the queue.
    ghb_dict_set(uiDict, "Preferences", ghb_value_dup(ud->prefs));

    // Make a copy of current settings to be used for the new job
    ghb_dict_set_int(uiDict, "job_status", GHB_QUEUE_PENDING);
    ghb_dict_set_int(uiDict, "job_unique_id", 0);

    ghb_array_append(ud->queue, queueDict);
    ghb_add_to_queue_list(ud, queueDict);
    ghb_save_queue(ud->queue);
    ghb_update_pending(ud);
    ghb_queue_buttons_grey(ud);

    return TRUE;
}

static gboolean
title_destination_is_unique (GhbValue *settings_array, gint index)
{
    gint count, ii;
    GhbValue *settings, *gdest;
    const char *dest;

    settings = ghb_array_get(settings_array, index);
    gdest = ghb_dict_get_value(settings, "destination");
    dest = ghb_value_get_string(gdest);
    if (dest == NULL)
        return FALSE;

    count = ghb_array_len(settings_array);
    count = count < index ? count : index;
    for (ii = 0; ii < count; ii++)
    {
        const char *tmp;

        settings = ghb_array_get(settings_array, ii);
        gdest = ghb_dict_get_value(settings, "destination");
        tmp = ghb_value_get_string(gdest);
        if (tmp != NULL && !strncmp(dest, tmp, PATH_MAX))
            return FALSE;
    }
    return TRUE;
}

static PangoAttrList *default_title_attrs;

static void
title_add_set_sensitive (GtkWidget *row, gboolean sensitive)
{
    PangoAttrList *pal;
    PangoAttribute *bg, *alpha;
    GtkWidget *widget;
    widget = find_widget(row, "title_selected");
    gtk_widget_set_sensitive(widget, sensitive);

    widget = find_widget(row, "title_label");

    if (!sensitive)
    {
        alpha = pango_attr_background_alpha_new(0);
        gtk_widget_set_has_tooltip(widget, TRUE);
    }
    else
    {
        alpha = pango_attr_background_alpha_new(1);
        gtk_label_set_attributes(GTK_LABEL(widget), default_title_attrs);
        gtk_widget_set_has_tooltip(widget, FALSE);
    }

    pal = pango_attr_list_new();
    bg = pango_attr_background_new(0xFFFF, 0xFFFF, 0xA000);
    pango_attr_list_insert(pal, bg);
    pango_attr_list_insert(pal, alpha);

    gtk_label_set_attributes(GTK_LABEL(widget), pal);
}

static gboolean
title_add_are_conflicts (signal_user_data_t *ud)
{
    GtkListBox *list;
    GtkWidget *row;
    gint count, ii;

    list = GTK_LIST_BOX(ghb_builder_widget("title_add_multiple_list"));
    count = ghb_array_len(ud->settings_array);
    for (ii = 0; ii < count; ii++)
    {
        row = GTK_WIDGET(gtk_list_box_get_row_at_index(list, ii));
        if (!title_destination_is_unique(ud->settings_array, ii))
        {
            title_add_set_sensitive(GTK_WIDGET(row), FALSE);
            return TRUE;
        }
        title_add_set_sensitive(GTK_WIDGET(row), TRUE);
    }
    return FALSE;
}

static void
title_add_set_error_text (signal_user_data_t *ud, gboolean are_conflicts)
{
    const gchar *msg;
    static gboolean conflict_showing = FALSE;
    GtkMessageType msg_type;

    if (are_conflicts)
    {
        msg = _("Duplicate destination files detected. Duplicates will not be added to the queue.");
            msg_type = GTK_MESSAGE_WARNING;
    }
    else
    {
        msg = _("Destination files OK.  No duplicates detected.");
            msg_type = GTK_MESSAGE_INFO;
    }
    if (are_conflicts ^ conflict_showing)
    {
        GtkInfoBar *info = GTK_INFO_BAR(ghb_builder_widget("title_add_multiple_infobar"));
        GtkLabel *label = GTK_LABEL(ghb_builder_widget("title_add_multiple_label1"));
        gtk_label_set_markup(label, msg);
        gtk_info_bar_set_message_type(info, msg_type);
        conflict_showing = are_conflicts;
    }
}

static void title_add_check_conflicts (signal_user_data_t *ud)
{
    gint count, ii;
    GhbValue *settings;
    GtkWidget *row;
    GtkListBox *list;
    GtkWidget *selected;
    gboolean can_select;
    gboolean are_conflicts = FALSE;

    list = GTK_LIST_BOX(ghb_builder_widget("title_add_multiple_list"));
    count = ghb_array_len(ud->settings_array);
    for (ii = 0; ii < count; ii++)
    {
        row = GTK_WIDGET(gtk_list_box_get_row_at_index(list, ii));
        selected = find_widget(row, "title_selected");

        settings = ghb_array_get(ud->settings_array, ii);
        can_select = title_destination_is_unique(ud->settings_array, ii);
        ghb_dict_set_bool(settings, "title_selected", FALSE);
        gtk_check_button_set_active(GTK_CHECK_BUTTON(selected), FALSE);
        title_add_set_sensitive(GTK_WIDGET(row), can_select);
        are_conflicts |= !can_select;
    }
    title_add_set_error_text(ud, are_conflicts);
}

static void
add_multiple_titles (signal_user_data_t *ud)
{
    gint count, ii;

    count = ghb_array_len(ud->settings_array);
    for (ii = 0; ii < count; ii++)
    {
        GhbValue *settings;

        settings = ghb_array_get(ud->settings_array, ii);
        if (ghb_dict_get_bool(settings, "title_selected") &&
            !ghb_add_title_to_queue(ud, settings, 1))
        {
            ghb_log("Validation failed. Could not add all titles to queue.");
            break;
        }
    }
    ghb_queue_selection_init(ud);
}

static GtkListBoxRow *list_box_get_row (GtkWidget *widget)
{
    while (widget != NULL && G_OBJECT_TYPE(widget) != GTK_TYPE_LIST_BOX_ROW)
    {
        widget = gtk_widget_get_parent(widget);
    }
    return GTK_LIST_BOX_ROW(widget);
}

static GtkWidget *title_create_row (signal_user_data_t *ud)
{
    GtkBox *hbox, *vbox_dest;
    GtkCheckButton *selected;
    GtkLabel *title;
    GtkWidget *dest_file;
    GhbFileButton *dest_dir;

    hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
    gtk_box_set_spacing(hbox, 6);
    gtk_widget_set_margin_start(GTK_WIDGET(hbox), 6);
    gtk_widget_set_margin_end(GTK_WIDGET(hbox), 6);
    gtk_widget_set_visible(GTK_WIDGET(hbox), TRUE);

    // Select checkbox
    selected = GTK_CHECK_BUTTON(gtk_check_button_new());
    gtk_widget_add_css_class(GTK_WIDGET(selected), "selection-mode");
    gtk_widget_set_tooltip_markup(GTK_WIDGET(selected),
      _("Select this title for adding to the queue.\n"));
    gtk_widget_set_valign(GTK_WIDGET(selected), GTK_ALIGN_CENTER);
    gtk_widget_set_name(GTK_WIDGET(selected), "title_selected");
    gtk_widget_set_visible(GTK_WIDGET(selected), TRUE);
    g_signal_connect(selected, "toggled", (GCallback)title_selected_cb, ud);
    gtk_box_append(hbox, GTK_WIDGET(selected));

    // Title label
    title = GTK_LABEL(gtk_label_new(_("No Title")));
    gtk_label_set_width_chars(title, 12);
    gtk_widget_set_halign(GTK_WIDGET(title), GTK_ALIGN_START);
    gtk_widget_set_valign(GTK_WIDGET(title), GTK_ALIGN_CENTER);
    gtk_widget_set_name(GTK_WIDGET(title), "title_label");
    gtk_widget_set_visible(GTK_WIDGET(title), TRUE);
    gtk_box_append(hbox, GTK_WIDGET(title));

    default_title_attrs = gtk_label_get_attributes(title);
    gtk_widget_set_tooltip_text(GTK_WIDGET(title),
        _("There is another title with the same destination file name.\n"
        "This title will not be added to the queue unless you change\n"
        "the output file name.\n"));
    gtk_widget_set_has_tooltip(GTK_WIDGET(title), FALSE);

    // Destination entry and file button
    vbox_dest = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
    gtk_widget_set_hexpand(GTK_WIDGET(vbox_dest), TRUE);
    gtk_widget_set_halign(GTK_WIDGET(vbox_dest), GTK_ALIGN_FILL);
    //gtk_widget_set_hexpand(GTK_WIDGET(vbox_dest), TRUE);
    dest_file = gtk_entry_new();
    gtk_editable_set_width_chars(GTK_EDITABLE(dest_file), 40);
    gtk_widget_set_name(dest_file, "title_file");
    //gtk_widget_set_hexpand(dest_file, TRUE);
    gtk_widget_set_visible(dest_file, TRUE);
    g_signal_connect(dest_file, "changed", (GCallback)title_dest_file_cb, ud);
    gtk_box_append(vbox_dest, dest_file);
    dest_dir = ghb_file_button_new(_("Destination Directory"), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
    g_signal_connect(dest_dir, "notify::file",
                     G_CALLBACK(title_dest_dir_cb), ud);
    gtk_widget_set_name(GTK_WIDGET(dest_dir), "title_dir");
    gtk_widget_set_hexpand(GTK_WIDGET(dest_dir), TRUE);
    gtk_widget_set_visible(GTK_WIDGET(dest_dir), TRUE);
    gtk_box_append(vbox_dest, GTK_WIDGET(dest_dir));
    gtk_widget_set_visible(GTK_WIDGET(vbox_dest), TRUE);
    gtk_box_append(hbox, GTK_WIDGET(vbox_dest));

    return GTK_WIDGET(hbox);
}

static gboolean clear_select_all_busy = FALSE;

G_MODULE_EXPORT void
title_add_select_all_cb (GSimpleAction *action, GVariant *param, gpointer data)
{
    gint count, ii;
    GhbValue *settings;
    GtkWidget *row;
    GtkListBox *list;
    gboolean can_select;
    GtkWidget *selected;
    signal_user_data_t *ud = (signal_user_data_t *)data;

    clear_select_all_busy = TRUE;

    list = GTK_LIST_BOX(ghb_builder_widget("title_add_multiple_list"));
    count = ghb_array_len(ud->settings_array);
    for (ii = 0; ii < count; ii++)
    {
        row = GTK_WIDGET(gtk_list_box_get_row_at_index(list, ii));
        selected = find_widget(row, "title_selected");
        settings = ghb_array_get(ud->settings_array, ii);
        can_select = title_destination_is_unique(ud->settings_array, ii);
        ghb_dict_set_bool(settings, "title_selected", can_select);
        gtk_check_button_set_active(GTK_CHECK_BUTTON(selected), TRUE);
        title_add_set_sensitive(GTK_WIDGET(row), can_select);
    }
    clear_select_all_busy = FALSE;
}

G_MODULE_EXPORT void
title_add_clear_all_cb (GSimpleAction *action, GVariant *param, gpointer data)
{
    gint count, ii;
    GhbValue *settings;
    GtkWidget *row;
    GtkListBox *list;
    GtkWidget *selected;
    signal_user_data_t *ud = (signal_user_data_t *)data;

    clear_select_all_busy = TRUE;

    list = GTK_LIST_BOX(ghb_builder_widget("title_add_multiple_list"));
    count = ghb_array_len(ud->settings_array);
    for (ii = 0; ii < count; ii++)
    {
        row = GTK_WIDGET(gtk_list_box_get_row_at_index(list, ii));
        selected = find_widget(row, "title_selected");
        settings = ghb_array_get(ud->settings_array, ii);
        ghb_dict_set_bool(settings, "title_selected", FALSE);
        gtk_check_button_set_active(GTK_CHECK_BUTTON(selected), FALSE);
    }
    clear_select_all_busy = FALSE;
}

G_MODULE_EXPORT void
title_add_invert_cb (GSimpleAction *action, GVariant *param, gpointer data)
{
    gint count, ii;
    GhbValue *settings;
    GtkWidget *row;
    GtkListBox *list;
    GtkWidget *selected;
    signal_user_data_t *ud = (signal_user_data_t *)data;

    clear_select_all_busy = TRUE;

    list = GTK_LIST_BOX(ghb_builder_widget("title_add_multiple_list"));
    count = ghb_array_len(ud->settings_array);
    for (ii = 0; ii < count; ii++)
    {
        row = GTK_WIDGET(gtk_list_box_get_row_at_index(list, ii));
        selected = find_widget(row, "title_selected");
        settings = ghb_array_get(ud->settings_array, ii);
        gboolean is_selected = ghb_dict_get_bool(settings, "title_selected");
        ghb_dict_set_bool(settings, "title_selected", !is_selected);
        gtk_check_button_set_active(GTK_CHECK_BUTTON(selected), !is_selected);
    }
    clear_select_all_busy = FALSE;
}

G_MODULE_EXPORT void
title_selected_cb (GtkWidget *widget, signal_user_data_t *ud)
{
    GhbValue *settings;
    gboolean selected;
    gboolean can_select;

    if (clear_select_all_busy)
        return;

    GtkListBoxRow * row = list_box_get_row(widget);
    if (row == NULL)
        return;

    gint index = gtk_list_box_row_get_index(row);
    selected = ghb_widget_boolean(widget);
    settings = ghb_array_get(ud->settings_array, index);
    can_select = title_destination_is_unique(ud->settings_array, index);
    ghb_dict_set_bool(settings, "title_selected",
                             selected && can_select);
}

G_MODULE_EXPORT void
title_dest_file_cb (GtkWidget *widget, signal_user_data_t *ud)
{
    GhbValue *settings;
    const gchar *dest_dir;
    gchar *dest_file, *dest;
    GtkListBoxRow * row = list_box_get_row(widget);
    if (row == NULL)
        return;
    gint index = gtk_list_box_row_get_index(row);

    dest_file = ghb_widget_string(widget);
    settings = ghb_array_get(ud->settings_array, index);

    ghb_dict_set_string(settings, "dest_file", dest_file);
    dest_dir = ghb_dict_get_string(settings, "dest_dir");
    dest = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", dest_dir, dest_file);
    ghb_dict_set_string(settings, "destination", dest);
    GhbValue *dest_dict = ghb_get_job_dest_settings(settings);
    ghb_dict_set_string(dest_dict, "File", dest);

    // Check if changing the destination file name resolves
    // a file name conflict.  Enable selection if so.
    // Disable selection if it creates a conflict!!!
    gboolean selected, can_select;

    widget     = find_widget(GTK_WIDGET(row), "title_selected");
    selected   = ghb_widget_boolean(widget);
    can_select = title_destination_is_unique(ud->settings_array, index);

    ghb_dict_set_bool(settings, "title_selected", selected && can_select);
    title_add_set_sensitive(GTK_WIDGET(row), can_select);

    g_free(dest_file);
    g_free(dest);

    title_add_set_error_text(ud,
        title_add_are_conflicts(ud));
}

G_MODULE_EXPORT void
title_dest_dir_cb (GtkWidget *widget, GParamSpec *spec, signal_user_data_t *ud)
{
    GhbValue *settings;
    const gchar *dest_file;
    gchar *dest_dir, *dest;
    GtkListBoxRow * row = list_box_get_row(widget);
    if (row == NULL)
        return;
    gint index = gtk_list_box_row_get_index(row);

    dest_dir = ghb_widget_string(widget);
    settings = ghb_array_get(ud->settings_array, index);

    ghb_dict_set_string(settings, "dest_dir", dest_dir);
    dest_file = ghb_dict_get_string(settings, "dest_file");
    dest = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", dest_dir, dest_file);
    ghb_dict_set_string(settings, "destination", dest);
    GhbValue *dest_dict = ghb_get_job_dest_settings(settings);
    ghb_dict_set_string(dest_dict, "File", dest);

    // Check if changing the destination file name resolves
    // a file name conflict.  Enable selection if so.
    // Disable selection if it creates a conflict!!!
    gboolean selected, can_select;

    widget     = find_widget(GTK_WIDGET(row), "title_selected");
    selected   = ghb_widget_boolean(widget);
    can_select = title_destination_is_unique(ud->settings_array, index);

    ghb_dict_set_bool(settings, "title_selected", selected && can_select);
    title_add_set_sensitive(GTK_WIDGET(row), can_select);

    g_free(dest_dir);
    g_free(dest);

    title_add_set_error_text(ud,
        title_add_are_conflicts(ud));
}

G_MODULE_EXPORT void
title_add_action_cb (GSimpleAction *action, GVariant *param,
                     signal_user_data_t *ud)
{
    ghb_add_title_to_queue(ud, ud->settings, 0);
    ghb_queue_selection_init(ud);
    // Validation of settings may have changed audio list
    ghb_audio_list_refresh_all(ud);
}

static void
title_add_multiple_response_cb (GtkDialog *dialog, int response,
                                signal_user_data_t *ud)
{
    g_signal_handlers_disconnect_by_data(dialog, ud);
    gtk_widget_set_visible(GTK_WIDGET(dialog), FALSE);
    if (response == GTK_RESPONSE_OK)
    {
        add_multiple_titles(ud);
    }
}

G_MODULE_EXPORT void
title_add_multiple_action_cb (GSimpleAction *action, GVariant *param,
                              signal_user_data_t *ud)
{
    GtkListBox *list;
    GtkWidget *row;
    gint count, ii;
    gint max_title_len = 0;
    GhbValue * preset = NULL;

    list = GTK_LIST_BOX(ghb_builder_widget("title_add_multiple_list"));
    // Clear title list
    ghb_list_box_remove_all(list);

    if (ghb_dict_get_bool(ud->prefs, "SyncTitleSettings"))
    {
        preset = ghb_settings_to_preset(ud->settings);
    }

    // Set up the list of titles
    count = ghb_array_len(ud->settings_array);
    for (ii = 0; ii < count; ii++)
    {
        GhbValue *settings;
        GtkLabel *label;
        GtkEditable *entry;
        GhbFileButton *button;
        gchar *title_label;
        const gchar *dest_dir, *dest_file;
        gint title_id, titleindex;
        const hb_title_t *title;

        row = title_create_row(ud);
        label = GTK_LABEL(find_widget(row, "title_label"));
        entry = GTK_EDITABLE(find_widget(row, "title_file"));
        button = GHB_FILE_BUTTON(find_widget(row, "title_dir"));

        settings = ghb_array_get(ud->settings_array, ii);
        if (preset != NULL)
        {
            ghb_preset_to_settings(settings, preset);
            ghb_set_title_settings(ud, settings);
        }
        title_id = ghb_dict_get_int(settings, "title");
        title = ghb_lookup_title(title_id, &titleindex);
        if (title != NULL)
        {
            gint len;

            title_label = ghb_create_title_label(title);
            len = strnlen(title_label, PATH_MAX);
            if (len > max_title_len)
                max_title_len = len;

            dest_file = ghb_dict_get_string(settings, "dest_file");
            dest_dir = ghb_dict_get_string(settings, "dest_dir");

            gtk_label_set_markup(label, title_label);
            gtk_editable_set_text(entry, dest_file);
            ghb_file_button_set_filename(button, dest_dir);

            g_free(title_label);
        }

        gtk_list_box_insert(list, row, -1);
    }

    if (preset != NULL)
    {
        ghb_value_free(&preset);
    }

    // Now we need to set the width of the title label since it
    // can vary on each row
    if (max_title_len > 60)
        max_title_len = 60;
    for (ii = 0; ii < count; ii++)
    {
        GtkLabel *label;

        row = GTK_WIDGET(gtk_list_box_get_row_at_index(list, ii));
        label = GTK_LABEL(find_widget(row, "title_label"));
        gtk_label_set_max_width_chars(label, max_title_len);
        gtk_label_set_width_chars(label, max_title_len);
        gtk_label_set_ellipsize(label, PANGO_ELLIPSIZE_END);
    }

    // Disable selection of files with duplicate file names.
    title_add_check_conflicts(ud);

    // Pop up the title multiple selections dialog
    GtkWidget *dialog = ghb_builder_widget("title_add_multiple_dialog");
    g_signal_connect(dialog, "response",
                     G_CALLBACK(title_add_multiple_response_cb), ud);
    gtk_widget_set_visible(dialog, TRUE);
}

G_MODULE_EXPORT void
title_add_all_action_cb (GSimpleAction *action, GVariant *param,
                         signal_user_data_t *ud)
{
    gint count, ii;
    GhbValue * preset = NULL;

    if (ghb_dict_get_bool(ud->prefs, "SyncTitleSettings"))
    {
        preset = ghb_settings_to_preset(ud->settings);
    }

    // Set up the list of titles
    count = ghb_array_len(ud->settings_array);
    for (ii = 0; ii < count; ii++)
    {
        GhbValue *settings;

        settings = ghb_array_get(ud->settings_array, ii);
        if (preset != NULL)
        {
            ghb_preset_to_settings(settings, preset);
            ghb_set_title_settings(ud, settings);
        }
        ghb_dict_set_bool(settings, "title_selected", TRUE);
    }

    if (preset != NULL)
    {
        ghb_value_free(&preset);
    }

    for (ii = 0; ii < count; ii++)
    {
        if (!title_destination_is_unique(ud->settings_array, ii))
        {
            ghb_question_dialog_run(GTK_WINDOW(ghb_builder_widget("hb_window")),
                GHB_ACTION_NORMAL, _("OK"), NULL, _("Cannot Add Titles"),
                _("The filenames are not unique. Please choose\n"
                  "a unique destination filename for each title."));
            title_add_multiple_action_cb(action, param, ud);
            return;
        }
    }
    add_multiple_titles(ud);
}
