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