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