edu.tsinghua.lumaqq.ui.ClusterIMContainer.java Source code

Java tutorial

Introduction

Here is the source code for edu.tsinghua.lumaqq.ui.ClusterIMContainer.java

Source

/*
* LumaQQ - Java QQ Client
*
* Copyright (C) 2004 luma <stubma@163.com>
*
* 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
*/
package edu.tsinghua.lumaqq.ui;

import static edu.tsinghua.lumaqq.resource.Messages.*;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.ViewForm;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FontDialog;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

import edu.tsinghua.lumaqq.ecore.face.Face;
import edu.tsinghua.lumaqq.events.IFaceSelectionListener;
import edu.tsinghua.lumaqq.models.Cluster;
import edu.tsinghua.lumaqq.models.ClusterType;
import edu.tsinghua.lumaqq.models.GroupType;
import edu.tsinghua.lumaqq.models.Model;
import edu.tsinghua.lumaqq.models.ModelRegistry;
import edu.tsinghua.lumaqq.models.User;
import edu.tsinghua.lumaqq.qq.beans.ClusterIM;
import edu.tsinghua.lumaqq.qq.events.QQEvent;
import edu.tsinghua.lumaqq.qq.packets.in.ClusterCommandReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.ReceiveIMPacket;
import edu.tsinghua.lumaqq.record.IKeyConstants;
import edu.tsinghua.lumaqq.record.RecordEntry;
import edu.tsinghua.lumaqq.resource.Colors;
import edu.tsinghua.lumaqq.resource.DefaultFace;
import edu.tsinghua.lumaqq.resource.Resources;
import edu.tsinghua.lumaqq.ui.config.user.UserInfoWindow;
import edu.tsinghua.lumaqq.ui.helper.DateTool;
import edu.tsinghua.lumaqq.ui.helper.FaceRegistry;
import edu.tsinghua.lumaqq.ui.provider.ClusterMemberLabelProvider;
import edu.tsinghua.lumaqq.ui.provider.MapValueContentProvider;
import edu.tsinghua.lumaqq.ui.sorter.ModelSorter;
import edu.tsinghua.lumaqq.widgets.FaceImageAdvisor;
import edu.tsinghua.lumaqq.widgets.IImageSelectorAdvisor;
import edu.tsinghua.lumaqq.widgets.ImageSelector;
import edu.tsinghua.lumaqq.widgets.rich.IRichContent;
import edu.tsinghua.lumaqq.widgets.rich.LineStyle;
import edu.tsinghua.lumaqq.widgets.rich.RichBox;

/**
 * ?
 * 
 * @author luma
 */
public class ClusterIMContainer extends Composite implements IIMContainer, IFaceSelectionListener, IIMSender {
    //?
    private Runnable enableRunnable = new Runnable() {
        public void run() {
            if (inputBox.isDisposed())
                return;
            inputBox.setReadonly(false);
            inputBox.setBackground(Colors.WHITE);
        }
    };

    /**
     * ???
     * 
     * @author luma
     */
    private class AppendRunnable implements Runnable {
        public String hint;

        /* (non-Javadoc)
         * @see java.lang.Runnable#run()
         */
        public void run() {
            if (inputBox.isDisposed())
                return;
            appendHint('\n' + hint + "\n\n", hintStyle);
        }
    }

    private Resources res;
    private MainShell main;

    private IContainerKeyHandler keyHandler;
    private int unread;
    private boolean active;
    private Cluster cluster;

    // ???
    private ClusterIMDelegate workflow;

    private AppendRunnable appendRunnable;

    private CLabel lblName;
    private CLabel lblList;
    private RichBox outputBox, inputBox;
    private Composite sideContainer, chatContainer, noticeContainer;
    private TableViewer listViewer;
    private Sash vSash;
    private Text textNotice;
    private ToolBar infoBar;
    private Menu memberMenu;

    // style?
    private List<LineStyle> styleCache;
    // ????
    private static final LineStyle hintStyle = new LineStyle(null, null, "", SWT.NORMAL, 9);
    // ??????
    private static final LineStyle myStyle = new LineStyle(Colors.MY_HINT_COLOR, null, "", SWT.NORMAL, 9);
    private static final LineStyle otherStyle = new LineStyle(Colors.BLUE, null, "", SWT.NORMAL, 9);

    private ViewForm inputForm, infoForm;

    // true?
    private boolean infoFormExpanded;

    // ?action
    private Runnable closeAction = new Runnable() {
        public void run() {
            if (keyHandler != null)
                keyHandler.onKeyDown(SWT.MOD3, 'C');
        }
    };
    // action
    private Runnable showRecordAction = new Runnable() {
        public void run() {
            if (keyHandler != null)
                keyHandler.onKeyDown(SWT.MOD3, 'H');
        }
    };
    // ???action
    private Runnable sendAction = new Runnable() {
        public void run() {
            if (!inputBox.isReadonly())
                send();
        }
    };
    // ?tab
    private Runnable nextUnreadTabAction = new Runnable() {
        public void run() {
            if (keyHandler != null)
                keyHandler.onKeyDown(SWT.MOD3, 'Q');
        }
    };
    // ?tab
    private Runnable nextTabAction = new Runnable() {
        public void run() {
            if (keyHandler != null)
                keyHandler.onKeyDown(SWT.MOD3, SWT.ARROW_RIGHT);
        }
    };
    // ?tab
    private Runnable prevTabAction = new Runnable() {
        public void run() {
            if (keyHandler != null)
                keyHandler.onKeyDown(SWT.MOD3, SWT.ARROW_LEFT);
        }
    };

    // ?
    private static boolean IS_GTK;
    private static boolean IS_MOTIF;
    static {
        String platform = SWT.getPlatform();
        IS_GTK = "gtk".equals(platform);
        IS_MOTIF = "motif".equals(platform);
    }

    public ClusterIMContainer(Composite parent) {
        super(parent, SWT.NONE);
        res = Resources.getInstance();
        unread = 0;
        active = false;
        styleCache = new ArrayList<LineStyle>();
        initLayout();
    }

    private void initLayout() {
        GridLayout layout = new GridLayout(3, false);
        layout.horizontalSpacing = layout.verticalSpacing = layout.marginHeight = layout.marginWidth = 0;
        setLayout(layout);
        setBackground(Colors.MAINSHELL_BACKGROUND);

        // ?
        chatContainer = new Composite(this, SWT.NONE);
        chatContainer.setLayoutData(new GridData(GridData.FILL_BOTH));
        layout = new GridLayout();
        layout.marginHeight = layout.marginWidth = 1;
        layout.horizontalSpacing = layout.verticalSpacing = 0;
        chatContainer.setLayout(layout);
        chatContainer.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                Composite c = (Composite) e.getSource();
                Rectangle rect = c.getClientArea();
                rect.width--;
                rect.height--;
                e.gc.setForeground(Colors.WIDGET_BORDER);
                e.gc.drawRectangle(rect);
            }
        });

        ViewForm outputForm = new ViewForm(chatContainer, SWT.FLAT);
        outputForm.setLayoutData(new GridData(GridData.FILL_BOTH));
        lblName = new CLabel(outputForm, SWT.LEFT);
        lblName.setBackground(Colors.VIEWFORM_BANNER_BACKGROUND);
        lblName.setCursor(getDisplay().getSystemCursor(SWT.CURSOR_HAND));
        lblName.setForeground(Colors.VIEWFORM_BANNER_TEXT);
        lblName.addMouseListener(new MouseAdapter() {
            public void mouseUp(MouseEvent e) {
                main.getShellLauncher().openClusterInfoWindow(cluster);
            }
        });
        lblName.addMouseTrackListener(new MouseTrackAdapter() {
            public void mouseEnter(MouseEvent e) {
                lblName.setForeground(Colors.VIEWFORM_BANNER_TEXT_HOVER);
            }

            public void mouseExit(MouseEvent e) {
                lblName.setForeground(Colors.VIEWFORM_BANNER_TEXT);
            }
        });
        outputForm.setTopLeft(lblName);
        outputBox = new RichBox(outputForm);
        outputBox.setReadonly(true);
        outputBox.setBackground(Colors.WHITE);
        outputForm.setContent(outputBox);

        Sash sash = new Sash(chatContainer, SWT.HORIZONTAL);
        sash.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        sash.setBackground(Colors.READONLY_BACKGROUND);
        sash.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                GridData gd = (GridData) inputForm.getLayoutData();
                gd.heightHint = inputForm.getParent().getClientArea().height - e.y;
                inputForm.getParent().layout();
            }
        });

        inputForm = new ViewForm(chatContainer, SWT.FLAT);
        inputForm.setBackground(Colors.VIEWFORM_BANNER_BACKGROUND);
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        gd.heightHint = 100;
        inputForm.setLayoutData(gd);
        final ToolBar tb = new ToolBar(inputForm, SWT.FLAT);
        tb.setBackground(Colors.VIEWFORM_BANNER_BACKGROUND);
        // 
        ToolItem tiFont = new ToolItem(tb, SWT.NONE);
        tiFont.setImage(res.getImage(Resources.icoFont));
        tiFont.setToolTipText(tooltip_button_font);
        tiFont.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                FontDialog dialog = new FontDialog(getShell());
                if (main.getDefaultStyle() != null)
                    dialog.setFontList(main.getDefaultStyle().createFontData());
                if (main.getFontColor() != null)
                    dialog.setRGB(main.getFontColor().getRGB());
                dialog.open();
                FontData[] fontList = dialog.getFontList();
                if (fontList == null)
                    return;
                RGB rgb = dialog.getRGB();
                if (main.getFontColor() != null)
                    main.getFontColor().dispose();
                main.getDefaultStyle().fontName = fontList[0].getName();
                main.getDefaultStyle().fontSize = fontList[0].getHeight();
                main.getDefaultStyle().fontStyle = fontList[0].getStyle();
                if (rgb != null) {
                    main.setFontColor(new Color(main.getDisplay(), rgb));
                    main.getDefaultStyle().foreground = main.getFontColor();
                }
                inputBox.setDefaultStyle(main.getDefaultStyle());
            }
        });
        // ??
        if (IS_GTK || IS_MOTIF) {
            ToolItem tiColor = new ToolItem(tb, SWT.NONE);
            tiColor.setImage(res.getImage(Resources.icoColor));
            tiColor.setToolTipText(tooltip_button_color);
            tiColor.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent e) {
                    ColorDialog dialog = new ColorDialog(getShell());
                    if (main.getFontColor() != null)
                        dialog.setRGB(main.getFontColor().getRGB());
                    dialog.open();
                    RGB rgb = dialog.getRGB();
                    if (rgb != null) {
                        if (main.getFontColor() != null)
                            main.getFontColor().dispose();
                        if (rgb != null) {
                            main.setFontColor(new Color(main.getDisplay(), rgb));
                            main.getDefaultStyle().foreground = main.getFontColor();
                        }
                    }
                    inputBox.setDefaultStyle(main.getDefaultStyle());
                }
            });
        }
        // 
        ToolItem tiFace = new ToolItem(tb, SWT.NONE);
        tiFace.setImage(res.getImage(Resources.icoSmiley));
        tiFace.setToolTipText(tooltip_button_face);
        tiFace.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                Rectangle bound = tb.getBounds();
                openImageSelectionShell(tb.getParent().toDisplay(bound.x, bound.y + bound.height));
            }
        });
        // ?
        ToolItem tiAnimation = new ToolItem(tb, SWT.CHECK);
        tiAnimation.setImage(res.getImage(Resources.icoStop));
        tiAnimation.setToolTipText(tooltip_button_disable_animation);
        tiAnimation.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                ToolItem ti = (ToolItem) e.widget;
                outputBox.setForbiddenAnimation(ti.getSelection());
                inputBox.setForbiddenAnimation(ti.getSelection());
                ti.setToolTipText(ti.getSelection() ? tooltip_button_animation : tooltip_button_disable_animation);
            }
        });
        inputForm.setTopLeft(tb);
        ToolBar tbSwitch = new ToolBar(inputForm, SWT.FLAT);
        tbSwitch.setBackground(Colors.VIEWFORM_BANNER_BACKGROUND);
        ToolItem tiSwitch = new ToolItem(tbSwitch, SWT.NONE);
        tiSwitch.setImage(res.getImage(Resources.icoRightArrow));
        tiSwitch.setToolTipText(tooltip_button_hide);
        inputForm.setTopRight(tbSwitch);
        inputBox = new RichBox(inputForm);
        inputBox.setBackground(Colors.WHITE);
        inputForm.setContent(inputBox);
        tiSwitch.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                ToolItem item = (ToolItem) e.getSource();
                GridData d = (GridData) sideContainer.getLayoutData();
                d.exclude = !d.exclude;
                d = (GridData) vSash.getLayoutData();
                d.exclude = !d.exclude;
                if (d.exclude) {
                    sideContainer.setVisible(false);
                    ((GridData) chatContainer.getLayoutData()).horizontalSpan = 3;
                    item.setImage(res.getImage(Resources.icoLeftArrow));
                    item.setToolTipText(tooltip_button_show);
                } else {
                    sideContainer.setVisible(true);
                    ((GridData) chatContainer.getLayoutData()).horizontalSpan = 1;
                    item.setImage(res.getImage(Resources.icoRightArrow));
                    item.setToolTipText(tooltip_button_hide);
                }

                sideContainer.getParent().layout();
            }
        });

        // sash
        vSash = new Sash(this, SWT.VERTICAL);
        gd = new GridData(GridData.FILL_VERTICAL);
        gd.verticalSpan = 2;
        vSash.setLayoutData(gd);
        vSash.setBackground(Colors.MAINSHELL_BACKGROUND);
        vSash.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                GridData d = (GridData) sideContainer.getLayoutData();
                GridLayout l = (GridLayout) sideContainer.getParent().getLayout();
                d.widthHint = sideContainer.getParent().getClientArea().width - e.x - (l.marginWidth << 1);
                sideContainer.getParent().layout();
            }
        });

        // ?
        sideContainer = new Composite(this, SWT.NONE);
        gd = new GridData(GridData.FILL_VERTICAL);
        gd.verticalSpan = 2;
        gd.widthHint = 140;
        sideContainer.setLayoutData(gd);
        layout = new GridLayout();
        layout.marginHeight = layout.marginWidth = 1;
        layout.horizontalSpacing = layout.verticalSpacing = 0;
        sideContainer.setLayout(layout);
        sideContainer.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                Composite c = (Composite) e.getSource();
                Rectangle rect = c.getClientArea();
                rect.width--;
                rect.height--;
                if (rect.width > 0 && rect.height > 0) {
                    e.gc.setForeground(Colors.WIDGET_BORDER);
                    e.gc.drawRectangle(rect);
                }
            }
        });

        // ?
        MouseTrackListener mtl = new MouseTrackAdapter() {
            @Override
            public void mouseEnter(MouseEvent e) {
                if (!infoBar.isVisible())
                    infoBar.setVisible(true);
            }

            @Override
            public void mouseExit(MouseEvent e) {
                Control control = main.getDisplay().getCursorControl();
                if (control == null)
                    infoBar.setVisible(false);
                else if (control == infoBar || control == noticeContainer || control.getParent() == noticeContainer)
                    return;
                else
                    infoBar.setVisible(false);
            }
        };

        infoForm = new ViewForm(sideContainer, SWT.FLAT);
        gd = new GridData(GridData.FILL_HORIZONTAL);
        gd.heightHint = 140;
        infoForm.setLayoutData(gd);
        final CLabel lblExpand = new CLabel(infoForm, SWT.LEFT);
        lblExpand.setImage(res.getImage(Resources.icoFlatCollapse));
        lblExpand.setText(cluster_im_list_info);
        lblExpand.setBackground(Colors.VIEWFORM_BANNER_BACKGROUND);
        lblExpand.setForeground(Colors.VIEWFORM_BANNER_TEXT);
        infoForm.setTopLeft(lblExpand);
        lblExpand.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                infoFormExpanded = !infoFormExpanded;
                if (infoFormExpanded) {
                    lblExpand.setImage(res.getImage(Resources.icoFlatCollapse));
                    GridData gd = (GridData) infoForm.getLayoutData();
                    gd.heightHint = 140;
                    sideContainer.layout();
                } else {
                    lblExpand.setImage(res.getImage(Resources.icoFlatExpand));
                    GridData gd = (GridData) infoForm.getLayoutData();
                    gd.heightHint = infoForm.getTopLeft().getSize().y;
                    sideContainer.layout();
                }
            }
        });
        Composite content = new Composite(infoForm, SWT.NONE);
        content.setLayout(new FormLayout());
        FormData fd = new FormData();
        fd.left = fd.top = new FormAttachment(0, 0);
        fd.bottom = fd.right = new FormAttachment(100, 0);
        noticeContainer = new Composite(content, SWT.NONE);
        noticeContainer.setBackground(Colors.TOOLTIP_BACKGROUND);
        noticeContainer.setLayout(new GridLayout());
        noticeContainer.setLayoutData(fd);
        CLabel lblNotice = new CLabel(noticeContainer, SWT.LEFT);
        lblNotice.setImage(res.getImage(Resources.icoSysMsg));
        lblNotice.setText(cluster_im_label_notice);
        lblNotice.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
        lblNotice.setBackground(noticeContainer.getBackground());
        lblNotice.addMouseTrackListener(mtl);
        textNotice = new Text(noticeContainer, SWT.MULTI | SWT.WRAP | SWT.READ_ONLY);
        textNotice.setBackground(noticeContainer.getBackground());
        textNotice.setLayoutData(new GridData(GridData.FILL_BOTH));
        textNotice.addMouseTrackListener(mtl);
        infoBar = new ToolBar(content, SWT.FLAT);
        infoBar.setBackground(Colors.WHITE);
        ToolItem tiInfo = new ToolItem(infoBar, SWT.PUSH);
        tiInfo.setImage(res.getImage(Resources.icoClusterInfo));
        tiInfo.setToolTipText(cluster_im_button_info);
        tiInfo.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                main.getShellLauncher().openClusterInfoWindow(cluster);
            }
        });
        ToolItem tiCard = new ToolItem(infoBar, SWT.PUSH);
        tiCard.setImage(res.getImage(Resources.icoClusterCard));
        tiCard.setToolTipText(cluster_im_button_card);
        tiCard.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                User u = cluster.getMember(main.getMyModel().qq);
                if (u == null)
                    return;
                u.info = main.getMyModel().info;
                UserInfoWindow uiw = main.getShellLauncher().openUserInfoWindow(u, UserInfoWindow.EDITABLE);
                uiw.setCurrentPage(UserInfoWindow.CARD);
            }
        });
        ToolItem tiUpdate = new ToolItem(infoBar, SWT.PUSH);
        tiUpdate.setImage(res.getImage(Resources.icoRefresh));
        tiUpdate.setToolTipText(cluster_im_button_update);
        tiUpdate.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                main.getClient().getClusterInfo(cluster.clusterId);
                cluster.cardUpdated = false;
                main.getClient().getCardBatch(cluster.clusterId, 0);
            }
        });
        fd = new FormData();
        fd.right = fd.bottom = new FormAttachment(100, 0);
        infoBar.setLayoutData(fd);
        infoBar.moveAbove(noticeContainer);
        infoBar.setVisible(false);
        infoBar.addMouseTrackListener(mtl);
        infoBar.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                Rectangle rect = infoBar.getClientArea();
                e.gc.setForeground(Colors.PAGE_CONTROL_BORDER);
                rect.height--;
                rect.width--;
                e.gc.drawRectangle(rect);
            }
        });
        noticeContainer.addMouseTrackListener(mtl);
        infoForm.setContent(content);

        // ?
        ViewForm listForm = new ViewForm(sideContainer, SWT.FLAT);
        listForm.setLayoutData(new GridData(GridData.FILL_BOTH));
        lblList = new CLabel(listForm, SWT.LEFT);
        lblList.setImage(res.getSmallClusterHead(6));
        lblList.setBackground(Colors.VIEWFORM_BANNER_BACKGROUND);
        lblList.setForeground(Colors.VIEWFORM_BANNER_TEXT);
        listForm.setTopLeft(lblList);
        listViewer = new TableViewer(listForm, SWT.SINGLE | SWT.V_SCROLL | SWT.FULL_SELECTION);
        listViewer.setLabelProvider(new ClusterMemberLabelProvider());
        listViewer.setSorter(new ModelSorter());
        Table t = listViewer.getTable();
        new TableColumn(t, SWT.LEFT);
        new TableColumn(t, SWT.LEFT);
        t.addControlListener(new ControlAdapter() {
            public void controlResized(ControlEvent e) {
                Table table = (Table) e.getSource();
                table.getColumn(0).setWidth(20);
                table.getColumn(1).setWidth(table.getClientArea().width - 20);
            }
        });
        t.addMouseListener(new MouseAdapter() {
            public void mouseUp(MouseEvent e) {
                if (e.button == 3) {
                    Table table = (Table) e.getSource();
                    memberMenu.setLocation(table.toDisplay(e.x, e.y));
                    memberMenu.setVisible(true);
                }
            }
        });
        t.addSelectionListener(new SelectionAdapter() {
            public void widgetDefaultSelected(SelectionEvent e) {
                IStructuredSelection selection = (IStructuredSelection) listViewer.getSelection();
                if (!selection.isEmpty()) {
                    User f = (User) selection.getFirstElement();
                    main.getShellLauncher().openUserInfoWindow(f, UserInfoWindow.READ_ONLY);
                    main.getClient().getUserInfo(f.qq);
                }
            }
        });
        t.setHeaderVisible(false);
        t.setLinesVisible(false);
        listForm.setContent(t);

        // ???
        initMemberMenu();

        addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                int size = styleCache.size();
                for (int i = 0; i < size; i++) {
                    LineStyle style = (LineStyle) styleCache.get(i);
                    if (style.foreground != null)
                        style.foreground.dispose();
                    if (style.background != null)
                        style.background.dispose();
                }
                workflow.dispose();
            }
        });
    }

    /**
     * ??
     */
    private void initKeyAction() {
        // mod3 -> alt
        inputBox.setUserKeyAction('C' | SWT.MOD3, closeAction);
        inputBox.setUserKeyAction('S' | SWT.MOD3, sendAction);
        inputBox.setUserKeyAction('H' | SWT.MOD3, showRecordAction);
        inputBox.setUserKeyAction('Q' | SWT.MOD3, nextUnreadTabAction);
        inputBox.setUserKeyAction(SWT.MOD3 | SWT.ARROW_RIGHT, nextTabAction);
        inputBox.setUserKeyAction(SWT.MOD3 | SWT.ARROW_LEFT, prevTabAction);

        // ????
        if (main.getOptionHelper().isUseEnterInTalkMode()) {
            inputBox.removeUserKeyAction(SWT.MOD1 | SWT.CR);
            inputBox.setUserKeyAction(SWT.CR, sendAction);
            inputBox.setKeyBinding(SWT.MOD1 | SWT.CR, RichBox.NEW_LINE);
        } else {
            inputBox.removeUserKeyAction(SWT.CR);
            inputBox.setUserKeyAction(SWT.MOD1 | SWT.CR, sendAction);
            inputBox.setKeyBinding(SWT.CR, RichBox.NEW_LINE);
        }
    }

    /**
      * ?
      */
    private void openImageSelectionShell(Point loc) {
        ImageSelector fss = new ImageSelector(getShell(), new FaceImageAdvisor(main));
        fss.setListener(this);
        fss.setLocation(loc);
        fss.open();
    }

    public void setKeyHandler(IContainerKeyHandler ka) {
        keyHandler = ka;
    }

    public Image getHead() {
        if (cluster.isPermanent())
            return res.getSmallClusterHead(cluster.headId);
        else
            return res.getImage(Resources.icoDialog);
    }

    public int getUnreadCount() {
        return unread;
    }

    public void setActive(boolean active) {
        this.active = active;
        if (active)
            unread = 0;
    }

    public String getDisplayedName() {
        return cluster.name;
    }

    public int getId() {
        return cluster.clusterId;
    }

    public Model getModel() {
        return cluster;
    }

    public void setModel(Model model) {
        this.cluster = (Cluster) model;
        GridData gd = (GridData) infoForm.getLayoutData();
        gd.exclude = !cluster.isPermanent();
        infoForm.getParent().layout();
        if (!gd.exclude)
            setNotice(cluster.notice);

        setListLabel(cluster.members.size(), 0);
        setClusterNameLabel();
        listViewer.setContentProvider(new MapValueContentProvider(cluster.members));
        listViewer.setInput(this);
    }

    /**
     * ??
     */
    private void setClusterNameLabel() {
        if (cluster.clusterType == ClusterType.NORMAL)
            lblName.setImage(res.getSmallClusterHead(cluster.headId));
        else
            lblName.setImage(res.getImage(Resources.icoDialog));

        if (cluster.isPermanent())
            lblName.setText(cluster.name + " (" + cluster.externalId + ')');
        else
            lblName.setText(cluster.name);
    }

    public void setMainShell(MainShell main) {
        this.main = main;
        workflow = new ClusterIMDelegate(main.getClient());
        workflow.setSender(this);
        // ??
        initKeyAction();
    }

    public void send() {
        String s = inputBox.getText();
        // ??
        if (s.length() == 0) {
            MessageDialog.openWarning(getShell(), message_box_common_warning_title,
                    message_box_cannot_send_empty_message);
            return;
        }
        // ?
        if (main.getClient().getUser().isLoggedIn()) {
            workflow.setOriginalMessage(s);
            workflow.start();
        } else {
            MessageDialog.openWarning(getShell(), message_box_common_warning_title, message_box_please_login_first);
        }
    }

    public void putMessage(ReceiveIMPacket packet) {
        appendMessage(cluster, packet.clusterIM, false);
        if (!active)
            unread++;
    }

    public void setUseEnter(boolean useEnter) {
        if (useEnter) {
            inputBox.removeUserKeyAction(SWT.MOD1 | SWT.CR);
            inputBox.setUserKeyAction(SWT.CR, sendAction);
            inputBox.setKeyBinding(SWT.MOD1 | SWT.CR, RichBox.NEW_LINE);
        } else {
            inputBox.removeUserKeyAction(SWT.CR);
            inputBox.setUserKeyAction(SWT.MOD1 | SWT.CR, sendAction);
            inputBox.setKeyBinding(SWT.CR, RichBox.NEW_LINE);
        }
    }

    public Composite getComposite() {
        return this;
    }

    public void setText(String text) {
        inputBox.setText(text);
    }

    public void qqEvent(QQEvent e) {
        switch (e.type) {
        case QQEvent.QQ_GET_CLUSTER_INFO_FAIL:
        case QQEvent.QQ_GET_MEMBER_INFO_FAIL:
        case QQEvent.QQ_GET_ONLINE_MEMBER_FAIL:
        case QQEvent.QQ_GET_TEMP_CLUSTER_INFO_FAIL:
            processClusterCommandFail(e);
            break;
        default:
            workflow.delegateQQEvent(e);
            break;
        }
    }

    /**
    * <pre>
    * ??????
    * ?
    * </pre>
    * @param e
    */
    private void processClusterCommandFail(QQEvent e) {
        final ClusterCommandReplyPacket packet = (ClusterCommandReplyPacket) e.getSource();
        if (packet.clusterId == cluster.clusterId) {
            main.getDisplay().asyncExec(new Runnable() {
                public void run() {
                    MessageDialog.openError(main.getShell(), message_box_common_fail_title, packet.errorMessage);
                }
            });
        }
    }

    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.events.IFaceSelectionListener#faceSelected(edu.tsinghua.lumaqq.ui.IImageProvider, int, int)
     */
    public void faceSelected(IImageSelectorAdvisor provider, int group, int sequence) {
        if (group == 0) {
            int index = res.getFaceCode(sequence);
            if (index != -1)
                inputBox.insertImage(IRichContent.DEFAULT_FACE_TAG, index);
        } else {
            FaceRegistry util = FaceRegistry.getInstance();
            Face face = util.getFace(group - 1, sequence);
            if (face == null)
                return;
            int id = face.getId();
            inputBox.insertImage(IRichContent.CUSTOM_FACE_TAG, id);
        }
    }

    /**
     * 
     * 
     * @param notice
     */
    private void setNotice(String notice) {
        if (notice != null && infoForm != null)
            textNotice.setText(notice);
    }

    /**
     * ??
     * 
     * @param total
     *       
     * @param online
     *       
     */
    private void setListLabel(int total, int online) {
        lblList.setText(NLS.bind(cluster_im_list_label, String.valueOf(online), String.valueOf(total)));
    }

    /**
     * ????
     */
    private void initMemberMenu() {
        memberMenu = new Menu(getShell());
        // ??
        final MenuItem miSend = new MenuItem(memberMenu, SWT.PUSH);
        miSend.setText(cluster_im_menu_member_send);
        miSend.setImage(res.getImage(Resources.icoSendReceiveMessage));
        miSend.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                openMemberMessageShell();
            }
        });
        // ?
        MenuItem mi = new MenuItem(memberMenu, SWT.PUSH);
        mi.setImage(res.getImage(Resources.icoTempSessionIM));
        mi.setText(menu_friend_temp_session);
        mi.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                IStructuredSelection selection = (IStructuredSelection) listViewer.getSelection();
                if (!selection.isEmpty()) {
                    User u = (User) selection.getFirstElement();
                    main.getShellLauncher().openTempSessionIMWindow(u.qq);
                }
            }
        });
        new MenuItem(memberMenu, SWT.SEPARATOR);
        // ?
        final MenuItem miAdd = new MenuItem(memberMenu, SWT.PUSH);
        miAdd.setText(cluster_im_menu_member_add);
        miAdd.setImage(res.getImage(Resources.icoAddFriend));
        miAdd.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                IStructuredSelection selection = (IStructuredSelection) listViewer.getSelection();
                if (!selection.isEmpty()) {
                    User f = (User) selection.getFirstElement();
                    main.getShellLauncher().openAddReceiveSystemMessageShell(f);
                    main.getClient().addFriend(f.qq);
                }
            }
        });
        // 
        mi = new MenuItem(memberMenu, SWT.PUSH);
        mi.setText(cluster_im_menu_member_viewinfo);
        mi.setImage(res.getImage(Resources.icoPersonInfo));
        mi.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                IStructuredSelection selection = (IStructuredSelection) listViewer.getSelection();
                if (!selection.isEmpty()) {
                    User f = (User) selection.getFirstElement();
                    main.getShellLauncher().openUserInfoWindow(f, UserInfoWindow.READ_ONLY);
                }
            }
        });
        // separator
        mi = new MenuItem(memberMenu, SWT.SEPARATOR);
        // ????
        mi = new MenuItem(memberMenu, SWT.CASCADE);
        mi.setText(cluster_im_menu_member_message);
        mi.setImage(res.getImage(Resources.icoMessageManage));
        Menu msgMenu = new Menu(mi);
        mi.setMenu(msgMenu);
        // ???
        mi = new MenuItem(msgMenu, SWT.PUSH);
        mi.setText(cluster_im_menu_member_export);
        mi.setImage(res.getImage(Resources.icoTxtFile));
        mi.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                IStructuredSelection selection = (IStructuredSelection) listViewer.getSelection();
                if (!selection.isEmpty()) {
                    User f = (User) selection.getFirstElement();
                    // 
                    main.getExportHelper().exportMessage(f);
                }
            }
        });
        // ???
        memberMenu.addMenuListener(new MenuAdapter() {
            public void menuShown(MenuEvent e) {
                IStructuredSelection selection = (IStructuredSelection) listViewer.getSelection();
                if (selection.isEmpty())
                    memberMenu.setVisible(false);
                else {
                    User f = (User) selection.getFirstElement();
                    // ???????????
                    boolean b = f.isFriend();
                    miSend.setEnabled(b);
                    miAdd.setEnabled(!b);
                }
            }
        });
    }

    /**
    * ????
    * 
    * @return 
    *       ??ReceiveMessageShell, ?SendMessageShell
    */
    protected void openMemberMessageShell() {
        IStructuredSelection selection = (IStructuredSelection) listViewer.getSelection();
        if (!selection.isEmpty()) {
            User f = (User) selection.getFirstElement();
            // ?model
            User f2 = ModelRegistry.getUser(f.qq);
            // modelnull???
            // ????model
            // ?
            if (f2 == null) {
                /* ??? */
                f2 = f;
                main.getBlindHelper().addUser(f2, GroupType.STRANGER_GROUP);
                main.getBlindHelper().refreshGroup(f2.group);
            }
            main.getShellLauncher().openNormalIMWindow(f2);
        }
    }

    public void send(String msg) {
        // ??       
        LineStyle style = main.getDefaultStyle();
        if (cluster.isPermanent())
            main.getClient().sendClusterIM(cluster.clusterId, msg, workflow.getMessageId(),
                    workflow.getTotalFragments(), workflow.getCurrentFragment(), style.fontName,
                    (style.fontStyle & SWT.BOLD) != 0, (style.fontStyle & SWT.ITALIC) != 0, false, style.fontSize,
                    style.foreground.getRed(), style.foreground.getGreen(), style.foreground.getBlue());
        else
            main.getClient().sendTempClusterIM(cluster.clusterType.toQQConstant(), cluster.clusterId,
                    cluster.parentClusterId, msg, workflow.getMessageId(), workflow.getTotalFragments(),
                    workflow.getCurrentFragment(), style.fontName, (style.fontStyle & SWT.BOLD) != 0,
                    (style.fontStyle & SWT.ITALIC) != 0, false, style.fontSize, style.foreground.getRed(),
                    style.foreground.getGreen(), style.foreground.getBlue());
    }

    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.ui.IIMSender#notifyOver()
     */
    public void notifyOver() {
        main.getDisplay().syncExec(enableRunnable);
    }

    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.ui.IIMSender#notifyStart(java.lang.String)
     */
    public void notifyStart(String toBeSent) {
        // ??
        inputBox.setReadonly(true);
        inputBox.setBackground(Colors.READONLY_BACKGROUND);
        // ??
        RecordEntry key = new RecordEntry();
        key.owner = cluster.clusterId;
        key.sender = main.getMyModel().qq;
        key.senderParent = cluster.clusterId;
        key.receiver = cluster.clusterId;
        key.time = System.currentTimeMillis();
        key.type = IKeyConstants.CLUSTER;
        key.message = toBeSent;
        main.getRecordManager().addRecord(key);
        // ??
        inputBox.clear();
        // ???
        appendMessage(key);
    }

    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.ui.IIMSender#notifyFail(java.lang.String)
     */
    public void notifyFail(String msg) {
        main.getDisplay().syncExec(enableRunnable);
        appendRunnable.hint = msg;
        main.getDisplay().syncExec(appendRunnable);
    }

    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.ui.IIMSender#notifyTimeout(java.lang.String)
     */
    public void notifyTimeout(String failToSent) {
        main.getDisplay().syncExec(enableRunnable);
        appendRunnable.hint = NLS.bind(cluster_im_hint_timemout, failToSent);
        main.getDisplay().syncExec(appendRunnable);
    }

    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.ui.IIMSender#getSenderId()
     */
    public int getSenderId() {
        return cluster.clusterId;
    }

    /**
     * ???
     * 
     * @param hint
     * @param style
     */
    private void appendHint(String hint, LineStyle style) {
        outputBox.appendText(hint, style);
    }

    /**
     * ?
     * 
     * @param entry
     *       RecordEntry
     */
    private void appendMessage(RecordEntry entry) {
        outputBox.appendText(
                '(' + DefaultFace.escapeFaces(main.getMyModel().displayName) + ")  " + DateTool.format(entry.time),
                myStyle);
        outputBox.appendText(entry.message, getLineStyle(main.getDefaultStyle()));
    }

    /**
     * @param c
     *       model
     * @param im
     *       ?
     * @param me
     *       true
     */
    public void appendMessage(Cluster c, ClusterIM im, boolean me) {
        User f = c.getMember(im.sender);
        String name = (f == null) ? String.valueOf(im.sender) : DefaultFace.escapeFaces(f.displayName);
        outputBox.appendText('(' + name + ")  " + DateTool.format(im.sendTime), me ? myStyle : otherStyle);
        LineStyle style = getLineStyle(im);
        outputBox.appendText(im.message, style);
    }

    /**
     * ?
     * 
     * @param im
     * @return
     */
    private LineStyle getLineStyle(ClusterIM im) {
        int fontStyle = 0;
        if (im.fontStyle.isBold())
            fontStyle |= SWT.BOLD;
        if (im.fontStyle.isItalic())
            fontStyle |= SWT.ITALIC;
        if (fontStyle == 0)
            fontStyle = SWT.NORMAL;

        return getLineStyle(im.fontStyle.getFontName(), fontStyle, im.fontStyle.getFontSize(),
                im.fontStyle.getRed(), im.fontStyle.getGreen(), im.fontStyle.getBlue());
    }

    /**
     * ?stylecache??style
     * 
     * @param style
     * @return
     */
    private LineStyle getLineStyle(LineStyle ls) {
        int size = styleCache.size();
        for (int i = 0; i < size; i++) {
            LineStyle style = styleCache.get(i);
            if (style.equals(ls))
                return style;
        }

        LineStyle style = (LineStyle) ls.clone();
        styleCache.add(style);
        return style;
    }

    /**
     * ???style
     * 
     * @param fontName
     * @param fontStyle
     * @param fontSize
     * @param red
     * @param green
     * @param blue
     * @return
     */
    private LineStyle getLineStyle(String fontName, int fontStyle, int fontSize, int red, int green, int blue) {
        int size = styleCache.size();
        for (int i = 0; i < size; i++) {
            LineStyle style = styleCache.get(i);
            if (!style.fontName.equals(fontName))
                continue;
            if (style.fontSize != fontSize)
                continue;
            if (style.fontStyle != fontStyle)
                continue;
            // TODO add underline here
            if (style.foreground.getRed() != red)
                continue;
            if (style.foreground.getGreen() != green)
                continue;
            if (style.foreground.getBlue() != blue)
                continue;
            return style;
        }

        LineStyle style = new LineStyle(new Color(main.getDisplay(), red, green, blue), null, fontName, fontStyle,
                fontSize);
        styleCache.add(style);
        return style;
    }

    public void release() {
        dispose();
    }

    public void setKeyboardFocus() {
        inputBox.setFocus();
    }

    public void init() {
        inputBox.setDefaultStyle(main.getDefaultStyle());
        main.getClient().getClusterInfo(cluster.clusterId);
    }

    public boolean isActiveContainer() {
        return true;
    }

    public void appendText(String text) {
        inputBox.appendText(text, main.getDefaultStyle());
    }
}