blob: b4e86be5ff04186de59f844a16f5dd9936bcbd96 [file] [log] [blame]
/****************************************************************************
* Copyright (c) 2007 Remy Suen, Composent, Inc., and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Remy Suen <remy.suen@gmail.com> - initial API and implementation
*****************************************************************************/
package org.eclipse.ecf.presence.ui;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.util.ECFException;
import org.eclipse.ecf.internal.presence.ui.Messages;
import org.eclipse.ecf.presence.im.IChatID;
import org.eclipse.ecf.presence.im.IChatMessage;
import org.eclipse.ecf.presence.im.IChatMessageSender;
import org.eclipse.ecf.presence.im.ITypingMessageEvent;
import org.eclipse.ecf.presence.im.ITypingMessageSender;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.progress.UIJob;
public class MessagesView extends ViewPart {
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat(
"(hh:mm:ss a)"); //$NON-NLS-1$
public static final String VIEW_ID = "org.eclipse.ecf.presence.ui.MessagesView"; //$NON-NLS-1$
private static final int[] WEIGHTS = { 75, 25 };
private CTabFolder tabFolder;
private Color redColor;
private Color blueColor;
private Image image;
private FormToolkit toolkit;
private List switchActions;
private List menuManagers;
private Map tabs;
private boolean showTimestamps = true;
private static final String getUserName(ID id) {
IChatID chatID = (IChatID) id.getAdapter(IChatID.class);
return chatID == null ? id.getName() : chatID.getUsername();
}
public MessagesView() {
menuManagers = new ArrayList();
switchActions = new ArrayList();
tabs = new HashMap();
}
public void createPartControl(Composite parent) {
tabFolder = new CTabFolder(parent, SWT.NONE);
tabFolder.setTabPosition(SWT.BOTTOM);
toolkit = new FormToolkit(tabFolder.getDisplay());
tabFolder.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
Iterator it = tabs.values().iterator();
while (it.hasNext()) {
ChatTab tab = (ChatTab) it.next();
if (tab.item == e.item) {
tab.switchItem.getAction().setChecked(true);
tab.inputText.setFocus();
} else {
tab.switchItem.getAction().setChecked(false);
}
}
}
});
IMenuManager manager = getViewSite().getActionBars().getMenuManager();
IAction timestampAction = new Action(
Messages.MessagesView_ShowTimestamps, IAction.AS_CHECK_BOX) {
public void run() {
showTimestamps = !showTimestamps;
}
};
timestampAction.setChecked(true);
IAction clearChatLogAction = new Action(
Messages.MessagesView_ClearChatLog) {
public void run() {
CTabItem item = tabFolder.getSelection();
if (item != null) {
Iterator iterator = tabs.values().iterator();
while (iterator.hasNext()) {
ChatTab tab = (ChatTab) iterator.next();
if (tab.item == item) {
if (MessageDialog
.openConfirm(
tabFolder.getShell(),
Messages.MessagesView_ClearChatLogDialogTitle,
NLS
.bind(
Messages.MessagesView_ClearChatLogDialogMessage,
MessagesView
.getUserName(tab.remoteID)))) {
synchronized (tab) {
tab.chatText.setText(""); //$NON-NLS-1$
}
}
return;
}
}
}
}
};
manager.add(clearChatLogAction);
manager.add(timestampAction);
redColor = new Color(parent.getDisplay(), 255, 0, 0);
blueColor = new Color(parent.getDisplay(), 0, 0, 255);
}
public void dispose() {
toolkit.dispose();
redColor.dispose();
blueColor.dispose();
super.dispose();
}
private ChatTab getTab(IChatMessageSender messageSender,
ITypingMessageSender typingSender, ID localID, ID userID) {
ChatTab tab = (ChatTab) tabs.get(userID);
if (tab == null) {
tab = new ChatTab(messageSender, typingSender, localID, userID);
tabs.put(userID, tab);
}
return tab;
}
/**
* Display a message to notify the current user that a typing event has
* occurred.
*
* @param event
* the typing message event
*/
public void displayTypingNotification(ITypingMessageEvent event) {
ChatTab tab = null;
synchronized (tabs) {
tab = (ChatTab) tabs.get(event.getFromID());
}
if (tab != null) {
tab.showIsTyping(event.getTypingMessage().isTyping());
}
}
/**
* Opens a new tab for conversing with a user.
*
* @param messageSender
* the <tt>IChatMessageSender</tt> interface that can be used
* to send messages to the other user
* @param typingSender
* the <tt>ITypingMessageSender</tt> interface to notify the
* other user that the current user is typing a message,
* <tt>null</tt> if unsupported
* @param localID
* the ID of the local user
* @param remoteID
* the ID of the remote user
*/
public synchronized void openTab(IChatMessageSender messageSender,
ITypingMessageSender typingSender, ID localID, ID remoteID) {
Assert.isNotNull(messageSender);
Assert.isNotNull(localID);
Assert.isNotNull(remoteID);
ChatTab tab = getTab(messageSender, typingSender, localID, remoteID);
// if there is only one tab, select this tab
if (tabs.size() == 1) {
tabFolder.setSelection(tab.item);
}
}
synchronized void selectTab(IChatMessageSender messageSender,
ITypingMessageSender typingSender, ID localID, ID userID) {
ChatTab tab = getTab(messageSender, typingSender, localID, userID);
for (int i = 0; i < switchActions.size(); i++) {
IAction action = ((ActionContributionItem) switchActions.get(i))
.getAction();
action.setChecked(false);
}
tab.switchItem.getAction().setChecked(true);
tabFolder.setSelection(tab.item);
tab.inputText.setFocus();
}
/**
* Display a chat message from a remote user in their designated chat box.
*
* @param message
* a chat message that has been sent to the local user
*/
public synchronized void showMessage(IChatMessage message) {
Assert.isNotNull(message);
ID remoteID = message.getFromID();
ChatTab tab = (ChatTab) tabs.get(remoteID);
if (tab != null) {
tab.append(remoteID, message.getBody());
}
}
private synchronized void removeTab(ChatTab tab) {
for (Iterator it = tabs.keySet().iterator(); it.hasNext();) {
Object key = it.next();
if (tabs.get(key) == tab) {
tabs.remove(key);
return;
}
}
}
public void setFocus() {
CTabItem item = tabFolder.getSelection();
if (item != null) {
for (Iterator it = tabs.values().iterator(); it.hasNext();) {
ChatTab tab = (ChatTab) it.next();
if (tab.item == item) {
tab.inputText.setFocus();
break;
}
}
}
}
private class ChatTab {
private CTabItem item;
private Form form;
private StyledText chatText;
private Text inputText;
private IMenuManager manager;
private ActionContributionItem switchItem;
private IChatMessageSender icms;
private ITypingMessageSender itms;
private ID localID;
private ID remoteID;
private boolean sendTyping = false;
private boolean isFirstMessage = true;
private ChatTab(IChatMessageSender icms, ITypingMessageSender itms,
ID localID, ID remoteID) {
this.icms = icms;
this.itms = itms;
this.localID = localID;
this.remoteID = remoteID;
constructWidgets();
addListeners();
}
private void addListeners() {
inputText.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
switch (e.keyCode) {
case SWT.CR:
case SWT.KEYPAD_CR:
if (e.stateMask == 0) {
String text = inputText.getText();
inputText.setText(""); //$NON-NLS-1$
try {
if (!text.equals("")) { //$NON-NLS-1$
icms.sendChatMessage(remoteID, text);
}
append(localID, text);
} catch (ECFException ex) {
form
.setMessage(
NLS
.bind(
Messages.MessagesView_CouldNotSendMessage,
text),
IMessageProvider.ERROR);
}
e.doit = false;
sendTyping = false;
}
break;
}
}
});
inputText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
if (!sendTyping && itms != null) {
sendTyping = true;
try {
itms.sendTypingMessage(remoteID, true, null);
} catch (ECFException ex) {
// ignored since this is not really that important
return;
}
}
}
});
}
private void append(ID fromID, String body) {
if (!isFirstMessage) {
chatText.append(Text.DELIMITER);
}
int length = chatText.getCharCount();
String name = getUserName(fromID);
if (fromID.equals(remoteID)) {
if (showTimestamps) {
chatText.append(FORMATTER.format(new Date(System
.currentTimeMillis())) + ' ');
chatText.setStyleRange(new StyleRange(length, 13, redColor,
null));
length = chatText.getCharCount();
}
chatText.append(name + ": " + body); //$NON-NLS-1$
chatText.setStyleRange(new StyleRange(length,
name.length() + 1, redColor, null, SWT.BOLD));
form.setMessage(null);
if (isFirstMessage) {
final MessageNotificationPopup popup = new MessageNotificationPopup(
getSite().getWorkbenchWindow(), tabFolder
.getShell(), remoteID);
popup.setContent(name, body);
popup.open();
new UIJob(tabFolder.getDisplay(), "Close Popup Job") { //$NON-NLS-1$
public IStatus runInUIThread(IProgressMonitor monitor) {
Shell shell = popup.getShell();
if (shell != null && !shell.isDisposed()) {
popup.close();
}
return Status.OK_STATUS;
}
}.schedule(5000);
}
} else {
if (showTimestamps) {
chatText.append(FORMATTER.format(new Date(System
.currentTimeMillis())) + ' ');
chatText.setStyleRange(new StyleRange(length, 13,
blueColor, null));
length = chatText.getCharCount();
}
chatText.append(name + ": " + body); //$NON-NLS-1$
chatText.setStyleRange(new StyleRange(length,
name.length() + 1, blueColor, null, SWT.BOLD));
}
isFirstMessage = false;
chatText.invokeAction(ST.PAGE_DOWN);
}
private void constructWidgets() {
item = new CTabItem(tabFolder, SWT.NONE);
form = toolkit.createForm(tabFolder);
form.setImage(image);
toolkit.decorateFormHeading(form);
form.setText(getUserName(remoteID));
form.getBody().setLayout(new GridLayout());
SashForm sash = new SashForm(form.getBody(), SWT.VERTICAL);
sash.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Composite client = toolkit.createComposite(sash);
client.setLayout(new FillLayout());
SourceViewer result = new SourceViewer(client, null, null, true,
SWT.BORDER | SWT.WRAP | SWT.V_SCROLL | SWT.MULTI
| SWT.READ_ONLY);
result.configure(new TextSourceViewerConfiguration(EditorsUI
.getPreferenceStore()));
result.setDocument(new Document());
chatText = result.getTextWidget();
client = toolkit.createComposite(sash);
client.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
client.setLayout(new FillLayout());
inputText = new Text(client, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
sash.setWeights(WEIGHTS);
Menu menu = new Menu(chatText);
MenuItem mi = new MenuItem(menu, SWT.PUSH);
mi.setText(Messages.MessagesView_Copy);
mi.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(
org.eclipse.ui.ISharedImages.IMG_TOOL_COPY));
mi.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
String text = chatText.getSelectionText();
if (!text.equals("")) { //$NON-NLS-1$
chatText.copy();
}
}
});
mi = new MenuItem(menu, SWT.PUSH);
mi.setText(Messages.MessagesView_SelectAll);
mi.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
chatText.selectAll();
}
});
chatText.setMenu(menu);
IAction action = new Action(getUserName(remoteID) + '\t',
IAction.AS_RADIO_BUTTON) {
public void run() {
tabFolder.setSelection(item);
}
};
switchItem = new ActionContributionItem(action);
manager = form.getMenuManager();
switchActions.add(switchItem);
menuManagers.add(manager);
for (int i = menuManagers.size() - 1; i > -1; i--) {
IMenuManager manager = (IMenuManager) menuManagers.get(i);
manager.removeAll();
for (int j = 0; j < switchActions.size(); j++) {
IAction switchAction = ((ActionContributionItem) switchActions
.get(j)).getAction();
switchAction.setChecked(false);
manager.add(new ActionContributionItem(switchAction));
}
manager.update();
}
action.setChecked(true);
action = new Action() {
public void run() {
item.dispose();
removeTab(ChatTab.this);
switchActions.remove(switchItem);
menuManagers.remove(manager);
for (int i = 0; i < menuManagers.size(); i++) {
IMenuManager manager = (IMenuManager) menuManagers
.get(i);
manager.remove(switchItem);
manager.update(true);
}
}
};
action.setImageDescriptor(PlatformUI.getWorkbench()
.getSharedImages().getImageDescriptor(
ISharedImages.IMG_TOOL_DELETE));
form.getToolBarManager().add(action);
form.getToolBarManager().update(true);
item.setControl(form);
item.setText(getUserName(remoteID));
toolkit.paintBordersFor(form.getBody());
}
private void showIsTyping(boolean isTyping) {
form.setMessage(isTyping ? NLS.bind(
Messages.MessagesView_TypingNotification,
getUserName(remoteID)) : null);
}
}
}