org.eclipse.swt.widgets.FileDialog Maven / Gradle / Ivy
Show all versions of org.eclipse.swt.gtk.linux.ppc64le Show documentation
/*******************************************************************************
* Copyright (c) 2000, 2024 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.widgets;
import java.io.*;
import java.util.*;
import org.eclipse.swt.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.gtk.*;
import org.eclipse.swt.internal.gtk3.*;
import org.eclipse.swt.internal.gtk4.*;
/**
* Instances of this class allow the user to navigate
* the file system and select or enter a file name.
*
* - Styles:
* - SAVE, OPEN, MULTI
* - Events:
* - (none)
*
*
* Note: Only one of the styles SAVE and OPEN may be specified.
*
* IMPORTANT: This class is not intended to be subclassed.
*
*
* @see FileDialog snippets
* @see SWT Example: ControlExample, Dialog tab
* @see Sample code and further information
* @noextend This class is not intended to be subclassed by clients.
*/
public class FileDialog extends Dialog {
String [] filterNames = new String [0];
String [] filterExtensions = new String [0];
String filterPath = "";
String fileName = "";
String[] fileNames = new String [0];
String fullPath = "";
int filterIndex = -1;
boolean overwrite = false;
boolean uriMode;
long handle;
static final char SEPARATOR = File.separatorChar;
static final char EXTENSION_SEPARATOR = ';';
static final char FILE_EXTENSION_SEPARATOR = '.';
/**
* Constructs a new instance of this class given only its parent.
*
* @param parent a shell which will be the parent of the new instance
*
* @exception IllegalArgumentException
* - ERROR_NULL_ARGUMENT - if the parent is null
*
* @exception SWTException
* - ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent
* - ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass
*
*/
public FileDialog (Shell parent) {
this (parent, SWT.APPLICATION_MODAL);
}
/**
* Constructs a new instance of this class given its parent
* and a style value describing its behavior and appearance.
*
* The style value is either one of the style constants defined in
* class SWT
which is applicable to instances of this
* class, or must be built by bitwise OR'ing together
* (that is, using the int
"|" operator) two or more
* of those SWT
style constants. The class description
* lists the style constants that are applicable to the class.
* Style bits are also inherited from superclasses.
*
*
* @param parent a shell which will be the parent of the new instance
* @param style the style of dialog to construct
*
* @exception IllegalArgumentException
* - ERROR_NULL_ARGUMENT - if the parent is null
*
* @exception SWTException
* - ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent
* - ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass
*
*
* @see SWT#SAVE
* @see SWT#OPEN
* @see SWT#MULTI
*/
public FileDialog (Shell parent, int style) {
super (parent, checkStyle (parent, style));
checkSubclass ();
}
String computeResultChooserDialog (long file) {
/* MULTI is only valid if the native dialog's action is Open */
fullPath = null;
if ((style & SWT.MULTI) != 0) {
long list = 0;
if (GTK.GTK4) {
list = file;
} else {
if (uriMode) {
list = GTK3.gtk_file_chooser_get_uris (handle);
} else {
list = GTK3.gtk_file_chooser_get_filenames (handle);
}
}
int listLength;
if(GTK.GTK4) listLength = OS.g_list_model_get_n_items(list);
else listLength = OS.g_slist_length (list);
fileNames = new String [listLength];
long current = list;
int writePos = 0;
for (int i = 0; i < listLength; i++) {
long name;
if(GTK.GTK4) name = OS.g_list_model_get_item(list, i);
else name = OS.g_slist_data (current);
long utf8Ptr = 0;
if (uriMode) {
if (GTK.GTK4) {
utf8Ptr = OS.g_file_get_uri(name);
} else {
utf8Ptr = name;
}
} else {
if (GTK.GTK4) {
name = OS.g_file_get_path(name);
}
utf8Ptr = OS.g_filename_to_utf8 (name, -1, null, null, null);
if (utf8Ptr == 0) utf8Ptr = OS.g_filename_display_name (name);
}
if (name != utf8Ptr) OS.g_free (name);
if (utf8Ptr != 0) {
long [] items_written = new long [1];
long utf16Ptr = OS.g_utf8_to_utf16 (utf8Ptr, -1, null, items_written, null);
OS.g_free (utf8Ptr);
if (utf16Ptr != 0) {
int clength = (int)items_written [0];
char [] chars = new char [clength];
C.memmove (chars, utf16Ptr, clength * 2);
OS.g_free (utf16Ptr);
fullPath = new String (chars);
fileNames [writePos++] = fullPath.substring (fullPath.lastIndexOf (SEPARATOR) + 1);
}
}
if(!GTK.GTK4) current = OS.g_slist_next (current);
}
if (writePos != 0 && writePos != listLength) {
String [] validFileNames = new String [writePos];
System.arraycopy (fileNames, 0, validFileNames, 0, writePos);
fileNames = validFileNames;
}
if(GTK.GTK4) OS.g_object_unref(list);
else OS.g_slist_free (list);
} else {
long utf8Ptr = 0;
if (uriMode) {
if (GTK.GTK4) {
utf8Ptr = OS.g_file_get_uri(file);
} else {
utf8Ptr = GTK3.gtk_file_chooser_get_uri (handle);
}
} else {
long path;
if (GTK.GTK4) {
path = OS.g_file_get_path(file);
} else {
path = GTK3.gtk_file_chooser_get_filename (handle);
}
if (path != 0) {
utf8Ptr = OS.g_filename_to_utf8 (path, -1, null, null, null);
if (utf8Ptr == 0) utf8Ptr = OS.g_filename_display_name (path);
if (path != utf8Ptr) OS.g_free (path);
}
}
if (utf8Ptr != 0) {
long [] items_written = new long [1];
long utf16Ptr = OS.g_utf8_to_utf16 (utf8Ptr, -1, null, items_written, null);
OS.g_free (utf8Ptr);
if (utf16Ptr != 0) {
int clength = (int)items_written [0];
char [] chars = new char [clength];
C.memmove (chars, utf16Ptr, clength * 2);
OS.g_free (utf16Ptr);
fullPath = new String (chars);
fileNames = new String [1];
fileNames[0] = fullPath.substring (fullPath.lastIndexOf (SEPARATOR) + 1);
}
}
}
filterIndex = -1;
long filter;
if (GTK.GTK4) {
filter = GTK4.gtk_file_dialog_get_default_filter(handle);
} else {
filter = GTK3.gtk_file_chooser_get_filter (handle);
}
if (filter != 0) {
long filterNamePtr = GTK.gtk_file_filter_get_name (filter);
if (filterNamePtr != 0) {
int length = C.strlen (filterNamePtr);
byte[] buffer = new byte [length];
C.memmove (buffer, filterNamePtr, length);
//OS.g_free (filterNamePtr); //GTK owns this pointer - do not free
String filterName = new String (Converter.mbcsToWcs (buffer));
for (int i = 0; i < filterExtensions.length; i++) {
if (filterNames.length > 0) {
if (filterNames[i].equals(filterName)) {
filterIndex = i;
break;
}
} else {
if (filterExtensions[i].equals(filterName)) {
filterIndex = i;
break;
}
}
}
}
}
if (fullPath != null) {
int separatorIndex = fullPath.lastIndexOf (SEPARATOR);
fileName = fullPath.substring (separatorIndex + 1);
filterPath = fullPath.substring (0, separatorIndex);
}
return fullPath;
}
/**
* Returns the path of the first file that was
* selected in the dialog relative to the filter path, or an
* empty string if no such file has been selected.
*
* @return the relative path of the file
*/
public String getFileName () {
return fileName;
}
/**
* Returns a (possibly empty) array with the paths of all files
* that were selected in the dialog relative to the filter path.
*
* @return the relative paths of the files
*/
public String [] getFileNames () {
return fileNames;
}
/**
* Returns the file extensions which the dialog will
* use to filter the files it shows.
*
* @return the file extensions filter
*/
public String [] getFilterExtensions () {
return filterExtensions;
}
/**
* Get the 0-based index of the file extension filter
* which was selected by the user, or -1 if no filter
* was selected.
*
* This is an index into the FilterExtensions array and
* the FilterNames array.
*
*
* @return index the file extension filter index
*
* @see #getFilterExtensions
* @see #getFilterNames
*
* @since 3.4
*/
public int getFilterIndex () {
return filterIndex;
}
/**
* Returns the names that describe the filter extensions
* which the dialog will use to filter the files it shows.
*
* @return the list of filter names
*/
public String [] getFilterNames () {
return filterNames;
}
/**
* Returns the directory path that the dialog will use, or an empty
* string if this is not set. File names in this path will appear
* in the dialog, filtered according to the filter extensions.
*
* @return the directory path string
*
* @see #setFilterExtensions
*/
public String getFilterPath () {
return filterPath;
}
/**
* Returns the flag that the dialog will use to
* determine whether to prompt the user for file
* overwrite if the selected file already exists.
*
* @return true if the dialog will prompt for file overwrite, false otherwise
*
* @since 3.4
*/
public boolean getOverwrite () {
return overwrite;
}
/**
* Makes the dialog visible and brings it to the front
* of the display.
*
* @return a string describing the absolute path of the first selected file,
* or null if the dialog was cancelled or an error occurred
*
* @exception SWTException
* - ERROR_WIDGET_DISPOSED - if the dialog has been disposed
* - ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the dialog
*
*/
public String open () {
try {
return openDialog().orElse(null);
} catch (SWTException e) {
if (e.code == SWT.ERROR_INVALID_RETURN_VALUE) {
return null;
}
throw e;
}
}
/**
* Makes the dialog visible and brings it to the front of the display.
* Equal to {@link FileDialog#open()} but also exposes for state information like user cancellation.
*
* @return an Optional that either contains the absolute path of the first selected file
* or is empty in case the dialog was canceled
*
* @exception SWTException
* - ERROR_WIDGET_DISPOSED - if the dialog has been disposed
* - ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the dialog
* - ERROR_INVALID_RETURN_VALUE - if the dialog was not cancelled and did not return a valid path
*
*
* @since 3.126
*/
public Optional openDialog () {
return openNativeChooserDialog();
}
/**
* Open the file chooser dialog using the GtkFileChooserNative API (GTK3.20+) for running applications
* without direct filesystem access (such as Flatpak). API for GtkFileChoosernative does not
* give access to any GtkWindow or GtkWidget for the dialog, thus this method omits calls that
* requires such access. These are be handled by the GtkNativeDialog API.
*
* @return an Optional that contains a string describing the absolute path of the first selected file
* or is empty if the dialog is canceled
*/
Optional openNativeChooserDialog () {
byte [] titleBytes = Converter.wcsToMbcs (title, true);
int action = (style & SWT.SAVE) != 0 ? GTK.GTK_FILE_CHOOSER_ACTION_SAVE : GTK.GTK_FILE_CHOOSER_ACTION_OPEN;
long shellHandle = parent.topHandle();
Display display = parent != null ? parent.getDisplay (): Display.getCurrent();
if (GTK.GTK4) {
handle = GTK4.gtk_file_dialog_new();
GTK4.gtk_file_dialog_set_title(handle, titleBytes);
} else {
handle = GTK3.gtk_file_chooser_native_new(titleBytes, shellHandle, action, null, null);
}
if (handle == 0) error (SWT.ERROR_NO_HANDLES);
if (uriMode && !GTK.GTK4) {
// GTK4 file chooser works on GFiles and does not need to worry about this
GTK3.gtk_file_chooser_set_local_only (handle, false);
}
presetChooserDialog ();
display.addIdleProc ();
int signalId = 0;
long hookId = 0;
if ((style & SWT.RIGHT_TO_LEFT) != 0) {
signalId = OS.g_signal_lookup (OS.map, GTK.GTK_TYPE_WIDGET());
hookId = OS.g_signal_add_emission_hook (signalId, 0, display.emissionProc, handle, 0);
}
int response;
long file = 0;
if (GTK.GTK4) {
if ((style & SWT.MULTI) != 0) {
file = SyncDialogUtil.run(display,
asyncCallback -> GTK4.gtk_file_dialog_open_multiple(handle, shellHandle, 0, asyncCallback, 0),
asyncResult -> GTK4.gtk_file_dialog_open_multiple_finish(handle, asyncResult, null));
} else {
if ((style & SWT.SAVE) != 0) {
file = SyncDialogUtil.run(display,
asyncCallback -> GTK4.gtk_file_dialog_save(handle, shellHandle, 0, asyncCallback, 0),
asyncResult -> GTK4.gtk_file_dialog_save_finish(handle, asyncResult, null));
} else {
file = SyncDialogUtil.run(display,
asyncCallback -> GTK4.gtk_file_dialog_open(handle, shellHandle, 0, asyncCallback, 0),
asyncResult -> GTK4.gtk_file_dialog_open_finish(handle, asyncResult, null));
}
}
response = file != 0 ? GTK.GTK_RESPONSE_ACCEPT : GTK.GTK_RESPONSE_CANCEL;
} else {
display.externalEventLoop = true;
display.sendPreExternalEventDispatchEvent ();
response = GTK3.gtk_native_dialog_run(handle);
display.externalEventLoop = false;
display.sendPostExternalEventDispatchEvent ();
}
if ((style & SWT.RIGHT_TO_LEFT) != 0) {
OS.g_signal_remove_emission_hook (signalId, hookId);
}
Optional result = Optional.empty();
if (response == GTK.GTK_RESPONSE_ACCEPT) {
result = Optional.ofNullable(computeResultChooserDialog (file));
}
display.removeIdleProc ();
OS.g_object_unref(handle);
if (result.isPresent() || response == GTK.GTK_RESPONSE_CANCEL) {
return result;
}
throw new SWTException(SWT.ERROR_INVALID_RETURN_VALUE);
}
void presetChooserDialog () {
/* MULTI is only valid if the native dialog's action is Open */
if (!GTK.GTK4 && (style & (SWT.SAVE | SWT.MULTI)) == SWT.MULTI) {
GTK3.gtk_file_chooser_set_select_multiple (handle, true);
}
if (filterPath == null) filterPath = "";
if (fileName == null) fileName = "";
if ((style & SWT.SAVE) != 0) {
if (fileName.equals ("")) {
fileName = "Untitled";
}
if (filterPath.length () > 0) {
if (uriMode) {
byte [] buffer = Converter.wcsToMbcs (filterPath, true);
if (GTK.GTK4) {
long file = OS.g_file_new_for_uri(buffer);
GTK4.gtk_file_dialog_set_initial_folder(handle, file);
OS.g_object_unref(file);
} else {
GTK3.gtk_file_chooser_set_current_folder_uri (handle, buffer);
}
} else {
/* filename must be a full path */
byte [] buffer = Converter.wcsToMbcs (SEPARATOR + filterPath, true);
/*
* in GTK version 2.10, gtk_file_chooser_set_current_folder requires path
* to be true canonical path. So using realpath to convert the path to
* true canonical path.
*/
long ptr = OS.realpath (buffer, null);
if (ptr != 0) {
if (GTK.GTK4) {
long file = OS.g_file_new_for_path(buffer);
GTK4.gtk_file_dialog_set_initial_folder(handle, file);
OS.g_object_unref(file);
} else {
GTK3.gtk_file_chooser_set_current_folder (handle, ptr);
}
OS.g_free (ptr);
}
}
}
if (fileName.length () > 0) {
StringBuilder filenameWithExt = new StringBuilder();
filenameWithExt.append (fileName);
if ((fileName.lastIndexOf (FILE_EXTENSION_SEPARATOR) == -1) &&
(filterExtensions.length != 0)) { // Filename doesn't contain the extension and user has provided filter extensions
String selectedFilter = null;
if (this.filterIndex == -1) {
selectedFilter = filterExtensions[0];
} else {
selectedFilter = filterExtensions[filterIndex];
}
String extFilter = null;
int index = selectedFilter.indexOf (EXTENSION_SEPARATOR);
if (index == -1) {
extFilter = selectedFilter.trim ();
} else {
extFilter = selectedFilter.substring (0, index).trim ();
}
int separatorIndex = extFilter.lastIndexOf (FILE_EXTENSION_SEPARATOR);
String extension = extFilter.substring (separatorIndex);
if (!isGlobPattern (extension)) { //if the extension is of type glob pattern we should not add the extension
filenameWithExt.append (extension);
}
}
byte [] buffer = Converter.wcsToMbcs (filenameWithExt.toString (), true);
if (GTK.GTK4) {
GTK4.gtk_file_dialog_set_initial_name(handle, buffer);
} else {
GTK3.gtk_file_chooser_set_current_name (handle, buffer);
}
}
} else {
StringBuilder stringBuilder = new StringBuilder();
if (filterPath.length () > 0) {
if (!uriMode) {
/* filename must be a full path */
stringBuilder.append(SEPARATOR);
}
stringBuilder.append(filterPath);
stringBuilder.append(SEPARATOR);
}
if (fileName.length () > 0) {
stringBuilder.append(fileName);
}
byte [] buffer = Converter.wcsToMbcs (stringBuilder.toString(), true);
if (GTK.GTK4) {
long file;
if (uriMode) {
file = OS.g_file_new_for_uri(buffer);
GTK4.gtk_file_dialog_set_initial_file(handle, file);
} else {
file = OS.g_file_new_for_path(buffer);
if (fileName.length() > 0) {
GTK4.gtk_file_dialog_set_initial_file(handle, file);
}
}
OS.g_object_unref(file);
} else {
if (uriMode) {
GTK3.gtk_file_chooser_set_uri (handle, buffer);
} else {
/*
* in GTK version 2.10, gtk_file_chooser_set_current_folder requires path
* to be true canonical path. So using realpath to convert the path to
* true canonical path.
*/
long ptr = OS.realpath (buffer, null);
if (ptr != 0) {
if (fileName.length() > 0) {
GTK3.gtk_file_chooser_set_filename (handle, ptr);
} else {
GTK3.gtk_file_chooser_set_current_folder (handle, ptr);
}
OS.g_free (ptr);
}
}
}
}
/* Set overwrite mode */
if ((style & SWT.SAVE) != 0) {
if (GTK.GTK4) {
// TODO: GTK4 does not this property for the file chooser, not sure what the default behavior is. Must test before trying an alternative.
} else {
GTK3.gtk_file_chooser_set_do_overwrite_confirmation (handle, overwrite);
}
}
/* Set the extension filters */
if (filterNames == null) filterNames = new String [0];
if (filterExtensions == null) filterExtensions = new String [0];
long initialFilter = 0;
long fileFilters = 0;
if (GTK.GTK4) {
fileFilters = OS.g_list_store_new(GTK.GTK_TYPE_FILE_FILTER());
}
for (int i = 0; i < filterExtensions.length; i++) {
if (filterExtensions [i] != null) {
long filter = GTK.gtk_file_filter_new ();
if (filterNames.length > i && filterNames [i] != null) {
byte [] name = Converter.wcsToMbcs (filterNames [i], true);
GTK.gtk_file_filter_set_name (filter, name);
} else {
byte [] name = Converter.wcsToMbcs (filterExtensions [i], true);
GTK.gtk_file_filter_set_name (filter, name);
}
int start = 0;
int index = filterExtensions [i].indexOf (EXTENSION_SEPARATOR);
while (index != -1) {
String current = filterExtensions [i].substring (start, index);
byte [] filterString = Converter.wcsToMbcs (current, true);
GTK.gtk_file_filter_add_pattern (filter, filterString);
start = index + 1;
index = filterExtensions [i].indexOf (EXTENSION_SEPARATOR, start);
}
String current = filterExtensions [i].substring (start);
byte [] filterString = Converter.wcsToMbcs (current, true);
GTK.gtk_file_filter_add_pattern (filter, filterString);
if (GTK.GTK4) {
OS.g_list_store_append(fileFilters, filter);
} else {
GTK3.gtk_file_chooser_add_filter (handle, filter);
}
if (i == filterIndex) {
initialFilter = filter;
}
}
}
if (GTK.GTK4) {
GTK4.gtk_file_dialog_set_filters(handle, fileFilters);
OS.g_object_unref(fileFilters);
}
if (initialFilter != 0) {
if (GTK.GTK4) {
GTK4.gtk_file_dialog_set_default_filter(handle, initialFilter);
} else {
GTK3.gtk_file_chooser_set_filter(handle, initialFilter);
}
}
fullPath = null;
fileNames = new String [0];
}
/**
* Check whether the file extension is a glob pattern.
* For example, *.* is a glob pattern which corresponds to all files
* *.jp* corresponds to all the files with extension starting with jp like jpg,jpeg etc
* *.jp? corresponds to 3 letter extension starting with jp with any 3rd letter
* *.[pq]ng this corresponds to *.png and *.qng
*
* @param extension file extension as a string
*
* @return true if the extension contains any of the glob pattern wildcards
*/
private boolean isGlobPattern (String extension) {
if (extension.contains ("*") ||
extension.contains ("?") ||
(extension.contains ("[") && extension.contains ("]"))) {
return true;
}
return false;
}
/**
* Set the initial filename which the dialog will
* select by default when opened to the argument,
* which may be null. The name will be prefixed with
* the filter path when one is supplied.
*
* @param string the file name
*/
public void setFileName (String string) {
fileName = string;
}
/**
* Set the file extensions which the dialog will
* use to filter the files it shows to the argument,
* which may be null.
*
* The strings are platform specific. For example, on
* some platforms, an extension filter string is typically
* of the form "*.extension", where "*.*" matches all files.
* For filters with multiple extensions, use semicolon as
* a separator, e.g. "*.jpg;*.png".
*
*
* Note: On Mac, setting the file extension filter affects how
* app bundles are treated by the dialog. When a filter extension
* having the app extension (.app) is selected, bundles are treated
* as files. For all other extension filters, bundles are treated
* as directories. When no filter extension is set, bundles are
* treated as files.
*
*
* @param extensions the file extension filter
*
* @see #setFilterNames to specify the user-friendly
* names corresponding to the extensions
*/
public void setFilterExtensions (String [] extensions) {
filterExtensions = extensions;
}
/**
* Set the 0-based index of the file extension filter
* which the dialog will use initially to filter the files
* it shows to the argument.
*
* This is an index into the FilterExtensions array and
* the FilterNames array.
*
*
* @param index the file extension filter index
*
* @see #setFilterExtensions
* @see #setFilterNames
*
* @since 3.4
*/
public void setFilterIndex (int index) {
filterIndex = index;
}
/**
* Sets the names that describe the filter extensions
* which the dialog will use to filter the files it shows
* to the argument, which may be null.
*
* Each name is a user-friendly short description shown for
* its corresponding filter. The names
array must
* be the same length as the extensions
array.
*
*
* @param names the list of filter names, or null for no filter names
*
* @see #setFilterExtensions
*/
public void setFilterNames (String [] names) {
filterNames = names;
}
/**
* Sets the directory path that the dialog will use
* to the argument, which may be null. File names in this
* path will appear in the dialog, filtered according
* to the filter extensions. If the string is null,
* then the operating system's default filter path
* will be used.
*
* Note that the path string is platform dependent.
* For convenience, either '/' or '\' can be used
* as a path separator.
*
*
* @param string the directory path
*
* @see #setFilterExtensions
*/
public void setFilterPath (String string) {
filterPath = string;
}
/**
* Sets the flag that the dialog will use to
* determine whether to prompt the user for file
* overwrite if the selected file already exists.
*
* Note: On some platforms where suppressing the overwrite prompt
* is not supported, the prompt is shown even when invoked with
* overwrite false.
*
*
* @param overwrite true if the dialog will prompt for file overwrite, false otherwise
*
* @since 3.4
*/
public void setOverwrite (boolean overwrite) {
this.overwrite = overwrite;
}
/* Sets URI Mode.
*
* When the FileDialog is in URI mode it returns
* a URI (instead of a file name) for the following
* methods: open() and getFilterPath().
* The input argment for setFilterPath() should also
* be a URI.
*/
/*public*/ void setURIMode (boolean uriMode) {
this.uriMode = uriMode;
}
}