1   /**
2    * Distribution License:
3    * BibleDesktop is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU General Public License, version 2 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 General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/gpl.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: ParallelBookPicker.java 2105 2011-03-07 21:13:31Z dmsmith $
21   */
22  package org.crosswire.bibledesktop.book;
23  
24  import java.awt.Component;
25  import java.awt.FlowLayout;
26  import java.awt.event.ActionEvent;
27  import java.awt.event.ActionListener;
28  import java.awt.event.ItemEvent;
29  import java.awt.event.ItemListener;
30  import java.io.IOException;
31  import java.io.ObjectInputStream;
32  import java.util.ArrayList;
33  import java.util.Comparator;
34  import java.util.List;
35  
36  import javax.swing.JButton;
37  import javax.swing.JComboBox;
38  import javax.swing.JPanel;
39  import javax.swing.event.EventListenerList;
40  
41  import org.crosswire.bibledesktop.BDMsg;
42  import org.crosswire.common.swing.ActionFactory;
43  import org.crosswire.common.swing.CWAction;
44  import org.crosswire.common.swing.GuiUtil;
45  import org.crosswire.jsword.book.Book;
46  import org.crosswire.jsword.book.BookFilter;
47  import org.crosswire.jsword.book.BookProvider;
48  
49  /**
50   * A picker of more than one book at a time.
51   * 
52   * @see gnu.gpl.License for license details.<br>
53   *      The copyright to this program is held by it's authors.
54   * @author DM Smith [dmsmith555 at yahoo dot com]
55   */
56  public class ParallelBookPicker extends JPanel implements BookProvider {
57  
58      /**
59       * General constructor
60       * 
61       * @param filter
62       *            the kinds of books to pick.
63       * @param comparator
64       *            the order to put the books in
65       */
66      public ParallelBookPicker(BookFilter filter, Comparator<Book> comparator) {
67          this.filter = filter;
68          this.comparator = comparator;
69          initialize();
70      }
71  
72      /**
73       * Initialize the GUI
74       */
75      private void initialize() {
76          setLayout(new FlowLayout(FlowLayout.LEADING, 1, 1));
77          listeners = new EventListenerList();
78          actions = new ActionFactory(this);
79  
80          JPanel buttonBox = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
81  
82          CWAction action = actions.addAction("RemovePicker");
83          // TRANSLATOR: This is a tooltip for an icon that allows one to
84          // remove a Bible from the parallel Bible view
85          action.setTooltip(BDMsg.gettext("Remove a parallel Bible"));
86          action.setSmallIcon("images/subtract-13.png");
87          buttonBox.add(GuiUtil.flatten(new JButton(action)));
88  
89          action = actions.addAction("AddPicker");
90          // TRANSLATOR: This is a tooltip for an icon that allows one to
91          // add a Bible from the parallel Bible view
92          action.setTooltip(BDMsg.gettext("Add a parallel Bible"));
93          action.setSmallIcon("images/add-13.png");
94          buttonBox.add(GuiUtil.flatten(new JButton(action)));
95          add(buttonBox);
96  
97          // Add the first picker
98          doAddPicker();
99      }
100 
101     /**
102      * Add an new picker.
103      */
104     public void doAddPicker() {
105         BooksComboBoxModel mdlBook = new BooksComboBoxModel(filter, comparator);
106         JComboBox cboBook = new JComboBox(mdlBook);
107         cboBook.setPrototypeDisplayValue(BookListCellRenderer.PROTOTYPE_BOOK_NAME);
108         cboBook.setRenderer(new BookListCellRenderer(true));
109         cboBook.addItemListener(new SelectedItemListener(this));
110         cboBook.addActionListener(new SelectedActionListener());
111         add(cboBook);
112 
113         Book book = mdlBook.getSelectedBook();
114         if (book != null) {
115             cboBook.setToolTipText(book.getName());
116         }
117 
118         enableButtons();
119         GuiUtil.applyDefaultOrientation(this);
120         GuiUtil.refresh(this);
121         fireBooksChosen(new BookSelectEvent(this));
122     }
123 
124     /**
125      * Remove the last picker provided that there will be one that remains.
126      */
127     public void doRemovePicker() {
128         // There should always be 2 components present:
129         // the first picker and the panel holding the add/remove buttons
130         int size = getComponentCount();
131         if (size > 2) {
132             remove(size - 1);
133             enableButtons();
134             GuiUtil.refresh(this);
135             fireBooksChosen(new BookSelectEvent(this));
136         }
137 
138     }
139 
140     /* (non-Javadoc)
141      * @see org.crosswire.jsword.book.BookProvider#getBooks()
142      */
143     public Book[] getBooks() {
144         List<Book> books = new ArrayList<Book>();
145         int count = getComponentCount();
146         for (int i = 1; i < count; i++) {
147             Component comp = getComponent(i);
148             if (comp instanceof JComboBox) {
149                 JComboBox combo = (JComboBox) comp;
150                 Book book = (Book) combo.getSelectedItem();
151                 if (book != null) {
152                     books.add(book);
153                 }
154             }
155         }
156         return books.toArray(new Book[books.size()]);
157     }
158 
159     /* (non-Javadoc)
160      * @see org.crosswire.jsword.book.BookProvider#getFirstBook()
161      */
162     public Book getFirstBook() {
163         int count = getComponentCount();
164         for (int i = 1; i < count; i++) {
165             Component comp = getComponent(i);
166             if (comp instanceof JComboBox) {
167                 JComboBox combo = (JComboBox) comp;
168                 return (Book) combo.getSelectedItem();
169             }
170         }
171         return null;
172     }
173 
174     /**
175      * @return the maxPickers
176      */
177     public static int getMaxPickers() {
178         return maxPickers;
179     }
180 
181     /**
182      * @param maxPickers
183      *            the maxPickers to set
184      */
185     public static void setMaxPickers(int maxPickers) {
186         ParallelBookPicker.maxPickers = maxPickers;
187     }
188 
189     /**
190      * Add a BookSelectListener listener
191      */
192     public synchronized void addBookListener(BookSelectListener li) {
193         listeners.add(BookSelectListener.class, li);
194     }
195 
196     /**
197      * Remove a BookSelectListener listener
198      */
199     public synchronized void removeBookListener(BookSelectListener li) {
200         listeners.remove(BookSelectListener.class, li);
201     }
202 
203     /**
204      * Inform the version listeners
205      */
206     protected void fireBooksChosen(BookSelectEvent ev) {
207         // Guaranteed to return a non-null array
208         Object[] contents = listeners.getListenerList();
209 
210         // Process the listeners last to first, notifying
211         // those that are interested in this event
212         for (int i = contents.length - 2; i >= 0; i -= 2) {
213             if (contents[i] == BookSelectListener.class) {
214                 ((BookSelectListener) contents[i + 1]).booksChosen(ev);
215             }
216         }
217     }
218 
219     public void enableButtons() {
220         int count = getComponentCount() - 1;
221         actions.findAction("RemovePicker").setEnabled(count > 1);
222         actions.findAction("AddPicker").setEnabled(count < maxPickers);
223         getComponent(0).setVisible(maxPickers >= 2 || count > maxPickers);
224     }
225 
226     /**
227      * Serialization support.
228      * 
229      * @param is
230      * @throws IOException
231      * @throws ClassNotFoundException
232      */
233     private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
234         filter = null;
235         comparator = null;
236         listeners = new EventListenerList();
237         actions = new ActionFactory(this);
238 
239         is.defaultReadObject();
240     }
241 
242     /**
243      * An ItemListener for a particular combo box that tracks it's selected
244      * item.
245      */
246     final class SelectedItemListener implements ItemListener, BookProvider {
247         SelectedItemListener(ParallelBookPicker picker) {
248             this.picker = picker;
249         }
250 
251         /* (non-Javadoc)
252          * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent)
253          */
254         public void itemStateChanged(ItemEvent ev) {
255             if (ev.getStateChange() == ItemEvent.SELECTED) {
256                 JComboBox combo = (JComboBox) ev.getSource();
257 
258                 Book selected = (Book) combo.getSelectedItem();
259 
260                 fireBooksChosen(new BookSelectEvent(this));
261                 combo.setToolTipText(selected.getName());
262             }
263         }
264 
265         /* (non-Javadoc)
266          * @see org.crosswire.jsword.book.BookProvider#getBooks()
267          */
268         public Book[] getBooks() {
269             return picker.getBooks();
270         }
271 
272         /* (non-Javadoc)
273          * @see org.crosswire.jsword.book.BookProvider#getFirstBook()
274          */
275         public Book getFirstBook() {
276             return picker.getFirstBook();
277         }
278 
279         private ParallelBookPicker picker;
280     }
281 
282     /**
283      * Ensures that something is always selected.
284      */
285     static final class SelectedActionListener implements ActionListener {
286         /* (non-Javadoc)
287          * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
288          */
289         public void actionPerformed(ActionEvent e) {
290             JComboBox cbo = (JComboBox) e.getSource();
291             if (cbo.getSelectedIndex() == -1 && cbo.getItemCount() > 0) {
292                 cbo.setSelectedIndex(0);
293             }
294         }
295     }
296 
297     /**
298      * The filter to apply
299      */
300     private transient BookFilter filter;
301 
302     /**
303      * The comparator to order the books.
304      */
305     private transient Comparator<Book> comparator;
306 
307     /**
308      * Allow for adding and removing pickers.
309      */
310     private transient ActionFactory actions;
311 
312     /**
313      * Who is interested in things this DisplaySelectPane does
314      */
315     private transient EventListenerList listeners;
316 
317     /**
318      * What is the default maximum number of pickers.
319      */
320     private static final int MAX_PICKERS = 5;
321 
322     /**
323      * The maximum number of pickers.
324      */
325     private static int maxPickers = MAX_PICKERS;
326 
327     /**
328      * Serialization ID
329      */
330     private static final long serialVersionUID = 1633401996774729671L;
331 }
332