com.salas.bb.search.SearchDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.salas.bb.search.SearchDialog.java

Source

// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 by R. Pito Salas
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software Foundation;
// either version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with this program;
// if not, write to the Free Software Foundation, Inc., 59 Temple Place,
// Suite 330, Boston, MA 02111-1307 USA
//
// Contact: R. Pito Salas
// mailto:pitosalas@users.sourceforge.net
// More information: about BlogBridge
// http://www.blogbridge.com
// http://sourceforge.net/projects/blogbridge
//
// $Id: SearchDialog.java,v 1.26 2007/07/20 16:51:27 spyromus Exp $
//

package com.salas.bb.search;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.uif.AbstractDialog;
import com.jgoodies.uif.util.Resizer;
import com.jgoodies.uif.util.ResourceUtils;
import com.jgoodies.uif.util.SystemUtils;
import com.salas.bb.utils.i18n.Strings;
import com.salas.bb.utils.uif.BBFormBuilder;
import com.salas.bb.utils.uif.ProgressSpinner;
import com.salas.bb.utils.uif.UifUtilities;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.text.MessageFormat;

/**
 * Search dialog box.
 */
public class SearchDialog extends AbstractDialog {
    /** Minimum dialog size. */
    private static final Dimension MIN_SIZE = new Dimension(500, 350);

    private final SearchEngine searchEngine;

    private ResultsList itemsList;
    private JLabel lbResults;
    private ProgressSpinner pgSpinner;
    private final SearchDialog.ResultsListener resultsListener;
    private SearchResultsListModel model;

    /**
     * Creates search dialog.
     *
     * @param owner     the dialog's parent frame.
     * @param engine    search engine to use.
     * @param listener  selection listener.
     */
    public SearchDialog(Frame owner, SearchEngine engine, ActionListener listener) {
        super(owner, Strings.message("search.dialog.title"));
        searchEngine = engine;

        model = new SearchResultsListModel();
        itemsList = new ResultsList(model);
        itemsList.addActionListener(listener);

        lbResults = new JLabel();
        UifUtilities.smallerFont(lbResults);

        pgSpinner = new ProgressSpinner();

        resultsListener = new ResultsListener();
        searchEngine.getResult().addChangesListener(resultsListener);

        setModal(false);
    }

    /** Release resources before closing. */
    public void close() {
        searchEngine.getResult().removeChangesListener(resultsListener);

        itemsList = null;
        super.close();
        getContentPane().removeAll();
    }

    /**
     * Sets the dialog's resizable state. By default dialogs are non-resizable; subclasses may
     * override.
     */
    protected void setResizable() {
        setResizable(true);
    }

    /**
     * Creates main content pane.
     *
     * @return the dialog's main content without header and border.
     */
    protected JComponent buildContent() {
        JPanel panel = new JPanel(new BorderLayout());

        panel.add(buildTopBar(), BorderLayout.NORTH);
        panel.add(buildResultsPanel(), BorderLayout.CENTER);

        return panel;
    }

    /**
     * Creates top bar with progress indicator, results count and search field.
     *
     * @return top bar component.
     */
    private Component buildTopBar() {
        final JLabel helpIcon = new JLabel(ResourceUtils.getIcon("search.ext.icon"));
        helpIcon.setToolTipText(Strings.message("search.ext.text"));
        helpIcon.setEnabled(false);

        final JCheckBox chPinnedArticlesOnly = new JCheckBox(Strings.message("search.pinned.articles.only"));
        UifUtilities.smallerFont(chPinnedArticlesOnly);
        if (SystemUtils.IS_OS_MAC)
            chPinnedArticlesOnly.setMargin(new Insets(0, 0, 2, 0));

        final SearchField tfSearch = new SearchField();
        tfSearch.addKeyListener(new NavigationListener());

        // Register changes monitor
        ActionListener monitor = new SearchCriteriaChangeMonitor(chPinnedArticlesOnly, tfSearch, helpIcon);
        tfSearch.addActionListener(monitor);
        chPinnedArticlesOnly.addActionListener(monitor);

        BBFormBuilder builder = new BBFormBuilder("p, 2dlu, 50dlu, 2dlu, p, 4dlu, p, 7dlu, p, 14dlu:grow, p");

        JLabel lbSearch = builder.append(Strings.message("search.prompt"), 1);
        lbSearch.setLabelFor(tfSearch);
        UifUtilities.smallerFont(lbSearch);
        UifUtilities.smallerFont(tfSearch);
        builder.append(tfSearch);
        builder.append(helpIcon);
        builder.append(chPinnedArticlesOnly, 1, CellConstraints.DEFAULT, CellConstraints.CENTER);
        builder.append(lbResults);
        builder.append(pgSpinner);
        builder.appendUnrelatedComponentsGapRow();

        return builder.getPanel();
    }

    /**
     * Creates results panel with results list and controls.
     *
     * @return results panel component.
     */
    private Component buildResultsPanel() {
        JPanel panel = new JPanel(new BorderLayout());

        JPanel panel2 = new JPanel(new BorderLayout());
        panel2.add(itemsList, BorderLayout.NORTH);
        panel2.setBackground(Color.WHITE);

        panel.add(panel2, BorderLayout.CENTER);
        panel.add(buildControlPanel(), BorderLayout.EAST);

        JScrollPane sp = new JScrollPane(panel);
        sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        return sp;
    }

    /**
     * Returns currently selected item.
     *
     * @return item.
     */
    public ResultItem getSelectedItem() {
        return itemsList == null ? null : itemsList.getSelectedItem();
    }

    /**
     * Creates control panel with grouping, sorting and filtering options.
     *
     * @return control panel.
     */
    private JComponent buildControlPanel() {
        BBFormBuilder builder = new BBFormBuilder("5dlu, p, 5dlu");

        builder.appendRelatedComponentsGapRow();
        builder.setLeadingColumnOffset(1);
        builder.nextLine();

        // Grouping
        Action actGroup = new AbstractAction() {
            private ActionLabel selection;

            public void actionPerformed(ActionEvent e) {
                if (selection == e.getSource())
                    return;
                if (selection != null)
                    selection.setSelected(false);
                onGroupingChange(e.getID());
                selection = (ActionLabel) e.getSource();
            }
        };
        ActionLabel albFlat = new ActionLabel(actGroup, Strings.message("search.groupping.flat"),
                SearchResultsListModel.GROUP_FLAT);
        ActionLabel albKind = new ActionLabel(actGroup, Strings.message("search.groupping.kind"),
                SearchResultsListModel.GROUP_KIND);
        ActionLabel albDate = new ActionLabel(actGroup, Strings.message("search.groupping.date"),
                SearchResultsListModel.GROUP_DATE);
        albKind.setSelected(true);

        // Filtering
        Action actFiltering = new AbstractAction() {
            private ActionLabel selection;

            public void actionPerformed(ActionEvent e) {
                if (selection == e.getSource())
                    return;
                if (selection != null)
                    selection.setSelected(false);
                onFilteringChange(e.getID());
                selection = (ActionLabel) e.getSource();
            }
        };
        ActionLabel albAnyDate = new ActionLabel(actFiltering, Strings.message("search.when.any.date"),
                ResultsList.DATE_ANY);
        ActionLabel albToday = new ActionLabel(actFiltering, Strings.message("search.when.today"),
                ResultsList.DATE_TODAY);
        ActionLabel albYesterday = new ActionLabel(actFiltering, Strings.message("search.when.since.yesterday"),
                ResultsList.DATE_YESTERDAY);
        ActionLabel albThisWeek = new ActionLabel(actFiltering, Strings.message("search.when.this.week"),
                ResultsList.DATE_WEEK);
        ActionLabel albThisMonth = new ActionLabel(actFiltering, Strings.message("search.when.this.month"),
                ResultsList.DATE_MONTH);
        ActionLabel albThisYear = new ActionLabel(actFiltering, Strings.message("search.when.this.year"),
                ResultsList.DATE_YEAR);
        albAnyDate.setSelected(true);

        builder.append(smallLabel(Strings.message("search.groupping")));
        builder.nextLine();
        builder.append(albFlat);
        builder.nextLine();
        builder.append(albKind);
        builder.nextLine();
        builder.append(albDate);
        //        builder.nextLine();

        builder.appendUnrelatedComponentsGapRow(2);
        builder.setLeadingColumnOffset(0);
        builder.append(new JLabel(), 3);
        //        builder.append(new JPopupMenu.Separator(), 3);

        builder.setLeadingColumnOffset(1);
        builder.append(smallLabel(Strings.message("search.when")));
        builder.nextLine();
        builder.append(albAnyDate);
        builder.nextLine();
        builder.append(albToday);
        builder.nextLine();
        builder.append(albYesterday);
        builder.nextLine();
        builder.append(albThisWeek);
        builder.nextLine();
        builder.append(albThisMonth);
        builder.nextLine();
        builder.append(albThisYear);
        builder.nextLine();

        return builder.getPanel();
    }

    /**
     * Creates small label component.
     *
     * @param txt text.
     *
     * @return label component.
     */
    private static JComponent smallLabel(String txt) {
        JLabel label = new JLabel(txt);
        UifUtilities.smallerFont(label);

        return label;
    }

    /**
     * Inoked when grouping changes.
     *
     * @param grouping new grouping option.
     */
    private void onGroupingChange(int grouping) {
        model.setGroupBy(grouping);
    }

    /**
     * Invoked when filtering changes.
     *
     * @param filtering new filtering option.
     */
    private void onFilteringChange(int filtering) {
        itemsList.setDateRange(filtering);
    }

    /**
     * Resizes the specified component. This method is called during the build process and enables
     * subclasses to achieve a better aspect ratio, by applying a resizer, e.g. the
     * <code>Resizer</code>.
     *
     * @param component the component to be resized
     */
    protected void resizeHook(JComponent component) {
        component.setPreferredSize(Resizer.ONE2ONE.fromWidth(MIN_SIZE.width));
    }

    // ---------------------------------------------------------------------------------------------

    /** Action label with selection indication and click handler. */
    private static class ActionLabel extends JLabel {
        private final Action action;
        private final int id;

        /**
         * Creates label.
         *
         * @param anAction action to call when clicked.
         * @param title    label title.
         * @param anID       ID.
         */
        public ActionLabel(Action anAction, String title, int anID) {
            super(title);
            UifUtilities.smallerFont(this);

            this.action = anAction;
            id = anID;

            setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
            enableEvents(AWTEvent.MOUSE_EVENT_MASK);
        }

        /**
         * Selects / deselects item.
         *
         * @param aSelected <code>TRUE</code> to select.
         */
        public void setSelected(boolean aSelected) {
            Color cl = aSelected ? Color.BLUE : Color.BLACK;
            setForeground(cl);
            if (aSelected)
                action.actionPerformed(new ActionEvent(this, id, null));
        }

        /**
         * Processes clicks.
         *
         * @param e event.
         */
        protected void processMouseEvent(MouseEvent e) {
            if (e.getID() == MouseEvent.MOUSE_PRESSED) {
                setSelected(true);
            }
        }
    }

    /**
     * Listener for results updates.
     */
    private class ResultsListener implements ISearchResultListener {
        private int results = 0;

        /**
         * Invoked when new result item is added to the list.
         *
         * @param result results list object.
         * @param item   item added.
         * @param index  item index.
         */
        public void itemAdded(ISearchResult result, final ResultItem item, int index) {
            results++;

            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    lbResults.setText(
                            MessageFormat.format(Strings.message("search.0.results"), Integer.toString(results)));
                    model.add(item);
                }
            });
        }

        /**
         * Invoked when the result items are removed from the list.
         *
         * @param result results list object.
         */
        public void itemsRemoved(ISearchResult result) {
            results = 0;

            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    lbResults.setText(Strings.message("search.no.results"));
                    model.clear();
                }
            });
        }

        /**
         * Invoked when underlying search is finished.
         *
         * @param result results list object.
         */
        public void finished(ISearchResult result) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    pgSpinner.stop();
                }
            });
        }
    }

    /**
     * Listens to key taps and passes them to the list component.
     */
    private class NavigationListener extends KeyAdapter {
        /**
         * Invoked when a key has been pressed.
         *
         * @param e event.
         */
        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
            case KeyEvent.VK_UP:
                itemsList.onPrevItemSelected();
                break;
            case KeyEvent.VK_DOWN:
                itemsList.onNextItemSelected();
                break;
            case KeyEvent.VK_ESCAPE:
                doCancel();
                break;
            }
        }
    }

    private class SearchCriteriaChangeMonitor implements ActionListener {
        private String lastText;
        public boolean lastPinnedArticlesOnly;
        private final JCheckBox chPinnedArticlesOnly;
        private final JLabel helpIcon;
        private final SearchField tfSearch;

        public SearchCriteriaChangeMonitor(JCheckBox chPinnedArticlesOnly, SearchField tfSearch, JLabel helpIcon) {
            this.chPinnedArticlesOnly = chPinnedArticlesOnly;
            this.helpIcon = helpIcon;
            this.tfSearch = tfSearch;
            lastPinnedArticlesOnly = chPinnedArticlesOnly.isSelected();
        }

        public void actionPerformed(ActionEvent e) {
            String text = tfSearch.getText();
            boolean pinnedArticlesOnly = chPinnedArticlesOnly.isSelected();

            showHelpIfNecessary(text);
            if (!text.equalsIgnoreCase(lastText) || pinnedArticlesOnly != lastPinnedArticlesOnly) {
                pgSpinner.start();
                searchEngine.setSearchText(text, pinnedArticlesOnly);
                lastText = text;
                lastPinnedArticlesOnly = pinnedArticlesOnly;
            } else {
                itemsList.onItemFired();
            }
        }

        private void showHelpIfNecessary(String text) {
            helpIcon.setEnabled(SearchEngine.isComplexSeachPattern(text));
        }
    }
}