| TabbedBookDataDisplay.java |
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: TabbedBookDataDisplay.java 2091 2011-03-07 04:15:31Z dmsmith $
21 */
22 package org.crosswire.bibledesktop.display.basic;
23
24 import java.awt.BorderLayout;
25 import java.awt.Component;
26 import java.beans.PropertyChangeEvent;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31
32 import javax.swing.JPanel;
33 import javax.swing.JScrollPane;
34 import javax.swing.JTabbedPane;
35 import javax.swing.SwingConstants;
36 import javax.swing.event.ChangeEvent;
37 import javax.swing.event.ChangeListener;
38
39 import org.crosswire.bibledesktop.BDMsg;
40 import org.crosswire.bibledesktop.display.BookDataDisplay;
41 import org.crosswire.bibledesktop.display.BookDataDisplayFactory;
42 import org.crosswire.bibledesktop.display.URIEventListener;
43 import org.crosswire.bibledesktop.passage.KeyChangeListener;
44 import org.crosswire.common.swing.CWScrollPane;
45 import org.crosswire.common.swing.GuiUtil;
46 import org.crosswire.jsword.book.Book;
47 import org.crosswire.jsword.passage.Key;
48 import org.crosswire.jsword.passage.KeyUtil;
49 import org.crosswire.jsword.passage.Passage;
50
51 /**
52 * An inner component of Passage pane that can't show the list.
53 * <p>
54 * At some stage we should convert this code to remove Passage so it will work
55 * with all Books and not just Bibles. Code is included (commented out) on how
56 * this could be done.
57 *
58 * @see gnu.gpl.License for license details.<br>
59 * The copyright to this program is held by it's authors.
60 * @author Joe Walker [joe at eireneh dot com]
61 */
62 public class TabbedBookDataDisplay implements BookDataDisplay {
63 /**
64 * Simple Constructor
65 */
66 public TabbedBookDataDisplay() {
67 views = new HashMap<JScrollPane, BookDataDisplay>();
68 displays = new ArrayList<BookDataDisplay>();
69 pnlMore = new JPanel();
70 pnlMain = new JPanel();
71
72 bookDataDisplay = createInnerDisplayPane();
73
74 tabMain = new JTabbedPane();
75 tabMain.setTabPlacement(SwingConstants.BOTTOM);
76 tabMain.addChangeListener(new ChangeListener() {
77 public void stateChanged(ChangeEvent ev) {
78 tabChanged();
79 }
80 });
81
82 pnlMain.setLayout(new BorderLayout());
83
84 center = bookDataDisplay.getComponent();
85 scrMain = new CWScrollPane(center);
86 pnlMain.add(scrMain, BorderLayout.CENTER);
87 }
88
89 /* (non-Javadoc)
90 * @see org.crosswire.bibledesktop.display.BookDataDisplay#getComponent()
91 */
92 public Component getComponent() {
93 return pnlMain;
94 }
95
96 /* (non-Javadoc)
97 * @see org.crosswire.bibledesktop.display.BookDataDisplay#clearBookData()
98 */
99 public void clearBookData() {
100 setBookData(null, null);
101 }
102
103 /* (non-Javadoc)
104 * @see org.crosswire.bibledesktop.display.BookDataDisplay#setBookData(org.crosswire.jsword.book.Book[], org.crosswire.jsword.passage.Key)
105 */
106 public void setBookData(Book[] books, Key newkey) {
107 this.books = books == null ? null : (Book[]) books.clone();
108 this.key = KeyUtil.getPassage(newkey);
109
110 // Tabbed view or not we should clear out the old tabs
111 tabMain.removeAll();
112 views.clear();
113 displays.clear();
114 displays.add(bookDataDisplay);
115
116 // So use purely Keys and not Passage, create a utility to cut up
117 // a key into a number of keys.
118 // private Key keys;
119 // private Passage waiting;
120 // ...
121 // keys = null; // OSISUtil.pagenate(key, pagesize * 10);
122 // tabs = (keys.size() > 1);
123 // And then inside the if:
124 // Key first = (Key) keys.get(0);
125 // in place of the first/waiting code.
126 // Then down in tabChanged()
127 // // What do we display next
128 // int countTabs = tabMain.getTabCount();
129 // Key next = (Key) keys.get(countTabs);
130 // And a bit lower:
131 // // Do we need a new more tab
132 // if (countTabs >= keys.size())
133
134 // Do we need a tabbed view
135 tabs = key != null && key.countVerses() > pageSize;
136 if (tabs) {
137 // Calc the verses to display in this tab
138 Passage first = (Passage) key.clone();
139 waiting = first.trimVerses(pageSize);
140
141 // Create the first tab
142 BookDataDisplay pnlNew = createInnerDisplayPane();
143 pnlNew.setBookData(books, first);
144
145 Component display = pnlNew.getComponent();
146 JScrollPane scrView = new CWScrollPane(display);
147 views.put(scrView, pnlNew);
148
149 tabMain.add(getTabName(first), scrView);
150 // TRANSLATOR: Extra bottom tabs are created when there is too much to display in one.
151 // Rather than figuring out how many tabs there should be, we label one "More..."
152 // When the user clicks on it, it is filled with what remains. And if it is filled
153 // to overflowing, another "More..." tab is created.
154 tabMain.add(BDMsg.gettext("More ..."), pnlMore);
155
156 setCenterComponent(tabMain);
157 } else {
158 bookDataDisplay.setBookData(books, key);
159
160 setCenterComponent(bookDataDisplay.getComponent());
161 }
162
163 // Since we changed the contents of the page we need to cause it to
164 // repaint
165 GuiUtil.refresh(pnlMain);
166 }
167
168 /* (non-Javadoc)
169 * @see org.crosswire.bibledesktop.display.BookDataDisplay#setCompareBooks(boolean)
170 */
171 public void setCompareBooks(boolean compare) {
172 // Now go through all the known tabs and refresh each
173 for (BookDataDisplay bdd : displays) {
174 bdd.setCompareBooks(compare);
175 }
176 }
177
178 /* (non-Javadoc)
179 * @see org.crosswire.bibledesktop.display.BookDataDisplay#refresh()
180 */
181 public void refresh() {
182 // Now go through all the known tabs and refresh each
183 for (BookDataDisplay bdd : displays) {
184 bdd.refresh();
185 }
186 }
187
188 /* (non-Javadoc)
189 * @see org.crosswire.bibledesktop.display.BookDataDisplay#getKey()
190 */
191 public Key getKey() {
192 return key;
193 }
194
195 /* (non-Javadoc)
196 * @see org.crosswire.bibledesktop.display.BookDataDisplay#getBook()
197 */
198 public Book[] getBooks() {
199 return books == null ? null : (Book[]) books.clone();
200 }
201
202 /* (non-Javadoc)
203 * @see org.crosswire.bibledesktop.display.BookDataDisplay#getFirstBook()
204 */
205 public Book getFirstBook() {
206 return books != null && books.length > 0 ? books[0] : null;
207 }
208
209 /* (non-Javadoc)
210 * @see org.crosswire.bibledesktop.display.BookDataDisplay#copy()
211 */
212 public void copy() {
213 getInnerDisplayPane().copy();
214 }
215
216 /* (non-Javadoc)
217 * @see org.crosswire.bibledesktop.display.BookDataDisplay#addKeyChangeListener(org.crosswire.bibledesktop.passage.KeyChangeListener)
218 */
219 public void addKeyChangeListener(KeyChangeListener listener) {
220 // First add to our list of listeners so when we add a new tab
221 // we can add this new listener to the new tab
222 List<KeyChangeListener> temp = new ArrayList<KeyChangeListener>();
223 if (keyEventListeners == null) {
224 temp.add(listener);
225 keyEventListeners = temp;
226 } else {
227 temp.addAll(keyEventListeners);
228
229 if (!temp.contains(listener)) {
230 temp.add(listener);
231 keyEventListeners = temp;
232 }
233 }
234
235 for (BookDataDisplay bdd : displays) {
236 bdd.addKeyChangeListener(listener);
237 }
238 }
239
240 /* (non-Javadoc)
241 * @see org.crosswire.bibledesktop.display.BookDataDisplay#removeKeyChangeListener(org.crosswire.bibledesktop.passage.KeyChangeListener)
242 */
243 public void removeKeyChangeListener(KeyChangeListener listener) {
244 // First remove from the list of listeners
245 if (keyEventListeners != null && keyEventListeners.contains(listener)) {
246 List<KeyChangeListener> temp = new ArrayList<KeyChangeListener>();
247 temp.addAll(keyEventListeners);
248 temp.remove(listener);
249 keyEventListeners = temp;
250 }
251
252 for (BookDataDisplay bdd : displays) {
253 bdd.removeKeyChangeListener(listener);
254 }
255 }
256
257 /* (non-Javadoc)
258 * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
259 */
260 public void propertyChange(PropertyChangeEvent evt) {
261 // Now go through all the known syncs and add this one in
262 for (BookDataDisplay bdd : displays) {
263 bdd.propertyChange(evt);
264 }
265 }
266
267 /* (non-Javadoc)
268 * @see org.crosswire.bibledesktop.display.BookDataDisplay#addURIEventListener(org.crosswire.bibledesktop.display.URIEventListener)
269 */
270 public synchronized void addURIEventListener(URIEventListener listener) {
271 // First add to our list of listeners so when we add a new tab
272 // we can add this new listener to the new tab
273 List<URIEventListener> temp = new ArrayList<URIEventListener>();
274 if (uriEventListeners == null) {
275 temp.add(listener);
276 uriEventListeners = temp;
277 } else {
278 temp.addAll(uriEventListeners);
279
280 if (!temp.contains(listener)) {
281 temp.add(listener);
282 uriEventListeners = temp;
283 }
284 }
285
286 // Now go through all the known syncs and add this one in
287 for (BookDataDisplay bdd : displays) {
288 bdd.addURIEventListener(listener);
289 }
290 }
291
292 /* (non-Javadoc)
293 * @see org.crosswire.bibledesktop.display.BookDataDisplay#removeURIEventListener(org.crosswire.bibledesktop.display.URIEventListener)
294 */
295 public synchronized void removeURIEventListener(URIEventListener listener) {
296 // First remove from the list of listeners
297 if (uriEventListeners != null && uriEventListeners.contains(listener)) {
298 List<URIEventListener> temp = new ArrayList<URIEventListener>();
299 temp.addAll(uriEventListeners);
300 temp.remove(listener);
301 uriEventListeners = temp;
302 }
303
304 // Now remove from all the known syncs
305 for (BookDataDisplay bdd : displays) {
306 bdd.removeURIEventListener(listener);
307 }
308 }
309
310 /**
311 * Make a new component reside in the center of this panel
312 */
313 private void setCenterComponent(Component comp) {
314 // We are currently viewing either a set of tabs (tabMain)
315 // or a single page (bookDataDisplay.getComponent()).
316 // The new center component is either tabMain
317 // or something that should be wrapped with a scroller.
318 //
319 // So when we go from tabMain, we need to remove center
320 // and when we go from a single page we need to remove the scroller
321 //
322
323 if (center != comp) {
324 pnlMain.removeAll();
325 center = comp;
326 if (center == tabMain) {
327 pnlMain.add(tabMain, BorderLayout.CENTER);
328 } else {
329 pnlMain.add(scrMain, BorderLayout.CENTER);
330 // scrMain.setViewportView(center);
331 }
332 }
333 }
334
335 /**
336 * Tabs changed, generate some stuff
337 */
338 /*private*/final void tabChanged() {
339 // This is someone clicking on more isnt it?
340 if (tabMain.getSelectedComponent() != pnlMore) {
341 return;
342 }
343
344 // First remove the old more ... tab that the user has just selected
345 tabMain.remove(pnlMore);
346
347 // What do we display next
348 Passage next = waiting;
349 waiting = next.trimVerses(pageSize);
350
351 // Create a new tab
352 BookDataDisplay pnlNew = createInnerDisplayPane();
353 pnlNew.setBookData(books, next);
354
355 JScrollPane scrView = new CWScrollPane(pnlNew.getComponent());
356 views.put(scrView, pnlNew);
357
358 tabMain.add(getTabName(next), scrView);
359
360 // Do we need a new more tab
361 if (waiting != null) {
362 // TRANSLATOR: Extra bottom tabs are created when there is too much to display in one.
363 // Rather than figuring out how many tabs there should be, we label one "More..."
364 // When the user clicks on it, it is filled with what remains. And if it is filled
365 // to overflowing, another "More..." tab is created.
366 tabMain.add(BDMsg.gettext("More ..."), pnlMore);
367 }
368
369 // Select the real new tab in place of any more tabs
370 tabMain.setSelectedComponent(scrView);
371 }
372
373 /**
374 * Accessor for the current TextComponent
375 */
376 public BookDataDisplay getInnerDisplayPane() {
377 if (tabs) {
378 Object o = tabMain.getSelectedComponent();
379 JScrollPane sp = (JScrollPane) o;
380 return views.get(sp);
381 }
382 return bookDataDisplay;
383 }
384
385 /**
386 * Tab creation helper
387 */
388 private synchronized BookDataDisplay createInnerDisplayPane() {
389 BookDataDisplay display = BookDataDisplayFactory.createBookDataDisplay();
390 displays.add(display);
391
392 // Add all the known listeners to this new BookDataDisplay
393 if (uriEventListeners != null) {
394 for (URIEventListener li : uriEventListeners) {
395 display.addURIEventListener(li);
396 }
397 }
398
399 // Add all the known listeners to this new BookDataDisplay
400 if (keyEventListeners != null) {
401 for (KeyChangeListener li : keyEventListeners) {
402 display.addKeyChangeListener(li);
403 }
404 }
405
406 return display;
407 }
408
409 /**
410 * Accessor for the page size
411 */
412 public static void setPageSize(int pageSize) {
413 TabbedBookDataDisplay.pageSize = pageSize;
414 }
415
416 /**
417 * Accessor for the page size
418 */
419 public static int getPageSize() {
420 return pageSize;
421 }
422
423 /**
424 * Ensure that the tab names are not too long - 25 chars max
425 *
426 * @param key
427 * The key to get a short name from
428 * @return The first 9 chars followed by ... followed by the last 9
429 */
430 private static String getTabName(Key key) {
431 String tabname = key.getName();
432 int len = tabname.length();
433 if (len > TITLE_LENGTH) {
434 tabname = tabname.substring(0, 9) + " ... " + tabname.substring(len - 9, len);
435 }
436
437 return tabname;
438 }
439
440 /**
441 * What is the max length for a tab title
442 */
443 private static final int TITLE_LENGTH = 25;
444
445 /**
446 * How many verses on a tab.
447 */
448 private static int pageSize = 200; // There are 176 in Ps 119.
449
450 /**
451 * A list of all the URIEventListeners
452 */
453 private List<URIEventListener> uriEventListeners;
454
455 /**
456 * A list of all the keyEventListeners
457 */
458 private List<KeyChangeListener> keyEventListeners;
459
460 /**
461 * The passage that we are displaying (in one or more tabs)
462 */
463 private Passage key;
464
465 /**
466 * The verses that we have not created tabs for yet
467 */
468 private Passage waiting;
469
470 /**
471 * The version used for display
472 */
473 private Book[] books;
474
475 /**
476 * Are we using tabs?
477 */
478 private boolean tabs;
479
480 /**
481 * If we are using tabs, this is the main view
482 */
483 private JTabbedPane tabMain;
484
485 /**
486 * If we are not using tabs, this is the main view
487 */
488 private BookDataDisplay bookDataDisplay;
489
490 /**
491 * An map of components to their views
492 */
493 private Map<JScrollPane, BookDataDisplay> views;
494
495 /**
496 * A list of all the InnerDisplayPanes so we can control listeners
497 */
498 private List<BookDataDisplay> displays;
499
500 /**
501 * Pointer to whichever of the above is currently in use
502 */
503 private Component center;
504
505 /**
506 * Blank thing for the "More..." button
507 */
508 private JPanel pnlMore;
509
510 /**
511 * The top level component
512 */
513 private JPanel pnlMain;
514
515 /**
516 * The top level component
517 */
518 private JScrollPane scrMain;
519 }
520