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: EditSitePane.java 2157 2011-04-09 16:22:00Z dmsmith $
21   */
22  package org.crosswire.bibledesktop.book.install;
23  
24  import java.awt.BorderLayout;
25  import java.awt.Component;
26  import java.awt.Dimension;
27  import java.awt.FlowLayout;
28  import java.awt.Frame;
29  import java.awt.GridBagConstraints;
30  import java.awt.GridBagLayout;
31  import java.awt.Insets;
32  import java.awt.event.ActionEvent;
33  import java.awt.event.ActionListener;
34  import java.awt.event.KeyEvent;
35  import java.io.IOException;
36  import java.io.ObjectInputStream;
37  
38  import javax.swing.BorderFactory;
39  import javax.swing.JButton;
40  import javax.swing.JComboBox;
41  import javax.swing.JComponent;
42  import javax.swing.JDialog;
43  import javax.swing.JLabel;
44  import javax.swing.JList;
45  import javax.swing.JOptionPane;
46  import javax.swing.JPanel;
47  import javax.swing.JScrollPane;
48  import javax.swing.JSeparator;
49  import javax.swing.JSplitPane;
50  import javax.swing.JTextField;
51  import javax.swing.KeyStroke;
52  import javax.swing.ListSelectionModel;
53  import javax.swing.WindowConstants;
54  import javax.swing.event.DocumentEvent;
55  import javax.swing.event.DocumentListener;
56  import javax.swing.event.ListSelectionEvent;
57  import javax.swing.event.ListSelectionListener;
58  
59  import org.crosswire.bibledesktop.BDMsg;
60  import org.crosswire.common.swing.ActionFactory;
61  import org.crosswire.common.swing.CWAction;
62  import org.crosswire.common.swing.CWLabel;
63  import org.crosswire.common.swing.CWOptionPane;
64  import org.crosswire.common.swing.CWScrollPane;
65  import org.crosswire.common.swing.FixedSplitPane;
66  import org.crosswire.common.swing.GuiUtil;
67  import org.crosswire.jsword.book.install.InstallManager;
68  import org.crosswire.jsword.book.install.Installer;
69  import org.crosswire.jsword.book.install.InstallerFactory;
70  
71  /**
72   * An editor for the list of available update sites.
73   * 
74   * @see gnu.gpl.License for license details.<br>
75   *      The copyright to this program is held by it's authors.
76   * @author Joe Walker [joe at eireneh dot com]
77   * @author DM Smith [dmsmith555 at yahoo dot com]
78   */
79  public class EditSitePane extends JPanel {
80      /**
81       * This is the default constructor
82       */
83      public EditSitePane(InstallManager imanager) {
84          this.imanager = imanager;
85          userInitiated = true;
86  
87          init();
88          setState(EditState.DISPLAY, null);
89          select();
90      }
91  
92      /**
93       * GUI init
94       */
95      private void init() {
96          actions = new ActionFactory(this);
97  
98          lstSite = new JList(new InstallManagerComboBoxModel(imanager));
99          JScrollPane scrSite = new CWScrollPane(lstSite);
100 
101         lstSite.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
102         lstSite.addListSelectionListener(new ListSelectionListener() {
103             public void valueChanged(ListSelectionEvent ev) {
104                 if (ev.getValueIsAdjusting()) {
105                     return;
106                 }
107 
108                 select();
109             }
110         });
111 
112         // TRANSLATOR: This is the label for the button that allows the user to add a new download site
113         CWAction action = actions.addAction("Add", BDMsg.gettext("Add"));
114         // TRANSLATOR: This is the tooltip for the button that allows the user to add a new download site
115         action.setTooltip(BDMsg.gettext("Add a new installation site."));
116         JButton btnAdd = new JButton(action);
117 
118         // TRANSLATOR: This is the label for the button that allows the user to edit an existing download site
119         action = actions.addAction("Edit", BDMsg.gettext("Edit"));
120         // TRANSLATOR: This is the tooltip for the button that allows the user to edit an existing download site
121         action.setTooltip(BDMsg.gettext("Edit the current installation site."));
122         JButton btnEdit = new JButton(action);
123 
124         // TRANSLATOR: This is the label for the button that allows the user to remove an existing download site
125         action = actions.addAction("Delete", BDMsg.gettext("Delete"));
126         // TRANSLATOR: This is the tooltip for the button that allows the user to remove an existing download site
127         action.setTooltip(BDMsg.gettext("Delete Site?"));
128         JButton btnDelete = new JButton(action);
129 
130         JPanel pnlBtn1 = new JPanel();
131         pnlBtn1.add(btnAdd, null);
132         pnlBtn1.add(btnEdit, null);
133         pnlBtn1.add(btnDelete, null);
134 
135         JPanel pnlSite = new JPanel();
136         pnlSite.setLayout(new BorderLayout());
137         pnlSite.add(scrSite, BorderLayout.CENTER);
138         pnlSite.add(pnlBtn1, BorderLayout.SOUTH);
139 
140         txtName = new JTextField();
141         txtName.setColumns(10);
142         txtName.getDocument().addDocumentListener(new DocumentListener() {
143             public void changedUpdate(DocumentEvent ev) {
144                 siteUpdate();
145             }
146 
147             public void insertUpdate(DocumentEvent ev) {
148                 siteUpdate();
149             }
150 
151             public void removeUpdate(DocumentEvent ev) {
152                 siteUpdate();
153             }
154         });
155 
156         // TRANSLATOR: This is the label for a Site Name text box
157         JLabel lblName = CWLabel.createJLabel(BDMsg.gettext("Site Name:"));
158         lblName.setLabelFor(txtName);
159 
160         cboType = new JComboBox(new InstallerFactoryComboBoxModel(imanager));
161         cboType.setEditable(false);
162         cboType.setSelectedIndex(0);
163         cboType.addActionListener(new ActionListener() {
164             public void actionPerformed(ActionEvent ev) {
165                 newType();
166             }
167         });
168 
169         // TRANSLATOR: This is the label for the dropdown giving the type of the site
170         // either FTP or HTTP
171         // This is currently unused, as we only do HTTP, but will be present soon.
172         JLabel lblType = CWLabel.createJLabel(BDMsg.gettext("Site Type"));
173         lblType.setLabelFor(cboType);
174 
175         lblMesg = new JLabel();
176         lblMesg.setText(" ");
177 
178         // TRANSLATOR: This is the label for a button that resets the details for a download site
179         // to what was last saved.
180         action = actions.addAction("Reset", BDMsg.gettext("Reset"));
181         // TRANSLATOR: This is the tooltip for a button that resets the details for a download site
182         // to what was last saved.
183         action.setTooltip(BDMsg.gettext("Reset the details."));
184         JButton btnReset = new JButton(action);
185 
186         // TRANSLATOR: This is the label for a button that saves the details for a download site
187         action = actions.addAction("Save", BDMsg.gettext("Save"));
188         // TRANSLATOR: This is the tooltip for a button that saves the details for a download site
189         action.setTooltip(BDMsg.gettext("Save the current changes."));
190         JButton btnSave = new JButton(action);
191 
192         JPanel pnlBtn2 = new JPanel();
193         pnlBtn2.add(btnSave, null);
194         pnlBtn2.add(btnReset, null);
195 
196         siteEditorPane = new JPanel();
197         siteEditorPane.setLayout(new GridBagLayout());
198         JPanel pnlMain = new JPanel();
199         pnlMain.setPreferredSize(new Dimension(300, 300));
200         pnlMain.setLayout(new GridBagLayout());
201         pnlMain.add(lblMesg, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(10, 10, 10, 10), 0, 0));
202         pnlMain.add(lblName, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(2, 10, 2, 2), 0, 0));
203         pnlMain.add(txtName, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 10), 0, 0));
204         // If there is only one type, then don't give the user a choice
205         if (imanager.getInstallerFactoryNames().size() > 1) {
206             pnlMain.add(lblType, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(2, 10, 2, 2), 0, 0));
207             pnlMain.add(cboType, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 10), 0, 0));
208         }
209         pnlMain.add(new JSeparator(), new GridBagConstraints(0, 3, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(10, 10, 10, 10), 0, 0));
210         pnlMain.add(siteEditorPane, new GridBagConstraints(0, 4, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
211         pnlMain.add(pnlBtn2, new GridBagConstraints(0, 5, 2, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
212 
213         JSplitPane sptMain = new FixedSplitPane();
214         sptMain.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
215         // Make resizing affect the right only
216         sptMain.setResizeWeight(0.0D);
217         sptMain.setLeftComponent(pnlSite);
218         sptMain.setRightComponent(pnlMain);
219 
220         this.setLayout(new BorderLayout());
221         this.add(sptMain, BorderLayout.CENTER);
222 
223         // TRANSLATOR: This is the label for a button that closes the dialog
224         btnClose = new JButton(actions.addAction("Close", BDMsg.gettext("Close")));
225 
226         pnlAction = new JPanel();
227         pnlAction.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
228         pnlAction.setLayout(new FlowLayout(FlowLayout.TRAILING));
229         pnlAction.add(btnClose, null);
230         GuiUtil.applyDefaultOrientation(this);
231     }
232 
233     /**
234      * Open us in a new modal dialog window
235      * 
236      * @param parent
237      *            The component to which to attach the new dialog
238      */
239     public void showInDialog(Component parent) {
240         Frame root = JOptionPane.getFrameForComponent(parent);
241         dlgMain = new JDialog(root);
242 
243         ActionListener closer = new ActionListener() {
244             public void actionPerformed(ActionEvent ev) {
245                 doClose();
246             }
247         };
248 
249         KeyStroke esc = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
250 
251         dlgMain.getContentPane().setLayout(new BorderLayout());
252         dlgMain.getContentPane().add(new JPanel(), BorderLayout.NORTH);
253         dlgMain.getContentPane().add(pnlAction, BorderLayout.SOUTH);
254         dlgMain.getContentPane().add(this, BorderLayout.CENTER);
255         dlgMain.getContentPane().add(new JPanel(), BorderLayout.LINE_END);
256         dlgMain.getContentPane().add(new JPanel(), BorderLayout.LINE_START);
257         dlgMain.getRootPane().setDefaultButton(btnClose);
258         dlgMain.getRootPane().registerKeyboardAction(closer, esc, JComponent.WHEN_IN_FOCUSED_WINDOW);
259         dlgMain.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
260         // TRANSLATOR: Title for the dialog allowing the editing of SWORD download sites.
261         dlgMain.setTitle(BDMsg.gettext("Edit Update Sites"));
262         dlgMain.setResizable(true);
263         dlgMain.setModal(true);
264 
265         GuiUtil.setSize(dlgMain, new Dimension(750, 400));
266         GuiUtil.centerOnScreen(dlgMain);
267         GuiUtil.applyDefaultOrientation(dlgMain);
268         dlgMain.setVisible(true);
269         dlgMain.toFront();
270     }
271 
272     /**
273      * Close the window, and save the install manager state
274      */
275     public void doClose() {
276         imanager.save();
277         dlgMain.dispose();
278     }
279 
280     /**
281      * The name field has been updated, so we need to check the entry is valid
282      */
283     public final void siteUpdate() {
284         if (txtName.isEditable()) {
285             String name = txtName.getText().trim();
286 
287             if (name.length() == 0) {
288                 // TRANSLATOR: Indicate to the user that they did not supply a download site name.
289                 setState(EditState.EDIT_ERROR, BDMsg.gettext("Missing site name"));
290                 return;
291             }
292 
293             if (imanager.getInstaller(name) != null) {
294                 // TRANSLATOR: Indicate that the user supplied a name that matched a download site that they already have.
295                 setState(EditState.EDIT_ERROR, BDMsg.gettext("Duplicate site name"));
296                 return;
297             }
298 
299             setState(EditState.EDIT_OK, "");
300         }
301     }
302 
303     /**
304      * The installer type combo box has been changed
305      */
306     /*private*/final void newType() {
307         if (userInitiated) {
308             String type = (String) cboType.getSelectedItem();
309             InstallerFactory ifactory = imanager.getInstallerFactory(type);
310             Installer installer = ifactory.createInstaller();
311 
312             setInstaller(installer);
313         }
314     }
315 
316     /**
317      * Someone has picked a new installer
318      */
319     protected final void select() {
320         String name = (String) lstSite.getSelectedValue();
321         if (name == null) {
322             actions.findAction("Edit").setEnabled(false);
323             clear();
324         } else {
325             actions.findAction("Edit").setEnabled(true);
326 
327             Installer installer = imanager.getInstaller(name);
328             display(name, installer);
329         }
330 
331         // Since setting the display undoes any work done to set the edit state
332         // of the bean panel we need to redo it here. Since we are always in
333         // display mode at this point, this is fairly easy.
334         if (siteEditor != null) {
335             siteEditor.setEditable(false);
336         }
337     }
338 
339     /**
340      * Add a new installer to the list
341      */
342     public void doAdd() {
343         newType();
344 
345         editName = null;
346         editInstaller = null;
347 
348         // We need to call setState() to enable the text boxes so that
349         // siteUpdate() works properly
350         setState(EditState.EDIT_OK, null);
351         siteUpdate();
352 
353         GuiUtil.refresh(this);
354     }
355 
356     /**
357      * Move the selected installer to the installer edit panel
358      */
359     public void doEdit() {
360         String name = (String) lstSite.getSelectedValue();
361         if (name == null) {
362             // TRANSLATOR: Dialog title letting the user know that they they have not selected a download site to edit.
363             String title = BDMsg.gettext("No Site");
364             // TRANSLATOR: Let the user know that they have not selected a download site to edit.
365             String msg = BDMsg.gettext("No selected site to edit");
366             CWOptionPane.showMessageDialog(this, msg, title, JOptionPane.INFORMATION_MESSAGE);
367             return;
368         }
369 
370         editName = name;
371         editInstaller = imanager.getInstaller(name);
372 
373         imanager.removeInstaller(name);
374 
375         setState(EditState.EDIT_OK, null);
376         siteUpdate();
377 
378         txtName.grabFocus();
379     }
380 
381     /**
382      * Delete the selected installer from the list (on the left hand side)
383      */
384     public void doDelete() {
385         String name = (String) lstSite.getSelectedValue();
386         if (name == null) {
387             return;
388         }
389         // TRANSLATOR: Dialog title asking the user to confirm the delete of a download site.
390         String title = BDMsg.gettext("Delete Site?");
391         // TRANSLATOR: Message asking the user to confirm the delete of a download site. {0} is a placeholder for the name of the download site.
392         String msg = BDMsg.gettext("Are you sure you want to delete {0}?", name);
393         if (CWOptionPane.showConfirmDialog(this, msg, title, JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
394             imanager.removeInstaller(name);
395         }
396 
397         clear();
398         setState(EditState.DISPLAY, null);
399     }
400 
401     /**
402      * End editing the current installer
403      */
404     public void doReset() {
405         if (editName != null) {
406             imanager.addInstaller(editName, editInstaller);
407         }
408 
409         clear();
410         editName = null;
411         editInstaller = null;
412 
413         setState(EditState.DISPLAY, "");
414         select();
415     }
416 
417     /**
418      * Save the current installer to the list of installers
419      */
420     public void doSave() {
421         String name = txtName.getText();
422         siteEditor.save();
423         Installer installer = siteEditor.getInstaller();
424         imanager.addInstaller(name, installer);
425 
426         clear();
427         editName = null;
428         editInstaller = null;
429 
430         setState(EditState.DISPLAY, "");
431         select();
432     }
433 
434     /**
435      * Set the various gui elements depending on the current edit mode
436      */
437     private void setState(EditState stateEditError, String message) {
438         switch (stateEditError) {
439         case DISPLAY:
440             actions.findAction("Add").setEnabled(true);
441             actions.findAction("Delete").setEnabled(true);
442             actions.findAction("Edit").setEnabled(true);
443             lstSite.setEnabled(true);
444 
445             actions.findAction("Reset").setEnabled(false);
446             actions.findAction("Save").setEnabled(false);
447 
448             actions.findAction("Close").setEnabled(true);
449 
450             txtName.setEditable(false);
451             cboType.setEnabled(false);
452 
453             if (siteEditor != null) {
454                 siteEditor.setEditable(false);
455             }
456 
457             break;
458 
459         case EDIT_OK:
460         case EDIT_ERROR:
461             actions.findAction("Add").setEnabled(false);
462             actions.findAction("Delete").setEnabled(false);
463             actions.findAction("Edit").setEnabled(false);
464             lstSite.setEnabled(false);
465 
466             actions.findAction("Reset").setEnabled(true);
467             actions.findAction("Save").setEnabled(stateEditError == EditState.EDIT_OK);
468 
469             actions.findAction("Close").setEnabled(false);
470 
471             txtName.setEditable(true);
472             cboType.setEnabled(true);
473 
474             if (siteEditor != null) {
475                 siteEditor.setEditable(true);
476             }
477 
478             break;
479 
480         default:
481             assert false : stateEditError;
482         }
483 
484         if (message == null || message.trim().length() == 0) {
485             lblMesg.setText(" ");
486         } else {
487             lblMesg.setText(message);
488         }
489     }
490 
491     /**
492      * Set the display in the RHS to the given installer
493      */
494     private void display(String name, Installer installer) {
495         txtName.setText(name);
496 
497         String type = imanager.getFactoryNameForInstaller(installer);
498         userInitiated = false;
499         cboType.setSelectedItem(type);
500         userInitiated = true;
501 
502         setInstaller(installer);
503     }
504 
505     /**
506      * Clear the display in the RHS of any installers
507      */
508     private void clear() {
509         txtName.setText("");
510         setInstaller(null);
511     }
512 
513     /**
514      * Convenience method to allow us to change the type of the current
515      * installer.
516      * 
517      * @param installer
518      *            The new installer to introspect
519      */
520     private void setInstaller(Installer installer) {
521         siteEditorPane.removeAll();
522         siteEditor = null;
523         if (installer != null) {
524             siteEditor = SiteEditorFactory.createSiteEditor(installer);
525             siteEditorPane.add((Component) siteEditor, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
526                     new Insets(0, 0, 0, 0), 0, 0));
527             GuiUtil.applyDefaultOrientation(siteEditorPane);
528         }
529 
530         GuiUtil.refresh(this);
531     }
532 
533     /**
534      * Serialization support.
535      * 
536      * @param is
537      * @throws IOException
538      * @throws ClassNotFoundException
539      */
540     private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
541         // Broken but we don't serialize views
542         imanager = null;
543         editInstaller = null;
544         actions = new ActionFactory(this);
545         is.defaultReadObject();
546     }
547 
548 
549     /**
550      * An EditState give the possible states that an editor can be in.
551      */
552     private enum EditState {
553         /**
554          * The state is viewing a site
555          */
556         DISPLAY,
557 
558         /**
559          * The state is editing a site (syntactically valid)
560          */
561         EDIT_OK,
562 
563         /**
564          * The state is editing a site (syntactically invalid)
565          */
566         EDIT_ERROR,
567     }
568 
569     /**
570      * The model that we are providing a view/controller for
571      */
572     private transient InstallManager imanager;
573 
574     /**
575      * If we are editing an installer, we need to know it's original name in
576      * case someone clicks cancel.
577      */
578     private String editName;
579 
580     /**
581      * If we are editing an installer, we need to know it's original value in
582      * case someone clicks cancel.
583      */
584     private transient Installer editInstaller;
585 
586     /**
587      * Edits to the type combo box mean different things depending on whether it
588      * was triggered by the user or the application.
589      */
590     private boolean userInitiated;
591 
592     /*
593      * The ActionFactory holding the actions used by this
594      * EditSite.
595      */
596     private transient ActionFactory actions;
597 
598     /*
599      * GUI Components for the list of sites
600      */
601     private JList lstSite;
602 
603     /*
604      * GUI Components for the site view/edit area
605      */
606     private JLabel lblMesg;
607     private JTextField txtName;
608     private JComboBox cboType;
609     private JPanel siteEditorPane;
610     private SiteEditor siteEditor;
611 
612     /*
613      * Components for the dialog box including the button bar at the bottom.
614      * These are separated in this way in case this component is reused in a
615      * larger context.
616      */
617     protected JDialog dlgMain;
618     private JButton btnClose;
619     private JPanel pnlAction;
620 
621     /**
622      * Serialization ID
623      */
624     private static final long serialVersionUID = 3256446910616057650L;
625 }
626