001 /* 002 * Copyright 2002-2004 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.richclient.dialog; 017 018 import java.awt.CardLayout; 019 import java.awt.Component; 020 import java.util.HashMap; 021 import java.util.Iterator; 022 import java.util.List; 023 import java.util.Map; 024 025 import javax.swing.JComponent; 026 import javax.swing.JPanel; 027 import javax.swing.JScrollPane; 028 import javax.swing.JTree; 029 import javax.swing.event.TreeSelectionEvent; 030 import javax.swing.event.TreeSelectionListener; 031 import javax.swing.tree.DefaultMutableTreeNode; 032 import javax.swing.tree.DefaultTreeModel; 033 import javax.swing.tree.TreeNode; 034 import javax.swing.tree.TreePath; 035 import javax.swing.tree.TreeSelectionModel; 036 037 import org.springframework.richclient.form.Form; 038 import org.springframework.richclient.layout.TableLayoutBuilder; 039 import org.springframework.richclient.tree.FocusableTreeCellRenderer; 040 import org.springframework.richclient.tree.TreeUtils; 041 import org.springframework.util.Assert; 042 043 /** 044 * A concrete implementation of <code>CompositeDialogPage</code> that presents 045 * the child pages in a tree on the left, and the pages itself on the right. 046 * <p> 047 * When the user selects a page in the tree, it is shown on the right. 048 * <p> 049 * This class also decorates the entries in the tree to indicate the page 050 * completed status. 051 * 052 * @author Peter De Bruycker 053 * @author Oliver Hutchison 054 */ 055 public class TreeCompositeDialogPage extends CompositeDialogPage { 056 057 private static final DialogPage ROOT_PAGE = null; 058 059 private final PageSelector pageSelector = new PageSelector(); 060 061 private final PageTitleCellRenderer treeCellRenderer = new PageTitleCellRenderer(); 062 063 private CardLayout cardLayout; 064 065 private DefaultTreeModel pageTreeModel; 066 067 private JPanel pagePanel; 068 069 private JTree pageTree; 070 071 private Map nodes; 072 073 074 /** 075 * Constructs a new <code>TreeCompositeDialogPage</code> instance. 076 * 077 * @param pageId 078 * the pageId 079 */ 080 public TreeCompositeDialogPage(String pageId) { 081 this(pageId, true); 082 } 083 084 public TreeCompositeDialogPage(String pageId, boolean autoConfigure) { 085 super(pageId, autoConfigure); 086 nodes = new HashMap(); 087 nodes.put(ROOT_PAGE, new DefaultMutableTreeNode("pages")); 088 } 089 090 /** 091 * Adds a DialogPage to the tree. The page will be added at the top level of 092 * the tree hierarchy. 093 * 094 * @param page 095 * the page to add 096 */ 097 public void addPage(DialogPage page) { 098 addPage(ROOT_PAGE, page); 099 } 100 101 /** 102 * Adds a new page to the tree. The page is created by wrapping the form 103 * page in a FormBackedDialogPage. 104 * 105 * @param parent 106 * the parent page in the tree hierarchy 107 * @param formPage 108 * the form page to be inserted 109 * @return the DialogPage that wraps form 110 */ 111 public DialogPage addForm(DialogPage parent, Form form) { 112 DialogPage page = createDialogPage(form); 113 addPage(parent, page); 114 return page; 115 } 116 117 /** 118 * Adds a DialogPage to the tree. 119 * 120 * @param parent 121 * the parent page in the tree hierarchy 122 * @param page 123 * the page to add 124 */ 125 public void addPage(DialogPage parent, DialogPage child) { 126 DefaultMutableTreeNode parentNode = getNode(parent); 127 Assert.notNull(parentNode, "Parent dialog page must have been added before child"); 128 DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child); 129 parentNode.add(childNode); 130 nodes.put(child, childNode); 131 super.addPage(child); 132 133 // If we've already been constructed, then update our model and cards 134 if( pageTreeModel != null ) { 135 pageTreeModel.nodeStructureChanged(parentNode); 136 } 137 if( pagePanel != null ) { 138 prepareDialogPage(child); 139 processDialogPage(child); 140 // TODO: should resize all pages if this new page is the largest 141 } 142 } 143 144 /** 145 * Remove a page from the tree. 146 * @param page to remove 147 */ 148 public void removePage( DialogPage page ) { 149 DefaultMutableTreeNode treeNode = getNode(page); 150 TreeNode parentNode = treeNode.getParent(); 151 152 treeNode.removeFromParent(); 153 154 // If we've already been constructed, then update our model and cards 155 if( pagePanel != null ) { 156 JComponent control = page.getControl(); 157 pagePanel.remove(control); 158 } 159 160 if( pageTreeModel != null ) { 161 pageTreeModel.nodeStructureChanged(parentNode); 162 } 163 } 164 165 /** 166 * Adds a group DialogPages to the tree. 167 * 168 * @param parent 169 * the parent page in the tree hierarchy 170 * @param pages 171 * the pages to add 172 */ 173 public void addPages(DialogPage parent, DialogPage[] pages) { 174 for (int i = 0; i < pages.length; i++) { 175 addPage(parent, pages[i]); 176 } 177 } 178 179 /** 180 * Expands or collapses all of the tree nodes. 181 * 182 * @param expand 183 * when true expand all nodes; otherwise collapses all nodes 184 */ 185 public void expandAll(boolean expand) { 186 if (!isControlCreated()) { 187 getControl(); 188 } 189 TreeUtils.expandAll(pageTree, expand); 190 } 191 192 /** 193 * Expands or collapses a number of levels of tree nodes. 194 * 195 * @param levels 196 * the number of levels to expand/collapses 197 * @param expand 198 * when true expand all nodes; otherwise collapses all nodes 199 */ 200 public void expandLevels(int levels, boolean expand) { 201 if (!isControlCreated()) { 202 getControl(); 203 } 204 TreeUtils.expandLevels(pageTree, levels, expand); 205 } 206 207 /** 208 * @see org.springframework.richclient.dialog.AbstractDialogPage#createControl() 209 */ 210 protected JComponent createControl() { 211 createPageControls(); 212 213 cardLayout = new CardLayout(); 214 pagePanel = new JPanel(cardLayout); 215 216 List pages = getPages(); 217 for (Iterator i = pages.iterator(); i.hasNext();) { 218 DialogPage page = (DialogPage)i.next(); 219 220 processDialogPage(page); 221 } 222 223 DefaultMutableTreeNode rootNode = getNode(null); 224 pageTreeModel = new DefaultTreeModel(rootNode); 225 226 createTreeControl(); 227 pageTree.setModel(pageTreeModel); 228 229 if (rootNode.getChildCount() > 0) { 230 pageTree.setSelectionInterval(0, 0); 231 } 232 233 return createContentControl(); 234 } 235 236 protected void processDialogPage(DialogPage page) { 237 JComponent control = page.getControl(); 238 control.setPreferredSize(getLargestPageSize()); 239 pagePanel.add(control, page.getId()); 240 } 241 242 protected JPanel createContentControl() { 243 TableLayoutBuilder panelBuilder = new TableLayoutBuilder(); 244 String colSpec = "colSpec=" + getTreeControlWidth() + " rowSpec=fill:default:grow"; 245 panelBuilder.cell(new JScrollPane(pageTree), colSpec); 246 panelBuilder.gapCol(); 247 panelBuilder.cell(pagePanel, "valign=top"); 248 return panelBuilder.getPanel(); 249 } 250 251 /** 252 * Get the width of the tree component to use in the final control construction. This 253 * default implementation returns 150. 254 * @return width of tree control 255 */ 256 protected int getTreeControlWidth() { 257 return 150; 258 } 259 260 protected void createTreeControl() { 261 pageTree = new JTree(); 262 pageTree.setRootVisible(false); 263 pageTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 264 pageTree.addTreeSelectionListener(pageSelector); 265 pageTree.setCellRenderer(treeCellRenderer); 266 pageTree.setShowsRootHandles(true); 267 } 268 269 protected void updatePageComplete(DialogPage page) { 270 super.updatePageComplete(page); 271 if (pageTreeModel != null) { 272 pageTreeModel.nodeChanged(getNode(page)); 273 } 274 } 275 276 protected void updatePageLabels(DialogPage page) { 277 if (pageTreeModel != null) { 278 pageTreeModel.nodeChanged(getNode(page)); 279 } 280 } 281 282 283 protected DefaultMutableTreeNode getNode(DialogPage page) { 284 return (DefaultMutableTreeNode)nodes.get(page); 285 } 286 287 288 /** 289 * Get the nodes map. 290 * @return nodes map. 291 */ 292 protected Map getNodes() { 293 return nodes; 294 } 295 296 /** 297 * Get the page tree. 298 * @return page tree component. 299 */ 300 protected JTree getPageTree() { 301 return pageTree; 302 } 303 304 /** 305 * Get the page panel. 306 * @return page panel component. 307 */ 308 protected JPanel getPagePanel() { 309 return pagePanel; 310 } 311 312 /** 313 * Get the page tree model. 314 * @return page tree model. 315 */ 316 protected DefaultTreeModel getPageTreeModel() { 317 return pageTreeModel; 318 } 319 320 protected class PageTitleCellRenderer extends FocusableTreeCellRenderer { 321 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, 322 boolean leaf, int row, boolean hasFocus) { 323 super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); 324 325 DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; 326 if (node.getUserObject() instanceof DialogPage) { 327 DialogPage page = (DialogPage)node.getUserObject(); 328 329 this.setText(getDecoratedPageTitle(page)); 330 this.setIcon(page.getIcon()); 331 } 332 333 return this; 334 } 335 } 336 337 protected class PageSelector implements TreeSelectionListener { 338 private TreePath currentSelection; 339 340 public void valueChanged(TreeSelectionEvent e) { 341 DefaultMutableTreeNode node = (DefaultMutableTreeNode)pageTree.getLastSelectedPathComponent(); 342 343 if (node == null) { 344 pageTree.setSelectionPath(currentSelection); 345 346 return; 347 } 348 currentSelection = e.getPath(); 349 350 DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)e.getPath().getLastPathComponent(); 351 DialogPage activePage = (DialogPage)selectedNode.getUserObject(); 352 cardLayout.show(pagePanel, activePage.getId()); 353 setActivePage(activePage); 354 } 355 } 356 }