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 }