1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: AbstractViewLayout.java 2088 2011-03-05 20:36:55Z dmsmith $
21   */
22  package org.crosswire.common.swing.desktop;
23  
24  import java.awt.BorderLayout;
25  import java.awt.Component;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  import javax.swing.JPanel;
32  import javax.swing.event.EventListenerList;
33  
34  import org.crosswire.common.swing.GuiUtil;
35  import org.crosswire.common.swing.CWMsg;
36  import org.crosswire.common.swing.desktop.event.ViewEvent;
37  import org.crosswire.common.swing.desktop.event.ViewEventListener;
38  
39  /**
40   * Abstract manager of how we layout views.
41   * 
42   * @see gnu.lgpl.License for license details.<br>
43   *      The copyright to this program is held by it's authors.
44   * @author Joe Walker [joe at eireneh dot com]
45   * @author DM Smith [dmsmith555 at yahoo dot com]
46   */
47  public abstract class AbstractViewLayout implements Viewable {
48      /**
49       * This constructor is protected because it only needs to be seen by the sub
50       * classes
51       */
52      protected AbstractViewLayout() {
53          panel = new JPanel(new BorderLayout());
54          GuiUtil.applyDefaultOrientation(panel);
55          views = new ArrayList<Component>();
56          listenerList = new EventListenerList();
57      }
58  
59      /**
60       * Add a view to the set.
61       */
62      public void addView(Component component) {
63          views.add(component);
64      }
65  
66      /**
67       * Remove a view from the set.
68       */
69      public void removeView(Component component) {
70          views.remove(component);
71          fireViewRemoved(new ViewEvent(component));
72      }
73  
74      /**
75       * Unconditionally remove a view from the set.
76       */
77      protected void forceRemoveView(Component component) {
78          views.remove(component);
79      }
80  
81      /**
82       * Get a snapshot of the views as a collection.
83       * 
84       * @return the views
85       */
86      public Collection<Component> getViews() {
87          return new ArrayList<Component>(views);
88      }
89  
90      /**
91       * Get an iterator of a snapshot of views.
92       * 
93       * @return an iterator over the views.
94       */
95      public Iterator<Component> iterator() {
96          return getViews().iterator();
97      }
98  
99      /**
100      * Copies all the views from the one layout to the other
101      * 
102      * @param other
103      *            the other layout
104      */
105     public void moveTo(AbstractViewLayout other) {
106         // Make sure we are copying to something else
107         if (getClass() == other.getClass()) {
108             return;
109         }
110         // Go through the views removing them from the layout
111         // and adding them to the other
112         for (Component view : this) {
113             forceRemoveView(view);
114             other.addView(view);
115         }
116     }
117 
118     /**
119      * Close all the views. Note the policy is enforced that one view is kept.
120      * This will keep the last one added.
121      */
122     public void closeAll() {
123         for (Component view : this) {
124             removeView(view);
125         }
126     }
127 
128     /**
129      * Close all the views but the one provided.
130      * 
131      * @param component
132      *            the view that is to remain open.
133      */
134     public void closeOthers(Component component) {
135         for (Component view : this) {
136             if (view != component) {
137                 removeView(view);
138             }
139         }
140     }
141 
142     /**
143      * Visit every view in the order that they were added.
144      * 
145      * @param visitor
146      *            The visitor for the view
147      */
148     public void visit(ViewVisitor visitor) {
149         for (Component view : this) {
150             visitor.visitView(view);
151         }
152     }
153 
154     /**
155      * Update the title of the view. If the component does not implement
156      * Titleable, then a generated title will be used.
157      * 
158      * @param component
159      *            the component whose title is to be used
160      */
161     public abstract void updateTitle(Component component);
162 
163     /**
164      * Returns the top view. If no view is the top, it returns the first one
165      * added.
166      */
167     public abstract Component getSelected();
168 
169     /**
170      * Find the view and select it.
171      * 
172      * @param component
173      */
174     public abstract void select(Component component);
175 
176     /**
177      * The number of views held by this layout.
178      * 
179      * @return the number of views held by this layout
180      */
181     public int getViewCount() {
182         return views.size();
183     }
184 
185     /**
186      * Get the view by position. Note that adding and removing views changes the
187      * indexes of the views. Do not use this for iteration as it is not thread
188      * safe.
189      * 
190      * @param i
191      *            the index of the view
192      * @return the requested view.
193      */
194     public Component getView(int i) {
195         return views.get(i);
196     }
197 
198     /**
199      * Get the title from the component, truncating it if necessary. If the
200      * component does not implement Titleable of if the title is empty, then
201      * titles are generated.
202      * 
203      * @param component
204      *            from whom the title is gotten
205      * @return the title, possibly truncated or generated
206      */
207     protected String getTitle(Component component) {
208         if (component instanceof Titleable) {
209             Titleable view = (Titleable) component;
210             String title = view.getTitle();
211             if (title != null && title.length() > 0) {
212                 if (title.length() <= MAX_TITLE_LEN) {
213                     return title;
214                 }
215                 return title.substring(0, MAX_TITLE_LEN - 3) + "...";
216             }
217 
218             // should set the title also
219             return generateTitle();
220         }
221         return generateTitle();
222     }
223 
224     /**
225      * Generates a generic title
226      * 
227      * @return the generated title
228      */
229     private String generateTitle() {
230         // TRANSLATOR: This is the label on a Bible View tab when it is cleared.
231         // {0} is a number to make the label unique.
232         return CWMsg.gettext("Untitled {0}", Integer.valueOf(base++));
233     }
234 
235     /**
236      * All parts are put into a panel. This prevents the programmer from having
237      * to change containers.
238      * 
239      * @return Returns the panel.
240      */
241     protected JPanel getPanel() {
242         return panel;
243     }
244 
245     /**
246      * A constraint that allows the panel to be filled up, stretching
247      * horizontally and vertically.
248      * 
249      * @return the constraint
250      */
251     protected Object getConstraint() {
252         return null;
253     }
254 
255     /**
256      * Adds a view event listener for notification of any changes to the view.
257      * 
258      * @param listener
259      *            the listener
260      */
261     public synchronized void addViewEventListener(ViewEventListener listener) {
262         listenerList.add(ViewEventListener.class, listener);
263     }
264 
265     /**
266      * Removes a view event listener.
267      * 
268      * @param listener
269      *            the listener
270      */
271     public synchronized void removeViewEventListener(ViewEventListener listener) {
272         listenerList.remove(ViewEventListener.class, listener);
273     }
274 
275     /**
276      * Notify the listeners that the view has been removed.
277      * 
278      * @param e
279      *            the event
280      * @see EventListenerList
281      */
282     public void fireViewRemoved(ViewEvent e) {
283         // Guaranteed to return a non-null array
284         Object[] listeners = listenerList.getListenerList();
285         // Process the listeners last to first, notifying
286         // those that are interested in this event
287         for (int i = listeners.length - 2; i >= 0; i -= 2) {
288             if (listeners[i] == ViewEventListener.class) {
289                 ((ViewEventListener) listeners[i + 1]).viewRemoved(e);
290             }
291         }
292     }
293 
294     /**
295      * The list of views.
296      */
297     private List<Component> views;
298 
299     /**
300      * The listeners for handling ViewEvent Listeners
301      */
302     private EventListenerList listenerList = new EventListenerList();
303 
304     /**
305      * The maximum length of a title before it is abbreviated
306      */
307     private static final int MAX_TITLE_LEN = 30;
308 
309     /**
310      * A shared counter for creating unknown titles.
311      */
312     private static int base = 1;
313 
314     /**
315      * The panel into which all components are placed.
316      */
317     private JPanel panel;
318 
319 }
320