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: MultiBookPane.java 2056 2010-12-12 04:34:41Z 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.beans.PropertyChangeEvent;
29  import java.io.IOException;
30  import java.io.ObjectInputStream;
31  
32  import javax.swing.JComboBox;
33  import javax.swing.JList;
34  import javax.swing.JPanel;
35  import javax.swing.JScrollPane;
36  import javax.swing.JSplitPane;
37  import javax.swing.JTree;
38  import javax.swing.ListSelectionModel;
39  import javax.swing.event.ListSelectionEvent;
40  import javax.swing.event.ListSelectionListener;
41  import javax.swing.event.TreeSelectionEvent;
42  import javax.swing.event.TreeSelectionListener;
43  import javax.swing.tree.TreePath;
44  
45  import org.crosswire.bibledesktop.display.BookDataDisplay;
46  import org.crosswire.bibledesktop.display.BookDataDisplayFactory;
47  import org.crosswire.bibledesktop.display.URIEventListener;
48  import org.crosswire.bibledesktop.passage.KeyChangeListener;
49  import org.crosswire.bibledesktop.passage.KeyListListModel;
50  import org.crosswire.bibledesktop.passage.KeyTreeCellRenderer;
51  import org.crosswire.bibledesktop.passage.KeyTreeModel;
52  import org.crosswire.bibledesktop.passage.KeyTreeNode;
53  import org.crosswire.common.swing.CWScrollPane;
54  import org.crosswire.common.swing.FixedSplitPane;
55  import org.crosswire.common.swing.GuiUtil;
56  import org.crosswire.common.util.Logger;
57  import org.crosswire.jsword.book.Book;
58  import org.crosswire.jsword.book.BookFilter;
59  import org.crosswire.jsword.book.BookFilters;
60  import org.crosswire.jsword.book.Defaults;
61  import org.crosswire.jsword.book.KeyType;
62  import org.crosswire.jsword.passage.Key;
63  import org.crosswire.jsword.passage.NoSuchKeyException;
64  import org.crosswire.jsword.passage.Passage;
65  import org.crosswire.jsword.passage.PreferredKey;
66  import org.crosswire.jsword.passage.Verse;
67  
68  /**
69   * Builds a panel on which all the non-Bible books and their entries are
70   * visible.
71   * 
72   * @see gnu.gpl.License for license details.<br>
73   *      The copyright to this program is held by it's authors.
74   * @author Joe Walker [joe at eireneh dot com]
75   * @author DM Smith [dmsmith555 at yahoo dot com]
76   */
77  public class MultiBookPane extends JSplitPane implements BookDataDisplay {
78      /**
79       * Setup the GUI
80       */
81      public MultiBookPane() {
82          init();
83  
84          // This must come after the setViewportView() calls so scrolling works
85          bookList.setSelectedValue(Defaults.getDailyDevotional(), true);
86      }
87  
88      /**
89       * GUI initializer
90       */
91      private void init() {
92          display = BookDataDisplayFactory.createBookDataDisplay();
93  
94          Component bookPicker = createBookPicker();
95  
96          commentaryPicker = createCommentaryPicker();
97  
98          dictionaryKeyScroller = createDictionaryPicker();
99  
100         genBookKeyScroller = createGenBookPicker();
101 
102         JScrollPane scrDisplay = new CWScrollPane(display.getComponent());
103 
104         sptMain = new FixedSplitPane(false);
105         sptMain.setOrientation(JSplitPane.VERTICAL_SPLIT);
106         // Make the top 20% of the total
107         sptMain.setResizeWeight(0.2D);
108         sptMain.setTopComponent(new JPanel());
109         sptMain.setBottomComponent(scrDisplay);
110 
111         // this.setResizeWeight(0.1D);
112         // this.setMinimumSize(new Dimension(0, 0));
113         this.setOrientation(JSplitPane.VERTICAL_SPLIT);
114         this.setTopComponent(bookPicker);
115         this.setBottomComponent(sptMain);
116         this.setBorder(null);
117 
118         Object thisUI = this.getUI();
119         if (thisUI instanceof javax.swing.plaf.basic.BasicSplitPaneUI) {
120             ((javax.swing.plaf.basic.BasicSplitPaneUI) thisUI).getDivider().setBorder(null);
121         }
122     }
123 
124     /* (non-Javadoc)
125      * @see org.crosswire.bibledesktop.display.BookDataDisplay#clearBookData()
126      */
127     public void clearBookData() {
128         setBookData(null, null);
129     }
130 
131     /* (non-Javadoc)
132      * @see org.crosswire.bibledesktop.display.BookDataDisplay#setBookData(org.crosswire.jsword.book.Book[], org.crosswire.jsword.passage.Key)
133      */
134     public void setBookData(Book[] books, Key key) {
135         display.setBookData(books, key);
136     }
137 
138     /* (non-Javadoc)
139      * @see org.crosswire.bibledesktop.display.BookDataDisplay#setCompareBooks(boolean)
140      */
141     public void setCompareBooks(boolean compare) {
142         display.setCompareBooks(compare);
143     }
144 
145     /* (non-Javadoc)
146      * @see org.crosswire.bibledesktop.display.BookDataDisplay#refresh()
147      */
148     public void refresh() {
149         display.refresh();
150     }
151 
152     /* (non-Javadoc)
153      * @see org.crosswire.bibledesktop.display.BookDataDisplay#getComponent()
154      */
155     public Component getComponent() {
156         return this;
157     }
158 
159     /* (non-Javadoc)
160      * @see org.crosswire.bibledesktop.display.BookDataDisplay#copy()
161      */
162     public void copy() {
163         display.copy();
164     }
165 
166     /* (non-Javadoc)
167      * @see org.crosswire.bibledesktop.display.BookDataDisplay#getKey()
168      */
169     public Key getKey() {
170         return (Key) dictionaryKeyList.getSelectedValue();
171     }
172 
173     /* (non-Javadoc)
174      * @see org.crosswire.bibledesktop.display.BookDataDisplay#addKeyChangeListener(org.crosswire.bibledesktop.passage.KeyChangeListener)
175      */
176     public void addKeyChangeListener(KeyChangeListener listener) {
177         display.addKeyChangeListener(listener);
178     }
179 
180     /* (non-Javadoc)
181      * @see org.crosswire.bibledesktop.display.BookDataDisplay#removeKeyChangeListener(org.crosswire.bibledesktop.passage.KeyChangeListener)
182      */
183     public void removeKeyChangeListener(KeyChangeListener listener) {
184         display.removeKeyChangeListener(listener);
185     }
186 
187     /* (non-Javadoc)
188      * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
189      */
190     public void propertyChange(PropertyChangeEvent evt) {
191         display.propertyChange(evt);
192     }
193 
194     /* (non-Javadoc)
195      * @see org.crosswire.bibledesktop.display.BookDataDisplay#addURIEventListener(org.crosswire.bibledesktop.display.URIEventListener)
196      */
197     public void addURIEventListener(URIEventListener listener) {
198         display.addURIEventListener(listener);
199     }
200 
201     /* (non-Javadoc)
202      * @see org.crosswire.bibledesktop.display.BookDataDisplay#removeURIEventListener(org.crosswire.bibledesktop.display.URIEventListener)
203      */
204     public void removeURIEventListener(URIEventListener listener) {
205         display.removeURIEventListener(listener);
206     }
207 
208     /* (non-Javadoc)
209      * @see org.crosswire.bibledesktop.display.BookDataDisplay#getBooks()
210      */
211     public Book[] getBooks() {
212         return new Book[] {
213             book
214         };
215     }
216 
217     /* (non-Javadoc)
218      * @see org.crosswire.bibledesktop.display.BookDataDisplay#getFirstBook()
219      */
220     public Book getFirstBook() {
221         return book;
222     }
223 
224     /**
225      * See if the current dictionary has a mention of the word in question.
226      * LATER(joe): add a background task to highlight other dictionaries that
227      * have the word.
228      */
229     public void setWord(String data) {
230         if (book == null) {
231             return;
232         }
233 
234         try {
235             Key key = book.getKey(data);
236             dictionaryKeyList.setSelectedValue(key, true);
237         } catch (NoSuchKeyException ex) {
238             return;
239         }
240 
241     }
242 
243     /**
244      * Accessor for the current passage
245      */
246     public void setKey(Key key) {
247         if (key instanceof Passage) {
248             Passage ref = (Passage) key;
249             if (ref.countVerses() > 0) {
250                 set.setVerse(ref.getVerseAt(0));
251             }
252         }
253 
254         updateDisplay();
255     }
256 
257     /*
258         // Code to search for a word
259         for (BookMetaData bmd : Books.getBooks(filter)) {
260             DictionaryMetaData dmd = (DictionaryMetaData) bmd;
261             Dictionary tempdict = dmd.getDictionary();
262             try
263             {
264                 Key key = tempdict.getKey(data);
265                 bookList.setSelectedValue(tempdict, true);
266                 lstentries.setSelectedValue(key, true);
267                 return;
268             }
269             catch (BookException ex)
270             {
271                 // ignore - we only wanted to see if it could be done.
272             }
273         }     
274     */
275 
276     public void selectBook(Book selectedBook) {
277         bookList.setSelectedValue(selectedBook, true);
278     }
279 
280     /**
281      * Create a book picker of all non-bibles
282      * 
283      * @return The scrollable picker
284      */
285     private Component createBookPicker() {
286         BookFilter filter = BookFilters.getNonBibles();
287         BooksComboBoxModel mdlBooks = new BooksComboBoxModel(filter);
288 
289         bookList = new JList();
290         bookList.setVisibleRowCount(6);
291         bookList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
292         bookList.setModel(mdlBooks);
293         bookList.setCellRenderer(new BookListCellRenderer());
294         bookList.setPrototypeCellValue(BookListCellRenderer.PROTOTYPE_BOOK_NAME);
295         bookList.addListSelectionListener(new ListSelectionListener() {
296             public void valueChanged(ListSelectionEvent ev) {
297                 if (ev.getValueIsAdjusting()) {
298                     return;
299                 }
300 
301                 newBook();
302             }
303         });
304 
305         Component scroller = new CWScrollPane(bookList);
306         GuiUtil.applyDefaultOrientation(scroller);
307         return scroller;
308     }
309 
310     /**
311      * Build a tree for a GenBook
312      */
313     private Component createCommentaryPicker() {
314         JComboBox cboBooks = new JComboBox();
315         JComboBox cboChaps = new JComboBox();
316         JComboBox cboVerse = new JComboBox();
317         set = new BibleComboBoxModelSet(cboBooks, cboChaps, cboVerse);
318 
319         set.addActionListener(new ActionListener() {
320             public void actionPerformed(ActionEvent ev) {
321                 updateDisplay();
322             }
323         });
324 
325         JPanel panel = new JPanel();
326         panel.setLayout(new FlowLayout());
327         panel.add(cboBooks, null);
328         panel.add(cboChaps, null);
329         panel.add(cboVerse, null);
330 
331         GuiUtil.applyDefaultOrientation(panel);
332 
333         return panel;
334     }
335 
336     /**
337      * Build a tree for a GenBook
338      */
339     private Component createDictionaryPicker() {
340         dictionaryKeyList = new JList();
341         dictionaryKeyList.addListSelectionListener(new ListSelectionListener() {
342             public void valueChanged(ListSelectionEvent ev) {
343                 newEntry();
344             }
345         });
346         Component scroller = new CWScrollPane(dictionaryKeyList);
347         GuiUtil.applyDefaultOrientation(scroller);
348         return scroller;
349     }
350 
351     /**
352      * Build a tree for a GenBook
353      */
354     private Component createGenBookPicker() {
355         genBookKeyTree = new JTree();
356         genBookKeyTree.setModel(new KeyTreeModel(null));
357         genBookKeyTree.setShowsRootHandles(true);
358         genBookKeyTree.setRootVisible(false);
359         genBookKeyTree.putClientProperty("JTree.lineStyle", "Angled");
360         genBookKeyTree.setCellRenderer(new KeyTreeCellRenderer());
361         genBookKeyTree.addTreeSelectionListener(new TreeSelectionListener() {
362             public void valueChanged(TreeSelectionEvent ev) {
363                 selected();
364             }
365         });
366 
367         Component scroller = new CWScrollPane(genBookKeyTree);
368         GuiUtil.applyDefaultOrientation(scroller);
369         return scroller;
370     }
371 
372     /**
373      * Called when someone selects a new Dictionary
374      */
375     /*private*/final void newBook() {
376         // First ensure that all the pickers make sense if the user
377         // unselects the book
378         if (book != null) {
379             KeyType currentCategory = book.getBookMetaData().getKeyType();
380             if (KeyType.LIST.equals(currentCategory)) {
381                 // Don't leave the scroller in the middle of the list!
382                 dictionaryKeyList.ensureIndexIsVisible(0);
383                 // Make sure that the list of keys is empty.
384                 dictionaryKeyList.setModel(new KeyListListModel(null));
385             } else if (KeyType.TREE.equals(currentCategory)) {
386                 // Don't leave the scroller in the middle of the list!
387                 genBookKeyTree.scrollRowToVisible(0);
388 
389                 // Make sure that the list of keys is empty.
390                 KeyTreeModel model = new KeyTreeModel(null);
391                 genBookKeyTree.setModel(model);
392             }
393         }
394         // Make sure that the display is empty.
395         display.clearBookData();
396 
397         Object selected = bookList.getSelectedValue();
398         if (selected != null) {
399             Book selectedBook = (Book) selected;
400             KeyType category = selectedBook.getBookMetaData().getKeyType();
401             // divider snaps back to its starting point when a new component is
402             // set
403             int dividerLocation = sptMain.getDividerLocation();
404             if (KeyType.VERSE.equals(category)) {
405                 updateDisplay();
406                 sptMain.setTopComponent(commentaryPicker);
407             } else if (KeyType.LIST.equals(category)) {
408                 book = selectedBook;
409                 Key key = book.getGlobalKeyList();
410 
411                 KeyListListModel model = new KeyListListModel(key);
412                 dictionaryKeyList.setModel(model);
413 
414                 if (book instanceof PreferredKey) {
415                     PreferredKey pref = (PreferredKey) book;
416                     Key prefkey = pref.getPreferred();
417 
418                     dictionaryKeyList.setSelectedValue(prefkey, true);
419                 }
420 
421                 sptMain.setTopComponent(dictionaryKeyScroller);
422             } else  { // KeyType.TREE.equals(category)
423                 book = selectedBook;
424                 Key key = book.getGlobalKeyList();
425 
426                 KeyTreeModel model = new KeyTreeModel(key);
427                 genBookKeyTree.setModel(model);
428 
429                 sptMain.setTopComponent(genBookKeyScroller);
430             }
431 
432             sptMain.setDividerLocation(dividerLocation);
433         }
434     }
435 
436     /**
437      * 
438      */
439     /*protected*/final void updateDisplay() {
440         Book selectedBook = (Book) bookList.getSelectedValue();
441         if (selectedBook == null) {
442             log.warn("no selected book");
443             return;
444         }
445 
446         Verse verse = set.getVerse();
447         display.setBookData(new Book[] {
448             selectedBook
449         }, verse);
450     }
451 
452     /**
453      * Called when someone selects a new entry from the current dictionary
454      */
455     /*private*/final void newEntry() {
456         Key key = (Key) dictionaryKeyList.getSelectedValue();
457         if (key != null) {
458             display.setBookData(new Book[] {
459                 book
460             }, key);
461         }
462     }
463 
464     /**
465      * Something has been (un)selected in the tree
466      */
467     /*private*/void selected() {
468         TreePath path = genBookKeyTree.getSelectionPath();
469 
470         if (path != null) {
471             KeyTreeNode node = (KeyTreeNode) path.getLastPathComponent();
472             if (node != null) {
473                 display.setBookData(new Book[] {
474                     book
475                 }, node.getKey());
476             }
477         }
478 
479     }
480 
481     /**
482      * Serialization support.
483      * 
484      * @param is
485      * @throws IOException
486      * @throws ClassNotFoundException
487      */
488     private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
489         // We don't serialize views
490         display = BookDataDisplayFactory.createBookDataDisplay();
491         book = null;
492         set = null;
493         is.defaultReadObject();
494     }
495 
496     /**
497      * The display of OSIS data
498      */
499     private transient BookDataDisplay display;
500     private transient Book book;
501     protected transient BibleComboBoxModelSet set;
502     private Component commentaryPicker;
503     private Component genBookKeyScroller;
504     private JTree genBookKeyTree;
505     private JList bookList;
506     private JSplitPane sptMain;
507     private Component dictionaryKeyScroller;
508     private JList dictionaryKeyList;
509 
510     /**
511      * The log stream
512      */
513     private static final Logger log = Logger.getLogger(MultiBookPane.class);
514 
515     /**
516      * Serialization ID
517      */
518     private static final long serialVersionUID = 3616449020667442997L;
519 }
520