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
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;
052import javax.swing.AbstractAction;
053import javax.swing.Action;
054import javax.swing.JComponent;
055import javax.swing.JMenu;
056import javax.swing.JMenuBar;
057import javax.swing.JPanel;
058import javax.swing.JTabbedPane;
059import javax.swing.SwingConstants;
060import javax.swing.SwingUtilities;
061import javax.swing.event.ChangeEvent;
062import javax.swing.event.ChangeListener;
063
064import 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 */
071public 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}