/*
* Copyright (c) 2004-2007 by Michael Connor. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of FormLayoutBuilder or Michael Connor nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.mlc.swing.layout;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import javax.swing.filechooser.FileFilter;
import com.jgoodies.forms.factories.Borders;
/**
* This is the frame that enables you to build a layout. The principle component
* is the FormEditor panel.
*
* @author Michael Connor mlconnor@yahoo.com
@version $Id$
@since Ptolemy II 7.1
*/
@SuppressWarnings("serial")
public class LayoutFrame extends JFrame implements MultiContainerFrame {
LayoutConstraintsManager constraintsManager;
JMenuBar menuBar = new JMenuBar();
JMenu actionMenu = new JMenu("File");
JMenuItem saveXML = new JMenuItem("Save As");
JMenuItem viewCode = new JMenuItem("View Code");
JMenuItem exit = new JMenuItem("Exit");
JMenu viewMenu = new JMenu("View");
JCheckBoxMenuItem viewDebugMenu = new JCheckBoxMenuItem("Debug Frame");
final JFileChooser fileChooser = new JFileChooser();
Map<ContainerLayout, FormEditor> editors = new HashMap<ContainerLayout, FormEditor>();
JTabbedPane tabs = new JTabbedPane();
Map<ContainerLayout, Component> layoutToTab = new HashMap<ContainerLayout, Component>();
List<ContainerLayout> newLayouts = new ArrayList<ContainerLayout>();
// Palette palette = new Palette();
public JFrame dframe = null;
/** Creates a new instance of Class */
public LayoutFrame(LayoutConstraintsManager constraintsManager) {
super("FormLayoutMaker - Constraints Editor");
if (constraintsManager.getLayouts().size() == 0) {
throw new RuntimeException(
"You must register at least one container by calling LayoutConstraintsManager.setLayout(String name, Container container) before instantiating a LayoutFrame");
}
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
this.constraintsManager = constraintsManager;
actionMenu.setMnemonic('F');
saveXML.setMnemonic('A');
saveXML.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A,
InputEvent.CTRL_MASK));
viewCode.setMnemonic('V');
viewCode.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
InputEvent.CTRL_MASK));
exit.setMnemonic('X');
actionMenu.add(saveXML);
actionMenu.add(viewCode);
actionMenu.add(exit);
viewDebugMenu.setMnemonic('D');
viewDebugMenu.setSelected(UserPrefs.getPrefs().showDebugPanel());
viewMenu.add(viewDebugMenu);
// KBR 03/26/06 Disable by default for invocation from user's
// program. Enabled when the debug preview window is established.
viewDebugMenu.setEnabled(false);
menuBar.add(actionMenu);
menuBar.add(viewMenu);
this.setJMenuBar(menuBar);
fileChooser.setFileFilter(new XmlFileFilter());
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
exitApplication();
}
});
exit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
exitApplication();
}
});
viewDebugMenu.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
LayoutFrame.this.enableDebugPreview(viewDebugMenu.isSelected());
}
});
List<ContainerLayout> layouts = constraintsManager.getLayouts();
for (int index = 0; index < layouts.size(); index++) {
ContainerLayout containerLayout = layouts.get(index);
Container container = constraintsManager
.getContainer(containerLayout);
if (container == null) {
throw new RuntimeException(
"A container with name "
+ containerLayout.getName()
+ " was found in the contstraints file but was not found in the container");
}
addContainerLayout(containerLayout, container);
}
getContentPane().setLayout(new BorderLayout(3, 3));
getContentPane().add(tabs, BorderLayout.CENTER);
// getContentPane().add (palette, BorderLayout.SOUTH);
viewCode.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
List<ContainerLayout> layouts = LayoutFrame.this.constraintsManager
.getLayouts();
StringBuffer declarationBuffer = new StringBuffer(
"// here are declarations for the controls you created\n");
StringBuffer setLayoutBuffer = new StringBuffer(
"// here is where we load the layout constraints. "
+ "change the xml filename!!!\norg.mlc.swing.layout.LayoutConstraintsManager layoutConstraintsManager "
+ "= \n org.mlc.swing.layout.LayoutConstraintsManager.getLayoutConstraintsManager(\n "
+ "this.getClass().getResourceAsStream(\"yourConstraintFile.xml\"));\n");
/** @todo KBR generate compilable code */
// LayoutFrame layoutFrame = new LayoutFrame(layoutConstraintsManager);
// layoutFrame.setVisible(true);
StringBuffer addBuffer = new StringBuffer(
"// here we add the controls to the container. you may\n// "
+ "need to change the name of panel\n");
StringBuffer importBuffer = new StringBuffer();
importBuffer.append("import org.mlc.swing.layout.*;\n");
HashSet<String> importSet = new HashSet<String>();
StringBuffer declBuffer = new StringBuffer();
declBuffer
.append("// here are declarations for the controls you created\n");
StringBuffer addBuffer2 = new StringBuffer();
addBuffer2
.append("// here we add the controls to the container.\n");
StringBuffer confBuffer = new StringBuffer();
confBuffer.append("// control configuration\n");
for (int index = 0; index < layouts.size(); index++) {
ContainerLayout containerLayout = layouts.get(index);
FormEditor editor = editors.get(containerLayout);
Map<Component, String> componentsToNames = containerLayout
.getComponentsToNames();
for (Iterator i = componentsToNames.keySet().iterator(); i
.hasNext();) {
Component component = (Component) i.next();
String componentName = componentsToNames.get(component);
if (editor.isNewComponent(component)) {
String _decl = "";
String _import = "";
String _add = "";
String _config = "";
ComponentDef cDef = containerLayout
.getComponentDef(componentName);
if (cDef == null) {
// "old style"
String constructorArg = "";
if (LayoutConstraintsManager
.isTextComponent(component)) {
Map<String, Object> customProperties = containerLayout
.getCustomProperties(componentName);
Object textValue = customProperties
.get("text");
if (textValue != null
&& textValue instanceof String) {
constructorArg = "\""
+ (String) textValue + "\"";
}
}
_decl = component.getClass().getName()
+ " "
+ containerLayout
.getComponentName(component)
+ " = new "
+ component.getClass().getName() + "("
+ constructorArg + ");\n";
_add = containerLayout.getName() + ".add ("
+ componentName + ", \""
+ componentName + "\");\n";
} else {
// "new style"
_import = cDef.getImports(componentName);
_decl = cDef.getDeclarations(componentName);
_add = cDef.getAdd(componentName);
_add = _add.replaceAll("\\$\\{container\\}",
containerLayout.getName());
_config = cDef.getConfigure(componentName);
}
// put imports into a set to prevent multiple instances
// KBR 09/05/05 Need to put each line of the import into
// the set [using JButton and ButtonBar was generating
// two JButton import statements]
String[] outstrs = _import.split("\n");
for (int ii = 0; ii < outstrs.length; ii++) {
importSet.add(outstrs[ii]);
}
declBuffer.append(_decl + "\n");
addBuffer2.append(_add + "\n");
if (_config.trim().length() != 0) {
confBuffer.append(_config + "\n");
}
String constructorArg = "";
if (LayoutConstraintsManager
.isTextComponent(component)) {
Map<String, Object> customProperties = containerLayout
.getCustomProperties(componentName);
Object textValue = customProperties.get("text");
if (textValue != null
&& textValue instanceof String) {
constructorArg = "\"" + (String) textValue
+ "\"";
}
}
String newDeclaration = component.getClass()
.getName()
+ " "
+ containerLayout
.getComponentName(component)
+ " = new "
+ component.getClass().getName()
+ "("
+ constructorArg + ");\n";
declarationBuffer.append(newDeclaration);
addBuffer.append(containerLayout.getName()
+ ".add (" + componentName + ", \""
+ componentName + "\");\n");
}
}
if (newLayouts.contains(containerLayout)) {
setLayoutBuffer
.append(containerLayout.getName()
+ ".setBorder(com.jgoodies.forms.factories.Borders.DIALOG_BORDER);\n");
setLayoutBuffer
.append(containerLayout.getName()
+ ".setLayout(layoutConstraintsManager.createLayout (\""
+ containerLayout.getName() + "\", "
+ containerLayout.getName() + ");\n");
}
}
// build up the imports string using all unique imports
Iterator<String> itor = importSet.iterator();
while (itor.hasNext()) {
importBuffer.append(itor.next() + "\n");
}
// String finalText = declarationBuffer.toString() + "\n" +
// setLayoutBuffer.toString() + "\n" + addBuffer.toString();
String finalText = importBuffer.toString() + "\n"
+ declBuffer.toString() + "\n"
+ setLayoutBuffer.toString() + "\n"
+ addBuffer2.toString() + "\n" + confBuffer.toString()
+ "\n";
CodeDialog codeDialog = new CodeDialog(LayoutFrame.this,
finalText);
codeDialog.setVisible(true);
}
});
saveXML.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
java.util.prefs.Preferences prefs = java.util.prefs.Preferences
.userNodeForPackage(getClass());
String pathString = prefs.get("lastpath", null);
if (pathString != null) {
File path = new File(pathString);
if (path.exists()) {
fileChooser.setCurrentDirectory(path);
}
}
int returnVal = fileChooser.showSaveDialog(LayoutFrame.this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
// KBR fix logged bug. If the user does not specify an XML extension,
// add one, UNLESS they specify the trailing period.
String filename = file.getAbsolutePath();
if (!filename.endsWith(".xml")
&& !filename.endsWith(".XML")
&& !filename.endsWith(".")) {
file = new File(file.getAbsolutePath() + ".XML");
}
if (file.exists()) {
File path = file.getParentFile();
if (path != null) {
pathString = path.getAbsolutePath();
prefs.put("lastpath", pathString);
}
int result = JOptionPane
.showConfirmDialog(
LayoutFrame.this,
"The file you selected exists, ok to overwrite?",
"File Exists",
JOptionPane.YES_NO_OPTION);
if (result != JOptionPane.YES_OPTION) {
return;
}
}
FileOutputStream outStream = null;
try {
outStream = new FileOutputStream(file);
String xml = LayoutFrame.this.constraintsManager
.getXML();
outStream.write(xml.getBytes());
} catch (Exception exception) {
JOptionPane.showMessageDialog(LayoutFrame.this,
"Error writing to file. "
+ exception.getMessage());
exception.printStackTrace();
} finally {
try {
if (outStream != null) {
outStream.close();
}
} catch (Exception ignore) {
}
}
}
}
});
pack();
}
public boolean hasContainer(String name) {
return constraintsManager.getContainerLayout(name) != null;
}
private class XmlFileFilter extends FileFilter {
public boolean accept(File f) {
if (f.isDirectory()) {
return true;
}
String ext = null;
String s = f.getName();
int i = s.lastIndexOf('.');
boolean isXml = false;
if (i > 0 && i < s.length() - 1) {
ext = s.substring(i + 1).toLowerCase();
isXml = ext.equals("xml");
}
return isXml;
}
public String getDescription() {
return "xml files";
}
}
public void exitApplication() {
int result = JOptionPane.showConfirmDialog(LayoutFrame.this,
"Are you sure you want to exit?", "Exit Confirmation",
JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
// UserPrefs.getPrefs().saveWinLoc("main", getLocationOnScreen(), getSize());
// UserPrefs.getPrefs().saveWinLoc("debug", dframe.getLocationOnScreen(),
// dframe.getSize());
UserPrefs.getPrefs().saveWinLoc("main", this);
UserPrefs.getPrefs().saveWinLoc("debug", dframe);
UserPrefs.getPrefs().saveDebugState(viewDebugMenu.isSelected());
setVisible(false);
System.exit(0);
}
}
public void removeContainer(String name) {
ContainerLayout layout = constraintsManager.getContainerLayout(name);
if (layout == null) {
throw new RuntimeException("Container " + name + " does not exist");
}
// Also have to remove any contained containers!
// EAL, 3/3/06.
Container container = constraintsManager.getContainer(layout);
Component[] components = container.getComponents();
for (int i = 0; i < components.length; i++) {
if (components[i] instanceof Container) {
String componentName = layout.getComponentName(components[i]);
if (hasContainer(componentName)) {
removeContainer(componentName);
}
}
}
constraintsManager.removeLayout(layout);
FormEditor editor = editors.get(layout);
tabs.remove(editor);
newLayouts.remove(layout);
}
/**
* This is for adding containers on the fly. The idea is that when someone
* creates a new panel in one of the existing FormEditors, it can be added
* here and then they can lay it out.
*/
public void addContainer(String name, Container container)
throws IllegalArgumentException {
// check to see if another panel with this name already exists
ContainerLayout layout = constraintsManager.getContainerLayout(name);
if (layout != null) {
throw new IllegalArgumentException("A container with name " + name
+ " already exists");
}
layout = new ContainerLayout(name, "pref", "pref");
constraintsManager.addLayout(layout);
container.setLayout(layout);
newLayouts.add(layout);
addContainerLayout(layout, container);
}
private void addContainerLayout(ContainerLayout containerLayout,
Container container) {
FormEditor formEditor = new FormEditor(this, containerLayout, container);
editors.put(containerLayout, formEditor);
tabs.addTab(containerLayout.getName(), formEditor);
}
@SuppressWarnings("serial")
private class CodeDialog extends JDialog {
public CodeDialog(Frame owner, String text) {
super(owner, "FormLayoutMaker - Code View", true);
UserPrefs.getPrefs().useSavedBounds("codeview", this);
JPanel content = new JPanel();
getContentPane().setLayout(new BorderLayout());
getContentPane().add(content, BorderLayout.CENTER);
JTextArea textArea = new JTextArea();
textArea.setEditable(false);
textArea.setText(text);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
content.setLayout(new BorderLayout());
JScrollPane areaScrollPane = new JScrollPane(textArea);
areaScrollPane.setPreferredSize(new Dimension(600, 400));
content.add(areaScrollPane, BorderLayout.CENTER);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
UserPrefs.getPrefs()
.saveWinLoc("codeview", CodeDialog.this);
}
});
}
}
/**
* Establish the current preview window. Used to switch between the "normal"
* and "debug" preview windows.
*
* KBR 03/26/06 Use this as the mechanism to enable the 'debug preview'
* menu, which is disabled by default (to have it disabled when FLM is
* invoked via the user's app).
*
* @param dframe the Jframe for the window.
*/
void setPreviewFrame(LayoutConstraintsManager lcm, JFrame dframe) {
// if ( dframe == null )
// dframe = makeNormalPreview(lcm);
if (this.dframe != null) {
this.dframe.setVisible(false);
}
this.dframe = dframe;
// ContainerLayout layout = constraintsManager.getContainerLayout("panel");
// FormEditor fe = editors.get(layout);
// if ( fe != null )
// fe.setContainer(lcm.getContainer(layout));
UserPrefs.getPrefs().useSavedBounds("debug", dframe);
// Rectangle r = UserPrefs.getPrefs().getWinLoc("debug");
// dframe.setLocation(r.x, r.y);
// dframe.setSize(r.width, r.height);
dframe.setVisible(true);
viewDebugMenu.setEnabled(true); // we have a debug frame, enable the menu
}
/**
* Activate "debug" version of preview frame. The title is set accordingly.
* @param b true to activate debug version
*/
protected void enableDebugPreview(boolean b) {
if (dframe == null) {
return;
}
dframe.setTitle("FormLayoutMaker - Preview" + (b ? " (Debug)" : ""));
FormDebugPanel fdp = (FormDebugPanel) dframe.getContentPane()
.getComponent(0);
fdp.deactivate(!b);
}
/**
* Makes a preview frame using FormDebugPanel. The panel can be switched
* between debug and non-debug modes via @see enableDebugPreview.
* @param lcm the constraints to be used by the preview panel
* @return JFrame the preview window
*/
private static JFrame makeDebugPreview(LayoutConstraintsManager lcm) {
FormDebugPanel fdp = new FormDebugPanel(true, false);
fdp.setBorder(Borders.DIALOG_BORDER);
JFrame debugFrame = new JFrame();
lcm.setLayout("panel", fdp);
debugFrame.getContentPane().add(fdp, BorderLayout.CENTER);
debugFrame
.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
return debugFrame;
}
public static void main(String[] args) {
LayoutConstraintsManager constraintsManager = new LayoutConstraintsManager();
// Always use a FormDebugPanel as the preview panel, but switch
// it depending on user preference.
JFrame frame = LayoutFrame.makeDebugPreview(constraintsManager);
LayoutFrame layoutFrame = new LayoutFrame(constraintsManager);
JFrame.setDefaultLookAndFeelDecorated(true);
UserPrefs.getPrefs().useSavedBounds("main", layoutFrame);
// Rectangle r = UserPrefs.getPrefs().getWinLoc("main");
// layoutFrame.setLocation(r.x, r.y);
// layoutFrame.setSize(r.width, r.height);
layoutFrame.setVisible(true);
layoutFrame
.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
layoutFrame.setPreviewFrame(constraintsManager, frame);
layoutFrame.enableDebugPreview(UserPrefs.getPrefs().showDebugPanel());
}
}