1
24 package org.crosswire.bibledesktop.book;
25
26 import java.awt.FlowLayout;
27 import java.awt.GridBagConstraints;
28 import java.awt.GridBagLayout;
29 import java.awt.Insets;
30 import java.awt.SystemColor;
31 import java.awt.Toolkit;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.ActionListener;
34 import java.awt.event.KeyAdapter;
35 import java.awt.event.KeyEvent;
36 import java.io.IOException;
37 import java.io.ObjectInputStream;
38
39 import javax.swing.JButton;
40 import javax.swing.JComboBox;
41 import javax.swing.JLabel;
42 import javax.swing.JOptionPane;
43 import javax.swing.JPanel;
44 import javax.swing.JTextField;
45 import javax.swing.event.EventListenerList;
46
47 import org.crosswire.bibledesktop.BDMsg;
48 import org.crosswire.bibledesktop.book.install.IndexResolver;
49 import org.crosswire.bibledesktop.passage.KeyChangeEvent;
50 import org.crosswire.bibledesktop.passage.KeyChangeListener;
51 import org.crosswire.common.swing.ActionFactory;
52 import org.crosswire.common.swing.CWAction;
53 import org.crosswire.common.swing.CWLabel;
54 import org.crosswire.common.swing.CWOptionPane;
55 import org.crosswire.common.swing.GuiUtil;
56 import org.crosswire.common.swing.QuickHelpDialog;
57 import org.crosswire.common.swing.desktop.event.TitleChangedEvent;
58 import org.crosswire.common.swing.desktop.event.TitleChangedListener;
59 import org.crosswire.common.util.Reporter;
60 import org.crosswire.jsword.book.Book;
61 import org.crosswire.jsword.book.BookComparators;
62 import org.crosswire.jsword.book.BookException;
63 import org.crosswire.jsword.book.BookFilters;
64 import org.crosswire.jsword.book.BookProvider;
65 import org.crosswire.jsword.index.IndexStatus;
66 import org.crosswire.jsword.index.IndexStatusEvent;
67 import org.crosswire.jsword.index.IndexStatusListener;
68 import org.crosswire.jsword.index.search.DefaultSearchModifier;
69 import org.crosswire.jsword.index.search.DefaultSearchRequest;
70 import org.crosswire.jsword.passage.Key;
71 import org.crosswire.jsword.passage.KeyUtil;
72 import org.crosswire.jsword.passage.NoSuchKeyException;
73 import org.crosswire.jsword.passage.PassageTally;
74 import org.crosswire.jsword.passage.RocketPassage;
75 import org.crosswire.jsword.passage.Verse;
76 import org.crosswire.jsword.passage.VerseRange;
77 import org.crosswire.jsword.versification.BibleBook;
78 import org.crosswire.jsword.versification.Versification;
79 import org.crosswire.jsword.versification.system.Versifications;
80
81
89 public class DisplaySelectPane extends JPanel implements KeyChangeListener, BookSelectListener, BookProvider {
90
93 public DisplaySelectPane() {
94 initialize();
95 }
96
97
100 private void initialize() {
101 listeners = new EventListenerList();
102
103 advanced = new AdvancedSearchPane();
104
105 title = BDMsg.gettext("Untitled {0}", Integer.valueOf(base++));
107
108 actions = new ActionFactory(this);
109
110 isl = new IndexStatusListener() {
111 public void statusChanged(IndexStatusEvent ev) {
112 enableComponents();
113 }
114 };
115
116 biblePicker = new ParallelBookPicker(BookFilters.getBibles(), BookComparators.getInitialComparator());
118 biblePicker.addBookListener(this);
119 selected = biblePicker.getBooks();
120 if (selected != null && selected.length > 0) {
121 selected[0].addIndexStatusListener(isl);
122 key = selected[0].createEmptyKeyList();
123 } else {
124 key = new RocketPassage(Versifications.instance().getDefaultVersification());
129 }
130
131 JComboBox cboBooks = new JComboBox();
132 JComboBox cboChaps = new JComboBox();
133 quickSet = new BibleComboBoxModelSet(cboBooks, cboChaps, null);
134 quickSet.addActionListener(new ActionListener() {
135 public void actionPerformed(ActionEvent ev) {
136 BibleComboBoxModelSet set = (BibleComboBoxModelSet) ev.getSource();
137 Verse start = set.getVerse();
138 BibleBook book = start.getBook();
139 int chapter = start.getChapter();
140 Versification v11n = KeyUtil.getPassage(key).getVersification();
141 VerseRange range = new VerseRange(v11n, start, new Verse(book, chapter, v11n.getLastVerse(book, chapter)));
142 txtSearch.setText("");
143 txtKey.setText(range.getName());
144 doGoPassage();
145 }
146 });
147
148 JPanel quickPicker = new JPanel();
149 quickPicker.setLayout(new FlowLayout());
150 quickPicker.add(cboBooks);
151 quickPicker.add(cboChaps);
152
153 JLabel lblBible = CWLabel.createJLabel(BDMsg.gettext("Bible:"));
155 lblBible.setLabelFor(biblePicker);
156
157 JLabel lblKey = CWLabel.createJLabel(BDMsg.gettext("Show Passage:"));
159
160 CWAction action = actions.addAction("PassageAction");
161 action.setTooltip(BDMsg.gettext("Enter a passage to display."));
163 txtKey = new JTextField();
164 txtKey.setAction(action);
165 txtKey.addKeyListener(new KeyAdapter() {
166
169 @Override
170 public void keyTyped(KeyEvent ev) {
171 if (ev.getKeyChar() == '\n' && ev.getModifiers() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) {
172 showSelectDialog();
173 }
174 }
175 });
176 action = actions.addAction("More", BDMsg.gettext("Select"));
179 action.setTooltip(BDMsg.gettext("Pick a passage to display"));
180 btnKey = new JButton(action);
181
182 action = actions.addAction("GoPassage", BDMsg.gettext("Go (passage)"));
186 action.setTooltip(BDMsg.gettext("Display the passage"));
187 btnKeyGo = new JButton(action);
188
189 action = actions.addAction("SearchAction"); action.setTooltip(BDMsg.gettext("Search for a passage."));
193 txtSearch = new JTextField();
194 txtSearch.setAction(action);
195
196 JLabel lblSearch = CWLabel.createJLabel(BDMsg.gettext("Search:"));
198 lblSearch.setLabelFor(txtSearch);
199
200 action = actions.addAction("GoSearch", BDMsg.gettext("Go (search)"));
204 action.setTooltip(BDMsg.gettext("Search for a passage."));
205 btnSearch = new JButton(action);
206
207 action = actions.addAction("HelpAction");
208 action.setTooltip(BDMsg.gettext("Quick Search Help"));
210 action.setSmallIcon("toolbarButtonGraphics/general/ContextualHelp16.gif");
211 JButton btnHelp = GuiUtil.flatten(new JButton(action));
212 String dialogTitle = BDMsg.gettext("Search Quick Help");
214
215 StringBuilder buf = new StringBuilder(200);
216 buf.append("<html><b>");
217 buf.append(BDMsg.gettext("Search Tips"));
219 buf.append("</b><br>");
220 buf.append(BDMsg.gettext("You can use || to join phrases, for example <code>balaam || balak</code> finds passages containing Balak OR Balaam"));
223 buf.append("<br>");
224 buf.append(BDMsg.gettext("Using && requires both words, e.g. <code>aaron && moses</code> finds passages containing both Aaron AND Moses"));
227 buf.append("<br>");
228 buf.append(BDMsg.gettext("Using a ! removes words from the result e.g. <code>lord ! jesus</code> is passages containing Lord BUT NOT Jesus"));
231 buf.append("<br>");
232 buf.append(BDMsg.gettext("Using ~2 widens the passage by 2 verses either side on any match. So <code>amminadab ~1 perez</code> finds verses containting Amminadab within 1 verse of mention of Perez."));
235 buf.append("<br>");
236 buf.append(BDMsg.gettext("Using +[Gen-Exo] at the beginning will restrict a search to that range of verses."));
239 dlgHelp = new QuickHelpDialog(GuiUtil.getFrame(this), dialogTitle, buf.toString());
240
241 action = actions.addAction("Advanced", BDMsg.gettext("Advanced"));
243 action.setTooltip(BDMsg.gettext("Advanced Search"));
245 btnAdvanced = new JButton(action);
246
247 action = actions.addAction("Index", BDMsg.gettext("Enable Search"));
249 action.setTooltip(BDMsg.gettext("Create a search index"));
251 btnIndex = new JButton(action);
252
253 this.setLayout(new GridBagLayout());
254 this.add(lblBible, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.VERTICAL, new Insets(0, 0, 0, 5), 0, 0));
255 this.add(biblePicker, new GridBagConstraints(2, 0, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
256 this.add(quickPicker, new GridBagConstraints(4, 0, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
257
258 this.add(lblKey, new GridBagConstraints(0, 1, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 0, 0, 5), 0, 0));
259 this.add(txtKey, new GridBagConstraints(2, 1, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(2, 0, 1, 2), 0, 0));
260 this.add(btnKeyGo, new GridBagConstraints(4, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
261 this.add(btnKey, new GridBagConstraints(5, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.HORIZONTAL, new Insets(2, 0, 2, 2), 0, 0));
262
263 this.add(btnHelp, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
264 this.add(lblSearch, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 0, 0, 5), 0, 0));
265 this.add(btnIndex, new GridBagConstraints(2, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(2, 0, 2, 2), 0, 0));
266 this.add(txtSearch, new GridBagConstraints(2, 2, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(2, 0, 3, 2), 0, 0));
267 this.add(btnSearch, new GridBagConstraints(4, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
268 this.add(btnAdvanced, new GridBagConstraints(5, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.HORIZONTAL, new Insets(2, 0, 2, 2), 0, 0));
269
270 enableComponents();
271 GuiUtil.applyDefaultOrientation(this);
272
273 }
274
275
279 public void doInitialTextDisplay() {
280 Verse start = quickSet.getVerse();
281 BibleBook book = start.getBook();
282 int chapter = start.getChapter();
283 Versification v11n = Versifications.instance().getDefaultVersification();
285 VerseRange range = new VerseRange(v11n, start, new Verse(book, chapter, v11n.getLastVerse(book, chapter)));
286 txtSearch.setText("");
287 txtKey.setText(range.getName());
288 doGoPassage();
289 }
290
291
294 public Book[] getBooks() {
295 return selected.clone();
296 }
297
298
301 public Book getFirstBook() {
302 return selected != null && selected.length > 0 ? selected[0] : null;
303 }
304
305
308 public void clear() {
309 setKey(selected == null || selected.length == 0 ? new RocketPassage(Versifications.instance().getDefaultVersification()) : selected[0].createEmptyKeyList());
311 setTitle(Mode.CLEAR);
312 }
313
314
317 public boolean isClear() {
318 return title.indexOf(BDMsg.gettext("Untitled")) != -1;
321 }
322
323
326 public void doMore() {
327 showSelectDialog();
328 }
329
330
333 public void doGoPassage() {
334 doPassageAction();
335 }
336
337
340 public void doGoSearch() {
341 doSearchAction();
342 }
343
344
347 public void doPassageAction() {
348 setKey(txtKey.getText());
349 if (!key.isEmpty()) {
350 txtSearch.setText("");
351 setTitle(Mode.PASSAGE);
352 }
353 }
354
355
358 public void doSearchAction() {
359 if (selected == null || selected.length == 0) {
360 noBookInstalled();
361 return;
362 }
363
364 try {
365 String param = txtSearch.getText();
366 if (param == null || param.length() == 0) {
367 return;
368 }
369
370 boolean rank = advanced.isRanked();
371
372 DefaultSearchModifier modifier = new DefaultSearchModifier();
373 modifier.setRanked(rank);
374
375 int rankCount = getNumRankedVerses();
377 if (rank && rankCount != 0) {
378 modifier.setMaxResults(rankCount);
379 }
380
381 Key results = selected[0].find(new DefaultSearchRequest(param, modifier));
382 int partial = results.getCardinality();
383 int total = partial;
384
385 if (results instanceof PassageTally) {
387 PassageTally tally = (PassageTally) results;
388 total = tally.getTotal();
389 tally.setOrdering(PassageTally.Order.TALLY);
390 }
391
392 if (total == 0) {
393 Reporter.informUser(this, BDMsg.gettext("Could not find verses with: {0}", param));
396 } else {
397 if (total == partial) {
398 Reporter.informUser(this, BDMsg.gettext("There are {1} verses with: {0}", param, Integer.valueOf(total)));
403 } else {
404 Reporter.informUser(this, BDMsg.gettext("Showing {1} of {2} verses with: {0}", param, Integer.toString(partial), Integer.toString(total)));
410 }
411 setTitle(Mode.SEARCH);
412 setKey(results);
413 }
414 } catch (BookException ex) {
415 Reporter.informUser(this, ex);
416 }
417 }
418
419
422 public void doAdvanced() {
423 String reply = advanced.showInDialog(this, BDMsg.gettext("Advanced Search"), true, txtSearch.getText());
425 if (reply != null) {
426 txtSearch.setText(reply);
427 doSearchAction();
428 }
429 }
430
431
436 public void doRank() {
437 }
439
440
443 public void doHelpAction() {
444 dlgHelp.setVisible(true);
445 }
446
447
450 public void doIndex() {
451 if (selected == null || selected.length == 0) {
452 noBookInstalled();
453 return;
454 }
455
456 IndexResolver.scheduleIndex(selected[0], this);
457 enableComponents();
458 }
459
460
463 private void updateDisplay() {
464 if (selected == null || selected.length == 0) {
465 noBookInstalled();
466 return;
467 }
468
469 fireCommandMade(new DisplaySelectEvent(this, key));
470 }
471
472
475 public String getTitle() {
476 return title;
477 }
478
479
482 public ParallelBookPicker getBiblePicker() {
483 return biblePicker;
484 }
485
486
490 public void setKey(String newKey) {
491 if (selected == null || selected.length == 0) {
492 return;
493 }
494
495 try {
496 setKey(selected[0].getKey(newKey));
497 } catch (NoSuchKeyException e) {
498 Reporter.informUser(this, e);
499 }
500 }
501
502
506 public void setKey(Key newKey) {
507 if (newKey == null || newKey.isEmpty()) {
508 if (!key.isEmpty()) {
509 key = selected[0].createEmptyKeyList();
510 txtKey.setText("");
511 txtSearch.setText("");
512
513 updateDisplay();
514 setTitle(Mode.CLEAR);
515 }
516 } else if (!newKey.equals(key)) {
517 key = newKey;
518 String text = key.getName();
519 txtKey.setText(text);
520 updateDisplay();
521 if (isClear()) {
522 setTitle(Mode.PASSAGE);
523 txtSearch.setText("");
524 }
525 }
526 }
527
528
534 public static int getNumRankedVerses() {
535 return numRankedVerses;
536 }
537
538
546 public static void setNumRankedVerses(int newNumRankedVerses) {
547 int count = newNumRankedVerses;
548 if (count < 0) {
549 count = 0;
550 } else if (count > maxNumRankedVerses) {
551 count = maxNumRankedVerses;
552 }
553 numRankedVerses = count;
554 }
555
556
559 public static int getMaxNumRankedVerses() {
560 return maxNumRankedVerses;
561 }
562
563
567 public static void setMaxNumRankedVerses(int newMaxNumRankedVerses) {
568 int count = newMaxNumRankedVerses;
569 if (count < numRankedVerses) {
570 count = numRankedVerses;
571 }
572 maxNumRankedVerses = count;
573 }
574
575 private void setTitle(Mode clear) {
576 mode = clear;
577 switch (mode) {
578 case CLEAR:
579 title = BDMsg.gettext("Untitled {0}", Integer.valueOf(base++));
581 break;
582 case PASSAGE:
583 title = key.getName();
584 break;
585 case SEARCH:
586 title = txtSearch.getText();
587 break;
588 default:
589 assert false;
590 }
591 if (title.length() == 0) {
592 setTitle(Mode.CLEAR);
593 } else {
594 fireTitleChanged(new TitleChangedEvent(this, title));
595 }
596 }
597
598
601 private void noBookInstalled() {
602 String noBible = BDMsg.gettext("No Bible is installed");
609 CWOptionPane.showMessageDialog(this, noBible, noBible, JOptionPane.WARNING_MESSAGE);
610 }
611
612
615 final void enableComponents() {
616 boolean readable = selected != null && selected.length > 0;
617 boolean searchable = readable && selected[0].getIndexStatus().equals(IndexStatus.DONE);
618 boolean indexable = readable && selected[0].getIndexStatus().equals(IndexStatus.UNDONE);
619
620 txtSearch.setEnabled(searchable);
621 txtSearch.setBackground(searchable ? SystemColor.text : SystemColor.control);
622 txtSearch.setVisible(searchable);
623 btnAdvanced.setEnabled(searchable);
624 btnSearch.setEnabled(searchable);
625 txtKey.setEnabled(readable);
626 txtKey.setBackground(readable ? SystemColor.text : SystemColor.control);
627 btnKey.setEnabled(readable);
628 btnKeyGo.setEnabled(readable);
629 btnIndex.setVisible(indexable);
630 btnIndex.setEnabled(indexable);
631 }
632
633
636 final void showSelectDialog() {
637 if (dlgSelect == null) {
638 dlgSelect = new PassageSelectionPane();
639 }
640
641 String passg = dlgSelect.showInDialog(this, BDMsg.gettext("Select Passage"), true, txtKey.getText());
643 if (passg != null) {
644 txtKey.setText(passg);
645 doPassageAction();
646 }
647 }
648
649
652 public void booksChosen(BookSelectEvent ev) {
653 Book[] books = ev.getBookProvider().getBooks();
654 assert books.length > 0;
655
656 Book newSelected = ev.getBookProvider().getFirstBook();
657
658 if (selected.length > 0 && selected[0] != newSelected) {
659 selected[0].removeIndexStatusListener(isl);
660 newSelected.addIndexStatusListener(isl);
661 }
662
663 selected = books;
664
665 enableComponents();
666
667 if (selected == null || selected.length == 0) {
668 noBookInstalled();
669 return;
670 }
671
672 fireVersionChanged(new DisplaySelectEvent(this, key));
673 }
674
675
678 public void keyChanged(KeyChangeEvent ev) {
679 setKey(ev.getKey());
680 }
681
682
685 public synchronized void addTitleChangedListener(TitleChangedListener li) {
686 listeners.add(TitleChangedListener.class, li);
687 }
688
689
692 public synchronized void removeTitleChangedListener(TitleChangedListener li) {
693 listeners.remove(TitleChangedListener.class, li);
694 }
695
696
702 protected void fireTitleChanged(TitleChangedEvent ev) {
703 Object[] contents = listeners.getListenerList();
705
706 for (int i = contents.length - 2; i >= 0; i -= 2) {
709 if (contents[i] == TitleChangedListener.class) {
710 ((TitleChangedListener) contents[i + 1]).titleChanged(ev);
711 }
712 }
713 }
714
715
718 public synchronized void addCommandListener(DisplaySelectListener li) {
719 listeners.add(DisplaySelectListener.class, li);
720 }
721
722
725 public synchronized void removeCommandListener(DisplaySelectListener li) {
726 listeners.remove(DisplaySelectListener.class, li);
727 }
728
729
732 protected void fireCommandMade(DisplaySelectEvent ev) {
733 Object[] contents = listeners.getListenerList();
735
736 for (int i = contents.length - 2; i >= 0; i -= 2) {
739 if (contents[i] == DisplaySelectListener.class) {
740 ((DisplaySelectListener) contents[i + 1]).passageSelected(ev);
741 }
742 }
743 }
744
745
748 protected void fireVersionChanged(DisplaySelectEvent ev) {
749 Object[] contents = listeners.getListenerList();
751
752 for (int i = contents.length - 2; i >= 0; i -= 2) {
755 if (contents[i] == DisplaySelectListener.class) {
756 ((DisplaySelectListener) contents[i + 1]).bookChosen(ev);
757 }
758 }
759 }
760
761
768 private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
769 selected = null;
771
772 listeners = new EventListenerList();
773
774 actions = new ActionFactory(this);
775
776 isl = new IndexStatusListener() {
777 public void statusChanged(IndexStatusEvent ev) {
778 enableComponents();
779 }
780 };
781 is.defaultReadObject();
782 }
783
784
787 private transient IndexStatusListener isl;
788
789 private static int base = 1;
790
791 private String title;
792
793 private QuickHelpDialog dlgHelp;
794
795 private transient ActionFactory actions;
796
797 private transient Book[] selected;
798
801 private BibleComboBoxModelSet quickSet;
802 private PassageSelectionPane dlgSelect;
803 private ParallelBookPicker biblePicker;
804 protected JTextField txtKey;
805 protected JTextField txtSearch;
806 private JButton btnAdvanced;
807 private JButton btnSearch;
808 private JButton btnKey;
809 private JButton btnKeyGo;
810 private AdvancedSearchPane advanced;
811 private JButton btnIndex;
812
813
816 private enum Mode {
817 CLEAR,
818 PASSAGE,
819 SEARCH,
820 }
821
824 private Mode mode;
825
826
829 private Key key;
830
831
834 private transient EventListenerList listeners;
835
836
839 private static int numRankedVerses = 20;
840
841
844 private static int maxNumRankedVerses = 200;
845
846
849 private static final long serialVersionUID = 3256446910616057650L;
850 }
851