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}