/**
* $RCSfile: ,v $
* $Revision: $
* $Date: $
*
* Copyright (C) 2004-2011 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.spark.ui.conferences;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TimerTask;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.bookmark.BookmarkManager;
import org.jivesoftware.smackx.bookmark.BookmarkedConference;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import org.jivesoftware.smackx.packet.DiscoverItems;
import org.jivesoftware.smackx.packet.DiscoverInfo.Identity;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.component.JiveTreeCellRenderer;
import org.jivesoftware.spark.component.JiveTreeNode;
import org.jivesoftware.spark.component.RolloverButton;
import org.jivesoftware.spark.component.Tree;
import org.jivesoftware.spark.plugin.ContextMenuListener;
import org.jivesoftware.spark.util.GraphicUtils;
import org.jivesoftware.spark.util.ResourceUtils;
import org.jivesoftware.spark.util.SwingWorker;
import org.jivesoftware.spark.util.TaskEngine;
import org.jivesoftware.spark.util.log.Log;
/**
* BookmarkedConferences is used to display the UI for all bookmarked conference rooms.
*/
public class BookmarksUI extends JPanel {
private static final long serialVersionUID = -315974309284551232L;
private Tree tree;
private JiveTreeNode rootNode;
private Collection<String> mucServices;
private Set<String> autoJoinRooms = new HashSet<String>();
private List<ContextMenuListener> listeners = new ArrayList<ContextMenuListener>();
/**
* Bookmarks listeners
*/
private List<BookmarksListener> bookmarkListeners = new ArrayList<BookmarksListener>();
private BookmarkManager manager;
/**
*
*/
public BookmarksUI() {
}
/**
* Initialize Conference UI.
*/
public void loadUI() {
setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
setLayout(new GridBagLayout());
add(getServicePanel(), new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0));
rootNode = new JiveTreeNode("Conference Services");
tree = new Tree(rootNode) {
private static final long serialVersionUID = -8445572224948613446L;
@Override
protected void setExpandedState(TreePath path, boolean state) {
// Ignore all collapse requests; collapse events will not be fired
if (state) {
super.setExpandedState(path, state);
}
}
};
tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent mouseEvent) {
tree.setCursor(GraphicUtils.HAND_CURSOR);
}
@Override
public void mouseExited(MouseEvent mouseEvent) {
tree.setCursor(GraphicUtils.DEFAULT_CURSOR);
}
});
JScrollPane scrollPane = new JScrollPane(tree);
add(scrollPane, new GridBagConstraints(0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
// Add all registered services.
addRegisteredServices();
tree.setCellRenderer(new JiveTreeCellRenderer());
tree.putClientProperty("JTree.lineStyle", "None");
tree.setRootVisible(false);
tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent mouseEvent) {
if (mouseEvent.getClickCount() == 2) {
TreePath path = tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY());
if (path == null) {
return;
}
JiveTreeNode node = (JiveTreeNode)path.getLastPathComponent();
if (node != null && node.getAllowsChildren()) {
browseRooms((String)node.getUserObject());
}
else if (node != null) {
String roomJID = node.getAssociatedObject().toString();
ConferenceUtils.joinConferenceOnSeperateThread(node.getUserObject().toString(), roomJID, null);
}
}
}
@Override
public void mouseReleased(MouseEvent mouseEvent) {
checkPopup(mouseEvent);
}
@Override
public void mousePressed(MouseEvent mouseEvent) {
checkPopup(mouseEvent);
}
}
);
setBackground(Color.white);
try {
manager = BookmarkManager.getBookmarkManager(SparkManager.getConnection());
}
catch (XMPPException e) {
Log.error(e);
}
final TimerTask bookmarkTask = new TimerTask() {
@Override
public void run() {
Collection<BookmarkedConference> bc = null;
while(bc == null){
try {
bc = manager.getBookmarkedConferences();
}catch (XMPPException error) {
Log.error(error);
}
}
setBookmarks(bc);
}
};
TaskEngine.getInstance().schedule(bookmarkTask, 5000);
}
private void checkPopup(MouseEvent mouseEvent) {
// Handle no path for x y coordinates
if (tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY()) == null) {
return;
}
final JiveTreeNode node = (JiveTreeNode)tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY()).getLastPathComponent();
if (mouseEvent.isPopupTrigger() && node != null) {
JPopupMenu popupMenu = new JPopupMenu();
// Define service actions
Action browseAction = new AbstractAction() {
private static final long serialVersionUID = -8866708581713789939L;
@Override
public void actionPerformed(ActionEvent actionEvent) {
browseRooms(node.toString());
}
};
browseAction.putValue(Action.NAME, Res.getString("menuitem.browse.service"));
browseAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DATA_FIND_IMAGE));
Action removeServiceAction = new AbstractAction() {
private static final long serialVersionUID = -5276754429117462223L;
@Override
public void actionPerformed(ActionEvent actionEvent) {
DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel();
treeModel.removeNodeFromParent(node);
}
};
removeServiceAction.putValue(Action.NAME, Res.getString("menuitem.remove.service"));
removeServiceAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DELETE));
JMenuItem browseServiceMenu = new JMenuItem(browseAction);
JMenuItem removeServiceMenu = new JMenuItem(removeServiceAction);
// Define room actions
Action joinRoomAction = new AbstractAction() {
private static final long serialVersionUID = -356016505214728244L;
@Override
public void actionPerformed(ActionEvent actionEvent) {
String roomName = node.getUserObject().toString();
String roomJID = node.getAssociatedObject().toString();
ConferenceUtils.joinConferenceOnSeperateThread(roomName, roomJID, null);
}
};
joinRoomAction.putValue(Action.NAME, Res.getString("menuitem.join.room"));
joinRoomAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_USER_ENTER));
Action removeRoomAction = new AbstractAction() {
private static final long serialVersionUID = -7560090091884746914L;
@Override
public void actionPerformed(ActionEvent actionEvent) {
DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel();
treeModel.removeNodeFromParent(node);
String roomJID = node.getAssociatedObject().toString();
autoJoinRooms.remove(roomJID);
removeBookmark(roomJID);
}
};
removeRoomAction.putValue(Action.NAME, Res.getString("menuitem.remove.bookmark"));
removeRoomAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.DELETE_BOOKMARK_ICON));
JMenuItem joinRoomMenu = new JMenuItem(joinRoomAction);
JMenuItem removeRoomMenu = new JMenuItem(removeRoomAction);
if (node.getAllowsChildren()) {
popupMenu.add(browseServiceMenu);
popupMenu.add(removeServiceMenu);
}
else {
popupMenu.add(joinRoomMenu);
popupMenu.add(removeRoomMenu);
popupMenu.addSeparator();
Action autoJoin = new AbstractAction() {
private static final long serialVersionUID = 7857469398581933449L;
@Override
public void actionPerformed(ActionEvent e) {
String roomJID = node.getAssociatedObject().toString();
if (autoJoinRooms.contains(roomJID)) {
autoJoinRooms.remove(roomJID);
}
else {
autoJoinRooms.add(roomJID);
}
String name = node.getUserObject().toString();
addBookmark(name, roomJID, autoJoinRooms.contains(roomJID));
}
};
autoJoin.putValue(Action.NAME, Res.getString("menuitem.join.on.startup"));
JCheckBoxMenuItem item = new JCheckBoxMenuItem(autoJoin);
String roomJID = node.getAssociatedObject().toString();
item.setSelected(autoJoinRooms.contains(roomJID));
popupMenu.add(item);
// Define service actions
Action roomInfoAction = new AbstractAction() {
private static final long serialVersionUID = -8336773839944003744L;
@Override
public void actionPerformed(ActionEvent actionEvent) {
String roomJID = node.getAssociatedObject().toString();
RoomBrowser roomBrowser = new RoomBrowser();
roomBrowser.displayRoomInformation(roomJID);
}
};
roomInfoAction.putValue(Action.NAME, Res.getString("menuitem.view.room.info"));
roomInfoAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DATA_FIND_IMAGE));
popupMenu.add(roomInfoAction);
}
// Fire menu listeners
fireContextMenuListeners(popupMenu, node);
// Display popup menu.
popupMenu.show(tree, mouseEvent.getX(), mouseEvent.getY());
}
}
public void browseRooms(String serviceName) {
ConferenceRoomBrowser rooms = new ConferenceRoomBrowser(this, serviceName);
rooms.invoke();
}
private void addRegisteredServices() {
SwingWorker worker = new SwingWorker() {
@Override
public Object construct() {
try {
if (SparkManager.getConnection().isConnected()) {
mucServices = MultiUserChat.getServiceNames(SparkManager.getConnection());
}
}
catch (XMPPException e) {
Log.error("Unable to load MUC Service Names.", e);
}
return mucServices;
}
@Override
public void finished() {
if (mucServices == null) {
return;
}
for (Object mucService : mucServices) {
String service = (String) mucService;
if (!hasService(service)) {
addServiceToList(service);
}
}
}
};
worker.start();
}
/**
* Adds a new service (ex. conferences@jabber.org) to the services list.
*
* @param service the new service.
* @return the new service node created.
*/
public JiveTreeNode addServiceToList(String service) {
final JiveTreeNode serviceNode = new JiveTreeNode(service, true, SparkRes.getImageIcon(SparkRes.SERVER_ICON));
rootNode.add(serviceNode);
final DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
model.nodeStructureChanged(rootNode);
// expand the tree for displaying
for (int i = 0; i <= tree.getRowCount(); i++) {
tree.expandPath(tree.getPathForRow(i));
}
return serviceNode;
}
/**
* Adds a new bookmark to a particular service node.
*
* @param serviceNode the service node.
* @param roomName the name of the room to bookmark.
* @param roomJID the jid of the room.
* @return the new bookmark created.
*/
public JiveTreeNode addBookmark(JiveTreeNode serviceNode, String roomName, String roomJID) {
JiveTreeNode roomNode = new JiveTreeNode(roomName, false, SparkRes.getImageIcon(SparkRes.BOOKMARK_ICON));
roomNode.setAssociatedObject(roomJID);
serviceNode.add(roomNode);
final DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
model.nodeStructureChanged(serviceNode);
return roomNode;
}
public void addBookmark(String roomName, String roomJID, boolean autoJoin) {
try {
if (autoJoin)
{
autoJoinRooms.add(roomJID);
}
manager.addBookmarkedConference(roomName, roomJID, autoJoin, null, null);
fireBookmarksAdded(roomJID); //fire bookmark event
}
catch (XMPPException e) {
Log.error(e);
}
}
public void removeBookmark(String roomJID) {
try {
if (autoJoinRooms.contains(roomJID))
{
autoJoinRooms.remove(roomJID);
}
manager.removeBookmarkedConference(roomJID);
fireBookmarksRemoved(roomJID); // fire bookmark remove event
}
catch (XMPPException e) {
Log.error(e);
}
}
private JPanel getServicePanel() {
final JPanel servicePanel = new JPanel();
servicePanel.setOpaque(false);
servicePanel.setLayout(new GridBagLayout());
final JLabel serviceLabel = new JLabel();
final RolloverButton addButton = new RolloverButton(SparkRes.getImageIcon(SparkRes.SMALL_ADD_IMAGE));
addButton.setToolTipText(Res.getString("message.add.conference.service"));
final JTextField serviceField = new JTextField();
servicePanel.add(serviceLabel, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 2, 5), 0, 0));
servicePanel.add(serviceField, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 2, 5), 0, 0));
servicePanel.add(addButton, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 2, 5), 0, 0));
// Add resource utils
ResourceUtils.resLabel(serviceLabel, serviceField, Res.getString("label.add.conference.service"));
final Action conferenceAction = new AbstractAction() {
private static final long serialVersionUID = 7973928300442518496L;
@Override
public void actionPerformed(ActionEvent e) {
final String conferenceService = serviceField.getText();
if (hasService(conferenceService)) {
JOptionPane.showMessageDialog(null, Res.getString("message.service.already.exists"), Res.getString("title.error"), JOptionPane.ERROR_MESSAGE);
serviceField.setText("");
}
else {
final List<String> serviceList = new ArrayList<String>();
serviceField.setText(Res.getString("message.searching.please.wait"));
serviceField.setEnabled(false);
addButton.setEnabled(false);
SwingWorker worker = new SwingWorker() {
DiscoverInfo discoInfo;
@Override
public Object construct() {
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection());
try {
discoInfo = discoManager.discoverInfo(conferenceService);
Iterator<Identity> iter = discoInfo.getIdentities();
while (iter.hasNext()) {
DiscoverInfo.Identity identity = (DiscoverInfo.Identity)iter.next();
if ("conference".equals(identity.getCategory())) {
serviceList.add(conferenceService);
break;
}
else if ("server".equals(identity.getCategory())) {
try {
Collection<String> services = getConferenceServices(conferenceService);
for (String service : services) {
serviceList.add(service);
}
}
catch (Exception e1) {
Log.error("Unable to load conference services in server.", e1);
}
}
}
}
catch (XMPPException e1) {
Log.error("Error in disco discovery.", e1);
}
return true;
}
@Override
public void finished() {
if (discoInfo != null) {
for (String aServiceList : serviceList) {
if (!hasService(aServiceList)) {
addServiceToList(aServiceList);
}
}
serviceField.setText("");
serviceField.setEnabled(true);
addButton.setEnabled(true);
}
else {
JOptionPane.showMessageDialog(SparkManager.getMainWindow(), Res.getString("message.conference.service.error"), Res.getString("title.error"), JOptionPane.ERROR_MESSAGE);
serviceField.setText("");
serviceField.setEnabled(true);
addButton.setEnabled(true);
}
}
};
worker.start();
}
}
};
addButton.addActionListener(conferenceAction);
serviceField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
conferenceAction.actionPerformed(null);
}
}
});
return servicePanel;
}
private Collection<String> getConferenceServices(String server) throws Exception {
List<String> answer = new ArrayList<String>();
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection());
DiscoverItems items = discoManager.discoverItems(server);
for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) {
DiscoverItems.Item item = (DiscoverItems.Item)it.next();
if (item.getEntityID().startsWith("conference") || item.getEntityID().startsWith("private")) {
answer.add(item.getEntityID());
}
else {
try {
DiscoverInfo info = discoManager.discoverInfo(item.getEntityID());
if (info.containsFeature("http://jabber.org/protocol/muc")) {
answer.add(item.getEntityID());
}
}
catch (XMPPException e) {
Log.error("Problem when loading conference service.", e);
}
}
}
return answer;
}
private boolean hasService(String service) {
TreePath path = tree.findByName(tree, new String[]{rootNode.getUserObject().toString(), service});
return path != null;
}
/**
* Returns the Tree used to display bookmarks.
*
* @return Tree used to display bookmarks.
*/
public Tree getTree() {
return tree;
}
/**
* Sets the current bookmarks used with this account.
*
* @param bookmarks the current bookmarks used with this account.
*/
public void setBookmarks(Collection<BookmarkedConference> bookmarks) {
for (BookmarkedConference bookmark : bookmarks) {
String serviceName = StringUtils.parseServer(bookmark.getJid());
String roomJID = bookmark.getJid();
String roomName = bookmark.getName();
if (bookmark.isAutoJoin()) {
ConferenceUtils.joinConferenceOnSeperateThread(bookmark.getName(), bookmark.getJid(), bookmark.getPassword());
ConferenceUtils.addUnclosableChatRoom(roomJID);
autoJoinRooms.add(bookmark.getJid());
}
// Get Service Node
TreePath path = tree.findByName(tree, new String[]{rootNode.getUserObject().toString(), serviceName});
JiveTreeNode serviceNode;
if (path == null) {
serviceNode = addServiceToList(serviceName);
path = tree.findByName(tree, new String[]{rootNode.getUserObject().toString(), serviceName});
}
else {
serviceNode = (JiveTreeNode)path.getLastPathComponent();
}
addBookmark(serviceNode, roomName, roomJID);
tree.expandPath(path);
}
}
/**
* Returns all MUC services available.
*
* @return a collection of MUC services.
*/
public Collection<String> getMucServices() {
return mucServices;
}
/**
* Adds a new ContextMenuListener.
*
* @param listener the listener.
*/
public void addContextMenuListener(ContextMenuListener listener) {
listeners.add(listener);
}
/**
* Removes a ContextMenuListener.
*
* @param listener the listener.
*/
public void removeContextMenuListener(ContextMenuListener listener) {
listeners.remove(listener);
}
private void fireContextMenuListeners(JPopupMenu popup, JiveTreeNode node) {
for (ContextMenuListener listener : new ArrayList<ContextMenuListener>(listeners)) {
listener.poppingUp(node, popup);
}
}
/**
* Adds a new BookmarksListener.
*
* @param bookmarkListener the bookmarkListener.
*/
public void addBookmarksListener(BookmarksListener bookmarkListener) {
bookmarkListeners.add(bookmarkListener);
}
/**
* Removes a BookmarksListener.
*
* @param bookmarkListener the bookmarkListener.
*/
public void removeBookmarksListener(BookmarksListener bookmarkListener) {
bookmarkListeners.remove(bookmarkListener);
}
private void fireBookmarksAdded(String roomJID) {
for (BookmarksListener bookmarkListener : new ArrayList<BookmarksListener>(bookmarkListeners)) {
bookmarkListener.bookmarkAdded(roomJID);
}
}
private void fireBookmarksRemoved(String roomJID) {
for (BookmarksListener bookmarkListener : new ArrayList<BookmarksListener>(bookmarkListeners)) {
bookmarkListener.bookmarkRemoved(roomJID);
}
}
/**
* Returns a list of bookmarks.
* @return a Collection of bookmarks.
*/
public Collection<BookmarkedConference> getBookmarks() {
try {
return manager.getBookmarkedConferences();
}
catch (XMPPException e) {
Log.error(e);
}
return Collections.emptyList();
}
}