View Javadoc

1   /*
2    * Copyright (c) 2004-2005 by Michael Connor. All Rights Reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without 
5    * modification, are permitted provided that the following conditions are met:
6    * 
7    *  o Redistributions of source code must retain the above copyright notice, 
8    *    this list of conditions and the following disclaimer. 
9    *     
10   *  o Redistributions in binary form must reproduce the above copyright notice, 
11   *    this list of conditions and the following disclaimer in the documentation 
12   *    and/or other materials provided with the distribution. 
13   *     
14   *  o Neither the name of FormLayoutBuilder or Michael Connor nor the names of 
15   *    its contributors may be used to endorse or promote products derived 
16   *    from this software without specific prior written permission. 
17   *     
18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
19   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
20   * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
21   * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
22   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
23   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
24   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
25   * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
26   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
27   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
28   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
29   */
30  package org.mlc.swing.layout;
31  
32  import java.awt.BorderLayout;
33  import java.awt.Component;
34  import java.awt.Container;
35  import java.awt.Dimension;
36  import java.awt.Frame;
37  import java.awt.event.ActionEvent;
38  import java.awt.event.ActionListener;
39  import java.awt.event.KeyEvent;
40  import java.awt.event.WindowAdapter;
41  import java.awt.event.WindowEvent;
42  import java.io.File;
43  import java.io.FileOutputStream;
44  import java.util.ArrayList;
45  import java.util.HashMap;
46  import java.util.HashSet;
47  import java.util.Iterator;
48  import java.util.List;
49  import java.util.Map;
50  
51  import javax.swing.JCheckBoxMenuItem;
52  import javax.swing.JDialog;
53  import javax.swing.JFileChooser;
54  import javax.swing.JFrame;
55  import javax.swing.JMenu;
56  import javax.swing.JMenuBar;
57  import javax.swing.JMenuItem;
58  import javax.swing.JOptionPane;
59  import javax.swing.JPanel;
60  import javax.swing.JScrollPane;
61  import javax.swing.JTabbedPane;
62  import javax.swing.JTextArea;
63  import javax.swing.KeyStroke;
64  import javax.swing.WindowConstants;
65  import javax.swing.filechooser.FileFilter;
66  
67  import com.jgoodies.forms.factories.Borders;
68  
69  /***
70   * This is the frame that enables you to build a layout. The principle component
71   * is the FormEditor panel.
72   * 
73   * @author Michael Connor mlconnor@yahoo.com
74   */
75  @SuppressWarnings("serial")
76  public class LayoutFrame extends JFrame
77  {
78    LayoutConstraintsManager constraintsManager;
79  
80    JMenuBar menuBar = new JMenuBar();
81  
82    JMenu actionMenu = new JMenu("File");
83    JMenuItem saveXML = new JMenuItem("Save As");
84    JMenuItem viewCode = new JMenuItem("View Code");
85    JMenuItem exit = new JMenuItem("Exit");
86  
87    JMenu viewMenu = new JMenu("View");
88    JCheckBoxMenuItem viewDebugMenu = new JCheckBoxMenuItem("Debug Frame");
89     
90    final JFileChooser fileChooser = new JFileChooser();
91  
92    Map<ContainerLayout, FormEditor> editors = new HashMap<ContainerLayout, FormEditor>();
93  
94    JTabbedPane tabs = new JTabbedPane();
95  
96    Map<ContainerLayout, Component> layoutToTab = new HashMap<ContainerLayout, Component>();
97  
98    List<ContainerLayout> newLayouts = new ArrayList<ContainerLayout>();
99  
100   // Palette palette = new Palette();
101   public JFrame dframe = null;
102 
103   /*** Creates a new instance of Class */
104   public LayoutFrame(LayoutConstraintsManager constraintsManager)
105   {
106     super("FormLayoutMaker - Constraints Editor");
107 
108     if (constraintsManager.getLayouts().size() == 0)
109       throw new RuntimeException(
110           "You must register at least one container by calling LayoutConstraintsManager.setLayout(String name, Container container) before instantiating a LayoutFrame");
111 
112     setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
113     this.constraintsManager = constraintsManager;
114 
115     actionMenu.setMnemonic('F');
116     saveXML.setMnemonic('A');
117     saveXML.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A,
118         KeyEvent.CTRL_MASK));
119     viewCode.setMnemonic('V');
120     viewCode.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
121         KeyEvent.CTRL_MASK));
122     exit.setMnemonic('X');
123     actionMenu.add(saveXML);
124     actionMenu.add(viewCode);
125     actionMenu.add(exit);
126     
127     viewDebugMenu.setMnemonic('D');
128     viewDebugMenu.setSelected(UserPrefs.getPrefs().showDebugPanel());
129     viewMenu.add(viewDebugMenu);
130     // KBR 03/26/06 Disable by default for invocation from user's
131     // program. Enabled when the debug preview window is established.
132     viewDebugMenu.setEnabled(false); 
133     
134     menuBar.add(actionMenu);
135     menuBar.add(viewMenu);
136     this.setJMenuBar(menuBar);
137 
138     fileChooser.setFileFilter(new XmlFileFilter());
139 
140     this.addWindowListener(new WindowAdapter()
141     {
142       public void windowClosing(WindowEvent e)
143       {
144         exitApplication();
145       }
146     });
147 
148     exit.addActionListener(new ActionListener()
149     {
150       public void actionPerformed(ActionEvent e)
151       {
152         exitApplication();
153       }
154     });
155 
156     viewDebugMenu.addActionListener(new ActionListener()
157     {
158       public void actionPerformed(ActionEvent e)
159       {
160         LayoutFrame.this.enableDebugPreview(viewDebugMenu.isSelected());
161       }
162     });
163     
164     List<ContainerLayout> layouts = constraintsManager.getLayouts();
165 
166     for (int index = 0; index < layouts.size(); index++)
167     {
168       ContainerLayout containerLayout = layouts.get(index);
169       Container container = constraintsManager.getContainer(containerLayout);
170       if (container == null)
171         throw new RuntimeException(
172             "A container with name "
173                 + containerLayout.getName()
174                 + " was found in the contstraints file but was not found in the container");
175       addContainerLayout(containerLayout, container);
176     }
177 
178     getContentPane().setLayout(new BorderLayout(3, 3));
179     getContentPane().add(tabs, BorderLayout.CENTER);
180     // getContentPane().add (palette, BorderLayout.SOUTH);
181 
182     viewCode.addActionListener(new ActionListener()
183     {
184       public void actionPerformed(ActionEvent e)
185       {
186         List<ContainerLayout> layouts = LayoutFrame.this.constraintsManager
187             .getLayouts();
188         StringBuffer declarationBuffer = new StringBuffer(
189             "// here are declarations for the controls you created\n");
190         StringBuffer setLayoutBuffer = new StringBuffer(
191             "// here is where we load the layout constraints.  "
192                 + "change the xml filename!!!\norg.mlc.swing.layout.LayoutConstraintsManager layoutConstraintsManager "
193                 + "= \n    org.mlc.swing.layout.LayoutConstraintsManager.getLayoutConstraintsManager(\n        "
194                 + "this.getClass().getResourceAsStream(\"yourConstraintFile.xml\"));\n");
195 /*** @todo KBR generate compilable code */        
196 // LayoutFrame layoutFrame = new LayoutFrame(layoutConstraintsManager); 
197 // layoutFrame.setVisible(true);         
198         StringBuffer addBuffer = new StringBuffer(
199             "// here we add the controls to the container.  you may\n// "
200                 + "need to change the name of panel\n");
201 
202         StringBuffer importBuffer = new StringBuffer();
203         importBuffer.append("import org.mlc.swing.layout.*;\n");
204         HashSet<String> importSet = new HashSet<String> ();
205         
206         StringBuffer declBuffer = new StringBuffer();
207         declBuffer.append("// here are declarations for the controls you created\n");
208 
209         StringBuffer addBuffer2 = new StringBuffer();
210         addBuffer2.append("// here we add the controls to the container.\n");
211 
212         StringBuffer confBuffer = new StringBuffer();
213         confBuffer.append("// control configuration\n");
214 
215         for (int index = 0; index < layouts.size(); index++)
216         {
217           ContainerLayout containerLayout = layouts.get(index);
218           FormEditor editor = editors.get(containerLayout);
219           Map<Component, String> componentsToNames = containerLayout.getComponentsToNames();
220 
221           for (Iterator i = componentsToNames.keySet().iterator(); i.hasNext();)
222           {
223             Component component = (Component) i.next();
224             String componentName = componentsToNames.get(component);
225             if (editor.isNewComponent(component))
226             {
227               String _decl = "";
228               String _import = "";
229               String _add = "";
230               String _config = "";
231 
232               ComponentDef cDef = containerLayout.getComponentDef(componentName);
233               if ( cDef == null )
234               {
235                 // "old style"
236                 String constructorArg = "";
237                 if ( LayoutConstraintsManager.isTextComponent(component) ) 
238                 {
239                     Map<String,Object> customProperties = containerLayout.getCustomProperties(componentName);
240                     Object textValue = customProperties.get("text");
241                     if ( textValue != null && textValue instanceof String )
242                         constructorArg = "\"" + (String) textValue + "\"";
243                 }
244                 _decl = component.getClass().getName() + " " +
245                         containerLayout.getComponentName(component) + 
246                         " = new " + component.getClass().getName() + 
247                         "(" + constructorArg + ");\n";
248                 _add = containerLayout.getName() + ".add (" + 
249                        componentName + ", \"" + componentName + "\");\n";
250               }
251               else
252               {
253                 // "new style"
254                 _import = cDef.getImports(componentName);
255                 _decl = cDef.getDeclarations(componentName);
256                 _add = cDef.getAdd(componentName);
257                 _add = _add.replaceAll("//$//{container//}", containerLayout.getName());                
258                 _config = cDef.getConfigure(componentName);
259               }
260               
261               // put imports into a set to prevent multiple instances
262               // KBR 09/05/05 Need to put each line of the import into
263               // the set [using JButton and ButtonBar was generating
264               // two JButton import statements]
265               String [] outstrs = _import.split("\n");
266               for ( int ii = 0; ii < outstrs.length; ii++ )
267                 importSet.add(outstrs[ii]);
268               
269               declBuffer.append( _decl + "\n");
270               addBuffer2.append( _add + "\n");
271               if ( _config.trim().length() != 0 )
272                 confBuffer.append( _config + "\n");
273 
274               String constructorArg = "";
275               if (LayoutConstraintsManager.isTextComponent(component))
276               {
277                 Map<String, Object> customProperties = containerLayout
278                     .getCustomProperties(componentName);
279                 Object textValue = customProperties.get("text");
280                 if (textValue != null && textValue instanceof String)
281                   constructorArg = "\"" + (String) textValue + "\"";
282               }
283 
284               String newDeclaration = component.getClass().getName() + " "
285                   + containerLayout.getComponentName(component) + " = new "
286                   + component.getClass().getName() + "(" + constructorArg
287                   + ");\n";
288               declarationBuffer.append(newDeclaration);
289               addBuffer.append(containerLayout.getName() + ".add ("
290                   + componentName + ", \"" + componentName + "\");\n");
291             }
292           }
293 
294           if (newLayouts.contains(containerLayout))
295           {
296             setLayoutBuffer
297                 .append(containerLayout.getName()
298                     + ".setBorder(com.jgoodies.forms.factories.Borders.DIALOG_BORDER);\n");
299             setLayoutBuffer.append(containerLayout.getName()
300                 + ".setLayout(layoutConstraintsManager.createLayout (\""
301                 + containerLayout.getName() + "\", "
302                 + containerLayout.getName() + ");\n");
303           }
304         }
305 
306         // build up the imports string using all unique imports
307         Iterator<String> itor = importSet.iterator();
308         while ( itor.hasNext() )
309           importBuffer.append(itor.next() + "\n");
310         
311         // String finalText = declarationBuffer.toString() + "\n" +
312         // setLayoutBuffer.toString() + "\n" + addBuffer.toString();
313         String finalText = importBuffer.toString() + "\n" + 
314                            declBuffer.toString() + "\n" + 
315                            setLayoutBuffer.toString() + "\n" + 
316                            addBuffer2.toString() + "\n" + 
317                            confBuffer.toString() + "\n";
318         CodeDialog codeDialog = new CodeDialog(LayoutFrame.this, finalText);
319         codeDialog.setVisible(true);
320       }
321     });
322 
323     saveXML.addActionListener(new ActionListener()
324     {
325       public void actionPerformed(ActionEvent e)
326       {
327         java.util.prefs.Preferences prefs = java.util.prefs.Preferences
328             .userNodeForPackage(getClass());
329         String pathString = prefs.get("lastpath", null);
330         if (pathString != null)
331         {
332           File path = new File(pathString);
333           if (path.exists())
334           {
335             fileChooser.setCurrentDirectory(path);
336           }
337         }
338 
339         int returnVal = fileChooser.showSaveDialog(LayoutFrame.this);
340 
341         if (returnVal == JFileChooser.APPROVE_OPTION)
342         {
343           File file = fileChooser.getSelectedFile();
344 
345           // KBR fix logged bug. If the user does not specify an XML extension,
346           // add one, UNLESS they specify the trailing period.
347           String filename = file.getAbsolutePath();
348           if ( !filename.endsWith(".xml") &&
349                !filename.endsWith(".XML") &&
350                !filename.endsWith("."))
351             file = new File( file.getAbsolutePath() + ".XML" ); 
352 
353           if (file.exists())
354           {
355             File path = file.getParentFile();
356             if (path != null)
357             {
358               pathString = path.getAbsolutePath();
359               prefs.put("lastpath", pathString);
360             }
361 
362             int result = JOptionPane.showConfirmDialog(LayoutFrame.this,
363                 "The file you selected exists, ok to overwrite?",
364                 "File Exists", JOptionPane.YES_NO_OPTION);
365             if (result != JOptionPane.YES_OPTION)
366               return;
367           }
368 
369           FileOutputStream outStream = null;
370           try
371           {
372             outStream = new FileOutputStream(file);
373             String xml = LayoutFrame.this.constraintsManager.getXML();
374             outStream.write(xml.getBytes());
375           }
376           catch (Exception exception)
377           {
378             JOptionPane.showMessageDialog(LayoutFrame.this,
379                 "Error writing to file. " + exception.getMessage());
380             exception.printStackTrace();
381           }
382           finally
383           {
384             try
385             {
386               if (outStream != null)
387                 outStream.close();
388             }
389             catch (Exception ignore)
390             {
391             }
392           }
393         }
394       }
395     });
396 
397     pack();
398   }
399 
400   public boolean hasContainer(String name)
401   {
402     return constraintsManager.getContainerLayout(name) != null;
403   }
404 
405   private class XmlFileFilter extends FileFilter
406   {
407 
408     public boolean accept(File f)
409     {
410       if (f.isDirectory())
411         return true;
412 
413       String ext = null;
414       String s = f.getName();
415       int i = s.lastIndexOf('.');
416       boolean isXml = false;
417 
418       if (i > 0 && i < s.length() - 1)
419       {
420         ext = s.substring(i + 1).toLowerCase();
421         isXml = ext.equals("xml");
422       }
423 
424       return isXml;
425 
426     }
427 
428     public String getDescription()
429     {
430       return "xml files";
431     }
432 
433   }
434 
435   public void exitApplication()
436   {
437     int result = JOptionPane.showConfirmDialog(LayoutFrame.this,
438         "Are you sure you want to exit?", "Exit Confirmation",
439         JOptionPane.YES_NO_OPTION);
440     if (result == JOptionPane.YES_OPTION)
441     {
442 //      UserPrefs.getPrefs().saveWinLoc("main", getLocationOnScreen(), getSize());
443 //      UserPrefs.getPrefs().saveWinLoc("debug", dframe.getLocationOnScreen(),
444 //          dframe.getSize());
445       UserPrefs.getPrefs().saveWinLoc("main", this);
446       UserPrefs.getPrefs().saveWinLoc("debug", dframe);
447       UserPrefs.getPrefs().saveDebugState(viewDebugMenu.isSelected());
448       setVisible(false);
449       System.exit(0);
450     }
451   }
452 
453   public void removeContainer(String name)
454   {
455     ContainerLayout layout = constraintsManager.getContainerLayout(name);
456     if (layout == null)
457       throw new RuntimeException("Container " + name + " does not exist");
458     constraintsManager.removeLayout(layout);
459     FormEditor editor = editors.get(layout);
460     tabs.remove(editor);
461     newLayouts.remove(layout);
462   }
463 
464   /***
465    * This is for adding containers on the fly. The idea is that when someone
466    * creates a new panel in one of the existing FormEditors, it can be added
467    * here and then they can lay it out.
468    */
469   public void addContainer(String name, Container container)
470       throws IllegalArgumentException
471   {
472     // check to see if another panel with this name already exists
473     ContainerLayout layout = constraintsManager.getContainerLayout(name);
474     if (layout != null)
475       throw new IllegalArgumentException("A container with name " + name
476           + " already exists");
477 
478     layout = new ContainerLayout(name, "pref", "pref");
479     constraintsManager.addLayout(layout);
480     container.setLayout(layout);
481     newLayouts.add(layout);
482 
483     addContainerLayout(layout, container);
484   }
485 
486   private void addContainerLayout(ContainerLayout containerLayout,
487       Container container)
488   {
489     FormEditor formEditor = new FormEditor(this, containerLayout, container);
490     editors.put(containerLayout, formEditor);
491     tabs.addTab(containerLayout.getName(), formEditor);
492   }
493 
494   @SuppressWarnings("serial")
495   private class CodeDialog extends JDialog
496   {
497     public CodeDialog(Frame owner, String text)
498     {
499       super(owner, "FormLayoutMaker - Code View", true);
500       
501       UserPrefs.getPrefs().useSavedBounds("codeview",this);
502       
503       JPanel content = new JPanel();
504       getContentPane().setLayout(new BorderLayout());
505       getContentPane().add(content, BorderLayout.CENTER);
506 
507       JTextArea textArea = new JTextArea();
508       textArea.setEditable(false);
509       textArea.setText(text);
510       textArea.setLineWrap(true);
511       textArea.setWrapStyleWord(true);
512       content.setLayout(new BorderLayout());
513 
514       JScrollPane areaScrollPane = new JScrollPane(textArea);
515       areaScrollPane.setPreferredSize(new Dimension(600, 400));
516 
517       content.add(areaScrollPane, BorderLayout.CENTER);
518       pack();
519 
520       addWindowListener(new WindowAdapter()
521       {
522         public void windowClosing(WindowEvent e)
523         {
524           UserPrefs.getPrefs().saveWinLoc("codeview",CodeDialog.this);
525         }
526       });
527     }
528   }
529 
530   /***
531    * Establish the current preview window. Used to switch between the "normal"
532    * and "debug" preview windows.
533    * 
534    * KBR 03/26/06 Use this as the mechanism to enable the 'debug preview'
535    * menu, which is disabled by default (to have it disabled when FLM is 
536    * invoked via the user's app).
537    * 
538    * @param dframe the Jframe for the window.
539    */
540   void setPreviewFrame(LayoutConstraintsManager lcm, JFrame dframe)
541   {
542 //    if ( dframe == null )
543 //      dframe = makeNormalPreview(lcm);
544     if ( this.dframe != null )
545       this.dframe.setVisible(false);
546     this.dframe = dframe;
547 
548 //    ContainerLayout layout = constraintsManager.getContainerLayout("panel");
549 //    FormEditor fe = editors.get(layout);
550 //    if ( fe != null )
551 //      fe.setContainer(lcm.getContainer(layout));
552     
553     UserPrefs.getPrefs().useSavedBounds("debug", dframe);
554 //    Rectangle r = UserPrefs.getPrefs().getWinLoc("debug");
555 //    dframe.setLocation(r.x, r.y);
556 //    dframe.setSize(r.width, r.height);
557     dframe.setVisible(true);
558     
559     viewDebugMenu.setEnabled(true); // we have a debug frame, enable the menu
560   }
561 
562   /***
563    * Activate "debug" version of preview frame. The title is set accordingly.
564    * @param b true to activate debug version
565    */
566   protected void enableDebugPreview(boolean b)
567   {
568 	if ( dframe == null ) // KBR 03/26/06 dframe unavailable when run in user app
569 		return;
570     dframe.setTitle("FormLayoutMaker - Preview" + (b ? " (Debug)" : ""));
571     FormDebugPanel fdp = (FormDebugPanel)dframe.getContentPane().getComponent(0);
572     fdp.deactivate( !b );
573   }
574 
575   /***
576    * Makes a preview frame using FormDebugPanel. The panel can be switched
577    * between debug and non-debug modes via @see enableDebugPreview.
578    * @param lcm the constraints to be used by the preview panel
579    * @return JFrame the preview window
580    */
581   private static JFrame makeDebugPreview(LayoutConstraintsManager lcm)
582   {
583     FormDebugPanel fdp = new FormDebugPanel(true,false);
584     fdp.setBorder(Borders.DIALOG_BORDER);
585     JFrame debugFrame = new JFrame();
586     lcm.setLayout("panel", fdp);
587     debugFrame.getContentPane().add(fdp, BorderLayout.CENTER);
588     debugFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
589     return debugFrame;
590   }
591   
592   public static void main(String[] args)
593   {
594     LayoutConstraintsManager constraintsManager = new LayoutConstraintsManager();
595     
596     // Always use a FormDebugPanel as the preview panel, but switch
597     // it depending on user preference.
598     JFrame frame = LayoutFrame.makeDebugPreview(constraintsManager);
599     
600     LayoutFrame layoutFrame = new LayoutFrame(constraintsManager);
601     LayoutFrame.setDefaultLookAndFeelDecorated(true);    
602     UserPrefs.getPrefs().useSavedBounds("main", layoutFrame);
603 //    Rectangle r = UserPrefs.getPrefs().getWinLoc("main");
604 //    layoutFrame.setLocation(r.x, r.y);
605 //    layoutFrame.setSize(r.width, r.height);
606     layoutFrame.setVisible(true);
607     layoutFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
608     layoutFrame.setPreviewFrame(constraintsManager, frame);
609     layoutFrame.enableDebugPreview(UserPrefs.getPrefs().showDebugPanel());
610   }
611 }