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