Package com.salas.bb.search

Source Code of com.salas.bb.search.ResultsList

// 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: ResultsList.java,v 1.14 2007/09/19 16:09:25 spyromus Exp $
//

package com.salas.bb.search;

import com.salas.bb.core.GlobalController;
import com.salas.bb.domain.IArticle;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;

/**
* Search list, which is capable of displaying result items in a grouped, sorted and
* filtered way.
*/
public class ResultsList extends JPanel implements IResultsListModelListener, Scrollable
{
    /** Any dates allowed. */
    public static final int DATE_ANY        = 0;
    /** Today items only allowed. */
    public static final int DATE_TODAY      = 1;
    /** Items since yesterday are allowed. */
    public static final int DATE_YESTERDAY  = 2;
    /** Items during this week are allowed. */
    public static final int DATE_WEEK       = 3;
    /** Items during this month are allowed. */
    public static final int DATE_MONTH      = 4;
    /** Items during this year are allowed. */
    public static final int DATE_YEAR       = 5;

    /** The map of group keys to groups. */
    private final Map<Integer, ResultGroupPanel> groups = new TreeMap<Integer, ResultGroupPanel>();

    /** Current date filtering option. */
    private int dateRange = DATE_ANY;

    /** Limit time is based on the {@link #dateRange} property and works as filter. */
    private long limitTime;

    /** Number of items to show in each group initially. */
    private int itemGroupLimit;
    /** Currently selected item. */
    private ResultItemPanel selectedItem;

    /** Action listeners. */
    private final List<ActionListener> listeners = new ArrayList<ActionListener>();

    /**
     * Creates search results list.
     *
     * @param model model this list will be using.
     */
    public ResultsList(ResultsListModel model)
    {
        // Register the model
        model.addListener(this);

        itemGroupLimit = 5;
        limitTime = getLimitTime(dateRange);

        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

        setFocusable(true);
        enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);
    }

    /**
     * Invoked when the model is cleared.
     *
     * @param model model.
     */
    public void onClear(IResultsListModel model)
    {
        groups.clear();
        removeAll();

        revalidate();
    }

    /**
     * Invoked when an item is added to the model.
     *
     * @param model model.
     * @param item  item added.
     * @param group group the item was added to.
     */
    public void onItemAdded(IResultsListModel model, ResultItem item, ResultGroup group)
    {
        // Create the item panel and register it
        ResultItemPanel itemPanel = new ResultItemPanel(item);
        ResultGroupPanel groupPanel = groups.get(group.getKey());

        // Register the item within the panel
        int index = groupPanel.register(itemPanel) + 1;
        index += indexOf(groupPanel);
        add(itemPanel, index);

        // Revalidate the list
        revalidate();
    }

    /**
     * Invoked when an item is removed from the group.
     *
     * @param model model.
     * @param item  item removed.
     * @param group group the item was removed from.
     */
    public void onItemRemoved(IResultsListModel model, ResultItem item, ResultGroup group)
    {
        // Remove item panel from the group
        ResultGroupPanel groupPanel = groups.get(group.getKey());
        ResultItemPanel itemPanel = groupPanel.removeItemPanelFor(item);

        if (itemPanel != null) remove(itemPanel);
       
        // Revalidate the list
        revalidate();
    }

    /**
     * Invoked when the model adds a group to hold new items.
     *
     * @param model     model.
     * @param group     group added.
     * @param ordered   when <code>TRUE</code> group is added in the order of appearance.
     */
    public void onGroupAdded(IResultsListModel model, ResultGroup group, boolean ordered)
    {
        // Create the group panel and initialize it with parameters
        ResultGroupPanel groupPanel = createGroupPanel(group);
        groupPanel.setItemLimit(itemGroupLimit);
        groupPanel.setLimitTime(limitTime);
        groups.put(group.getKey(), groupPanel);

        // Find a group before which to add this one
        ResultGroupPanel preGroup = null;
        if (!ordered)
        {
            Integer key = group.getKey();
            Iterator<Integer> it = groups.keySet().iterator();
            while (preGroup == null && it.hasNext())
            {
                Object otherKey = it.next();
                if (key == otherKey && it.hasNext()) preGroup = groups.get(it.next());
            }
        }

        // Add the group and its more-component
        int index = preGroup == null ? -1 : indexOf(preGroup);
        add(groupPanel, index);
        add(groupPanel.getMoreComponent(), index == -1 ? -1 : index + 1);
    }

    /**
     * Creates group panel.
     *
     * @param group group.
     *
     * @return panel.
     */
    protected ResultGroupPanel createGroupPanel(ResultGroup group)
    {
        return new ResultGroupPanel(group.getName());
    }

    /**
     * Invoked when the model removes a group to hold new items.
     *
     * @param model model.
     * @param group group added.
     */
    public void onGroupRemoved(IResultsListModel model, ResultGroup group)
    {
        // Remove the panel and it's more-component
        ResultGroupPanel panel = groups.remove(group.getKey());
        remove(panel);
        remove(panel.getMoreComponent());

        // Remove all items
        panel.removeAllItemsFrom(this);

        revalidate();
    }

    /**
     * Invoked when the model changes a group to hold new items.
     *
     * @param model model.
     * @param group group added.
     */
    public void onGroupUpdated(IResultsListModel model, ResultGroup group)
    {
        // Update the group title
        ResultGroupPanel groupPanel = groups.get(group.getKey());
        if (groupPanel != null)
        {
            String title = group.getName();
            groupPanel.setTitle(title);
        }
    }

    /**
     * Sets the number of item to display in the group max.
     *
     * @param limit limit.
     */
    public void setItemGroupLimit(int limit)
    {
        itemGroupLimit = limit;

        for (ResultGroupPanel group : groups.values()) group.setItemLimit(limit);
    }

    /**
     * Sets date range for filtering out unwanted items.
     *
     * @param aDateRange range.
     */
    public void setDateRange(int aDateRange)
    {
        if (dateRange != aDateRange)
        {
            dateRange = aDateRange;
            limitTime = getLimitTime(dateRange);

            for (ResultGroupPanel group : groups.values()) group.setLimitTime(limitTime);
        }
    }

    /**
     * Adds action listener.
     *
     * @param l listener.
     */
    public void addActionListener(ActionListener l)
    {
        listeners.add(l);
    }

    /**
     * Removes action listeners.
     *
     * @param l listener.
     */
    public void removeActionListener(ActionListener l)
    {
        listeners.remove(l);
    }

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

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

    /**
     * Returns index of component.
     *
     * @param comp component.
     *
     * @return index.
     */
    private int indexOf(Component comp)
    {
        int index = -1;

        for (int i = 0; index == -1 && i < getComponentCount(); i++)
        {
            if (getComponent(i) == comp) index = i;
        }

        return index;
    }

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

    /**
     * Invoked when new item is selected.
     *
     * @param item item.
     */
    private void onItemSelected(ResultItemPanel item)
    {
        requestFocusInWindow();
       
        if (selectedItem != null) selectedItem.setSelected(false);

        selectedItem = item;

        if (selectedItem != null)
        {
            selectedItem.setSelected(true);
            scrollRectToVisible(selectedItem.getBounds());
        }
    }

    /**
     * Invoked when item selection is confirmed (fired).
     */
    void onItemFired()
    {
        ActionEvent event = null;

        for (ActionListener listener : listeners)
        {
            if (event == null) event = new ActionEvent(this, 0, "");
            listener.actionPerformed(event);
        }
    }

    /**
     * Invoked when item selection is used to toggle the read state.
     */
    private void onItemToggleReadState()
    {
        if (selectedItem != null)
        {
            Object o = selectedItem.getItem().getObject();
            if (o instanceof IArticle)
            {
                IArticle article = (IArticle)o;

                GlobalController.readArticles(!article.isRead(),
                    null,
                    null,
                    article);
            }
        }
    }

    /**
     * Invoked when item selection is used to toggle the pin state.
     */
    private void onItemTogglePinState()
    {
        if (selectedItem != null)
        {
            Object o = selectedItem.getItem().getObject();
            if (o instanceof IArticle)
            {
                IArticle article = (IArticle)o;
                GlobalController.pinArticles(!article.isPinned(),
                    null,
                    null,
                    article);
            }
        }
    }

    /**
     * Invoked when previous visible item selection is requested.
     */
    public void onPrevItemSelected()
    {
        if (selectedItem != null)
        {
            int index = indexOf(selectedItem);
            ResultItemPanel toSelect = null;
            for (int i = index - 1; toSelect == null && i > 0; i--)
            {
                Component c = getComponent(i);
                if (c instanceof ResultItemPanel && c.isVisible()) toSelect = (ResultItemPanel)c;
            }

            if (toSelect != null) onItemSelected(toSelect);
        }
    }

    /**
     * Invoked when next visible item selection is requested.
     */
    public void onNextItemSelected()
    {
        int index = selectedItem == null ? 0 : indexOf(selectedItem);
        ResultItemPanel toSelect = null;
        for (int i = index + 1; toSelect == null && i < getComponentCount(); i++)
        {
            Component c = getComponent(i);
            if (c instanceof ResultItemPanel && c.isVisible()) toSelect = (ResultItemPanel)c;
        }

        if (toSelect != null) onItemSelected(toSelect);
    }

    /**
     * Calculates limit time basing on the date range.
     *
     * @param dateRange date range.
     *
     * @return limit time.
     */
    private static long getLimitTime(int dateRange)
    {
        if (dateRange == DATE_ANY) return -1L;

        // Get today time
        GregorianCalendar cal = new GregorianCalendar();
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);

        long time;

        switch (dateRange)
        {
            case DATE_YESTERDAY:
                cal.add(Calendar.DATE, -1);
                break;
            case DATE_WEEK:
                cal.add(Calendar.DATE, -7);
                break;
            case DATE_MONTH:
                cal.set(Calendar.DATE, 1);
                break;
            case DATE_YEAR:
                cal.set(Calendar.DAY_OF_YEAR, 1);
                break;
            default:
                break;
        }

        time = cal.getTimeInMillis();

        return time;
    }

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

    /**
     * Returns the preferred size of the viewport for a view component. For example, the preferred size of a
     * <code>JList</code> component is the size required to accommodate all of the cells in its list. However, the value
     * of <code>preferredScrollableViewportSize</code> is the size required for <code>JList.getVisibleRowCount</code>
     * rows. A component without any properties that would affect the viewport size should just return
     * <code>getPreferredSize</code> here.
     *
     * @return the preferredSize of a <code>JViewport</code> whose view is this <code>Scrollable</code>
     *
     * @see javax.swing.JViewport#getPreferredSize
     */
    public Dimension getPreferredScrollableViewportSize()
    {
        return getPreferredSize();
    }

    /**
     * Components that display logical rows or columns should compute the scroll increment that will completely expose
     * one new row or column, depending on the value of orientation.  Ideally, components should handle a partially
     * exposed row or column by returning the distance required to completely expose the item.
     * <p/>
     * Scrolling containers, like JScrollPane, will use this method each time the user requests a unit scroll.
     *
     * @param visibleRect The view area visible within the viewport
     * @param orientation Either SwingConstants.VERTICAL or SwingConstants.HORIZONTAL.
     * @param direction   Less than zero to scroll up/left, greater than zero for down/right.
     *
     * @return The "unit" increment for scrolling in the specified direction. This value should always be positive.
     *
     * @see javax.swing.JScrollBar#setUnitIncrement
     */
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
    {
        int increment = 1;

        if (orientation == SwingConstants.VERTICAL)
        {
            int px = visibleRect.x;
            int py = visibleRect.y;

            increment = getIncrementForBorderChild(direction, px, py);
            if (increment == 0)
            {
                // Nowhere to move for the border child
                // See if we can move one child up / down
                if (direction < 0)
                {
                    // up
                    if (py > 0) increment = getIncrementForBorderChild(direction, px, py - 1);
                } else
                {
                    // down
                    int height = getHeight();
                    if (py < height - visibleRect.height) increment = getIncrementForBorderChild(direction, px, py + 1);
                }

                // We need to compensate for these '-1' or '+1' above
                if (increment > 0) increment++;
            }
        }

        return increment;
    }

    private int getIncrementForBorderChild(int direction, int px, int py)
    {
        int increment = 0;

        Component comp = findComponentAt(px, py);
        if (comp != null)
        {
            Point inCompPoint = SwingUtilities.convertPoint(this, px, py, comp);
            int y = inCompPoint.y;
            if (direction < 0)
            {
                // up
                if (y > 0) increment = y;
            } else
            {
                // down
                int height = comp.getHeight();
                if (y < height) increment = height - y;
            }
        }

        return increment;
    }

    /**
     * Components that display logical rows or columns should compute the scroll increment that will completely expose
     * one block of rows or columns, depending on the value of orientation.
     * <p/>
     * Scrolling containers, like JScrollPane, will use this method each time the user requests a block scroll.
     *
     * @param visibleRect The view area visible within the viewport
     * @param orientation Either SwingConstants.VERTICAL or SwingConstants.HORIZONTAL.
     * @param direction   Less than zero to scroll up/left, greater than zero for down/right.
     *
     * @return The "block" increment for scrolling in the specified direction. This value should always be positive.
     *
     * @see javax.swing.JScrollBar#setBlockIncrement
     */
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
    {
        int px = visibleRect.x;
        int py = visibleRect.y;
        int he = getHeight();
        int maxy = he - visibleRect.height;
        Component started = findComponentAt(px, py);

        int inc = 0;
        boolean found = false;

        if (direction > 0)
        {
            for (int y = py + 1; !found && y < maxy; y++)
            {
                Component comp = findComponentAt(px, y);
                if (comp != started && comp instanceof ResultGroupPanel)
                {
                    found = true;
                    inc = getIncrementForBorderChild(-direction, px, y) + (y - py) - 2;
                } else
                {
                    // Jump to the end of the item
                    y += getIncrementForBorderChild(direction, px, y);
                }
            }
        } else
        {
            for (int y = py - 1; !found && y >= 0; y--)
            {
                Component comp = findComponentAt(px, y);
                if (comp != started && comp instanceof ResultGroupPanel)
                {
                    found = true;
                    inc = getIncrementForBorderChild(direction, px, y) + (py - y);
                } else
                {
                    // Jump to the end of the item
                    y -= getIncrementForBorderChild(direction, px, y);
                }
            }
        }

        return inc;
    }

    /**
     * Return true if a viewport should always force the width of this <code>Scrollable</code> to match the width of the
     * viewport. For example a normal text view that supported line wrapping would return true here, since it would be
     * undesirable for wrapped lines to disappear beyond the right edge of the viewport.  Note that returning true for a
     * Scrollable whose ancestor is a JScrollPane effectively disables horizontal scrolling.
     * <p/>
     * Scrolling containers, like JViewport, will use this method each time they are validated.
     *
     * @return True if a viewport should force the Scrollables width to match its own.
     */
    public boolean getScrollableTracksViewportWidth()
    {
        return true;
    }

    /**
     * Return true if a viewport should always force the height of this Scrollable to match the height of the viewport.
     * For example a columnar text view that flowed text in left to right columns could effectively disable vertical
     * scrolling by returning true here.
     * <p/>
     * Scrolling containers, like JViewport, will use this method each time they are validated.
     *
     * @return True if a viewport should force the Scrollables height to match its own.
     */
    public boolean getScrollableTracksViewportHeight()
    {
        return false;
    }

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

    /**
     * Processes mouse events.
     *
     * @param e event.
     */
    protected void processMouseEvent(MouseEvent e)
    {
        if (!SwingUtilities.isLeftMouseButton(e)) return;

        Component c = getComponentAt(e.getPoint());
        if (!(c instanceof ResultItemPanel)) return;

        switch (e.getID())
        {
            case MouseEvent.MOUSE_PRESSED:
                onItemSelected((ResultItemPanel)c);
                break;

            case MouseEvent.MOUSE_CLICKED:
                if (e.getClickCount() == 2) onItemFired();
                break;
        }
    }

    /**
     * Processes navigation events.
     *
     * @param e event.
     */
    protected void processKeyEvent(KeyEvent e)
    {
        if (e.getID() != KeyEvent.KEY_PRESSED) return;

        int code = e.getKeyCode();

        switch (code)
        {
            case KeyEvent.VK_UP:
                onPrevItemSelected();
                break;

            case KeyEvent.VK_DOWN:
                onNextItemSelected();
                break;

            case KeyEvent.VK_ENTER:
                onItemFired();
                break;

            case KeyEvent.VK_Q:
                onItemToggleReadState();
                break;

            case KeyEvent.VK_P:
                onItemTogglePinState();
                break;

            case KeyEvent.VK_PAGE_UP:
                Rectangle visible = getVisibleRect();
                visible.y = Math.max(0, visible.y - visible.height);
                scrollRectToVisible(visible);
                break;

            case KeyEvent.VK_PAGE_DOWN:
                visible = getVisibleRect();
                visible.y = Math.max(0, Math.min(getHeight() - visible.height, visible.y + visible.height));
                scrollRectToVisible(visible);
                break;

            case KeyEvent.VK_HOME:
                visible = getVisibleRect();
                visible.y = 0;
                scrollRectToVisible(visible);
                break;

            case KeyEvent.VK_END:
                visible = getVisibleRect();
                visible.y = Math.max(0, getHeight() - visible.height);
                scrollRectToVisible(visible);
                break;
        }
    }
}
TOP

Related Classes of com.salas.bb.search.ResultsList

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.