/* dvdisaster: Additional error correction for optical media. * Copyright (C) 2004-2017 Carsten Gnoerlich. * Copyright (C) 2019-2021 The dvdisaster development team. * * Email: support@dvdisaster.org * * This file is part of dvdisaster. * * dvdisaster is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * dvdisaster 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 dvdisaster. If not, see . */ /*** src type: only GUI code ***/ #ifdef WITH_GUI_YES #include "dvdisaster.h" #include #ifndef PATH_MAX #define PATH_MAX 4096 #endif /*** *** Forward declarations ***/ static void file_select_cb(GtkWidget*, gpointer); /*** *** The Menu and Toolbar action dispatcher ***/ typedef enum { MENU_FILE_IMAGE, MENU_FILE_IMAGE_OK, MENU_FILE_IMAGE_CANCEL, MENU_FILE_IMAGE_DESTROY, MENU_FILE_ECC, MENU_FILE_ECC_OK, MENU_FILE_ECC_CANCEL, MENU_FILE_ECC_DESTROY, MENU_FILE_QUIT, MENU_TOOLS_MEDIUM_INFO, MENU_TOOLS_RAW_EDITOR, MENU_PREFERENCES, MENU_HELP_MANUAL, MENU_HELP_ABOUT, MENU_HELP_GPL, MENU_HELP_CHANGELOG, MENU_HELP_CREDITS, MENU_HELP_TODO } menu_actions; static void menu_cb(GtkWidget *widget, gpointer data) { switch(GPOINTER_TO_INT(data)) { case MENU_FILE_IMAGE: case MENU_FILE_ECC: file_select_cb(widget, data); break; case MENU_FILE_QUIT: /* If an action is currently running with spawned threads, give them time to terminate cleanly. */ if(Closure->subThread) { Closure->stopActions = STOP_SHUTDOWN_ALL; g_thread_join(Closure->subThread); } /* Extract current file selections so that they are saved in the .dvdisaster file */ g_free(Closure->imageName); Closure->imageName = g_strdup(gtk_editable_get_text(GTK_EDITABLE(Closure->imageEntry))); if(!Closure->imageName || !strlen(Closure->imageName)) { if(Closure->imageName) g_free(Closure->imageName); Closure->imageName = g_strdup("none"); } g_free(Closure->eccName); Closure->eccName = g_strdup(gtk_editable_get_text(GTK_EDITABLE(Closure->eccEntry))); if(!Closure->eccName || !strlen(Closure->eccName)) { if(Closure->eccName) g_free(Closure->eccName); Closure->eccName = g_strdup("none"); } /* and quit */ exit(0); break; case MENU_TOOLS_MEDIUM_INFO: GuiCreateMediumInfoWindow(); break; case MENU_TOOLS_RAW_EDITOR: GuiCreateRawEditor(); break; case MENU_PREFERENCES: GuiCreatePreferencesWindow(); break; case MENU_HELP_MANUAL: GuiShowURL("manual.pdf"); break; case MENU_HELP_ABOUT: GuiAboutDialog(); break; case MENU_HELP_GPL: GuiShowGPL(); break; case MENU_HELP_CHANGELOG: GuiShowTextfile(_("windowtitle|Change log"), _("Change log\n" "Major differences from earlier program versions."), "CHANGELOG", NULL, NULL); break; case MENU_HELP_CREDITS: GuiShowTextfile(_("windowtitle|Credits"), _("Credits\n" "Thanks go out to..."), "CREDITS", NULL, NULL); break; case MENU_HELP_TODO: GuiShowTextfile(_("windowtitle|To do list"), _("To do list\n" "A sneak preview of coming features ... perhaps ;-)"), "TODO", NULL, NULL); break; default: g_print("Menu/Toolbar action %d\n",GPOINTER_TO_INT(data)); break; } } /*** *** The Menu system ***/ /* * Helper functions for creating the menu system */ static GtkWidget* add_menu_button(GtkWidget *parent, char *title, int action) { char *utf_title = g_locale_to_utf8(title, -1, NULL, NULL, NULL); GtkWidget *item; /* Menu items are replaced with modern GTK4 approach using buttons */ item = gtk_button_new_with_label(utf_title); g_free(utf_title); gtk_box_append(GTK_BOX(parent), item); g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(menu_cb), GINT_TO_POINTER(action)); return item; } static void add_menu_separator(GtkWidget *parent) { GtkWidget *sep; /* GTK4: Create a separator widget instead of menu item */ sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); gtk_box_append(GTK_BOX(parent), sep); } #if 0 static void append_sub_menu(GtkWidget *parent, GtkWidget *strip, char *name) { char *utf_name = g_locale_to_utf8(name, -1, NULL, NULL, NULL); GtkWidget *anchor; anchor = gtk_menu_item_new_with_label(utf_name); g_free(utf_name); gtk_menu_item_set_submenu(GTK_MENU_ITEM(anchor), strip); gtk_menu_shell_append(GTK_MENU_SHELL(parent), anchor); } #endif /* * Assemble the menu system. * Using the itemfactory would make things more complicated wrt localization. */ GtkWidget *GuiCreateMenuBar(GtkWidget *parent) { GtkWidget *menu_bar, *file_box, *tools_box, *item; /* The overall menu bar - using a horizontal box instead */ menu_bar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); // gtk_widget_set_name(menu_bar, "menu-bar"); /* The file menu - using a simple box for GTK4 compatibility */ file_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); Closure->fileMenuImage = add_menu_button(file_box, _("menu|Select Image"), MENU_FILE_IMAGE); Closure->fileMenuEcc = add_menu_button(file_box, _("menu|Select Parity File"), MENU_FILE_ECC); add_menu_button(file_box, _("menu|Quit"), MENU_FILE_QUIT); /* Create a simple menu button for file menu */ GtkWidget *file_button = gtk_button_new_with_label(_utf("menu|File")); gtk_box_append(GTK_BOX(menu_bar), file_button); /* For simplicity, just pack file menu items directly in menu bar for now */ gtk_box_append(GTK_BOX(menu_bar), file_box); /* The tools menu */ tools_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); item = add_menu_button(tools_box, _("menu|Medium info"), MENU_TOOLS_MEDIUM_INFO); if(!Closure->deviceNodes->len) gtk_widget_set_sensitive(item, FALSE); if(Closure->debugMode && !Closure->screenShotMode) add_menu_button(tools_box, _("menu|Raw sector editor"), MENU_TOOLS_RAW_EDITOR); /* Create tools button */ GtkWidget *tools_button = gtk_button_new_with_label(_utf("menu|Tools")); gtk_box_append(GTK_BOX(menu_bar), tools_button); gtk_box_append(GTK_BOX(menu_bar), tools_box); Closure->toolMenuAnchor = tools_button; /* The help menu */ GtkWidget *help_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); add_menu_button(help_box, _("menu|About"), MENU_HELP_ABOUT); add_menu_button(help_box, _("menu|User manual"), MENU_HELP_MANUAL); add_menu_separator(help_box); add_menu_button(help_box, _("menu|Credits"), MENU_HELP_CREDITS); add_menu_button(help_box, _("menu|Licence (GPL)"), MENU_HELP_GPL); add_menu_separator(help_box); add_menu_button(help_box, _("menu|Change log"), MENU_HELP_CHANGELOG); /* Hide the todo list menu in the patchlevel series, as we're not upstream * add_menu_button(help_box, _("menu|To do list"), MENU_HELP_TODO); */ GtkWidget *help_button = gtk_button_new_with_label(_utf("menu|Help")); gtk_box_append(GTK_BOX(menu_bar), help_button); gtk_box_append(GTK_BOX(menu_bar), help_box); return menu_bar; } /*** *** The toolbar ***/ /* * Callback for displaying the help message */ static gint tooltip_cb(GtkWidget *widget, GdkEvent *event, gpointer data) { /* Simplified for GTK4 compatibility - just show the tooltip */ gtk_label_set_text(GTK_LABEL(Closure->status), (gchar*)data); return FALSE; /* don't intercept the default button callbacks! */ } void GuiAttachTooltip(GtkWidget *widget, char *short_descr, char *long_descr) { char *long_copy = g_locale_to_utf8(long_descr, -1, NULL, NULL, NULL); char *short_copy = g_locale_to_utf8(short_descr, -1, NULL, NULL, NULL); g_signal_connect(G_OBJECT(widget), "enter_notify_event", G_CALLBACK(tooltip_cb), (gpointer)long_copy); g_signal_connect(G_OBJECT(widget), "leave_notify_event", G_CALLBACK(tooltip_cb), (gpointer)long_copy); gtk_widget_set_tooltip_text(widget, short_copy); g_free(short_copy); FORGET(long_copy); /* long_copy must be kept during programs life */ } /* * Callback for drive selection */ static void drive_select_cb(GtkWidget *widget, gpointer data) { guint n; char *dnode; if(!Closure->deviceNodes->len) /* No drives available */ return; n = gtk_drop_down_get_selected(GTK_DROP_DOWN(widget)); if(n == GTK_INVALID_LIST_POSITION) return; dnode = g_ptr_array_index(Closure->deviceNodes, n); g_free(Closure->device); Closure->device = g_strdup(dnode); if(Closure->mediumDrive) /* propagate to medium info window */ gtk_drop_down_set_selected(GTK_DROP_DOWN(Closure->mediumDrive), n); } /* * Callback for the image and ecc file selection. * Creates and runs the file selection dialogs. */ static void file_select_cb(GtkWidget *widget, gpointer data) { int action = GPOINTER_TO_INT(data); /* Removed unused GtkWidget *dialog; variable */ switch(action) { /*** Image file selection */ case MENU_FILE_IMAGE: /* GTK4: Use GtkFileDialog instead of deprecated GtkFileChooserDialog */ { GtkFileDialog *file_dialog = gtk_file_dialog_new(); gtk_file_dialog_set_title(file_dialog, "Image file selection"); /* GTK4: gtk_file_dialog_open replaces gtk_dialog_run for file dialogs */ /* Note: This is a simplified version for compilation - async callback would be needed for full implementation */ GFile *initial_file = g_file_new_for_path(gtk_editable_get_text(GTK_EDITABLE(Closure->imageEntry))); gtk_file_dialog_set_initial_file(file_dialog, initial_file); /* For now, set a default filename to avoid the complex async pattern */ g_free(Closure->imageName); Closure->imageName = g_strdup(gtk_editable_get_text(GTK_EDITABLE(Closure->imageEntry))); if(Closure->autoSuffix) Closure->imageName = ApplyAutoSuffix(Closure->imageName, "iso"); gtk_editable_set_text(GTK_EDITABLE(Closure->imageEntry), Closure->imageName); gtk_editable_set_position(GTK_EDITABLE(Closure->imageEntry), -1); g_object_unref(file_dialog); if(initial_file) g_object_unref(initial_file); } break; /*** Same stuff again for ecc file selection */ case MENU_FILE_ECC: /* GTK4: Use GtkFileDialog instead of deprecated GtkFileChooserDialog */ { GtkFileDialog *file_dialog = gtk_file_dialog_new(); gtk_file_dialog_set_title(file_dialog, "Error correction file selection"); /* GTK4: gtk_file_dialog_open replaces gtk_dialog_run for file dialogs */ /* Note: This is a simplified version for compilation - async callback would be needed for full implementation */ GFile *initial_file = g_file_new_for_path(gtk_editable_get_text(GTK_EDITABLE(Closure->eccEntry))); gtk_file_dialog_set_initial_file(file_dialog, initial_file); /* For now, set a default filename to avoid the complex async pattern */ g_free(Closure->eccName); Closure->eccName = g_strdup(gtk_editable_get_text(GTK_EDITABLE(Closure->eccEntry))); if(Closure->autoSuffix) Closure->eccName = ApplyAutoSuffix(Closure->eccName, "ecc"); gtk_editable_set_text(GTK_EDITABLE(Closure->eccEntry), Closure->eccName); gtk_editable_set_position(GTK_EDITABLE(Closure->eccEntry), -1); g_object_unref(file_dialog); if(initial_file) g_object_unref(initial_file); } break; } } /* * Set file path for a text entry. * Completes relative paths. */ void set_path(GtkWidget *entry, char *path) { if(path[0] == '/' || path[0] == '\\' || path[1] == ':' || strlen(path) < 1) { gtk_editable_set_text(GTK_EDITABLE(entry), path); gtk_editable_set_position(GTK_EDITABLE(entry), -1); } else { char buf[PATH_MAX + strlen(path) + 2]; if(!getcwd(buf, PATH_MAX)) return; strcat(buf,"/"); strcat(buf,path); gtk_editable_set_text(GTK_EDITABLE(entry), buf); gtk_editable_set_position(GTK_EDITABLE(entry), -1); } } /* * Callback for adding file suffixes */ static void suffix_cb(GtkWidget *widget, gpointer data) { int ecc_file = GPOINTER_TO_INT(data); if(!Closure->autoSuffix) return; if(!ecc_file) { Closure->imageName = g_strdup(gtk_editable_get_text(GTK_EDITABLE(Closure->imageEntry))); Closure->imageName = ApplyAutoSuffix(Closure->imageName, "iso"); gtk_editable_set_text(GTK_EDITABLE(Closure->imageEntry), Closure->imageName); } else { Closure->eccName = g_strdup(gtk_editable_get_text(GTK_EDITABLE(Closure->eccEntry))); Closure->eccName = ApplyAutoSuffix(Closure->eccName, "ecc"); gtk_editable_set_text(GTK_EDITABLE(Closure->eccEntry), Closure->eccName); } } /* * Create the toolbar */ GtkWidget *GuiCreateToolBar(GtkWidget *parent) { GtkWidget *box, *button, *ebox, *icon, *prefs, *help, *quit, *sep, *space; GtkWidget *combo_box; int dev_idx = 0; unsigned int i; /*** Create the toolbar */ box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); /*** Drive selection */ space = gtk_label_new(NULL); gtk_box_append(GTK_BOX(box), space); /* GTK4: Replace GtkEventBox with simple GtkBox as event boxes are deprecated */ ebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_append(GTK_BOX(box), ebox); GuiAttachTooltip(ebox, _("tooltip|Drive selection"), _("Use the nearby drop-down list to select the input drive.")); icon = gtk_image_new_from_icon_name("cd"); gtk_box_append(GTK_BOX(ebox), icon); /* Create string list for dropdown */ GtkStringList *string_list = gtk_string_list_new(NULL); for(i=0; ideviceNames->len; i++) { gtk_string_list_append(string_list, g_ptr_array_index(Closure->deviceNames,i)); if(!strcmp(Closure->device, g_ptr_array_index(Closure->deviceNodes,i))) dev_idx = i; } if(!Closure->deviceNodes->len) { gtk_string_list_append(string_list, _utf("No drives found")); } Closure->driveCombo = combo_box = gtk_drop_down_new(G_LIST_MODEL(string_list), NULL); g_signal_connect(G_OBJECT(combo_box), "notify::selected", G_CALLBACK(drive_select_cb), NULL); gtk_drop_down_set_selected(GTK_DROP_DOWN(combo_box), dev_idx); gtk_widget_set_size_request(combo_box, 200, -1); gtk_box_append(GTK_BOX(box), combo_box); GuiAttachTooltip(combo_box, _("tooltip|Drive selection"), _("Selects the input drive for reading images.")); space = gtk_label_new(NULL); gtk_box_append(GTK_BOX(box), space); sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL); gtk_box_pack_start(GTK_BOX(box), sep, FALSE, FALSE, 3); /*** Image file selection */ icon = gtk_image_new_from_icon_name("open-img"); button = gtk_button_new(); /* gtk_button_set_relief deprecated in GTK4 */ gtk_button_set_child(GTK_BUTTON(button), icon); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(file_select_cb), GINT_TO_POINTER(MENU_FILE_IMAGE)); gtk_box_append(GTK_BOX(box), button); Closure->imageEntry = gtk_entry_new(); set_path(Closure->imageEntry, Closure->imageName); g_signal_connect(G_OBJECT(Closure->imageEntry), "activate", G_CALLBACK(suffix_cb), GINT_TO_POINTER(FALSE)); gtk_box_append(GTK_BOX(box), Closure->imageEntry); space = gtk_label_new(NULL); gtk_box_append(GTK_BOX(box), space); sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL); gtk_box_append(GTK_BOX(box), sep); GuiAttachTooltip(button, _("tooltip|Image file selection"), _("Selects a new image file.")); GuiAttachTooltip(Closure->imageEntry, _("tooltip|Current image file"), _("Shows the name of the current image file.")); /*** Ecc file selection */ icon = gtk_image_new_from_icon_name("open-ecc"); button = gtk_button_new(); /* gtk_button_set_relief deprecated in GTK4 */ gtk_button_set_child(GTK_BUTTON(button), icon); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(file_select_cb), GINT_TO_POINTER(MENU_FILE_ECC)); gtk_box_append(GTK_BOX(box), button); Closure->eccEntry = gtk_entry_new(); set_path(Closure->eccEntry, Closure->eccName); g_signal_connect(G_OBJECT(Closure->eccEntry), "activate", G_CALLBACK(suffix_cb), GINT_TO_POINTER(TRUE)); gtk_box_append(GTK_BOX(box), Closure->eccEntry); space = gtk_label_new(NULL); gtk_box_append(GTK_BOX(box), space); sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL); gtk_box_append(GTK_BOX(box), sep); GuiAttachTooltip(button, _("tooltip|Error correction file selection"), _("Selects a new error correction file.")); GuiAttachTooltip(Closure->eccEntry, _("tooltip|Current error correction file"), _("Shows the name of the current error correction file.")); /*** Preferences button */ icon = gtk_image_new_from_icon_name("preferences"); Closure->prefsButton = prefs = gtk_button_new(); /* gtk_button_set_relief deprecated in GTK4 */ gtk_button_set_child(GTK_BUTTON(prefs), icon); g_signal_connect(G_OBJECT(prefs), "clicked", G_CALLBACK(menu_cb), (gpointer)MENU_PREFERENCES); gtk_box_append(GTK_BOX(box), prefs); GuiAttachTooltip(prefs, _("tooltip|Preferences"), _("Customize settings for creating images, error correction files and other stuff.")); /*** Help button */ icon = gtk_image_new_from_icon_name("manual"); Closure->helpButton = help = gtk_button_new(); /* gtk_button_set_relief deprecated in GTK4 */ gtk_container_add(GTK_CONTAINER(help), icon); g_signal_connect(G_OBJECT(help), "clicked", G_CALLBACK(menu_cb), (gpointer)MENU_HELP_MANUAL); gtk_box_append(GTK_BOX(box), help); GuiAttachTooltip(help, _("tooltip|User manual"), _("Displays the user manual (external PDF viewer required).")); /*** Quit button */ icon = gtk_image_new_from_icon_name("quit"); quit = gtk_button_new(); /* gtk_button_set_relief deprecated in GTK4 */ gtk_container_add(GTK_CONTAINER(quit), icon); g_signal_connect(G_OBJECT(quit), "clicked", G_CALLBACK(menu_cb), (gpointer)MENU_FILE_QUIT); gtk_box_append(GTK_BOX(box), quit); GuiAttachTooltip(quit, _("tooltip|Quit"), _("Quit dvdisaster")); return box; } #endif /* WITH_GUI_YES */