// multiplatform implementation of system dialogs // // 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git // is the prefered solution, but it is not compatible with linux snap packaging // because of 'zenity' access rights nightmare :( // Thus this re-implementation of native GTK+ dialogs for linux #if defined(LINUX) #define USE_TINYFILEDIALOG 0 #else #define USE_TINYFILEDIALOG 1 #endif #include "defines.h" #include "SystemToolkit.h" #include "DialogToolkit.h" #if USE_TINYFILEDIALOG #include "tinyfiledialogs.h" #else #include #include static bool gtk_init_ok = false; static int window_x = -1, window_y = -1; void add_filter_any_file_dialog( GtkWidget *dialog) { /* append a wildcard option */ GtkFileFilter *filter; filter = gtk_file_filter_new(); gtk_file_filter_set_name( filter, "Any file" ); gtk_file_filter_add_pattern( filter, "*" ); gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter ); } void add_filter_file_dialog( GtkWidget *dialog, int const countfilterPatterns, char const * const * const filterPatterns, char const * const filterDescription) { GtkFileFilter *filter; filter = gtk_file_filter_new(); gtk_file_filter_set_name( filter, filterDescription ); for (int i = 0; i < countfilterPatterns; i++ ) gtk_file_filter_add_pattern( filter, filterPatterns[i] ); gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter ); } void wait_for_event(void) { while ( gtk_events_pending() ) gtk_main_iteration(); } bool gtk_init() { if (!gtk_init_ok) { if ( gtk_init_check( NULL, NULL ) ) gtk_init_ok = true; } return gtk_init_ok; } #endif std::string DialogToolkit::saveSessionFileDialog(const std::string &path) { std::string filename = ""; char const * save_pattern[1] = { "*.mix" }; #if USE_TINYFILEDIALOG char const * save_file_name; save_file_name = tinyfd_saveFileDialog( "Save a session file", path.c_str(), 1, save_pattern, "vimix session"); if (save_file_name) filename = std::string(save_file_name); #else if (!gtk_init()) { ErrorDialog("Could not initialize GTK+ for dialog"); return filename; } GtkWidget *dialog = gtk_file_chooser_dialog_new( "Save Session File", NULL, GTK_FILE_CHOOSER_ACTION_SAVE, "_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL ); gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE ); // set file filters add_filter_file_dialog(dialog, 1, save_pattern, "vimix session"); add_filter_any_file_dialog(dialog); // Set the default path gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), path.c_str() ); // ensure front and centered gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); if (window_x > 0 && window_y > 0) gtk_window_move( GTK_WINDOW(dialog), window_x, window_y); // display and get filename if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { char *save_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); if (save_file_name) filename = std::string(save_file_name); g_free( save_file_name ); } // remember position gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y); // done gtk_widget_destroy(dialog); wait_for_event(); #endif std::string extension = filename.substr(filename.find_last_of(".") + 1); if (!filename.empty() && extension != "mix") filename += ".mix"; return filename; } std::string DialogToolkit::openSessionFileDialog(const std::string &path) { std::string filename = ""; std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); char const * open_pattern[1] = { "*.mix" }; #if USE_TINYFILEDIALOG char const * open_file_name; open_file_name = tinyfd_openFileDialog( "Import a file", startpath.c_str(), 1, open_pattern, "vimix session", 0); if (open_file_name) filename = std::string(open_file_name); #else if (!gtk_init()) { ErrorDialog("Could not initialize GTK+ for dialog"); return filename; } GtkWidget *dialog = gtk_file_chooser_dialog_new( "Open Session File", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL ); // set file filters add_filter_file_dialog(dialog, 1, open_pattern, "vimix session"); add_filter_any_file_dialog(dialog); // Set the default path gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() ); // ensure front and centered gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); if (window_x > 0 && window_y > 0) gtk_window_move( GTK_WINDOW(dialog), window_x, window_y); // display and get filename if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); if (open_file_name) filename = std::string(open_file_name); g_free( open_file_name ); } // remember position gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y); // done gtk_widget_destroy(dialog); wait_for_event(); #endif return filename; } std::string DialogToolkit::openMediaFileDialog(const std::string &path) { std::string filename = ""; std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); char const * open_pattern[18] = { "*.mix", "*.mp4", "*.mpg", "*.avi", "*.mov", "*.mkv", "*.webm", "*.mod", "*.wmv", "*.mxf", "*.ogg", "*.flv", "*.asf", "*.jpg", "*.png", "*.gif", "*.tif", "*.svg" }; #if USE_TINYFILEDIALOG char const * open_file_name; open_file_name = tinyfd_openFileDialog( "Open Media File", startpath.c_str(), 18, open_pattern, "All supported formats", 0); if (open_file_name) filename = std::string(open_file_name); #else if (!gtk_init()) { ErrorDialog("Could not initialize GTK+ for dialog"); return filename; } GtkWidget *dialog = gtk_file_chooser_dialog_new( "Open Media File", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL ); // set file filters add_filter_file_dialog(dialog, 18, open_pattern, "All supported formats"); add_filter_any_file_dialog(dialog); // Set the default path gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() ); // ensure front and centered gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); if (window_x > 0 && window_y > 0) gtk_window_move( GTK_WINDOW(dialog), window_x, window_y); // display and get filename if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); if (open_file_name) filename = std::string(open_file_name); g_free( open_file_name ); } // remember position gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y); // done gtk_widget_destroy(dialog); wait_for_event(); #endif return filename; } std::string DialogToolkit::openFolderDialog(const std::string &path) { std::string foldername = ""; std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); #if USE_TINYFILEDIALOG char const * open_folder_name; open_folder_name = tinyfd_selectFolderDialog("Select folder", startpath.c_str()); if (open_folder_name) foldername = std::string(open_folder_name); #else if (!gtk_init()) { ErrorDialog("Could not initialize GTK+ for dialog"); return foldername; } GtkWidget *dialog = gtk_file_chooser_dialog_new( "Select folder", NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, "_Cancel", GTK_RESPONSE_CANCEL, "_Select", GTK_RESPONSE_ACCEPT, NULL ); gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE ); // Set the default path gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() ); // ensure front and centered gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); if (window_x > 0 && window_y > 0) gtk_window_move( GTK_WINDOW(dialog), window_x, window_y); // display and get filename if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { char *open_folder_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); if (open_folder_name) foldername = std::string(open_folder_name); g_free( open_folder_name ); } // remember position gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y); // done gtk_widget_destroy(dialog); wait_for_event(); #endif return foldername; } std::list DialogToolkit::selectImagesFileDialog(const std::string &path) { std::list files; std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); char const * open_pattern[6] = { "*.tif", "*.jpg", "*.png", "*.TIF", "*.JPG", "*.PNG" }; #if USE_TINYFILEDIALOG char const * open_file_names; open_file_names = tinyfd_openFileDialog( "Select images", startpath.c_str(), 6, open_pattern, "Images", 1); if (open_file_names) { const std::string& str (open_file_names); const std::string& delimiters = "|"; // Skip delimiters at beginning. std::string::size_type lastPos = str.find_first_not_of(delimiters, 0); // Find first non-delimiter. std::string::size_type pos = str.find_first_of(delimiters, lastPos); while (std::string::npos != pos || std::string::npos != lastPos) { // Found a token, add it to the vector. files.push_back(str.substr(lastPos, pos - lastPos)); // Skip delimiters. lastPos = str.find_first_not_of(delimiters, pos); // Find next non-delimiter. pos = str.find_first_of(delimiters, lastPos); } } #else if (!gtk_init()) { ErrorDialog("Could not initialize GTK+ for dialog"); return files; } GtkWidget *dialog = gtk_file_chooser_dialog_new( "Select images", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL ); // set file filters add_filter_file_dialog(dialog, 6, open_pattern, "All supported formats"); add_filter_any_file_dialog(dialog); // multiple files gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), true ); // Set the default path gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() ); // ensure front and centered gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); if (window_x > 0 && window_y > 0) gtk_window_move( GTK_WINDOW(dialog), window_x, window_y); // display and get filename if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { GSList *open_file_names = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) ); while (open_file_names) { files.push_back( (char *) open_file_names->data ); open_file_names = open_file_names->next; // g_free( open_file_names->data ); } g_slist_free( open_file_names ); } // remember position gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y); // done gtk_widget_destroy(dialog); wait_for_event(); #endif return files; } void DialogToolkit::ErrorDialog(const char* message) { #if USE_TINYFILEDIALOG tinyfd_messageBox( APP_TITLE, message, "ok", "error", 0); #else if (!gtk_init()) { return; } GtkWidget *dialog = gtk_message_dialog_new( NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Error: %s", message); gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); static int x = 0, y = 0; if (x != 0) gtk_window_move( GTK_WINDOW(dialog), x, y); // show gtk_dialog_run( GTK_DIALOG(dialog) ); // remember position gtk_window_get_position( GTK_WINDOW(dialog), &x, &y); // done gtk_widget_destroy( dialog ); wait_for_event(); #endif }