org.dishevelled.brainstorm.BrainStorm.java Source code

Java tutorial

Introduction

Here is the source code for org.dishevelled.brainstorm.BrainStorm.java

Source

/*
    
dsh-brainstorm  Brain storm, a fit of mental confusion or excitement.
Copyright (c) 2008-2013 held jointly by the individual authors.
    
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 3 of the License, or (at
your option) any later version.
    
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
License for more details.
    
You should have received a copy of the GNU Lesser General Public License
along with this library;  if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.
    
> http://www.fsf.org/licensing/licenses/lgpl.html
> http://www.opensource.org/licenses/lgpl-license.php
    
*/
package org.dishevelled.brainstorm;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;

import static java.awt.RenderingHints.*;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;

import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;

import java.awt.image.BufferedImage;

import java.io.File;

import java.util.Arrays;
import java.util.List;

import javax.swing.Action;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.InputMap;
import javax.swing.KeyStroke;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;

import org.apache.commons.lang.StringUtils;

import org.apache.commons.io.FileUtils;

import org.dishevelled.commandline.ArgumentList;
import org.dishevelled.commandline.CommandLine;
import org.dishevelled.commandline.CommandLineParser;
import org.dishevelled.commandline.CommandLineParseException;
import org.dishevelled.commandline.Switch;
import org.dishevelled.commandline.Usage;

import org.dishevelled.commandline.argument.IntegerArgument;
import org.dishevelled.commandline.argument.StringArgument;

/**
 * Brain storm.
 *
 * @author  Michael Heuer
 * @version $Revision$ $Date$
 */
public final class BrainStorm extends JPanel implements Runnable {
    /** File name. */
    private final String fileName;

    /** Font name. */
    private final String fontName;

    /** Font size. */
    private final int fontSize;

    /** Number of rows to display. */
    private final int rows;

    /** Background color. */
    private final Color backgroundColor;

    /** Text color. */
    private final Color textColor;

    /** Default font name, <code>null</code>. */
    private static final String DEFAULT_FONT_NAME = null;

    /** Default font size, <code>28</code>. */
    private static final int DEFAULT_FONT_SIZE = 28;

    /** Default number of rows to display, <code>2</code>. */
    private static final int DEFAULT_ROWS = 2;

    /** Default background color, <code>black</code>. */
    private static final String DEFAULT_BACKGROUND_COLOR = "black";

    /** Default text color, <code>rgb(200, 200, 200)</code>. */
    private static final String DEFAULT_TEXT_COLOR = "rgb(200, 200, 200)";

    /** Text area. */
    private JTextArea textArea;

    /** Hidden cursor. */
    private Cursor hiddenCursor;

    /** Placeholder. */
    private Component placeholder;

    /**
     * Create a new brain storm with the specified file name.
     *
     * @param fileName file name
     */
    public BrainStorm(final String fileName) {
        this(fileName, DEFAULT_FONT_NAME, DEFAULT_FONT_SIZE, DEFAULT_ROWS, DEFAULT_BACKGROUND_COLOR,
                DEFAULT_TEXT_COLOR);
    }

    /**
     * Create a new brain storm with the specified file name.
     *
     * @param fileName file name
     */
    public BrainStorm(final String fileName, final String fontName, final int fontSize, final int rows,
            final String backgroundColor, final String textColor) {
        super();
        this.fileName = fileName;
        this.fontName = fontName;
        this.fontSize = fontSize;
        this.rows = rows;
        this.backgroundColor = parse(backgroundColor);
        this.textColor = parse(textColor);
        setOpaque(true);
        initComponents();
        layoutComponents();
    }

    /**
     * Initialize components.
     */
    private void initComponents() {
        Font font = new Font(chooseFontName(), Font.PLAIN, fontSize);
        hiddenCursor = createHiddenCursor();
        textArea = new JTextArea() {
            /** {@inheritDoc} */
            protected void paintComponent(final Graphics graphics) {
                Graphics2D g2 = (Graphics2D) graphics;
                g2.setRenderingHint(KEY_FRACTIONALMETRICS, VALUE_FRACTIONALMETRICS_ON);
                g2.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_LCD_HRGB);
                super.paintComponent(g2);
            }
        };
        textArea.setFont(font);
        textArea.setOpaque(true);
        textArea.setCursor(hiddenCursor);
        textArea.setBackground(backgroundColor);
        textArea.setForeground(textColor);
        textArea.setRows(rows);
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);

        // clear all input mappings
        InputMap inputMap = textArea.getInputMap();
        while (inputMap != null) {
            inputMap.clear();
            inputMap = inputMap.getParent();
        }
        // re-add select default input mappings
        textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "insert-break");
        textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "insert-tab");
        textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "delete-previous");
        int keyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, keyMask), "delete-previous-word");

        // add new input mappings
        textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, keyMask), "increase-font-size");
        textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, keyMask), "decrease-font-size");
        textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_S, keyMask), "save");
        textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "quit");
        textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, keyMask), "quit");

        Action increaseFontSizeAction = new IncreaseFontSizeAction();
        Action decreaseFontSizeAction = new DecreaseFontSizeAction();
        Action saveAction = new SaveAction();
        Action quitAction = new QuitAction();

        textArea.getActionMap().put("increase-font-size", increaseFontSizeAction);
        textArea.getActionMap().put("decrease-font-size", decreaseFontSizeAction);
        textArea.getActionMap().put("save", saveAction);
        textArea.getActionMap().put("quit", quitAction);

        placeholder = Box.createGlue();
    }

    /**
     * Choose an installed font name from a list of preferred font names.
     */
    private String chooseFontName() {
        List<String> preferredNames = Arrays
                .asList(new String[] { "Luxi Sans", "Calibri", "Corbel", "Lucida Sans Unicode", "Lucida Grande" });
        List<String> actualNames = Arrays
                .asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
        for (String actual : actualNames) {
            if ((fontName != null) && (fontName.equalsIgnoreCase(actual))) {
                return actual;
            }
        }
        for (String actual : actualNames) {
            for (String preferred : preferredNames) {
                if (preferred.equalsIgnoreCase(actual)) {
                    return actual;
                }
            }
        }
        return "Dialog";
    }

    /**
     * Calculate the placeholder size based on the current window size.
     */
    private void calculatePlaceholderSize() {
        int height = (int) (getHeight() / 3);
        placeholder = Box.createVerticalStrut(height);
    }

    /**
     * Calculate the text area size in rows and columns based on the current
     * window size and text area font size.
     */
    private void calculateTextAreaSize() {
        double width = (double) getWidth();
        FontRenderContext frc = new FontRenderContext(new AffineTransform(), true, true);
        TextLayout textLayout = new TextLayout("W", textArea.getFont(), frc);
        Rectangle2D textBounds = textLayout.getBounds();
        int columns = Math.min(45, (int) (width / textBounds.getWidth()) - 4);
        textArea.setColumns(columns);
    }

    /**
     * Layout components.
     */
    private void layoutComponents() {
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        JScrollPane scrollPane = new JScrollPane(textArea);
        scrollPane.setBorder(null);
        scrollPane.setOpaque(true);
        scrollPane.setBackground(backgroundColor);
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
        add(placeholder);

        JPanel flow = new JPanel();
        flow.setLayout(new FlowLayout());
        flow.setOpaque(false);
        flow.add(scrollPane);

        add(flow);
        add(Box.createVerticalGlue());
        add(Box.createVerticalGlue());
    }

    /**
     * Save.
     */
    private void save() {
        final String baseName = StringUtils.isBlank(fileName) ? getFirstWord()
                : fileName.substring(0, fileName.indexOf("."));
        final String text = textArea.getText();
        (new SwingWorker<String, Object>() {
            /** @{inheritDoc} */
            public String doInBackground() {
                try {
                    File file = new File(baseName + ".txt");
                    File revisions = new File(baseName + "-revisions");
                    revisions.mkdir();
                    File latestRevision = new File(revisions,
                            baseName + "-rev" + System.currentTimeMillis() + ".txt");
                    if (file.exists()) {
                        FileUtils.copyFile(file, latestRevision);
                    }
                    FileUtils.writeStringToFile(file, text);
                    return "Saved";
                } catch (Exception e) {
                    e.printStackTrace();
                    return "Not saved";
                }

                // todo:  show status?
            }
        }).execute();
    }

    /**
     * Return the first word of text, or <code>untitled</code> if the text is blank.
     *
     * @return the first word of text, or <code>untitled</code> if the text is blank
     */
    private String getFirstWord() {
        String firstWord = textArea.getText().split("\\W")[0];
        return (StringUtils.isBlank(firstWord)) ? "untitled" : firstWord;
    }

    /**
     * Quit.
     */
    private void quit() {
        save();
        System.exit(0);
    }

    /**
     * Show preferences.
     */
    private void showPreferences() {
        // empty
    }

    /**
     * Create and return a new hidden cursor, that is a fully transparent one pixel custom cursor.
     *
     * @return a new hidden cursor
     */
    private Cursor createHiddenCursor() {
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Dimension size = toolkit.getBestCursorSize(1, 1);
        BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = image.createGraphics();
        graphics.setBackground(new Color(0.0f, 0.0f, 0.0f, 0.0f));
        graphics.clearRect(0, 0, size.width, size.height);
        graphics.dispose();
        return toolkit.createCustomCursor(image, new Point(0, 0), "hiddenCursor");
    }

    /** {@inheritDoc} */
    public void paintComponent(final Graphics graphics) {
        Graphics2D g = (Graphics2D) graphics;
        Paint oldPaint = g.getPaint();
        g.setPaint(backgroundColor);
        g.fill(getBounds());
        g.setPaint(oldPaint);
    }

    /** {@inheritDoc} */
    public void run() {
        JFrame f = new JFrame("Brain storm");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add("Center", this);
        f.setResizable(false);
        f.setUndecorated(true);
        // hide cursor on linux and windows platforms
        f.setCursor(hiddenCursor);
        GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(f);
        f.validate();

        SwingUtilities.invokeLater(new Runnable() {
            /** {@inheritDoc} */
            public void run() {
                calculatePlaceholderSize();
                calculateTextAreaSize();
                validate();
                textArea.scrollRectToVisible(
                        new Rectangle(0, textArea.getHeight() - 1, textArea.getWidth(), textArea.getHeight()));
            }
        });

        // save every five minutes
        Timer t = new Timer(5 * 60 * 1000, new ActionListener() {
            /** {@inheritDoc} */
            public void actionPerformed(final ActionEvent event) {
                save();
            }
        });
        t.setRepeats(true);
        t.start();
    }

    /**
     * Increase font size action.
     */
    private class IncreaseFontSizeAction extends AbstractAction {
        /**
         * Create a new increase font size action.
         */
        IncreaseFontSizeAction() {
            super("Increase font size");
        }

        /** {@inheritDoc} */
        public void actionPerformed(final ActionEvent event) {
            Font oldFont = textArea.getFont();
            Font font = oldFont.deriveFont(oldFont.getSize2D() * 1.1f);
            textArea.setFont(font);
            calculateTextAreaSize();
            validate();
            textArea.scrollRectToVisible(
                    new Rectangle(0, textArea.getHeight() - 1, textArea.getWidth(), textArea.getHeight()));
        }
    }

    /**
     * Decrease font size action.
     */
    private class DecreaseFontSizeAction extends AbstractAction {
        /**
         * Create a new decrease font size action.
         */
        DecreaseFontSizeAction() {
            super("Decrease font size");
        }

        /** {@inheritDoc} */
        public void actionPerformed(final ActionEvent event) {
            Font oldFont = textArea.getFont();
            Font font = oldFont.deriveFont(oldFont.getSize2D() * 0.9f);
            textArea.setFont(font);
            calculateTextAreaSize();
            validate();
            textArea.scrollRectToVisible(
                    new Rectangle(0, textArea.getHeight() - 1, textArea.getWidth(), textArea.getHeight()));
        }
    }

    /**
     * Save action.
     */
    private class SaveAction extends AbstractAction {
        /**
         * Create a new save action.
         */
        SaveAction() {
            super("Save");
        }

        /** {@inheritDoc} */
        public void actionPerformed(final ActionEvent event) {
            save();
        }
    }

    /**
     * Quit action.
     */
    private class QuitAction extends AbstractAction {
        /**
         * Create a new quit action.
         */
        QuitAction() {
            super("Quit");
        }

        /** {@inheritDoc} */
        public void actionPerformed(final ActionEvent event) {
            quit();
        }
    }

    /**
     * Show preferences action.
     */
    private class ShowPreferencesAction extends AbstractAction {
        /**
         * Create a new show preferences action.
         */
        ShowPreferencesAction() {
            super("Show preferences");
        }

        /** {@inheritDoc} */
        public void actionPerformed(final ActionEvent event) {
            showPreferences();
        }
    }

    // todo:  use parseColor impl from elsewhere
    private static Color parse(final String value) {
        if ("black".equals(value)) {
            return Color.BLACK;
        } else {
            return Color.LIGHT_GRAY;
        }
    }

    /**
     * Main.
     *
     * @param args command line arguments
     */
    public static final void main(final String[] args) {
        // hide cursor on mac platform
        System.setProperty("apple.awt.fullscreenhidecursor", "true");

        CommandLine commandLine = null;
        ArgumentList arguments = null;
        try {
            Switch help = new Switch("h", "help", "display help and usage text");
            StringArgument fileName = new StringArgument("f", "file-name", "file name", false);
            StringArgument fontName = new StringArgument("n", "font-name",
                    "font name, default " + DEFAULT_FONT_NAME, false);
            IntegerArgument fontSize = new IntegerArgument("z", "font-size",
                    "font size, default " + DEFAULT_FONT_SIZE, false);
            IntegerArgument rows = new IntegerArgument("r", "rows",
                    "number of rows to display, default " + DEFAULT_ROWS, false);
            StringArgument backgroundColor = new StringArgument("b", "background-color",
                    "background color, default " + DEFAULT_BACKGROUND_COLOR, false);
            StringArgument textColor = new StringArgument("t", "text-color",
                    "text color, default " + DEFAULT_TEXT_COLOR, false);
            arguments = new ArgumentList(help, fileName, fontName, fontSize, rows, backgroundColor, textColor);
            commandLine = new CommandLine(args);
            CommandLineParser.parse(commandLine, arguments);

            if (help.wasFound()) {
                System.out.println(
                        "Brain storm, a fit of mental confusion or excitement.\n\nCopyright (c) 2008-2013 held jointly by the individual authors.\nLicensed under the GNU Lesser General Public License (LGPL). \n");
                Usage.usage("java BrainStorm [args]", null, commandLine, arguments, System.out);
            } else {
                SwingUtilities.invokeLater(new BrainStorm(fileName.getValue(), fontName.getValue(DEFAULT_FONT_NAME),
                        fontSize.getValue(DEFAULT_FONT_SIZE), rows.getValue(DEFAULT_ROWS),
                        backgroundColor.getValue(DEFAULT_BACKGROUND_COLOR),
                        textColor.getValue(DEFAULT_TEXT_COLOR)));
            }
        } catch (CommandLineParseException e) {
            Usage.usage("java BrainStorm [args]", e, commandLine, arguments, System.err);
        }
    }
}