001    /* ========================================================================
002     * JCommon : a free general purpose class library for the Java(tm) platform
003     * ========================================================================
004     *
005     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006     * 
007     * Project Info:  http://www.jfree.org/jcommon/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it 
010     * under the terms of the GNU Lesser General Public License as published by 
011     * the Free Software Foundation; either version 2.1 of the License, or 
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but 
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022     * USA.  
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     * 
027     * ---------------------
028     * AbstractTabbedUI.java
029     * ---------------------
030     * (C)opyright 2004, by Thomas Morgner and Contributors.
031     *
032     * Original Author:  Thomas Morgner;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *
035     * $Id: AbstractTabbedUI.java,v 1.10 2007/11/02 17:50:37 taqua Exp $
036     *
037     * Changes
038     * -------------------------
039     * 16-Feb-2004 : Initial version
040     * 07-Jun-2004 : Added standard header (DG);
041     */
042    
043    package org.jfree.ui.tabbedui;
044    
045    import java.awt.BorderLayout;
046    import java.awt.Component;
047    import java.awt.Window;
048    import java.awt.event.ActionEvent;
049    import java.beans.PropertyChangeEvent;
050    import java.beans.PropertyChangeListener;
051    import java.util.ArrayList;
052    import javax.swing.AbstractAction;
053    import javax.swing.Action;
054    import javax.swing.JComponent;
055    import javax.swing.JMenu;
056    import javax.swing.JMenuBar;
057    import javax.swing.JPanel;
058    import javax.swing.JTabbedPane;
059    import javax.swing.SwingConstants;
060    import javax.swing.SwingUtilities;
061    import javax.swing.event.ChangeEvent;
062    import javax.swing.event.ChangeListener;
063    
064    import org.jfree.util.Log;
065    
066    /**
067     * A tabbed GUI. All views on the data are contained in tabs. 
068     *
069     * @author Thomas Morgner
070     */
071    public abstract class AbstractTabbedUI extends JComponent {
072    
073        /** The menu bar property key. */
074        public static final String JMENUBAR_PROPERTY = "jMenuBar";
075        
076        /** The global menu property. */
077        public static final String GLOBAL_MENU_PROPERTY = "globalMenu";
078    
079        /**
080         * An exit action.
081         */
082        protected class ExitAction extends AbstractAction {
083    
084            /**
085             * Defines an <code>Action</code> object with a default
086             * description string and default icon.
087             */
088            public ExitAction() {
089                putValue(NAME, "Exit");
090            }
091    
092            /**
093             * Invoked when an action occurs.
094             *
095             * @param e the event.
096             */
097            public void actionPerformed(final ActionEvent e) {
098                attempExit();
099            }
100    
101        }
102    
103        /**
104         * A tab change handler.
105         */
106        private class TabChangeHandler implements ChangeListener {
107    
108            /** The tabbed pane to which this handler is registered. */
109            private final JTabbedPane pane;
110    
111            /**
112             * Creates a new handler.
113             *
114             * @param pane the pane.
115             */
116            public TabChangeHandler(final JTabbedPane pane) {
117                this.pane = pane;
118            }
119    
120            /**
121             * Invoked when the target of the listener has changed its state.
122             *
123             * @param e a ChangeEvent object
124             */
125            public void stateChanged(final ChangeEvent e) {
126                setSelectedEditor(this.pane.getSelectedIndex());
127            }
128        }
129    
130        /**
131         * A tab enable change listener.
132         */
133        private class TabEnableChangeListener implements PropertyChangeListener {
134            
135            /**
136             * Default constructor.
137             */
138            public TabEnableChangeListener() {
139            }
140    
141            /**
142             * This method gets called when a bound property is changed.
143             *
144             * @param evt A PropertyChangeEvent object describing the event source
145             *            and the property that has changed.
146             */
147            public void propertyChange(final PropertyChangeEvent evt) {
148                if (evt.getPropertyName().equals("enabled") == false) {
149                    Log.debug ("PropertyName");
150                    return;
151                }
152                if (evt.getSource() instanceof RootEditor == false) {
153                    Log.debug ("Source");
154                    return;
155                }
156                final RootEditor editor = (RootEditor) evt.getSource();
157                updateRootEditorEnabled(editor);
158            }
159        }
160    
161        /** The list of root editors. One for each tab. */
162        private ArrayList rootEditors;
163        /** The tabbed pane filling the content area. */
164        private JTabbedPane tabbedPane;
165        /** The index of the currently selected root editor. */
166        private int selectedRootEditor;
167        /** The current toolbar. */
168        private JComponent currentToolbar;
169        /** The container component for the toolbar. */
170        private JPanel toolbarContainer;
171        /** The close action assigned to this UI. */
172        private Action closeAction;
173        /** The current menu bar. */
174        private JMenuBar jMenuBar;
175        /** Whether the UI should build a global menu from all root editors. */
176        private boolean globalMenu;
177    
178        /**
179         * Default constructor.
180         */
181        public AbstractTabbedUI() {
182            this.selectedRootEditor = -1;
183    
184            this.toolbarContainer = new JPanel();
185            this.toolbarContainer.setLayout(new BorderLayout());
186    
187            this.tabbedPane = new JTabbedPane(SwingConstants.BOTTOM);
188            this.tabbedPane.addChangeListener(new TabChangeHandler(this.tabbedPane));
189    
190            this.rootEditors = new ArrayList();
191    
192            setLayout(new BorderLayout());
193            add(this.toolbarContainer, BorderLayout.NORTH);
194            add(this.tabbedPane, BorderLayout.CENTER);
195    
196            this.closeAction = createCloseAction();
197        }
198    
199        /**
200         * Returns the tabbed pane.
201         * 
202         * @return The tabbed pane.
203         */
204        protected JTabbedPane getTabbedPane() {
205            return this.tabbedPane;
206        }
207    
208        /**
209         * Defines whether to use a global unified menu bar, which contains
210         * all menus from all tab-panes or whether to use local menubars.
211         * <p>
212         * From an usability point of view, global menubars should be preferred,
213         * as this way users always see which menus are possibly available and
214         * do not wonder where the menus are disappearing.
215         *
216         * @return true, if global menus should be used, false otherwise.
217         */
218        public boolean isGlobalMenu() {
219            return this.globalMenu;
220        }
221    
222        /**
223         * Sets the global menu flag.
224         * 
225         * @param globalMenu  the flag.
226         */
227        public void setGlobalMenu(final boolean globalMenu) {
228            this.globalMenu = globalMenu;
229            if (isGlobalMenu()) {
230                setJMenuBar(updateGlobalMenubar());
231            }
232            else {
233                if (getRootEditorCount () > 0) {
234                  setJMenuBar(createEditorMenubar(getRootEditor(getSelectedEditor())));
235                }
236            }
237        }
238    
239        /**
240         * Returns the menu bar.
241         * 
242         * @return The menu bar.
243         */
244        public JMenuBar getJMenuBar() {
245            return this.jMenuBar;
246        }
247    
248        /**
249         * Sets the menu bar.
250         * 
251         * @param menuBar  the menu bar.
252         */
253        protected void setJMenuBar(final JMenuBar menuBar) {
254            final JMenuBar oldMenuBar = this.jMenuBar;
255            this.jMenuBar = menuBar;
256            firePropertyChange(JMENUBAR_PROPERTY, oldMenuBar, menuBar);
257        }
258    
259        /**
260         * Creates a close action.
261         * 
262         * @return A close action.
263         */
264        protected Action createCloseAction() {
265            return new ExitAction();
266        }
267    
268        /**
269         * Returns the close action.
270         * 
271         * @return The close action.
272         */
273        public Action getCloseAction() {
274            return this.closeAction;
275        }
276    
277        /**
278         * Returns the prefix menus.
279         *
280         * @return The prefix menus.
281         */
282        protected abstract JMenu[] getPrefixMenus();
283    
284        /**
285         * The postfix menus.
286         *
287         * @return The postfix menus.
288         */
289        protected abstract JMenu[] getPostfixMenus();
290    
291        /**
292         * Adds menus.
293         *
294         * @param menuBar the menu bar
295         * @param customMenus the menus that should be added.
296         */
297        private void addMenus(final JMenuBar menuBar, final JMenu[] customMenus) {
298            for (int i = 0; i < customMenus.length; i++) {
299                menuBar.add(customMenus[i]);
300            }
301        }
302    
303        /**
304         * Updates the global menu bar.
305         * @return the fully initialized menu bar.
306         */
307        private JMenuBar updateGlobalMenubar () {
308          JMenuBar menuBar = getJMenuBar();
309          if (menuBar == null) {
310              menuBar = new JMenuBar();
311          }
312          else {
313              menuBar.removeAll();
314          }
315    
316          addMenus(menuBar, getPrefixMenus());
317          for (int i = 0; i < this.rootEditors.size(); i++)
318          {
319              final RootEditor editor = (RootEditor) this.rootEditors.get(i);
320              addMenus(menuBar, editor.getMenus());
321          }
322          addMenus(menuBar, getPostfixMenus());
323          return menuBar;
324        }
325    
326        /**
327         * Creates a menu bar.
328         *
329         * @param root
330         * @return A menu bar.
331         */
332        private JMenuBar createEditorMenubar(final RootEditor root) {
333    
334            JMenuBar menuBar = getJMenuBar();
335            if (menuBar == null) {
336                menuBar = new JMenuBar();
337            }
338            else {
339                menuBar.removeAll();
340            }
341    
342            addMenus(menuBar, getPrefixMenus());
343            if (isGlobalMenu())
344            {
345                for (int i = 0; i < this.rootEditors.size(); i++)
346                {
347                    final RootEditor editor = (RootEditor) this.rootEditors.get(i);
348                    addMenus(menuBar, editor.getMenus());
349                }
350            }
351            else
352            {
353                addMenus(menuBar, root.getMenus());
354            }
355            addMenus(menuBar, getPostfixMenus());
356            return menuBar;
357        }
358    
359        /**
360         * Adds a root editor.
361         *
362         * @param rootPanel the root panel.
363         */
364        public void addRootEditor(final RootEditor rootPanel) {
365            this.rootEditors.add(rootPanel);
366            this.tabbedPane.add(rootPanel.getEditorName(), rootPanel.getMainPanel());
367            rootPanel.addPropertyChangeListener("enabled", new TabEnableChangeListener());
368            updateRootEditorEnabled(rootPanel);
369            if (getRootEditorCount () == 1) {
370                setSelectedEditor(0);
371            }
372            else if (isGlobalMenu()) {
373                setJMenuBar(updateGlobalMenubar());
374            }
375        }
376    
377        /**
378         * Returns the number of root editors.
379         * 
380         * @return The count.
381         */
382        public int getRootEditorCount () {
383            return this.rootEditors.size();
384        }
385    
386        /**
387         * Returns the specified editor.
388         * 
389         * @param pos  the position index.
390         *
391         * @return The editor at the given position.
392         */
393        public RootEditor getRootEditor(final int pos) {
394            return (RootEditor) this.rootEditors.get(pos);
395        }
396    
397        /**
398         * Returns the selected editor.
399         * 
400         * @return The selected editor.
401         */
402        public int getSelectedEditor() {
403            return this.selectedRootEditor;
404        }
405    
406        /**
407         * Sets the selected editor.
408         *
409         * @param selectedEditor the selected editor.
410         */
411        public void setSelectedEditor(final int selectedEditor) {
412            final int oldEditor = this.selectedRootEditor;
413            if (oldEditor == selectedEditor) {
414                // no change - so nothing to do!
415                return;
416            }
417            this.selectedRootEditor = selectedEditor;
418            // make sure that only the selected editor is active.
419            // all other editors will be disabled, if needed and
420            // not touched if they are already in the correct state
421    
422            for (int i = 0; i < this.rootEditors.size(); i++) {
423                final boolean shouldBeActive = (i == selectedEditor);
424                final RootEditor container =
425                    (RootEditor) this.rootEditors.get(i);
426                if (container.isActive() && (shouldBeActive == false)) {
427                    container.setActive(false);
428                }
429            }
430    
431            if (this.currentToolbar != null) {
432                closeToolbar();
433                this.toolbarContainer.removeAll();
434                this.currentToolbar = null;
435            }
436    
437            for (int i = 0; i < this.rootEditors.size(); i++) {
438                final boolean shouldBeActive = (i == selectedEditor);
439                final RootEditor container =
440                    (RootEditor) this.rootEditors.get(i);
441                if ((container.isActive() == false) && (shouldBeActive == true)) {
442                    container.setActive(true);
443                    setJMenuBar(createEditorMenubar(container));
444                    this.currentToolbar = container.getToolbar();
445                    if (this.currentToolbar != null) {
446                        this.toolbarContainer.add
447                            (this.currentToolbar, BorderLayout.CENTER);
448                        this.toolbarContainer.setVisible(true);
449                        this.currentToolbar.setVisible(true);
450                    }
451                    else {
452                        this.toolbarContainer.setVisible(false);
453                    }
454    
455                    this.getJMenuBar().repaint();
456                }
457            }
458        }
459    
460        /**
461         * Closes the toolbar.
462         */
463        private void closeToolbar() {
464            if (this.currentToolbar != null) {
465                if (this.currentToolbar.getParent() != this.toolbarContainer) {
466                    // ha!, the toolbar is floating ...
467                    // Log.debug (currentToolbar.getParent());
468                    final Window w = SwingUtilities.windowForComponent(this.currentToolbar);
469                    if (w != null) {
470                        w.setVisible(false);
471                        w.dispose();
472                    }
473                }
474                this.currentToolbar.setVisible(false);
475            }
476        }
477    
478        /**
479         * Attempts to exit.
480         */
481        protected abstract void attempExit();
482    
483        /**
484         * Update handler for the enable state of the root editor.
485         * 
486         * @param editor  the editor.
487         */
488        protected void updateRootEditorEnabled(final RootEditor editor) {
489    
490            final boolean enabled = editor.isEnabled();
491            for (int i = 0; i < this.tabbedPane.getTabCount(); i++) {
492                final Component tab = this.tabbedPane.getComponentAt(i);
493                if (tab == editor.getMainPanel()) {
494                    this.tabbedPane.setEnabledAt(i, enabled);
495                    return;
496                }
497            }
498        }
499    }