// 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.views.feeds.twitter;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.uifextras.util.PopupAdapter;
import com.jgoodies.uif.action.ActionManager;
import com.salas.bb.domain.IArticle;
import com.salas.bb.views.feeds.AbstractFeedDisplay;
import com.salas.bb.views.feeds.IArticleDisplay;
import com.salas.bb.views.feeds.html.ArticlesGroup;
import com.salas.bb.views.feeds.html.IArticleDisplayConfig;
import com.salas.bb.views.feeds.html.IHTMLFeedDisplayConfig;
import com.salas.bb.views.mainframe.MainFrame;
import com.salas.bb.core.GlobalController;
import com.salas.bb.core.actions.ActionsTable;
import com.salas.bb.twitter.*;
import com.salas.bb.utils.i18n.Strings;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.ActionEvent;
import java.util.logging.Logger;
import java.util.Map;
import java.util.Collections;
import java.util.WeakHashMap;
import java.net.URL;
import java.io.IOException;
/**
* Twitter feed display.
*/
public class TwitterFeedDisplay extends AbstractFeedDisplay
{
private static final Map<String, String> USER_INFO_CACHE =
Collections.synchronizedMap(new WeakHashMap<String, String>());
private static final Map<String, String> SEARCH_CACHE =
Collections.synchronizedMap(new WeakHashMap<String, String>());
private static final Logger LOG = Logger.getLogger(TwitterFeedDisplay.class.getName());
private IHTMLFeedDisplayConfig htmlConfig;
private PopupAdapter userLinkPopupAdapter;
private PopupAdapter hashtagLinkPopupAdapter;
/**
* Abstract view.
*
* @param aConfig display configuration.
* @param pageModel page model to update when page changes.
* @param pageCountModel page model with the number of pages (updated by the FeedDisplayModel).
*/
public TwitterFeedDisplay(IHTMLFeedDisplayConfig aConfig, ValueModel pageModel, ValueModel pageCountModel)
{
super(aConfig, pageModel, pageCountModel);
htmlConfig = aConfig;
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
for (ArticlesGroup group : groups) add(group);
add(noContentPanel);
enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK);
}
/**
* Returns current logger.
*
* @return logger object.
*/
protected Logger getLogger()
{
return LOG;
}
/**
* Creates new article display for addition to the display.
*
* @param aArticle article to create display for.
*
* @return display.
*/
protected IArticleDisplay createNewArticleDisplay(IArticle aArticle)
{
IArticleDisplayConfig articleConfig = htmlConfig.getArticleViewConfig();
return new TwitterArticleDisplay(aArticle, articleConfig);
}
/**
* Returns the view popup adapter.
*
* @return view popup adapter.
*/
protected MouseListener getViewPopupAdapter()
{
return GlobalController.SINGLETON.getMainFrame().getHTMLDisplayPopupAdapter();
}
/**
* Returns the link popup adapter.
*
* @return link popup adapter.
*/
protected MouseListener getLinkPopupAdapter()
{
MouseListener listener = null;
if (TwitterFeature.areAdvancedFeaturesAvailable())
{
listener = isUserLink(hoveredLink) ? getArticleUserLinkPopupAdapter()
: isHashtagLink(hoveredLink) ? getHashtagLinkPopupAdapter() : null;
}
return listener != null ? listener : htmlConfig.getLinkPopupAdapter();
}
/**
* Returns TRUE if it's the user link that is hovered.
*
* @param link link.
*
* @return TRUE if it is.
*/
private boolean isUserLink(URL link)
{
if (link == null) return false;
String urls = link.toString();
return urls.matches("^http://(www\\.)?twitter\\.com/[^/]+($|\\?|#)");
}
/**
* Returns TRUE if it's the hashtag link that is hovered.
*
* @param link link.
*
* @return TRUE if it is.
*/
private boolean isHashtagLink(URL link)
{
if (link == null) return false;
String urls = link.toString();
return urls.matches("^http://search\\.twitter\\.com/search(\\.(json|atom))?\\?q=(#|%23)[^&]+");
}
/**
* Returns popup adapter for article user hyper-links.
*
* @return popup adapter.
*/
public synchronized PopupAdapter getArticleUserLinkPopupAdapter()
{
if (userLinkPopupAdapter == null)
{
userLinkPopupAdapter = new PopupAdapter()
{
protected JPopupMenu buildPopupMenu(MouseEvent anevent)
{
GlobalController controller = GlobalController.SINGLETON;
MainFrame frame = controller.getMainFrame();
JPopupMenu menu = frame.createNonLockingPopupMenu("User Link");
FollowAction actFollow = FollowAction.getInstance();
SubscribeAction actSubscribe = (SubscribeAction)ActionManager.get(ActionsTable.CMD_TWITTER_SUBSCRIBE);
// Set links to the actions as the hovered link will be reset upon
// the menu opening as the mouse pointer will move away off the link.
URL link = controller.getHoveredHyperLink();
actFollow.setUserURL(link);
ReplyAction.getInstance().setUserURL(link);
actSubscribe.setUserURL(link);
if (actFollow.isAvailable()) menu.add(actFollow);
menu.add(ActionManager.get(ActionsTable.CMD_TWITTER_MESSAGE));
menu.add(actSubscribe);
return menu;
}
};
}
return userLinkPopupAdapter;
}
/**
* Returns popup adapter for article user hashtag hyper-links.
*
* @return popup adapter.
*/
public synchronized PopupAdapter getHashtagLinkPopupAdapter()
{
if (hashtagLinkPopupAdapter == null)
{
hashtagLinkPopupAdapter = new PopupAdapter()
{
protected JPopupMenu buildPopupMenu(MouseEvent anevent)
{
GlobalController controller = GlobalController.SINGLETON;
MainFrame frame = controller.getMainFrame();
JPopupMenu menu = frame.createNonLockingPopupMenu("Hashtag Link");
SubscribeAction action = (SubscribeAction)ActionManager.get(ActionsTable.CMD_TWITTER_SUBSCRIBE);
// Set links to the actions as the hovered link will be reset upon
// the menu opening as the mouse pointer will move away off the link.
URL link = controller.getHoveredHyperLink();
action.setUserURL(link);
menu.add(action);
return menu;
}
};
}
return hashtagLinkPopupAdapter;
}
/**
* Returns tool-tip for a give link.
*
* @param link link.
* @param textPane pane requesting the tooltip.
*
* @return tool-tip text.
*/
protected String getHoveredLinkTooltip(URL link, final JComponent textPane)
{
if (link == null || !TwitterFeature.areAdvancedFeaturesAvailable()) return null;
return isUserLink(link) ? getUserInfoTooltipText(link, textPane)
: isHashtagLink(link) ? getHashtagTooltipText(link, textPane)
: null;
}
/**
* Returns the search info tooltip text and schedules the async
*
* @param link link.
* @param textPane pane.
*
* @return text.
*/
private String getHashtagTooltipText(URL link, final JComponent textPane)
{
final String hashtag = TwitterGateway.urlToHashtag(link);
String html = SEARCH_CACHE.get(hashtag);
if (html == null)
{
new Thread("Twitter Hashtag Search")
{
public void run()
{
String html;
try
{
html = TwitterGateway.search("#" + hashtag);
} catch (IOException e)
{
html = Strings.message("twitter.unavailable");
}
cacheAndSetHashtag(html, hashtag, textPane);
}
}.start();
}
return html;
}
/**
* Returns the user info tooltip text and schedules the async loading if necessary.
*
* @param link link.
* @param textPane text pane.
*
* @return text.
*/
private String getUserInfoTooltipText(URL link, final JComponent textPane)
{
final String screenName = TwitterGateway.urlToScreenName(link);
String info = USER_INFO_CACHE.get(screenName);
if (info == null)
{
new Thread("Twitter User Info")
{
public void run()
{
String userInfo;
try
{
userInfo = TwitterGateway.userInfoHTML(screenName);
} catch (IOException e)
{
userInfo = Strings.message("twitter.unavailable");
}
cacheAndSetUserInfo(userInfo, screenName, textPane);
}
}.start();
}
return info;
}
/**
* Places the info in cache and shows the tooltip.
*
* @param userInfo info HTML.
* @param screenName screen name.
* @param textPane text pane component.
*/
private void cacheAndSetUserInfo(final String userInfo, final String screenName, final JComponent textPane)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
USER_INFO_CACHE.put(screenName, userInfo);
showTooltip(textPane, userInfo);
}
});
}
/**
* Places the results in cache and shows the tooltip.
*
* @param html search HTML.
* @param hashtag hash tag.
* @param textPane pane.
*/
private void cacheAndSetHashtag(final String html, final String hashtag, final JComponent textPane)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
SEARCH_CACHE.put(hashtag, html);
showTooltip(textPane, html);
}
});
}
/**
* Makes the tooltip re-appear with the given text.
*
* @param textPane pane.
* @param text text.
*/
private static void showTooltip(JComponent textPane, String text)
{
// Action hideTip = textPane.getActionMap().get("hideTip");
// if (hideTip != null) hideTip.actionPerformed(new ActionEvent(textPane, ActionEvent.ACTION_PERFORMED, "hideTip"));
textPane.setToolTipText(text);
Action postTip = textPane.getActionMap().get("postTip");
if (postTip != null) postTip.actionPerformed(new ActionEvent(textPane, ActionEvent.ACTION_PERFORMED, "postTip"));
}
}