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 }