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