001/* ----------------------------------------------------------------------------
002   The Kiwi Toolkit - A Java Class Library
003   Copyright (C) 1998-2004 Mark A. Lindner
004
005   This library is free software; you can redistribute it and/or
006   modify it under the terms of the GNU General Public License as
007   published by the Free Software Foundation; either version 2 of the
008   License, or (at your option) any later version.
009
010   This library is distributed in the hope that it will be useful,
011   but WITHOUT ANY WARRANTY; without even the implied warranty of
012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013   General Public License for more details.
014
015   You should have received a copy of the GNU General Public License
016   along with this library; if not, write to the Free Software
017   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018   02111-1307, USA.
019 
020   The author may be contacted at: mark_a_lindner@yahoo.com
021   ----------------------------------------------------------------------------
022   $Log: WorkspaceManager.java,v $
023   Revision 1.11  2004/05/12 19:15:12  markl
024   comment block updates
025
026   Revision 1.10  2004/03/16 06:43:39  markl
027   LocaleManager method change
028
029   Revision 1.9  2003/01/19 09:50:54  markl
030   Javadoc & comment header updates.
031
032   Revision 1.8  2001/03/20 00:54:54  markl
033   Fixed deprecated calls.
034
035   Revision 1.7  2001/03/12 09:28:03  markl
036   Source code and Javadoc cleanup.
037
038   Revision 1.6  2000/03/24 10:24:36  markl
039   Fixed compile errors.
040
041   Revision 1.5  2000/03/24 10:17:54  markl
042   Internationalization changes.
043
044   Revision 1.4  1999/06/28 09:51:28  markl
045   Added getAllEditors() method.
046
047   Revision 1.3  1999/01/10 02:57:18  markl
048   minor fixes
049
050   Revision 1.2  1999/01/10 00:48:36  markl
051   Fixed to call setUnsavedChanges(false) after the call to save().
052   ----------------------------------------------------------------------------
053*/
054
055package kiwi.ui;
056
057import java.util.*;
058import java.awt.Component;
059import java.awt.event.*;
060import java.beans.*;
061import javax.swing.*;
062import javax.swing.event.*;
063
064import kiwi.event.*;
065import kiwi.ui.dialog.DialogSet;
066import kiwi.util.*;
067
068/** This class represents a workspace manager. The manager controls a set of
069  * <code>WorkspaceEditor</code>s and their relationship to a
070  * <code>JDesktopPane</code>. Each <code>WorkspaceEditor</code> is associated
071  * with an arbitrary user object (the object that it is editing) and is
072  * represented by a <code>JInternalFrame</code> in the desktop. The manager's
073  * duties include saving unsaved changes in editors when their windows are
074  * closed and providing a high-level interface for retrieving information
075  * about the editors in the desktop.
076  *
077  * @see kiwi.ui.WorkspaceEditor
078  * @see javax.swing.JDesktopPane
079  *
080  * @author Mark Lindner
081  */
082
083public class WorkspaceManager
084  {
085  private Vector editors, listeners;
086  private JDesktopPane desktop;
087  private WorkspaceEditor activeEditor = null;
088  private _FrameListener frameListener;
089  private DialogSet dialogs;
090  private Hashtable menus;
091  private LocaleData loc;
092  
093  /** Construct a new <code>WorkspaceManager</code>.
094   *
095   * @param desktop The <code>JDesktopPane</code> that is associated with this
096   * workspace. This <code>JDesktopPane</code> must have already been added to
097   * the application's component hierarchy before this constructor is called.
098   */
099
100  public WorkspaceManager(JDesktopPane desktop)
101    {
102    editors = new Vector();
103    listeners = new Vector();
104    menus = new Hashtable();
105    this.desktop = desktop;
106    frameListener = new _FrameListener();
107    dialogs = new DialogSet(KiwiUtils.getFrameForComponent(desktop),
108                            DialogSet.CENTER_PLACEMENT);
109
110    loc = LocaleManager.getDefault().getLocaleData("KiwiDialogs");
111
112    }
113
114  /** Get an enumeration of all editors existing in this workspace.
115   *
116   * @return An <code>Enumeration</code> of the editors.
117   */
118  
119  public Enumeration getAllEditors()
120    {
121    return(editors.elements());
122    }
123  
124  /** Get the currently active editor.
125   *
126   * @return The currently active editor, or <code>null</code> if no editor is
127   * currently active.
128   */
129
130  public WorkspaceEditor getActiveEditor()
131    {
132    JInternalFrame f[] = desktop.getAllFramesInLayer(0);
133    if(f.length > 0)
134      return((WorkspaceEditor)f[0]);
135    else
136      return(null);
137    }
138
139  /** Add an editor to the workspace. The editor's window becomes visible in
140   * the workspace.
141   *
142   * @param editor The <code>WorkspaceEditor</code> to add to the workspace.
143   * @see #removeEditor
144   * @see #closeEditor
145   */
146
147  public void addEditor(WorkspaceEditor editor)
148    {
149    editors.addElement(editor);
150    editor.setWorkspaceManager(this);
151    desktop.add(editor);
152    editor.addInternalFrameListener(frameListener);
153    activateEditor(editor);
154    }
155
156  /** Activate a specific editor. The editor is brought to the foreground in
157   * the desktop, deiconified if necessary, and given mouse focus. This method
158   * results in a call to the editor's <code>startEditing()</code> method.
159   *
160   * @param editor The editor to activate.
161   * @see kiwi.ui.WorkspaceEditor#startEditing
162   */
163
164  public void activateEditor(WorkspaceEditor editor)
165    {
166    editor.moveToFront();
167
168    try
169      {
170      editor.setSelected(true);
171
172      if(editor.isIcon())
173        editor.setIcon(false);
174      }
175    catch(PropertyVetoException ex) {}
176
177    editor.beginFocus();
178    startEditing(editor);
179    }
180
181  /** Remove an editor from the workspace. The editor's window becomes
182   * invisible. This method does <i>not</i> attempt a save on the editor. If
183   * the editor is currently active, its <code>stopEditing()</code> method is
184   * called before it is removed.
185   *
186   * @param editor The <code>WorkspaceEditor</code> to remove from the
187   * workspace.
188   * @see #addEditor
189   * @see #closeEditor
190   * @see #removeAllEditors
191   * @see kiwi.ui.WorkspaceEditor#stopEditing
192   */
193
194  public void removeEditor(WorkspaceEditor editor)
195    {
196    stopEditing(editor);
197    editors.removeElement(editor);
198    desktop.remove(editor);
199    editor.removeInternalFrameListener(frameListener);
200    desktop.repaint();
201    fireEditorClosed(editor);
202    }
203
204  /** Determine if there are any editors in the workspace that have unsaved
205   * changes.
206   *
207   * @return <code>true</code> if there is at least one editor with unsaved
208   * changes, <code>false</code> otherwise.
209   */
210
211  public boolean areUnsavedEditors()
212    {
213    Enumeration e = editors.elements();
214    while(e.hasMoreElements())
215      {
216      WorkspaceEditor editor = (WorkspaceEditor)e.nextElement();
217      if(editor.hasUnsavedChanges())
218        return(true);
219      }
220
221    return(false);
222    }
223
224  /** Get the editor for a specific object. Each editor can be associated with
225   * an arbitrary user object (the object that it is editing). This method
226   * searches for an editor that is associated with the given object. Objects
227   * are compared by calling <code>Object.equals()</code>, not by comparing
228   * references.
229   *
230   * @param object The object whose editor is desired.
231   * @return The editor associated with <code>object</code>, or
232   * <code>null</code> if there is no editor for this object in the workspace.
233   * @see java.lang.Object#equals
234   */
235
236  public WorkspaceEditor getEditorForObject(Object object)
237    {
238    Enumeration e = editors.elements();
239    while(e.hasMoreElements())
240      {
241      WorkspaceEditor editor = (WorkspaceEditor)e.nextElement();
242      Object o = editor.getObject();
243      if(o != null)
244        if(o.equals(object))
245          return(editor);
246      }
247
248    return(null);
249    }
250
251  /** Register a <code>WorkspaceListener</code> with this
252   * <code>WorkspaceManager</code>. Listeners are notified about events
253   * relating to the editors currently being managed by this manager.
254   *
255   * @param listener The listener to register.
256   * @see #removeWorkspaceListener
257   */
258
259  public void addWorkspaceListener(WorkspaceListener listener)
260    {
261    listeners.addElement(listener);
262    }
263
264  /** Unregister a <code>WorkspaceListener</code> from this
265   * <code>WorkspaceManager</code>.
266   *
267   * @param listener The listener to unregister.
268   * @see #addWorkspaceListener
269   */
270
271  public void removeWorkspaceListener(WorkspaceListener listener)
272    {
273    listeners.removeElement(listener);
274    }
275
276  /** Notify listeners that an editor has been selected in the workspace.
277   *
278   * @param editor The editor that was selected.
279   */
280
281  protected void fireEditorSelected(WorkspaceEditor editor)
282    {
283    WorkspaceEvent evt = null;
284    Enumeration e = listeners.elements();
285
286    while(e.hasMoreElements())
287      {
288      WorkspaceListener l = (WorkspaceListener)e.nextElement();
289      if(evt == null)
290        evt = new WorkspaceEvent(this, editor);
291      l.editorSelected(evt);
292      }
293    }
294
295  /** Notify listeners that an editor has been deselected in the workspace.
296   *
297   * @param editor The editor that was deselected.
298   */
299
300  protected void fireEditorDeselected(WorkspaceEditor editor)
301    {
302    WorkspaceEvent evt = null;
303    Enumeration e = listeners.elements();
304
305    while(e.hasMoreElements())
306      {
307      WorkspaceListener l = (WorkspaceListener)e.nextElement();
308      if(evt == null)
309        evt = new WorkspaceEvent(this, editor);
310      l.editorDeselected(evt);
311      }
312    }
313
314  /** Notify listeners that an editor has been maximized in the workspace.
315   *
316   * @param editor The editor that was maximized.
317   */
318
319  protected void fireEditorRestored(WorkspaceEditor editor)
320    {
321    WorkspaceEvent evt = null;
322    Enumeration e = listeners.elements();
323
324    while(e.hasMoreElements())
325      {
326      WorkspaceListener l = (WorkspaceListener)e.nextElement();
327      if(evt == null)
328        evt = new WorkspaceEvent(this, editor);
329      l.editorRestored(evt);
330      }
331    }
332
333  /** Notify listeners that an editor has been minimized (iconified) in the
334   * workspace.
335   *
336   * @param editor The editor that was minimized.
337   */
338
339  protected void fireEditorIconified(WorkspaceEditor editor)
340    {
341    WorkspaceEvent evt = null;
342    Enumeration e = listeners.elements();
343
344    while(e.hasMoreElements())
345      {
346      WorkspaceListener l = (WorkspaceListener)e.nextElement();
347      if(evt == null)
348        evt = new WorkspaceEvent(this, editor);
349      l.editorIconified(evt);
350      }
351    }
352
353  /** Notify listeners that an editor has been closed in the workspace.
354   *
355   * @param editor The editor that was closed.
356   */
357
358  protected void fireEditorClosed(WorkspaceEditor editor)
359    {
360    WorkspaceEvent evt = null;
361    Enumeration e = listeners.elements();
362
363    while(e.hasMoreElements())
364      {
365      WorkspaceListener l = (WorkspaceListener)e.nextElement();
366      if(evt == null)
367        evt = new WorkspaceEvent(this, editor);
368      l.editorClosed(evt);
369      }
370    }
371
372  /** Notify listeners that an editor's state has changed.
373   *
374   * @param editor The editor whose state has changed.
375   */
376
377  protected void fireEditorStateChanged(WorkspaceEditor editor)
378    {
379    WorkspaceEvent evt = null;
380    Enumeration e = listeners.elements();
381
382    while(e.hasMoreElements())
383      {
384      WorkspaceListener l = (WorkspaceListener)e.nextElement();
385      if(evt == null)
386        evt = new WorkspaceEvent(this, editor);
387      l.editorStateChanged(evt);
388      }
389    }
390
391  /** Count the editors in the workspace.
392   *
393   * @return The number of editors currently in the workspace (and being
394   * managed by this <code>WorkspaceManager</code>).
395   */
396
397  public int getEditorCount()
398    {
399    return(editors.size());
400    }
401
402  /** Close an editor. If the editor has unsaved changes, the user will be
403   * prompted with a dialog to that effect. If this editor is currently
404   * active, its <code>stopEditing()</code> method is called before it is
405   * closed.
406   *
407   * @param editor The editor to close.
408   * @see #removeEditor
409   * @see #closeAllEditors
410   * @see kiwi.ui.WorkspaceEditor#stopEditing
411   */
412
413  public boolean closeEditor(WorkspaceEditor editor)
414    {
415    if(editor.hasUnsavedChanges())
416      {
417      activateEditor(editor);
418      Object o = editor.getObject();
419      
420      String msg = loc.getMessage("kiwi.dialog.message.save_to",
421                                  ((o != null) ? o.toString() : "(Untitled)"));
422
423      if(dialogs.showQuestionDialog(msg))
424        {
425        if(!editor.save())
426          {
427          dialogs.showMessageDialog(
428            loc.getMessage("kiwi.dialog.message.save_failed"));
429          return(false);
430          }
431        else
432          {
433          editor.setChangesMade(false);
434          removeEditor(editor);
435          return(true);
436          }
437        }
438      else
439        {
440        removeEditor(editor);
441        return(true);
442        }
443      }
444    else
445      removeEditor(editor);
446
447    return(true);
448    }
449
450  /** Remove all editors from the workspace. Unconditionally removes each
451   * editor from the workspace, without attempting to save unsaved changes.
452   *
453   * @see #closeAllEditors
454   */
455
456  public void removeAllEditors()
457    {
458    for(int i = editors.size() - 1; i >= 0; i--)
459      {
460      WorkspaceEditor editor = (WorkspaceEditor)editors.elementAt(i);
461      removeEditor(editor);
462      }
463    }
464  
465  /** Close all of the editors in the workspace. For each editor with unsaved
466   * changes, the user will be prompted with a dialog asking whether that
467   * editor should save its changes.
468   *
469   * @return <code>true</code> if all editors were closed successfully,
470   * <code>false</code> otherwise.
471   * @see #removeAllEditors
472   */
473  
474  public boolean closeAllEditors()
475    {
476    for(int i = editors.size() - 1; i >= 0; i--)
477      {
478      WorkspaceEditor editor = (WorkspaceEditor)editors.elementAt(i);
479
480      // save changes
481
482      if(!closeEditor(editor)) return(false);
483      }
484
485    return(true);
486    }
487
488  /* frame event listener */
489
490  private class _FrameListener extends InternalFrameAdapter
491    {
492    public void internalFrameActivated(InternalFrameEvent evt)
493      {
494      WorkspaceEditor e = (WorkspaceEditor)evt.getSource();
495
496      if(activeEditor != null)
497        fireEditorDeselected(activeEditor);
498                        
499      fireEditorSelected(e);
500      activeEditor = e;
501      startEditing(e);
502      }
503
504    public void internalFrameIconified(InternalFrameEvent evt)
505      {
506      WorkspaceEditor e = (WorkspaceEditor)evt.getSource();
507
508      stopEditing(e);
509      fireEditorIconified(e);
510      }
511
512    public void internalFrameDeiconified(InternalFrameEvent evt)
513      {
514      WorkspaceEditor e = (WorkspaceEditor)evt.getSource();
515
516      fireEditorRestored(e);
517      }
518
519    public void internalFrameClosed(InternalFrameEvent evt)
520      {
521      }
522
523    public void internalFrameClosing(InternalFrameEvent evt)
524      {
525      WorkspaceEditor e = (WorkspaceEditor)evt.getSource();
526
527      if(e.hasUnsavedChanges())
528        {
529        Object o = e.getObject();
530
531        String msg = loc.getMessage("kiwi.dialog.message.save_to",
532                                    ((o != null) ? o.toString()
533                                     : "(Untitled)"));
534        
535        if(dialogs.showQuestionDialog(msg))
536          {
537          if(!e.save())
538            {
539            dialogs.showMessageDialog(
540              loc.getMessage("kiwi.dialog.message.save_failed"));
541            }
542          else
543            {
544            e.setChangesMade(false);
545            removeEditor(e);
546            }
547          }
548        }
549      else
550        {
551        removeEditor(e);
552        }
553      }
554    }
555
556  /** Notify the manager that an editor's state has changed. A
557   * <code>WorkspaceEditor</code> uses this method to broadcast a change event
558   * to all <code>WorkspaceListener</code>s.
559   *
560   * @param editor The editor whose state has changed.
561   */
562
563  public void notifyStateChanged(WorkspaceEditor editor)
564    {
565    fireEditorStateChanged(editor);
566    }
567
568  /** Update the look and feel of all of the components being managed by this
569   * <code>WorkspaceManager</code>.
570   */
571
572  public synchronized void updateLookAndFeel()
573    {
574    Enumeration e = editors.elements();
575    while(e.hasMoreElements())
576      {
577      WorkspaceEditor ed = (WorkspaceEditor)e.nextElement();
578      SwingUtilities.updateComponentTreeUI((Component)ed);
579      SwingUtilities.updateComponentTreeUI((Component)ed.getDesktopIcon());
580      }
581    }
582
583  /** Register a menu with this <code>WorkspaceManager</code>. This convenience
584   * method provides a means for a <code>WorkspaceEditor</code> to manipulate
585   * one or more menus in an external menubar (such as the main application's
586   * menubar).
587   *
588   * @param name A symbolic name for the menu.
589   * @param menu The <code>JMenu</code> to register.
590   * @see #unregisterMenu
591   * @see #getMenu
592   */
593  
594  public void registerMenu(String name, JMenu menu)
595    {
596    menus.put(name, menu);
597    }
598
599  /** Unregister a menu from this <code>WorkspaceManager</code>.
600   *
601   * @param name The name of the menu to unregister.
602   * @see #registerMenu
603   */
604  
605  public void unregisterMenu(String name)
606    {
607    menus.remove(name);
608    }
609
610  /** Get a reference to a <code>JMenu</code> associated with this
611   * <code>WorkspaceManager</code>.
612   *
613   * @param name The name under which the menu was registered.
614   * @return The <code>JMenu</code> reference, or <code>null</code> if there is
615   * no menu registered under the specified name.
616   * @see #registerMenu
617   * @see #unregisterMenu
618   */
619  
620  public JMenu getMenu(String name)
621    {
622    return((JMenu)(menus.get(name)));
623    }
624
625  /* Attach (or detach) an editor as a listener of all menu item action
626   * events.
627   */
628
629  private void updateMenuHooks(WorkspaceEditor editor, boolean unhook)
630    {
631    Enumeration e = menus.keys();
632    while(e.hasMoreElements())
633      {
634      String m = (String)e.nextElement();
635      JMenu menu = (JMenu)menus.get(m);
636      int c = menu.getItemCount();
637      for(int i = 0; i < c; i++)
638        {
639        JMenuItem item = menu.getItem(i);
640        if(unhook)
641          item.removeActionListener(editor);
642        else
643          item.addActionListener(editor);
644        }
645      }
646    }
647
648  /* Stop editing in a given editor.
649   *
650   * @param editor The editor to stop editing in.  The editor is detached
651   * as a listener for action events on all menu items of all registered
652   * manus, and then the editor's <code>stopEditing()</code> method is
653   * called.
654   */
655  
656  private void stopEditing(WorkspaceEditor editor)
657    {
658    editor.stopEditing();
659    updateMenuHooks(editor, true);
660    }
661
662  /* Start editing for a given editor.
663   *
664   * @param editor The editor to start editing for. The editor is attached
665   * as a listener for action events on all menu items of all registered
666   * manus, and then the editor's <code>startEditing()</code> method is
667   * called.
668   */
669  
670  private void startEditing(WorkspaceEditor editor)
671    {
672    updateMenuHooks(editor, false);
673    editor.startEditing();
674    }
675
676  }
677
678/* end of source file */