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