Package com.salas.bb.core

Source Code of com.salas.bb.core.NavigatorAdv$Destination

// 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: NavigatorAdv.java,v 1.14 2008/03/17 12:23:06 spyromus Exp $
//

package com.salas.bb.core;

import com.salas.bb.domain.GuidesSet;
import com.salas.bb.domain.IArticle;
import com.salas.bb.domain.IFeed;
import com.salas.bb.domain.IGuide;
import com.salas.bb.utils.i18n.Strings;
import com.salas.bb.views.feeds.IFeedDisplayConstants;

import java.text.MessageFormat;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* This class is intended to provide navigation services. It drives the process of
* switching between channels and guides on user's demand. It finds next or previous
* targets for switching and makes this switching using <code>GlobalController</code>
* services.
*/
public class NavigatorAdv extends ControllerAdapter
{
    private static final Logger LOG = Logger.getLogger(NavigatorAdv.class.getName());

    private GuideModel       navigationModel;
    private GuideModel       viewModel;

    private final GuidesListModel guidesListModel;
    private GuidesSet               guidesSet;

    private IGuide                  selectedGuide;
    private IFeed                   selectedFeed;

    /**
     * Creates navigator for the model.
     *
     * @param aNavigationModel  navigation model used for view.
     * @param guidesListModel   guides list model.
     */
    public NavigatorAdv(GuideModel aNavigationModel, GuidesListModel guidesListModel)
    {
        navigationModel = aNavigationModel;
        this.guidesListModel = guidesListModel;

        viewModel = null;
    }

    /**
     * Sets the view model that will be used for navavigation through visible guide. If this
     * model isn't set then the regular model will be used.
     *
     * @param aViewModel view model.
     */
    public void setViewModel(GuideModel aViewModel)
    {
        viewModel = aViewModel;
    }

    /**
     * Sets the set of gudies to refer to when looking for next/previous guide.
     *
     * @param set guide set.
     */
    public void setGuidesSet(GuidesSet set)
    {
        guidesSet = set;
    }

    /**
     * Invoked after application changes the guide.
     *
     * @param guide guide to with we have switched.
     */
    public synchronized void guideSelected(IGuide guide)
    {
        selectedGuide = guide;
        selectedFeed = null;
    }

    /**
     * Invoked after application changes the channel.
     *
     * @param feed channel to which we are switching.
     */
    public synchronized void feedSelected(IFeed feed)
    {
        selectedFeed = feed;
    }

    /**
     * Returns destination feed for the given action key.
     *
     * @param key   key.
     *
     * @return feed or NULL if there's no destination.
     */
    public synchronized Destination getDestination(NavigationInfoKey key)
    {
        Destination dest;

        if (key instanceof NavigationInfoKey.Next)
        {
            boolean unreadOnly = key == NavigationInfoKey.NEXT_UNREAD;

            dest = recalcNext(unreadOnly);
        } else
        {
            boolean unreadOnly = key == NavigationInfoKey.PREV_UNREAD;

            dest = recalcPrev(unreadOnly);
        }

        return dest;
    }

    /**
     * Called by <code>Calculator</code> in separate thread.
     *
     * @param unreadOnly    if TRUE feeds with unread articles only will be returned.
     *                                                             
     * @return destination for "Next" command.
     */
    private synchronized Destination recalcNext(boolean unreadOnly)
    {
        IFeed destFeed = null;
        IFeed feed;
        IGuide guide;
        GuideModel model;

        feed = selectedFeed;
        guide = selectedGuide;
        if (guide == null) return null;

        // We are ready to start

        int pass = 0;
        while (destFeed == null && pass < 2)
        {
            // Get model for the guide
            if (guide != null && isVisible(guide))
            {
                model = getModel(guide);

                feed = getNextInGuide(model, feed, unreadOnly);
            }

            // If feed isn't empty then we probably found what we needed.
            if (feed != null)
            {
                destFeed = feed;
            } else
            {
                // We increment pass counter each time the guide is what we started with.
                // On the leaving the guide for the second time we will break the loop.
                if (guide == selectedGuide) pass++;

                guide = getNextGuide(guide);
            }
        }

        return destFeed == null ? null : new Destination(guide, destFeed);
    }

    /**
     * Called by <code>Calculator</code> in separate thread.
     *
     * @param unreadOnly    if TRUE feeds with unread articles only will be returned.
     *
     * @return destination for "Previous" command.
     */
    private synchronized Destination recalcPrev(boolean unreadOnly)
    {
        IFeed destFeed = null;
        IFeed feed;
        IGuide guide;
        GuideModel model;

        feed = selectedFeed;
        guide = selectedGuide;
        if (guide == null) return null;

        // We are ready to start

        int pass = 0;
        while (destFeed == null && pass < 2)
        {
            // Get model for the guide
            if (guide != null && isVisible(guide))
            {
                model = getModel(guide);

                feed = getPrevInGuide(model, feed, unreadOnly);
            }

            // If feed isn't empty then we probably found what we needed.
            if (feed != null)
            {
                destFeed = feed;
            } else
            {
                // We increment pass counter each time the guide is what we started with.
                // On the leaving the guide for the second time we will break the loop.
                if (guide == selectedGuide) pass++;

                guide = getPrevGuide(guide);
            }
        }

        return destFeed == null ? null : new Destination(guide, destFeed);
    }

    private boolean isVisible(IGuide guide)
    {
        return guidesListModel == null || guidesListModel.indexOf(guide) != -1;
    }

    /**
     * Returns mode initialized for the guide.
     *
     * @param guide guide.
     *
     * @return model.
     */
    private GuideModel getModel(IGuide guide)
    {
        GuideModel model;

        if (guide == selectedGuide && viewModel != null)
        {
            model = viewModel;
        } else
        {
            model = navigationModel;
            navigationModel.setGuide(guide);
        }

        return model;
    }

    /**
     * Looks for the feed, which is next in relation to <code>currentFeed</code> in the given
     * <code>model</code>. If <code>unreadOnly</code> or <code>keywordsOnly</code> properties set
     * then only appropriate feeds will be choosen.
     *
     * @param model         model to analyze.
     * @param currentFeed   current feed to start searching from or NULL if to start from the start.
     * @param unreadOnly    if TRUE feeds with unread articles only will be returned.
     *
     * @return next feed, <code>stopFeed</code> or NULL (if next feed not found).
     */
    IFeed getNextInGuide(GuideModel model, IFeed currentFeed, boolean unreadOnly)
    {
        int size = model.getSize();
        int index = currentFeed == null ? -1 : model.indexOf(currentFeed);

        IFeed next = null;

        if (currentFeed != null && index == -1)
        {
            LOG.log(Level.SEVERE, MessageFormat.format(Strings.error("feed.does.not.belong.to.model.feed"),
                currentFeed));
        } else
        {
            for (int i = index + 1; next == null && i < size; i++)
            {
                next = (IFeed)model.getElementAt(i);

                // Make sure feed matches expectations according to the unreadOnly parameter
                if (!isFeedMatching(next, unreadOnly, getArticleFilter())) next = null;
            }
        }

        return next;
    }

    /**
     * Looks for the feed, which is previous in relation to <code>currentFeed</code> in the given
     * <code>model</code>. If <code>unreadOnly</code> or <code>keywordsOnly</code> properties set
     * then only appropriate feeds will be choosen.
     *
     * @param model         model to analyze.
     * @param currentFeed   current feed to start searching from or NULL if to start from the start.
     * @param unreadOnly    if TRUE feeds with unread articles only will be returned.
     *
     * @return previous feed, <code>stopFeed</code> or NULL (if next feed not found).
     */
    IFeed getPrevInGuide(GuideModel model, IFeed currentFeed, boolean unreadOnly)
    {
        int size = model.getSize();
        int index = currentFeed == null ? size : model.indexOf(currentFeed);

        IFeed prev = null;

        if (currentFeed != null && index == -1)
        {
            LOG.log(Level.SEVERE, MessageFormat.format(Strings.error("feed.does.not.belong.to.model.feed"),
                currentFeed));
        } else
        {
            for (int i = index - 1; prev == null && i >= 0; i--)
            {
                prev = (IFeed)model.getElementAt(i);

                // Make sure feed matches expectations according to the unreadOnly parameter
                if (!isFeedMatching(prev, unreadOnly, getArticleFilter())) prev = null;
            }
        }

        return prev;
    }

    /**
     * Returns TRUE if the feed matches the selection criteria.
     *
     * @param feed          feed.
     * @param unreadOnly    unread-only flag.
     * @param filter        article filtering mode (IFeedDisplayConstants.FILTER_XYZ).
     *
     * @return TRUE if the feed matches the selection criteria.
     */
    static boolean isFeedMatching(IFeed feed, boolean unreadOnly, int filter)
    {
        boolean hasVisibleArticles = false;

        int count = feed.getArticlesCount();
        if (count > 0)
        {
            if (filter == IFeedDisplayConstants.FILTER_UNREAD)
            {
                // If showing unread, this will be sufficient
                hasVisibleArticles = feed.getUnreadArticlesCount() > 0;
            } else if (filter == IFeedDisplayConstants.FILTER_ALL)
            {
                // If showing all, this will be sufficient
                hasVisibleArticles = !unreadOnly || feed.getUnreadArticlesCount() > 0;
            } else
            {
                // For all other filters, iterate until all articles are scanned, or
                // the first visible is found.
                for (int i = 0; !hasVisibleArticles && i < count; i++)
                {
                    IArticle article = feed.getArticleAt(i);
                    if (!unreadOnly || !article.isRead())
                    {
                        // If read state match, see others
                        switch (filter)
                        {
                            case IFeedDisplayConstants.FILTER_PINNED:
                                hasVisibleArticles = article.isPinned();
                                break;

                            case IFeedDisplayConstants.FILTER_NEGATIVE:
                                hasVisibleArticles = article.isNegative();
                                break;

                            case IFeedDisplayConstants.FILTER_POSITIVE:
                                hasVisibleArticles = article.isPositive();
                                break;

                            case IFeedDisplayConstants.FILTER_NON_NEGATIVE:
                                hasVisibleArticles = !article.isNegative();
                                break;
                        }
                    }
                }
            }
        }

        return hasVisibleArticles;
    }

    /**
     * Returns guide which is next to the current in the channel guides set.
     *
     * @param currentGuide  current guide.
     *
     * @return next guide.
     */
    IGuide getNextGuide(IGuide currentGuide)
    {
        IGuide next = currentGuide;

        if (guidesSet == null)
        {
            LOG.warning(Strings.error("guide.set.not.registered"));
        } else
        {
            next = nextGuide(currentGuide);
        }

        return next;
    }

    private IGuide nextGuide(IGuide aCurrentGuide)
    {
        int index = aCurrentGuide == null ? -1 : guidesSet.indexOf(aCurrentGuide);
        int next = index + 1;
        int size = guidesSet.getGuidesCount();

        return guidesSet.getGuideAt(next >= size ? 0 : next);
    }

    /**
     * Returns guide which is previous to the current in the channel guides set.
     *
     * @param currentGuide  current guide.
     *
     * @return previous guide.
     */
    IGuide getPrevGuide(IGuide currentGuide)
    {
        IGuide prev = currentGuide;

        if (guidesSet == null)
        {
            LOG.warning(Strings.error("guide.set.not.registered"));
        } else
        {
            prev = prevChannelGuide(currentGuide);
        }

        return prev;
    }

    private IGuide prevChannelGuide(IGuide aCurrentGuide)
    {
        int size = guidesSet.getGuidesCount();
        int index = aCurrentGuide == null ? size : guidesSet.indexOf(aCurrentGuide);
        int prev = index - 1;

        return guidesSet.getGuideAt(prev < 0 ? size - 1 : prev);
    }

    /**
     * Returns current article filter.
     *
     * @return filter.
     */
    private static int getArticleFilter()
    {
        return GlobalModel.SINGLETON.getGlobalRenderingSettings().getArticleFilter();
    }

    // ------------------------------------------------------------------------
    // Supplementary classes
    // ------------------------------------------------------------------------

    /**
     * Navigation destination.
     */
    public static class Destination
    {
        public IGuide   guide;
        public IFeed    feed;

        /**
         * Creates holder.
         *
         * @param aGuide    guide.
         * @param aFeed     feed.
         */
        public Destination(IGuide aGuide, IFeed aFeed)
        {
            guide = aGuide;
            feed = aFeed;
        }
    }

    /**
     * Keys for navigation info recalculation.
     */
    public static interface NavigationInfoKey
    {
        /**
         * Next.
         */
        NavigationInfoKey NEXT                  = new Next();

        /**
         * Next (unread only).
         */
        NavigationInfoKey NEXT_UNREAD           = new Next();

        /**
         * Previous.
         */
        NavigationInfoKey PREV                  = new Prev();

        /**
         * Previous (unread only).
         */
        NavigationInfoKey PREV_UNREAD           = new Prev();

        /**
         * Marker class for next-operations.
         */
        static final class Next implements NavigationInfoKey
        {
        }

        /**
         * Marker class for previous-operations.
         */
        static final class Prev implements  NavigationInfoKey
        {
        }
    }
}
TOP

Related Classes of com.salas.bb.core.NavigatorAdv$Destination

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.