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: KeySidebar.java 2223 2012-01-26 21:28:02Z dmsmith $
21   */
22  package org.crosswire.bibledesktop.passage;
23  
24  import java.awt.BorderLayout;
25  import java.awt.FlowLayout;
26  import java.io.IOException;
27  import java.io.ObjectInputStream;
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  import javax.swing.JButton;
32  import javax.swing.JList;
33  import javax.swing.JPanel;
34  import javax.swing.JScrollPane;
35  import javax.swing.event.ListSelectionEvent;
36  import javax.swing.event.ListSelectionListener;
37  
38  import org.crosswire.bibledesktop.BDMsg;
39  import org.crosswire.bibledesktop.book.DisplaySelectEvent;
40  import org.crosswire.bibledesktop.book.DisplaySelectListener;
41  import org.crosswire.common.swing.ActionFactory;
42  import org.crosswire.common.swing.CWAction;
43  import org.crosswire.common.swing.CWScrollPane;
44  import org.crosswire.common.swing.GuiUtil;
45  import org.crosswire.jsword.book.Book;
46  import org.crosswire.jsword.passage.Key;
47  import org.crosswire.jsword.passage.Passage;
48  import org.crosswire.jsword.passage.RestrictionType;
49  import org.crosswire.jsword.passage.VerseRange;
50  
51  /**
52   * A list view of a key range list.
53   * 
54   * @see gnu.gpl.License for license details.<br>
55   *      The copyright to this program is held by it's authors.
56   * @author DM Smith [dmsmith555 at yahoo dot com]
57   */
58  public class KeySidebar extends JPanel implements DisplaySelectListener, KeyChangeListener {
59      /**
60       * Initialize the SplitBookDataDisplay
61       */
62      public KeySidebar(Book[] books) {
63          this.books = books == null ? null : (Book[]) books.clone();
64          init();
65          setActive();
66      }
67  
68      /**
69       * Create the GUI
70       */
71      private void init() {
72          setLayout(new BorderLayout());
73  
74          model = new RangeListModel(RestrictionType.CHAPTER);
75  
76          list = new JList(model);
77          list.addListSelectionListener(new ListSelectionListener() {
78              public void valueChanged(ListSelectionEvent ev) {
79                  if (ev.getValueIsAdjusting()) {
80                      return;
81                  }
82  
83                  selection();
84              }
85          });
86  
87          JScrollPane scroll = new CWScrollPane(list);
88  
89          ActionFactory actions = new ActionFactory(this);
90  
91          actDelete = actions.addAction("DeleteSelected");
92          // TRANSLATOR: This is the tooltip for the delete selected icon button in the Passage Sidebar
93          actDelete.setTooltip(BDMsg.gettext("Remove the selected passages in the current passage list."));
94          actDelete.setSmallIcon("toolbarButtonGraphics/general/Remove16.gif");
95          actDelete.setLargeIcon("toolbarButtonGraphics/general/Remove24.gif");
96  
97          actBlur1 = actions.addAction("Blur1");
98          // TRANSLATOR: This is the tooltip for the blur by 1 icon button in the Passage Sidebar
99          actBlur1.setTooltip(BDMsg.gettext("Expand all or the selected passage by 1 verse."));
100         actBlur1.setSmallIcon("images/Blur1_16.gif");
101 
102         actBlur5 = actions.addAction("Blur5");
103         // TRANSLATOR: This is the tooltip for the blur by 5 icon button in the Passage Sidebar
104         actBlur5.setTooltip(BDMsg.gettext("Expand all or the selected passage by 5 verses."));
105         actBlur5.setSmallIcon("images/Blur5_16.gif");
106 
107         JButton delete = new JButton(actDelete);
108         // delete.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
109         delete.setText(null);
110         JButton blur1 = new JButton(actBlur1);
111         blur1.setText(null);
112         // blur1.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
113         JButton blur5 = new JButton(actBlur5);
114         blur5.setText(null);
115         // blur5.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
116 
117         JPanel mutate = new JPanel(new FlowLayout());
118         mutate.add(delete);
119         mutate.add(blur1);
120         mutate.add(blur5);
121 
122         add(mutate, BorderLayout.NORTH);
123         add(scroll, BorderLayout.CENTER);
124     }
125 
126     /**
127      * Blur (expand) the current passage action by one verse on each side. This
128      * bound by the boundaries of the Chapter.
129      */
130     public void doBlur1() {
131         doBlur(1);
132     }
133 
134     /**
135      * Blur (expand) the current key by five verses on each side. This bound by
136      * the boundaries of the Chapter.
137      */
138     public void doBlur5() {
139         doBlur(5);
140     }
141 
142     /**
143      * Blur (expand) the current key action by amount verses on each side. This
144      * bound by the default Blur Restriction.
145      * 
146      * @param amount
147      *            The amount of blurring
148      */
149     private void doBlur(int amount) {
150         // Remember what was selected
151         Object[] objs = list.getSelectedValues();
152 
153         // Make sure that key changes are not visible until blur is done.
154         Key copy = key.clone();
155 
156         // Either blur the entire unselected list or just the selected elements.
157         if (objs.length == 0) {
158             copy.blur(amount, RestrictionType.getDefaultBlurRestriction());
159         } else {
160             for (Object obj : objs) {
161                 Key keyCopy = ((VerseRange) obj).clone();
162                 keyCopy.blur(amount, RestrictionType.getDefaultBlurRestriction());
163                 copy.addAll(keyCopy);
164             }
165         }
166         fireKeyChanged(new KeyChangeEvent(this, copy));
167 
168         // Restore the selection
169         int total = model.getSize();
170         int count = objs.length;
171         for (int i = 0; i < total; i++) {
172             Key listedKey = (Key) model.getElementAt(i);
173 
174             // As keys are found, remove them
175             for (Object obj : objs) {
176                 Key selectedKey = (VerseRange) obj;
177                 if (listedKey.contains(selectedKey)) {
178                     list.addSelectionInterval(i, i);
179                     --count;
180                 }
181             }
182 
183             // If the list is empty then we are done.
184             if (count <= 0) {
185                 break;
186             }
187         }
188 
189         GuiUtil.refresh(this);
190     }
191 
192     /**
193      * Remove the selected verses out of this KeySidebar.
194      */
195     public void doDeleteSelected() {
196         RangeListModel rlm = (RangeListModel) list.getModel();
197 
198         Passage ref = rlm.getPassage();
199         Object[] selected = list.getSelectedValues();
200         for (int i = 0; i < selected.length; i++) {
201             VerseRange range = (VerseRange) selected[i];
202             ref.remove(range);
203         }
204 
205         list.setSelectedIndices(new int[0]);
206 
207         partial = null;
208         model.setPassage((Passage) key);
209         fireKeyChanged(new KeyChangeEvent(this, key));
210         setActive();
211     }
212 
213     public Key getKey() {
214         return key;
215     }
216 
217     private void setKey(Key newKey) {
218         if (partial != null && partial.equals(newKey)) {
219             return;
220         }
221 
222         if (key != null && key.equals(newKey)) {
223             return;
224         }
225 
226         // Have to have a copy of the key
227         // since we allow it to be blurred and
228         // that would cause the shared location
229         // to get the change w/o seeing it.
230         if (newKey == null) {
231             key = null;
232         } else {
233             if (key != newKey) {
234                 key = newKey.clone();
235             }
236         }
237         partial = null;
238         model.setPassage((Passage) key);
239         fireKeyChanged(new KeyChangeEvent(this, key));
240         setActive();
241     }
242 
243     /**
244      * Someone clicked on a value in the list
245      */
246     /*private*/final void selection() {
247         Object[] selected = list.getSelectedValues();
248 
249         if (selected.length > 0) {
250             partial = books[0].createEmptyKeyList();
251 
252             for (int i = 0; i < selected.length; i++) {
253                 partial.addAll((Key) selected[i]);
254             }
255 
256             fireKeyChanged(new KeyChangeEvent(this, partial));
257         } else {
258             // Nothing selected so use the whole passage
259             fireKeyChanged(new KeyChangeEvent(this, key));
260         }
261 
262         setActive();
263     }
264 
265     /**
266      * Make sure the correct buttons are made active
267      */
268     private void setActive() {
269         Object[] selected = list.getSelectedValues();
270 
271         // make sure the mutator buttons are correctly active
272         actDelete.setEnabled(selected.length != 0);
273         boolean blurable = model.getSize() != 0;
274         actBlur1.setEnabled(blurable);
275         actBlur5.setEnabled(blurable);
276     }
277 
278     /* (non-Javadoc)
279      * @see org.crosswire.bibledesktop.book.DisplaySelectListener#passageSelected(org.crosswire.bibledesktop.book.DisplaySelectEvent)
280      */
281     public void passageSelected(DisplaySelectEvent ev) {
282         setKey(ev.getKey());
283     }
284 
285     /* (non-Javadoc)
286      * @see org.crosswire.bibledesktop.book.DisplaySelectListener#bookChosen(org.crosswire.bibledesktop.book.DisplaySelectEvent)
287      */
288     public void bookChosen(DisplaySelectEvent ev) {
289         books = ev.getBookProvider().getBooks();
290     }
291 
292     /* (non-Javadoc)
293      * @see org.crosswire.bibledesktop.book.KeyChangeListener#keyChanged(org.crosswire.bibledesktop.book.KeyChangeEvent)
294      */
295     public void keyChanged(KeyChangeEvent ev) {
296         setKey(ev.getKey());
297     }
298 
299     /**
300      * Add a command listener
301      */
302     public synchronized void addKeyChangeListener(KeyChangeListener listener) {
303         List<KeyChangeListener> temp = new ArrayList<KeyChangeListener>(2);
304 
305         if (keyChangeListeners != null) {
306             temp.addAll(keyChangeListeners);
307         }
308 
309         if (!temp.contains(listener)) {
310             temp.add(listener);
311             keyChangeListeners = temp;
312         }
313     }
314 
315     /**
316      * Remove a command listener
317      */
318     public synchronized void removeKeyChangeListener(KeyChangeListener listener) {
319         if (keyChangeListeners != null && keyChangeListeners.contains(listener)) {
320             List<KeyChangeListener> temp = new ArrayList<KeyChangeListener>();
321             temp.addAll(keyChangeListeners);
322 
323             temp.remove(listener);
324             keyChangeListeners = temp;
325         }
326     }
327 
328     /**
329      * Inform the command keyChangeListeners
330      */
331     /*private*/final synchronized void fireKeyChanged(KeyChangeEvent ev) {
332         if (keyChangeListeners != null) {
333             for (KeyChangeListener li : keyChangeListeners) {
334                 li.keyChanged(ev);
335             }
336         }
337     }
338 
339     /**
340      * Serialization support.
341      * 
342      * @param is
343      * @throws IOException
344      * @throws ClassNotFoundException
345      */
346     private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
347         // Broken but we don't serialize views
348         books = null;
349         keyChangeListeners = null;
350         is.defaultReadObject();
351     }
352 
353     /**
354      * The whole key that we are viewing
355      */
356     private Key key;
357 
358     /**
359      * The key that is selected
360      */
361     private Key partial;
362 
363     /**
364      * The books who's keys we are looking at
365      */
366     private transient Book[] books;
367 
368     /**
369      * The listener for KeyChangeEvents
370      */
371     private transient List<KeyChangeListener> keyChangeListeners;
372 
373     /*
374      * GUI Components
375      */
376     private JList list;
377     private RangeListModel model;
378     private CWAction actDelete;
379     private CWAction actBlur1;
380     private CWAction actBlur5;
381 
382     /**
383      * Serialization ID
384      */
385     private static final long serialVersionUID = 3905241217179466036L;
386 }
387