View Javadoc

1   package com.melloware.jukes.gui.view.dialogs;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Component;
5   import java.awt.Dimension;
6   import java.awt.Frame;
7   import java.awt.Image;
8   import java.awt.event.ActionEvent;
9   import java.util.Collection;
10  import java.util.Iterator;
11  
12  import javax.swing.AbstractAction;
13  import javax.swing.Action;
14  import javax.swing.DefaultListModel;
15  import javax.swing.JButton;
16  import javax.swing.JComponent;
17  import javax.swing.JList;
18  import javax.swing.JPanel;
19  import javax.swing.JScrollPane;
20  import javax.swing.JSplitPane;
21  import javax.swing.JTable;
22  import javax.swing.JTextField;
23  import javax.swing.ListSelectionModel;
24  import javax.swing.RowSorter;
25  import javax.swing.event.ListSelectionEvent;
26  import javax.swing.event.ListSelectionListener;
27  import javax.swing.table.TableModel;
28  import javax.swing.table.TableRowSorter;
29  
30  import org.apache.commons.lang.StringUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  import com.jgoodies.forms.builder.PanelBuilder;
35  import com.jgoodies.forms.factories.Borders;
36  import com.jgoodies.forms.factories.ButtonBarFactory;
37  import com.jgoodies.forms.layout.CellConstraints;
38  import com.jgoodies.forms.layout.FormLayout;
39  import com.jgoodies.uif.AbstractDialog;
40  import com.jgoodies.uif.panel.SimpleInternalFrame;
41  import com.jgoodies.uif.util.Resizer;
42  import com.jgoodies.uif.util.Worker;
43  import com.jgoodies.uifextras.panel.HeaderPanel;
44  import com.jgoodies.uifextras.util.UIFactory;
45  import com.melloware.jukes.exception.WebServiceException;
46  import com.melloware.jukes.file.FileUtil;
47  import com.melloware.jukes.gui.tool.Resources;
48  import com.melloware.jukes.gui.tool.Settings;
49  import com.melloware.jukes.gui.view.component.AlbumImage;
50  import com.melloware.jukes.gui.view.component.EnhancedTableHeader;
51  import com.melloware.jukes.gui.view.component.ExportableTable;
52  import com.melloware.jukes.util.GuiUtil;
53  import com.melloware.jukes.ws.AmazonItem;
54  import com.melloware.jukes.ws.AmazonSearch;
55  
56  /**
57   * Searches the web for an artist or album.  Uses Amazon.com web services to
58   * query the web using Apache Axis.  Allows user to select YEAR, ALBUM NAME, and
59   * a cover image to update the disc being edited.
60   * <p>
61   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
62   * @author Emil A. Lefkof III <info@melloware.com>
63   * @version 4.0
64   */
65  public final class WebSearchDialog
66      extends AbstractDialog {
67  
68      private static final Log LOG = LogFactory.getLog(WebSearchDialog.class);
69      private final AlbumImage webImagePreview;
70      private AmazonItem selection;
71      private Collection selectedTracks;
72      private DefaultListModel listModel;
73      private final EnhancedTableHeader header;
74      private final ExportableTable resultsTable;
75      private Image selectedImage;
76      private JButton buttonApply;
77      private JButton buttonCancel;
78      private JButton buttonSearch;
79      private JButton buttonStop;
80      private JComponent buttonBar;
81      private JComponent splitPane;
82      private JList list;
83      private final JTextField artistField;
84      private final JTextField discField;
85      private Object[] results;
86      private String selectedArtist;
87      private String selectedDisc;
88      private String selectedYear;
89      private RowSorter<TableModel> sorter;
90      private WebSearchTableModel tableModel;
91      private Worker worker;
92  
93      /**
94       * Constructs a default about dialog using the given owner.
95       *
96       * @param owner   the dialog's owner
97       */
98      public WebSearchDialog(Frame owner, Settings settings) {
99          super(owner);
100         LOG.debug("Web Search Dialog created.");
101         artistField = new JTextField();
102         discField = new JTextField();
103         resultsTable = new ExportableTable();
104         resultsTable.setEvenColor(settings.getRowColorEven());
105         resultsTable.setOddColor(settings.getRowColorOdd());
106         header = new EnhancedTableHeader(resultsTable.getColumnModel(), resultsTable);
107         resultsTable.setTableHeader(header);
108         webImagePreview = new AlbumImage();
109         webImagePreview.setPreferredSize(new Dimension(100, 100));
110         this.setPreferredSize(new Dimension(720, 576));
111     }
112 
113     /**
114      * Gets the selectedArtist.
115      * <p>
116      * @return Returns the selectedArtist.
117      */
118     public String getSelectedArtist() {
119         return FileUtil.capitalize(this.selectedArtist);
120     }
121 
122     /**
123      * Gets the selectedDisc.
124      * <p>
125      * @return Returns the selectedDisc.
126      */
127     public String getSelectedDisc() {
128         return FileUtil.capitalize(this.selectedDisc);
129     }
130 
131     /**
132      * Gets the selectedImage.
133      * <p>
134      * @return Returns the selectedImage.
135      */
136     public Image getSelectedImage() {
137         return this.selectedImage;
138     }
139 
140     /**
141      * Gets the selectedTracks.
142      * <p>
143      * @return Returns the selectedTracks.
144      */
145     public Collection getSelectedTracks() {
146         return this.selectedTracks;
147     }
148 
149     /**
150      * Gets the selectedYear.
151      * <p>
152      * @return Returns the selectedYear.
153      */
154     public String getSelectedYear() {
155         return this.selectedYear;
156     }
157 
158     /**
159      * Sets the selectedArtist.
160      * <p>
161      * @param aSelectedArtist The selectedArtist to set.
162      */
163     public void setSelectedArtist(String aSelectedArtist) {
164         this.selectedArtist = aSelectedArtist;
165         artistField.setText(this.selectedArtist);
166     }
167 
168     /**
169      * Sets the selectedDisc.
170      * <p>
171      * @param aSelectedDisc The selectedDisc to set.
172      */
173     public void setSelectedDisc(String aSelectedDisc) {
174         this.selectedDisc = StringUtils.substringBeforeLast(aSelectedDisc, " - ");
175         discField.setText(this.selectedDisc);
176     }
177 
178     /**
179      * Sets the selectedImage.
180      * <p>
181      * @param aSelectedImage The selectedImage to set.
182      */
183     public void setSelectedImage(Image aSelectedImage) {
184         this.selectedImage = aSelectedImage;
185     }
186 
187     /**
188      * Sets the selectedTracks.
189      * <p>
190      * @param aSelectedTracks The selectedTracks to set.
191      */
192     public void setSelectedTracks(Collection aSelectedTracks) {
193         this.selectedTracks = aSelectedTracks;
194     }
195 
196     /**
197      * Sets the selectedYear.
198      * <p>
199      * @param aSelectedYear The selectedYear to set.
200      */
201     public void setSelectedYear(String aSelectedYear) {
202         this.selectedYear = aSelectedYear;
203     }
204 
205     /* (non-Javadoc)
206      * @see com.jgoodies.swing.AbstractDialog#doApply()
207      */
208     public void doApply() {
209         LOG.debug("Select pressed.");
210         AmazonItem item = selection;
211         setSelectedArtist(item.getArtist());
212         setSelectedDisc(item.getDisc());
213         setSelectedYear(item.getReleaseYear());
214         setSelectedImage(item.getBestImage());
215         setSelectedTracks(item.getTracks());
216         super.doClose();
217     }
218 
219     /* (non-Javadoc)
220      * @see com.jgoodies.swing.AbstractDialog#doCancel()
221      */
222     public void doCancel() {
223         LOG.debug("Cancel Pressed.");
224         super.doCancel();
225     }
226 
227     /**
228      * Runs the Amazon.com SOAP query in a thread.
229      */
230     public void doSearch() {
231         LOG.debug("Searching...");
232 
233         if ((StringUtils.isBlank(artistField.getText())) && (StringUtils.isBlank(discField.getText()))) {
234             LOG.warn("Please select either an artist or disc or both.");
235             return;
236         }
237         GuiUtil.setBusyCursor(this, true);
238         buttonSearch.setEnabled(false);
239         buttonCancel.setEnabled(false);
240         buttonApply.setEnabled(false);
241         buttonStop.setEnabled(true);
242 
243         /* Invoking start() on the SwingWorker causes a new Thread
244          * to be created that will call construct(), and then finished().  Note that finished() is called even if the
245          * worker is interrupted because we catch the InterruptedException in doWork().
246          */
247         worker = new Worker() {
248                 public Object construct() {
249                     return doWork();
250                 }
251 
252                 public void finished() {
253                     threadFinished(get());
254                 }
255             };
256         worker.start();
257 
258     }
259 
260     /**
261      * Stops the current SOAP query.
262      */
263     public void doStop() {
264         LOG.debug("Stopping...");
265         GuiUtil.setBusyCursor(this, false);
266         worker.interrupt();
267         buttonSearch.setEnabled(true);
268         buttonCancel.setEnabled(true);
269         buttonStop.setEnabled(false);
270     }
271 
272     /**
273      * Builds and answers the dialog's content.
274      *
275      * @return the dialog's content with tabbed pane and button bar
276      */
277     protected JComponent buildContent() {
278         JPanel content = new JPanel(new BorderLayout());
279         JButton[] buttons = new JButton[2];
280         JButton button = createApplyButton();
281         button.setText("Select");
282         button.setEnabled(false);
283         buttonApply = button;
284         buttonCancel = createCancelButton();
285         buttons[0] = buttonApply;
286         buttons[1] = buttonCancel;
287         buttonBar = ButtonBarFactory.buildRightAlignedBar(buttons);
288         splitPane = buildSplitPane();
289         content.add(splitPane, BorderLayout.CENTER);
290         content.add(buttonBar, BorderLayout.SOUTH);
291         return content;
292     }
293 
294     /**
295      * Builds and returns the dialog's header.
296      *
297      * @return the dialog's header component
298      */
299     protected JComponent buildHeader() {
300         return new HeaderPanel("Web Search ",
301                                "Search the web for an artist or disc using Amazon.com as the query engine. Amazon.com limits the search to 10 results.",
302                                Resources.WEB_SEARCH_ICON);
303     }
304 
305     /**
306      * Builds and returns the dialog's pane.
307      *
308      * @return the dialog's  pane component
309      */
310     protected JComponent buildMainPanel() {
311         FormLayout layout = new FormLayout("fill:pref:grow", "p, p, p, p, p, p, p, p, p, p, p");
312         PanelBuilder builder = new PanelBuilder(layout);
313         builder.setDefaultDialogBorder();
314         CellConstraints cc = new CellConstraints();
315         int row = 1;
316         row++;
317         builder.add(buildCriteria(), cc.xy(1, row++));
318         builder.add(buildResultsPanel(), cc.xy(1, 7));
319         return builder.getPanel();
320     }
321 
322     /**
323      * Resizes the given component to give it a quadratic aspect ratio.
324      *
325      * @param component   the component to be resized
326      */
327     protected void resizeHook(JComponent component) {
328         Resizer.ONE2ONE.resizeDialogContent(component);
329     }
330 
331     /**
332      * Builds the search criteria panel.
333      * <p>
334      * @return the panel used to specify criteria
335      */
336     private JComponent buildCriteria() {
337         FormLayout layout = new FormLayout("right:max(14dlu;pref), fill:pref:grow", "4px, p, 4px, p, 4px, p, 4px");
338 
339         layout.setRowGroups(new int[][] {
340                                 { 2, 4 }
341                             });
342         PanelBuilder builder = new PanelBuilder(layout);
343         CellConstraints cc = new CellConstraints();
344 
345         JButton[] buttons = new JButton[2];
346 
347         // Create an action with an icon
348         Action search = new AbstractAction("Search", Resources.THREAD_START_ICON) {
349             // This method is called when the button is pressed
350             public void actionPerformed(ActionEvent evt) {
351                 doSearch();
352             }
353         };
354         buttonSearch = new JButton(search);
355         // Create an action with an icon
356         Action stop = new AbstractAction("Stop", Resources.THREAD_STOP_ICON) {
357             // This method is called when the button is pressed
358             public void actionPerformed(ActionEvent evt) {
359                 doStop();
360             }
361         };
362         buttonStop = new JButton(stop);
363         buttonStop.setEnabled(false);
364 
365         buttons[0] = buttonSearch;
366         buttons[1] = buttonStop;
367         JPanel searchButtonBar = ButtonBarFactory.buildCenteredBar(buttons);
368 
369         builder.addLabel("Artist: ", cc.xy(1, 2));
370         builder.add(artistField, cc.xy(2, 2));
371         builder.addLabel("Disc: ", cc.xy(1, 4));
372         builder.add(discField, cc.xy(2, 4));
373         builder.add(searchButtonBar, cc.xyw(1, 6, 2));
374         return builder.getPanel();
375     }
376 
377     /**
378      * Builds the panel with the JTable results in it.
379      * <p>
380      * @return the panel used to display messages
381      */
382     private JComponent buildResultsPanel() {
383         final Component dialog = this;
384         // build the table and model
385         resultsTable.setShowGrid(false);
386         resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
387         resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
388         // Ask to be notified of selection changes.
389         ListSelectionModel rowSM = resultsTable.getSelectionModel();
390         rowSM.addListSelectionListener(new ListSelectionListener() {
391                 public void valueChanged(ListSelectionEvent e) {
392                     // Ignore extra messages.
393                     if (e.getValueIsAdjusting()) {
394                         return;
395                     }
396 
397                     ListSelectionModel lsm = (ListSelectionModel)e.getSource();
398                     if (lsm.isSelectionEmpty()) {
399                         selection = null;
400                         buttonApply.setEnabled(false);
401                         webImagePreview.setImage(null);
402                         listModel.removeAllElements();
403                     } else {
404                         GuiUtil.setBusyCursor(dialog, true);
405                         try {
406                             int selectedRow = resultsTable.getSelectedRow();
407                             selectedRow = sorter.convertRowIndexToModel(selectedRow);
408                             selection = (AmazonItem)results[selectedRow];
409                             buttonApply.setEnabled(true);
410                             webImagePreview.setImage(selection.getSmallestImage());
411                             listModel.removeAllElements();
412                             int count = 0;
413                             for (Iterator iter = selection.getTracks().iterator(); iter.hasNext();) {
414                                 String element = (String)iter.next();
415                                 count++;
416                                 listModel.addElement(count + ". " + element);
417                             }
418                         } catch (Exception ex) {
419                             LOG.error("RuntimeException", ex);
420                         } finally {
421                             GuiUtil.setBusyCursor(dialog, false);
422                         }
423                     }
424                 }
425             });
426 
427         // build the tracks JList and model
428         listModel = new DefaultListModel();
429         list = new JList(listModel);
430         list.setFocusable(false);
431         list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
432         list.setSelectedIndex(0);
433         list.setVisibleRowCount(7);
434         JScrollPane listScrollPane = UIFactory.createStrippedScrollPane(list);
435         listScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
436         listScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
437         JPanel previewPanel = new JPanel(new BorderLayout());
438         previewPanel.add(webImagePreview, BorderLayout.NORTH);
439         previewPanel.add(listScrollPane, BorderLayout.CENTER);
440         final SimpleInternalFrame previewer = new SimpleInternalFrame("Preview");
441         previewer.add(previewPanel);
442         previewer.setPreferredSize(new Dimension(150, 304));
443 
444         JComponent resultsPane = UIFactory.createTablePanel(resultsTable);
445         resultsPane.setPreferredSize(new Dimension(300, 300));
446 
447         // build the form
448         FormLayout layout = new FormLayout("fill:pref:grow, right:max(14dlu;pref)", "p");
449         layout.setRowGroups(new int[][] {
450                                 { 1 }
451                             });
452         PanelBuilder builder = new PanelBuilder(layout);
453         CellConstraints cc = new CellConstraints();
454         builder.add(resultsPane, cc.xy(1, 1));
455         builder.add(previewer, cc.xy(2, 1));
456         builder.getPanel().setPreferredSize(new Dimension(450, 300));
457         return builder.getPanel();
458     }
459 
460     /**
461      * Builds the <code>Search Criteria</code>, the <code>Results</code>
462      * and answers them wrapped by a stripped <code>JSplitPane</code>.
463      */
464     private JComponent buildSplitPane() {
465         splitPane = UIFactory.createStrippedSplitPane(JSplitPane.VERTICAL_SPLIT, buildCriteria(), buildResultsPanel(),
466                                                       0.25);
467         splitPane.setPreferredSize(new Dimension(630, 470));
468         splitPane.setBorder(Borders.DIALOG_BORDER);
469         return splitPane;
470     }
471 
472     /**
473      * This method represents the application code that we'd like to
474      * run on a separate thread.
475      */
476     private Object doWork() {
477         Object result = null;
478         try {
479             // do the search
480             try {
481                 result = AmazonSearch.findItemsByArtistDisc(artistField.getText(), discField.getText());
482             } catch (WebServiceException ex) {
483                 String message = "Error querying the web service. \n\n" + ex.getMessage();
484                 LOG.error(message);
485                 throw new InterruptedException(message);
486             }
487 
488             if (Thread.interrupted()) {
489                 LOG.debug("Thread interrupted.");
490                 throw new InterruptedException();
491             }
492 
493         } catch (InterruptedException e) {
494 
495             return result;    // SwingWorker.get() returns this
496         }
497         return result;    // or this
498     }
499 
500     /**
501      * When the thread is finished this method is called.
502      * <p>
503      * @param result the Object return from the doWork thread.
504      */
505     private void threadFinished(Object result) {
506         if (LOG.isDebugEnabled()) {
507             LOG.debug("Thread Finished");
508         }
509         buttonSearch.setEnabled(true);
510         buttonCancel.setEnabled(true);
511         buttonStop.setEnabled(false);
512         GuiUtil.setBusyCursor(this, false);
513 
514         if (result == null) {
515             results = null;
516             tableModel = new WebSearchTableModel(results);
517             tableModel.setData(null);
518             tableModel.fireTableDataChanged();
519         } else {
520             final Collection items = (Collection)result;
521             results = items.toArray();
522             tableModel = new WebSearchTableModel(results);
523             sorter = new TableRowSorter<TableModel>(tableModel);
524             resultsTable.setModel(tableModel);
525             resultsTable.setRowSorter(sorter);
526             header.autoSizeColumns();
527         }
528 
529         resultsTable.updateUI();
530         splitPane.updateUI();
531     }
532 
533 }