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