| /**************************************************************************** |
| * Copyright (c) 2007, 2009 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 |
| * Hiroyuki Inaba <hiroyuki.inaba@gmail.com> - Bug 259856 The error message when the chat message cannot be sent is not correct. |
| *****************************************************************************/ |
| package org.eclipse.ecf.presence.ui; |
| |
| import java.text.SimpleDateFormat; |
| import java.util.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.ecf.core.identity.ID; |
| import org.eclipse.ecf.core.util.ECFException; |
| import org.eclipse.ecf.internal.presence.ui.Activator; |
| import org.eclipse.ecf.internal.presence.ui.Messages; |
| import org.eclipse.ecf.presence.im.*; |
| import org.eclipse.jface.action.*; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.source.SourceViewer; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.*; |
| import org.eclipse.swt.events.*; |
| import org.eclipse.swt.graphics.*; |
| import org.eclipse.swt.layout.FillLayout; |
| import org.eclipse.swt.widgets.*; |
| import org.eclipse.ui.IWorkbenchPreferenceConstants; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.editors.text.EditorsUI; |
| import org.eclipse.ui.editors.text.TextSourceViewerConfiguration; |
| 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 Map tabs; |
| |
| private boolean showTimestamps = true; |
| |
| public MessagesView() { |
| tabs = new HashMap(); |
| } |
| |
| public void createPartControl(Composite parent) { |
| boolean useTraditionalTabFolder = PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.SHOW_TRADITIONAL_STYLE_TABS); |
| |
| tabFolder = new CTabFolder(parent, SWT.CLOSE); |
| tabFolder.setTabPosition(SWT.BOTTOM); |
| tabFolder.setSimple(useTraditionalTabFolder); |
| PlatformUI.getPreferenceStore().addPropertyChangeListener(new IPropertyChangeListener() { |
| public void propertyChange(PropertyChangeEvent event) { |
| if (event.getProperty().equals(IWorkbenchPreferenceConstants.SHOW_TRADITIONAL_STYLE_TABS) && !tabFolder.isDisposed()) { |
| tabFolder.setSimple(((Boolean) event.getNewValue()).booleanValue()); |
| tabFolder.redraw(); |
| } |
| } |
| }); |
| |
| 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.inputText.setFocus(); |
| break; |
| } |
| } |
| } |
| }); |
| |
| tabFolder.addCTabFolder2Listener(new CTabFolder2Adapter() { |
| public void close(CTabFolderEvent e) { |
| Iterator it = tabs.keySet().iterator(); |
| while (it.hasNext()) { |
| Object key = it.next(); |
| ChatTab tab = (ChatTab) tabs.get(key); |
| if (tab.item == e.item) { |
| tabs.remove(key); |
| break; |
| } |
| } |
| } |
| }); |
| |
| 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, Messages.MessagesView_ClearChatLogDialogMessage)) { |
| 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() { |
| redColor.dispose(); |
| blueColor.dispose(); |
| super.dispose(); |
| } |
| |
| private ChatTab getTab(IChatMessageSender messageSender, ITypingMessageSender typingSender, ID localID, ID userID, String localName) { |
| ChatTab tab = (ChatTab) tabs.get(userID); |
| if (tab == null) { |
| tab = new ChatTab(messageSender, typingSender, localID, userID, localName); |
| 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 |
| * @since 2.4 |
| */ |
| public synchronized void openTab(IChatMessageSender messageSender, ITypingMessageSender typingSender, ID localID, ID remoteID, String localName) { |
| Assert.isNotNull(messageSender); |
| Assert.isNotNull(localID); |
| Assert.isNotNull(remoteID); |
| ChatTab tab = getTab(messageSender, typingSender, localID, remoteID, localName); |
| // if there is only one tab, select this tab |
| if (tabs.size() == 1) { |
| tabFolder.setSelection(tab.item); |
| } |
| } |
| |
| private static String getLocalName(ID id) { |
| IChatID cID = (IChatID) id.getAdapter(IChatID.class); |
| return (cID == null) ? id.getName() : cID.getUsername(); |
| } |
| |
| /** |
| * @since 2.3 |
| */ |
| public synchronized void openTab(IChatMessageSender messageSender, ITypingMessageSender typingSender, ID localID, ID remoteID) { |
| openTab(messageSender, typingSender, localID, remoteID, getLocalName(remoteID)); |
| } |
| |
| /** |
| * @since 2.4 |
| */ |
| public synchronized void selectTab(IChatMessageSender messageSender, ITypingMessageSender typingSender, ID localID, ID userID, String localName) { |
| ChatTab tab = getTab(messageSender, typingSender, localID, userID, localName); |
| tabFolder.setSelection(tab.item); |
| tab.inputText.setFocus(); |
| } |
| |
| public synchronized void selectTab(IChatMessageSender messageSender, ITypingMessageSender typingSender, ID localID, ID userID) { |
| selectTab(messageSender, typingSender, localID, userID, getLocalName(userID)); |
| } |
| |
| /** |
| * 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()); |
| } |
| } |
| |
| 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 StyledText chatText; |
| |
| private Text inputText; |
| |
| private IChatMessageSender icms; |
| |
| private ITypingMessageSender itms; |
| |
| private ID localID; |
| private ID remoteID; |
| |
| private boolean sendTyping = false; |
| |
| private boolean isFirstMessage = true; |
| |
| private String localName; |
| |
| private ChatTab(IChatMessageSender icms, ITypingMessageSender itms, ID localID, ID remoteID, String localName) { |
| this.icms = icms; |
| this.itms = itms; |
| this.localID = localID; |
| this.remoteID = remoteID; |
| this.localName = localName; |
| 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) { |
| String message = ex.getMessage(); |
| if (message == null || message.equals("")) { //$NON-NLS-1$ |
| message = ex.getStatus().getMessage(); |
| if (message == null || message.equals("")) { //$NON-NLS-1$ |
| message = ex.getCause().getMessage(); |
| } |
| } |
| |
| if (message == null || message.equals("")) { //$NON-NLS-1$ |
| setContentDescription(Messages.MessagesView_CouldNotSendMessage); |
| } else { |
| setContentDescription(NLS.bind(Messages.MessagesView_CouldNotSendMessageCauseKnown, message)); |
| } |
| } |
| 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; |
| } |
| } |
| } |
| }); |
| |
| ScrollBar vscrollBar = chatText.getVerticalBar(); |
| if (vscrollBar != null) { |
| vscrollBar.addSelectionListener(scrollSelectionListener); |
| chatText.addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| ScrollBar bar = chatText.getVerticalBar(); |
| if (bar != null) |
| bar.removeSelectionListener(scrollSelectionListener); |
| } |
| }); |
| } |
| } |
| |
| private SelectionListener scrollSelectionListener = new SelectionListener() { |
| public void widgetDefaultSelected(SelectionEvent e) { |
| // do nothing |
| } |
| |
| public void widgetSelected(SelectionEvent e) { |
| if (shouldScrollToEnd(chatText)) |
| boldTabTitle(false); |
| } |
| }; |
| |
| private boolean shouldScrollToEnd(StyledText chatText1) { |
| Point locAtEnd = chatText1.getLocationAtOffset(chatText1.getText().length()); |
| Rectangle bounds = chatText1.getBounds(); |
| if (locAtEnd.y > bounds.height + 5) |
| return false; |
| return true; |
| } |
| |
| private void append(ID fromID, String body) { |
| boolean scrollToEnd = shouldScrollToEnd(chatText); |
| |
| if (!isFirstMessage) { |
| chatText.append(Text.DELIMITER); |
| } |
| int length = chatText.getCharCount(); |
| String name = localName; |
| 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)); |
| setContentDescription(""); //$NON-NLS-1$ |
| 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; |
| if (scrollToEnd) |
| chatText.invokeAction(ST.TEXT_END); |
| boldTabTitle(!scrollToEnd); |
| } |
| |
| private StyledText createStyledTextWidget(Composite parent) { |
| try { |
| SourceViewer result = new SourceViewer(parent, 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()); |
| return result.getTextWidget(); |
| } catch (Exception e) { |
| Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, IStatus.WARNING, Messages.MessagesView_WARNING_HYPERLINKING_NOT_AVAILABLE, e)); |
| return new StyledText(parent, SWT.BORDER | SWT.WRAP | SWT.V_SCROLL | SWT.MULTI | SWT.READ_ONLY); |
| } catch (NoClassDefFoundError e) { |
| Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, IStatus.WARNING, Messages.MessagesView_WARNING_HYPERLINKING_NOT_AVAILABLE, e)); |
| return new StyledText(parent, SWT.BORDER | SWT.WRAP | SWT.V_SCROLL | SWT.MULTI | SWT.READ_ONLY); |
| } |
| } |
| |
| private void boldTabTitle(boolean bold) { |
| Font oldFont = item.getFont(); |
| FontData[] fd = oldFont.getFontData(); |
| item.setFont(new Font(oldFont.getDevice(), fd[0].getName(), fd[0].getHeight(), (bold) ? SWT.BOLD : SWT.NORMAL)); |
| } |
| |
| private void constructWidgets() { |
| item = new CTabItem(tabFolder, SWT.NONE); |
| Composite parent = new Composite(tabFolder, SWT.NONE); |
| parent.setLayout(new FillLayout()); |
| |
| SashForm sash = new SashForm(parent, SWT.VERTICAL); |
| |
| chatText = createStyledTextWidget(sash); |
| |
| inputText = new Text(sash, 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); |
| |
| item.setControl(parent); |
| item.setText(localName); |
| } |
| |
| private void showIsTyping(boolean isTyping) { |
| setContentDescription(isTyping ? NLS.bind(Messages.MessagesView_TypingNotification, localName) : ""); //$NON-NLS-1$ |
| } |
| } |
| |
| } |