View Javadoc

1   package com.melloware.jukes.gui.view.dialogs;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Dimension;
5   import java.awt.Frame;
6   import java.awt.event.ActionEvent;
7   import java.awt.event.KeyAdapter;
8   import java.awt.event.KeyEvent;
9   import java.text.MessageFormat;
10  import java.util.Collection;
11  
12  import javax.swing.AbstractAction;
13  import javax.swing.Action;
14  import javax.swing.JButton;
15  import javax.swing.JComboBox;
16  import javax.swing.JComponent;
17  import javax.swing.JPanel;
18  import javax.swing.JPopupMenu;
19  import javax.swing.JSplitPane;
20  import javax.swing.JTable;
21  import javax.swing.JTextField;
22  import javax.swing.ListSelectionModel;
23  import javax.swing.RowSorter;
24  import javax.swing.event.ListSelectionEvent;
25  import javax.swing.event.ListSelectionListener;
26  import javax.swing.table.TableModel;
27  import javax.swing.table.TableRowSorter;
28  
29  import org.apache.commons.lang.StringEscapeUtils;
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.action.ActionManager;
41  import com.jgoodies.uif.util.Resizer;
42  import com.jgoodies.uif.util.ResourceUtils;
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.db.HibernateDao;
47  import com.melloware.jukes.db.orm.Track;
48  import com.melloware.jukes.file.image.ImageFactory;
49  import com.melloware.jukes.file.tag.MusicTag;
50  import com.melloware.jukes.gui.tool.Actions;
51  import com.melloware.jukes.gui.tool.Resources;
52  import com.melloware.jukes.gui.tool.Settings;
53  import com.melloware.jukes.gui.view.MainMenuBuilder;
54  import com.melloware.jukes.gui.view.component.AlbumImage;
55  import com.melloware.jukes.gui.view.component.EnhancedTableHeader;
56  import com.melloware.jukes.gui.view.component.ExportableTable;
57  import com.melloware.jukes.util.GuiUtil;
58  
59  /**
60   * Searches the database by criteria and returns results in Track format.
61   * <p>
62   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
63   * @author Emil A. Lefkof III <info@melloware.com>
64   * @version 4.0
65   */
66  public final class SearchDialog
67      extends AbstractDialog {
68  
69      private static final Log LOG = LogFactory.getLog(SearchDialog.class);
70      private final AlbumImage webImagePreview;
71      private final EnhancedTableHeader header;
72      private final ExportableTable resultsTable;
73      private JButton buttonCancel;
74      private JButton buttonSearch;
75      private JButton buttonSelect;
76      private JButton buttonStop;
77      private final JComboBox bitrateField;
78      private final JComboBox genreField;
79      private final JComboBox operatorBitrate;
80      private final JComboBox operatorYear;
81      private JComponent buttonBar;
82      private JComponent splitPane;
83      private final JTextField searchField;
84      private final JTextField yearField;
85      private Object[] results;
86      private SearchTableModel tableModel;
87      private final Settings settings;
88      private RowSorter<TableModel> sorter;
89      private Track selection;
90      private Worker worker;
91  
92      /**
93       * Constructs a default about dialog using the given owner.
94       *
95       * @param owner   the dialog's owner
96       */
97      public SearchDialog(Frame owner, Settings settings) {
98          super(owner);
99          LOG.debug("Search Dialogcreated.");
100         this.settings = settings;
101         this.settings.getDatabaseLocation();
102 
103         webImagePreview = new AlbumImage();
104         searchField = new JTextField();
105         searchField.addKeyListener(new KeyAdapter() {
106                 public void keyPressed(KeyEvent e) {
107                     int key = e.getKeyCode();
108                     if (key == KeyEvent.VK_ENTER) {
109                         doSearch();
110                     } else {
111                         super.keyPressed(e);
112                     }
113 
114                 }
115             });
116         genreField = new JComboBox(MusicTag.getGenreTypes().toArray());
117         genreField.setSelectedItem(null);
118         bitrateField = new JComboBox(Resources.BITRATES);
119         bitrateField.setSelectedItem(null);
120         operatorBitrate = new JComboBox(Resources.OPERATOR);
121         operatorBitrate.setSelectedItem(">=");
122         operatorYear = new JComboBox(Resources.OPERATOR);
123         operatorYear.setSelectedItem(">=");
124         yearField = new JTextField();
125         ((JTextField)yearField).setColumns(5);
126         resultsTable = new ExportableTable();
127         resultsTable.setEvenColor(this.settings.getRowColorEven());
128         resultsTable.setOddColor(this.settings.getRowColorOdd());
129         final JPopupMenu popup = MainMenuBuilder.buildPlayerPopupMenu(resultsTable);
130         resultsTable.addPopupMenu(popup);
131         ActionManager.get(Actions.TRACK_PLAY_IMMEDIATE_ID).setEnabled(true);
132         header = new EnhancedTableHeader(resultsTable.getColumnModel(), resultsTable);
133         resultsTable.setTableHeader(header);
134 
135         this.setPreferredSize(new Dimension(700, 576));
136 
137     }
138 
139     /**
140      * Gets the selection.
141      * <p>
142      * @return Returns the selection.
143      */
144     public Track getSelection() {
145         return this.selection;
146     }
147 
148     /* (non-Javadoc)
149      * @see com.jgoodies.swing.AbstractDialog#doApply()
150      */
151     public void doAccept() {
152         LOG.debug("Select pressed.");
153         super.doAccept();
154 
155     }
156 
157     /* (non-Javadoc)
158      * @see com.jgoodies.swing.AbstractDialog#doCancel()
159      */
160     public void doCancel() {
161         LOG.debug("Cancel Pressed.");
162         super.doCancel();
163     }
164 
165     /**
166      * Builds and answers the dialog's content.
167      *
168      * @return the dialog's content with tabbed pane and button bar
169      */
170     protected JComponent buildContent() {
171         JPanel content = new JPanel(new BorderLayout());
172         JButton[] buttons = new JButton[2];
173         JButton button = createAcceptButton("Select", false);
174         button.setEnabled(false);
175         buttonSelect = button;
176         buttonCancel = createCancelButton();
177         buttons[0] = buttonSelect;
178         buttons[1] = buttonCancel;
179         buttonBar = ButtonBarFactory.buildRightAlignedBar(buttons);
180         splitPane = buildSplitPane();
181         content.add(splitPane, BorderLayout.CENTER);
182         content.add(buttonBar, BorderLayout.SOUTH);
183         return content;
184     }
185 
186     /**
187      * Builds and returns the dialog's header.
188      *
189      * @return the dialog's header component
190      */
191     protected JComponent buildHeader() {
192         final HeaderPanel header = new HeaderPanel("Search ",
193                                              "Searches the catalog by various search criteria. Results limited to 500 for speed.",
194                                              Resources.SEARCH_ICON);
195 
196         return header;
197     }
198 
199     /**
200      * Builds and returns the dialog's pane.
201      *
202      * @return the dialog's  pane component
203      */
204     protected JComponent buildMainPanel() {
205         FormLayout layout = new FormLayout("fill:pref:grow", "p, p, p, p, p, p, p, p, p, p, p");
206         PanelBuilder builder = new PanelBuilder(layout);
207         builder.setDefaultDialogBorder();
208         CellConstraints cc = new CellConstraints();
209         builder.add(buildCriteria(), cc.xy(1, 1));
210         builder.add(buildResultsTablePanel(), cc.xy(1, 7));
211         return builder.getPanel();
212     }
213 
214     /**
215     * Executes the search.
216     */
217     protected void doSearch() {
218         LOG.debug("Searching...");
219         if ((StringUtils.isBlank(searchField.getText()))) {
220             LOG.warn("Please enter search criteria.");
221             return;
222         }
223         GuiUtil.setBusyCursor(this, true);
224         buttonSearch.setEnabled(false);
225         buttonCancel.setEnabled(false);
226         buttonSelect.setEnabled(false);
227         buttonStop.setEnabled(true);
228 
229         /* Invoking start() on the SwingWorker causes a new Thread
230          * to be created that will call construct(), and then finished().  Note that finished() is called even if the
231          * worker is interrupted because we catch the InterruptedException in doWork().
232          */
233         worker = new Worker() {
234                 public Object construct() {
235                     return doWork();
236                 }
237 
238                 public void finished() {
239                     threadFinished(get());
240                 }
241             };
242         worker.start();
243     }
244 
245     /**
246      * Stops the search.
247      */
248     protected void doStop() {
249         LOG.debug("Stopping...");
250         GuiUtil.setBusyCursor(this, false);
251         worker.interrupt();
252         buttonSearch.setEnabled(true);
253         buttonCancel.setEnabled(true);
254         buttonStop.setEnabled(false);
255     }
256 
257     /**
258      * Resizes the given component to give it a quadratic aspect ratio.
259      *
260      * @param component   the component to be resized
261      */
262     protected void resizeHook(JComponent component) {
263         Resizer.ONE2ONE.resizeDialogContent(component);
264     }
265 
266     /**
267     * When the thread is finished this method is called.
268     * <p>
269     * @param result the Object return from the doWork thread.
270     */
271     protected void threadFinished(Object result) {
272         if (LOG.isDebugEnabled()) {
273             LOG.debug("Thread Finished");
274         }
275         buttonSearch.setEnabled(true);
276         buttonCancel.setEnabled(true);
277         buttonStop.setEnabled(false);
278         GuiUtil.setBusyCursor(this, false);
279 
280         if (result == null) {
281             results = null;
282             tableModel = new SearchTableModel(results);
283             tableModel.setData(null);
284             tableModel.fireTableDataChanged();
285         } else {
286             final Collection items = (Collection)result;
287             results = items.toArray();
288             tableModel = new SearchTableModel(results);
289             sorter = new TableRowSorter<TableModel>(tableModel);
290             resultsTable.setModel(tableModel);
291             resultsTable.setRowSorter(sorter);
292             header.autoSizeColumns();
293         }
294 
295         resultsTable.updateUI();
296         splitPane.updateUI();
297 
298     }
299 
300     /**
301      * Builds the search criteria panel.
302      * <p>
303      * @return the panel used to specify criteria
304      */
305     private JComponent buildCriteria() {
306         FormLayout layout = new FormLayout("right:max(14dlu;pref), pref, pref, 260px, pref, pref ,pref, fill:pref:grow",
307                                            "p, 4px, p, 4px, p, 4px, p, 4px, p, 4px");
308 
309         layout.setRowGroups(new int[][] {
310                                 { 2, 4 }
311                             });
312         PanelBuilder builder = new PanelBuilder(layout);
313         CellConstraints cc = new CellConstraints();
314 
315         JButton[] buttons = new JButton[2];
316 
317         // Create an action with an icon
318         Action search = new AbstractAction("Search", Resources.THREAD_START_ICON) {
319             public void actionPerformed(ActionEvent evt) {
320                 doSearch();
321             }
322         };
323         buttonSearch = new JButton(search);
324         // Create an action with an icon
325         Action stop = new AbstractAction("Stop", Resources.THREAD_STOP_ICON) {
326             public void actionPerformed(ActionEvent evt) {
327                 doStop();
328             }
329         };
330         buttonStop = new JButton(stop);
331         buttonStop.setEnabled(false);
332 
333         buttons[0] = buttonSearch;
334         buttons[1] = buttonStop;
335         JPanel searchButtonBar = ButtonBarFactory.buildCenteredBar(buttons);
336 
337         builder.addLabel("Find: ", cc.xy(1, 1));
338         builder.add(searchField, cc.xyw(2, 1, 4));
339         builder.add(webImagePreview, cc.xywh(6, 1, 1, 7));
340         builder.addLabel(Resources.getString("label.genre") + ": ", cc.xy(1, 3));
341         builder.add(genreField, cc.xyw(2, 3, 4));
342         builder.addLabel(Resources.getString("label.year") + ": ", cc.xy(1, 5));
343         builder.add(operatorYear, cc.xy(2, 5));
344         builder.add(yearField, cc.xy(3, 5));
345         builder.addLabel(Resources.getString("label.bitrate") + ": ", cc.xy(1, 7));
346         builder.add(operatorBitrate, cc.xy(2, 7));
347         builder.add(bitrateField, cc.xy(3, 7));
348         builder.add(searchButtonBar, cc.xyw(1, 9, 8));
349         return builder.getPanel();
350     }
351 
352     /**
353      * Builds the panel with the JTable tags in it.
354      * <p>
355      * @return the panel used to display messages
356      */
357     private JComponent buildResultsTablePanel() {
358 
359         // build the table and model
360         resultsTable.setShowGrid(false);
361         resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
362         resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
363         // Ask to be notified of selection changes.
364         final ListSelectionModel rowSM = resultsTable.getSelectionModel();
365         rowSM.addListSelectionListener(new ListSelectionListener() {
366                 public void valueChanged(ListSelectionEvent e) {
367                     // Ignore extra messages.
368                     if (e.getValueIsAdjusting()) {
369                         return;
370                     }
371 
372                     final ListSelectionModel lsm = (ListSelectionModel)e.getSource();
373                     if (lsm.isSelectionEmpty()) {
374                         selection = null;
375                         buttonSelect.setEnabled(false);
376                         webImagePreview.setImage(null);
377                     } else {
378                         int selectedRow = resultsTable.getSelectedRow();
379                         selectedRow = sorter.convertRowIndexToModel(selectedRow);
380                         selection = (Track)results[selectedRow];
381                         buttonSelect.setEnabled(true);
382                         if (StringUtils.isNotBlank(selection.getDisc().getCoverUrl())) {
383                             webImagePreview.setImage(ImageFactory.getScaledImage(selection.getDisc().getCoverUrl(), 90,
384                                                                                  90).getImage());
385                         } else {
386                             webImagePreview.setImage(null);
387                         }
388                     }
389                 }
390             });
391 
392         final JComponent resultsPane = UIFactory.createTablePanel(resultsTable);
393         resultsPane.setPreferredSize(new Dimension(300, 250));
394 
395         // build the form
396         final FormLayout layout = new FormLayout("fill:pref:grow", "p");
397         final PanelBuilder builder = new PanelBuilder(layout);
398         final CellConstraints cc = new CellConstraints();
399         builder.add(resultsPane, cc.xy(1, 1));
400         return builder.getPanel();
401     }
402 
403     /**
404     * Builds the <code>Search Criteria</code>, the <code>Results</code>
405     * and answers them wrapped by a stripped <code>JSplitPane</code>.
406     */
407     private JComponent buildSplitPane() {
408         splitPane = UIFactory.createStrippedSplitPane(JSplitPane.VERTICAL_SPLIT, buildCriteria(),
409                                                       buildResultsTablePanel(), 0.25);
410         splitPane.setPreferredSize(new Dimension(605, 470));
411         splitPane.setBorder(Borders.DIALOG_BORDER);
412         return splitPane;
413     }
414 
415     /**
416     * This method represents the application code that we'd like to
417     * run on a separate thread.
418     */
419     private Object doWork() {
420         Object result = null;
421         try {
422             // do the search
423             try {
424                 String query = "";
425                 if (genreField.getSelectedItem() != null) {
426                     query = query + " and disc.genre = '" + (String)genreField.getSelectedItem() + "'";
427                 }
428                 if (StringUtils.isNotBlank(yearField.getText())) {
429                     query = query + " and disc.year " + operatorYear.getSelectedItem().toString()
430                             + StringEscapeUtils.escapeSql(yearField.getText());
431                 }
432                 if (bitrateField.getSelectedItem() != null) {
433                     query = query + " and track.bitrate  " + operatorBitrate.getSelectedItem().toString()
434                             + StringEscapeUtils.escapeSql((String)bitrateField.getSelectedItem());
435                 }
436                 final String resource = ResourceUtils.getString("hql.search.criteria");
437                 final String hql = MessageFormat.format(resource,
438                                                         new Object[] {
439                                                             StringEscapeUtils.escapeSql(searchField.getText()), query
440                                                         });
441                 result = HibernateDao.findByQuery(hql, 500);
442             } catch (RuntimeException ex) {
443                 final String message = "Unexpected error occured performing search.";
444                 LOG.debug(message, ex);
445                 throw new InterruptedException(message);
446             }
447 
448             if (Thread.interrupted()) {
449                 LOG.debug("Thread interrupted.");
450                 throw new InterruptedException();
451             }
452 
453         } catch (InterruptedException e) {
454 
455             return result;    // SwingWorker.get() returns this
456         }
457         return result;    // or this
458     }
459 
460 }