All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kotcrab.vis.ui.widget.file.FileChooser Maven / Gradle / Ivy

There is a newer version: 1.5.3
Show newest version
/*
 * Copyright 2014-2017 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.kotcrab.vis.ui.widget.file;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Buttons;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.*;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.utils.*;
import com.badlogic.gdx.utils.*;
import com.kotcrab.vis.ui.FocusManager;
import com.kotcrab.vis.ui.Focusable;
import com.kotcrab.vis.ui.Sizes;
import com.kotcrab.vis.ui.VisUI;
import com.kotcrab.vis.ui.layout.GridGroup;
import com.kotcrab.vis.ui.util.OsUtils;
import com.kotcrab.vis.ui.util.dialog.Dialogs;
import com.kotcrab.vis.ui.util.dialog.Dialogs.OptionDialogType;
import com.kotcrab.vis.ui.util.dialog.InputDialogAdapter;
import com.kotcrab.vis.ui.util.dialog.OptionDialogAdapter;
import com.kotcrab.vis.ui.util.value.ConstantIfVisibleValue;
import com.kotcrab.vis.ui.util.value.PrefHeightIfVisibleValue;
import com.kotcrab.vis.ui.util.value.PrefWidthIfVisibleValue;
import com.kotcrab.vis.ui.widget.*;
import com.kotcrab.vis.ui.widget.Tooltip;
import com.kotcrab.vis.ui.widget.ButtonBar.ButtonType;
import com.kotcrab.vis.ui.widget.file.internal.*;
import com.kotcrab.vis.ui.widget.file.internal.DriveCheckerService.DriveCheckerListener;
import com.kotcrab.vis.ui.widget.file.internal.DriveCheckerService.RootMode;
import com.kotcrab.vis.ui.widget.file.internal.FileChooserWinService.RootNameListener;
import com.kotcrab.vis.ui.widget.file.internal.FileHistoryManager.FileHistoryCallback;
import com.kotcrab.vis.ui.widget.file.internal.FilePopupMenu.FilePopupMenuCallback;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.StringBuilder;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static com.kotcrab.vis.ui.widget.file.internal.FileChooserText.*;

/**
 * Widget allowing user to choose files. FileChooser is heavy widget and should be reused whenever possible, typically
 * one instance is enough for application. Chooser is platform dependent and can be only used on desktop.
 * 

* FileChooser will be centered on screen after adding to Stage use {@link #setCenterOnAdd(boolean)} to change this. * @author Kotcrab * @since 0.1.0 */ public class FileChooser extends VisWindow implements FileHistoryCallback { private static final long FILE_WATCHER_CHECK_DELAY_MILLIS = 2000; private static final ShortcutsComparator SHORTCUTS_COMPARATOR = new ShortcutsComparator(); private static final Vector2 tmpVector = new Vector2(); private static boolean saveLastDirectory = false; public static boolean focusFileScrollPaneOnShow = true; public static boolean focusSelectedFileTextFieldOnShow = true; private Mode mode; private ViewMode viewMode = ViewMode.DETAILS; private SelectionMode selectionMode = SelectionMode.FILES; private AtomicReference sorting = new AtomicReference(FileSorting.NAME); private AtomicBoolean sortingOrderAscending = new AtomicBoolean(true); private FileChooserListener listener = new FileChooserAdapter(); private FileFilter fileFilter = new DefaultFileFilter(this); private FileDeleter fileDeleter = new DefaultFileDeleter(); private FileTypeFilter fileTypeFilter = null; private FileTypeFilter.Rule activeFileTypeRule = null; private FileIconProvider iconProvider; private DriveCheckerService driveCheckerService = DriveCheckerService.getInstance(); private Array driveCheckerListeners = new Array(); private FileChooserWinService chooserWinService = FileChooserWinService.getInstance(); private ExecutorService listDirExecutor = Executors.newSingleThreadExecutor(new ServiceThreadFactory("FileChooserListDirThread")); private Future listDirFuture; private ShowBusyBarTask showBusyBarTask = new ShowBusyBarTask(); private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); private boolean showSelectionCheckboxes = false; public static final int DEFAULT_KEY = -1; private boolean multiSelectionEnabled = false; private int groupMultiSelectKey = DEFAULT_KEY; //shift by default private int multiSelectKey = DEFAULT_KEY; //ctrl (or command on mac) by default private PreferencesIO preferencesIO; private Array favorites; private Array recentDirectories; private FileHandle currentDirectory; private Array currentFiles = new Array(); private IdentityMap currentFilesMetadata = new IdentityMap(); private FileListAdapter fileListAdapter; private Array selectedItems = new Array(); private ShortcutItem selectedShortcut; private String defaultFileName; private boolean watchingFilesEnabled = true; private Thread fileWatcherThread; private boolean shortcutsListRebuildScheduled; private boolean filesListRebuildScheduled; private FileHistoryManager historyManager; // UI private FileChooserStyle style; private Sizes sizes; private VisSplitPane mainSplitPane; private VisTable shortcutsTable; private VerticalGroup shortcutsMainPanel; private VerticalGroup shortcutsRootsPanel; private VerticalGroup shortcutsFavoritesPanel; private ListView fileListView; private float maxDateLabelWidth; private BusyBar fileListBusyBar; private VisImageButton favoriteFolderButton; private VisImageButton viewModeButton; private Tooltip favoriteFolderButtonTooltip; private VisTextField currentPath; private VisTextField selectedFileTextField; private VisSelectBox fileTypeSelectBox; private VisTextButton confirmButton; private FilePopupMenu fileMenu; private FileSuggestionPopup fileNameSuggestionPopup; private DirsSuggestionPopup dirsSuggestionPopup; private VisLabel fileTypeLabel; private PopupMenu viewModePopupMenu; /** @param mode whether this chooser will be used to open or save files */ public FileChooser (Mode mode) { this((FileHandle) null, mode); } /** * @param directory starting chooser directory * @param mode whether this chooser will be used to open or save files */ public FileChooser (FileHandle directory, Mode mode) { super(""); this.mode = mode; getTitleLabel().setText(TITLE_CHOOSE_FILES.get()); style = VisUI.getSkin().get(FileChooserStyle.class); sizes = VisUI.getSizes(); init(directory); } /** * @param title chooser window title * @param mode whether this chooser will be used to open or save files */ public FileChooser (String title, Mode mode) { this("default", title, mode); } /** * @param styleName skin style name * @param title chooser window title * @param mode whether this chooser will be used to open or save files */ public FileChooser (String styleName, String title, Mode mode) { super(title); this.mode = mode; style = VisUI.getSkin().get(styleName, FileChooserStyle.class); sizes = VisUI.getSizes(); init(null); } /** * @param prefsName file name that will be used to store chooser preferences such as favorites or recent directories. * Should be your application package name with appended `.filechooser` e.g. com.seriouscompay.seriousprogram.filechooser. * This name should be unique and should not be reused with other preferences of your application to avoid key collisions. */ public static void setDefaultPrefsName (String prefsName) { PreferencesIO.setDefaultPrefsName(prefsName); } /** @deprecated replaced by {@link #setDefaultPrefsName(String)} */ @Deprecated public static void setFavoritesPrefsName (String name) { PreferencesIO.setDefaultPrefsName(name); } private void init (FileHandle directory) { setModal(true); setResizable(true); setMovable(true); addCloseButton(); closeOnEscape(); iconProvider = new DefaultFileIconProvider(this); preferencesIO = new PreferencesIO(); reloadPreferences(false); createToolbar(); viewModePopupMenu = new PopupMenu(style.popupMenuStyle); createViewModePopupMenu(); createCenterContentPanel(); createFileTextBox(); createBottomButtons(); createShortcutsMainPanel(); shortcutsRootsPanel = new VerticalGroup(); shortcutsFavoritesPanel = new VerticalGroup(); rebuildShortcutsFavoritesPanel(); fileMenu = new FilePopupMenu(this, new FilePopupMenuCallback() { @Override public void showNewDirDialog () { showNewDirectoryDialog(); } @Override public void showFileDelDialog (FileHandle file) { showFileDeleteDialog(file); } }); fileNameSuggestionPopup = new FileSuggestionPopup(this); fileNameSuggestionPopup.setListener(new PopupMenu.PopupMenuListener() { @Override public void activeItemChanged (MenuItem newItem, boolean changedByKeyboard) { if (changedByKeyboard == false || newItem == null) return; highlightFiles(currentDirectory.child(newItem.getText().toString())); updateSelectedFileFieldText(true); } }); rebuildShortcutsList(); if (directory == null) { FileHandle startingDir = null; if (saveLastDirectory) startingDir = preferencesIO.loadLastDirectory(); if (startingDir == null || startingDir.exists() == false) startingDir = getDefaultStartingDirectory(); setDirectory(startingDir, HistoryPolicy.IGNORE); } else { setDirectory(directory, HistoryPolicy.IGNORE); } setSize(500, 600); centerWindow(); createListeners(); setFileTypeFilter(null); setFavoriteFolderButtonVisible(false); } private void createToolbar () { VisTable toolbarTable = new VisTable(true); toolbarTable.defaults().minWidth(30).right(); add(toolbarTable).fillX().expandX().pad(3).padRight(2); historyManager = new FileHistoryManager(style, this); currentPath = new VisTextField(); final VisImageButton showRecentDirButton = new VisImageButton(style.expandDropdown); showRecentDirButton.setFocusBorderEnabled(false); dirsSuggestionPopup = new DirsSuggestionPopup(this, currentPath); dirsSuggestionPopup.setListener(new PopupMenu.PopupMenuListener() { @Override public void activeItemChanged (MenuItem newItem, boolean changedByKeyboard) { if (changedByKeyboard == false || newItem == null) return; setCurrentPathFieldText(newItem.getText().toString()); } }); currentPath.addListener(new InputListener() { @Override public boolean keyTyped (InputEvent event, char character) { if (event.getKeyCode() == Keys.ENTER) { dirsSuggestionPopup.remove(); return false; } float targetWidth = currentPath.getWidth() + showRecentDirButton.getWidth(); dirsSuggestionPopup.pathFieldKeyTyped(getChooserStage(), targetWidth); return false; } @Override public boolean keyDown (InputEvent event, int keycode) { if (keycode == Keys.ENTER) { FileHandle file = Gdx.files.absolute(currentPath.getText()); if (file.exists()) { if (file.isDirectory() == false) file = file.parent(); setDirectory(file, HistoryPolicy.ADD); addRecentDirectory(file); } else { showDialog(POPUP_DIRECTORY_DOES_NOT_EXIST.get()); setCurrentPathFieldText(currentDirectory.path()); } event.stop(); } return false; } }); currentPath.addListener(new FocusListener() { @Override public void keyboardFocusChanged (FocusEvent event, Actor actor, boolean focused) { if (focused == false) { setCurrentPathFieldText(currentDirectory.path()); } } }); showRecentDirButton.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { float targetWidth = currentPath.getWidth() + showRecentDirButton.getWidth(); dirsSuggestionPopup.showRecentDirectories(getChooserStage(), recentDirectories, targetWidth); } }); VisImageButton folderParentButton = new VisImageButton(style.iconFolderParent, PARENT_DIRECTORY.get()); favoriteFolderButton = new VisImageButton(style.iconStar); favoriteFolderButtonTooltip = new Tooltip.Builder(CONTEXT_MENU_ADD_TO_FAVORITES.get()).target(favoriteFolderButton).build(); viewModeButton = new VisImageButton(style.iconListSettings); new Tooltip.Builder(CHANGE_VIEW_MODE.get()).target(viewModeButton).build(); VisImageButton folderNewButton = new VisImageButton(style.iconFolderNew, NEW_DIRECTORY.get()); toolbarTable.add(historyManager.getButtonsTable()); toolbarTable.add(currentPath).spaceRight(0).expand().fill(); toolbarTable.add(showRecentDirButton).width(15 * sizes.scaleFactor).growY(); toolbarTable.add(folderParentButton); toolbarTable.add(favoriteFolderButton).width(PrefWidthIfVisibleValue.INSTANCE).spaceRight(new ConstantIfVisibleValue(sizes.spacingRight)); toolbarTable.add(viewModeButton).width(PrefWidthIfVisibleValue.INSTANCE).spaceRight(new ConstantIfVisibleValue(sizes.spacingRight)); toolbarTable.add(folderNewButton); folderParentButton.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { FileHandle parent = currentDirectory.parent(); // if current directory is drive root (eg. "C:/") navigating to parent // would navigate to "/" which would work but it is bad for UX if (OsUtils.isWindows() && currentDirectory.path().endsWith(":/")) return; setDirectory(parent, HistoryPolicy.ADD); } }); favoriteFolderButton.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { if (favorites.contains(currentDirectory, false)) { removeFavorite(currentDirectory); } else { addFavorite(currentDirectory); } } }); folderNewButton.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { showNewDirectoryDialog(); } }); addListener(historyManager.getDefaultClickListener()); } private void createViewModePopupMenu () { rebuildViewModePopupMenu(); viewModeButton.addListener(new InputListener() { @Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { //show menu on next frame, without it menu would be closed instantly it was opened //the other solution is to call event.stop but this could lead to some other PopupMenu not being closed //on touchDown event because event.stop stops event propagation Gdx.app.postRunnable(new Runnable() { @Override public void run () { viewModePopupMenu.showMenu(getChooserStage(), viewModeButton); } }); return true; } }); } private void rebuildViewModePopupMenu () { viewModePopupMenu.clear(); for (final ViewMode mode : ViewMode.values()) { if (mode.thumbnailMode && iconProvider.isThumbnailModesSupported() == false) continue; viewModePopupMenu.addItem(new MenuItem(mode.getBundleText(), new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { setViewMode(mode); } })); } } private void updateFavoriteFolderButton () { VisLabel label = (VisLabel) favoriteFolderButtonTooltip.getContent(); if (favorites.contains(currentDirectory, false)) { favoriteFolderButton.getStyle().imageUp = style.iconStar; label.setText(CONTEXT_MENU_REMOVE_FROM_FAVORITES.get()); } else { favoriteFolderButton.getStyle().imageUp = style.iconStarOutline; label.setText(CONTEXT_MENU_ADD_TO_FAVORITES.get()); } favoriteFolderButtonTooltip.pack(); } private void createCenterContentPanel () { fileListAdapter = new FileListAdapter(this, currentFiles); fileListView = new ListView(fileListAdapter); setupDefaultScrollPane(fileListView.getScrollPane()); VisTable fileScrollPaneTable = new VisTable(); fileListBusyBar = new BusyBar(); fileListBusyBar.setVisible(false); fileScrollPaneTable.add(fileListBusyBar).space(0).height(PrefHeightIfVisibleValue.INSTANCE).growX().row(); fileScrollPaneTable.add(fileListView.getMainTable()).pad(2).top().expand().fillX(); fileScrollPaneTable.setTouchable(Touchable.enabled); // shortcutsTable is contained in shortcutsScrollPane contained in shortcutsScrollPaneTable contained in mainSplitPane shortcutsTable = new VisTable(); final VisScrollPane shortcutsScrollPane = setupDefaultScrollPane(new VisScrollPane(shortcutsTable)); VisTable shortcutsScrollPaneTable = new VisTable(); shortcutsScrollPaneTable.add(shortcutsScrollPane).pad(2).top().expand().fillX(); mainSplitPane = new VisSplitPane(shortcutsScrollPaneTable, fileScrollPaneTable, false) { @Override public void invalidate () { super.invalidate(); invalidateChildHierarchy(shortcutsScrollPane); } }; mainSplitPane.setSplitAmount(0.3f); mainSplitPane.setMinSplitAmount(0.05f); mainSplitPane.setMaxSplitAmount(0.80f); row(); add(mainSplitPane).expand().fill(); row(); fileScrollPaneTable.addListener(new InputListener() { @Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { return true; } @Override public void touchUp (InputEvent event, float x, float y, int pointer, int button) { if (button == Buttons.RIGHT && fileMenu.isAddedToStage() == false) { fileMenu.build(); fileMenu.showMenu(getChooserStage(), event.getStageX(), event.getStageY()); } } }); } private void invalidateChildHierarchy (WidgetGroup layout) { if (layout != null) { layout.invalidate(); for (Actor actor : layout.getChildren()) { if (actor instanceof WidgetGroup) invalidateChildHierarchy((WidgetGroup) actor); else if (actor instanceof Layout) ((Layout) actor).invalidate(); } } } private void setCurrentPathFieldText (String text) { currentPath.setText(text); currentPath.setCursorAtTextEnd(); } private void createFileTextBox () { VisTable table = new VisTable(true); VisLabel nameLabel = new VisLabel(FILE_NAME.get()); selectedFileTextField = new VisTextField(); selectedFileTextField.setProgrammaticChangeEvents(false); fileTypeLabel = new VisLabel(FILE_TYPE.get()); fileTypeSelectBox = new VisSelectBox(); fileTypeSelectBox.getSelection().setProgrammaticChangeEvents(false); fileTypeSelectBox.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { activeFileTypeRule = fileTypeSelectBox.getSelected(); rebuildFileList(); } }); table.defaults().left(); table.add(nameLabel).spaceBottom(new ConstantIfVisibleValue(fileTypeSelectBox, 5f)); table.add(selectedFileTextField).expandX().fillX() .spaceBottom(new ConstantIfVisibleValue(fileTypeSelectBox, 5f)).row(); table.add(fileTypeLabel).height(PrefHeightIfVisibleValue.INSTANCE) .spaceBottom(new ConstantIfVisibleValue(sizes.spacingBottom)); table.add(fileTypeSelectBox).height(PrefHeightIfVisibleValue.INSTANCE) .spaceBottom(new ConstantIfVisibleValue(sizes.spacingBottom)).expand().fill(); selectedFileTextField.addListener(new InputListener() { @Override public boolean keyDown (InputEvent event, int keycode) { if (keycode == Keys.ENTER) { selectionFinished(); return true; } return false; } }); selectedFileTextField.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { deselectAll(false); fileNameSuggestionPopup.pathFieldKeyTyped(getChooserStage(), currentFiles, selectedFileTextField); FileHandle enteredFile = currentDirectory.child(selectedFileTextField.getText()); if (currentFiles.contains(enteredFile, false)) { highlightFiles(enteredFile); } } }); add(table).expandX().fillX().pad(3f).padRight(2f).padBottom(2f); row(); } private void updateFileTypeSelectBox () { if (fileTypeFilter == null || selectionMode == SelectionMode.DIRECTORIES) { fileTypeLabel.setVisible(false); fileTypeSelectBox.setVisible(false); fileTypeSelectBox.invalidateHierarchy(); return; } else { fileTypeLabel.setVisible(true); fileTypeSelectBox.setVisible(true); fileTypeSelectBox.invalidateHierarchy(); } Array rules = new Array(fileTypeFilter.getRules()); if (fileTypeFilter.isAllTypesAllowed()) { FileTypeFilter.Rule allTypesRule = new FileTypeFilter.Rule(ALL_FILES.get()); rules.add(allTypesRule); } fileTypeSelectBox.setItems(rules); fileTypeSelectBox.setSelected(activeFileTypeRule); } private void createBottomButtons () { VisTextButton cancelButton = new VisTextButton(CANCEL.get()); confirmButton = new VisTextButton(mode == Mode.OPEN ? OPEN.get() : SAVE.get()); VisTable buttonTable = new VisTable(true); buttonTable.defaults().minWidth(70).right(); add(buttonTable).padTop(3).padBottom(3).padRight(2).fillX().expandX(); ButtonBar buttonBar = new ButtonBar(); buttonBar.setIgnoreSpacing(true); buttonBar.setButton(ButtonType.CANCEL, cancelButton); buttonBar.setButton(ButtonType.OK, confirmButton); buttonTable.add(buttonBar.createTable()).expand().right(); cancelButton.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { fadeOut(); listener.canceled(); } }); confirmButton.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { selectionFinished(); } }); } private void createShortcutsMainPanel () { shortcutsMainPanel = new VerticalGroup(); String userHome = System.getProperty("user.home"); String userName = System.getProperty("user.name"); File userDesktop = new File(userHome + "/Desktop"); if (userDesktop.exists()) shortcutsMainPanel.addActor(new ShortcutItem(userDesktop, DESKTOP.get(), style.iconFolder)); shortcutsMainPanel.addActor(new ShortcutItem(new File(userHome), userName, style.iconFolder)); } private void createListeners () { addListener(new InputListener() { @Override public boolean keyDown (InputEvent event, int keycode) { if (keycode == Keys.A && UIUtils.ctrl() && getChooserStage().getKeyboardFocus() instanceof VisTextField == false) { selectAll(); return true; } return false; } @Override public boolean keyTyped (InputEvent event, char character) { if (getChooserStage().getKeyboardFocus() instanceof VisTextField) return false; if (Character.isLetterOrDigit(character) == false) return false; String name = String.valueOf(character); for (FileHandle file : currentFiles) { if (file.name().toLowerCase().startsWith(name)) { deselectAll(); highlightFiles(file); return true; } } return false; } }); } private void selectionFinished () { if (selectedItems.size == 1) { // only files allowed but directory is selected? // navigate to that directory! if (selectionMode == SelectionMode.FILES) { FileHandle selected = selectedItems.get(0).getFile(); if (selected.isDirectory()) { setDirectory(selected, HistoryPolicy.ADD); return; } } // only directories allowed but file is selected? // display dialog :( if (selectionMode == SelectionMode.DIRECTORIES) { FileHandle selected = selectedItems.get(0).getFile(); if (selected.isDirectory() == false) { showDialog(POPUP_ONLY_DIRECTORIES.get()); return; } } } if (selectedItems.size > 0 || mode == Mode.SAVE) { Array files = getFileListFromSelected(); notifyListenerAndCloseDialog(files); } else { if (selectionMode == SelectionMode.FILES) { showDialog(POPUP_CHOOSE_FILE.get()); } else { Array files = new Array(); if (selectedFileTextField.getText().length() != 0) { files.add(currentDirectory.child(selectedFileTextField.getText())); } else { // this part is executed when nothing is selected but selection mode is `directories` or `files and directories` // it is perfectly valid, nothing is selected so that means the current chooser directory have to be // selected and passed to listener files.add(currentDirectory); } notifyListenerAndCloseDialog(files); } } } @Override protected void close () { listener.canceled(); super.close(); } private void notifyListenerAndCloseDialog (Array files) { if (files == null) return; if (mode == Mode.OPEN) { for (FileHandle file : files) { if (file.exists() == false) { showDialog(POPUP_SELECTED_FILE_DOES_NOT_EXIST.get()); return; } } } if (files.size != 0) { listener.selected(files); if (saveLastDirectory) { preferencesIO.saveLastDirectory(currentDirectory); } } fadeOut(); } @Override public void fadeOut (float time) { super.fadeOut(time); fileMenu.remove(); dirsSuggestionPopup.remove(); fileNameSuggestionPopup.remove(); viewModePopupMenu.remove(); } protected VisScrollPane setupDefaultScrollPane (VisScrollPane scrollPane) { scrollPane.setOverscroll(false, false); scrollPane.setFlickScroll(false); scrollPane.setFadeScrollBars(false); scrollPane.setScrollingDisabled(true, false); return scrollPane; } private Array getFileListFromSelected () { Array list = new Array(); if (mode == Mode.OPEN) { for (FileItem item : selectedItems) list.add(item.getFile()); return list; } else if (selectedItems.size > 0) { for (FileItem item : selectedItems) list.add(item.getFile()); showOverwriteQuestion(list); return null; } else { String fileName = selectedFileTextField.getText(); FileHandle file = currentDirectory.child(fileName); if (FileUtils.isValidFileName(fileName) == false) { showDialog(POPUP_FILENAME_INVALID.get()); return null; } if (file.exists()) { list.add(file); showOverwriteQuestion(list); return null; } else { //if user typed no extension or extension is wrong and there is active file type rule //then the first extension rule will be appended/replaced automatically to entered file name if (activeFileTypeRule != null) { Array ruleExts = activeFileTypeRule.getExtensions(); if (ruleExts.size > 0 && ruleExts.contains(file.extension(), false) == false) { file = file.sibling(file.nameWithoutExtension() + "." + ruleExts.first()); } } list.add(file); if (file.exists()) { showOverwriteQuestion(list); return null; } else { return list; } } } } private void showDialog (String text) { Dialogs.showOKDialog(getChooserStage(), POPUP_TITLE.get(), text); } private void showOverwriteQuestion (final Array filesList) { String text = filesList.size == 1 ? POPUP_FILE_EXIST_OVERWRITE.get() : POPUP_MULTIPLE_FILE_EXIST_OVERWRITE.get(); Dialogs.showOptionDialog(getChooserStage(), POPUP_TITLE.get(), text, OptionDialogType.YES_NO, new OptionDialogAdapter() { @Override public void yes () { notifyListenerAndCloseDialog(filesList); } }); } private void rebuildShortcutsList (boolean rebuildRootCache) { shortcutsTable.clear(); shortcutsTable.add(shortcutsMainPanel).left().row(); shortcutsTable.addSeparator(); if (rebuildRootCache) rebuildFileRootsCache(); shortcutsTable.add(shortcutsRootsPanel).left().row(); if (shortcutsFavoritesPanel.getChildren().size > 0) shortcutsTable.addSeparator(); shortcutsTable.add(shortcutsFavoritesPanel).left().row(); } private void rebuildShortcutsList () { shortcutsListRebuildScheduled = false; rebuildShortcutsList(true); } private void rebuildFileRootsCache () { shortcutsRootsPanel.clear(); File[] roots = File.listRoots(); driveCheckerListeners.clear(); for (final File root : roots) { DriveCheckerListener listener = new DriveCheckerListener() { @Override public void rootMode (File root, RootMode mode) { if (driveCheckerListeners.removeValue(this, true) == false) return; String initialName = root.toString(); if (initialName.equals("/")) initialName = COMPUTER.get(); final ShortcutItem item = new ShortcutItem(root, initialName, style.iconDrive); if (OsUtils.isWindows()) chooserWinService.addListener(root, item); shortcutsRootsPanel.addActor(item); shortcutsRootsPanel.getChildren().sort(SHORTCUTS_COMPARATOR); } }; driveCheckerListeners.add(listener); driveCheckerService.addListener(root, mode == Mode.OPEN ? RootMode.READABLE : RootMode.WRITABLE, listener); } } private void rebuildShortcutsFavoritesPanel () { shortcutsFavoritesPanel.clear(); if (favorites.size > 0) { for (FileHandle f : favorites) shortcutsFavoritesPanel.addActor(new ShortcutItem(f.file(), f.name(), style.iconFolder)); } } private void rebuildFileList () { rebuildFileList(false); } private void rebuildFileList (final boolean stageChanged) { filesListRebuildScheduled = false; final FileHandle[] selectedFiles = new FileHandle[selectedItems.size]; for (int i = 0; i < selectedFiles.length; i++) { selectedFiles[i] = selectedItems.get(i).getFile(); } deselectAll(); setCurrentPathFieldText(currentDirectory.path()); if (showBusyBarTask.isScheduled() == false) { Timer.schedule(showBusyBarTask, 0.2f); //quiet period before busy bar is shown } if (listDirFuture != null) listDirFuture.cancel(true); listDirFuture = listDirExecutor.submit(new Runnable() { @Override public void run () { if (currentDirectory.exists() == false || currentDirectory.isDirectory() == false) { Gdx.app.postRunnable(new Runnable() { @Override public void run () { setDirectory(getDefaultStartingDirectory(), HistoryPolicy.ADD); } }); return; } final Array files = FileUtils.sortFiles(listFilteredCurrentDirectory(), sorting.get().comparator, !sortingOrderAscending.get()); if (Thread.currentThread().isInterrupted()) return; final IdentityMap metadata = new IdentityMap(files.size); for (FileHandle file : files) { metadata.put(file, FileHandleMetadata.of(file)); } if (Thread.currentThread().isInterrupted()) return; Gdx.app.postRunnable(new Runnable() { @Override public void run () { buildFileList(files, metadata, selectedFiles, stageChanged); } }); } }); } private void buildFileList (Array files, IdentityMap metadata, FileHandle[] selectedFiles, boolean stageChanged) { currentFiles.clear(); currentFilesMetadata.clear(); showBusyBarTask.cancel(); fileListBusyBar.setVisible(false); if (files.size == 0) { fileListAdapter.itemsChanged(); return; } maxDateLabelWidth = 0; currentFiles.addAll(files); currentFilesMetadata = metadata; fileListAdapter.itemsChanged(); fileListView.getScrollPane().setScrollX(0); fileListView.getScrollPane().setScrollY(0); highlightFiles(selectedFiles); if (stageChanged && selectedFiles.length == 0 && defaultFileName != null) { selectedFileTextField.setText(defaultFileName); FileHandle enteredFile = currentDirectory.child(selectedFileTextField.getText()); if (currentFiles.contains(enteredFile, false)) { highlightFiles(enteredFile); } } } /** * Sets chooser selected files. All files that are invalid for current selection won't be selected. Files that doesn't * exist will be ignored. * @param files absolute {@link FileHandle}s of files to be selected */ public void setSelectedFiles (FileHandle... files) { deselectAll(false); for (FileHandle file : files) { FileItem item = fileListAdapter.getViews().get(file); if (item != null) { item.select(false); } } removeInvalidSelections(); updateSelectedFileFieldText(); } /** * Changes default file name that will be displayed in file name text field after chooser is added to stage. * This for example can be used to suggest default file name when chooser is in SAVE mode. *

* Note that when chooser is in OPEN mode and file with such name doesn't exist then pressing "Open" button * will still display message that selected file does not exist. *

* Default file name is only used after chooser is added to {@link Stage} and no other file was selected by * {@link #setSelectedFiles(FileHandle...)} or user. *

* This will automatically highlight matching file in file list (if file already exist). * @param text new default file name, may be null */ public void setDefaultFileName (String text) { defaultFileName = text; } /** Refresh chooser lists content */ public void refresh () { refresh(false); } private void refresh (boolean stageChanged) { rebuildShortcutsList(); rebuildFileList(stageChanged); } /** * Adds favorite to favorite list * @param favourite to be added */ public void addFavorite (FileHandle favourite) { favorites.add(favourite); preferencesIO.saveFavorites(favorites); rebuildShortcutsFavoritesPanel(); rebuildShortcutsList(false); updateFavoriteFolderButton(); } /** * Removes favorite from current favorite list * @param favorite to be removed (path to favorite) * @return true if favorite was removed, false otherwise */ public boolean removeFavorite (FileHandle favorite) { boolean removed = favorites.removeValue(favorite, false); preferencesIO.saveFavorites(favorites); rebuildShortcutsFavoritesPanel(); rebuildShortcutsList(false); updateFavoriteFolderButton(); return removed; } private void addRecentDirectory (FileHandle file) { if (recentDirectories.contains(file, false)) return; recentDirectories.insert(0, file); if (recentDirectories.size > AbstractSuggestionPopup.MAX_SUGGESTIONS) recentDirectories.pop(); preferencesIO.saveRecentDirectories(recentDirectories); } public void clearRecentDirectories () { recentDirectories.clear(); preferencesIO.saveRecentDirectories(recentDirectories); } @Override public void setVisible (boolean visible) { if (isVisible() == false && visible) deselectAll(); // reset selected item when dialog is changed from invisible to visible super.setVisible(visible); } private void deselectAll () { deselectAll(true); } private void deselectAll (boolean updateTextField) { for (FileItem item : selectedItems) item.deselect(false); selectedItems.clear(); if (updateTextField) updateSelectedFileFieldText(); } private void selectAll () { for (FileItem item : fileListAdapter.getOrderedViews()) item.select(false); removeInvalidSelections(); updateSelectedFileFieldText(); } /** * Sets chooser selected files. Compared to {@link #setSelectedFiles(FileHandle...)} does not remove invalid files * from selection. */ public void highlightFiles (FileHandle... files) { for (FileHandle file : files) { FileItem item = fileListAdapter.getViews().get(file); if (item != null) { item.select(false); } } if (files.length > 0) { FileItem item = fileListAdapter.getViews().get(files[0]); if (item != null) { if (item.getParent() instanceof Table) { //table at this point may need additional layout to calculate proper target scroll cords ((Table) item.getParent()).layout(); } item.localToParentCoordinates(tmpVector.setZero()); fileListView.getScrollPane().scrollTo(tmpVector.x, tmpVector.y, item.getWidth(), item.getHeight(), false, true); } } updateSelectedFileFieldText(); } private void updateSelectedFileFieldText () { updateSelectedFileFieldText(false); } private void updateSelectedFileFieldText (boolean ignoreKeyboardFocus) { if (ignoreKeyboardFocus == false && getChooserStage() != null) { if (getChooserStage().getKeyboardFocus() == selectedFileTextField) return; } if (selectedItems.size == 0) { selectedFileTextField.setText(""); } else if (selectedItems.size == 1) { selectedFileTextField.setText(selectedItems.get(0).getFile().name()); } else { StringBuilder builder = new StringBuilder(); for (FileItem item : selectedItems) { builder.append('"'); builder.append(item.file.name()); builder.append("\" "); } selectedFileTextField.setText(builder.toString()); } selectedFileTextField.setCursorAtTextEnd(); } private void removeInvalidSelections () { if (selectionMode == SelectionMode.FILES) { Iterator it = selectedItems.iterator(); while (it.hasNext()) { FileItem item = it.next(); if (item.file.isDirectory()) { item.deselect(false); it.remove(); } } } if (selectionMode == SelectionMode.DIRECTORIES) { Iterator it = selectedItems.iterator(); while (it.hasNext()) { FileItem item = it.next(); if (item.file.isDirectory() == false) { item.deselect(false); it.remove(); } } } } public Mode getMode () { return mode; } public void setMode (Mode mode) { this.mode = mode; confirmButton.setText(mode == Mode.OPEN ? OPEN.get() : SAVE.get()); refresh(); } public ViewMode getViewMode () { return viewMode; } public void setViewMode (ViewMode viewMode) { if (this.viewMode == viewMode) return; this.viewMode = viewMode; iconProvider.viewModeChanged(viewMode); rebuildFileList(); } public void setDirectory (String directory) { setDirectory(Gdx.files.absolute(directory), HistoryPolicy.CLEAR); } public void setDirectory (File directory) { setDirectory(Gdx.files.absolute(directory.getAbsolutePath()), HistoryPolicy.CLEAR); } public void setDirectory (FileHandle directory) { setDirectory(directory, HistoryPolicy.CLEAR); } /** * Changes file chooser active directory. * Warning: To avoid hanging listing directory is performed asynchronously. In case of passing invalid file handle * file chooser will fallback to default one. */ @Override public void setDirectory (FileHandle directory, HistoryPolicy historyPolicy) { if (directory.equals(currentDirectory)) return; if (historyPolicy == HistoryPolicy.ADD) historyManager.historyAdd(); currentDirectory = directory; iconProvider.directoryChanged(directory); rebuildFileList(); if (historyPolicy == HistoryPolicy.CLEAR) historyManager.historyClear(); updateFavoriteFolderButton(); } @Override public FileHandle getCurrentDirectory () { return currentDirectory; } private FileHandle getDefaultStartingDirectory () { return Gdx.files.absolute(System.getProperty("user.home")); } /** List currently set directory with all active filters */ private FileHandle[] listFilteredCurrentDirectory () { FileHandle[] files = currentDirectory.list(fileFilter); if (fileTypeFilter == null || activeFileTypeRule == null) return files; FileHandle[] filtered = new FileHandle[files.length]; int count = 0; for (FileHandle file : files) { if (file.isDirectory() == false && activeFileTypeRule.accept(file) == false) continue; filtered[count++] = file; } if (count == 0) return new FileHandle[0]; FileHandle[] newFiltered = new FileHandle[count]; System.arraycopy(filtered, 0, newFiltered, 0, count); return newFiltered; } public FileFilter getFileFilter () { return fileFilter; } public void setFileFilter (FileFilter fileFilter) { this.fileFilter = fileFilter; rebuildFileList(); } /** * Sets new {@link FileTypeFilter}. Note that if you modify {@link FileTypeFilter} you must call this method again with * modified instance to apply changes. Setting file type filter won't have any effect when selection mode is set to * directories. */ public void setFileTypeFilter (FileTypeFilter fileTypeFilter) { if (fileTypeFilter == null) { this.fileTypeFilter = null; this.activeFileTypeRule = null; } else { if (fileTypeFilter.getRules().size == 0) throw new IllegalArgumentException("FileTypeFilter doesn't have any rules added"); this.fileTypeFilter = new FileTypeFilter(fileTypeFilter); this.activeFileTypeRule = this.fileTypeFilter.getRules().first(); } updateFileTypeSelectBox(); rebuildFileList(); } public FileTypeFilter.Rule getActiveFileTypeFilterRule () { return activeFileTypeRule; } public SelectionMode getSelectionMode () { return selectionMode; } /** * Changes selection mode, also updates the title of this file chooser to match current selection mode (eg. Choose file, Choose * directory etc.) */ public void setSelectionMode (SelectionMode selectionMode) { if (selectionMode == null) selectionMode = SelectionMode.FILES; this.selectionMode = selectionMode; switch (selectionMode) { case FILES: getTitleLabel().setText(TITLE_CHOOSE_FILES.get()); break; case DIRECTORIES: getTitleLabel().setText(TITLE_CHOOSE_DIRECTORIES.get()); break; case FILES_AND_DIRECTORIES: getTitleLabel().setText(TITLE_CHOOSE_FILES_AND_DIRECTORIES.get()); break; } updateFileTypeSelectBox(); rebuildFileList(); } public FileSorting getSorting () { return sorting.get(); } public void setSorting (FileSorting sorting, boolean sortingOrderAscending) { this.sorting.set(sorting); this.sortingOrderAscending.set(sortingOrderAscending); rebuildFileList(); } public void setSorting (FileSorting sorting) { this.sorting.set(sorting); rebuildFileList(); } public boolean isSortingOrderAscending () { return sortingOrderAscending.get(); } public void setSortingOrderAscending (boolean sortingOrderAscending) { this.sortingOrderAscending.set(sortingOrderAscending); rebuildFileList(); } public void setFavoriteFolderButtonVisible (boolean favoriteFolderButtonVisible) { favoriteFolderButton.setVisible(favoriteFolderButtonVisible); } public boolean isFavoriteFolderButtonVisible () { return favoriteFolderButton.isVisible(); } public void setViewModeButtonVisible (boolean viewModeButtonVisible) { viewModeButton.setVisible(viewModeButtonVisible); } public boolean isViewModeButtonVisible () { return viewModeButton.isVisible(); } public boolean isMultiSelectionEnabled () { return multiSelectionEnabled; } public void setMultiSelectionEnabled (boolean multiSelectionEnabled) { this.multiSelectionEnabled = multiSelectionEnabled; } public void setListener (FileChooserListener newListener) { this.listener = newListener; if (listener == null) listener = new FileChooserAdapter(); } public boolean isShowSelectionCheckboxes () { return showSelectionCheckboxes; } public void setShowSelectionCheckboxes (boolean showSelectionCheckboxes) { this.showSelectionCheckboxes = showSelectionCheckboxes; rebuildFileList(); } public int getMultiSelectKey () { return multiSelectKey; } /** @param multiSelectKey from {@link Keys} or {@link FileChooser#DEFAULT_KEY} to restore default */ public void setMultiSelectKey (int multiSelectKey) { this.multiSelectKey = multiSelectKey; } public int getGroupMultiSelectKey () { return groupMultiSelectKey; } /** @param groupMultiSelectKey from {@link Keys} or {@link FileChooser#DEFAULT_KEY} to restore default */ public void setGroupMultiSelectKey (int groupMultiSelectKey) { this.groupMultiSelectKey = groupMultiSelectKey; } private boolean isMultiSelectKeyPressed () { if (multiSelectKey == DEFAULT_KEY) return UIUtils.ctrl(); else return Gdx.input.isKeyPressed(multiSelectKey); } private boolean isGroupMultiSelectKeyPressed () { if (groupMultiSelectKey == DEFAULT_KEY) return UIUtils.shift(); else return Gdx.input.isKeyPressed(groupMultiSelectKey); } public FileChooserStyle getChooserStyle () { return style; } public Sizes getSizes () { return sizes; } private Stage getChooserStage () { return getStage(); } /** * If false file chooser won't pool directories for changes, adding new files or connecting new drive won't refresh file list. * This must be called when file chooser is not added to Stage */ public void setWatchingFilesEnabled (boolean watchingFilesEnabled) { if (getChooserStage() != null) throw new IllegalStateException("Pooling setting cannot be changed when file chooser is added to Stage!"); this.watchingFilesEnabled = watchingFilesEnabled; } public void setPrefsName (String prefsName) { preferencesIO = new PreferencesIO(prefsName); reloadPreferences(true); } private void reloadPreferences (boolean rebuildUI) { favorites = preferencesIO.loadFavorites(); recentDirectories = preferencesIO.loadRecentDirectories(); if (rebuildUI) rebuildShortcutsFavoritesPanel(); } @Override public void draw (Batch batch, float parentAlpha) { super.draw(batch, parentAlpha); if (shortcutsListRebuildScheduled) rebuildShortcutsList(); if (filesListRebuildScheduled) rebuildFileList(); } @Override protected void setStage (Stage stage) { super.setStage(stage); if (stage != null) { refresh(true); rebuildShortcutsFavoritesPanel(); //if by any chance multiple choosers changed favorites deselectAll(); if (focusFileScrollPaneOnShow) { stage.setScrollFocus(fileListView.getScrollPane()); } if (focusSelectedFileTextFieldOnShow) { FocusManager.switchFocus(stage, selectedFileTextField); stage.setKeyboardFocus(selectedFileTextField); } } if (watchingFilesEnabled) { if (stage != null) { startFileWatcher(); } else { stopFileWatcher(); } } } private void startFileWatcher () { if (fileWatcherThread != null) return; fileWatcherThread = new Thread(new Runnable() { File[] lastRoots; FileHandle lastCurrentDirectory; FileHandle[] lastCurrentFiles; @Override public void run () { lastRoots = File.listRoots(); lastCurrentDirectory = currentDirectory; lastCurrentFiles = currentDirectory.list(); while (fileWatcherThread != null) { File[] roots = File.listRoots(); if (roots.length != lastRoots.length || Arrays.equals(lastRoots, roots) == false) shortcutsListRebuildScheduled = true; lastRoots = roots; // if current directory changed during pools then our lastCurrentDirectoryFiles list is outdated and we shouldn't // schedule files list rebuild if (lastCurrentDirectory.equals(currentDirectory) == true) { FileHandle[] currentFiles = currentDirectory.list(); if (lastCurrentFiles.length != currentFiles.length || Arrays.equals(lastCurrentFiles, currentFiles) == false) filesListRebuildScheduled = true; lastCurrentFiles = currentFiles; } else lastCurrentFiles = currentDirectory.list(); // if list is outdated, refresh it lastCurrentDirectory = currentDirectory; try { Thread.sleep(FILE_WATCHER_CHECK_DELAY_MILLIS); } catch (InterruptedException ignored) { } } } }, "FileWatcherThread"); fileWatcherThread.setDaemon(true); fileWatcherThread.start(); } private void stopFileWatcher () { if (fileWatcherThread == null) return; fileWatcherThread.interrupt(); fileWatcherThread = null; } private void showNewDirectoryDialog () { Dialogs.showInputDialog(getChooserStage(), NEW_DIRECTORY_DIALOG_TITLE.get(), NEW_DIRECTORY_DIALOG_TEXT.get(), true, new InputDialogAdapter() { @Override public void finished (String input) { if (FileUtils.isValidFileName(input) == false) { Dialogs.showErrorDialog(getChooserStage(), NEW_DIRECTORY_DIALOG_ILLEGAL_CHARACTERS.get()); return; } for (FileHandle file : currentDirectory.list()) { if (file.name().equals(input)) { Dialogs.showErrorDialog(getChooserStage(), NEW_DIRECTORY_DIALOG_ALREADY_EXISTS.get()); return; } } FileHandle newDir = currentDirectory.child(input); newDir.mkdirs(); refresh(); highlightFiles(newDir); } }); } private void showFileDeleteDialog (final FileHandle fileToDelete) { Dialogs.showOptionDialog(getChooserStage(), POPUP_TITLE.get(), fileDeleter.hasTrash() ? CONTEXT_MENU_MOVE_TO_TRASH_WARNING.get() : CONTEXT_MENU_DELETE_WARNING.get(), OptionDialogType.YES_NO, new OptionDialogAdapter() { @Override public void yes () { try { boolean success = fileDeleter.delete(fileToDelete); if (success == false) { Dialogs.showErrorDialog(getChooserStage(), POPUP_DELETE_FILE_FAILED.get()); } } catch (IOException e) { Dialogs.showErrorDialog(getChooserStage(), POPUP_DELETE_FILE_FAILED.get(), e); e.printStackTrace(); } refresh(); } }); } /** * Sets {@link FileChooser.FileDeleter} that will be used for deleting files. You SHOULD NOT set your own file deleter. * You should use either {@link DefaultFileDeleter} or JNAFileDeleter from vis-ui-contrib project. JNAFileDeleter * supports moving file to system trash instead of deleting it permanently, however it requires JNA library in your * project classpath. */ public void setFileDeleter (FileDeleter fileDeleter) { if (fileDeleter == null) throw new IllegalStateException("fileDeleter can't be null"); this.fileDeleter = fileDeleter; fileMenu.fileDeleterChanged(fileDeleter.hasTrash()); } public void setIconProvider (FileIconProvider iconProvider) { this.iconProvider = iconProvider; rebuildViewModePopupMenu(); } public FileIconProvider getIconProvider () { return iconProvider; } public static boolean isSaveLastDirectory () { return saveLastDirectory; } /** * @param saveLastDirectory if true then chooser will store last directory user browsed in preferences file. Note that * this only applies to using chooser between separate app launches. When single instance of chooser is reused in single * app session then last directory is always remembered. Default is false. This must be called before creating FileChooser. */ public static void setSaveLastDirectory (boolean saveLastDirectory) { FileChooser.saveLastDirectory = saveLastDirectory; } public enum Mode { OPEN, SAVE } public enum SelectionMode { FILES, DIRECTORIES, FILES_AND_DIRECTORIES } public enum FileSorting { NAME(FileUtils.FILE_NAME_COMPARATOR), MODIFIED_DATE(FileUtils.FILE_MODIFIED_DATE_COMPARATOR), SIZE(FileUtils.FILE_SIZE_COMPARATOR); private final Comparator comparator; FileSorting (Comparator comparator) { this.comparator = comparator; } } public enum HistoryPolicy { ADD, CLEAR, IGNORE } public enum ViewMode { DETAILS(false, VIEW_MODE_DETAILS), BIG_ICONS(true, VIEW_MODE_BIG_ICONS), MEDIUM_ICONS(true, VIEW_MODE_MEDIUM_ICONS), SMALL_ICONS(true, VIEW_MODE_SMALL_ICONS), LIST(false, VIEW_MODE_LIST); private final FileChooserText bundleText; private final boolean thumbnailMode; ViewMode (boolean thumbnailMode, FileChooserText bundleText) { this.thumbnailMode = thumbnailMode; this.bundleText = bundleText; } public String getBundleText () { return bundleText.get(); } public void setupGridGroup (Sizes sizes, GridGroup group) { if (isGridMode() == false) return; float gridSize = getGridSize(sizes); if (gridSize < 0) { throw new IllegalStateException("FileChooser's ViewMode " + this.toString() + " has invalid size defined in Sizes. " + "Expected value greater than 0, got: " + gridSize + ". Check your skin Sizes definition."); } if (this == LIST) { group.setItemSize(gridSize, 22 * sizes.scaleFactor); return; } group.setItemSize(gridSize); } public boolean isGridMode () { return isThumbnailMode() || this == LIST; } public boolean isThumbnailMode () { return thumbnailMode; } public float getGridSize (Sizes sizes) { switch (this) { case DETAILS: return -1; case BIG_ICONS: return sizes.fileChooserViewModeBigIconsSize; case MEDIUM_ICONS: return sizes.fileChooserViewModeMediumIconsSize; case SMALL_ICONS: return sizes.fileChooserViewModeSmallIconsSize; case LIST: return sizes.fileChooserViewModeListWidthSize; default: return -1; } } } /** * Provides icons that will be used for file thumbnail on file list. If not set default is used that supports * directories and few basic file types. If you want to add your custom icon your should extend {@link DefaultFileIconProvider} */ public interface FileIconProvider { /** @return icon that will be used for this file or null if no icon should be displayed */ Drawable provideIcon (FileItem item); /** * @return true if this icon provider can supply proper icons for {@link ViewMode#BIG_ICONS}, {@link ViewMode#MEDIUM_ICONS} * and {@link ViewMode#SMALL_ICONS} view modes, false otherwise. If false thumbnail view modes won't be available for selection. */ boolean isThumbnailModesSupported (); void directoryChanged (FileHandle newDirectory); void viewModeChanged (ViewMode newViewMode); } public static class DefaultFileIconProvider implements FileIconProvider { protected FileChooser chooser; protected FileChooserStyle style; public DefaultFileIconProvider (FileChooser chooser) { this.chooser = chooser; this.style = chooser.style; } @Override public Drawable provideIcon (FileItem item) { if (item.isDirectory()) return getDirIcon(item); String ext = item.getFile().extension().toLowerCase(); if (ext.equals("jpg") || ext.equals("jpeg") || ext.equals("png") || ext.equals("bmp")) return getImageIcon(item); if (ext.equals("wav") || ext.equals("ogg") || ext.equals("mp3")) return getAudioIcon(item); if (ext.equals("pdf")) return getPdfIcon(item); if (ext.equals("txt")) return getTextIcon(item); return getDefaultIcon(item); } protected Drawable getDirIcon (FileItem item) { return style.iconFolder; } protected Drawable getImageIcon (FileItem item) { return style.iconFileImage; } protected Drawable getAudioIcon (FileItem item) { return style.iconFileAudio; } protected Drawable getPdfIcon (FileItem item) { return style.iconFilePdf; } protected Drawable getTextIcon (FileItem item) { return style.iconFileText; } protected Drawable getDefaultIcon (FileItem item) { return null; } @Override public boolean isThumbnailModesSupported () { return false; } @Override public void directoryChanged (FileHandle newDirectory) { } @Override public void viewModeChanged (ViewMode newViewMode) { } } public static class DefaultFileFilter implements FileFilter { private FileChooser chooser; private boolean ignoreChooserSelectionMode = false; public DefaultFileFilter (FileChooser chooser) { this.chooser = chooser; } @Override public boolean accept (File f) { if (f.isHidden()) return false; if (chooser.getMode() == Mode.OPEN ? f.canRead() == false : f.canWrite() == false) return false; if (ignoreChooserSelectionMode == false && f.isDirectory() == false && chooser.getSelectionMode() == SelectionMode.DIRECTORIES) { return false; } return true; } public boolean isIgnoreChooserSelectionMode () { return ignoreChooserSelectionMode; } public void setIgnoreChooserSelectionMode (boolean ignoreChooserSelectionMode) { this.ignoreChooserSelectionMode = ignoreChooserSelectionMode; } } public interface FileDeleter { boolean hasTrash (); boolean delete (FileHandle file) throws IOException; } public static final class DefaultFileDeleter implements FileDeleter { @Override public boolean hasTrash () { return false; } @Override public boolean delete (FileHandle file) { return file.deleteDirectory(); } } private class ShowBusyBarTask extends Timer.Task { @Override public void run () { fileListBusyBar.resetSegment(); fileListBusyBar.setVisible(true); currentFiles.clear(); currentFilesMetadata.clear(); fileListAdapter.itemsChanged(); } @Override public synchronized void cancel () { super.cancel(); fileListBusyBar.setVisible(false); } } /** Internal FileChooser API. */ public class FileItem extends Table implements Focusable { private FileHandle file; private FileHandleMetadata metadata; private VisCheckBox selectCheckBox; private VisImage iconImage; public FileItem (final FileHandle file, ViewMode viewMode) { this.file = file; this.metadata = currentFilesMetadata.get(file); if (metadata == null) metadata = FileHandleMetadata.of(file); //fallback, should not ever happen setTouchable(Touchable.enabled); VisLabel name = new VisLabel(metadata.name(), viewMode == ViewMode.SMALL_ICONS ? "small" : "default"); name.setEllipsis(true); Drawable icon = iconProvider.provideIcon(this); selectCheckBox = new VisCheckBox(""); selectCheckBox.setFocusBorderEnabled(false); selectCheckBox.setProgrammaticChangeEvents(false); boolean shouldShowItemShowCheckBox = showSelectionCheckboxes && ( (selectionMode == SelectionMode.FILES_AND_DIRECTORIES) || (selectionMode == SelectionMode.FILES && metadata.isDirectory() == false) || (selectionMode == SelectionMode.DIRECTORIES && metadata.isDirectory()) ); left(); if (viewMode.isThumbnailMode()) { if (shouldShowItemShowCheckBox) { IconStack stack = new IconStack(iconImage = new VisImage(icon, Scaling.none), selectCheckBox); add(stack).padTop(3).grow().row(); add(name).minWidth(1); } else { add(iconImage = new VisImage(icon, Scaling.none)).padTop(3).grow().row(); add(name).minWidth(1); } } else { if (shouldShowItemShowCheckBox) add(selectCheckBox).padLeft(3); add(iconImage = new VisImage(icon)).padTop(3).minWidth(22 * sizes.scaleFactor); add(name).minWidth(1).growX().padRight(10); VisLabel size = new VisLabel(isDirectory() ? "" : metadata.readableFileSize(), "small"); VisLabel dateLabel = new VisLabel(dateFormat.format(metadata.lastModified()), "small"); size.setAlignment(Align.right); if (viewMode == ViewMode.DETAILS) { maxDateLabelWidth = Math.max(dateLabel.getWidth(), maxDateLabelWidth); add(size).right().padRight(isDirectory() ? 0 : 10); add(dateLabel).padRight(6).width(new Value() { @Override public float get (Actor context) { return maxDateLabelWidth; } }); } } addListeners(); } /** * Updates file item icon, can be used for asynchronous icon loading. Note that icon provided must not return null * even if this item icon will be loaded later. */ public void setIcon (Drawable icon, Scaling scaling) { iconImage.setDrawable(icon); iconImage.setScaling(scaling); iconImage.invalidateHierarchy(); } private void addListeners () { addListener(new InputListener() { @Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { FocusManager.switchFocus(getChooserStage(), FileItem.this); getChooserStage().setKeyboardFocus(FileItem.this); return true; } @Override public void touchUp (InputEvent event, float x, float y, int pointer, int button) { if (event.getButton() == Buttons.RIGHT) { fileMenu.build(favorites, file); fileMenu.showMenu(getChooserStage(), event.getStageX(), event.getStageY()); } } @Override public boolean keyDown (InputEvent event, int keycode) { if (keycode == Keys.FORWARD_DEL) { showFileDeleteDialog(file); return true; } return false; } }); addListener(new ClickListener() { @Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { // very fast selecting and deselecting folder would navigate to that folder // return false will protect against that (tap count won't be increased) if (handleSelectClick(false) == false) return false; return super.touchDown(event, x, y, pointer, button); } @Override public void clicked (InputEvent event, float x, float y) { super.clicked(event, x, y); if (getTapCount() == 2 && selectedItems.contains(FileItem.this, true)) { if (file.isDirectory()) { setDirectory(file, HistoryPolicy.ADD); } else selectionFinished(); } } }); selectCheckBox.addListener(new InputListener() { @Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { event.stop(); return true; } }); selectCheckBox.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { event.stop(); handleSelectClick(true); } }); } private boolean handleSelectClick (boolean checkboxClicked) { if (selectedShortcut != null) selectedShortcut.deselect(); if (checkboxClicked) { if (multiSelectionEnabled == false && selectedItems.contains(FileItem.this, true) == false) deselectAll(); } else { if (multiSelectionEnabled == false || (isMultiSelectKeyPressed() == false && isGroupMultiSelectKeyPressed() == false)) deselectAll(); } boolean itemSelected = select(); if (selectedItems.size > 1 && multiSelectionEnabled && isGroupMultiSelectKeyPressed()) selectGroup(); if (selectedItems.size > 1) removeInvalidSelections(); updateSelectedFileFieldText(); return itemSelected; } private void selectGroup () { Array actors = fileListAdapter.getOrderedViews(); int thisSelectionIndex = getItemId(actors, FileItem.this); int lastSelectionIndex = getItemId(actors, selectedItems.get(selectedItems.size - 2)); int start; int end; if (thisSelectionIndex > lastSelectionIndex) { start = lastSelectionIndex; end = thisSelectionIndex; } else { start = thisSelectionIndex; end = lastSelectionIndex; } for (int i = start; i < end; i++) { FileItem item = actors.get(i); item.select(false); } } private int getItemId (Array actors, FileItem item) { for (int i = 0; i < actors.size; i++) { if (actors.get(i) == item) return i; } throw new IllegalStateException("Item not found in cells"); } /** Selects this items, if item is already in selectedList it will be deselected */ private boolean select () { return select(true); } private boolean select (boolean deselectIfAlreadySelected) { if (deselectIfAlreadySelected && selectedItems.contains(this, true)) { deselect(); return false; } setBackground(style.highlight); selectCheckBox.setChecked(true); if (selectedItems.contains(this, true) == false) selectedItems.add(this); return true; } private void deselect () { deselect(true); } private void deselect (boolean removeFromList) { setBackground((Drawable) null); selectCheckBox.setChecked(false); if (removeFromList) selectedItems.removeValue(this, true); } @Override public void focusLost () { } @Override public void focusGained () { } public FileHandle getFile () { return file; } public boolean isDirectory () { return metadata.isDirectory(); } } private class ShortcutItem extends Table implements RootNameListener, Focusable { public File file; private VisLabel name; /** Used only by shortcuts panel */ public ShortcutItem (final File file, String customName, Drawable icon) { this.file = file; name = new VisLabel(customName); name.setEllipsis(true); add(new Image(icon)).padTop(3); Cell labelCell = add(name).padRight(6); labelCell.width(new Value() { @Override public float get (Actor context) { return mainSplitPane.getFirstWidgetBounds().width - 30; } }); addListener(); } private void addListener () { addListener(new InputListener() { @Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { FocusManager.switchFocus(getChooserStage(), ShortcutItem.this); getChooserStage().setKeyboardFocus(ShortcutItem.this); return true; } @Override public void touchUp (InputEvent event, float x, float y, int pointer, int button) { if (event.getButton() == Buttons.RIGHT) { fileMenu.buildForFavorite(favorites, file); fileMenu.showMenu(getChooserStage(), event.getStageX(), event.getStageY()); } } @Override public boolean keyDown (InputEvent event, int keycode) { if (keycode == Keys.FORWARD_DEL) { FileHandle gdxFile = Gdx.files.absolute(file.getAbsolutePath()); if (favorites.contains(gdxFile, false)) { removeFavorite(gdxFile); } return true; } return false; } }); addListener(new ClickListener() { @Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { deselectAll(); updateSelectedFileFieldText(); select(); return super.touchDown(event, x, y, pointer, button); } @Override public void clicked (InputEvent event, float x, float y) { super.clicked(event, x, y); if (getTapCount() == 1) { File file = ShortcutItem.this.file; if (file.exists() == false) { showDialog(POPUP_DIRECTORY_DOES_NOT_EXIST.get()); refresh(); return; } if (file.isDirectory()) { setDirectory(Gdx.files.absolute(file.getAbsolutePath()), HistoryPolicy.ADD); getChooserStage().setScrollFocus(fileListView.getScrollPane()); } } } }); } public void setLabelText (String text) { name.setText(text); } public String getLabelText () { return name.getText().toString(); } private void select () { if (selectedShortcut != null) selectedShortcut.deselect(); selectedShortcut = ShortcutItem.this; setBackground(style.highlight); } private void deselect () { setBackground((Drawable) null); } @Override public void setRootName (String newName) { setLabelText(newName); } @Override public void focusGained () { } @Override public void focusLost () { } } private static class ShortcutsComparator implements Comparator { @Override public int compare (Actor o1, Actor o2) { ShortcutItem s1 = (ShortcutItem) o1; ShortcutItem s2 = (ShortcutItem) o2; return s1.getLabelText().compareTo(s2.getLabelText()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy