/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ /* * queuehandler.c * Copyright (C) John Stebbins 2008-2021 * * queuehandler.c is free software. * * You may redistribute it and/or modify it under the terms of the * GNU General Public License version 2, as published by the Free Software * Foundation. * * queuehandler.c 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 main.c. If not, write to: * The Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301, USA. */ #include "ghbcompat.h" #include #include #include #include "handbrake/handbrake.h" #include "settings.h" #include "jobdict.h" #include "titledict.h" #include "hb-backend.h" #include "values.h" #include "callbacks.h" #include "presets.h" #include "audiohandler.h" #include "subtitlehandler.h" #include "ghb-dvd.h" #include "plist.h" #include "queuehandler.h" void ghb_queue_buttons_grey(signal_user_data_t *ud); // Callbacks G_MODULE_EXPORT void queue_remove_clicked_cb(GtkWidget *widget, signal_user_data_t *ud); #if GTK_CHECK_VERSION(3, 90, 0) G_MODULE_EXPORT void queue_drag_begin_cb(GtkWidget * widget, GdkDrag * context, signal_user_data_t * ud); G_MODULE_EXPORT void queue_drag_end_cb(GtkWidget * widget, GdkDrag * context, signal_user_data_t * ud); G_MODULE_EXPORT void queue_drag_data_get_cb(GtkWidget * widget, GdkDrag * context, GtkSelectionData * selection_data, signal_user_data_t * ud); G_MODULE_EXPORT gboolean queue_row_key_cb(GtkEventControllerKey * keycon, guint keyval, guint keycode, GdkModifierType state, signal_user_data_t * ud); #else G_MODULE_EXPORT void queue_drag_begin_cb(GtkWidget * widget, GdkDragContext * context, signal_user_data_t * ud); G_MODULE_EXPORT void queue_drag_end_cb(GtkWidget * widget, GdkDragContext * context, signal_user_data_t * ud); G_MODULE_EXPORT void queue_drag_data_get_cb(GtkWidget * widget, GdkDragContext * context, GtkSelectionData * selection_data, guint info, guint time, signal_user_data_t * ud); #endif 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, signal_user_data_t *ud); #if GTK_CHECK_VERSION(3, 90, 0) static const char * queue_drag_entries[] = { "application/queue-list-row-drop" }; void ghb_queue_drag_n_drop_init(signal_user_data_t * ud) { GtkWidget * widget; GdkContentFormats * targets; widget = GHB_WIDGET(ud->builder, "queue_list"); targets = gdk_content_formats_new(queue_drag_entries, G_N_ELEMENTS(queue_drag_entries)); gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_MOTION|GTK_DEST_DEFAULT_DROP, targets, GDK_ACTION_MOVE); gdk_content_formats_unref(targets); } #else static GtkTargetEntry queue_drag_entries[] = { { "GTK_LIST_BOX_ROW", GTK_TARGET_SAME_APP, 0 } }; void ghb_queue_drag_n_drop_init(signal_user_data_t * ud) { GtkWidget * widget; widget = GHB_WIDGET(ud->builder, "queue_list"); gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_MOTION|GTK_DEST_DEFAULT_DROP, queue_drag_entries, 1, GDK_ACTION_MOVE); } #endif static GtkWidget *find_widget(GtkWidget *widget, gchar *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; } if (GTK_IS_CONTAINER(widget)) { GList *list, *link; link = list = gtk_container_get_children(GTK_CONTAINER(widget)); while (link) { result = find_widget(GTK_WIDGET(link->data), name); if (result != NULL) break; link = link->next; } g_list_free(list); } return result; } static void queue_update_summary(GhbValue * queueDict, signal_user_data_t *ud) { GString * str; char * text; const char * ctext; const char * sep; GtkWidget * widget; GhbValue * uiDict = NULL; GhbValue * jobDict = NULL; GhbValue * sourceDict = NULL; GhbValue * rangeDict = NULL; GhbValue * titleDict = NULL; if (queueDict != NULL) { uiDict = ghb_dict_get(queueDict, "uiSettings"); jobDict = ghb_dict_get(queueDict, "Job"); titleDict = ghb_dict_get(queueDict, "Title"); } if (titleDict == NULL) { // No title, clear summary widget = GHB_WIDGET(ud->builder, "queue_summary_preset"); gtk_label_set_text(GTK_LABEL(widget), ""); widget = GHB_WIDGET(ud->builder, "queue_summary_source"); gtk_label_set_text(GTK_LABEL(widget), ""); widget = GHB_WIDGET(ud->builder, "queue_summary_dest"); gtk_label_set_text(GTK_LABEL(widget), ""); widget = GHB_WIDGET(ud->builder, "queue_summary_dimensions"); gtk_label_set_text(GTK_LABEL(widget), ""); widget = GHB_WIDGET(ud->builder, "queue_summary_video"); gtk_label_set_text(GTK_LABEL(widget), ""); widget = GHB_WIDGET(ud->builder, "queue_summary_audio"); gtk_label_set_text(GTK_LABEL(widget), ""); widget = GHB_WIDGET(ud->builder, "queue_summary_subtitle"); gtk_label_set_text(GTK_LABEL(widget), ""); return; } // Preset: PresetName const char * name; gboolean preset_modified; preset_modified = ghb_dict_get_bool(uiDict, "preset_modified"); name = ghb_dict_get_string(uiDict, "PresetFullName"); str = g_string_new(""); if (preset_modified) { g_string_append_printf(str, _("%s (Modified)"), name); } else { g_string_append_printf(str, "%s", name); } widget = GHB_WIDGET(ud->builder, "queue_summary_preset"); text = g_string_free(str, FALSE); gtk_label_set_text(GTK_LABEL(widget), text); g_free(text); // Source sourceDict = ghb_dict_get(jobDict, "Source"); ctext = ghb_dict_get_string(sourceDict, "Path"); widget = GHB_WIDGET(ud->builder, "queue_summary_source"); gtk_label_set_text(GTK_LABEL(widget), ctext); // Title const char * rangeType; int titleID; int64_t rangeStart, rangeEnd; titleID = ghb_dict_get_int(sourceDict, "Title"); rangeDict = ghb_dict_get(sourceDict, "Range"); rangeType = ghb_dict_get_string(rangeDict, "Type"); rangeStart = ghb_dict_get_int(rangeDict, "Start"); rangeEnd = ghb_dict_get_int(rangeDict, "End"); str = g_string_new(""); g_string_append_printf(str, "%-8d", titleID); if (!strcmp(rangeType, "chapter")) { g_string_append_printf(str, "Chapters: %ld to %ld", rangeStart, rangeEnd); } else if (!strcmp(rangeType, "time")) { int start_hh, start_mm; double start_ss; int end_hh, end_mm; double end_ss; ghb_break_pts_duration(rangeStart, &start_hh, &start_mm, &start_ss); ghb_break_pts_duration(rangeEnd, &end_hh, &end_mm, &end_ss); g_string_append_printf(str, "Time: %02d:%02d:%05.2f to %02d:%02d:%05.2f", start_hh, start_mm, start_ss, end_hh, end_mm, end_ss); } else if (!strcmp(rangeType, "frame")) { g_string_append_printf(str, "Frames: %ld to %ld", rangeStart, rangeEnd); } text = g_string_free(str, FALSE); widget = GHB_WIDGET(ud->builder, "queue_summary_title"); gtk_label_set_text(GTK_LABEL(widget), text); g_free(text); // Destination ctext = ghb_dict_get_string(uiDict, "destination"); str = g_string_new(ctext); const char * mux_name; const hb_container_t * container; mux_name = ghb_dict_get_string(uiDict, "FileFormat"); container = ghb_lookup_container_by_name(mux_name); g_string_append_printf(str, "\n%s", container->name); gboolean markers, av_align, ipod = FALSE, http = FALSE; markers = ghb_dict_get_bool(uiDict, "ChapterMarkers"); av_align = ghb_dict_get_bool(uiDict, "AlignAVStart"); if (container->format & HB_MUX_MASK_MP4) { ipod = ghb_dict_get_bool(uiDict, "Mp4iPodCompatible"); http = ghb_dict_get_bool(uiDict, "Mp4HttpOptimize"); } sep = "\n"; if (markers) { g_string_append_printf(str, "%s%s", sep, _("Chapter Markers")); sep = ", "; } if (av_align) { g_string_append_printf(str, "%s%s", sep, _("Align A/V")); sep = ", "; } if (http) { g_string_append_printf(str, "%s%s", sep, _("Web Optimized")); sep = ", "; } if (ipod) { g_string_append_printf(str, "%s%s", sep, _("iPod 5G")); sep = ", "; } text = g_string_free(str, FALSE); widget = GHB_WIDGET(ud->builder, "queue_summary_dest"); gtk_label_set_text(GTK_LABEL(widget), text); g_free(text); // Dimenstions double display_width; int width, height, display_height, par_width, par_height; int crop[4]; char * display_aspect; width = ghb_dict_get_int(uiDict, "scale_width"); height = ghb_dict_get_int(uiDict, "scale_height"); display_width = ghb_dict_get_int(uiDict, "PictureDisplayWidth"); display_height = ghb_dict_get_int(uiDict, "PictureDisplayHeight"); par_width = ghb_dict_get_int(uiDict, "PicturePARWidth"); par_height = ghb_dict_get_int(uiDict, "PicturePARHeight"); crop[0] = ghb_dict_get_int(uiDict, "PictureTopCrop"); crop[1] = ghb_dict_get_int(uiDict, "PictureBottomCrop"); crop[2] = ghb_dict_get_int(uiDict, "PictureLeftCrop"); crop[3] = ghb_dict_get_int(uiDict, "PictureRightCrop"); display_width = (double)width * par_width / par_height; display_aspect = ghb_get_display_aspect_string(display_width, display_height); display_width = ghb_dict_get_int(uiDict, "PictureDisplayWidth"); text = g_strdup_printf(_("%d:%d:%d:%d Crop\n" "%dx%d storage, %dx%d display\n" "%d:%d Pixel Aspect Ratio\n" "%s Display Aspect Ratio"), crop[0], crop[1], crop[2], crop[3], width, height, (int)display_width, display_height, par_width, par_height, display_aspect); widget = GHB_WIDGET(ud->builder, "queue_summary_dimensions"); gtk_label_set_text(GTK_LABEL(widget), text); g_free(text); g_free(display_aspect); // Video Track const hb_encoder_t * video_encoder; const hb_rate_t * fps; hb_rational_t vrate; char * rate_str; gboolean two_pass, vqtype; str = g_string_new(""); video_encoder = ghb_settings_video_encoder(uiDict, "VideoEncoder"); vqtype = ghb_dict_get_bool(uiDict, "vquality_type_constant"); two_pass = ghb_dict_get_bool(uiDict, "VideoTwoPass"); if (!vqtype) { // ABR int br = ghb_dict_get_int(uiDict, "VideoAvgBitrate"); if (!two_pass) { g_string_append_printf(str, _("%s, Bitrate %dkbps"), video_encoder->name, br); } else { g_string_append_printf(str, _("%s, Bitrate %dkbps (2 Pass)"), video_encoder->name, br); } } else { gdouble quality = ghb_dict_get_double(uiDict, "VideoQualitySlider"); g_string_append_printf(str, _("%s, Constant Quality %.4g(%s)"), video_encoder->name, quality, hb_video_quality_get_name(video_encoder->codec)); } const char * enc_preset = NULL; const char * enc_tune = NULL; const char * enc_level = NULL; const char * enc_profile = NULL; if (hb_video_encoder_get_presets(video_encoder->codec) != NULL) { // The encoder supports presets enc_preset = ghb_dict_get_string(uiDict, "VideoPreset"); } if (hb_video_encoder_get_tunes(video_encoder->codec) != NULL) { // The encoder supports tunes enc_tune = ghb_dict_get_string(uiDict, "VideoTune"); } if (hb_video_encoder_get_profiles(video_encoder->codec) != NULL) { // The encoder supports profiles enc_profile = ghb_dict_get_string(uiDict, "VideoProfile"); } if (hb_video_encoder_get_levels(video_encoder->codec) != NULL) { // The encoder supports levels enc_level = ghb_dict_get_string(uiDict, "VideoLevel"); } sep = "\n"; if (enc_preset != NULL) { g_string_append_printf(str, _("%sPreset %s"), sep, enc_preset); sep = ", "; } if (enc_tune != NULL) { g_string_append_printf(str, _("%sTune %s"), sep, enc_tune); sep = ", "; } if (enc_profile != NULL) { g_string_append_printf(str, _("%sProfile %s"), sep, enc_profile); sep = ", "; } if (enc_level != NULL) { g_string_append_printf(str, _("%sLevel %s"), sep, enc_level); sep = ", "; } fps = ghb_settings_video_framerate(uiDict, "VideoFramerate"); if (fps->rate == 0) { hb_dict_extract_rational(&vrate, titleDict, "FrameRate"); } else { vrate.num = 27000000; vrate.den = fps->rate; } rate_str = g_strdup_printf("%.6g", (gdouble)vrate.num / vrate.den); if (ghb_dict_get_bool(uiDict, "VideoFramerateCFR")) { g_string_append_printf(str, _("\nConstant Framerate %s fps"), rate_str); } else if (ghb_dict_get_bool(uiDict, "VideoFrameratePFR")) { g_string_append_printf(str, _("\nPeak Framerate %s fps (may be lower)"), rate_str); } else if (ghb_dict_get_bool(uiDict, "VideoFramerateVFR")) { g_string_append_printf(str, _("\nVariable Framerate %s fps"), rate_str); } g_free(rate_str); // Append Filters to video summary gboolean detel, comb_detect, deint, decomb, deblock, nlmeans, denoise; gboolean unsharp, lapsharp, rot, gray; ctext = ghb_dict_get_string(uiDict, "PictureDetelecine"); detel = ctext != NULL && !!strcasecmp(ctext, "off"); ctext = ghb_dict_get_string(uiDict, "PictureCombDetectPreset"); comb_detect = ctext != NULL && !!strcasecmp(ctext, "off"); ctext = ghb_dict_get_string(uiDict, "PictureDeinterlaceFilter"); deint = ctext != NULL && !strcasecmp(ctext, "deinterlace"); decomb = ctext != NULL && !strcasecmp(ctext, "decomb"); ctext = ghb_dict_get_string(uiDict, "PictureDeblockPreset"); deblock = ctext != NULL && !!strcasecmp(ctext, "off"); ctext = ghb_dict_get_string(uiDict, "PictureDenoiseFilter"); nlmeans = ctext != NULL && !strcasecmp(ctext, "nlmeans"); denoise = ctext != NULL && !strcasecmp(ctext, "hqdn3d"); ctext = ghb_dict_get_string(uiDict, "PictureSharpenFilter"); unsharp = ctext != NULL && !strcasecmp(ctext, "unsharp"); lapsharp = ctext != NULL && !strcasecmp(ctext, "lapsharp"); ctext = ghb_dict_get_string(uiDict, "PictureRotate"); rot = ctext != NULL && !!strcasecmp(ctext, "disable=1"); gray = ghb_dict_get_bool(uiDict, "VideoGrayScale"); sep = "\n"; if (detel) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_DETELECINE); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } if (comb_detect) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_COMB_DETECT); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } if (deint) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_DEINTERLACE); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } if (decomb) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_DECOMB); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } if (deblock) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_DEBLOCK); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } if (nlmeans) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_NLMEANS); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } if (denoise) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_DENOISE); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } if (unsharp) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_UNSHARP); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } if (lapsharp) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_LAPSHARP); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } if (rot) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_ROTATE); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } if (gray) { hb_filter_object_t * filter = hb_filter_get(HB_FILTER_GRAYSCALE); g_string_append_printf(str, "%s%s", sep, filter->name); sep = ", "; } text = g_string_free(str, FALSE); widget = GHB_WIDGET(ud->builder, "queue_summary_video"); gtk_label_set_text(GTK_LABEL(widget), text); g_free(text); GhbValue * audioList; GhbValue * sourceAudioList; int ii, count; sep = ""; str = g_string_new(""); sourceAudioList = ghb_dict_get(titleDict, "AudioList"); audioList = ghb_get_job_audio_list(queueDict); count = ghb_array_len(audioList); for (ii = 0; ii < count; ii++) { GhbValue * asettings, * asource; const hb_mixdown_t * audio_mix; const hb_encoder_t * audio_encoder; const char * lang; int track; asettings = ghb_array_get(audioList, ii); track = ghb_dict_get_int(asettings, "Track"); asource = ghb_array_get(sourceAudioList, track); lang = ghb_dict_get_string(asource, "Language"); audio_encoder = ghb_settings_audio_encoder(asettings, "Encoder"); if (audio_encoder->codec & HB_ACODEC_PASS_FLAG) { g_string_append_printf(str, "%s%s, %s", sep, lang, audio_encoder->name); } else { audio_mix = ghb_settings_mixdown(asettings, "Mixdown"); g_string_append_printf(str, "%s%s, %s, %s", sep, lang, audio_encoder->name, audio_mix->name); } sep = "\n"; } text = g_string_free(str, FALSE); widget = GHB_WIDGET(ud->builder, "queue_summary_audio"); gtk_label_set_text(GTK_LABEL(widget), text); g_free(text); GhbValue * subtitleDict; GhbValue * searchDict; GhbValue * subtitleList; GhbValue * sourceSubtitleList; gboolean search; sep = ""; str = g_string_new(""); sourceSubtitleList = ghb_dict_get(titleDict, "SubtitleList"); subtitleDict = ghb_get_job_subtitle_settings(queueDict); subtitleList = ghb_dict_get(subtitleDict, "SubtitleList"); searchDict = ghb_dict_get(subtitleDict, "Search"); search = ghb_dict_get_bool(searchDict, "Enable"); count = ghb_array_len(subtitleList); if (search) { gboolean force, burn, def; force = ghb_dict_get_bool(searchDict, "Forced"); burn = ghb_dict_get_bool(searchDict, "Burn"); def = ghb_dict_get_bool(searchDict, "Default"); g_string_append_printf(str, _("Foreign Audio Scan")); if (force) { g_string_append_printf(str, _(", Forced Only")); } if (burn) { g_string_append_printf(str, _(", Burned")); } else if (def) { g_string_append_printf(str, _(", Default")); } sep = "\n"; } for (ii = 0; ii < count; ii++) { GhbValue * subsettings, * subsource; int track; char * desc; gboolean force, burn, def; subsettings = ghb_array_get(subtitleList, ii); track = ghb_dict_get_int(subsettings, "Track"); subsource = ghb_array_get(sourceSubtitleList, track); desc = ghb_subtitle_short_description(subsource, subsettings); force = ghb_dict_get_bool(subsettings, "Forced"); burn = ghb_dict_get_bool(subsettings, "Burn"); def = ghb_dict_get_bool(subsettings, "Default"); g_string_append_printf(str, "%s%s", sep, desc); free(desc); if (force) { g_string_append_printf(str, _(", Forced Only")); } if (burn) { g_string_append_printf(str, _(", Burned")); } else if (def) { g_string_append_printf(str, _(", Default")); } sep = "\n"; } text = g_string_free(str, FALSE); widget = GHB_WIDGET(ud->builder, "queue_summary_subtitle"); gtk_label_set_text(GTK_LABEL(widget), text); g_free(text); } void queue_update_stats(GhbValue * queueDict, signal_user_data_t *ud) { GhbValue * uiDict; GtkLabel * label; uiDict = ghb_dict_get(queueDict, "uiSettings"); if (uiDict == NULL) // should never happen { return; } label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_pass_label")); gtk_widget_set_visible(GTK_WIDGET(label), FALSE); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_pass")); gtk_widget_set_visible(GTK_WIDGET(label), FALSE); const char * result = ""; int status = ghb_dict_get_int(uiDict, "job_status"); if (status == GHB_QUEUE_PENDING) { label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_start_time")); gtk_label_set_text(label, ""); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_finish_time")); gtk_label_set_text(label, ""); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_paused")); gtk_label_set_text(label, ""); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_encode")); gtk_label_set_text(label, ""); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_file_size")); gtk_label_set_text(label, ""); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_result")); gtk_label_set_text(label, "Pending"); return; } switch (status) { case GHB_QUEUE_RUNNING: // This job is running. // ghb_queue_update_live_stats() will update stats return; case GHB_QUEUE_DONE: result = _("Completed"); break; case GHB_QUEUE_CANCELED: result = _("Canceled"); break; case GHB_QUEUE_FAIL: result = _("Failed"); break; case GHB_QUEUE_PENDING: default: result = _("Pending"); break; } struct tm * tm; char date[40] = ""; char * str; time_t start, finish, paused, duration; start = ghb_dict_get_int(uiDict, "job_start_time"); finish = ghb_dict_get_int(uiDict, "job_finish_time"); paused = ghb_dict_get_int(uiDict, "job_pause_time_ms") / 1000; tm = localtime( &start ); strftime(date, 40, "%c", tm); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_start_time")); gtk_label_set_text(label, date); tm = localtime( &finish ); strftime(date, 40, "%c", tm); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_finish_time")); gtk_label_set_text(label, date); int dd = 0, hh, mm, ss; ghb_break_duration(paused, &hh, &mm, &ss); if (hh >= 24) { dd = hh / 24; hh = hh - dd * 24; } switch (dd) { case 0: str = g_strdup_printf("%02d:%02d:%02d", hh, mm, ss); break; case 1: str = g_strdup_printf(_("%d Day %02d:%02d:%02d"), dd, hh, mm, ss); break; default: str = g_strdup_printf(_("%d Days %02d:%02d:%02d"), dd, hh, mm, ss); break; } label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_paused")); gtk_label_set_text(label, str); g_free(str); duration = finish - start; if (duration < 0) { duration = 0; } dd = 0; ghb_break_duration(duration, &hh, &mm, &ss); if (hh >= 24) { dd = hh / 24; hh = hh - dd * 24; } switch (dd) { case 0: str = g_strdup_printf("%02d:%02d:%02d", hh, mm, ss); break; case 1: str = g_strdup_printf(_("%d Day %02d:%02d:%02d"), dd, hh, mm, ss); break; default: str = g_strdup_printf(_("%d Days %02d:%02d:%02d"), dd, hh, mm, ss); break; } label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_encode")); gtk_label_set_text(label, str); g_free(str); const char * path; struct stat stbuf; path = ghb_dict_get_string(uiDict, "destination"); if (g_stat(path, &stbuf) == 0) { const char * units = _("B"); double size = stbuf.st_size; if (size > 1024) { units = _("KB"); size /= 1024.0; } if (size > 1024) { units = _("MB"); size /= 1024.0; } if (size > 1024) { units = _("GB"); size /= 1024.0; } str = g_strdup_printf("%.2f %s", size, units); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_file_size")); gtk_label_set_text(label, str); g_free(str); } else { label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_file_size")); gtk_label_set_text(label, _("Not Available")); } label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_result")); gtk_label_set_text(label, result); } void queue_update_current_stats(signal_user_data_t * ud) { GtkListBox * lb; GtkListBoxRow * row; gint index; GhbValue * queueDict; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row != NULL) { // There is a queue list row selected index = gtk_list_box_row_get_index(row); if (index < 0 || index >= ghb_array_len(ud->queue)) { // Should never happen return; } queueDict = ghb_array_get(ud->queue, index); queue_update_stats(queueDict, ud); } } #define ACTIVITY_MAX_READ_SZ (1024*1024) static void read_log(signal_user_data_t * ud, const char * log_path) { FILE * f; size_t size, req_size; GtkTextIter iter; char * buf; if (ud->extra_activity_path != NULL && !strcmp(ud->extra_activity_path, log_path)) { return; } g_free(ud->extra_activity_path); ud->extra_activity_path = g_strdup(log_path); gtk_text_buffer_set_text(ud->extra_activity_buffer, "", 0); f = g_fopen(log_path, "r"); if (f == NULL) { return; } fseek(f, 0, SEEK_END); req_size = ftell(f); fseek(f, 0, SEEK_SET); if (req_size > ACTIVITY_MAX_READ_SZ) { req_size = ACTIVITY_MAX_READ_SZ; } buf = g_malloc(req_size); while (!feof(f)) { size = fread(buf, 1, req_size, f); if (size <= 0) { break; } gtk_text_buffer_get_end_iter(ud->extra_activity_buffer, &iter); gtk_text_buffer_insert(ud->extra_activity_buffer, &iter, buf, size); } fclose(f); g_free(buf); } // Display/Hide queue activity log pane // Choose buffer to display. Queue logs use 1 of 2 available buffers // queue_activity_buffer - live buffer, updated as encode proceeds // extra_activity_buffer - non-live buffer, populated from log file // If showing non-live buffer, read log file into buffer void ghb_queue_select_log(signal_user_data_t * ud) { GtkListBox * lb; GtkListBoxRow * row; GtkTextBuffer * current; gint index; GhbValue * queueDict, *uiDict; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row != NULL) { // There is a queue list row selected GtkTextView * tv; int status; const char * log_path; index = gtk_list_box_row_get_index(row); if (index < 0 || index >= ghb_array_len(ud->queue)) { // Should never happen return; } queueDict = ghb_array_get(ud->queue, index); uiDict = ghb_dict_get(queueDict, "uiSettings"); // Get the current buffer that is displayed in the queue log tv = GTK_TEXT_VIEW(GHB_WIDGET(ud->builder, "queue_activity_view")); current = gtk_text_view_get_buffer(tv); status = ghb_dict_get_int(uiDict, "job_status"); log_path = ghb_dict_get_string(uiDict, "ActivityFilename"); if (status != GHB_QUEUE_PENDING && log_path != NULL) { ghb_ui_update(ud, "queue_activity_location", ghb_string_value(log_path)); } else { ghb_ui_update(ud, "queue_activity_location", ghb_string_value("")); } if (status == GHB_QUEUE_RUNNING) { // Selected encode is running, enable display of log and // show the live buffer if (ud->queue_activity_buffer != current) { gtk_text_view_set_buffer(tv, ud->queue_activity_buffer); } } else { // Selected encode is pending/finished/canceled/failed // use non-live buffer (aka extra) to display log if (ud->extra_activity_buffer != current) { gtk_text_view_set_buffer(tv, ud->extra_activity_buffer); } log_path = ghb_dict_get_string(uiDict, "ActivityFilename"); if (status != GHB_QUEUE_PENDING && log_path != NULL) { // enable display of log and read log into display buffer read_log(ud, log_path); } else { // No log file, encode is pending // disable display of log g_free(ud->extra_activity_path); ud->extra_activity_path = NULL; gtk_text_buffer_set_text(ud->extra_activity_buffer, "", 0); } } } } void ghb_queue_selection_init(signal_user_data_t * ud) { GtkListBox * lb; GtkListBoxRow * row; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row == NULL) { row = gtk_list_box_get_row_at_index(lb, 0); if (row != NULL) { gtk_list_box_select_row(lb, row); } } } char * ghb_subtitle_short_description(const GhbValue *subsource, const GhbValue *subsettings) { GhbValue *import; char *desc = NULL; import = ghb_dict_get(subsettings, "Import"); if (import != NULL) { const gchar * format = "SRT"; const gchar * code; const gchar * lang; int source = IMPORTSRT; const iso639_lang_t *iso; format = ghb_dict_get_string(import, "Format"); lang = ghb_dict_get_string(import, "Language"); code = ghb_dict_get_string(import, "Codeset"); if (format != NULL && !strcasecmp(format, "SSA")) { source = IMPORTSSA; } iso = lang_lookup(lang); if (iso != NULL) { if (iso->native_name != NULL) lang = iso->native_name; else lang = iso->eng_name; } if (source == IMPORTSRT) { desc = g_strdup_printf("%s (%s)(%s)", lang, code, format); } else { desc = g_strdup_printf("%s (%s)", lang, format); } } else if (subsource == NULL) { desc = g_strdup(_("Foreign Audio Scan")); } else { const char * lang = ghb_dict_get_string(subsource, "Language"); desc = g_strdup_printf("%s", lang); } return desc; } void ghb_queue_progress_set_visible(signal_user_data_t *ud, int index, gboolean visible) { GtkListBox * lb; GtkListBoxRow * row; GtkWidget * progress; int count = ghb_array_len(ud->queue); if (index < 0 || index >= count) { // invalid index return; } lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_row_at_index(lb, index); if (row == NULL) { return; } progress = find_widget(GTK_WIDGET(row), "queue_item_progress"); gtk_widget_set_visible(progress, visible); } void ghb_queue_progress_set_fraction(signal_user_data_t *ud, int index, gdouble frac) { GtkListBox * lb; GtkListBoxRow * row; GtkProgressBar * progress; int count = ghb_array_len(ud->queue); if (index < 0 || index >= count) { // invalid index return; } lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_row_at_index(lb, index); if (row == NULL) { return; } progress = GTK_PROGRESS_BAR(find_widget(GTK_WIDGET(row), "queue_item_progress")); gtk_progress_bar_set_fraction(progress, frac); } void ghb_queue_update_live_stats(signal_user_data_t * ud, int index, ghb_instance_status_t * status) { int count = ghb_array_len(ud->queue); if (index < 0 || index >= count) { // invalid index return; } GtkListBox * lb; GtkListBoxRow * row; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row == NULL || index != gtk_list_box_row_get_index(row)) { return; } GhbValue * queueDict, * uiDict; queueDict = ghb_array_get(ud->queue, index); if (queueDict == NULL) // should never happen { return; } uiDict = ghb_dict_get(queueDict, "uiSettings"); if (uiDict == NULL) // should never happen { return; } GString * gstr = NULL; GtkLabel * label; struct tm * tm; char date[40] = ""; char * str; const char * pass = ""; const char * result = ""; time_t start, finish, paused, duration; start = ghb_dict_get_int(uiDict, "job_start_time"); finish = time(NULL); if (status->state & GHB_STATE_WORKING) { finish += status->eta_seconds; } paused = status->paused / 1000; if ((status->state & GHB_STATE_WORKING) && status->pass_count > 1) { label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_pass_label")); gtk_widget_set_visible(GTK_WIDGET(label), TRUE); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_pass")); gtk_widget_set_visible(GTK_WIDGET(label), TRUE); switch (status->pass_id) { case HB_PASS_SUBTITLE: pass = _("Foreign Audio Search"); break; case HB_PASS_ENCODE: pass = _("Encode"); break; case HB_PASS_ENCODE_1ST: pass = _("Encode First Pass (Analysis)"); break; case HB_PASS_ENCODE_2ND: pass = _("Encode Second Pass (Final)"); break; default: // Should never happen pass = _("Error"); break; } gstr = g_string_new(NULL); g_string_append_printf(gstr, _("pass %d of %d\n%s"), status->pass, status->pass_count, pass); } else { label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_pass_label")); gtk_widget_set_visible(GTK_WIDGET(label), FALSE); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_pass")); gtk_widget_set_visible(GTK_WIDGET(label), FALSE); } if (status->state & GHB_STATE_SCANNING) { result = _("Scanning Title"); } else if (status->state & GHB_STATE_SCANNING) { result = _("Encoding Paused"); } else if (status->state & GHB_STATE_WORKING) { result = _("Encoding In Progress"); } else if (status->state & GHB_STATE_WORKDONE) { switch (status->error) { case GHB_ERROR_NONE: result = _("Completed"); break; case GHB_ERROR_CANCELED: result = _("Canceled"); break; case GHB_ERROR_FAIL: result = _("Failed"); break; default: // Should never happen result = _("Unknown"); break; } } if (gstr != NULL) { label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_pass")); gtk_label_set_text(label, gstr->str); g_string_free(gstr, TRUE); } tm = localtime( &start ); strftime(date, 40, "%c", tm); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_start_time")); gtk_label_set_text(label, date); tm = localtime( &finish ); strftime(date, 40, "%c", tm); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_finish_time")); gtk_label_set_text(label, date); int dd = 0, hh, mm, ss; ghb_break_duration(paused, &hh, &mm, &ss); if (hh >= 24) { dd = hh / 24; hh = hh - dd * 24; } switch (dd) { case 0: str = g_strdup_printf("%02d:%02d:%02d", hh, mm, ss); break; case 1: str = g_strdup_printf(_("%d Day %02d:%02d:%02d"), dd, hh, mm, ss); break; default: str = g_strdup_printf(_("%d Days %02d:%02d:%02d"), dd, hh, mm, ss); break; } label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_paused")); gtk_label_set_text(label, str); g_free(str); duration = finish - start; if (duration < 0) { duration = 0; } dd = 0; ghb_break_duration(duration, &hh, &mm, &ss); if (hh >= 24) { dd = hh / 24; hh = hh - dd * 24; } switch (dd) { case 0: str = g_strdup_printf("%02d:%02d:%02d", hh, mm, ss); break; case 1: str = g_strdup_printf(_("%d Day %02d:%02d:%02d"), dd, hh, mm, ss); break; default: str = g_strdup_printf(_("%d Days %02d:%02d:%02d"), dd, hh, mm, ss); break; } label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_encode")); gtk_label_set_text(label, str); g_free(str); const char * path; struct stat stbuf; path = ghb_dict_get_string(uiDict, "destination"); if (g_stat(path, &stbuf) == 0) { const char * units = _("B"); double size = stbuf.st_size; if (size > 1024) { units = _("KB"); size /= 1024.0; } if (size > 1024) { units = _("MB"); size /= 1024.0; } if (size > 1024) { units = _("GB"); size /= 1024.0; } str = g_strdup_printf("%.2f %s", size, units); label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_file_size")); gtk_label_set_text(label, str); g_free(str); } else { label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_file_size")); gtk_label_set_text(label, _("Not Available")); } label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_stats_result")); gtk_label_set_text(label, result); } void ghb_queue_update_status_icon(signal_user_data_t *ud, int index) { int count = ghb_array_len(ud->queue); if (index < 0 || index >= count) { // invalid index return; } GhbValue * queueDict, * uiDict; queueDict = ghb_array_get(ud->queue, index); if (queueDict == NULL) // should never happen { return; } uiDict = ghb_dict_get(queueDict, "uiSettings"); if (uiDict == NULL) // should never happen { return; } int status = ghb_dict_get_int(uiDict, "job_status"); // Now update the UI const char * icon_name; switch (status) { case GHB_QUEUE_RUNNING: icon_name = "hb-start"; break; case GHB_QUEUE_PENDING: icon_name = "hb-source"; break; case GHB_QUEUE_FAIL: case GHB_QUEUE_CANCELED: icon_name = "hb-stop"; break; case GHB_QUEUE_DONE: icon_name = "hb-complete"; break; default: icon_name = "hb-source"; break; } GtkListBox * lb; GtkListBoxRow * row; GtkImage * status_icon; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_row_at_index(lb, index); if (row == NULL) // should never happen { return; } status_icon = GTK_IMAGE(find_widget(GTK_WIDGET(row), "queue_item_status")); if (status_icon == NULL) // should never happen { return; } ghb_image_set_from_icon_name(status_icon, icon_name, GHB_ICON_SIZE_BUTTON); } void ghb_queue_update_status(signal_user_data_t *ud, int index, int status) { int count = ghb_array_len(ud->queue); if (index < 0 || index >= count) { // invalid index return; } GhbValue * queueDict, * uiDict; queueDict = ghb_array_get(ud->queue, index); if (queueDict == NULL) // should never happen { return; } uiDict = ghb_dict_get(queueDict, "uiSettings"); if (uiDict == NULL) // should never happen { return; } if (ghb_dict_get_int(uiDict, "job_status") == GHB_QUEUE_RUNNING) { return; // Never change the status of currently running jobs } if (status == GHB_QUEUE_PENDING) { ghb_queue_progress_set_visible(ud, index, FALSE); } ghb_dict_set_int(uiDict, "job_status", status); ghb_queue_update_status_icon(ud, index); } void ghb_update_all_status(signal_user_data_t *ud, int status) { int count, ii; count = ghb_array_len(ud->queue); for (ii = 0; ii < count; ii++) { ghb_queue_update_status(ud, ii, status); } } static void save_queue_file(signal_user_data_t *ud) { int ii, count; GhbValue *queue = ghb_value_dup(ud->queue); count = ghb_array_len(queue); for (ii = 0; ii < count; ii++) { GhbValue *queueDict, *uiDict; queueDict = ghb_array_get(ud->queue, ii); uiDict = ghb_dict_get(queueDict, "uiSettings"); if (uiDict == NULL) continue; ghb_dict_set_int(uiDict, "job_status", GHB_QUEUE_PENDING); } GtkWidget *dialog; GtkWindow *hb_window; hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window")); dialog = gtk_file_chooser_dialog_new("Queue Destination", hb_window, GTK_FILE_CHOOSER_ACTION_SAVE, GHB_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GHB_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "queue.json"); if (gtk_dialog_run(GTK_DIALOG (dialog)) != GTK_RESPONSE_ACCEPT) { ghb_value_free(&queue); gtk_widget_destroy(dialog); return; } char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog)); gtk_widget_destroy(dialog); ghb_write_settings_file(filename, queue); g_free (filename); ghb_value_free(&queue); } static void add_to_queue_list(signal_user_data_t *ud, GhbValue *queueDict) { GtkListBox * lb; GtkWidget * row; GtkBox * hbox, * vbox, * dbox; GtkWidget * ebox; GtkWidget * status_icon; GtkWidget * dest_label; GtkWidget * delete_button; GtkWidget * progress; GhbValue * uiDict; const char * dest; gchar * basename; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); uiDict = ghb_dict_get(queueDict, "uiSettings"); row = gtk_list_box_row_new(); vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 6)); gtk_widget_set_margin_start(GTK_WIDGET(vbox), 12); hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6)); dbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6)); #if GTK_CHECK_VERSION(3, 90, 0) ebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); #else ebox = gtk_event_box_new(); #endif status_icon = ghb_image_new_from_icon_name("hb-source", GHB_ICON_SIZE_BUTTON); gtk_widget_set_name(status_icon, "queue_item_status"); gtk_image_set_pixel_size(GTK_IMAGE(status_icon), 16); gtk_widget_set_hexpand(status_icon, FALSE); dest = ghb_dict_get_string(uiDict, "destination"); basename = g_path_get_basename(dest); dest_label = gtk_label_new(basename); g_free(basename); gtk_widget_set_name(dest_label, "queue_item_dest"); gtk_widget_set_hexpand(dest_label, TRUE); gtk_widget_set_halign(dest_label, GTK_ALIGN_FILL); gtk_label_set_justify(GTK_LABEL(dest_label), GTK_JUSTIFY_LEFT); gtk_label_set_xalign(GTK_LABEL(dest_label), 0.0); gtk_label_set_width_chars(GTK_LABEL(dest_label), 50); gtk_label_set_ellipsize(GTK_LABEL(dest_label), PANGO_ELLIPSIZE_END); delete_button = ghb_button_new_from_icon_name("hb-remove"); gtk_button_set_relief(GTK_BUTTON(delete_button), GTK_RELIEF_NONE); g_signal_connect(delete_button, "clicked", (GCallback)queue_remove_clicked_cb, ud); gtk_widget_set_hexpand(delete_button, FALSE); progress = gtk_progress_bar_new(); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0); gtk_widget_set_name(progress, "queue_item_progress"); gtk_widget_set_visible(progress, FALSE); ghb_box_append_child(dbox, GTK_WIDGET(status_icon)); ghb_box_append_child(dbox, GTK_WIDGET(dest_label)); gtk_container_add(GTK_CONTAINER(ebox), GTK_WIDGET(dbox)); ghb_box_append_child(hbox, GTK_WIDGET(ebox)); ghb_box_append_child(hbox, GTK_WIDGET(delete_button)); ghb_box_append_child(vbox, GTK_WIDGET(hbox)); ghb_box_append_child(vbox, GTK_WIDGET(progress)); gtk_container_add(GTK_CONTAINER(row), GTK_WIDGET(vbox)); gtk_widget_show(GTK_WIDGET(row)); gtk_widget_show(GTK_WIDGET(vbox)); gtk_widget_show(GTK_WIDGET(hbox)); gtk_widget_show(GTK_WIDGET(dbox)); gtk_widget_show(GTK_WIDGET(ebox)); gtk_widget_show(status_icon); gtk_widget_show(dest_label); gtk_widget_show(delete_button); gtk_list_box_insert(lb, row, -1); // style class for CSS settings gtk_style_context_add_class(gtk_widget_get_style_context(row), "row"); // set row as a source for drag & drop #if GTK_CHECK_VERSION(3, 90, 0) GdkContentFormats * targets; targets = gdk_content_formats_new(queue_drag_entries, G_N_ELEMENTS(queue_drag_entries)); gtk_drag_source_set(ebox, GDK_BUTTON1_MASK, targets, GDK_ACTION_MOVE); gdk_content_formats_unref(targets); #else gtk_drag_source_set(ebox, GDK_BUTTON1_MASK, queue_drag_entries, 1, GDK_ACTION_MOVE); #endif g_signal_connect(ebox, "drag-begin", G_CALLBACK(queue_drag_begin_cb), NULL); g_signal_connect(ebox, "drag-end", G_CALLBACK(queue_drag_end_cb), NULL); g_signal_connect(ebox, "drag-data-get", G_CALLBACK(queue_drag_data_get_cb), NULL); #if GTK_CHECK_VERSION(3, 90, 0) // connect key event controller to capture "delete" key press on row GtkEventController * econ = gtk_event_controller_key_new(); gtk_widget_add_controller(row, econ); g_signal_connect(econ, "key-pressed", G_CALLBACK(queue_row_key_cb), ud); #endif } static void open_queue_file(signal_user_data_t *ud) { GtkWidget *dialog; GtkWindow *hb_window; hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window")); dialog = gtk_file_chooser_dialog_new("Queue Destination", hb_window, GTK_FILE_CHOOSER_ACTION_OPEN, GHB_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GHB_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); // Add filters GtkFileFilter *filter; GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog); filter = GTK_FILE_FILTER(GHB_OBJECT(ud->builder, "QueueFilterAll")); gtk_file_filter_set_name(filter, _("All")); gtk_file_filter_add_pattern(filter, "*"); gtk_file_chooser_add_filter(chooser, filter); filter = GTK_FILE_FILTER(GHB_OBJECT(ud->builder, "QueueFilterJSON")); gtk_file_filter_set_name(filter, "JSON"); gtk_file_filter_add_pattern(filter, "*.JSON"); gtk_file_filter_add_pattern(filter, "*.json"); gtk_file_chooser_add_filter(chooser, filter); if (gtk_dialog_run(GTK_DIALOG (dialog)) != GTK_RESPONSE_ACCEPT) { gtk_widget_destroy(dialog); return; } GhbValue *queue; char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); gtk_widget_destroy(dialog); queue = ghb_read_settings_file(filename); if (queue != NULL) { int ii, count; count = ghb_array_len(queue); for (ii = 0; ii < count; ii++) { GhbValue *queueDict, *uiDict; queueDict = ghb_array_get(queue, ii); uiDict = ghb_dict_get(queueDict, "uiSettings"); ghb_value_incref(queueDict); ghb_dict_set_int(uiDict, "job_status", GHB_QUEUE_PENDING); ghb_dict_set_int(uiDict, "job_unique_id", 0); if (ud->queue == NULL) ud->queue = ghb_array_new(); ghb_array_append(ud->queue, queueDict); add_to_queue_list(ud, queueDict); } ghb_queue_buttons_grey(ud); ghb_save_queue(ud->queue); ghb_value_free(&queue); } g_free (filename); } gint64 ghb_dest_free_space(GhbValue *settings) { GFile *gfile; GFileInfo *info; guint64 size = -1; const gchar *dest = ghb_dict_get_string(settings, "destination"); gchar *destdir = g_path_get_dirname(dest); gchar *resolved = ghb_resolve_symlink(destdir); gfile = g_file_new_for_path(resolved); info = g_file_query_filesystem_info(gfile, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, NULL); if (info != NULL) { if (g_file_info_has_attribute(info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE)) { size = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); } g_object_unref(info); } g_object_unref(gfile); g_free(resolved); g_free(destdir); return size; } gint ghb_find_queue_job(GhbValue *queue, gint unique_id, GhbValue **job) { GhbValue *queueDict, *uiDict; gint ii, count; gint job_unique_id; if (job != NULL) { *job = NULL; } if (unique_id == 0) // Invalid Id return -1; count = ghb_array_len(queue); for (ii = 0; ii < count; ii++) { queueDict = ghb_array_get(queue, ii); uiDict = ghb_dict_get(queueDict, "uiSettings"); job_unique_id = ghb_dict_get_int(uiDict, "job_unique_id"); if (job_unique_id == unique_id) { if (job != NULL) { *job = queueDict; } return ii; } } return -1; } void ghb_low_disk_check(signal_user_data_t *ud) { GtkWindow *hb_window; GtkWidget *dialog; GtkResponseType response; ghb_status_t status; const char *paused_msg = ""; const char *dest; gint64 free_size; gint64 free_limit; GhbValue *qDict; GhbValue *settings; if (ghb_dict_get_bool(ud->globals, "SkipDiskFreeCheck") || !ghb_dict_get_bool(ud->prefs, "DiskFreeCheck")) { return; } ghb_get_status(&status); if (status.queue.unique_id <= 0) { // No current job return; } ghb_find_queue_job(ud->queue, status.queue.unique_id, &qDict); if (qDict == NULL) { // Failed to find queue setting! return; } settings = ghb_dict_get(qDict, "uiSettings"); free_size = ghb_dest_free_space(settings); if (free_size < 0) { // Failed to read free space return; } // limit is in MB free_limit = ghb_dict_get_int(ud->prefs, "DiskFreeLimit") * 1024 * 1024; if (free_size > free_limit) { return; } if ((status.queue.state & GHB_STATE_WORKING) && !(status.queue.state & GHB_STATE_PAUSED)) { paused_msg = "Encoding has been paused.\n\n"; ghb_pause_queue(); } dest = ghb_dict_get_string(settings, "destination"); hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window")); dialog = gtk_message_dialog_new(hb_window, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, _("%sThe destination filesystem is almost full: %"PRId64" MB free.\n" "Destination: %s\n" "Encode may be incomplete if you proceed.\n"), paused_msg, free_size / (1024 * 1024), dest); gtk_dialog_add_buttons( GTK_DIALOG(dialog), _("Resume, I've fixed the problem"), 1, _("Resume, Don't tell me again"), 2, _("Cancel Current and Stop"), 3, NULL); response = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); switch ((int)response) { case 1: ghb_resume_queue(); break; case 2: ghb_dict_set_bool(ud->globals, "SkipDiskFreeCheck", TRUE); ghb_resume_queue(); break; case 3: ghb_stop_queue(); ud->cancel_encode = GHB_CANCEL_ALL; break; default: ghb_resume_queue(); break; } } 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 gchar *message; const gchar *dest; gint count, ii; gint title_id, titleindex; const hb_title_t *title; GtkWindow *hb_window; hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "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 (strcmp(dest, filename) == 0) { message = g_strdup_printf( _("Destination: %s\n\n" "Another queued job has specified the same destination.\n" "Do you want to overwrite?"), dest); if (!ghb_message_dialog(hb_window, GTK_MESSAGE_QUESTION, message, _("Cancel"), _("Overwrite"))) { g_free(message); return FALSE; } g_free(message); break; } } gchar *destdir = g_path_get_dirname(dest); if (!g_file_test(destdir, G_FILE_TEST_IS_DIR)) { message = g_strdup_printf( _("Destination: %s\n\n" "This is not a valid directory."), destdir); ghb_message_dialog(hb_window, GTK_MESSAGE_ERROR, message, _("Cancel"), NULL); g_free(message); g_free(destdir); return FALSE; } #if !defined(_WIN32) // This doesn't work properly on windows if (g_access(destdir, R_OK|W_OK) != 0) { message = g_strdup_printf( _("Destination: %s\n\n" "Can not read or write the directory."), destdir); ghb_message_dialog(hb_window, GTK_MESSAGE_ERROR, message, _("Cancel"), NULL); g_free(message); g_free(destdir); return FALSE; } #endif g_free(destdir); if (g_file_test(dest, G_FILE_TEST_EXISTS)) { message = g_strdup_printf( _("Destination: %s\n\n" "File already exists.\n" "Do you want to overwrite?"), dest); if (!ghb_message_dialog(hb_window, GTK_MESSAGE_QUESTION, message, _("Cancel"), _("Overwrite"))) { g_free(message); return FALSE; } g_free(message); g_unlink(dest); } // 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"); // Apply selected preset settings hb_preset_apply_mux(preset, job); hb_preset_apply_video(preset, job); hb_preset_apply_filters(preset, job); // Add scale filter since the above does not GhbValue *filter_list, *filter_dict; int width, height, crop[4]; filter_list = ghb_get_job_filter_list(settings); 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); ghb_value_free(&preset); } static gboolean queue_add(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 *uiDict = ghb_value_dup(settings); 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); 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_multiple_can_select(GhbValue *settings_array, int 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_multiple_set_sensitive(GtkWidget *row, gboolean sensitive) { GtkWidget *widget; widget = find_widget(row, "title_selected"); gtk_widget_set_sensitive(widget, sensitive); widget = find_widget(row, "title_label"); if (!sensitive) { PangoAttrList *pal; PangoAttribute *bg; bg = pango_attr_background_new(0xFFFF, 0xFFFF, 0xA000); pal = pango_attr_list_new(); pango_attr_list_insert(pal, bg); gtk_label_set_attributes(GTK_LABEL(widget), pal); gtk_widget_set_has_tooltip(widget, TRUE); } else { gtk_label_set_attributes(GTK_LABEL(widget), default_title_attrs); gtk_widget_set_has_tooltip(widget, FALSE); } } static gboolean title_add_multiple_are_conflicts(signal_user_data_t *ud) { GtkListBox *list; GtkWidget *row; gint count, ii; list = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "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_multiple_can_select(ud->settings_array, ii)) { title_add_multiple_set_sensitive(GTK_WIDGET(row), FALSE); return TRUE; } title_add_multiple_set_sensitive(GTK_WIDGET(row), TRUE); } return FALSE; } static void title_add_multiple_set_conflict_label( 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.\n" "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; GtkContainer *content_area; GList *list; GtkLabel *label; info = GTK_INFO_BAR(GHB_WIDGET(ud->builder, "title_add_multiple_infobar")); content_area = GTK_CONTAINER(gtk_info_bar_get_content_area(info)); list = gtk_container_get_children(content_area); // Label is first in list label = GTK_LABEL(list->data); gtk_label_set_markup(label, msg); gtk_info_bar_set_message_type(info, msg_type); conflict_showing = are_conflicts; } } static void title_add_multiple_check_conflicts(signal_user_data_t *ud) { gint count, ii; GhbValue *settings; GtkWidget *row; GtkListBox *list; GtkToggleButton *selected; gboolean can_select; gboolean are_conflicts = FALSE; list = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "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 = GTK_TOGGLE_BUTTON(find_widget(row, "title_selected")); settings = ghb_array_get(ud->settings_array, ii); can_select = title_multiple_can_select(ud->settings_array, ii); ghb_dict_set_bool(settings, "title_selected", FALSE); gtk_toggle_button_set_active(selected, FALSE); title_add_multiple_set_sensitive(GTK_WIDGET(row), can_select); are_conflicts |= !can_select; } title_add_multiple_set_conflict_label(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")) { queue_add(ud, settings, 1); } } 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); } GtkWidget * title_create_row(signal_user_data_t *ud) { GtkBox *hbox, *vbox_dest; GtkCheckButton *selected; GtkLabel *title; GtkEntry *dest_file; GtkFileChooserButton *dest_dir; hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); gtk_box_set_spacing(hbox, 6); gtk_widget_show(GTK_WIDGET(hbox)); // Select checkbox selected = GTK_CHECK_BUTTON(gtk_check_button_new()); 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_show(GTK_WIDGET(selected)); g_signal_connect(selected, "toggled", (GCallback)title_selected_cb, ud); ghb_box_append_child(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_show(GTK_WIDGET(title)); ghb_box_append_child(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 chooser 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(gtk_entry_new()); ghb_entry_set_width_chars(dest_file, 40); gtk_widget_set_name(GTK_WIDGET(dest_file), "title_file"); //gtk_widget_set_hexpand(GTK_WIDGET(dest_file), TRUE); gtk_widget_show(GTK_WIDGET(dest_file)); g_signal_connect(dest_file, "changed", (GCallback)title_dest_file_cb, ud); ghb_box_append_child(vbox_dest, GTK_WIDGET(dest_file)); dest_dir = GTK_FILE_CHOOSER_BUTTON( gtk_file_chooser_button_new(_("Destination Directory"), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)); g_signal_connect(dest_dir, "selection-changed", (GCallback)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_show(GTK_WIDGET(dest_dir)); ghb_box_append_child(vbox_dest, GTK_WIDGET(dest_dir)); gtk_widget_show(GTK_WIDGET(vbox_dest)); ghb_box_append_child(hbox, GTK_WIDGET(vbox_dest)); return GTK_WIDGET(hbox); } static void ghb_queue_remove_row_internal(signal_user_data_t *ud, int index) { GtkListBox * lb; GtkListBoxRow * row; GhbValue * queueDict, * uiDict; if (index < 0 || index >= ghb_array_len(ud->queue)) { return; } queueDict = ghb_array_get(ud->queue, index); uiDict = ghb_dict_get(queueDict, "uiSettings"); int status = ghb_dict_get_int(uiDict, "job_status"); if (status == GHB_QUEUE_RUNNING) { // Ask if wants to stop encode. if (!ghb_cancel_encode2(ud, NULL)) { return; } int unique_id = ghb_dict_get_int(uiDict, "job_unique_id"); ghb_remove_job(unique_id); } ghb_array_remove(ud->queue, index); // Update UI lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_row_at_index(lb, index); gtk_container_remove(GTK_CONTAINER(lb), GTK_WIDGET(row)); } void ghb_queue_remove_row(signal_user_data_t *ud, int index) { ghb_queue_remove_row_internal(ud, index); ghb_save_queue(ud->queue); ghb_queue_buttons_grey(ud); } void ghb_queue_buttons_grey(signal_user_data_t *ud) { GtkWidget * widget; GSimpleAction * action; gint queue_count; gint title_id, titleindex; const hb_title_t * title; gint queue_state, scan_state; gboolean allow_start, show_stop, allow_add, paused; GtkListBox * lb; GtkListBoxRow * row; gint index, status = GHB_QUEUE_PENDING; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row != NULL) { index = gtk_list_box_row_get_index(row); if (index >= 0 && index < ghb_array_len(ud->queue)) { GhbValue * queueDict, *uiDict; queueDict = ghb_array_get(ud->queue, index); uiDict = ghb_dict_get(queueDict, "uiSettings"); status = ghb_dict_get_int(uiDict, "job_status"); } } queue_count = ghb_array_len(ud->queue); title_id = ghb_dict_get_int(ud->settings, "title"); title = ghb_lookup_title(title_id, &titleindex); queue_state = ghb_get_queue_state(); scan_state = ghb_get_scan_state(); show_stop = queue_state & (GHB_STATE_WORKING | GHB_STATE_SEARCHING | GHB_STATE_SCANNING | GHB_STATE_MUXING | GHB_STATE_PAUSED); allow_start = !(scan_state & GHB_STATE_SCANNING) && (title != NULL || queue_count > 0); allow_add = !(scan_state & GHB_STATE_SCANNING) && title != NULL; paused = queue_state & GHB_STATE_PAUSED; action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-export")); g_simple_action_set_enabled(action, !!queue_count); action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-add")); g_simple_action_set_enabled(action, allow_add); action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-add-all")); g_simple_action_set_enabled(action, allow_add); action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-start")); g_simple_action_set_enabled(action, allow_start || show_stop); action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-pause")); g_simple_action_set_enabled(action, show_stop); action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-reset")); g_simple_action_set_enabled(action, row != NULL); action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-edit")); g_simple_action_set_enabled(action, row != NULL); action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-open-source")); g_simple_action_set_enabled(action, row != NULL); action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-open-dest")); g_simple_action_set_enabled(action, row != NULL); action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-open-log-dir")); g_simple_action_set_enabled(action, status != GHB_QUEUE_PENDING); action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app), "queue-open-log")); g_simple_action_set_enabled(action, status != GHB_QUEUE_PENDING); widget = GHB_WIDGET (ud->builder, "queue_start"); if (show_stop) { gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(widget), "hb-stop"); gtk_tool_button_set_label(GTK_TOOL_BUTTON(widget), _("Stop")); gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(widget), _("Stop Encoding")); } else { gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(widget), "hb-start"); gtk_tool_button_set_label(GTK_TOOL_BUTTON(widget), _("Start")); gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(widget), _("Start Encoding")); } widget = GHB_WIDGET (ud->builder, "queue_list_start"); if (show_stop) { gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(widget), "hb-stop"); gtk_tool_button_set_label(GTK_TOOL_BUTTON(widget), _("Stop")); gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(widget), _("Stop Encoding")); } else { gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(widget), "hb-start"); gtk_tool_button_set_label(GTK_TOOL_BUTTON(widget), _("Start")); gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(widget), _("Start Encoding")); } widget = GHB_WIDGET (ud->builder, "queue_start_menu"); if (show_stop) { gtk_menu_item_set_label(GTK_MENU_ITEM(widget), _("S_top Encoding")); gtk_widget_set_tooltip_text(widget, _("Stop Encoding")); } else { gtk_menu_item_set_label(GTK_MENU_ITEM(widget), _("_Start Encoding")); gtk_widget_set_tooltip_text(widget, _("Start Encoding")); } widget = GHB_WIDGET (ud->builder, "queue_pause"); if (paused) { gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(widget), "hb-start"); gtk_tool_button_set_label(GTK_TOOL_BUTTON(widget), _("Resume")); gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(widget), _("Resume Encoding")); } else { gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(widget), "hb-pause"); gtk_tool_button_set_label(GTK_TOOL_BUTTON(widget), _("Pause")); gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(widget), _("Pause Encoding")); } widget = GHB_WIDGET (ud->builder, "queue_list_pause"); if (paused) { gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(widget), "hb-start"); gtk_tool_button_set_label(GTK_TOOL_BUTTON(widget), _("Resume")); gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(widget), _("Resume Encoding")); } else { gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(widget), "hb-pause"); gtk_tool_button_set_label(GTK_TOOL_BUTTON(widget), _("Pause")); gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(widget), _("Pause Encoding")); } widget = GHB_WIDGET (ud->builder, "queue_pause_menu"); if (paused) { gtk_menu_item_set_label(GTK_MENU_ITEM(widget), _("_Resume Encoding")); gtk_widget_set_tooltip_text(widget, _("Resume Encoding")); } else { gtk_menu_item_set_label(GTK_MENU_ITEM(widget), _("_Pause Encoding")); gtk_widget_set_tooltip_text(widget, _("Pause Encoding")); } } gboolean ghb_reload_queue(signal_user_data_t *ud) { GhbValue *queue; gint count, ii; gint pid; gint status; GhbValue *queueDict, *uiDict; g_debug("ghb_reload_queue"); find_pid: pid = ghb_find_pid_file(); if (pid < 0) goto done; queue = ghb_load_old_queue(pid); ghb_remove_old_queue_file(pid); // Look for unfinished entries count = ghb_array_len(queue); for (ii = count-1; ii >= 0; ii--) { queueDict = ghb_array_get(queue, ii); uiDict = ghb_dict_get(queueDict, "uiSettings"); if (uiDict == NULL || ghb_dict_get_value(uiDict, "job_status") == NULL) { ghb_array_remove(queue, ii); continue; } status = ghb_dict_get_int(uiDict, "job_status"); if (status == GHB_QUEUE_DONE || status == GHB_QUEUE_CANCELED) { ghb_array_remove(queue, ii); continue; } } count = ghb_array_len(queue); if (count == 0) { ghb_value_free(&queue); goto find_pid; } else { GtkWidget *widget = GHB_WIDGET(ud->builder, "show_queue"); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), TRUE); ud->queue = queue; for (ii = 0; ii < count; ii++) { queueDict = ghb_array_get(queue, ii); uiDict = ghb_dict_get(queueDict, "uiSettings"); ghb_dict_set_int(uiDict, "job_unique_id", 0); ghb_dict_set_int(uiDict, "job_status", GHB_QUEUE_PENDING); add_to_queue_list(ud, queueDict); } ghb_queue_buttons_grey(ud); ghb_save_queue(ud->queue); ghb_update_pending(ud); ghb_queue_selection_init(ud); } done: ghb_write_pid_file(); return FALSE; } static gboolean queue_row_key(GtkListBoxRow * row, guint keyval, signal_user_data_t * ud) { gint index; if (keyval != GDK_KEY_Delete) return FALSE; if (row != NULL) { index = gtk_list_box_row_get_index(row); ghb_queue_remove_row_internal(ud, index); ghb_save_queue(ud->queue); return TRUE; } return FALSE; } #if GTK_CHECK_VERSION(3, 90, 0) G_MODULE_EXPORT gboolean queue_row_key_cb( GtkEventControllerKey * keycon, guint keyval, guint keycode, GdkModifierType state, signal_user_data_t * ud) { GtkListBoxRow * row; row = GTK_LIST_BOX_ROW(gtk_event_controller_get_widget( GTK_EVENT_CONTROLLER(keycon))); return queue_row_key(row, keyval, ud); } #else G_MODULE_EXPORT gboolean queue_key_press_cb( GtkWidget * widget, GdkEvent * event, signal_user_data_t * ud) { GtkListBoxRow * row; guint keyval; row = gtk_list_box_get_selected_row(GTK_LIST_BOX(widget)); ghb_event_get_keyval(event, &keyval); return queue_row_key(row, keyval, ud); } #endif G_MODULE_EXPORT void show_queue_action_cb(GSimpleAction *action, GVariant *value, signal_user_data_t *ud) { GtkWidget * queue_window; gboolean state = g_variant_get_boolean(value); g_simple_action_set_state(action, value); queue_window = GHB_WIDGET(ud->builder, "queue_window"); gtk_widget_set_visible(queue_window, state); } G_MODULE_EXPORT gboolean queue_window_delete_cb( GtkWidget *xwidget, #if !GTK_CHECK_VERSION(3, 90, 0) GdkEvent *event, #endif signal_user_data_t *ud) { gtk_widget_set_visible(xwidget, FALSE); GtkWidget *widget = GHB_WIDGET (ud->builder, "show_queue"); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE); return TRUE; } GhbValue *ghb_queue_edit_settings = NULL; G_MODULE_EXPORT void queue_edit_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { GtkListBox * lb; GtkListBoxRow * row; gint index, status; GhbValue * queueDict, *uiDict; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row != NULL) { index = gtk_list_box_row_get_index(row); if (index < 0 || index >= ghb_array_len(ud->queue)) return; queueDict = ghb_array_get(ud->queue, index); uiDict = ghb_dict_get(queueDict, "uiSettings"); ghb_queue_edit_settings = ghb_value_dup(uiDict); ghb_dict_set(ghb_queue_edit_settings, "Job", ghb_value_dup(ghb_dict_get(queueDict, "Job"))); ghb_dict_set(ghb_queue_edit_settings, "Title", ghb_value_dup(ghb_dict_get(queueDict, "Title"))); status = ghb_dict_get_int(uiDict, "job_status"); if (status == GHB_QUEUE_PENDING) { // Remove the selected item gtk_container_remove(GTK_CONTAINER(lb), GTK_WIDGET(row)); // Remove the corresponding item from the queue list ghb_array_remove(ud->queue, index); ghb_update_pending(ud); } const gchar *source; source = ghb_dict_get_string(ghb_queue_edit_settings, "source"); ghb_do_scan(ud, source, 0, FALSE); GtkWidget *widget = GHB_WIDGET(ud->builder, "show_queue"); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE); } } G_MODULE_EXPORT void queue_export_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { save_queue_file(ud); } G_MODULE_EXPORT void queue_import_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { open_queue_file(ud); } G_MODULE_EXPORT void queue_open_clicked_cb(GtkWidget *widget, signal_user_data_t *ud) { open_queue_file(ud); } G_MODULE_EXPORT void queue_open_source_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { GtkListBox * lb; GtkListBoxRow * row; gint index; GhbValue * queueDict, * titleDict; GString * str; const char * path; char * dir, * uri; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row != NULL) { index = gtk_list_box_row_get_index(row); if (index < 0 || index >= ghb_array_len(ud->queue)) { return; } queueDict = ghb_array_get(ud->queue, index); titleDict = ghb_dict_get(queueDict, "Title"); path = ghb_dict_get_string(titleDict, "Path"); dir = g_path_get_dirname(path); str = g_string_new(NULL); g_string_printf(str, "file://%s", dir); uri = g_string_free(str, FALSE); ghb_browse_uri(ud, uri); g_free(uri); g_free(dir); } } G_MODULE_EXPORT void queue_open_dest_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { GtkListBox * lb; GtkListBoxRow * row; gint index; GhbValue * queueDict, * uiDict; GString * str; const char * path; char * dir, * uri; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row != NULL) { index = gtk_list_box_row_get_index(row); if (index < 0 || index >= ghb_array_len(ud->queue)) { return; } queueDict = ghb_array_get(ud->queue, index); uiDict = ghb_dict_get(queueDict, "uiSettings"); path = ghb_dict_get_string(uiDict, "destination"); dir = g_path_get_dirname(path); str = g_string_new(NULL); g_string_printf(str, "file://%s", dir); uri = g_string_free(str, FALSE); ghb_browse_uri(ud, uri); g_free(uri); g_free(dir); } } G_MODULE_EXPORT void queue_open_log_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { GtkListBox * lb; GtkListBoxRow * row; gint index; GhbValue * queueDict, * uiDict; GString * str; const char * path; char * uri; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row != NULL) { index = gtk_list_box_row_get_index(row); if (index < 0 || index >= ghb_array_len(ud->queue)) { return; } queueDict = ghb_array_get(ud->queue, index); uiDict = ghb_dict_get(queueDict, "uiSettings"); path = ghb_dict_get_string(uiDict, "ActivityFilename"); if (path == NULL) { return; } str = g_string_new(NULL); g_string_printf(str, "file://%s", path); uri = g_string_free(str, FALSE); ghb_browse_uri(ud, uri); g_free(uri); } } G_MODULE_EXPORT void queue_open_log_dir_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { GtkListBox * lb; GtkListBoxRow * row; gint index; GhbValue * queueDict, * uiDict; GString * str; const char * path; char * dir, * uri; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row != NULL) { index = gtk_list_box_row_get_index(row); if (index < 0 || index >= ghb_array_len(ud->queue)) { return; } queueDict = ghb_array_get(ud->queue, index); uiDict = ghb_dict_get(queueDict, "uiSettings"); path = ghb_dict_get_string(uiDict, "ActivityFilename"); if (path == NULL) { return; } dir = g_path_get_dirname(path); str = g_string_new(NULL); g_string_printf(str, "file://%s", dir); uri = g_string_free(str, FALSE); ghb_browse_uri(ud, uri); g_free(uri); g_free(dir); } } G_MODULE_EXPORT void queue_list_selection_changed_cb(GtkListBox *box, GtkListBoxRow *row, signal_user_data_t *ud) { GhbValue * queueDict = NULL; int index = -1; if (row != NULL) { index = gtk_list_box_row_get_index(row); } if (index >= 0 && index < ghb_array_len(ud->queue)) { queueDict = ghb_array_get(ud->queue, index); } queue_update_summary(queueDict, ud); queue_update_stats(queueDict, ud); ghb_queue_select_log(ud); ghb_queue_buttons_grey(ud); } G_MODULE_EXPORT void queue_add_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { queue_add(ud, ud->settings, 0); ghb_queue_selection_init(ud); // Validation of settings may have changed audio list ghb_audio_list_refresh_all(ud); } G_MODULE_EXPORT void queue_add_all_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { GtkListBox *list; GtkWidget *row; gint count, ii; int max_title_len = 0; GhbValue * preset = NULL; list = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "title_add_multiple_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; GtkEntry *entry; GtkFileChooser *chooser; gchar *title_label; const gchar *dest_dir, *dest_file; int title_id, titleindex; const hb_title_t *title; row = title_create_row(ud); label = GTK_LABEL(find_widget(row, "title_label")); entry = GTK_ENTRY(find_widget(row, "title_file")); chooser = GTK_FILE_CHOOSER(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) { int 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); ghb_editable_set_text(entry, dest_file); gtk_file_chooser_set_filename(chooser, dest_dir); g_free(title_label); } gtk_list_box_insert(list, row, -1); } // 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++) { GtkWidget *row; 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); } // Clear "select all" and "clear all" options GtkToggleButton *select_all; GtkToggleButton *clear_all; clear_all = GTK_TOGGLE_BUTTON(GHB_WIDGET(ud->builder, "title_add_multiple_clear_all")); select_all = GTK_TOGGLE_BUTTON(GHB_WIDGET(ud->builder, "title_add_multiple_select_all")); gtk_toggle_button_set_active(clear_all, FALSE); gtk_toggle_button_set_active(select_all, FALSE); gtk_widget_set_sensitive(GTK_WIDGET(select_all), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(clear_all), TRUE); // Disable selection of files with duplicate file names. title_add_multiple_check_conflicts(ud); // Pop up the title multiple selections dialog GtkResponseType response; GtkWidget *dialog = GHB_WIDGET(ud->builder, "titla_add_multiple_dialog"); response = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_hide(dialog); if (response == GTK_RESPONSE_OK) { add_multiple_titles(ud); } // Clear title list ghb_container_empty(GTK_CONTAINER(list)); } G_MODULE_EXPORT void queue_delete_all_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { int ii, count; count = ghb_array_len(ud->queue); for (ii = count - 1; ii >= 0; ii--) { ghb_queue_remove_row_internal(ud, ii); } ghb_save_queue(ud->queue); ghb_update_pending(ud); ghb_queue_buttons_grey(ud); } G_MODULE_EXPORT void queue_delete_complete_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { int ii, count, status; GhbValue * queueDict, * uiDict; count = ghb_array_len(ud->queue); for (ii = count - 1; ii >= 0; ii--) { queueDict = ghb_array_get(ud->queue, ii); uiDict = ghb_dict_get(queueDict, "uiSettings"); status = ghb_dict_get_int(uiDict, "job_status"); if (status == GHB_QUEUE_DONE) { ghb_queue_remove_row_internal(ud, ii); } } ghb_save_queue(ud->queue); ghb_update_pending(ud); ghb_queue_buttons_grey(ud); } G_MODULE_EXPORT void queue_reset_all_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { ghb_update_all_status(ud, GHB_QUEUE_PENDING); ghb_save_queue(ud->queue); queue_update_current_stats(ud); ghb_queue_select_log(ud); ghb_update_pending(ud); ghb_queue_buttons_grey(ud); } G_MODULE_EXPORT void queue_reset_fail_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { int ii, count, status; GhbValue * queueDict, * uiDict; count = ghb_array_len(ud->queue); for (ii = count - 1; ii >= 0; ii--) { queueDict = ghb_array_get(ud->queue, ii); uiDict = ghb_dict_get(queueDict, "uiSettings"); status = ghb_dict_get_int(uiDict, "job_status"); if (status == GHB_QUEUE_FAIL) { ghb_queue_update_status(ud, ii, GHB_QUEUE_PENDING); } } ghb_save_queue(ud->queue); queue_update_current_stats(ud); ghb_queue_select_log(ud); ghb_update_pending(ud); ghb_queue_buttons_grey(ud); } G_MODULE_EXPORT void queue_reset_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { GtkListBox * lb; GtkListBoxRow * row; gint index; lb = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "queue_list")); row = gtk_list_box_get_selected_row(lb); if (row != NULL) { index = gtk_list_box_row_get_index(row); ghb_queue_update_status(ud, index, GHB_QUEUE_PENDING); ghb_save_queue(ud->queue); queue_update_current_stats(ud); ghb_queue_select_log(ud); ghb_update_pending(ud); ghb_queue_buttons_grey(ud); } } G_MODULE_EXPORT void queue_remove_clicked_cb(GtkWidget *widget, signal_user_data_t *ud) { GtkListBoxRow * row; gint index; row = list_box_get_row(widget); if (row != NULL) { index = gtk_list_box_row_get_index(row); ghb_queue_remove_row_internal(ud, index); ghb_save_queue(ud->queue); } ghb_update_pending(ud); ghb_queue_buttons_grey(ud); } G_MODULE_EXPORT void queue_start_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { GhbValue *queueDict, *uiDict; gboolean running = FALSE; gint count, ii; gint status; gint state; state = ghb_get_queue_state(); if (state & (GHB_STATE_WORKING | GHB_STATE_SEARCHING | GHB_STATE_SCANNING | GHB_STATE_MUXING)) { ghb_cancel_encode(ud, _("You are currently encoding. " "What would you like to do?\n\n")); return; } count = ghb_array_len(ud->queue); for (ii = 0; ii < count; ii++) { queueDict = ghb_array_get(ud->queue, ii); uiDict = ghb_dict_get(queueDict, "uiSettings"); status = ghb_dict_get_int(uiDict, "job_status"); if ((status == GHB_QUEUE_RUNNING) || (status == GHB_QUEUE_PENDING)) { running = TRUE; break; } } if (!running) { // The queue has no running or pending jobs. // Add current settings to the queue, then run. if (!queue_add(ud, ud->settings, 0)) { return; } ghb_queue_selection_init(ud); // Validation of settings may have changed audio list ghb_audio_list_refresh_all(ud); } if (state == GHB_STATE_IDLE) { // Add the first pending queue item and start ghb_start_next_job(ud); } } G_MODULE_EXPORT void queue_pause_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud) { ghb_pause_resume_queue(); } static gboolean clear_select_all_busy = FALSE; G_MODULE_EXPORT void title_add_multiple_select_all_cb(GtkWidget *widget, signal_user_data_t *ud) { gint count, ii; GhbValue *settings; GtkWidget *row; GtkListBox *list; GtkToggleButton *selected; gboolean can_select; GtkToggleButton *clear_all; GtkToggleButton *select_all; if (!ghb_widget_boolean(widget)) return; clear_select_all_busy = TRUE; clear_all = GTK_TOGGLE_BUTTON(GHB_WIDGET(ud->builder, "title_add_multiple_clear_all")); select_all = GTK_TOGGLE_BUTTON(GHB_WIDGET(ud->builder, "title_add_multiple_select_all")); gtk_toggle_button_set_active(clear_all, FALSE); gtk_widget_set_sensitive(GTK_WIDGET(select_all), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(clear_all), TRUE); list = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "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 = GTK_TOGGLE_BUTTON(find_widget(row, "title_selected")); settings = ghb_array_get(ud->settings_array, ii); can_select = title_multiple_can_select(ud->settings_array, ii); ghb_dict_set_bool(settings, "title_selected", can_select); gtk_toggle_button_set_active(selected, TRUE); title_add_multiple_set_sensitive(GTK_WIDGET(row), can_select); } clear_select_all_busy = FALSE; } G_MODULE_EXPORT void title_add_multiple_clear_all_cb(GtkWidget *widget, signal_user_data_t *ud) { gint count, ii; GhbValue *settings; GtkWidget *row; GtkListBox *list; GtkToggleButton *selected; GtkToggleButton *clear_all; GtkToggleButton *select_all; if (!ghb_widget_boolean(widget)) return; clear_select_all_busy = TRUE; clear_all = GTK_TOGGLE_BUTTON(GHB_WIDGET(ud->builder, "title_add_multiple_clear_all")); select_all = GTK_TOGGLE_BUTTON(GHB_WIDGET(ud->builder, "title_add_multiple_select_all")); gtk_toggle_button_set_active(select_all, FALSE); gtk_widget_set_sensitive(GTK_WIDGET(select_all), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(clear_all), FALSE); list = GTK_LIST_BOX(GHB_WIDGET(ud->builder, "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 = GTK_TOGGLE_BUTTON(find_widget(row, "title_selected")); settings = ghb_array_get(ud->settings_array, ii); ghb_dict_set_bool(settings, "title_selected", FALSE); gtk_toggle_button_set_active(selected, FALSE); } clear_select_all_busy = FALSE; } G_MODULE_EXPORT void title_selected_cb(GtkWidget *widget, signal_user_data_t *ud) { GhbValue *settings; gboolean selected; GtkToggleButton *select_all; GtkToggleButton *clear_all; gboolean can_select; if (clear_select_all_busy) return; GtkListBoxRow * row = list_box_get_row(widget); if (row == NULL) return; clear_all = GTK_TOGGLE_BUTTON(GHB_WIDGET(ud->builder, "title_add_multiple_clear_all")); select_all = GTK_TOGGLE_BUTTON(GHB_WIDGET(ud->builder, "title_add_multiple_select_all")); gtk_toggle_button_set_active(select_all, FALSE); gtk_toggle_button_set_active(clear_all, FALSE); gtk_widget_set_sensitive(GTK_WIDGET(clear_all), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(select_all), TRUE); 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_multiple_can_select(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_multiple_can_select(ud->settings_array, index); ghb_dict_set_bool(settings, "title_selected", selected && can_select); title_add_multiple_set_sensitive(GTK_WIDGET(row), can_select); g_free(dest_file); g_free(dest); title_add_multiple_set_conflict_label(ud, title_add_multiple_are_conflicts(ud)); } G_MODULE_EXPORT void title_dest_dir_cb(GtkWidget *widget, 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_multiple_can_select(ud->settings_array, index); ghb_dict_set_bool(settings, "title_selected", selected && can_select); title_add_multiple_set_sensitive(GTK_WIDGET(row), can_select); g_free(dest_dir); g_free(dest); title_add_multiple_set_conflict_label(ud, title_add_multiple_are_conflicts(ud)); } // Set up view of row to be dragged G_MODULE_EXPORT void #if GTK_CHECK_VERSION(3, 90, 0) queue_drag_begin_cb(GtkWidget * widget, GdkDrag * context, signal_user_data_t * ud) #else queue_drag_begin_cb(GtkWidget * widget, GdkDragContext * context, signal_user_data_t * ud) #endif { GtkListBox * lb; GtkWidget * row; row = gtk_widget_get_ancestor(widget, GTK_TYPE_LIST_BOX_ROW); lb = GTK_LIST_BOX(gtk_widget_get_parent(row)); gtk_list_box_select_row(lb, GTK_LIST_BOX_ROW(row)); #if GTK_CHECK_VERSION(3, 90, 0) GdkPaintable * paintable = gtk_widget_paintable_new(row); gtk_drag_set_icon_paintable(context, paintable, 0, 0); g_object_unref(paintable); #else GtkAllocation alloc; cairo_surface_t * surface; cairo_t * cr; int x, y; gtk_widget_get_allocation(row, &alloc); surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, alloc.width, alloc.height); cr = cairo_create(surface); gtk_style_context_add_class(gtk_widget_get_style_context(row), "drag-icon"); gtk_widget_draw(row, cr); gtk_style_context_remove_class(gtk_widget_get_style_context(row), "drag-icon"); gtk_widget_translate_coordinates(widget, row, 0, 0, &x, &y); cairo_surface_set_device_offset(surface, -x, -y); gtk_drag_set_icon_surface(context, surface); cairo_destroy(cr); cairo_surface_destroy(surface); #endif g_object_set_data(G_OBJECT(gtk_widget_get_parent(row)), "drag-row", row); gtk_style_context_add_class(gtk_widget_get_style_context(row), "drag-row"); } G_MODULE_EXPORT void #if GTK_CHECK_VERSION(3, 90, 0) queue_drag_end_cb(GtkWidget * widget, GdkDrag * context, signal_user_data_t * ud) #else queue_drag_end_cb(GtkWidget * widget, GdkDragContext * context, signal_user_data_t * ud) #endif { GtkWidget * row; row = gtk_widget_get_ancestor(widget, GTK_TYPE_LIST_BOX_ROW); g_object_set_data(G_OBJECT(gtk_widget_get_parent(row)), "drag-row", NULL); gtk_style_context_remove_class(gtk_widget_get_style_context(row), "drag-row"); gtk_style_context_remove_class(gtk_widget_get_style_context(row), "drag-hover"); } // Set selection to the row being dragged G_MODULE_EXPORT void #if GTK_CHECK_VERSION(3, 90, 0) queue_drag_data_get_cb(GtkWidget * widget, GdkDrag * context, GtkSelectionData * selection_data, signal_user_data_t * ud) #else queue_drag_data_get_cb(GtkWidget * widget, GdkDragContext * context, GtkSelectionData * selection_data, guint info, guint time, signal_user_data_t * ud) #endif { GtkWidget * row; row = gtk_widget_get_ancestor(widget, GTK_TYPE_LIST_BOX_ROW); gtk_selection_data_set(selection_data, ghb_atom_string("GTK_LIST_BOX_ROW"), 32, (const guchar *)&row, sizeof (gpointer)); } G_MODULE_EXPORT void #if GTK_CHECK_VERSION(3, 90, 0) queue_drag_leave_cb( GtkListBox * lb, GdkDrop * ctx, signal_user_data_t * ud) #else queue_drag_leave_cb( GtkListBox * lb, GdkDragContext * ctx, guint time, signal_user_data_t * ud) #endif { GtkWidget * drag_row; GtkWidget * row_before; GtkWidget * row_after; drag_row = GTK_WIDGET(g_object_get_data(G_OBJECT(lb), "drag-row")); row_before = GTK_WIDGET(g_object_get_data(G_OBJECT(lb), "row-before")); row_after = GTK_WIDGET(g_object_get_data(G_OBJECT(lb), "row-after")); gtk_style_context_remove_class(gtk_widget_get_style_context(drag_row), "drag-hover"); if (row_before) { gtk_style_context_remove_class(gtk_widget_get_style_context(row_before), "drag-hover-bottom"); } if (row_after) { gtk_style_context_remove_class(gtk_widget_get_style_context(row_after), "drag-hover-top"); } } static GtkListBoxRow * get_last_row(GtkListBox * list) { int i; GtkListBoxRow * row; row = NULL; for (i = 0; ; i++) { GtkListBoxRow * tmp; tmp = gtk_list_box_get_row_at_index(list, i); if (tmp == NULL) return row; row = tmp; } return row; } static GtkListBoxRow * get_row_before (GtkListBox * list, GtkListBoxRow * row) { int pos = gtk_list_box_row_get_index(row); return gtk_list_box_get_row_at_index(list, pos - 1); } static GtkListBoxRow * get_row_after (GtkListBox * list, GtkListBoxRow * row) { int pos = gtk_list_box_row_get_index(row); return gtk_list_box_get_row_at_index(list, pos + 1); } G_MODULE_EXPORT gboolean #if GTK_CHECK_VERSION(3, 90, 0) queue_drag_motion_cb( GtkListBox * lb, GdkDrop * ctx, gint x, gint y, signal_user_data_t * ud) #else queue_drag_motion_cb( GtkListBox * lb, GdkDragContext * ctx, gint x, gint y, guint time, signal_user_data_t * ud) #endif { GtkAllocation alloc; GtkWidget * row; int hover_row_y; int hover_row_height; GtkWidget * drag_row; GtkWidget * row_before; GtkWidget * row_after; row = GTK_WIDGET(gtk_list_box_get_row_at_y(GTK_LIST_BOX(lb), y)); drag_row = GTK_WIDGET(g_object_get_data(G_OBJECT(lb), "drag-row")); row_before = GTK_WIDGET(g_object_get_data(G_OBJECT(lb), "row-before")); row_after = GTK_WIDGET(g_object_get_data(G_OBJECT(lb), "row-after")); gtk_style_context_remove_class(gtk_widget_get_style_context(drag_row), "drag-hover"); if (row_before) { gtk_style_context_remove_class(gtk_widget_get_style_context(row_before), "drag-hover-bottom"); } if (row_after) { gtk_style_context_remove_class(gtk_widget_get_style_context(row_after), "drag-hover-top"); } if (row) { gtk_widget_get_allocation(row, &alloc); hover_row_y = alloc.y; hover_row_height = alloc.height; if (y < hover_row_y + hover_row_height/2) { row_after = row; row_before = GTK_WIDGET(get_row_before(lb, GTK_LIST_BOX_ROW(row))); } else { row_before = row; row_after = GTK_WIDGET(get_row_after(lb, GTK_LIST_BOX_ROW(row))); } } else { row_before = GTK_WIDGET(get_last_row(lb)); row_after = NULL; } g_object_set_data(G_OBJECT(lb), "row-before", row_before); g_object_set_data(G_OBJECT(lb), "row-after", row_after); if (drag_row == row_before || drag_row == row_after) { gtk_style_context_add_class(gtk_widget_get_style_context(drag_row), "drag-hover"); return FALSE; } if (row_before) { gtk_style_context_add_class(gtk_widget_get_style_context(row_before), "drag-hover-bottom"); } if (row_after) { gtk_style_context_add_class(gtk_widget_get_style_context(row_after), "drag-hover-top"); } return TRUE; } G_MODULE_EXPORT void #if GTK_CHECK_VERSION(3, 90, 0) queue_drag_data_received_cb(GtkListBox * lb, GdkDrop * context, GtkSelectionData * selection_data, signal_user_data_t * ud) #else queue_drag_data_received_cb(GtkListBox * lb, GdkDragContext * context, gint x, gint y, GtkSelectionData * selection_data, guint info, guint32 time, signal_user_data_t * ud) #endif { GtkWidget * row_before; GtkWidget * row_after; GtkWidget * src_row; int src_index, dst_index; row_before = GTK_WIDGET(g_object_get_data(G_OBJECT(lb), "row-before")); row_after = GTK_WIDGET(g_object_get_data(G_OBJECT(lb), "row-after")); g_object_set_data(G_OBJECT(lb), "row-before", NULL); g_object_set_data(G_OBJECT(lb), "row-after", NULL); if (row_before) { gtk_style_context_remove_class(gtk_widget_get_style_context(row_before), "drag-hover-bottom"); } if (row_after) { gtk_style_context_remove_class(gtk_widget_get_style_context(row_after), "drag-hover-top"); } src_row = GTK_WIDGET(*(gpointer*)gtk_selection_data_get_data(selection_data)); if (src_row == row_after) { gtk_list_box_select_row(lb, GTK_LIST_BOX_ROW(src_row)); return; } src_index = gtk_list_box_row_get_index(GTK_LIST_BOX_ROW(src_row)); if (row_after) { dst_index = gtk_list_box_row_get_index(GTK_LIST_BOX_ROW(row_after)); } else { dst_index = gtk_list_box_row_get_index(GTK_LIST_BOX_ROW(row_before)) + 1; } if (src_index < dst_index) { // The source row is removed before re-inserting it elsewhere // in the list. If the source is before the dest, the dest position // moves dst_index -= 1; } g_object_ref(src_row); gtk_container_remove(GTK_CONTAINER(lb), src_row); gtk_list_box_insert(lb, src_row, dst_index); g_object_unref(src_row); GhbValue * queueDict; queueDict = ghb_array_get(ud->queue, src_index); ghb_value_incref(queueDict); ghb_array_remove(ud->queue, src_index); ghb_array_insert(ud->queue, dst_index, queueDict); // Force refresh of current selection gtk_list_box_unselect_row(lb, GTK_LIST_BOX_ROW(src_row)); gtk_list_box_select_row(lb, GTK_LIST_BOX_ROW(src_row)); ghb_save_queue(ud->queue); }