001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: aschultz $'
006 * '$Date: 2011-03-19 02:24:12 +0000 (Sat, 19 Mar 2011) $' 
007 * '$Revision: 27324 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.gui;
031
032import java.awt.Dimension;
033import java.awt.Font;
034import java.awt.Insets;
035import java.awt.event.ActionListener;
036import java.awt.event.HierarchyEvent;
037import java.awt.event.HierarchyListener;
038import java.awt.event.KeyEvent;
039
040import javax.swing.Action;
041import javax.swing.BorderFactory;
042import javax.swing.Box;
043import javax.swing.BoxLayout;
044import javax.swing.JButton;
045import javax.swing.JPanel;
046import javax.swing.JTextField;
047import javax.swing.SwingConstants;
048import javax.swing.UIManager;
049import javax.swing.border.Border;
050import javax.swing.plaf.InsetsUIResource;
051
052import org.kepler.util.StaticResources;
053
054/**
055 * Class to build the Search User Interface JPanel that allows the user to enter
056 * a single search term to search by. 5 buttons can be optionally configured by
057 * setting Actions for those buttons.
058 * <ul>
059 * The 5 configurable buttons are
060 * <li>Search Button</li>
061 * <li>Reset Button</li>
062 * <li>Cancel Button</li>
063 * <li>Source Button</li>
064 * <li>Advanced Button</li>
065 * </ul>
066 * 
067 * @author brooke
068 * @since 4 Nov 2005
069 */
070public class SearchUIJPanel extends JPanel {
071
072        // constants
073
074        // SPACING is the exterior space around buttons
075        private static final int SPACING_HORIZ = 0; //originally:1 but now trying to minimize pane width
076        private static final int SPACING_VERT = 1;
077
078        private static final int BUTTON_WIDTH = 83; //originally:72 but didn't fit on os X default jbutton 
079        private static final int BUTTON_HEIGHT = 25;
080        private static final int SPACER_ABOVE_BELOW_TITLEDBORDER = 10;
081        private static final int WIDE_BUTTON_SPACER = 10;
082
083        private int PANEL_WIDTH = 200; // recalculated during init()
084        public static boolean SEARCHREPOS = false;
085        public static final String SEARCH_BUTTON_CAPTION = StaticResources
086                        .getDisplayString("search.search", "");
087        //public static final String RESET_BUTTON_CAPTION = StaticResources
088        //              .getDisplayString("search.reset", "");
089        public static final String CANCEL_BUTTON_CAPTION = StaticResources
090                        .getDisplayString("search.cancel", "");
091        public static final String SOURCE_BUTTON_CAPTION = StaticResources
092                        .getDisplayString("search.sources", "");
093        public static final String ADV_BUTTON_CAPTION = StaticResources
094                        .getDisplayString("search.advanced", "");
095
096        private final Dimension BUTTON_DIMS = new Dimension(BUTTON_WIDTH,
097                        BUTTON_HEIGHT);
098
099        // this seemed too wide
100        //private final Dimension WIDE_BUTTON_DIMS = new Dimension(SPACING_HORIZ + 2
101        //              * BUTTON_WIDTH, BUTTON_HEIGHT);
102        private final Dimension WIDE_BUTTON_DIMS = new Dimension(WIDE_BUTTON_SPACER+
103                          BUTTON_WIDTH, BUTTON_HEIGHT);
104
105        // set the inside space between the button's borders
106        // and the text it contains
107        private final InsetsUIResource BUTTON_INSIDE_PADDING = new InsetsUIResource(
108                        2, 0, 2, 0); // (top, left, bottom, right)
109
110        private static int BUTTON_FONT_SIZE = StaticResources.getSize(
111                        "button.limitedSpace.maxFontSize", 11);
112
113        private JTextField searchValueField = null;
114
115        private String borderTitle = "Search";
116        private Border titledBorder;
117
118        private Action searchAction;
119        //private Action resetAction;
120        private Action cancelAction;
121        private Action sourceAction;
122        private Action advancedAction;
123
124        private JButton searchButton;
125        //private JButton resetButton;
126        private JButton cancelButton;
127        private JButton sourceButton;
128        private JButton advancedButton;
129
130        /**
131         * Empty constructor.
132         */
133        public SearchUIJPanel() {
134        }
135
136        /**
137         * Constructor - creates a panel with a titled border (from String param
138         * panelBorderTitle), containing a textfield with up to 5 buttons beneath
139         * it. Constructor can accept <code>javax.swing.Action</code> objects for
140         * these 5 buttons; if any of these Action objects is null, the button will
141         * not be displayed on the user interface at runtime
142         * 
143         * @param panelBorderTitle
144         *            String
145         * @param searchButtonAction
146         *            Action
147         * @param resetButtonAction
148         *            Action
149         * @param cancelButtonAction
150         *            Action
151         * @param sourceButtonAction
152         *            Action
153         * @param advancedButtonAction
154         *            Action
155         */
156        public SearchUIJPanel(String panelBorderTitle, Action searchButtonAction,
157                        Action resetButtonAction, Action cancelButtonAction,
158                        Action sourceButtonAction, Action advancedButtonAction) {
159
160                setBorderTitle(panelBorderTitle);
161                setSearchAction(searchButtonAction);
162                //setResetAction(resetButtonAction);
163                setCancelAction(cancelButtonAction);
164                setSourceAction(sourceButtonAction);
165                setAdvancedAction(advancedButtonAction);
166        }
167
168        /**
169         * Returns the border title for this search panel. The default title is
170         * "Search".
171         * 
172         * @return the search border title
173         */
174        public String getBorderTitle() {
175                return borderTitle;
176        }
177
178        public void setBorderTitle(String borderTitle) {
179                this.borderTitle = borderTitle;
180        }
181
182        /**
183         * Returns the javax.swing.Action object that is called when the Search
184         * button is pressed.
185         * 
186         * @return the Action object that handles the search button
187         */
188        public Action getSearchAction() {
189                return searchAction;
190        }
191
192        /**
193         * Sets the Action to be used when the Search button is pressed. If this
194         * Action is not set then the Search button will not be visible to the user.
195         * 
196         * @param searchAction
197         *            the Action to be used when the Search button is pressed
198         */
199        public void setSearchAction(Action searchAction) {
200                this.searchAction = searchAction;
201        }
202
203        /**
204         * Returns the javax.swing.Action object that is called when the Reset
205         * button is pressed.
206         * 
207         * @return the Action object that handles the reset button
208         */
209        //public Action getResetAction() {
210        //      return resetAction;
211        //}
212
213        /**
214         * Sets the Action to be used when the Reset button is pressed. If this
215         * Action is not set then the Reset button will not be visible to the user.
216         * 
217         * @param resetAction
218         *            the Action to be used when the Reset button is pressed
219         */
220        //public void setResetAction(Action resetAction) {
221        //      this.resetAction = resetAction;
222        //}
223
224        /**
225         * Returns the javax.swing.Action object that is called when the Cancel
226         * button is pressed.
227         * 
228         * @return the Action object that handles the Cancel button
229         */
230        public Action getCancelAction() {
231                return cancelAction;
232        }
233
234        /**
235         * Sets the Action to be used when the Cancel button is pressed. If this
236         * Action is not set then the Cancel button will not be visible to the user.
237         * 
238         * @param cancelAction
239         *            the Action to be used when the Cancel button is pressed
240         */
241        public void setCancelAction(Action cancelAction) {
242                this.cancelAction = cancelAction;
243        }
244
245        /**
246         * Returns the javax.swing.Action object that is called when the Source
247         * button is pressed.
248         * 
249         * @return the Action object that handles the Source button
250         */
251        public Action getSourceAction() {
252                return sourceAction;
253        }
254
255        /**
256         * Sets the Action to be used when the Source button is pressed. If this
257         * Action is not set then the Source button will not be visible to the user.
258         * 
259         * @param sourceAction
260         *            the Action to be used when the Source button is pressed
261         */
262        public void setSourceAction(Action sourceAction) {
263                this.sourceAction = sourceAction;
264        }
265
266        /**
267         * Returns the javax.swing.Action object that is called when the Advanced
268         * button is pressed.
269         * 
270         * @return the Action object that handles the Advanced button
271         */
272        public Action getAdvancedAction() {
273                return advancedAction;
274        }
275
276        /**
277         * Sets the Action to be used when the Advanced button is pressed. If this
278         * Action is not set then the Advanced button will not be visible to the
279         * user.
280         * 
281         * @param advancedAction
282         *            the Action to be used when the Advanced button is pressed
283         */
284        public void setAdvancedAction(Action advancedAction) {
285                this.advancedAction = advancedAction;
286        }
287
288        /**
289         * 
290         * @return the current search term String from the textfield
291         */
292        public String getSearchTerm() {
293                return searchValueField.getText();
294        }
295
296        /**
297         * get the preferred/minimum width of this panel - calculated to allow
298         * enough space for all buttons and spacers etc
299         * 
300         * @return the minimum allowable width of this panel
301         */
302        public final int getMinimumWidth() {
303                return PANEL_WIDTH;
304        }
305
306        /**
307         * set the current search term String in the textfield
308         * 
309         * @param searchTerm
310         *            String
311         */
312        public void setSearchTerm(String searchTerm) {
313                if (searchTerm == null){
314                        searchTerm = "";
315                }
316                searchValueField.setText(searchTerm.trim());
317        }
318
319        /**
320         * if enabled==true, enable the textfield, search, reset, source and
321         * advanced buttons, and disable the cancel button. If enabled==false, do
322         * the reverse.
323         * 
324         * @param enabled
325         *            boolean
326         */
327        public void setSearchEnabled(boolean enabled) {
328                searchValueField.setEnabled(enabled);
329                if (searchButton != null){
330                        searchButton.setEnabled(enabled);
331                }
332                //if (resetButton != null)
333                //      resetButton.setEnabled(enabled);
334                if (cancelButton != null){
335                        cancelButton.setEnabled(!enabled);
336                }
337                if (sourceButton != null){
338                        sourceButton.setEnabled(enabled);
339                }
340                if (advancedButton != null){
341                        advancedButton.setEnabled(enabled);
342                }
343        }
344        
345        //enable/disable cancel button
346        public void setCancelButtonEnabled(boolean enabled) {
347                if (cancelButton != null){
348                        cancelButton.setEnabled(enabled);
349                }
350        }
351
352        /**
353         * Enables/disables _all_ Search buttons we will use this to disable all
354         * buttons as we fetch datasources from the repository if enabled==true,
355         * enable the textfield, search, reset, source and advanced buttons, and
356         * cancel button. If enabled==false, do the reverse.
357         * 
358         * @param enabled
359         *            boolean
360         */
361        public void setAllSearchEnabled(boolean enabled) {
362                searchValueField.setEnabled(enabled);
363                if (searchButton != null){
364                        searchButton.setEnabled(enabled);
365                }
366                //if (resetButton != null)
367                //      resetButton.setEnabled(enabled);
368                if (cancelButton != null){
369                        cancelButton.setEnabled(enabled);
370                }
371                if (sourceButton != null){
372                        sourceButton.setEnabled(enabled);
373                }
374                if (advancedButton != null){
375                        advancedButton.setEnabled(enabled);
376                }
377        }
378
379        /**
380         * Initialize the search panel. This should be called after constructing and
381         * adding whichever Actions you want the search panel to contain.
382         */
383        public void init() {
384
385                // the panel that contains the textfield
386                JPanel searchPanel = createPanel();
387                searchPanel.setBackground(TabManager.BGCOLOR);
388                JPanel checkBoxPanel = createPanel();
389                checkBoxPanel.setBackground(TabManager.BGCOLOR);
390                
391                // remember default button margins
392                final InsetsUIResource defaultUIMgrButtonMargin = (InsetsUIResource) UIManager
393                                .get("Button.margin");
394                // now set our custom ones
395                UIManager.put("Button.margin", BUTTON_INSIDE_PADDING);
396
397                // remember default button font
398                final Font defaultUIMgrButtonFont = (Font) UIManager.get("Button.font");
399
400                // now set our custom size, provided it's smaller than the default:
401                int buttonFontSize = (defaultUIMgrButtonFont.getSize() < BUTTON_FONT_SIZE) ? defaultUIMgrButtonFont
402                                .getSize()
403                                : BUTTON_FONT_SIZE;
404
405                final Font BUTTON_FONT = new Font(defaultUIMgrButtonFont.getFontName(),
406                                defaultUIMgrButtonFont.getStyle(), buttonFontSize);
407
408                UIManager.put("Button.font", BUTTON_FONT);
409                
410                searchValueField = new JTextField();
411                //special search style for mac os X
412                searchValueField.putClientProperty("JTextField.variant", "search");
413                //when mac os X user clicks x in textfield, Reset
414                searchValueField.putClientProperty("JTextField.Search.CancelAction", cancelAction);
415                searchValueField.setAction(searchAction);
416                
417                searchPanel.add(searchValueField);
418                
419                if (searchAction != null) {
420                        searchButton = createButton(SEARCH_BUTTON_CAPTION, searchAction);
421                        searchButton.setMnemonic(KeyEvent.VK_ENTER);
422                        searchPanel.add(searchButton);
423                        //searchValueField.addActionListener(searchAction);
424                }
425
426                JPanel buttonsPanel = createButtonPanel();
427                buttonsPanel.setBackground(TabManager.BGCOLOR);
428
429                if (advancedAction != null) {
430                        advancedButton = createWideButton(ADV_BUTTON_CAPTION,
431                                        advancedAction);
432                        buttonsPanel.add(advancedButton);
433                }
434                else{
435                        buttonsPanel.add(Box.createHorizontalStrut(BUTTON_DIMS.width));
436                }
437                if (sourceAction != null) {
438                        sourceButton = createButton(SOURCE_BUTTON_CAPTION, sourceAction);
439
440                        buttonsPanel.add(sourceButton);
441                        if (advancedAction != null) {
442                                buttonsPanel
443                                                .add(Box.createHorizontalStrut(SPACING_HORIZ));
444                        }
445                }
446                
447                if (cancelAction != null) {
448                        cancelButton = createButton(CANCEL_BUTTON_CAPTION, cancelAction);
449                        buttonsPanel.add(cancelButton);                 
450                }
451
452                // restore default button margins
453                if (defaultUIMgrButtonMargin != null) {
454                        UIManager.put("Button.margin", defaultUIMgrButtonMargin);
455                }
456                // restore default button font
457                if (defaultUIMgrButtonFont != null) {
458                        UIManager.put("Button.font", defaultUIMgrButtonFont);
459                }
460
461                JPanel titledPanel = new JPanel();
462                titledPanel.setBackground(TabManager.BGCOLOR);
463                titledPanel.setLayout(new BoxLayout(titledPanel, BoxLayout.Y_AXIS));
464                titledBorder = BorderFactory.createTitledBorder(borderTitle);
465                titledPanel.setBorder(titledBorder);
466                titledPanel.add(searchPanel);
467                titledPanel.add(checkBoxPanel);
468                titledPanel.add(buttonsPanel);
469
470                this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
471                this.add(Box.createVerticalStrut(SPACER_ABOVE_BELOW_TITLEDBORDER));
472                this.add(titledPanel);
473                this.add(Box.createVerticalStrut(SPACER_ABOVE_BELOW_TITLEDBORDER));
474
475                final JPanel instance = this;
476                this.addHierarchyListener(new HierarchyListener() {
477                        public void hierarchyChanged(HierarchyEvent e) {
478                                if (getRootPane() == null){
479                                        return;
480                                }
481                                if (!instance.isShowing()){
482                                        return;
483                                }
484                                // having searchButton be defaultButton 
485                                // means Enters get accepted a little too liberally
486                                // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4544
487                                //getRootPane().setDefaultButton(searchButton);
488                        }
489                });
490
491                final Insets borderInsets = titledBorder.getBorderInsets(this);
492                PANEL_WIDTH = (3 * (BUTTON_WIDTH + (2 * SPACING_HORIZ)))
493                                + borderInsets.left + borderInsets.right;
494                
495                setSearchEnabled(true);
496                this.setBackground(TabManager.BGCOLOR);
497        }
498        
499        public void closing() {
500                //System.out.println("SearchUIJPanel.closing()");
501                searchButton.setAction(null);
502                sourceButton.setAction(null);
503                cancelButton.setAction(null);
504                advancedButton.setAction(null);
505                searchAction = null;
506                sourceAction = null;
507                cancelAction = null;
508                advancedAction = null;
509        }
510
511        private JPanel createButtonPanel() {
512                JPanel buttonPanel = createPanel();
513                buttonPanel.setBackground(TabManager.BGCOLOR);
514                buttonPanel.add(Box.createHorizontalGlue());
515                return buttonPanel;
516        }
517
518        private JPanel createPanel() {
519                JPanel panel = new JPanel();
520                panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
521                panel.setBorder(BorderFactory.createEmptyBorder(SPACING_VERT,
522                                SPACING_HORIZ, SPACING_VERT, SPACING_HORIZ));
523                return panel;
524        }
525
526        private JButton createButton(String caption, ActionListener listener) {
527                return createButton(caption, listener, BUTTON_DIMS);
528        }
529
530        private JButton createWideButton(String caption, ActionListener listener) {
531                return createButton(caption, listener, WIDE_BUTTON_DIMS);
532        }
533
534        private JButton createButton(String caption, ActionListener listener,
535                        Dimension dims) {
536                JButton button = new JButton(caption);
537                button.addActionListener(listener);
538                button.setMinimumSize(dims);
539                button.setPreferredSize(dims);
540                button.setMaximumSize(dims);
541                button.setAlignmentX(SwingConstants.CENTER);
542                return button;
543        }
544}