net.daimonin.client3d.editor.main.Editor3D.java Source code

Java tutorial

Introduction

Here is the source code for net.daimonin.client3d.editor.main.Editor3D.java

Source

/*
 * This source file is part of Daimonin (http://daimonin.sourceforge.net)
 * Copyright (c) 2007 The Daimonin Team
 * Also see acknowledgements in Readme.html
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * In addition, as a special exception, the copyright holders of client3d give
 * you permission to combine the client3d program with lgpl libraries of your
 * choice and/or with the fmod libraries.
 * You may copy and distribute such a system following the terms of the GNU GPL
 * for client3d and the licenses of the other code concerned.
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA, or go to
 * http://www.gnu.org/licenses/licenses.html
 */
package net.daimonin.client3d.editor.main;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.renderable.ParameterBlock;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import javax.imageio.ImageIO;
import javax.media.jai.BorderExtenderConstant;
import javax.media.jai.JAI;

import net.daimonin.client3d.editor.object.ImageSetImage;
import net.daimonin.client3d.editor.ui.GeneratePanel;
import net.daimonin.client3d.editor.ui.MainFrame;
import net.daimonin.client3d.editor.ui.PreferencesPanel;
import net.daimonin.client3d.editor.util.FilenameFilterPNG;
import net.daimonin.client3d.editor.util.binpacker.BinPacker;
import net.daimonin.client3d.editor.util.binpacker.Rect;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

/**
 * The editor for the Daimonin 3D-Client.
 * 
 * @author Rumbuff
 */
public class Editor3D {

    private static final String CONF_RESTRICT_SIZE_HEIGHT = "imageset.maxHeight";

    private static final String CONF_RESTRICT_SIZE_WIDTH = "imageset.maxWidth";

    private static final String CONF_RESTRICT_SIZE = "imageset.restrictSize";

    private static final String CONF_FILENAME_IMAGESET_XML = "imageset.filename.xml";

    private static final String CONF_FILENAME_IMAGESET = "imageset.filename";

    private static final String CONF_BORDER_COLOR_BLUE = "imageset.border.color.blue";

    private static final String CONF_BORDER_COLOR_GREEN = "imageset.border.color.green";

    private static final String CONF_BORDER_COLOR_RED = "imageset.border.color.red";

    private static final String CONF_BORDER_SIZE = "imageset.border.size";

    private static final String CONF_IMAGES_DIR = "images.dir";

    /**
    * Current version number.
    */
    public static final String VERSION = "0.2.0";

    /**
    * Maximum border size.
    */
    private static final int BORDERSIZEMAX = 3;

    /**
    * Maximum border color.
    */
    private static final int BORDERCOLORMAX = 255;

    /**
    * Storage for configuration.
    */
    private static PropertiesConfiguration conf;

    /*
    * Configuration properties.
    */
    /** Name of the Imageset.PNG * */
    public static String imageset;

    /** Name of the GUI_ImageSet.xml * */
    public static String imagesetxml;

    /** Starting directory to search for PNGs * */
    public static String startingDir;

    /** Border size for all PNGs * */
    public static String borderSize;

    /** Border color for all PNGs, red * */
    public static String borderColorR;

    /** Border color for all PNGs, green * */
    public static String borderColorG;

    /** Border color for all PNGs, blue * */
    public static String borderColorB;

    /** If the generated Imageset should be restricted in size * */
    public static boolean restrictImageSize;

    /** Max. width, if size restricted * */
    public static int maxWidth;

    /** Max. height, if size restricted * */
    public static int maxHeight;

    /** Dimension of the packed imageset * */
    private static int[] dimension;

    /**
    * ImageSetImages, all read PNGs.
    */
    private static List<ImageSetImage> images;

    /**
    * Main window.
    */
    private static MainFrame frame;

    /**
    * @param args Command line parameters.
    */
    public static void main(final String[] args) {

        // read config file and set config values
        conf = Editor3D.readConfig();
        imageset = conf.getString(CONF_FILENAME_IMAGESET);
        imagesetxml = conf.getString(CONF_FILENAME_IMAGESET_XML);
        startingDir = conf.getString(CONF_IMAGES_DIR);
        borderSize = conf.getString(CONF_BORDER_SIZE);
        borderColorR = conf.getString(CONF_BORDER_COLOR_RED);
        borderColorG = conf.getString(CONF_BORDER_COLOR_GREEN);
        borderColorB = conf.getString(CONF_BORDER_COLOR_BLUE);
        restrictImageSize = conf.getBoolean(CONF_RESTRICT_SIZE);
        maxWidth = conf.getInt(CONF_RESTRICT_SIZE_WIDTH);
        maxHeight = conf.getInt(CONF_RESTRICT_SIZE_HEIGHT);

        Editor3D.frame = new MainFrame();
        Editor3D.frame.setVisible(true);
        printInfo("Welcome to Daimonin 3D GUI-editor " + VERSION);
    }

    /**
    * Gets the editor's configuration.
    * 
    * @return The editor's configuration.
    */
    private static PropertiesConfiguration readConfig() {
        try {
            final String config = "editor3d.config";
            // test if confFile exists
            if (!new File(config).exists()) {
                exit(1, "Configuration file '" + config + "' does not exist!");
            }
            return new PropertiesConfiguration(config);
        } catch (ConfigurationException e) {
            e.printStackTrace();
            printError(e.getMessage());
            return null;
        }
    }

    /**
    * Builds a single PNG out of all ImageSetImages, considering their calculated
    * coordinates.
    * 
    * @param fileNameImageSet Name of resulting PNG.
    * @param dimension [width, height] of the resulting PNG. where 0 is maximum
    *          compression, 1 is no compression at all.
    * @throws IOException IOException.
    */
    private static void writeImageSet(final String fileNameImageSet, final int[] dimension) throws IOException {

        BufferedImage bigImg = new BufferedImage(dimension[0], dimension[1], BufferedImage.TYPE_INT_ARGB);
        Graphics2D big = bigImg.createGraphics();
        for (int i = 0; i < images.size(); i++) {
            if (images.get(i).getBorderSize() > 0) {
                ParameterBlock params = new ParameterBlock();
                params.addSource(images.get(i).getImage());
                params.add(images.get(i).getBorderSize()); // left pad
                params.add(images.get(i).getBorderSize()); // right pad
                params.add(images.get(i).getBorderSize()); // top pad
                params.add(images.get(i).getBorderSize()); // bottom pad
                params.add(new BorderExtenderConstant(new double[] { images.get(i).getBorderColor().getRed(),
                        images.get(i).getBorderColor().getGreen(), images.get(i).getBorderColor().getBlue(),
                        BORDERCOLORMAX }));

                big.drawImage(JAI.create("border", params).getAsBufferedImage(), images.get(i).getPosX(),
                        images.get(i).getPosY(), null);

            } else {
                big.drawImage(images.get(i).getImage(), images.get(i).getPosX(), images.get(i).getPosY(), null);
            }
        }

        big.dispose();
        ImageIO.write(bigImg, "png", new File(fileNameImageSet));
        printInfo(System.getProperty("user.dir") + "/" + imageset + " created");
    }

    /**
    * Builds image groups/categories by file name, creates and writes the XML
    * containing all images with their attributes.
    * 
    * @param fileNameImageSet Name of resulting PNG.
    * @param fileNameXML Name of the XML.
    * @throws IOException IOException.
    */
    private static void writeXML(final String fileNameImageSet, final String fileNameXML) throws IOException {

        // group ImageSetImages by name/category.
        // the left side of the image file name up to the '_' is taken as the
        // category
        Hashtable<String, List<ImageSetImage>> names = new Hashtable<String, List<ImageSetImage>>();
        for (int i = 0; i < images.size(); i++) {
            if (!names.containsKey(images.get(i).getName())) {
                List<ImageSetImage> values = new ArrayList<ImageSetImage>();
                values.add(images.get(i));
                names.put(images.get(i).getName(), values);
            } else {
                names.get(images.get(i).getName()).add(images.get(i));
            }
        }

        // check if every category has a default state (except FontExtensions)
        Enumeration<String> keys2 = names.keys();
        while (keys2.hasMoreElements()) {
            List<ImageSetImage> img2 = names.get(keys2.nextElement());
            if (!"fontextensions".equals(img2.get(0).getName())) {
                boolean hasDefault = false;
                for (int i = 0; i < img2.size(); i++) {
                    if ("default".equals(img2.get(i).getState().toLowerCase())) {
                        hasDefault = true;
                    }
                }
                if (!hasDefault) {
                    printError("WARNING: image category '" + img2.get(0).getName()
                            + "' has no image with a default state!");
                }
            }
        }

        // create the XML structure
        Document document = DocumentHelper.createDocument();
        Element root = document.addElement("ImageSet").addAttribute("file", fileNameImageSet);

        List<ImageSetImage> fntex = names.get("fontextensions");
        if (fntex != null) {
            Element category = root.addElement("ImageFntExt").addAttribute("name", fntex.get(0).getName());
            for (int i = 0; i < fntex.size(); i++) {
                category.addElement("State").addAttribute("name", fntex.get(i).getState())
                        .addAttribute("posX", String.valueOf(fntex.get(i).getPosX() + fntex.get(i).getBorderSize()))
                        .addAttribute("posY", String.valueOf(fntex.get(i).getPosY() + fntex.get(i).getBorderSize()))
                        .addAttribute("width", String.valueOf(fntex.get(i).getWidth()))
                        .addAttribute("height", String.valueOf(fntex.get(i).getHeight()));
            }
            names.remove("fontextensions");
        }

        List<ImageSetImage> mouse = names.get("mousecursor");
        if (mouse != null) {
            Element category = root.addElement("Image").addAttribute("name", mouse.get(0).getName())
                    .addAttribute("width", String.valueOf(mouse.get(0).getImage().getWidth()))
                    .addAttribute("height", String.valueOf(mouse.get(0).getImage().getHeight()))
                    .addAttribute("alpha", mouse.get(0).getImage().getColorModel().hasAlpha() ? String.valueOf(1)
                            : String.valueOf(0));

            for (int i = 0; i < mouse.size(); i++) {
                checkImageSameDimension(mouse.get(0), mouse.get(i),
                        "Images of same category have different dimension");
                checkImageSameAlpha(mouse.get(0), mouse.get(i), "Images of same category have different alpha");
                category.addElement("State").addAttribute("name", mouse.get(i).getState())
                        .addAttribute("posX", String.valueOf(mouse.get(i).getPosX() + mouse.get(i).getBorderSize()))
                        .addAttribute("posY",
                                String.valueOf(mouse.get(i).getPosY() + mouse.get(i).getBorderSize()));
            }
            names.remove("mousecursor");
        }

        Enumeration<String> keys = names.keys();
        while (keys.hasMoreElements()) {
            List<ImageSetImage> img = names.get(keys.nextElement());
            Element category = root.addElement("Image").addAttribute("name", img.get(0).getName())
                    .addAttribute("width", String.valueOf(img.get(0).getImage().getWidth()))
                    .addAttribute("height", String.valueOf(img.get(0).getImage().getHeight()))
                    .addAttribute("alpha", img.get(0).getImage().getColorModel().hasAlpha() ? String.valueOf(1)
                            : String.valueOf(0));

            for (int i = 0; i < img.size(); i++) {
                checkImageSameDimension(img.get(0), img.get(i), "Images of same category have different dimension");
                checkImageSameAlpha(img.get(0), img.get(i), "Images of same category have different alpha");
                category.addElement("State").addAttribute("name", img.get(i).getState())
                        .addAttribute("posX", String.valueOf(img.get(i).getPosX() + img.get(i).getBorderSize()))
                        .addAttribute("posY", String.valueOf(img.get(i).getPosY() + img.get(i).getBorderSize()));
            }
        }

        // write the XML
        OutputFormat format = OutputFormat.createPrettyPrint();
        XMLWriter writer = new XMLWriter(new FileWriter(fileNameXML), format);
        writer.write(document);
        writer.close();
        printInfo(System.getProperty("user.dir") + "/" + imagesetxml + " created");
    }

    /**
    * Compares width and height of two images and throws a RuntimeException if
    * they differ.
    * 
    * @param img1 First image to check.
    * @param img2 Second image to check.
    * @param message Prefix for the exception's message.
    */
    private static void checkImageSameDimension(final ImageSetImage img1, final ImageSetImage img2,
            final String message) {
        final int width1 = img1.getWidth();
        final int width2 = img2.getWidth();
        final int height1 = img1.getHeight();
        final int height2 = img2.getHeight();
        if (width1 != width2 || height1 != height2) {
            exit(1, message + ": " + img1 + "-> width=" + width1 + " height=" + height1 + ", " + img2 + "-> width="
                    + width2 + " height=" + height2);
        }
    }

    /**
    * Compares the alpha/transparency of two images and throws a RuntimeException
    * if they differ.
    * 
    * @param img1 First image to check.
    * @param img2 Second image to check.
    * @param message Prefix for the exception's message.
    */
    private static void checkImageSameAlpha(final ImageSetImage img1, final ImageSetImage img2,
            final String message) {
        final boolean alpha1 = img1.getImage().getColorModel().hasAlpha();
        final boolean alpha2 = img2.getImage().getColorModel().hasAlpha();
        if (alpha1 != alpha2) {
            exit(1, message + ": " + img1 + "-> alpha=" + alpha1 + ", " + img2 + "-> alpha=" + alpha2);
        }
    }

    /**
    * Calculates optimum x and y coordinates of the PNGs using a 2D BinPacker
    * algorithm. After packing these coordinates get stored in the
    * ImageSetImages.
    * 
    * @param dimension Maximum dimension of the enclosing rectangle [width,
    *          height].
    * @return The dimension of the enclosing rectangle [width, height].
    */
    private static int[] packImages(final int[] dimension) {
        // transform ImageSetImages into BinPacker rectangles
        List<Rect> rects = new ArrayList<Rect>(images.size());
        for (int i = 0; i < images.size(); i++) {
            rects.add(createRect(images.get(i)));
        }

        // pack
        BinPacker binPacker = new BinPacker();
        List<Rect> packedRects = binPacker.packOptimized(rects, false, dimension);

        assert (images.size() == packedRects.size());

        // store the coordinates
        for (int i = 0; i < images.size(); i++) {
            for (int j = 0; j < packedRects.size(); j++) {
                if ((images.get(i).toString()).equals(packedRects.get(j).getId())) {
                    images.get(i).setPosX(packedRects.get(j).getX());
                    images.get(i).setPosY(packedRects.get(j).getY());
                }
            }
        }
        // printInfo("PNG files packed");
        return dimension;
    }

    /**
    * Reads all PNG files recursively from the starting dir and creates
    * ImageSetImages.
    * 
    * @param startingDir starting dir to read PNGs recursively.
    * @param borderSize border size, 0 for no border.
    * @param borderColorR border color, Red value.
    * @param borderColorG border color, Green value.
    * @param borderColorB border color, Blue value.
    */
    private static void readImages(final String startingDir, final String borderSize, final String borderColorR,
            final String borderColorG, final String borderColorB) {
        List<File> gfxFiles = new ArrayList<File>();
        readFilesRecursively(new File(startingDir), gfxFiles);

        // check if there are files to process
        if (gfxFiles.size() == 0) {
            exit(1, "No PNG files found in directory '" + startingDir + "'. Nothing to do.");
        }
        images = new ArrayList<ImageSetImage>(gfxFiles.size());

        // check the PNGs for correct file names
        if (!ImageSetImage.checkFilenames(gfxFiles)) {
            exit(1, "Invalid format of image file name. I have to exit.");
        }

        // check border size/color values
        int lBorderSize = Integer.valueOf(borderSize);
        if (lBorderSize < 0) {
            lBorderSize = 0;
        } else if (lBorderSize > BORDERSIZEMAX) {
            lBorderSize = BORDERSIZEMAX;
        }
        int lBorderColorR = Integer.valueOf(borderColorR);
        int lBorderColorG = Integer.valueOf(borderColorG);
        int lBorderColorB = Integer.valueOf(borderColorB);
        if (lBorderColorR < 0) {
            lBorderColorR = 0;
        } else if (lBorderColorR > BORDERCOLORMAX) {
            lBorderColorR = BORDERCOLORMAX;
        }
        if (lBorderColorG < 0) {
            lBorderColorG = 0;
        } else if (lBorderColorG > BORDERCOLORMAX) {
            lBorderColorG = BORDERCOLORMAX;
        }
        if (lBorderColorB < 0) {
            lBorderColorB = 0;
        } else if (lBorderColorB > BORDERCOLORMAX) {
            lBorderColorB = BORDERCOLORMAX;
        }

        for (int i = 0; i < gfxFiles.size(); i++) {
            ImageSetImage image = new ImageSetImage(gfxFiles.get(i));
            if (lBorderSize > 0) {
                image.setBorderSize(lBorderSize);
                image.setBorderColor(new Color(lBorderColorR, lBorderColorG, lBorderColorB));
            }
            images.add(image);
        }
        // printInfo("PNG files read");
    }

    /**
    * Fills a list of PNG files, recursively from a starting dir.
    * 
    * @param startingDir starting dir.
    * @param fileList file list to be filled.
    */
    private static void readFilesRecursively(final File startingDir, final List<File> fileList) {
        if (startingDir.isFile()) {
            fileList.add(startingDir);
        }

        File[] children = startingDir.listFiles(new FilenameFilterPNG());
        if (children != null) {
            for (int i = 0; i < children.length; i++) {
                readFilesRecursively(children[i], fileList);
            }
        }
    }

    /**
    * Creates a BinPacker rectangle out of an ImageSetImage.
    * 
    * @param img ImageSetImage.
    * @return BinPacker rectangle with the ImageSetImage's width and height.
    */
    private static Rect createRect(final ImageSetImage img) {
        return new Rect(img.toString(), img.getWidth() + img.getBorderSize() * 2,
                img.getHeight() + img.getBorderSize() * 2);
    }

    /**
    * Terminates the application, does some cleaning before if necessary.
    * 
    * @param status The exit status (0 = regular, 1 = error).
    * @param message Message to print out.
    */
    private static void exit(final int status, final String message) {
        if (status == 0) {
            printInfo(message);
            System.exit(0);
        } else {
            printError(message);
        }
    }

    /**
    * Main method for generation of the imageset.
    * 
    * @param allPanel allPanel
    */
    public static void generate(GeneratePanel allPanel) {
        try {
            saveConfig(allPanel);
            if (restrictImageSize) {
                dimension = new int[] { maxWidth, maxHeight };
            } else {
                dimension = new int[] { -1, -1 };
            }
            readImages(startingDir, borderSize, borderColorR, borderColorG, borderColorB);
            dimension = packImages(dimension);
            writeXML(imageset, imagesetxml);
            writeImageSet(imageset, dimension);
            printSuccess("Success.\n");
        } catch (IOException e) {
            e.printStackTrace();
            printError(e.getMessage());
        }
    }

    /**
    * Saves preferences if they were changed.
    * 
    * @param prefPanel prefPanel
    */
    public static void savePreferences(PreferencesPanel prefPanel) {
        boolean prefsChanged = false;

        final String filenamePNG = prefPanel.jTextFieldFilenamePNG.getText();
        if (filenamePNG != null && !filenamePNG.equals(conf.getString(CONF_FILENAME_IMAGESET))) {
            prefsChanged = true;
            conf.setProperty(CONF_FILENAME_IMAGESET, filenamePNG);
            imageset = filenamePNG;
        }

        final String filenameXML = prefPanel.jTextFieldFilenameXML.getText();
        if (filenameXML != null && !filenameXML.equals(conf.getString(CONF_FILENAME_IMAGESET_XML))) {
            prefsChanged = true;
            conf.setProperty(CONF_FILENAME_IMAGESET_XML, filenameXML);
            imagesetxml = filenameXML;
        }

        final boolean restrictSize = prefPanel.jCheckBoxRestrictPNGSize.isSelected();
        if (!String.valueOf(restrictSize).equals(conf.getString(CONF_RESTRICT_SIZE))) {
            prefsChanged = true;
            conf.setProperty(CONF_RESTRICT_SIZE, String.valueOf(restrictSize));
            restrictImageSize = restrictSize;
        }

        final String restrictedWidth = prefPanel.jTextFieldRestrictedWidth.getText();
        if (restrictedWidth != null && !restrictedWidth.equals(conf.getString(CONF_RESTRICT_SIZE_WIDTH))) {
            prefsChanged = true;
            conf.setProperty(CONF_RESTRICT_SIZE_WIDTH, restrictedWidth);
            maxWidth = Integer.valueOf(restrictedWidth);
        }

        final String restrictedHeight = prefPanel.jTextFieldRestrictedHeight.getText();
        if (restrictedHeight != null && !restrictedHeight.equals(conf.getString(CONF_RESTRICT_SIZE_HEIGHT))) {
            prefsChanged = true;
            conf.setProperty(CONF_RESTRICT_SIZE_HEIGHT, restrictedHeight);
            maxHeight = Integer.valueOf(restrictedHeight);
        }

        try {
            if (prefsChanged) {
                conf.save(conf.getFile());
                printInfo("Preferences saved");
            }
        } catch (ConfigurationException e) {
            e.printStackTrace();
            printError(e.getMessage());
        }

        // clean up
        frame.jScrollPaneMain.setViewportView(null);
        frame.prefPanel = null;
    }

    /**
    * Cleaning up after leaving the preferences.
    */
    public static void cancelPreferences() {
        // clean up
        frame.jScrollPaneMain.setViewportView(null);
        frame.prefPanel = null;
    }

    private static void saveConfig(GeneratePanel allPanel) {
        boolean confChanged = false;
        if (allPanel.jCheckBoxStartingDir.isSelected() && !startingDir.equals(conf.getString(CONF_IMAGES_DIR))) {
            confChanged = true;
            conf.setProperty(CONF_IMAGES_DIR, startingDir);
        }
        if (allPanel.jCheckBoxBorderSize.isSelected() && !borderSize.equals(conf.getString(CONF_BORDER_SIZE))) {
            confChanged = true;
            conf.setProperty(CONF_BORDER_SIZE, borderSize);
        }
        if (allPanel.jCheckBoxBorderColor.isSelected()
                && !borderColorR.equals(conf.getString(CONF_BORDER_COLOR_RED))) {
            confChanged = true;
            conf.setProperty(CONF_BORDER_COLOR_RED, borderColorR);
        }
        if (allPanel.jCheckBoxBorderColor.isSelected()
                && !borderColorG.equals(conf.getString(CONF_BORDER_COLOR_GREEN))) {
            confChanged = true;
            conf.setProperty(CONF_BORDER_COLOR_GREEN, borderColorG);
        }
        if (allPanel.jCheckBoxBorderColor.isSelected()
                && !borderColorB.equals(conf.getString(CONF_BORDER_COLOR_BLUE))) {
            confChanged = true;
            conf.setProperty(CONF_BORDER_COLOR_BLUE, borderColorB);
        }

        try {
            if (confChanged) {
                conf.save(conf.getFile());
                printInfo("Configuration saved");
            }
        } catch (ConfigurationException e) {
            e.printStackTrace();
            printError(e.getMessage());
        }
    }

    private static void printInfo(String msg) {
        // TODO: change Text color to black
        frame.jTextAreaInfo.append(msg + "\n");
        frame.jTextAreaInfo.setCaretPosition(frame.jTextAreaInfo.getText().length());
    }

    private static void printError(String msg) {
        // TODO: change Text color to red
        frame.jTextAreaInfo.append(msg + "\n");
        frame.jTextAreaInfo.setCaretPosition(frame.jTextAreaInfo.getText().length());
    }

    private static void printSuccess(String msg) {
        // TODO: change Text color to green
        frame.jTextAreaInfo.append(msg + "\n");
        frame.jTextAreaInfo.setCaretPosition(frame.jTextAreaInfo.getText().length());
    }
}