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    }