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