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: Desktop.java 2230 2012-02-08 00:00:10Z dmsmith $
21   */
22  package org.crosswire.bibledesktop.desktop;
23  
24  import java.awt.BorderLayout;
25  import java.awt.Component;
26  import java.awt.Container;
27  import java.awt.event.ActionEvent;
28  import java.awt.event.WindowAdapter;
29  import java.awt.event.WindowEvent;
30  import java.beans.PropertyChangeEvent;
31  import java.beans.PropertyChangeListener;
32  import java.beans.PropertyChangeSupport;
33  import java.io.IOException;
34  import java.io.ObjectInputStream;
35  import java.net.MalformedURLException;
36  import java.net.URI;
37  import java.util.List;
38  import java.util.Locale;
39  import java.util.Map;
40  import java.util.ResourceBundle;
41  import java.util.Set;
42  
43  import javax.swing.BorderFactory;
44  import javax.swing.ButtonGroup;
45  import javax.swing.ImageIcon;
46  import javax.swing.JCheckBoxMenuItem;
47  import javax.swing.JComponent;
48  import javax.swing.JFrame;
49  import javax.swing.JMenu;
50  import javax.swing.JMenuBar;
51  import javax.swing.JOptionPane;
52  import javax.swing.JPanel;
53  import javax.swing.JPopupMenu;
54  import javax.swing.JRadioButtonMenuItem;
55  import javax.swing.JSplitPane;
56  import javax.swing.SwingUtilities;
57  import javax.swing.WindowConstants;
58  import javax.swing.event.SwingPropertyChangeSupport;
59  
60  import org.crosswire.bibledesktop.BDMsg;
61  import org.crosswire.bibledesktop.book.BibleViewPane;
62  import org.crosswire.bibledesktop.book.DisplaySelectEvent;
63  import org.crosswire.bibledesktop.book.DisplaySelectListener;
64  import org.crosswire.bibledesktop.book.DisplaySelectPane;
65  import org.crosswire.bibledesktop.book.MultiBookPane;
66  import org.crosswire.bibledesktop.display.BookDataDisplay;
67  import org.crosswire.bibledesktop.display.URIEvent;
68  import org.crosswire.bibledesktop.display.URIEventListener;
69  import org.crosswire.bibledesktop.display.basic.SplitBookDataDisplay;
70  import org.crosswire.bibledesktop.util.ConfigurableSwingConverter;
71  import org.crosswire.common.config.ChoiceFactory;
72  import org.crosswire.common.config.Config;
73  import org.crosswire.common.history.History;
74  import org.crosswire.common.progress.JobManager;
75  import org.crosswire.common.progress.Progress;
76  import org.crosswire.common.swing.ActionFactory;
77  import org.crosswire.common.swing.CWAction;
78  import org.crosswire.common.swing.CWOptionPane;
79  import org.crosswire.common.swing.CatchingThreadGroup;
80  import org.crosswire.common.swing.ExceptionPane;
81  import org.crosswire.common.swing.FixedSplitPane;
82  import org.crosswire.common.swing.GuiUtil;
83  import org.crosswire.common.swing.LookAndFeelUtil;
84  import org.crosswire.common.swing.desktop.LayoutPersistence;
85  import org.crosswire.common.swing.desktop.LayoutType;
86  import org.crosswire.common.swing.desktop.TDIViewLayout;
87  import org.crosswire.common.swing.desktop.ToolBar;
88  import org.crosswire.common.swing.desktop.ViewGenerator;
89  import org.crosswire.common.swing.desktop.ViewManager;
90  import org.crosswire.common.swing.desktop.event.ViewEvent;
91  import org.crosswire.common.swing.desktop.event.ViewEventListener;
92  import org.crosswire.common.util.CWClassLoader;
93  import org.crosswire.common.util.CWProject;
94  import org.crosswire.common.util.FileUtil;
95  import org.crosswire.common.util.Logger;
96  import org.crosswire.common.util.OSType;
97  import org.crosswire.common.util.Reporter;
98  import org.crosswire.common.util.ResourceUtil;
99  import org.crosswire.common.util.Translations;
100 import org.crosswire.common.xml.Converter;
101 import org.crosswire.common.xml.XMLUtil;
102 import org.crosswire.jsword.book.Book;
103 import org.crosswire.jsword.book.BookFilters;
104 import org.crosswire.jsword.book.Books;
105 import org.crosswire.jsword.book.BooksEvent;
106 import org.crosswire.jsword.book.BooksListener;
107 import org.crosswire.jsword.book.Defaults;
108 import org.crosswire.jsword.passage.Key;
109 import org.crosswire.jsword.passage.NoSuchKeyException;
110 import org.crosswire.jsword.util.ConverterFactory;
111 import org.jdom.Document;
112 import org.jdom.JDOMException;
113 
114 /**
115  * The Desktop is the user's view of BibleDesktop.
116  * 
117  * @see gnu.gpl.License for license details.<br>
118  *      The copyright to this program is held by it's authors.
119  * @author Joe Walker [joe at eireneh dot com]
120  * @author Mark Goodwin [mark at thorubio dot org]
121  * @author DM Smith [dmsmith555 at yahoo dot com]
122  */
123 public class Desktop extends JFrame implements URIEventListener, ViewEventListener, DisplaySelectListener, ViewGenerator {
124     // This must be the first static in the program.
125     // To ensure this we place it at the top of the class!
126     // This will set it as a place to look for overrides for
127     // ResourceBundles, properties and other resources
128     private static final CWProject PROJECT = CWProject.instance();
129 
130     static {
131         CWProject.setHome("jsword.home", ".jsword", "JSword");
132     }
133 
134     /**
135      * Central start point.
136      * 
137      * @param args
138      *            The command line arguments
139      */
140     public static void main(String[] args) {
141         try {
142             ThreadGroup group = new CatchingThreadGroup("BibleDesktopUIGroup");
143             Thread t = new DesktopThread(group);
144             t.start();
145         } catch (Exception ex) {
146             // Something went wrong before we've managed to get on our feet.
147             // so we want the best possible shot at working out what failed.
148             ex.printStackTrace(System.err);
149             ExceptionPane.showExceptionDialog(null, ex);
150         }
151     }
152 
153     /**
154      * Construct a Desktop.
155      */
156     public Desktop() {
157         // Set the name that is used for Layout Persistence
158         setName("Desktop");
159 
160         // The first thing that has to be done is to set the locale.
161         Translations.instance().setLocale();
162 
163         URI predictURI = PROJECT.getWritableURI(SPLASH_PROPS, FileUtil.EXTENSION_PROPERTIES);
164         Progress startJob = JobManager.createJob("Startup");
165         // TRANSLATOR: Progress label shown on BibleDesktop startup.
166         startJob.beginJob(BDMsg.gettext("Startup"), predictURI);
167 
168         // Load the configuration. And create the lists of installed books.
169         // This has to be done before any GUI components are created
170         // This includes code that is invoked by it.
171         // This has to be done after setting the locale.
172         generateConfig();
173 
174         // Make this be the root frame of OptionDialogs
175         JOptionPane.setRootFrame(this);
176 
177         // Grab errors
178         Reporter.grabAWTExecptions(true);
179 
180         // Create the Desktop Actions
181         desktopActions = new DesktopActions(this);
182         actions = desktopActions.getActions();
183 
184         // TRANSLATOR: Progress label shown while BibleDesktop
185         // creates the GUI components
186         startJob.setSectionName(BDMsg.gettext("Generating Components"));
187         buildActionMap();
188         createComponents();
189 
190         // If necessary, make changes to the UI to help with debugging
191         debug();
192 
193         // TRANSLATOR: Progress label shown while BibleDesktop
194         // creates the GUI layout with panes and panels,
195         // and creates a few other GUI things
196         startJob.setSectionName(BDMsg.gettext("General configuration"));
197         createLayout();
198 
199         // Listen for book changes so that the Options can be kept current
200         BooksListener cbl = new BooksListener() {
201             public void bookAdded(BooksEvent ev) {
202                 generateConfig();
203             }
204 
205             public void bookRemoved(BooksEvent ev) {
206                 generateConfig();
207             }
208         };
209         Books.installed().addBooksListener(cbl);
210 
211         // Set the left-to-right or right-to-left orientation for this and all
212         // sub-components
213         GuiUtil.applyDefaultOrientation(this);
214 
215         startJob.done();
216     }
217 
218     /**
219      * Sometimes we need to make some changes to debug the GUI.
220      */
221     private void debug() {
222         // this.getContentPane().addContainerListener(new
223         // DebugContainerListener());
224 
225         // javax.swing.RepaintManager.currentManager(this).setDoubleBufferingEnabled(false);
226         // ((javax.swing.JComponent) getContentPane()).setDebugGraphicsOptions(javax.swing.DebugGraphics.LOG_OPTION);
227     }
228 
229     /**
230      * Call all the constructors
231      */
232     private void createComponents() {
233         barStatus = new StatusBar();
234         reference = new MultiBookPane();
235         sptBooks = new FixedSplitPane(false);
236 
237         changeSupport = new SwingPropertyChangeSupport(this);
238         views = new ViewManager(this);
239         views.setActionFactory(getViewActions(views));
240         views.addViewEventListener(this);
241         history = new History();
242     }
243 
244     private ActionFactory getViewActions(ViewManager viewMgr) {
245         ActionFactory viewActions = new ActionFactory(viewMgr);
246 
247         // TRANSLATOR: This is the label for the view option to show multiple tabs
248         CWAction cwAction = viewActions.addAction(ViewManager.TAB_MODE, BDMsg.gettext("Tabbed Mode"));
249         // TRANSLATOR: This is the tooltip for the view option to show multiple tabs
250         cwAction.setTooltip(BDMsg.gettext("View passages using tabs"));
251 
252         // TRANSLATOR: This is the label for the view option to show multiple windows
253         cwAction = viewActions.addAction(ViewManager.WINDOW_MODE, BDMsg.gettext("Sub-Window Mode"));
254         // TRANSLATOR: This is the tooltip for the view option to show multiple windows
255         cwAction.setTooltip(BDMsg.gettext("View passages using sub-windows"));
256 
257         // TRANSLATOR: This is the label for the menu and/or button to open a new Bible view
258         cwAction = viewActions.addAction(ViewManager.NEW_TAB, BDMsg.gettext("New Bible View"));
259         // TRANSLATOR: This is the tooltip for the view option to open a new Bible view
260         cwAction.setTooltip(BDMsg.gettext("Open a new Bible View"));
261         cwAction.setSmallIcon("toolbarButtonGraphics/general/New16.gif");
262         cwAction.setLargeIcon("toolbarButtonGraphics/general/New24.gif");
263         cwAction.setAccelerator("N,ctrl");
264 
265         // TRANSLATOR: This is the label for the menu and/or button to close the current Bible view.
266         cwAction = viewActions.addAction(ViewManager.CLOSE_VIEW, BDMsg.gettext("Close the Current View"));
267         cwAction.setAccelerator("0x73,ctrl");
268 
269         // TRANSLATOR: This is the label for the menu and/or button to clear the contents of the current Bible view
270         cwAction = viewActions.addAction(ViewManager.CLEAR_VIEW, BDMsg.gettext("Clear the Current View"));
271         cwAction.setTooltip(BDMsg.gettext("Clear the current view's passages"));
272 
273         // TRANSLATOR: This is the label for the menu and/or button to close all Bible views.
274         cwAction = viewActions.addAction(ViewManager.CLOSE_ALL_VIEWS, BDMsg.gettext("Close All Views"));
275         cwAction.setTooltip(BDMsg.gettext("Close all passages"));
276 
277         // TRANSLATOR: This is the label for the menu and/or button to close Bible views other than the current one.
278         cwAction = viewActions.addAction(ViewManager.CLOSE_OTHER_VIEWS, BDMsg.gettext("Close Other Views"));
279         // TRANSLATOR: This is the tooltip for the menu and/or button to close Bible views other than the current one.
280         cwAction.setTooltip(BDMsg.gettext("Close all the other passages."));
281         return viewActions;
282     }
283 
284     /**
285      * Initialize the GUI, and display it.
286      */
287     private void createLayout() {
288         addWindowListener(new WindowAdapter() {
289             /* (non-Javadoc)
290              * @see java.awt.event.WindowListener#windowClosed(java.awt.event.WindowEvent)
291              */
292             @Override
293             public void windowClosed(WindowEvent ev) {
294                 actions.findAction("Exit").actionPerformed(new ActionEvent(this, 0, EMPTY_STRING));
295             }
296         });
297 
298         setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
299 
300         TDIViewLayout tdi = (TDIViewLayout) LayoutType.TDI.getLayout();
301         tdi.addPopup(createPopupMenu());
302 
303         reference.addURIEventListener(this);
304 
305         sptBooks.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
306         sptBooks.setRightComponent(reference);
307         sptBooks.setLeftComponent(views.getDesktop());
308         sptBooks.setResizeWeight(0.8D);
309         sptBooks.setOpaque(true);
310         sptBooks.setBorder(null);
311 
312         // The toolbar needs to be in the outermost container, on the border
313         // And the only other item in that container can be CENTER
314         Container contentPane = getContentPane();
315         contentPane.setLayout(new BorderLayout());
316         ToolBar toolbar = createToolBar();
317         contentPane.add(toolbar, BorderLayout.NORTH);
318 
319         mainPanel = new JPanel(new BorderLayout());
320         mainPanel.setBorder(BorderFactory.createEmptyBorder(1, 5, 1, 5));
321         mainPanel.add(sptBooks, BorderLayout.CENTER);
322 
323         // Put everything else in its own panel
324         corePanel = new JPanel(new BorderLayout());
325         corePanel.add(mainPanel, BorderLayout.CENTER);
326         corePanel.add(barStatus, BorderLayout.SOUTH);
327         contentPane.add(corePanel, BorderLayout.CENTER);
328         setJMenuBar(createMenuBar(toolbar));
329 
330         setIconImage(ICON_APP.getImage());
331         setEnabled(true);
332         setTitle(BDMsg.getApplicationTitle());
333     }
334 
335     private JMenuBar createMenuBar(ToolBar toolbar) {
336         JMenuBar barMenu = new JMenuBar();
337         barMenu.add(createFileMenu());
338         barMenu.add(createEditMenu());
339         barMenu.add(createViewMenu(toolbar));
340         barMenu.add(createNavigateMenu());
341         barMenu.add(createToolsMenu());
342         barMenu.add(createHelpMenu());
343         return barMenu;
344     }
345 
346     private void buildActionMap() {
347         // File menu and it's items
348         // TRANSLATOR: This is the label of the top level "File" menu
349         CWAction cwAction = actions.addAction("File", BDMsg.gettext("File"));
350 
351         // TRANSLATOR: This is the label for the menu and/or button to open a save passage list.
352         cwAction = actions.addAction("Open", BDMsg.gettext("Open"));
353         // TRANSLATOR: This is the tooltip for the menu and/or button to open a save passage list.
354         cwAction.setTooltip(BDMsg.gettext("Open a saved passage."));
355         cwAction.setSmallIcon("toolbarButtonGraphics/general/Open16.gif");
356         cwAction.setLargeIcon("toolbarButtonGraphics/general/Open24.gif");
357         cwAction.setAccelerator("O,ctrl");
358 
359         // TRANSLATOR: This is the label for the menu and/or button to save a passage list for the current Bible view.
360         cwAction = actions.addAction("Save", BDMsg.gettext("Save"));
361         // TRANSLATOR: This is the tooltip for the menu and/or button to save a passage list for the current Bible view.
362         cwAction.setTooltip(BDMsg.gettext("Save the current passage."));
363         cwAction.setSmallIcon("toolbarButtonGraphics/general/Save16.gif");
364         cwAction.setLargeIcon("toolbarButtonGraphics/general/Save24.gif");
365         cwAction.setAccelerator("S,ctrl");
366 
367         // TRANSLATOR: This is the label for the menu and/or button to save a passage list under a different name.
368         cwAction = actions.addAction("SaveAs", BDMsg.gettext("Save As"));
369         // TRANSLATOR: This is the tooltip for the menu and/or button to save a passage list under a different name.
370         cwAction.setTooltip(BDMsg.gettext("Save the current passage under a different name."));
371         cwAction.setSmallIcon("toolbarButtonGraphics/general/SaveAs16.gif");
372         cwAction.setLargeIcon("toolbarButtonGraphics/general/SaveAs24.gif");
373         cwAction.setAccelerator("A,ctrl,shift");
374 
375         // TRANSLATOR: This is the label for the menu and/or button to save a passage list for each Bible view.
376         cwAction = actions.addAction("SaveAll", BDMsg.gettext("Save All"));
377         // TRANSLATOR: This is the tooltip for the menu and/or button to save a passage list for each Bible view.
378         cwAction.setTooltip(BDMsg.gettext("Save all passages."));
379         cwAction.setSmallIcon("toolbarButtonGraphics/general/SaveAll16.gif");
380         cwAction.setLargeIcon("toolbarButtonGraphics/general/SaveAll24.gif");
381         cwAction.setAccelerator("S,ctrl,shift");
382 
383         // TRANSLATOR: This is the label for the menu and/or button to exit Bible Desktop.
384         cwAction = actions.addAction("Exit", BDMsg.gettext("Exit"));
385         // TRANSLATOR: This is the tooltip for the menu and/or button to exit Bible Desktop.
386         cwAction.setTooltip(BDMsg.gettext("Exit the Desktop application."));
387         cwAction.setAccelerator("0x73,alt");
388 
389         // Edit menu and it's items
390         // TRANSLATOR: This is the label of the top level "Edit" menu
391         cwAction = actions.addAction("Edit",  BDMsg.gettext("Edit"));
392 
393         // TRANSLATOR: This is the label for the standard Cut menu and/or button item
394 //      cwAction = actions.addAction("Cut", UserMsg.gettext("Cut"));
395 //      // TRANSLATOR: This is the tooltip for the standard Cut menu and/or button item
396 //      cwAction.setTooltip(UserMsg.gettext("Cut the selection."));
397 //      cwAction.setSmallIcon("toolbarButtonGraphics/general/Cut16.gif");
398 //      cwAction.setLargeIcon("toolbarButtonGraphics/general/Cut24.gif");
399 //      cwAction.setAccelerator("X,ctrl");
400 
401         // TRANSLATOR: This is the label for the standard Copy menu and/or button item
402         cwAction = actions.addAction("Copy", BDMsg.gettext("Copy"));
403         // TRANSLATOR: This is the tooltip for the standard Copy menu and/or button item
404         cwAction.setTooltip(BDMsg.gettext("Copy the selection."));
405         cwAction.setSmallIcon("toolbarButtonGraphics/general/Copy16.gif");
406         cwAction.setLargeIcon("toolbarButtonGraphics/general/Copy24.gif");
407         cwAction.setAccelerator("C,ctrl");
408 
409         // TRANSLATOR: This is the label for the standard Paste menu and/or button item
410 //      cwAction = actions.addAction("Paste", UserMsg.gettext("Paste"));
411 //      // TRANSLATOR: This is the tooltip for the standard Paste menu and/or button item
412 //      cwAction.setTooltip(UserMsg.gettext("Paste the selection."));
413 //      cwAction.setSmallIcon("toolbarButtonGraphics/general/Paste16.gif");
414 //      cwAction.setLargeIcon("toolbarButtonGraphics/general/Paste24.gif");
415 //      cwAction.setAccelerator("V,ctrl");
416 
417         // Navigate menu and it's items
418         // TRANSLATOR: This is the label of the top level "Navigate" menu
419         cwAction = actions.addAction("Navigate", BDMsg.gettext("Navigate"));
420 
421         // TRANSLATOR: This is the label for the menu and/or button to navigate to a prior Bible View content
422         cwAction = actions.addAction("Back", BDMsg.gettext("Back"));
423         // TRANSLATOR: This is the tooltip for the menu and/or button to navigate to a prior Bible View content
424         cwAction.setTooltip(BDMsg.gettext("Go back to previous passage."));
425         cwAction.setSmallIcon("toolbarButtonGraphics/navigation/Back16.gif");
426         cwAction.setLargeIcon("toolbarButtonGraphics/navigation/Back24.gif");
427 
428         // TRANSLATOR: This is the label for the menu and/or button to navigate to a next Bible View content
429         cwAction = actions.addAction("Forward", BDMsg.gettext("Forward"));
430         // TRANSLATOR: This is the tooltip for the menu and/or button to navigate to a next Bible View content
431         cwAction.setTooltip(BDMsg.gettext("Go forward to next passage."));
432         cwAction.setSmallIcon("toolbarButtonGraphics/navigation/Forward16.gif");
433         cwAction.setLargeIcon("toolbarButtonGraphics/navigation/Forward24.gif");
434 
435         // Verse sub-menu and it's items
436         // TRANSLATOR: This is the label of the "Verse Numbers" sub-menu
437         cwAction = actions.addAction("Verse", BDMsg.gettext("Verse Numbers"));
438         // TRANSLATOR: This is the tooltip of the "Verse Numbers" sub-menu
439         cwAction.setTooltip(BDMsg.gettext("Set the style for verse numbers"));
440         // TRANSLATOR: This is the label for the radio button to show verse numbers
441         cwAction = actions.addAction(XSLTProperty.VERSE_NUMBERS.getName(), BDMsg.gettext("Show Verse Numbers"));
442         // TRANSLATOR: This is the label for the radio button to show chapter and verse numbers
443         cwAction = actions.addAction(XSLTProperty.CV.getName(), BDMsg.gettext("Show Chapter and Verse Numbers"));
444         // TRANSLATOR: This is the label for the radio button to show book name with chapter and verse numbers
445         cwAction = actions.addAction(XSLTProperty.BCV.getName(), BDMsg.gettext("Show Book, Chapter and Verse Numbers"));
446         // TRANSLATOR: This is the label for the radio button to hide verse numbers 
447         cwAction = actions.addAction(XSLTProperty.NO_VERSE_NUMBERS.getName(), BDMsg.gettext("Hide Verse Numbers"));
448 
449         // View menu and it's items
450         // TRANSLATOR: This is the label of the top level "View" menu
451         cwAction = actions.addAction("View", BDMsg.gettext("View"));
452         // TRANSLATOR: This is the label for the checkbox to toggle between showing tiny and large verse numbers.
453         cwAction = actions.addAction(XSLTProperty.TINY_VERSE_NUMBERS.getName(), BDMsg.gettext("Use Small Verse Numbers"));
454 
455         // TRANSLATOR: This is the label for the checkbox to start each verse on a new line
456         cwAction = actions.addAction(XSLTProperty.START_VERSE_ON_NEWLINE.getName(), BDMsg.gettext("Start Verses on Separate Lines"));
457         // TRANSLATOR: This is the tooltip for the checkbox to start each verse on a new line
458         cwAction.setTooltip(BDMsg.gettext("Start each verses on a new line"));
459 
460         // TRANSLATOR: This is the label for the checkbox to show/hide differences between parallel Bibles
461         cwAction = actions.addAction("CompareToggle", BDMsg.gettext("Show Differences"));
462         // TRANSLATOR: This is the tooltip for the checkbox to show/hide differences between parallel Bibles
463         cwAction.setTooltip(BDMsg.gettext("Toggle display of differences between different Bibles"));
464 
465         // TRANSLATOR: This is the label for the checkbox to show/hide headings
466         cwAction = actions.addAction(XSLTProperty.HEADINGS.getName(), BDMsg.gettext("Show Headings"));
467         // TRANSLATOR: This is the label for the checkbox to show/hide notes
468         cwAction = actions.addAction(XSLTProperty.NOTES.getName(), BDMsg.gettext("Show Study Notes"));
469         // TRANSLATOR: This is the label for the checkbox to show/hide cross references
470         cwAction = actions.addAction(XSLTProperty.XREF.getName(), BDMsg.gettext("Use Cross Reference Linkings"));
471         // TRANSLATOR: This is the label for the checkbox to show/hide Strong's Numbers
472         cwAction = actions.addAction(XSLTProperty.STRONGS_NUMBERS.getName(), BDMsg.gettext("Show Strong's Numbers"));
473         // TRANSLATOR: This is the label for the checkbox to show/hide word morphology
474         cwAction = actions.addAction(XSLTProperty.MORPH.getName(), BDMsg.gettext("Show Word Morphology"));
475 
476         // TRANSLATOR: This is the label for the checkbox to show/hide tooltips
477         cwAction = actions.addAction("ToolTipToggle", BDMsg.gettext("Show Tool Tips"));
478         // TRANSLATOR: This is the tooltip for the checkbox to show/hide tooltips
479         // which, of course, only shows when tooltips are showing :)
480         cwAction.setTooltip(BDMsg.gettext("Toggle display of tool tips"));
481         cwAction.setAccelerator("T,ctrl");
482 
483         // TRANSLATOR: This is the label for the checkbox to show/hide the status area
484         cwAction = actions.addAction("StatusToggle", BDMsg.gettext("Show the Status Area"));
485         // TRANSLATOR: This is the tooltip for the checkbox to show/hide the status area
486         cwAction.setTooltip(BDMsg.gettext("Toggle display of the status area"));
487 
488         // TRANSLATOR: This is the label for the checkbox to show/hide the passage side bar
489         cwAction = actions.addAction("SidebarToggle", BDMsg.gettext("Show the Passage Sidebar"));
490         // TRANSLATOR: This is the tooltip for the checkbox to show/hide the passage side bar
491         cwAction.setTooltip(BDMsg.gettext("Toggle display of the Passage Sidebar"));
492         cwAction.setAccelerator("B,ctrl");
493 
494         // TRANSLATOR: This is the label for the menu and/or button to show the view source dialog
495         cwAction = actions.addAction("ViewSource", BDMsg.gettext("View Source"));
496         // TRANSLATOR: This is the tooltip for the menu and/or button to show the view source dialog
497         cwAction.setTooltip(BDMsg.gettext("View the HTML and OSIS source to the current window"));
498         cwAction.setAccelerator("U,ctrl");
499 
500         // Tools menu and it's items
501         // TRANSLATOR: This is the label of the top level "Tools" menu
502         cwAction = actions.addAction("Tools", BDMsg.gettext("Tools"));
503 
504         // TRANSLATOR: This is the label for the menu and/or button to show the book installer dialog
505         cwAction = actions.addAction("Books", BDMsg.gettext("Books"));
506         // TRANSLATOR: This is the tooltip for the menu and/or button to show the book installer dialog
507         cwAction.setTooltip(BDMsg.gettext("Display/Install Books"));
508         cwAction.setSmallIcon("toolbarButtonGraphics/general/Import16.gif");
509         cwAction.setLargeIcon("toolbarButtonGraphics/general/Import24.gif");
510 
511         // TRANSLATOR: This is the label for the menu and/or button to show the Options/Preferences dialog
512         // Note: on a Mac, it is called Preferences located on the "Bible Desktop" menu
513         // and the operating system provides the translation.
514         cwAction = actions.addAction("Options", BDMsg.gettext("Options"));
515         // TRANSLATOR: This is the tooltip for the menu and/or button to show the Options/Preferences dialog
516         cwAction.setTooltip(BDMsg.gettext("Alter system settings."));
517         cwAction.setSmallIcon("toolbarButtonGraphics/general/Properties16.gif");
518         cwAction.setLargeIcon("toolbarButtonGraphics/general/Properties24.gif");
519 
520         // Help menu and it's items
521         // TRANSLATOR: This is the label of the top level "Help" menu
522         cwAction = actions.addAction("Help", BDMsg.gettext("Help"));
523 
524         // TRANSLATOR: This is the label for the menu and/or button to show the Help dialog
525         cwAction = actions.addAction("Contents", BDMsg.gettext("Contents"));
526         // TRANSLATOR: This is the tooltip for the menu and/or button to show the Help dialog
527         cwAction.setTooltip(BDMsg.gettext("Help file contents."));
528         cwAction.setSmallIcon("toolbarButtonGraphics/general/Help16.gif");
529         cwAction.setLargeIcon("toolbarButtonGraphics/general/Help24.gif");
530         cwAction.setAccelerator("0x70");
531 
532         // TRANSLATOR: This is the label for the menu and/or button to show the About dialog
533         // Note: on a Mac, it is located on the "Bible Desktop" menu
534         // and the operating system provides the translation.
535         cwAction = actions.addAction("About", BDMsg.gettext("About"));
536         // TRANSLATOR: This is the tooltip for the menu and/or button to show the About dialog
537         cwAction.setTooltip(BDMsg.gettext("Information about Bible Desktop"));
538         cwAction.setSmallIcon("toolbarButtonGraphics/general/About16.gif");
539         cwAction.setLargeIcon("toolbarButtonGraphics/general/About24.gif");
540     }
541 
542     /**
543      * Create the file menu
544      * 
545      * @return the file menu
546      */
547     private JMenu createFileMenu() {
548         JMenu menu = new JMenu(actions.findAction("File"));
549         menu.setToolTipText(null);
550 
551         menu.add(views.getContextAction(ViewManager.NEW_TAB)).addMouseListener(barStatus);
552         menu.add(views.getContextAction(ViewManager.CLOSE_VIEW)).addMouseListener(barStatus);
553         menu.add(views.getContextAction(ViewManager.CLEAR_VIEW)).addMouseListener(barStatus);
554         menu.add(views.getContextAction(ViewManager.CLOSE_OTHER_VIEWS)).addMouseListener(barStatus);
555         menu.add(views.getContextAction(ViewManager.CLOSE_ALL_VIEWS)).addMouseListener(barStatus);
556 
557         menu.addSeparator();
558         menu.add(actions.findAction("Open")).addMouseListener(barStatus);
559         menu.add(actions.findAction("Save")).addMouseListener(barStatus);
560         menu.add(actions.findAction("SaveAs")).addMouseListener(barStatus);
561         menu.add(actions.findAction("SaveAll")).addMouseListener(barStatus);
562 
563         // Mac OSX provides "Quit" on the Program menu
564         if (!desktopActions.isOSXRegistered()) {
565             menu.addSeparator();
566             menu.add(actions.findAction("Exit")).addMouseListener(barStatus);
567         }
568 
569         return menu;
570     }
571 
572     private JMenu createEditMenu() {
573         JMenu menu = new JMenu(actions.findAction("Edit"));
574         menu.setToolTipText(null);
575 //      menuEdit.add(actions.findAction("Cut")).addMouseListener(barStatus);
576         menu.add(actions.findAction("Copy")).addMouseListener(barStatus);
577 //      menuEdit.add(actions.findAction("Paste")).addMouseListener(barStatus);
578 
579         return menu;
580     }
581 
582     private JMenu createNavigateMenu() {
583         JMenu menu = new JMenu(actions.findAction("Navigate"));
584         menu.setToolTipText(null);
585 
586         menu.add(actions.findAction("Back")).addMouseListener(barStatus);
587         menu.add(actions.findAction("Forward")).addMouseListener(barStatus);
588 
589         return menu;
590     }
591 
592     private JRadioButtonMenuItem createRadioButton(ButtonGroup group, XSLTProperty prop) {
593         JRadioButtonMenuItem radio = new JRadioButtonMenuItem(actions.findAction(prop.getName()));
594         group.add(radio);
595         radio.setSelected(prop.getDefaultState());
596         return radio;
597     }
598 
599     private JCheckBoxMenuItem createCheckbox(XSLTProperty prop) {
600         JCheckBoxMenuItem toggle = new JCheckBoxMenuItem(actions.findAction(prop.getName()));
601         toggle.setSelected(prop.getDefaultState());
602         return toggle;
603     }
604 
605     private JMenu createVerseMenu() {
606         JMenu menu = new JMenu(actions.findAction("Verse"));
607         ButtonGroup group = new ButtonGroup();
608         menu.add(createRadioButton(group, XSLTProperty.VERSE_NUMBERS)).addMouseListener(barStatus);
609         menu.add(createRadioButton(group, XSLTProperty.CV)).addMouseListener(barStatus);
610         menu.add(createRadioButton(group, XSLTProperty.BCV)).addMouseListener(barStatus);
611         menu.add(createRadioButton(group, XSLTProperty.NO_VERSE_NUMBERS)).addMouseListener(barStatus);
612         return menu;
613     }
614 
615     /**
616      * Create the view menu.
617      * 
618      * @return the view menu.
619      */
620     private JMenu createViewMenu(ToolBar toolbar) {
621         JMenu menu = new JMenu(actions.findAction("View"));
622         menu.add(createCheckbox(XSLTProperty.TINY_VERSE_NUMBERS)).addMouseListener(barStatus);
623         menu.add(createCheckbox(XSLTProperty.START_VERSE_ON_NEWLINE)).addMouseListener(barStatus);
624         menu.add(createVerseMenu());
625 
626         menu.addSeparator();
627 
628         menu.add(new JCheckBoxMenuItem(actions.findAction("CompareToggle"))).addMouseListener(barStatus);
629         menu.add(createCheckbox(XSLTProperty.HEADINGS)).addMouseListener(barStatus);
630         menu.add(createCheckbox(XSLTProperty.NOTES)).addMouseListener(barStatus);
631         menu.add(createCheckbox(XSLTProperty.XREF)).addMouseListener(barStatus);
632         menu.add(createCheckbox(XSLTProperty.STRONGS_NUMBERS)).addMouseListener(barStatus);
633         menu.add(createCheckbox(XSLTProperty.MORPH)).addMouseListener(barStatus);
634 
635         menu.addSeparator();
636 
637         menu.add(views.getTdiView()).addMouseListener(barStatus);
638         menu.add(views.getMdiView()).addMouseListener(barStatus);
639 
640         menu.addSeparator();
641 
642         menu.add(toolbar.getShowToggle()).addMouseListener(barStatus);
643         menu.add(toolbar.getTextToggle()).addMouseListener(barStatus);
644         menu.add(toolbar.getIconSizeToggle()).addMouseListener(barStatus);
645 
646         JCheckBoxMenuItem toggle = new JCheckBoxMenuItem(actions.findAction("ToolTipToggle"));
647         toggle.setSelected(true);
648         menu.add(toggle).addMouseListener(barStatus);
649 
650         toggle = new JCheckBoxMenuItem(actions.findAction("StatusToggle"));
651         toggle.setSelected(true);
652         menu.add(toggle).addMouseListener(barStatus);
653 
654         sidebarToggle = new JCheckBoxMenuItem(actions.findAction("SidebarToggle"));
655         sidebarToggle.setSelected(isSidebarShowing());
656         menu.add(sidebarToggle).addMouseListener(barStatus);
657 
658         if (viewSourceShowing) {
659             menu.addSeparator();
660 
661             menu.add(actions.findAction("ViewSource")).addMouseListener(barStatus);
662             menu.setToolTipText(null);
663         }
664 
665         return menu;
666     }
667 
668     private JMenu createToolsMenu() {
669         JMenu menu = new JMenu(actions.findAction("Tools"));
670         menu.setToolTipText(null);
671 
672         menu.add(actions.findAction("Books")).addMouseListener(barStatus);
673 
674         // Mac OSX provides "Preferences" on the Program menu
675         if (!desktopActions.isOSXRegistered()) {
676             menu.add(actions.findAction("Options")).addMouseListener(barStatus);
677         }
678 
679         return menu;
680     }
681 
682     private JMenu createHelpMenu() {
683         JMenu menu = new JMenu(actions.findAction("Help"));
684         menu.setToolTipText(null);
685         menu.add(actions.findAction("Contents")).addMouseListener(barStatus);
686 
687         // Mac provides the About action on the Program menu.
688         if (!desktopActions.isOSXRegistered()) {
689             menu.addSeparator();
690             menu.add(actions.findAction("About")).addMouseListener(barStatus);
691         }
692 
693         return menu;
694     }
695 
696     private JPopupMenu createPopupMenu() {
697         JPopupMenu menu = new JPopupMenu();
698         menu.add(views.getContextAction(ViewManager.NEW_TAB)).addMouseListener(barStatus);
699         menu.add(views.getContextAction(ViewManager.CLOSE_VIEW)).addMouseListener(barStatus);
700         menu.add(views.getContextAction(ViewManager.CLEAR_VIEW)).addMouseListener(barStatus);
701         menu.add(views.getContextAction(ViewManager.CLOSE_OTHER_VIEWS)).addMouseListener(barStatus);
702         menu.add(views.getContextAction(ViewManager.CLOSE_ALL_VIEWS)).addMouseListener(barStatus);
703 
704         menu.addSeparator();
705 
706         menu.add(actions.findAction("Open")).addMouseListener(barStatus);
707         menu.add(actions.findAction("Save")).addMouseListener(barStatus);
708         menu.add(actions.findAction("SaveAs")).addMouseListener(barStatus);
709         menu.add(actions.findAction("SaveAll")).addMouseListener(barStatus);
710 
711         return menu;
712     }
713 
714     private ToolBar createToolBar() {
715         ToolBar menu = new ToolBar(this);
716 
717         menu.add(views.getContextAction(ViewManager.NEW_TAB)).addMouseListener(barStatus);
718         menu.add(actions.findAction("Open")).addMouseListener(barStatus);
719         menu.add(actions.findAction("Save")).addMouseListener(barStatus);
720         menu.addSeparator();
721 //      toolbar.add(actions.findAction("Cut").addMouseListener(barStatus);
722         menu.add(actions.findAction("Copy")).addMouseListener(barStatus);
723 //      toolbar.add(actions.findAction("Paste")).addMouseListener(barStatus);
724         menu.addSeparator();
725         menu.add(actions.findAction("Back")).addMouseListener(barStatus);
726         menu.add(actions.findAction("Forward")).addMouseListener(barStatus);
727         menu.addSeparator();
728         menu.add(actions.findAction("Contents")).addMouseListener(barStatus);
729 
730         // Mac OSX provides "About" on the Program menu
731         if (!desktopActions.isOSXRegistered()) {
732             menu.add(actions.findAction("About")).addMouseListener(barStatus);
733         }
734 
735         menu.setRollover(true);
736 
737         return menu;
738     }
739 
740     /**
741      * Get the size of the content panel and make that the preferred size.
742      */
743     public void establishPreferredSize() {
744         Container contentPane = getContentPane();
745         if (contentPane instanceof JComponent) {
746             ((JComponent) contentPane).setPreferredSize(contentPane.getSize());
747             //log.warn("The size of the contentpane is: " + contentPane.getSize());
748         }
749     }
750 
751     /**
752      * @return Returns the views.
753      */
754     public ViewManager getViews() {
755         return views;
756     }
757 
758     public Component createView() {
759         boolean show = sidebarToggle == null ? isSidebarShowing() : sidebarToggle.isSelected();
760         BibleViewPane view = new BibleViewPane(show);
761         BookDataDisplay display = view.getPassagePane().getBookDataDisplay();
762         display.addURIEventListener(this);
763         display.addURIEventListener(barStatus);
764         display.setCompareBooks(compareShowing);
765         changeSupport.addPropertyChangeListener(BookDataDisplay.COMPARE_BOOKS, display);
766         DisplaySelectPane dsp = view.getSelectPane();
767         dsp.addCommandListener(this);
768         return view;
769     }
770 
771     /* (non-Javadoc)
772      * @see org.crosswire.common.swing.desktop.ViewEventListener#viewRemoved(org.crosswire.common.swing.desktop.ViewEvent)
773      */
774     public void viewRemoved(ViewEvent event) {
775         BibleViewPane view = (BibleViewPane) event.getSource();
776         BookDataDisplay display = view.getPassagePane().getBookDataDisplay();
777         display.removeURIEventListener(this);
778         display.removeURIEventListener(barStatus);
779         changeSupport.removePropertyChangeListener(BookDataDisplay.COMPARE_BOOKS, display);
780         DisplaySelectPane dsp = view.getSelectPane();
781         dsp.removeCommandListener(this);
782     }
783 
784     /* (non-Javadoc)
785      * @see org.crosswire.bibledesktop.book.DisplaySelectListener#bookChosen(org.crosswire.bibledesktop.book.DisplaySelectEvent)
786      */
787     public void bookChosen(DisplaySelectEvent ev) {
788         // Do nothing
789     }
790 
791     /* (non-Javadoc)
792      * @see org.crosswire.bibledesktop.book.DisplaySelectListener#passageSelected(org.crosswire.bibledesktop.book.DisplaySelectEvent)
793      */
794     public void passageSelected(DisplaySelectEvent ev) {
795         Key key = ev.getKey();
796         if (key != null && !key.isEmpty()) {
797             // add the string because keys are heavy weights
798             history.add(key.getName());
799         }
800     }
801 
802     public void selectHistory(int i) {
803         Object obj = history.go(i);
804         if (obj != null) {
805             activateURI(new URIEvent(this, Desktop.BIBLE_PROTOCOL, (String) obj));
806         }
807     }
808 
809     /* (non-Javadoc)
810      * @see org.crosswire.bibledesktop.display.URIEventListener#activateURI(org.crosswire.bibledesktop.display.URIEvent)
811      */
812     public void activateURI(URIEvent ev) {
813         barStatus.activateURI(ev);
814         String protocol = ev.getScheme();
815         String data = ev.getURI();
816 
817         try {
818             if (protocol.equals(BIBLE_PROTOCOL)) {
819                 // Does a view contain the passage already?
820                 BibleViewPane clearView = null;
821                 for (Component comp : views) {
822                     BibleViewPane view = (BibleViewPane) comp;
823                     if (view.isClear()) {
824                         clearView = view;
825                         continue;
826                     }
827                     Book book = view.getSelectPane().getFirstBook();
828                     if (book != null) {
829                         Key key = book.getKey(data);
830                         String dataPassage = key.getName();
831                         if (view.getTitle().equals(dataPassage)) {
832                             // We found the passage so just select it
833                             views.select(view);
834                             return;
835                         }
836                     }
837                 }
838 
839                 // If we didn't find a view and BibleViews are reused,
840                 // then pretend that the selected view is clear.
841                 if (isBibleViewReused()) {
842                     BibleViewPane view = (BibleViewPane) views.getSelected();
843                     if (view != null) {
844                         clearView = view;
845                     }
846                 }
847 
848                 // Do we have an empty view we can use?
849                 if (clearView != null) {
850                     Book book = clearView.getSelectPane().getFirstBook();
851                     if (book != null) {
852                         Key key = book.getKey(data);
853                         // force it to be a clear view, if it is not really.
854                         clearView.setKey(book.createEmptyKeyList());
855                         clearView.setKey(key);
856                         views.select(clearView);
857                     }
858                     return;
859                 }
860 
861                 // If we got this far we need to create a view
862                 // and load it up.
863                 BibleViewPane view = (BibleViewPane) views.addView();
864 
865                 Book book = view.getSelectPane().getFirstBook();
866                 if (book != null) {
867                     Key key = book.getKey(data);
868                     view.setKey(key);
869                 }
870             } else if (protocol.equals(COMMENTARY_PROTOCOL)) {
871                 Book book = Defaults.getCommentary();
872                 if (book != null && Books.installed().getBook(book.getName()) != null) {
873                     reference.selectBook(book);
874                     Book[] books = reference.getBooks();
875                     Key key = books[0].getKey(data);
876                     reference.setKey(key);
877                 }
878             } else if (protocol.equals(GREEK_DEF_PROTOCOL)) {
879                 jump(Defaults.getGreekDefinitions(), data);
880             } else if (protocol.equals(HEBREW_DEF_PROTOCOL)) {
881                 jump(Defaults.getHebrewDefinitions(), data);
882             } else if (protocol.equals(GREEK_MORPH_PROTOCOL)) {
883                 jump(Defaults.getGreekParse(), data);
884             } else if (protocol.equals(HEBREW_MORPH_PROTOCOL)) {
885                 jump(Defaults.getHebrewParse(), data);
886             } else if (protocol.equals(DICTIONARY_PROTOCOL)) {
887                 jump(Defaults.getDictionary(), data);
888             } else {
889                 // TRANSLATOR: Uncommon error condition: JSword has provided a link that is not handled.
890                 Reporter.informUser(this, new MalformedURLException(BDMsg.gettext("Unknown protocol {0}", protocol)));
891             }
892         } catch (NoSuchKeyException ex) {
893             Reporter.informUser(this, ex);
894         }
895     }
896 
897     /**
898      * Open the requested book and go to the requested key.
899      * 
900      * @param book
901      *            The book to use
902      * @param data
903      *            The key to find
904      */
905     private void jump(Book book, String data) {
906         // TODO(DM): If it is not installed, offer to install it.
907         if (book != null && Books.installed().getBook(book.getName()) != null) {
908             reference.selectBook(book);
909             reference.setWord(data);
910         }
911     }
912 
913     /* (non-Javadoc)
914      * @see org.crosswire.bibledesktop.display.URIEventListener#enterURI(org.crosswire.bibledesktop.display.URIEvent)
915      */
916     public void enterURI(URIEvent ev) {
917         // We don't care about enter events
918     }
919 
920     /* (non-Javadoc)
921      * @see org.crosswire.bibledesktop.display.URIEventListener#leaveURI(org.crosswire.bibledesktop.display.URIEvent)
922      */
923     public void leaveURI(URIEvent ev) {
924         // We don't care about leave events
925     }
926 
927     /**
928      * Show or hide the status bar.
929      * 
930      * @param show
931      *            boolean
932      */
933     public void showStatusBar(boolean show) {
934         if (show) {
935             corePanel.add(barStatus, BorderLayout.SOUTH);
936         } else {
937             corePanel.remove(barStatus);
938         }
939         validate();
940     }
941 
942     /**
943      * Are the close buttons enabled?
944      * 
945      * @param enabled
946      *            The enabled state
947      */
948     public void setCloseEnabled(boolean enabled) {
949         views.getContextAction(ViewManager.CLEAR_VIEW).setEnabled(enabled);
950         views.getContextAction(ViewManager.CLOSE_OTHER_VIEWS).setEnabled(enabled);
951     }
952 
953     /**
954      * Load the config.xml file
955      */
956     public final void generateConfig() {
957         // Get the list of books for each book type.
958         fillChoiceFactory();
959 
960         // TRANSLATOR: The window title of BibleDesktop's preference/option dialog.
961         config = new Config(BDMsg.gettext("Desktop Options"));
962         try {
963             Document xmlconfig = XMLUtil.getDocument(CONFIG_KEY);
964 
965             Locale defaultLocale = Locale.getDefault();
966             ResourceBundle configResources = ResourceBundle.getBundle(CONFIG_KEY, defaultLocale, CWClassLoader.instance(Desktop.class));
967 
968             config.add(xmlconfig, configResources);
969 
970             try {
971                 config.setProperties(ResourceUtil.getProperties(DESKTOP_KEY));
972             } catch (IOException ex) {
973                 ex.printStackTrace(System.err);
974                 ExceptionPane.showExceptionDialog(null, ex);
975             }
976 
977             config.localToApplication();
978             config.addPropertyChangeListener(new PropertyChangeListener() {
979                 /* (non-Javadoc)
980                  * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
981                  */
982                 public void propertyChange(PropertyChangeEvent evt) {
983                     // When the font changes update all the visible locations
984                     // using it.
985                     if ("BibleDisplay.ConfigurableFont".equals(evt.getPropertyName()))
986                     {
987                         BibleViewPane view = (BibleViewPane) getViews().getSelected();
988                         SplitBookDataDisplay da = view.getPassagePane();
989                         da.getBookDataDisplay().refresh();
990 
991                         reference.refresh();
992                     }
993 
994                     if ("BibleDisplay.MaxPickers".equals(evt.getPropertyName()))
995                     {
996                         BibleViewPane view = (BibleViewPane) getViews().getSelected();
997                         DisplaySelectPane selector = view.getSelectPane();
998                         selector.getBiblePicker().enableButtons();
999                     }
1000                }
1001            });
1002        } catch (IOException e) {
1003            // Something went wrong before we've managed to get on our feet.
1004            // so we want the best possible shot at working out what failed.
1005            e.printStackTrace(System.err);
1006            ExceptionPane.showExceptionDialog(null, e);
1007        } catch (JDOMException e) {
1008            // Ditto
1009            e.printStackTrace(System.err);
1010            ExceptionPane.showExceptionDialog(null, e);
1011        }
1012
1013    }
1014
1015    public void checkForBooks() {
1016        // News users probably wont have any Bibles installed so we give them a
1017        // hand getting to the installation dialog.
1018        List<Book> bibles = Books.installed().getBooks(BookFilters.getBibles());
1019        if (bibles.isEmpty()) {
1020            // TRANSLATOR: Title of dialog asking the user to install at least one Bible.
1021            String title = BDMsg.gettext("Install Bibles?");
1022            StringBuilder msg = new StringBuilder(200);
1023            // TRANSLATOR: Tell the user that they have no Bibles installed and 
1024            // give them the option to do it now.
1025            msg.append(BDMsg.gettext("You have no Bibles installed. Do you wish to install some now?"));
1026            msg.append("\n");
1027            // TRANSLATOR: Since they have no Bibles installed, give instructions on how to do it later.
1028            msg.append("(This is also available from Books in the Tools menu)");
1029            int reply = CWOptionPane.showConfirmDialog(this, msg, title, JOptionPane.OK_CANCEL_OPTION,
1030                    JOptionPane.QUESTION_MESSAGE);
1031            if (reply == JOptionPane.OK_OPTION) {
1032                desktopActions.doBooks();
1033            }
1034        }
1035    }
1036
1037    /**
1038     * @param show
1039     *            Whether to show the KeySidebar at start up.
1040     */
1041    public static void setSidebarShowing(boolean show) {
1042        sidebarShowing = show;
1043    }
1044
1045    /**
1046     * @return Whether to show the KeySidebar at start up.
1047     */
1048    public static boolean isSidebarShowing() {
1049        return sidebarShowing;
1050    }
1051
1052    /**
1053     * @param show
1054     *            Whether to show the view source in menu at start up.
1055     */
1056    public static void setViewSourceShowing(boolean show) {
1057        viewSourceShowing = show;
1058    }
1059
1060    /**
1061     * @return Whether to show the view source in menu at start up.
1062     */
1063    public static boolean isViewSourceShowing() {
1064        return viewSourceShowing;
1065    }
1066
1067    /**
1068     * @param show
1069     *            Whether to show differences between versions of the Bible
1070     */
1071    public void setCompareShowing(boolean show) {
1072        boolean old = compareShowing;
1073        compareShowing = show;
1074        changeSupport.firePropertyChange(BookDataDisplay.COMPARE_BOOKS, old, compareShowing);
1075    }
1076
1077    /**
1078     * @return Whether to show differences between versions of the Bible
1079     */
1080    public boolean isCompareShowing() {
1081        return compareShowing;
1082    }
1083
1084    /**
1085     * @param reuse
1086     *            Whether reuse the current BibleView.
1087     */
1088    public static void setBibleViewReused(boolean reuse) {
1089        reuseBibleView = reuse;
1090    }
1091
1092    /**
1093     * @return Whether links use the current BibleView.
1094     */
1095    public static boolean isBibleViewReused() {
1096        return reuseBibleView;
1097    }
1098
1099    /**
1100     * @param override
1101     *            The path to the CSS that should be used to override.
1102     */
1103    public static void setCSSOverride(String override) {
1104        XSLTProperty.CSS.setState(override);
1105    }
1106
1107    /**
1108     * @return the current override
1109     */
1110    public static String getCSSOverride() {
1111        return XSLTProperty.CSS.getStringState();
1112    }
1113
1114    /**
1115     * Setup the choices so that the options dialog knows what there is to
1116     * select from.
1117     */
1118    /*private*/final void fillChoiceFactory() {
1119        // Get the list of books for each book type.
1120        refreshBooks();
1121
1122        Translations.instance().register();
1123
1124        // And the array of allowed osis>html converters
1125        Map<String, Class<Converter>> converters = ConverterFactory.getKnownConverters();
1126        Set<String> keys = converters.keySet();
1127        String[] names = keys.toArray(new String[keys.size()]);
1128        ChoiceFactory.getDataMap().put(CONV_KEY, names);
1129
1130        // The choice of configurable XSL stylesheets
1131        ConfigurableSwingConverter cstyle = new ConfigurableSwingConverter();
1132        String[] cstyles = cstyle.getStyles();
1133        ChoiceFactory.getDataMap().put(CSWING_KEY, cstyles);
1134    }
1135
1136    /**
1137     * Setup the book choices
1138     */
1139    protected final void refreshBooks() {
1140        // Instantiating Defaults finds all of the installed books.
1141        // Calling refreshBooks() gets the list of books for each book type.
1142        Defaults.refreshBooks();
1143
1144        // Has the number of reference books changed?
1145        boolean hasDictionaries = Defaults.getDictionary() != null;
1146        boolean hasCommentaries = Defaults.getCommentary() != null;
1147        boolean newRefBooks = hasDictionaries || hasCommentaries;
1148        if (newRefBooks != hasRefBooks) {
1149            // This method is called during setup
1150            if (reference != null) {
1151                if (!newRefBooks) {
1152                    sptBooks.setDividerLocation(8000);
1153                } else {
1154                    int norm = (int) (sptBooks.getMaximumDividerLocation() * 0.8);
1155                    sptBooks.setDividerLocation(norm);
1156                }
1157                // reference.setVisible(newRefBooks != 0);
1158                // sptBooks.setDividerLocation(0.8D);
1159            }
1160
1161            hasRefBooks = newRefBooks;
1162        }
1163    }
1164
1165    /**
1166     * @return The config set that this application uses to configure itself
1167     */
1168    public Config getConfig() {
1169        return config;
1170    }
1171
1172    /**
1173     * Serialization support.
1174     * 
1175     * @param is
1176     * @throws IOException
1177     * @throws ClassNotFoundException
1178     */
1179    private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
1180        config = null;
1181        history = null;
1182        desktopActions = new DesktopActions(this);
1183        actions = new ActionFactory(desktopActions);
1184        buildActionMap();
1185        views = new ViewManager(this);
1186        views.addViewEventListener(this);
1187        is.defaultReadObject();
1188    }
1189
1190    /**
1191     * Helper class to run the application in a thread group and capture errors.
1192     */
1193    private static final class DesktopThread extends Thread {
1194        DesktopThread(ThreadGroup group) {
1195            super(group, "BibleDesktopUIThread");
1196        }
1197
1198        /* (non-Javadoc)
1199         * @see java.lang.Runnable#run()
1200         */
1201        @Override
1202        public void run() {
1203            // The first thing that has to be done is to set the locale.
1204            Translations.instance().setLocale();
1205
1206            // These Mac properties give the application a Mac behavior
1207            if (OSType.MAC.equals(OSType.getOSType())) {
1208                System.setProperty("apple.laf.useScreenMenuBar", "true");
1209                System.setProperty("com.apple.mrj.application.apple.menu.about.name", BDMsg.getApplicationTitle());
1210                System.setProperty("com.apple.mrj.application.live-resize", "true");
1211            }
1212
1213            ExceptionPane.setHelpDeskListener(true);
1214            LookAndFeelUtil.initialize();
1215
1216            Splash splash = new Splash();
1217            Desktop desktop = new Desktop();
1218
1219            // Restore window size, position, and layout if previously opened,
1220            // otherwise use defaults.
1221            LayoutPersistence layoutPersistence = LayoutPersistence.instance();
1222            if (layoutPersistence.isLayoutPersisted(desktop)) {
1223                layoutPersistence.restoreLayout(desktop);
1224            } else {
1225                GuiUtil.defaultDesktopSize(desktop);
1226                GuiUtil.centerOnScreen(desktop);
1227            }
1228
1229            // Now bring up the app and offer to install books if the user has
1230            // none.
1231            SwingUtilities.invokeLater(new DesktopRunner(desktop, splash));
1232        }
1233    }
1234
1235    /**
1236     * Helper class to actually display the application at the right time.
1237     */
1238    private static class DesktopRunner implements Runnable {
1239        /**
1240         * @param aDesktop
1241         * @param aSplash
1242         */
1243        public DesktopRunner(Desktop aDesktop, Splash aSplash) {
1244            desktop = aDesktop;
1245            splash = aSplash;
1246        }
1247
1248        /* (non-Javadoc)
1249         * @see java.lang.Runnable#run()
1250         */
1251        public void run() {
1252            splash.close();
1253            desktop.setVisible(true);
1254            desktop.establishPreferredSize();
1255            desktop.pack();
1256            desktop.checkForBooks();
1257        }
1258
1259        private Desktop desktop;
1260        private Splash splash;
1261    }
1262
1263    private boolean hasRefBooks;
1264
1265    // Strings for the names of property files.
1266    private static final String SPLASH_PROPS = "splash";
1267
1268    // Strings for URL protocols/URI schemes
1269    public static final String BIBLE_PROTOCOL = "bible";
1270    public static final String DICTIONARY_PROTOCOL = "dict";
1271    public static final String GREEK_DEF_PROTOCOL = "gdef";
1272    public static final String HEBREW_DEF_PROTOCOL = "hdef";
1273    public static final String GREEK_MORPH_PROTOCOL = "gmorph";
1274    public static final String HEBREW_MORPH_PROTOCOL = "hmorph";
1275    public static final String COMMENTARY_PROTOCOL = "comment";
1276
1277    // Empty String
1278    private static final String EMPTY_STRING = "";
1279
1280    // Various other strings used as keys
1281    private static final String CONFIG_KEY = "config";
1282    private static final String DESKTOP_KEY = "desktop";
1283    private static final String CONV_KEY = "converters";
1284    private static final String CSWING_KEY = "cswing-styles";
1285
1286    /**
1287     * The configuration engine
1288     */
1289    private transient Config config;
1290
1291    /**
1292     * Whether to show the Key Sidebar at startup
1293     */
1294    private static boolean sidebarShowing;
1295
1296    /**
1297     * Whether to show the view source in the menu at startup
1298     */
1299    private static boolean viewSourceShowing;
1300
1301    /**
1302     * Whether to show differences between versions of the Bible
1303     */
1304    private boolean compareShowing;
1305
1306    /**
1307     * Whether to current BibleView should be used for links
1308     */
1309    private static boolean reuseBibleView = true;
1310
1311    /**
1312     * The log stream
1313     */
1314    protected static final Logger log = Logger.getLogger(Desktop.class);
1315
1316    /**
1317     * The factory for actions that this class works with
1318     */
1319    private transient volatile ActionFactory actions;
1320    /**
1321     * The DesktopActions is the holder for the actions, merely to keep the size of this file smaller.
1322     */
1323    protected transient DesktopActions desktopActions;
1324
1325    /**
1326     * The application icon
1327     */
1328    private static final ImageIcon ICON_APP = GuiUtil.getIcon("images/BibleDesktop16.png");
1329
1330    private transient ViewManager views;
1331    private JPanel corePanel;
1332    private JCheckBoxMenuItem sidebarToggle;
1333    private StatusBar barStatus;
1334    protected MultiBookPane reference;
1335    private JSplitPane sptBooks;
1336    private JPanel mainPanel;
1337    private transient History history;
1338    private PropertyChangeSupport changeSupport;
1339
1340    /**
1341     * Serialization ID
1342     */
1343    private static final long serialVersionUID = 3977014029116191800L;
1344}
1345