net.dermetfan.gdx.scenes.scene2d.ui.TreeFileChooser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of libgdx-utils Show documentation
Show all versions of libgdx-utils Show documentation
support library for libGDX
The newest version!
/** Copyright 2014 Robin Stumm ([email protected], http://dermetfan.net)
*
* 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 net.dermetfan.gdx.scenes.scene2d.ui;
import java.io.File;
import java.io.FileFilter;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.Button.ButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane.ScrollPaneStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Tree;
import com.badlogic.gdx.scenes.scene2d.ui.Tree.Node;
import com.badlogic.gdx.scenes.scene2d.ui.Tree.TreeStyle;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.scenes.scene2d.utils.Selection;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.Json.Serializable;
import com.badlogic.gdx.utils.JsonValue;
import com.badlogic.gdx.utils.Pools;
import net.dermetfan.utils.Function;
/** A {@link FileChooser} that uses a {@link Tree}. DO NOT FORGET TO {@link #add(FileHandle) ADD ROOTS}!
* @author dermetfan */
public class TreeFileChooser extends FileChooser {
/** @see #fileNode(FileHandle, Label.LabelStyle, Function) */
public static Node fileNode(FileHandle file, LabelStyle labelStyle) {
return fileNode(file, labelStyle, null);
}
/** @see #fileNode(FileHandle, FileFilter, Label.LabelStyle, Function) */
public static Node fileNode(FileHandle file, LabelStyle labelStyle, Function nodeConsumer) {
return fileNode(file, null, labelStyle, nodeConsumer);
}
/** @see #fileNode(FileHandle, FileFilter, Label.LabelStyle, Function) */
public static Node fileNode(FileHandle file, FileFilter filter, final LabelStyle labelStyle) {
return fileNode(file, filter, labelStyle, null);
}
/** passes an Accessor that creates labels representing the file name (with slash if it's a folder) using the given label style to {@link #fileNode(FileHandle, FileFilter, net.dermetfan.utils.Function, net.dermetfan.utils.Function)} (labelSupplier)
* @param labelStyle the {@link LabelStyle} to use for created labels
* @see #fileNode(FileHandle, FileFilter, Function, Function) */
public static Node fileNode(FileHandle file, FileFilter filter, final LabelStyle labelStyle, Function nodeConsumer) {
return fileNode(file, filter, new Function() {
@Override
public Label apply(FileHandle file) {
String name = file.name();
if(name.length() == 0) {
name = file.path();
name = name.substring(0, name.lastIndexOf('/'));
}
if(file.isDirectory())
name += File.separatorChar;
return new Label(name, labelStyle);
}
}, nodeConsumer);
}
/** @see #fileNode(FileHandle, FileFilter, Function, Function) */
public static Node fileNode(FileHandle file, FileFilter filter, Function labelSupplier) {
return fileNode(file, filter, labelSupplier, null);
}
/** creates an anonymous subclass of {@link Node} that recursively adds the children of the given file to it when being {@link Node#setExpanded(boolean) expanded} for the first time
* @param file the file to put in {@link Node#setObject(Object)}
* @param filter Filters children from being added. May be null to accept all files.
* @param labelSupplier supplies labels to use
* @param nodeConsumer Does something with nodes after they were created. May be null.
* @return the created Node */
public static Node fileNode(final FileHandle file, final FileFilter filter, final Function labelSupplier, final Function nodeConsumer) {
Label label = labelSupplier.apply(file);
Node node;
if(file.isDirectory()) {
final Node dummy = new Node(new Actor());
node = new Node(label) {
private boolean childrenAdded;
@Override
public void setExpanded(boolean expanded) {
if(expanded == isExpanded())
return;
if(expanded && !childrenAdded) {
if(filter != null)
for(FileHandle child : file.list(filter))
add(fileNode(file.child(child.name()), filter, labelSupplier, nodeConsumer));
else
for(FileHandle child : file.list())
add(fileNode(child, filter, labelSupplier, nodeConsumer));
childrenAdded = true;
remove(dummy);
}
super.setExpanded(expanded);
}
};
node.add(dummy);
if(nodeConsumer != null)
nodeConsumer.apply(dummy);
} else
node = new Node(label);
node.setObject(file);
if(nodeConsumer != null)
nodeConsumer.apply(node);
return node;
}
/** the style of this TreeFileChooser */
private Style style;
/** the Tree used to show files and folders */
private Tree tree;
/** the ScrollPane {@link #tree} is embedded in */
private ScrollPane treePane;
/** basic operation buttons */
private Button chooseButton, cancelButton;
/** Listener for {@link #tree}.
* {@link Button#setDisabled(boolean) Disables/enables} {@link #chooseButton} based on the {@link Tree#getSelection() selection} of {@link #tree} and {@link #isDirectoriesChoosable()} */
public final ClickListener treeListener = new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
Selection selection = tree.getSelection();
if(selection.size() < 1) {
chooseButton.setDisabled(true);
return;
}
if(!isDirectoriesChoosable()) {
Object lastObj = selection.getLastSelected().getObject();
if(lastObj instanceof FileHandle) {
FileHandle file = (FileHandle) lastObj;
if(file.isDirectory()) {
chooseButton.setDisabled(true);
return;
}
}
}
chooseButton.setDisabled(false);
}
};
/** Listener for {@link #chooseButton}.
* Calls {@link Listener#choose(Array)} or {@link Listener#choose(FileHandle)} depending on the {@link Tree#getSelection() selection} of {@link #tree} */
public final ClickListener chooseButtonListener = new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
if(chooseButton.isDisabled())
return;
Selection selection = tree.getSelection();
if(selection.size() < 1)
return;
if(selection.getMultiple() && selection.size() > 1) {
@SuppressWarnings("unchecked")
Array files = Pools.obtain(Array.class);
for(Node node : selection) {
Object object = node.getObject();
if(object instanceof FileHandle) {
FileHandle file = (FileHandle) object;
if(isDirectoriesChoosable() || !file.isDirectory())
files.add(file);
}
}
getListener().choose(files);
files.clear();
Pools.free(files);
} else {
Object object = selection.getLastSelected().getObject();
if(object instanceof FileHandle) {
FileHandle file = (FileHandle) object;
if(isDirectoriesChoosable() || !file.isDirectory())
getListener().choose(file);
}
}
}
};
/** Listener for {@link #cancelButton}.
* Calls {@link Listener#cancel()} of the {@link #getListener() listener} */
public final ClickListener cancelButtonListener = new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
getListener().cancel();
}
};
/** @param skin the skin to get a {@link Style} from
* @param listener the {@link #setListener(Listener) listener}
* @see #TreeFileChooser(Style, FileChooser.Listener) */
public TreeFileChooser(Skin skin, Listener listener) {
this(skin.get(Style.class), listener);
setSkin(skin);
}
/** @param skin the skin holding the {@link Style} to use
* @param styleName the {@link Skin#get(String, Class) name} of the {@link Style} to use
* @param listener the {@link #setListener(Listener) listener}
* @see #TreeFileChooser(Style, FileChooser.Listener)*/
public TreeFileChooser(Skin skin, String styleName, Listener listener) {
this(skin.get(styleName, Style.class), listener);
setSkin(skin);
}
/** @param style the {@link #style}
* @param listener the {@link #setListener(Listener) listener} */
public TreeFileChooser(Style style, Listener listener) {
super(listener);
this.style = style;
buildWidgets();
build();
}
/** @param file the {@link File} to {@link Tree#add(Node) add a root} for
* @return the added {@link #fileNode(FileHandle, FileFilter, Label.LabelStyle) file node} */
public Node add(FileHandle file) {
Node node = fileNode(file, handlingFileFilter, style.labelStyle);
tree.add(node);
return node;
}
/** builds {@link #chooseButton}, {@link #cancelButtonListener}, {@link #tree}, {@link #treePane} */
protected void buildWidgets() {
(tree = new Tree(style.treeStyle)).addListener(treeListener);
if(style.scrollPaneStyle != null)
treePane = new ScrollPane(tree, style.scrollPaneStyle);
else
treePane = new ScrollPane(tree);
(chooseButton = UIUtils.newButton(style.selectButtonStyle, "select")).addListener(chooseButtonListener);
chooseButton.setDisabled(true);
(cancelButton = UIUtils.newButton(style.cancelButtonStyle, "cancel")).addListener(cancelButtonListener);
}
@Override
protected void build() {
clearChildren();
treePane.setWidget(tree);
add(treePane).colspan(2).row();
add(chooseButton).fill();
add(cancelButton).fill();
}
/** @return the {@link #style} */
public Style getStyle() {
return style;
}
/** @param style the {@link #style} to set */
public void setStyle(Style style) {
this.style = style;
setBackground(style.background);
tree.setStyle(style.treeStyle);
chooseButton.setStyle(style.selectButtonStyle);
cancelButton.setStyle(style.cancelButtonStyle);
}
/** @return the {@link #tree} */
public Tree getTree() {
return tree;
}
/** @param tree the {@link #tree} to set */
public void setTree(Tree tree) {
this.tree.removeListener(treeListener);
(this.tree = tree).addListener(treeListener);
treePane.setWidget(tree);
}
/** @return the {@link #treePane} */
public ScrollPane getTreePane() {
return treePane;
}
/** @param treePane the {@link #treePane} to set */
public void setTreePane(ScrollPane treePane) {
treePane.setWidget(tree);
getCell(this.treePane).setActor(this.treePane = treePane);
}
/** @return the {@link #chooseButton} */
public Button getChooseButton() {
return chooseButton;
}
/** @param chooseButton the {@link #chooseButton} to set */
public void setChooseButton(Button chooseButton) {
this.chooseButton.removeListener(chooseButtonListener);
chooseButton.addListener(chooseButtonListener);
getCell(this.chooseButton).setActor(this.chooseButton = chooseButton);
}
/** @return the {@link #cancelButton} */
public Button getCancelButton() {
return cancelButton;
}
/** @param cancelButton the {@link #cancelButton} to set */
public void setCancelButton(Button cancelButton) {
this.cancelButton.removeListener(cancelButtonListener);
cancelButton.addListener(cancelButtonListener);
getCell(this.cancelButton).setActor(this.cancelButton = cancelButton);
}
/** defines styles for the widgets of a {@link TreeFileChooser}
* @author dermetfan */
public static class Style implements Serializable {
/** the style for the {@link TreeFileChooser#tree tree} */
public TreeStyle treeStyle;
/** the style for {@link TreeFileChooser#treePane} */
public ScrollPaneStyle scrollPaneStyle;
/** the style for the labels in the tree */
public LabelStyle labelStyle;
/** the button styles */
public ButtonStyle selectButtonStyle, cancelButtonStyle;
/** optional */
public Drawable background;
@Override
public void write(Json json) {
json.writeObjectStart("");
json.writeFields(this);
json.writeObjectEnd();
}
@Override
public void read(Json json, JsonValue jsonData) {
treeStyle = json.readValue("treeStyle", TreeStyle.class, jsonData);
if(jsonData.has("scrollPaneStyle"))
scrollPaneStyle = json.readValue("scrollPaneStyle", ScrollPaneStyle.class, jsonData);
labelStyle = json.readValue("labelStyle", LabelStyle.class, jsonData);
selectButtonStyle = UIUtils.readButtonStyle("selectButtonStyle", json, jsonData);
cancelButtonStyle = UIUtils.readButtonStyle("cancelButtonStyle", json, jsonData);
if(jsonData.has("background"))
background = json.readValue("background", Drawable.class, jsonData);
}
}
}