package nodebox.client;
import nodebox.node.NodeLibrary;
import nodebox.ui.Platform;
import nodebox.ui.Theme;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static nodebox.ui.SwingUtils.drawShadowText;
public class ExamplesBrowser extends JFrame {
private static final Image DEFAULT_EXAMPLE_IMAGE;
private static final File examplesDir;
private static final Pattern NUMBERS_PREFIX_PATTERN = Pattern.compile("^[0-9]+\\s");
static {
final File localDir = new File("examples");
if (localDir.isDirectory()) {
examplesDir = localDir;
} else {
examplesDir = nodebox.util.FileUtils.getApplicationFile("examples");
}
try {
DEFAULT_EXAMPLE_IMAGE = ImageIO.read(ExamplesBrowser.class.getResourceAsStream("/default-example.png"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private final JPanel categoriesPanel;
private final JPanel subCategoriesPanel;
private final JPanel examplesPanel;
public ExamplesBrowser() {
super("Examples");
setSize(800, 500);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setLocationByPlatform(true);
categoriesPanel = new CategoriesPanel();
categoriesPanel.setBackground(Color.WHITE);
subCategoriesPanel = new SubCategoriesPanel();
examplesPanel = new JPanel(new ExampleLayout(10, 10));
examplesPanel.setBackground(new Color(196, 196, 196));
JScrollPane examplesScroll = new JScrollPane(examplesPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
examplesScroll.setBorder(null);
examplesScroll.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
updateExamplesPanelSize();
}
});
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(categoriesPanel, BorderLayout.NORTH);
mainPanel.add(subCategoriesPanel, BorderLayout.WEST);
mainPanel.add(examplesScroll, BorderLayout.CENTER);
setContentPane(mainPanel);
if (Platform.onMac()) {
setJMenuBar(new NodeBoxMenuBar());
}
mainPanel.getActionMap().put("Reload", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
reload();
}
});
mainPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(Platform.getKeyStroke(KeyEvent.VK_R), "Reload");
reload();
}
/**
* Refresh the examples browser by loading everything from disk.
*/
private void reload() {
final List<Category> categories = parseCategories(examplesDir);
categoriesPanel.removeAll();
for (final Category category : categories) {
final CategoryButton b = new CategoryButton(category);
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
for (Component c : categoriesPanel.getComponents()) {
CategoryButton b = (CategoryButton) c;
b.setSelected(false);
}
b.setSelected(true);
selectCategory(category);
}
});
categoriesPanel.add(b);
}
categoriesPanel.validate();
categoriesPanel.repaint();
((CategoryButton) categoriesPanel.getComponent(0)).setSelected(true);
selectCategory(categories.get(0));
}
private void selectCategory(Category category) {
subCategoriesPanel.removeAll();
final List<SubCategory> subCategories = category.subCategories;
for (final SubCategory subCategory : subCategories) {
final SubCategoryButton b = new SubCategoryButton(subCategory);
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
for (Component c : subCategoriesPanel.getComponents()) {
SubCategoryButton b = (SubCategoryButton) c;
b.setSelected(false);
}
b.setSelected(true);
selectSubCategory(subCategory);
}
});
subCategoriesPanel.add(b);
}
subCategoriesPanel.validate();
subCategoriesPanel.repaint();
((SubCategoryButton) subCategoriesPanel.getComponent(0)).setSelected(true);
selectSubCategory(subCategories.get(0));
}
private void selectSubCategory(SubCategory subCategory) {
List<Example> examples = subCategory.examples;
examplesPanel.removeAll();
for (final Example e : examples) {
ExampleButton b = new ExampleButton(e.title, new ImageIcon(e.thumbnail));
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
openExample(e);
}
});
examplesPanel.add(b);
}
updateExamplesPanelSize();
examplesPanel.validate();
examplesPanel.repaint();
}
private void updateExamplesPanelSize() {
Component scrollPane = examplesPanel.getParent();
int scrollPaneWidth = scrollPane.getWidth();
ExampleLayout exampleLayout = (ExampleLayout) examplesPanel.getLayout();
int contentsHeight = exampleLayout.calculateHeight(examplesPanel, scrollPaneWidth);
examplesPanel.setSize(scrollPaneWidth, contentsHeight);
}
private void openExample(Example example) {
Application.getInstance().openExample(example.file);
}
public static List<Category> parseCategories(File parentDirectory) {
File[] directories = parentDirectory.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory() && !file.isHidden();
}
});
ArrayList<Category> categories = new ArrayList<Category>();
for (File d : directories) {
String name = fileToTitle(d);
List<SubCategory> subCategories = parseSubCategories(d);
categories.add(new Category(name, d, subCategories));
}
return categories;
}
private static List<SubCategory> parseSubCategories(File directory) {
File[] directories = directory.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory() && !file.isHidden();
}
});
ArrayList<SubCategory> subCategories = new ArrayList<SubCategory>();
for (File d : directories) {
String name = fileToTitle(d);
List<Example> examples = parseExamples(d);
subCategories.add(new SubCategory(name, d, examples));
}
return subCategories;
}
public static List<Example> parseExamples(File directory) {
File[] directories = directory.listFiles(new FileFilter() {
@Override
public boolean accept(File projectDirectory) {
return projectDirectory.isDirectory() && nodeBoxFileForDirectory(projectDirectory).exists();
}
});
ArrayList<Example> examples = new ArrayList<Example>();
for (File projectDirectory : directories) {
File nodeBoxFile = nodeBoxFileForDirectory(projectDirectory);
Map<String, String> propertyMap = NodeLibrary.parseHeader(nodeBoxFile);
examples.add(Example.fromNodeLibrary(nodeBoxFile, propertyMap));
}
return examples;
}
public static String fileToTitle(File file) {
String baseName = FileUtils.getBaseName(file.getName());
return NUMBERS_PREFIX_PATTERN.matcher(baseName).replaceFirst("");
}
public static File nodeBoxFileForDirectory(File projectDirectory) {
return new File(projectDirectory, projectDirectory.getName() + ".ndbx");
}
public static Image thumbnailForLibraryFile(File nodeBoxFile) {
if (nodeBoxFile == null) return DEFAULT_EXAMPLE_IMAGE;
File projectDirectory = nodeBoxFile.getParentFile();
String baseName = FileUtils.getBaseName(nodeBoxFile.getName());
File imageFile = new File(projectDirectory, baseName + ".png");
if (imageFile.exists()) {
try {
return ImageIO.read(imageFile);
} catch (IOException e) {
return DEFAULT_EXAMPLE_IMAGE;
}
} else {
return DEFAULT_EXAMPLE_IMAGE;
}
}
private static String getProperty(Map<String, String> propertyMap, String key, String defaultValue) {
if (propertyMap.containsKey(key)) {
return propertyMap.get(key);
} else {
return defaultValue;
}
}
public static final class Category {
public final String name;
public final File directory;
public final List<SubCategory> subCategories;
public Category(String name, File directory, List<SubCategory> subCategories) {
this.name = name;
this.directory = directory;
this.subCategories = subCategories;
}
}
public static final class SubCategory {
public final String name;
public final File directory;
public final List<Example> examples;
public SubCategory(String name, File directory, List<Example> examples) {
this.name = name;
this.directory = directory;
this.examples = examples;
}
}
public static class Example {
public final File file;
public final String title;
public final String description;
public final Image thumbnail;
public static Example fromNodeLibrary(File nodeBoxFile, Map<String, String> propertyMap) {
String title = getProperty(propertyMap, "title", fileToTitle(nodeBoxFile));
String description = getProperty(propertyMap, "description", "");
Image thumbnail = thumbnailForLibraryFile(nodeBoxFile);
return new Example(nodeBoxFile, title, description, thumbnail);
}
public Example(File file, String title, String description, Image thumbnail) {
this.file = file;
this.title = title;
this.description = description;
this.thumbnail = thumbnail;
}
}
private static class CategoriesPanel extends JPanel {
private CategoriesPanel() {
super(new FlowLayout(FlowLayout.LEADING, 0, 0));
setSize(300, 32);
}
@Override
protected void paintComponent(Graphics g) {
g.setColor(new Color(210, 210, 210));
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(new Color(225, 225, 225));
g.drawLine(0, 0, getWidth(), 0);
g.setColor(new Color(136, 136, 136));
g.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1);
}
}
private static void drawVLine(Graphics g, int x, int y, int height) {
g.drawLine(x, y, x, y + height);
}
private static void drawHLine(Graphics g, int x, int y, int width) {
g.drawLine(x, y, x + width, y);
}
private static class SubCategoriesPanel extends JPanel {
private SubCategoriesPanel() {
super();
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setSize(300, 32);
setMinimumSize(new Dimension(150, 32));
setMaximumSize(new Dimension(150, 1000));
setPreferredSize(new Dimension(150, 500));
}
@Override
protected void paintComponent(Graphics g) {
g.setColor(new Color(153, 153, 153));
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(new Color(146, 146, 146));
drawVLine(g, getWidth() - 3, 0, getHeight());
g.setColor(new Color(133, 133, 133));
drawVLine(g, getWidth() - 2, 0, getHeight());
g.setColor(new Color(112, 112, 112));
drawVLine(g, getWidth() - 1, 0, getHeight());
}
}
private static class CategoryButton extends JToggleButton {
private CategoryButton(Category category) {
super(category.name);
forceSize(this, 120, 32);
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
@Override
protected void paintComponent(Graphics g) {
int hMargin = 4;
int vMargin = 3;
Rectangle r = new Rectangle(0, 0, getWidth() - 1, getHeight() - 1);
r.grow(-hMargin, -vMargin);
if (isSelected()) {
g.setColor(new Color(198, 198, 198));
g.fillRect(r.x, r.y, r.width, r.height);
g.setColor(new Color(166, 166, 166));
g.drawRect(r.x + 1, r.y + 1, r.width - 2, r.height - 2);
g.setColor(new Color(119, 119, 119));
g.drawLine(r.x, r.y, r.x + r.width, r.y);
g.drawLine(r.x, r.y, r.x, r.y + r.height);
g.setColor(new Color(237, 237, 237));
g.drawLine(r.x, r.y + r.height, r.x + r.width, r.y + r.height);
g.drawLine(r.x + r.width, r.y, r.x + r.width, r.y + r.height);
} else {
g.setColor(new Color(179, 179, 179));
g.drawLine(0, 2, 0, getHeight() - 4);
g.setColor(new Color(237, 237, 237));
g.drawLine(1, 2, 1, getHeight() - 4);
}
g.setFont(Theme.SMALL_BOLD_FONT);
g.setFont(Theme.SMALL_BOLD_FONT);
if (isSelected()) {
g.setColor(Theme.TEXT_NORMAL_COLOR);
} else {
g.setColor(Theme.TEXT_HEADER_COLOR);
//g.setColor(new Color(160, 160, 160));
}
drawShadowText((Graphics2D) g, getText(), 10, 18);
//g2.setColor(Color.GREEN);
//g2.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}
}
private class SubCategoryButton extends JToggleButton {
public SubCategoryButton(SubCategory subCategory) {
super(subCategory.name);
forceSize(this, 150, 32);
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
@Override
protected void paintComponent(Graphics g) {
if (isSelected()) {
g.setColor(new Color(196, 196, 196));
g.fillRect(0, 0, getWidth(), getHeight());
} else if (isLastButton()) {
g.setColor(new Color(255, 255, 255, 50));
drawHLine(g, 0, 0, getWidth() - 2);
g.setColor(new Color(0, 0, 0, 50));
drawHLine(g, 0, getHeight() - 2, getWidth() - 2);
g.setColor(new Color(255, 255, 255, 50));
drawHLine(g, 0, getHeight() - 1, getWidth() - 2);
} else {
g.setColor(new Color(255, 255, 255, 50));
drawHLine(g, 0, 0, getWidth() - 1);
g.setColor(new Color(0, 0, 0, 50));
drawHLine(g, 0, getHeight() - 1, getWidth() - 1);
}
g.setFont(Theme.SMALL_BOLD_FONT);
g.setColor(Theme.TEXT_NORMAL_COLOR);
if (isSelected()) {
drawShadowText((Graphics2D) g, getText(), 5, 20);
} else {
drawShadowText((Graphics2D) g, getText(), 5, 20, Theme.DEFAULT_SHADOW_COLOR, 1);
}
}
private boolean isLastButton() {
return getParent().getComponent(getParent().getComponentCount() - 1) == SubCategoryButton.this;
}
}
private static class ExampleButton extends JButton {
private ExampleButton(String title, Icon icon) {
super(title, icon);
setSize(150, 125);
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
@Override
protected void paintComponent(Graphics g) {
getIcon().paintIcon(this, g, 0, 0);
g.setColor(new Color(140, 140, 140));
drawHLine(g, 0, 0, 149);
drawVLine(g, 0, 0, 100);
g.setColor(new Color(237, 237, 237));
drawHLine(g, 0, 100, 149);
drawVLine(g, 149, 0, 100);
g.setColor(new Color(166, 166, 166, 100));
g.drawRect(1, 1, 147, 98);
g.setFont(Theme.SMALL_BOLD_FONT);
g.setColor(Theme.TEXT_NORMAL_COLOR);
drawShadowText((Graphics2D) g, getText(), 0, 113);
}
}
private static class ExampleLayout implements LayoutManager {
private int hGap, vGap;
private ExampleLayout(int hGap, int vGap) {
this.hGap = hGap;
this.vGap = vGap;
}
@Override
public void addLayoutComponent(String s, Component component) {
}
@Override
public void removeLayoutComponent(Component component) {
}
@Override
public Dimension preferredLayoutSize(Container container) {
return container.getSize();
}
@Override
public Dimension minimumLayoutSize(Container container) {
return container.getSize();
}
/**
* Given a fixed width, what should be the height of this container?
*
* @param containerWidth The width of the parent component.
* @return The height in pixels.
*/
public int calculateHeight(Container container, int containerWidth) {
int y = vGap;
int x = hGap;
int maxHeightForRow = 0;
for (Component c : container.getComponents()) {
int width = c.getWidth();
int height = c.getHeight();
maxHeightForRow = Math.max(maxHeightForRow, height);
if (x > containerWidth - width + hGap * 2) {
x = hGap;
y += maxHeightForRow + vGap;
maxHeightForRow = 0;
}
x += width + hGap;
}
return y + 125 + vGap;
}
@Override
public void layoutContainer(Container container) {
int y = vGap;
int x = hGap;
int maxHeightForRow = 0;
for (Component c : container.getComponents()) {
int width = c.getWidth();
int height = c.getHeight();
maxHeightForRow = Math.max(maxHeightForRow, height);
if (x > container.getWidth() - width) {
x = hGap;
y += maxHeightForRow + vGap;
maxHeightForRow = 0;
}
c.setBounds(x, y, width, height);
x += width + hGap;
}
}
}
private static void forceSize(Component c, int width, int height) {
Dimension d = new Dimension(width, height);
c.setSize(d);
c.setMinimumSize(d);
c.setMaximumSize(d);
c.setPreferredSize(d);
}
public static void main(String[] args) {
ExamplesBrowser browser = new ExamplesBrowser();
browser.setVisible(true);
browser.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}