// 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 $ // package com.salas.bb.core; import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean; import com.salas.bb.domain.*; import com.salas.bb.domain.events.FeedRemovedEvent; import com.salas.bb.domain.prefs.UserPreferences; import com.salas.bb.domain.utils.DomainAdapter; import com.salas.bb.utils.osx.DockIcon; import javax.swing.*; import java.awt.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashSet; /** * Monitors unread counts and updates dock icon with badge. */ public class DockIconUnreadMonitor extends DomainAdapter implements PropertyChangeListener, IDisplayModeManagerListener { /** Show no badge. */ public static final int MODE_OFF = 0; /** Show the number of unread articles. */ public static final int MODE_ARTICLES = 1; /** Show the number of unread feeds. */ public static final int MODE_FEEDS = 2; // Current mode private int mode = MODE_ARTICLES; private GuidesSet set; /** * We use this boolean to avoid scheduling more updates when the previous one isn't * performed yet. It's good to know to avoid excessive database usage. */ private SynchronizedBoolean dockIconUpdateArmed = new SynchronizedBoolean(false); private Monitor monitor = new Monitor(); /** * Registers guides set to operate. * * @param set set. */ public void setSet(GuidesSet set) { this.set = set; update(); } /** * Gets mode of operation. * * @return mode of operation. * * @see #MODE_OFF * @see #MODE_ARTICLES * @see #MODE_FEEDS */ public int getMode() { return mode; } /** * Sets mode of operation. * * @param mode mode of operation. * * @see #MODE_OFF * @see #MODE_ARTICLES * @see #MODE_FEEDS */ public void setMode(int mode) { if (this.mode != mode) { this.mode = mode; update(); } } /** * Invoked when the property of the feed has been changed. * * @param feed feed. * @param property property of the feed. * @param oldValue old property value. * @param newValue new property value. */ public void propertyChanged(IFeed feed, String property, Object oldValue, Object newValue) { if (IFeed.PROP_UNREAD_ARTICLES_COUNT.equals(property)) { int oldv = ((Integer)oldValue).intValue(); int newv = ((Integer)newValue).intValue(); if (mode == MODE_ARTICLES || (mode == MODE_FEEDS && (oldv == 0 || newv == 0))) { update(); } } } /** * Invoked when the feed has been removed from the guide. * * @param event feed removal event. */ public void feedRemoved(FeedRemovedEvent event) { if (event.isLastEvent() && mode != MODE_OFF) update(); } /** * Invoked when the feed has been removed from the guide. * * @param guide parent guide. * @param feed removed feed. * @param index index of removed feed. */ public void feedRemoved(IGuide guide, IFeed feed, int index) { if (mode != MODE_OFF) update(); } /** * Invoked when user preferences property changes. * * @param evt event. */ public void propertyChange(PropertyChangeEvent evt) { if (!UserPreferences.FEED_VISIBILITY_PROPERTIES.contains(evt.getPropertyName())) return; // User preferences will affect unread counts, if the starz filter changes // or if feeds below the threshhold are changed to show or hide. // As a simple and conservative response, just repaint the guide list // entirely, and update the unread button. update(); } /** * Invoked when the color of feed changes. * * @param feedClass feed class. * @param oldColor old color. * @param newColor new color. */ public void onClassColorChanged(int feedClass, Color oldColor, Color newColor) { // Changes to feed display colors will affect unread counts if the // "hidden" color option is used to hide or show feeds. // For simplicity, just repaint all the guides entirely and update the // unread button. if (oldColor == null || newColor == null) update(); } /** * Updates the badge. Turns / on and off too. */ public synchronized void update() { // There's no need to schedule more updates if the previous one didn't happen yet if (dockIconUpdateArmed.get()) return; // Arm and call dockIconUpdateArmed.set(true); SwingUtilities.invokeLater(new Runnable() { public void run() { // Disarm to let the next update be scheduled dockIconUpdateArmed.set(false); // Update the badge int unread = getUnreadItemsCount(); DockIcon.setBadgeCounter(unread); } }); } /** * Gets the number of unread items (0 in <code>MODE_OFF</code>). * * @return the number of unread items. */ private int getUnreadItemsCount() { int unread = 0; if (set != null) { StandardGuide[] guides = set.getStandardGuides(null); HashSet checkedFeeds = new HashSet(); for (int i = 0; i < guides.length; i++) { StandardGuide guide = guides[i]; IFeed[] feeds = GlobalModel.SINGLETON.getVisibleFeeds(guide); for (int j = 0; j < feeds.length; j++) { IFeed feed = feeds[j]; if (checkedFeeds.contains(feed)) continue; checkedFeeds.add(feed); if (mode == MODE_ARTICLES && (feed instanceof DataFeed)) { unread += feed.getUnreadArticlesCount(); } else if (mode == MODE_FEEDS && !feed.isRead()) { unread++; } } } } return unread; } /** * Returns controller listener. * * @return controller listener. */ public IControllerListener getMonitor() { return monitor; } /** * Updates the badge when unread data feed deselection is detected. The * deselected feed can potentially become invisible and it's necessary to check. */ private class Monitor extends UnreadDataFeedDeselectionMonitor { /** * Invoked when unread data feed deselection detected. */ protected void unreadDataFeedDeselected() { // We call it in the other EDT event to let the view recalculate itself // before asking for visibility status SwingUtilities.invokeLater(new Runnable() { public void run() { update(); } }); } } }