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

Java tutorial

Introduction

Here is the source code for edu.tsinghua.lumaqq.ui.MainShell.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.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
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.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
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.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
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.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tray;
import org.eclipse.swt.widgets.TrayItem;
import org.eclipse.swt.widgets.TreeItem;

import edu.tsinghua.lumaqq.IPSeeker;
import edu.tsinghua.lumaqq.LumaQQ;
import edu.tsinghua.lumaqq.MessageQueue;
import edu.tsinghua.lumaqq.QQShowManager;
import edu.tsinghua.lumaqq.Sounder;
import edu.tsinghua.lumaqq.disk.DiskContentProvider;
import edu.tsinghua.lumaqq.disk.DiskLabelProvider;
import edu.tsinghua.lumaqq.disk.DiskManager;
import edu.tsinghua.lumaqq.disk.DiskViewerSorter;
import edu.tsinghua.lumaqq.ecore.option.OpType;
import edu.tsinghua.lumaqq.events.IQQShowListener;
import edu.tsinghua.lumaqq.hotkey.IHotkeyListener;
import edu.tsinghua.lumaqq.models.Group;
import edu.tsinghua.lumaqq.models.ModelRegistry;
import edu.tsinghua.lumaqq.models.User;
import edu.tsinghua.lumaqq.qq.QQ;
import edu.tsinghua.lumaqq.qq.QQClient;
import edu.tsinghua.lumaqq.qq.Util;
import edu.tsinghua.lumaqq.qq.beans.Directory;
import edu.tsinghua.lumaqq.qq.beans.File;
import edu.tsinghua.lumaqq.qq.beans.QQUser;
import edu.tsinghua.lumaqq.qq.events.QQEvent;
import edu.tsinghua.lumaqq.qq.packets.ErrorPacket;
import edu.tsinghua.lumaqq.qq.packets.InPacket;
import edu.tsinghua.lumaqq.qq.packets.in.LoginReplyPacket;
import edu.tsinghua.lumaqq.qq.packets.in.ReceiveIMPacket;
import edu.tsinghua.lumaqq.qq.packets.in.SystemNotificationPacket;
import edu.tsinghua.lumaqq.record.RecordManager;
import edu.tsinghua.lumaqq.resource.Colors;
import edu.tsinghua.lumaqq.resource.Resources;
import edu.tsinghua.lumaqq.ui.config.user.UserInfoWindow;
import edu.tsinghua.lumaqq.ui.dialogs.CrashDialog;
import edu.tsinghua.lumaqq.ui.dialogs.DiskPasswordDialog;
import edu.tsinghua.lumaqq.ui.dialogs.LoginDialog;
import edu.tsinghua.lumaqq.ui.helper.BlindHelper;
import edu.tsinghua.lumaqq.ui.helper.ClusterCategoryTool;
import edu.tsinghua.lumaqq.ui.helper.ConfigHelper;
import edu.tsinghua.lumaqq.ui.helper.DateTool;
import edu.tsinghua.lumaqq.ui.helper.DragHelper;
import edu.tsinghua.lumaqq.ui.helper.ExportHelper;
import edu.tsinghua.lumaqq.ui.helper.FaceRegistry;
import edu.tsinghua.lumaqq.ui.helper.FileTool;
import edu.tsinghua.lumaqq.ui.helper.MenuHelper;
import edu.tsinghua.lumaqq.ui.helper.MessageHelper;
import edu.tsinghua.lumaqq.ui.helper.OptionHelper;
import edu.tsinghua.lumaqq.ui.helper.ShellLauncher;
import edu.tsinghua.lumaqq.ui.helper.ShellRegistry;
import edu.tsinghua.lumaqq.ui.helper.TimerHelper;
import edu.tsinghua.lumaqq.ui.helper.TipHelper;
import edu.tsinghua.lumaqq.ui.helper.UIHelper;
import edu.tsinghua.lumaqq.ui.helper.UITool;
import edu.tsinghua.lumaqq.ui.jobs.BackgroundJobExecutor;
import edu.tsinghua.lumaqq.ui.jobs.CancelDiskPasswordJob;
import edu.tsinghua.lumaqq.ui.jobs.CreateFileJob;
import edu.tsinghua.lumaqq.ui.jobs.DialogJobExecutor;
import edu.tsinghua.lumaqq.ui.jobs.DownloadFileJob;
import edu.tsinghua.lumaqq.ui.jobs.ICancelableJob;
import edu.tsinghua.lumaqq.ui.jobs.IExecutor;
import edu.tsinghua.lumaqq.ui.jobs.MoveJob;
import edu.tsinghua.lumaqq.ui.jobs.SetDiskPasswordJob;
import edu.tsinghua.lumaqq.ui.jobs.UploadGroupJob;
import edu.tsinghua.lumaqq.ui.listener.CenterBorderPaintListener;
import edu.tsinghua.lumaqq.widgets.mac.Ring;
import edu.tsinghua.lumaqq.widgets.qstyle.Blind;
import edu.tsinghua.lumaqq.widgets.qstyle.Slat;
import edu.tsinghua.lumaqq.widgets.rich.LineStyle;
import edu.tsinghua.lumaqq.widgets.rich2.Animator;

/**
 * @author luma
 */
public class MainShell implements IQQShowListener {
    private Shell shell;
    private Display display;
    private TrayItem item;
    private Blind blind;
    private Label btnSysMsg;
    private Label btnSysMenu;
    private Ring statusRing;
    private QQClient client;
    private Resources res;

    // 
    private ClusterCustomFaceReceiver faceReceiver;
    // QQ?
    private QQEventProcessor processor;
    // ?
    private Sounder sounder;
    // 
    private IExecutor diskJobQueue;
    // ???
    private IExecutor imJobQueue;
    // model
    private User myModel;
    // ?
    private String currentOnlineNumber;
    // ??null?
    private Font font;
    private LineStyle defaultStyle;
    // null?
    private Color groupColor;
    // ip
    private IPSeeker seeker;
    // ??
    private List<User> currentOnlines;
    // ??
    private RecordManager rm;
    // QQ?
    private QQShowManager sm;
    // ?
    private OptionHelper optionHelper;
    // ?
    private MessageQueue mq;
    // ??
    private Point shellLocation;

    // ???
    private boolean groupDirty;

    // ???
    private Map<Integer, Group> deleteToMap;
    // ?
    private ShellRegistry shellRegistry;
    // ??
    private ShellLauncher shellLauncher;

    // ?
    private ConfigHelper configHelper;
    // blind
    private BlindHelper blindHelper;
    // UI
    private UIHelper uiHelper;
    // 
    private ExportHelper exportHelper;
    // ??
    private MessageHelper messageHelper;
    // ??
    private MenuHelper menuHelper;
    // ???
    private TipHelper tipHelper;
    // 
    private TimerHelper timerHelper;

    private ClusterCategoryTool ccu;

    private IHotkeyListener messageKeyListener;
    private Listener falseMessageKeyListener;
    private Label btnSMS;

    // ??
    private Composite[] panels;

    public static final int PANEL_MAIN = 0;
    public static final int PANEL_WAITING = 1;
    private Label waitingPanelHint;
    private Animator waitingPanelAnimator;
    private int currentPanel;

    // true???bar
    private boolean mouseOnBar;

    // view
    private Control[] views;
    public static final int VIEW_FRIEND = 0;
    public static final int VIEW_DISK = 1;
    private Composite viewContainer;

    // 
    private DiskManager diskManager;
    private TreeViewer diskViewer;
    private CLabel lblDiskHint;
    private Composite passwordPanel;
    private Composite diskCenter;
    private ToolItem tiUpload;
    private ToolItem tiDownload;
    private ToolItem tiRefresh;
    private Composite diskTreePanel;
    private ToolItem tiAbort;
    private ToolItem tiAbortAll;

    /**
     * ?
     */
    private void initialize() {
        this.display = Display.getCurrent();
        res = Resources.getInstance();

        // ?
        initLayout();

        initVariables();

        // ???
        menuHelper = new MenuHelper(this);
        menuHelper.initMenu();
    }

    /**
     * ???
     */
    private void initVariables() {
        mq = new MessageQueue();
        seeker = IPSeeker.getInstance();
        processor = new QQEventProcessor(this);
        faceReceiver = new ClusterCustomFaceReceiver(this);
        deleteToMap = new Hashtable<Integer, Group>();
        shellRegistry = new ShellRegistry();
        shellLauncher = new ShellLauncher(this);
        uiHelper = new UIHelper(this);
        exportHelper = new ExportHelper(this);
        messageHelper = new MessageHelper(this);
        blindHelper = new BlindHelper(this);
        tipHelper = new TipHelper(this);
        timerHelper = new TimerHelper();
        currentOnlineNumber = search_current_online_unknown;
        sounder = new Sounder();
        diskJobQueue = new BackgroundJobExecutor(this);
        diskJobQueue.setExitOnEmpty(false);
        imJobQueue = new BackgroundJobExecutor(this);
        imJobQueue.setExitOnEmpty(false);
        currentOnlines = new ArrayList<User>();
        mouseOnBar = false;
        ccu = new ClusterCategoryTool();
        ccu.init();
        messageKeyListener = new IHotkeyListener() {
            public void keyPressed() {
                onMessageKey();
            }
        };
        falseMessageKeyListener = new Listener() {
            public void handleEvent(Event e) {
                if (e.keyCode == 'z' && (e.stateMask & SWT.CTRL) != 0 && (e.stateMask & SWT.ALT) != 0) {
                    onMessageKey();
                }
            }
        };
    }

    /**
     * ???
     * 
     * @param panelId
     */
    public void switchPanel(int panelId) {
        BorderStyler styler = (BorderStyler) shell.getData(BorderStyler.STYLER);
        StackLayout layout = (StackLayout) styler.getCenter().getLayout();
        layout.topControl = panels[panelId];
        currentPanel = panelId;
        styler.getCenter().layout();
    }

    /**
     * ?
     * 
     * @param viewId
     */
    public void switchView(int viewId) {
        StackLayout layout = (StackLayout) viewContainer.getLayout();
        layout.topControl = views[viewId];
        viewContainer.layout();
    }

    /**
     * ???
     */
    public void checkGroupDirty() {
        // 
        if (isGroupDirty() && client.getUser().isLoggedIn()) {
            if (optionHelper.getAutoUploadGroup() == OpType.PROMPT_LITERAL) {
                if (MessageDialog.openQuestion(shell, message_box_common_question_title,
                        message_box_upload_group)) {
                    uploadGroup();
                }
            } else if (optionHelper.getAutoUploadGroup() == OpType.ALWAYS_LITERAL)
                uploadGroup();
        }
    }

    /**
     * 
     */
    private void uploadGroup() {
        IExecutor executor = new DialogJobExecutor(this);
        executor.addJob(new UploadGroupJob());
        executor.execute();
    }

    public boolean isGroupDirty() {
        return groupDirty;
    }

    /**
     * @return
     *       Shell
     */
    public Shell getShell() {
        return shell;
    }

    /**
     * @return
     *       ?
     */
    public Blind getBlind() {
        return blind;
    }

    /**
     * shell
     */
    public void close() {
        if (shell != null && !shell.isDisposed())
            shell.close();
    }

    public void setClient(QQClient client) {
        this.client = client;

        // ?
        if (configHelper == null)
            configHelper = new ConfigHelper(this);
        if (optionHelper == null)
            optionHelper = new OptionHelper();
        configHelper.initSystemOptions();
        // ?model
        configHelper.saveSelf();
        configHelper.initSelf();
        myModel = configHelper.getMyself();
        // ????
        if (display == null)
            initialize();
        // ??
        configHelper.postInitSystemOptions();
        // ?
        setTitle(String.valueOf(myModel.qq));
        // ???
        initAutoDock();
        // ??
        configHelper.initRemarks();
        // ???
        configHelper.initReplies();
        // ??
        configHelper.initProxies();
        // ?
        configHelper.initFaces();
        // ?model
        blindHelper.initModels();
        blindHelper.setTreeMode(optionHelper.isTreeMode());
        // ???
        initMessageManager(myModel.qq);
        // ?QQ?
        initQQShowManager(myModel.qq);

        // ?
        sounder.setEnable(optionHelper.isEnableSound());
        // Helper?
        menuHelper.setClient(client);
    }

    /**
     * ?
     * 
     * @param string
     */
    private void setTitle(String string) {
        shell.setText(string);
        BorderStyler styler = (BorderStyler) shell.getData(BorderStyler.STYLER);
        styler.repaintTitleBar();
    }

    /**
     * shell??
     */
    private void initAutoDock() {
        if (shell.getData(AutoDockManager.DOCK_MANAGER) == null) {
            BorderStyler styler = (BorderStyler) shell.getData(BorderStyler.STYLER);
            new AutoDockManager(this).addDockSupport(shell, styler);
        }
    }

    /**
     * ???
     */
    private void initMessageManager(int qqNum) {
        rm = new RecordManager(LumaQQ.RECORD_DIRECTORY);
    }

    /**
    * ?QQ?
    * @param qq
    */
    private void initQQShowManager(int qqNum) {
        if (sm != null)
            sm.removeQQShowListener(this);
        sm = QQShowManager.getInstance(LumaQQ.INSTALL_DIR + "/" + String.valueOf(qqNum));
        sm.addQQShowListener(this);
    }

    /**
     * shell
     */
    public void open() {
        sounder.start();
        diskJobQueue.execute();
        imJobQueue.execute();
        // ?Tray Icon
        initTray();

        // ???
        Rectangle bound = new Rectangle(optionHelper.getLocationX(), optionHelper.getLocationY(), 0, 0);
        bound.width = optionHelper.getWidth();
        bound.height = optionHelper.getHeight();
        if (bound.width == -1)
            bound.width = 200;
        if (bound.height == -1)
            bound.height = 500;
        shell.setSize(bound.width, bound.height);
        shell.setLocation(bound.x, bound.y);
        // ?????controlMoved???
        // ???
        shellLocation = shell.getLocation();
        // shell
        shell.layout();
        shell.open();
        // 
        checkLogin(false, false);
        // 
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
    }

    /**
     * ???
     * 
     * @param forceRandom
     *       true??
     * @return
     *       ?false?true
     */
    public boolean checkLogin(boolean forceRandom, boolean forceTcp) {
        // ?
        if (client.isLogging())
            return false;
        // ?
        if (!client.getUser().isLoggedIn()) {
            // ??
            if (getCurrentPanel() != PANEL_WAITING) {
                switchPanel(PANEL_WAITING);
                setWaitingPanelHint(hint_login);
                resumeWaitingPanelAnimation();
            }
            // tcp??tcp
            boolean useTcp = forceTcp ? true : optionHelper.isUseTcp();
            // ??
            if (optionHelper.isAutoSelect() || forceRandom) {
                String[] servers = useTcp ? LumaQQ.tcpServers : LumaQQ.udpServers;
                client.setLoginServer(servers[Util.random().nextInt(servers.length)]);
            } else {
                if (forceTcp && optionHelper.isUseTcp() != forceTcp)
                    client.setLoginServer(LumaQQ.tcpServers[0]);
                else
                    client.setLoginServer(optionHelper.getServer());
            }
            // TCP?TCP???
            if (useTcp) {
                client.getUser().setUdp(false);
                client.setTcpLoginPort(optionHelper.getTcpPort());
            } else
                client.getUser().setUdp(true);
            // ???????
            try {
                client.setProxy(new InetSocketAddress(optionHelper.getProxyServer(), optionHelper.getProxyPort()));
                client.setProxyType(optionHelper.getProxyType().getName());
                client.setProxyUsername(optionHelper.getProxyUsername());
                client.setProxyPassword(optionHelper.getProxyPassword());
            } catch (IllegalArgumentException e) {
                client.setProxyType("None");
            }
            // ??
            client.getUser().setShowFakeCam(optionHelper.isShowFakeCam());
            // 
            uiHelper.startStatusAnimation();
            processor.clear();
            client.addQQListener(processor);
            client.addQQListener(faceReceiver);
            try {
                client.login();
            } catch (Exception e) {
                client.getUser().setStatus(QQ.QQ_STATUS_OFFLINE);
                logout();
                MessageDialog.openError(shell, message_box_common_fail_title, e.getMessage());
            }
            return false;
        }
        return true;
    }

    /**
     * ?
     */
    public void logout() {
        // 
        client.logout();
        // ?
        uiHelper.stopStatusAnimation();
        uiHelper.stopBlinkSystemMessageIcon();
        uiHelper.stopBlinkImage();
        blindHelper.stopAllEffectOnBlind();
        // 
        timerHelper.dispose();
        // ??????
        uiHelper.setTrayIconByStatus();
        uiHelper.setAllFriendOffline();
        mq.clear();
    }

    /**
      * ?
      */
    private void initTray() {
        // Tray Icon
        Tray tray = display.getSystemTray();
        if (tray == null)
            return;
        item = new TrayItem(tray, SWT.NONE);
        // 
        item.setImage(res.getImage(Resources.icoOffline));
        // listener
        // ?
        item.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                if (mq.hasNext()) {
                    populateMessage();
                } else {
                    menuHelper.setStatusMenuVisible(false);
                    if (shell.getMinimized()) {
                        shell.setVisible(true);
                        shell.setMinimized(false);
                    } else if (isAutoDockEnabled()) {
                        if (isDocking())
                            pend();
                        else if (!isPending()) {
                            tipHelper.closeFriendTipShell();
                            shell.setMinimized(true);
                            shell.setVisible(false);
                        }
                    } else {
                        shell.setMinimized(true);
                        if (optionHelper.isHideWhenMinimize())
                            shell.setVisible(false);
                    }
                }
            }
        });
        // ?
        item.addListener(SWT.MenuDetect, new Listener() {
            public void handleEvent(Event event) {
                if (menuHelper.isSystemMenuVisible())
                    menuHelper.setSystemMenuVisible(false);
                else {
                    menuHelper.setSystemMenuData(1);
                    menuHelper.setSystemMenuLocation(display.getCursorLocation());
                    menuHelper.setSystemMenuVisible(true);
                }
            }
        });
        item.setToolTipText("LumaQQ " + String.valueOf(client.getUser().getQQ()));
    }

    /**
     * ???
     */
    protected void populateMessage() {
        switch (mq.nextMessageSource()) {
        case QQ.QQ_IM_FROM_SYS:
            InPacket in = mq.getSystemMessage();
            shellLauncher.openReceiveSystemMessageShell(in);
            if (in instanceof SystemNotificationPacket) {
                SystemNotificationPacket packet = (SystemNotificationPacket) in;
                if (packet.type == QQ.QQ_SYS_ADD_FRIEND_APPROVED
                        || packet.type == QQ.QQ_SYS_ADD_FRIEND_APPROVED_AND_ADD)
                    blindHelper.addFriend(packet.from);
            }
            break;
        case QQ.QQ_IM_FROM_SMS:
            onSMS();
            break;
        case QQ.QQ_IM_FROM_TEMP_SESSION:
            shellLauncher.openTempSessionIMWindow(mq.nextSender());
            break;
        default:
            shellLauncher.openIMShell(mq.nextSender());
            break;
        }
    }

    /**
     * ??
     */
    protected void pend() {
        ((AutoDockManager) getShell().getData(AutoDockManager.DOCK_MANAGER)).pend();
    }

    /**
     * @return
     *       QQClient
     */
    public QQClient getClient() {
        return client;
    }

    public MenuHelper getMenuHelper() {
        return menuHelper;
    }

    public Display getDisplay() {
        return display;
    }

    /**
     * 
     */
    public void createDefaultStyle() {
        String fontName = optionHelper.getFontName();
        if (fontName.equals(""))
            fontName = default_font;
        int style = SWT.NORMAL;
        if (optionHelper.getBold())
            style |= SWT.BOLD;
        if (optionHelper.getItalic())
            style |= SWT.ITALIC;

        int c = optionHelper.getFontColor();
        defaultStyle = new LineStyle(new Color(display, (c >>> 16) & 0xFF, (c >>> 8) & 0xFF, c & 0xFF), null,
                fontName, style, optionHelper.getFontSize());
    }

    /**
     * 
     * 
     * @param color
     *       
     */
    public void setGroupColor(Color color) {
        groupColor = color;
        blind.setContentBackground(groupColor);
    }

    /**
     * ??
     */
    private void initLayout() {
        Composite body = initShell();
        body.setLayout(new StackLayout());

        panels = new Composite[2];
        panels[PANEL_MAIN] = initMainPanel(body);
        panels[PANEL_WAITING] = initWaitingPanel(body);

        switchView(VIEW_FRIEND);
        switchPanel(PANEL_WAITING);
    }

    /**
     * ???
     * 
     * @param body
     */
    private Composite initWaitingPanel(Composite body) {
        Composite panel = new Composite(body, SWT.NONE | SWT.NO_BACKGROUND);
        panel.setLayout(new FormLayout());
        panel.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                Color blue = new Color(display, 0xD5, 0xEF, 0xFF);
                Composite panel = (Composite) e.getSource();
                Rectangle rect = panel.getClientArea();
                int height = rect.height >> 2;
                e.gc.setForeground(blue);
                e.gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
                e.gc.fillGradientRectangle(0, 0, rect.width, height, true);
                e.gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
                e.gc.setBackground(blue);
                e.gc.fillGradientRectangle(0, rect.height - height, rect.width, height, true);
                e.gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
                e.gc.fillRectangle(0, height, rect.width, rect.height - height - height);
                blue.dispose();
            }
        });
        // logo
        final Label lblLogo = new Label(panel, SWT.CENTER);
        lblLogo.setImage(res.getImage(Resources.bmpLumaQQ));
        FormData fd = new FormData();
        fd.left = new FormAttachment(0, 0);
        fd.top = new FormAttachment(0, 0);
        fd.height = 47;
        fd.width = 160;
        lblLogo.setLayoutData(fd);
        // ?
        waitingPanelAnimator = new Animator(panel);
        waitingPanelAnimator.setLoader(res.getImageLoader(Resources.bmpProgress));
        fd = new FormData();
        fd.left = new FormAttachment(0, 0);
        fd.top = new FormAttachment(0, 0);
        fd.height = 19;
        fd.width = 108;
        waitingPanelAnimator.setLayoutData(fd);
        // ??
        waitingPanelHint = new Label(panel, SWT.CENTER);
        waitingPanelHint.setBackground(Colors.WHITE);
        waitingPanelHint.setText(hint_login);
        fd = new FormData();
        fd.left = new FormAttachment(0, 0);
        fd.top = new FormAttachment(0, 0);
        fd.width = 160;
        waitingPanelHint.setLayoutData(fd);
        // ?
        final Label lblCancel = new Label(panel, SWT.CENTER);
        lblCancel.setImage(res.getImage(Resources.bmpCancelLoginNormal));
        fd = new FormData();
        fd.left = new FormAttachment(0, 0);
        fd.top = new FormAttachment(0, 0);
        fd.height = 26;
        fd.width = 90;
        lblCancel.setLayoutData(fd);
        lblCancel.addMouseTrackListener(new MouseTrackAdapter() {
            @Override
            public void mouseEnter(MouseEvent e) {
                lblCancel.setImage(res.getImage(Resources.bmpCancelLoginHover));
            }

            @Override
            public void mouseExit(MouseEvent e) {
                lblCancel.setImage(res.getImage(Resources.bmpCancelLoginNormal));
            }
        });
        lblCancel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDown(MouseEvent e) {
                lblCancel.setImage(res.getImage(Resources.bmpCancelLoginDown));
            }

            @Override
            public void mouseUp(MouseEvent e) {
                lblCancel.setImage(res.getImage(Resources.bmpCancelLoginNormal));
                restartLogin(null);
            }
        });

        panel.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Composite panel = (Composite) e.getSource();
                Rectangle rect = panel.getClientArea();

                int childrenHeight = 47 + 5 + 19 + 5 + 20 + 10 + 26;
                int reference = (rect.height - childrenHeight) >> 1;
                FormData fd = (FormData) lblLogo.getLayoutData();
                fd.left.offset = (rect.width - 160) >> 1;
                fd.top.offset = reference;

                fd = (FormData) waitingPanelAnimator.getLayoutData();
                fd.left.offset = (rect.width - 108) >> 1;
                fd.top.offset = reference + 52;

                fd = (FormData) waitingPanelHint.getLayoutData();
                fd.left.offset = (rect.width - 160) >> 1;
                fd.top.offset = reference + 47 + 5 + 19 + 5;

                fd = (FormData) lblCancel.getLayoutData();
                fd.left.offset = (rect.width - 90) >> 1;
                fd.top.offset = reference + 47 + 5 + 19 + 5 + 20 + 10;

                panel.layout();
            }
        });

        return panel;
    }

    /**
     * ????
     * 
     * @param text
     */
    public void setWaitingPanelHint(String text) {
        waitingPanelHint.setText(text);
    }

    /**
     * ???
     */
    public void stopWaitingPanelAnimation() {
        waitingPanelAnimator.stopAnimation();
    }

    /**
     * ???
     */
    public void resumeWaitingPanelAnimation() {
        waitingPanelAnimator.resumeAnimation();
    }

    /**
     * ???
     * 
     * @param body
     */
    private Composite initMainPanel(Composite body) {
        Composite panel = new Composite(body, SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.marginHeight = layout.marginWidth = layout.horizontalSpacing = layout.verticalSpacing = 0;
        panel.setLayout(layout);

        viewContainer = new Composite(panel, SWT.NONE);
        viewContainer.setLayoutData(new GridData(GridData.FILL_BOTH));
        viewContainer.setLayout(new StackLayout());
        viewContainer.setBackground(Colors.MAINSHELL_BACKGROUND);

        // ?
        views = new Control[2];
        initFriendView();
        initDiskView();

        // expand bar
        Label lblBar = new Label(panel, SWT.LEFT);
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        gd.heightHint = 4;
        lblBar.setLayoutData(gd);
        lblBar.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                boolean expanded = optionHelper.isBarExpanded();
                Color color = null;
                if (mouseOnBar)
                    color = new Color(display, 0xA2, 0xD3, 0x75);
                else
                    color = new Color(display, 0x5E, 0xB6, 0xF6);
                Label lbl = (Label) e.getSource();
                Rectangle rect = lbl.getBounds();
                rect.x = rect.y = 0;
                e.gc.setBackground(color);
                e.gc.fillRectangle(rect);
                color.dispose();

                int middle = rect.width >> 1;
                color = new Color(display, 0x39, 0x69, 0xB9);
                e.gc.setForeground(color);
                if (expanded) {
                    e.gc.drawLine(middle - 3, 0, middle + 2, 0);
                    e.gc.drawLine(middle - 2, 1, middle + 1, 1);
                    e.gc.drawLine(middle - 1, 2, middle, 2);
                } else {
                    e.gc.drawLine(middle - 1, 1, middle, 1);
                    e.gc.drawLine(middle - 2, 2, middle + 1, 2);
                    e.gc.drawLine(middle - 3, 3, middle + 2, 3);
                }
                color.dispose();
            }
        });
        lblBar.addMouseTrackListener(new MouseTrackAdapter() {
            @Override
            public void mouseEnter(MouseEvent e) {
                mouseOnBar = true;
                ((Label) e.getSource()).redraw();
            }

            @Override
            public void mouseExit(MouseEvent e) {
                mouseOnBar = false;
                ((Label) e.getSource()).redraw();
            }
        });
        lblBar.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                optionHelper.setBarExpanded(!optionHelper.isBarExpanded());
                ((Label) e.getSource()).redraw();
            }
        });

        /*  */
        // 
        Composite buttonContainer = new Composite(panel, SWT.NONE);
        gd = new GridData(GridData.FILL_HORIZONTAL);
        gd.heightHint = 24;
        buttonContainer.setLayoutData(gd);
        layout = new GridLayout(5, false);
        layout.marginHeight = layout.marginWidth = layout.verticalSpacing = 0;
        layout.horizontalSpacing = 6;
        buttonContainer.setLayout(layout);
        buttonContainer.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                paintBackground(e, ((Composite) e.getSource()).getClientArea().width,
                        res.getImage(Resources.bmpToolbarBackground));
            }
        });
        // ??
        btnSysMenu = new Label(buttonContainer, SWT.LEFT);
        btnSysMenu.setImage(res.getImage(Resources.bmpMenuNormal));
        gd = new GridData(GridData.FILL_VERTICAL);
        btnSysMenu.setLayoutData(gd);
        btnSysMenu.setToolTipText(tooltip_button_sysmenu);
        btnSysMenu.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDown(MouseEvent e) {
                btnSysMenu.setImage(res.getImage(Resources.bmpMenuDown));
            }

            public void mouseUp(MouseEvent e) {
                btnSysMenu.setImage(res.getImage(Resources.bmpMenuHover));
                if (menuHelper.isSystemMenuVisible())
                    menuHelper.setSystemMenuVisible(false);
                else {
                    menuHelper.setSystemMenuData(0);
                    menuHelper.setSystemMenuLocation(btnSysMenu.toDisplay(btnSysMenu.getLocation()));
                    menuHelper.setSystemMenuVisible(true);
                }
            }
        });
        btnSysMenu.addMouseTrackListener(new MouseTrackAdapter() {
            @Override
            public void mouseEnter(MouseEvent e) {
                btnSysMenu.setImage(res.getImage(Resources.bmpMenuHover));
            }

            @Override
            public void mouseExit(MouseEvent e) {
                btnSysMenu.setImage(res.getImage(Resources.bmpMenuNormal));
            }
        });
        // ?
        btnSysMsg = new Label(buttonContainer, SWT.CENTER);
        btnSysMsg.setImage(res.getImage(Resources.icoSysMsg));
        btnSysMsg.setToolTipText(tooltip_button_sysmsg);
        btnSysMsg.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        btnSysMsg.setCursor(display.getSystemCursor(SWT.CURSOR_HAND));
        btnSysMsg.addMouseListener(new MouseAdapter() {
            public void mouseUp(MouseEvent e) {
                if (mq.hasSystemMessage()) {
                    InPacket in = (InPacket) mq.getSystemMessage();
                    shellLauncher.openReceiveSystemMessageShell(in);
                    if (in instanceof SystemNotificationPacket) {
                        SystemNotificationPacket packet = (SystemNotificationPacket) in;
                        if (packet.type == QQ.QQ_SYS_ADD_FRIEND_APPROVED
                                || packet.type == QQ.QQ_SYS_ADD_FRIEND_APPROVED_AND_ADD)
                            blindHelper.addFriend(packet.from);
                    }
                } else
                    shellLauncher.openSystemMessageListWindow();
            }
        });
        btnSysMsg.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                Label label = (Label) e.getSource();
                Rectangle bound = label.getBounds();
                paintBackground(e, bound.width, res.getImage(Resources.bmpToolbarBackground));
                if (label.getImage() != null)
                    e.gc.drawImage(label.getImage(), 0, (bound.height - label.getImage().getBounds().height) >>> 1);
            }
        });
        // 
        Label btnFind = new Label(buttonContainer, SWT.CENTER);
        btnFind.setImage(res.getImage(Resources.icoSearch));
        btnFind.setToolTipText(tooltip_button_search);
        btnFind.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        btnFind.setCursor(display.getSystemCursor(SWT.CURSOR_HAND));
        btnFind.addMouseListener(new MouseAdapter() {
            public void mouseUp(MouseEvent e) {
                if (client.getUser().isLoggedIn())
                    shellLauncher.openSearchWizard();
            }
        });
        btnFind.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                Label label = (Label) e.getSource();
                Rectangle bound = label.getBounds();
                paintBackground(e, bound.width, res.getImage(Resources.bmpToolbarBackground));
                e.gc.drawImage(label.getImage(), 0, (bound.height - label.getImage().getBounds().height) >>> 1);
            }
        });
        // 
        btnSMS = new Label(buttonContainer, SWT.CENTER);
        btnSMS.setImage(res.getImage(Resources.icoMobile));
        btnSMS.setToolTipText(tooltip_button_sms);
        btnSMS.setCursor(display.getSystemCursor(SWT.CURSOR_HAND));
        btnSMS.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        btnSMS.addMouseListener(new MouseAdapter() {
            public void mouseUp(MouseEvent e) {
                onSMS();
            }
        });
        btnSMS.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                Label label = (Label) e.getSource();
                Rectangle bound = label.getBounds();
                paintBackground(e, bound.width, res.getImage(Resources.bmpToolbarBackground));
                if (label.getImage() != null)
                    e.gc.drawImage(label.getImage(), 0, (bound.height - label.getImage().getBounds().height) >>> 1);
            }
        });
        // ?
        statusRing = new Ring(buttonContainer);
        gd = new GridData(GridData.FILL_BOTH);
        statusRing.setLayoutData(gd);
        statusRing.setMinWidth(18);
        statusRing.setBackground(Colors.MAINSHELL_BACKGROUND);
        statusRing.setTiledBackground(res.getImage(Resources.bmpToolbarBackground));
        statusRing.setMaxAvailableHeight(18);
        statusRing.setCursor(display.getSystemCursor(SWT.CURSOR_HAND));
        statusRing.addMouseListener(new MouseAdapter() {
            public void mouseUp(MouseEvent e) {
                menuHelper.setStatusMenuLocation(statusRing.toDisplay(e.x, e.y));
                menuHelper.setStatusMenuVisible(true);
            }
        });

        return panel;
    }

    /**
     * 
     */
    public void refreshDiskViewer() {
        diskViewer.refresh();
    }

    /**
     * ???
     * 
     * @param hint
     */
    public void setDiskOpHint(String hint) {
        lblDiskHint.setText(hint);
    }

    /**
     * ?
     */
    private void initDiskView() {
        Composite comp = new Composite(viewContainer, SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.marginHeight = layout.marginWidth = layout.horizontalSpacing = layout.verticalSpacing = 0;
        comp.setLayout(layout);
        comp.setBackground(Colors.LIGHT_BLUE_2);

        // ??
        diskCenter = new Composite(comp, SWT.NONE);
        diskCenter.setLayoutData(new GridData(GridData.FILL_BOTH));
        diskCenter.setLayout(new StackLayout());

        diskTreePanel = new Composite(diskCenter, SWT.NONE);
        layout = new GridLayout();
        layout.marginHeight = layout.marginWidth = layout.horizontalSpacing = layout.verticalSpacing = 0;
        diskTreePanel.setLayout(layout);

        // ?
        ToolBar tb = new ToolBar(diskTreePanel, SWT.FLAT);
        tb.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        tb.setBackground(Colors.LIGHT_BLUE_2);
        tiRefresh = new ToolItem(tb, SWT.PUSH);
        tiRefresh.setImage(res.getImage(Resources.icoRefresh));
        tiRefresh.setToolTipText(disk_tooltip_refresh);
        tiRefresh.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                IStructuredSelection s = (IStructuredSelection) diskViewer.getSelection();
                diskManager.refresh(s.getFirstElement());
                diskViewer.refresh();
            }
        });
        tiDownload = new ToolItem(tb, SWT.PUSH);
        tiDownload.setImage(res.getImage(Resources.icoDiskDownload));
        tiDownload.setToolTipText(disk_tooltip_download);
        tiDownload.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                onDiskDownload((IStructuredSelection) diskViewer.getSelection());
            }
        });
        tiUpload = new ToolItem(tb, SWT.PUSH);
        tiUpload.setImage(res.getImage(Resources.icoDiskUpload));
        tiUpload.setToolTipText(disk_tooltip_upload);
        tiUpload.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                onDiskUpload((IStructuredSelection) diskViewer.getSelection());
            }
        });
        // separator
        new ToolItem(tb, SWT.SEPARATOR);
        // ?
        ToolItem tiPassword = new ToolItem(tb, SWT.PUSH);
        tiPassword.setImage(res.getImage(Resources.icoDiskPassword));
        tiPassword.setToolTipText(disk_tooltip_password);
        tiPassword.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                onDiskPassword();
            }
        });

        // 
        diskViewer = new TreeViewer(diskTreePanel, SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE);
        diskViewer.getTree().setLayoutData(new GridData(GridData.FILL_BOTH));
        diskManager = new DiskManager(this);
        diskViewer.setContentProvider(new DiskContentProvider(this));
        diskViewer.setLabelProvider(new DiskLabelProvider(this));
        diskViewer.setSorter(new DiskViewerSorter());
        diskViewer.setInput(this);
        diskViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                onDiskViewerSelection(event);
            }
        });
        diskViewer.getTree().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                if (e.button == 3) {
                    menuHelper.setDiskMenuLocation(display.map(diskViewer.getTree(), null, e.x, e.y));
                    menuHelper.setDiskMenuVisible(true);
                } else
                    menuHelper.hideAllMenu();
            }
        });
        diskViewer.addDragSupport(DND.DROP_MOVE, new Transfer[] { TextTransfer.getInstance() },
                new DragSourceListener() {
                    public void dragStart(DragSourceEvent event) {
                        IStructuredSelection s = (IStructuredSelection) diskViewer.getSelection();
                        if (s.isEmpty())
                            event.doit = false;

                        Object obj = s.getFirstElement();
                        if (obj instanceof Directory) {
                            if (!diskManager.isMovable((Directory) obj))
                                event.doit = false;
                        } else if (obj instanceof File) {
                            if (!diskManager.isMovable((File) obj))
                                event.doit = false;
                        } else
                            event.doit = false;
                    }

                    public void dragSetData(DragSourceEvent event) {
                        event.data = "dummy";
                        IStructuredSelection s = (IStructuredSelection) diskViewer.getSelection();
                        Object obj = s.getFirstElement();
                        DragHelper.setObject(obj);
                    }

                    public void dragFinished(DragSourceEvent event) {
                    }
                });
        diskViewer.addDropSupport(DND.DROP_MOVE, new Transfer[] { TextTransfer.getInstance() },
                new DropTargetListener() {
                    public void dragEnter(DropTargetEvent event) {
                    }

                    public void dragLeave(DropTargetEvent event) {
                    }

                    public void dragOperationChanged(DropTargetEvent event) {
                    }

                    public void dragOver(DropTargetEvent event) {
                        TreeItem item = (TreeItem) event.item;
                        if (item == null) {
                            event.detail = DND.DROP_NONE;
                        } else if (item.getData() instanceof Integer) {
                            boolean accept = ((Integer) item.getData()) == DiskContentProvider.MY_DISK;
                            event.detail = accept ? event.operations : DND.DROP_NONE;
                            if (accept)
                                event.feedback |= DND.FEEDBACK_EXPAND;
                        } else if (item.getData() instanceof Directory) {
                            boolean accept = diskManager.isChildCreatable((Directory) item.getData());
                            event.detail = accept ? event.operations : DND.DROP_NONE;
                            if (accept)
                                event.feedback |= DND.FEEDBACK_EXPAND;
                        } else
                            event.detail = DND.DROP_NONE;
                    }

                    public void drop(DropTargetEvent event) {
                        Object moved = DragHelper.getObject();
                        boolean moveFile = moved instanceof File;
                        Object obj = event.item.getData();
                        if (obj instanceof Integer) {
                            if (moveFile)
                                diskJobQueue.addJob(new MoveJob((File) moved, 0));
                            else
                                diskJobQueue.addJob(new MoveJob((Directory) moved, 0));
                        } else if (obj instanceof Directory) {
                            if (moveFile)
                                diskJobQueue.addJob(new MoveJob((File) moved, ((Directory) obj).id));
                            else
                                diskJobQueue.addJob(new MoveJob((Directory) moved, ((Directory) obj).id));
                        }
                    }

                    public void dropAccept(DropTargetEvent event) {
                    }
                });

        // ???
        passwordPanel = new Composite(diskCenter, SWT.NONE);
        passwordPanel.setBackground(Colors.WHITE);
        layout = new GridLayout();
        layout.verticalSpacing = 15;
        layout.marginWidth = layout.marginHeight = 15;
        passwordPanel.setLayout(layout);
        passwordPanel.addPaintListener(
                new CenterBorderPaintListener(new Class[] { Text.class }, 20, Colors.PAGE_CONTROL_BORDER));
        UITool.setDefaultBackground(passwordPanel.getBackground());
        // ??
        UITool.createLabel(passwordPanel, disk_hint_input_password);
        // ?
        final Text textPassword = UITool.createSingleText(passwordPanel, new GridData(GridData.FILL_HORIZONTAL),
                SWT.SINGLE | SWT.PASSWORD);
        textPassword.setTextLimit(16);
        // 
        Slat btnOK = UITool.createSlat(passwordPanel, button_ok, new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
        btnOK.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                diskManager.setPassword(textPassword.getText());
                switchToDiskViewer();
            }
        });

        // hint bar      
        Composite hintContainer = new Composite(comp, SWT.NONE);
        hintContainer.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        layout = new GridLayout(2, false);
        layout.marginWidth = layout.marginHeight = layout.horizontalSpacing = layout.verticalSpacing = 0;
        hintContainer.setLayout(layout);
        lblDiskHint = new CLabel(hintContainer, SWT.LEFT);
        lblDiskHint.setBackground(Colors.LIGHT_BLUE_2);
        lblDiskHint.setLayoutData(new GridData(GridData.FILL_BOTH));
        lblDiskHint.setText(NLS.bind(disk_hint_capacity, FileTool.getSizeString(diskManager.getUnused()),
                FileTool.getSizeString(diskManager.getCapacity())));
        // ??
        ToolBar cBar = new ToolBar(hintContainer, SWT.FLAT);
        cBar.setBackground(Colors.LIGHT_BLUE_2);
        cBar.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        tiAbort = new ToolItem(cBar, SWT.PUSH);
        tiAbort.setImage(res.getImage(Resources.icoAbort));
        tiAbort.setToolTipText(disk_hint_abort);
        tiAbort.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                ICancelableJob job = (ICancelableJob) tiAbort.getData();
                if (job == null)
                    return;
                job.cancel(ICancelableJob.ABORT);
            }
        });
        tiAbortAll = new ToolItem(cBar, SWT.PUSH);
        tiAbortAll.setImage(res.getImage(Resources.icoAbortAll));
        tiAbortAll.setToolTipText(disk_hint_abort_all);
        tiAbortAll.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                ICancelableJob job = (ICancelableJob) tiAbortAll.getData();
                if (job == null)
                    return;
                job.cancel(ICancelableJob.ABORT_ALL);
            }
        });

        switchToDiskViewer();
        onDiskViewerSelection(null);
        setFileAbortButtonStatus(false);

        views[VIEW_DISK] = comp;
    }

    /**
     * 
     *
     * @param selection
     */
    protected void onDiskUpload(IStructuredSelection selection) {
        FileDialog dialog = new FileDialog(getShell(), SWT.OPEN);
        String path = dialog.open();
        if (path == null)
            return;
        Object obj = selection.getFirstElement();
        String name = FileTool.getFilename(path);

        if (obj instanceof Integer) {
            File f = diskManager.getMyFile(name, 0);
            if (f == null)
                diskJobQueue.addJob(new CreateFileJob(0, path));
            else
                diskJobQueue.addJob(new CreateFileJob(0, path, f));
        } else if (obj instanceof Directory) {
            Directory d = (Directory) obj;
            File f = diskManager.getMyFile(name, d.id);
            if (f == null)
                diskJobQueue.addJob(new CreateFileJob(d.id, path));
            else
                diskJobQueue.addJob(new CreateFileJob(d.id, path, f));
        }
    }

    /**
     * ???data
     * 
     * @param job
     */
    public void hookCancelableJob(ICancelableJob job) {
        tiAbort.setData(job);
        tiAbortAll.setData(job);
    }

    /**
     * ?
     * 
     * @param enable
     */
    public void setFileAbortButtonStatus(boolean enable) {
        tiAbort.setEnabled(enable);
        tiAbortAll.setEnabled(enable);
    }

    /**
     * 
     * 
     * @param e
     */
    public void onDiskDownload(IStructuredSelection s) {
        if (s.isEmpty())
            return;

        Object obj = s.getFirstElement();
        if (obj instanceof File) {
            File f = (File) obj;
            DirectoryDialog dialog = new DirectoryDialog(getShell());
            String dir = dialog.open();
            if (dir != null) {
                // ?
                if (!dir.endsWith(java.io.File.separator))
                    dir += java.io.File.separatorChar;
                java.io.File diskfile = new java.io.File(dir + f.name);
                boolean resume = false;
                if (diskfile.exists()) {
                    MessageDialog msg = new MessageDialog(getShell(), message_box_common_question_title, null,
                            NLS.bind(message_box_resume_file, diskfile.getAbsolutePath()), MessageDialog.QUESTION,
                            new String[] { button_resume, button_overwrite, button_cancel }, 0);
                    switch (msg.open()) {
                    case 0:
                        resume = true;
                        break;
                    case 1:
                        resume = false;
                        break;
                    default:
                        return;
                    }
                }

                // 
                if (f.owner == 0)
                    return;
                diskJobQueue.addJob(new DownloadFileJob(f, dir, resume));
            }
        }
    }

    /**
     * ????
     */
    public void switchToDiskPasswordPanel() {
        StackLayout layout = (StackLayout) diskCenter.getLayout();
        if (layout.topControl != passwordPanel) {
            layout.topControl = passwordPanel;
            diskCenter.layout();
        }
    }

    /**
     * ???
     */
    public void switchToDiskViewer() {
        StackLayout layout = (StackLayout) diskCenter.getLayout();
        if (layout.topControl != diskTreePanel) {
            layout.topControl = diskTreePanel;
            diskCenter.layout();
        }
    }

    /**
     * ?
     */
    protected void onDiskPassword() {
        DiskPasswordDialog dialog = new DiskPasswordDialog(this);
        if (dialog.open() == IDialogConstants.OK_ID) {
            if (dialog.isSetPassword())
                diskJobQueue.addJob(new SetDiskPasswordJob(dialog.getOldPassword(), dialog.getNewPassword()));
            else
                diskJobQueue.addJob(new CancelDiskPasswordJob(dialog.getOldPassword()));
        }
    }

    /**
     * ??
     */
    private void initFriendView() {
        // ? 
        blind = new Blind(viewContainer, SWT.BORDER);
        views[VIEW_FRIEND] = blind;
    }

    /**
     * ?shell
     */
    private Composite initShell() {
        int shellStyle = SWT.NO_TRIM | SWT.NO_BACKGROUND;
        if (optionHelper.isOnTop())
            shellStyle |= SWT.ON_TOP;
        shell = new Shell(display, shellStyle);
        shell.setImage(res.getImage(Resources.icoLumaQQ));
        // ?
        shell.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                if (font != null)
                    font.dispose();
                if (defaultStyle != null)
                    if (defaultStyle.foreground != null)
                        defaultStyle.foreground.dispose();
                if (groupColor != null)
                    groupColor.dispose();
                Colors.dispose();
            }
        });
        shell.addShellListener(new ShellAdapter() {
            public void shellIconified(ShellEvent e) {
                if (item != null && !item.getVisible()) {
                    initTray();
                    uiHelper.setTrayIconByStatus();
                }
                menuHelper.hideAllMenu();
            }

            public void shellClosed(ShellEvent e) {
                // 
                checkGroupDirty();

                // Client
                if (processor != null && client != null)
                    client.removeQQListener(processor);
                // 
                if (client != null) {
                    client.logout();
                    client.release();
                }
                // 
                timerHelper.dispose();
                // ??
                configHelper.saveSelf();
                // ???
                blindHelper.saveModel();
                // Tray Icon
                if (item != null)
                    item.dispose();
                // ?
                if (sounder != null)
                    sounder.setStop(true);
                // ??
                if (optionHelper.getOptionsModel() != null) {
                    // ????
                    if (isOff()) {
                        Rectangle bound = shell.getBounds();
                        optionHelper.setLocationX(bound.x);
                        optionHelper.setLocationY(bound.y);
                        optionHelper.setWidth(bound.width);
                        optionHelper.setHeight(bound.height);
                    }
                    // ??
                    if (defaultStyle != null) {
                        optionHelper.setFontName(defaultStyle.fontName);
                        optionHelper.setFontSize(defaultStyle.fontSize);
                        optionHelper.setBold((defaultStyle.fontStyle & SWT.BOLD) != 0);
                        optionHelper.setItalic((defaultStyle.fontStyle & SWT.ITALIC) != 0);
                    }
                    // ?
                    if (defaultStyle.foreground != null) {
                        RGB rgb = defaultStyle.foreground.getRGB();
                        optionHelper.setFontColor(rgb);
                    }
                    optionHelper.save();
                }
                // ?
                FaceRegistry.getInstance().save();
                // ?
                if (rm != null)
                    rm.dispose();
                // 
                if (ccu != null)
                    ccu.dispose();
            }
        });
        shell.addControlListener(new ControlAdapter() {
            public void controlMoved(ControlEvent e) {
                shellLocation = shell.getLocation();
            }
        });

        BorderStyler styler = new BorderStyler(this);
        styler.setCheckMinimizeWhenClose(true);
        if (!optionHelper.isOnTop())
            styler.setHideWhenMinimize(optionHelper.isHideWhenMinimize());
        styler.setMaximizeWhenDoubleClick(false);
        styler.setShowPlusButton(true);
        styler.setPlusMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                if (menuHelper.isPlusMenuVisible())
                    menuHelper.setPlusMenuVisible(false);
                else {
                    Control c = (Control) e.getSource();
                    Rectangle bound = c.getBounds();
                    menuHelper.setPlusMenuLocation(c.getParent().toDisplay(bound.x, bound.y + bound.height));
                    menuHelper.setPlusMenuVisible(true);
                }
            }
        });
        styler.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDown(MouseEvent e) {
                menuHelper.hideAllMenu();
            }
        });
        return styler.decorateShell(shell);
    }

    /**
     * 
     * 
     * @param e
     * @param clientWidth
     */
    protected void paintBackground(PaintEvent e, int clientWidth, Image tile) {
        int backWidth = tile.getBounds().width;
        int x = 0;
        while (x < clientWidth) {
            e.gc.drawImage(tile, x, 0);
            x += backWidth;
        }
    }

    /**
     * @return Returns the configHelper.
     */
    public ConfigHelper getConfigHelper() {
        return configHelper;
    }

    public MessageHelper getMessageHelper() {
        return messageHelper;
    }

    public User getMyModel() {
        return myModel;
    }

    public List<User> getCurrentOnlines() {
        return currentOnlines;
    }

    public TipHelper getTipHelper() {
        return tipHelper;
    }

    public UIHelper getUIHelper() {
        return uiHelper;
    }

    /**
     * QQ
     * 
     * @param currentOnlineNumber
     *       ?QQ
     */
    public void setCurrentOnlineNumber(String currentOnlineNumber) {
        this.currentOnlineNumber = currentOnlineNumber;
    }

    /**
     * ??
     * 
     * @param key
     *          ?QQ?
     */
    public Group removeDeleteToMap(int key) {
        return deleteToMap.remove(key);
    }

    public void setGroupDirty(boolean groupDirty) {
        this.groupDirty = groupDirty;
    }

    /**
     * ????
     * 
     * @param b
     *          true???
     */
    public void setVisible(boolean b) {
        shell.setVisible(b);
    }

    public BlindHelper getBlindHelper() {
        return blindHelper;
    }

    public void qqShowDownloaded(final int qq) {
        if (shellRegistry.hasUserInfoWindow(qq) && sm.isCached(qq)) {
            display.syncExec(new Runnable() {
                public void run() {
                    UserInfoWindow uis = shellRegistry.getUserInfoWindow(qq);
                    if (uis != null)
                        uis.setQQShow(sm.getQQShowImage(qq));
                }
            });
        }
    }

    /**
     * @return Returns the groupColor.
     */
    public Color getGroupColor() {
        return groupColor;
    }

    public QQShowManager getQQShowManager() {
        return sm;
    }

    /**
     * @return
     *       ShellLauncher
     */
    public ShellLauncher getShellLauncher() {
        return shellLauncher;
    }

    /**
     * @return
     *       ?
     */
    public ShellRegistry getShellRegistry() {
        return shellRegistry;
    }

    public Sounder getSounder() {
        return sounder;
    }

    public ExportHelper getExportHelper() {
        return exportHelper;
    }

    public String getCurrentOnlineNumber() {
        return currentOnlineNumber;
    }

    public MessageQueue getMessageQueue() {
        return mq;
    }

    public IPSeeker getIPSeeker() {
        return seeker;
    }

    public LineStyle getDefaultStyle() {
        return defaultStyle;
    }

    public Color getFontColor() {
        return defaultStyle.foreground;
    }

    /**
     * 
     * 
     * @param fontColor
     *       
     */
    public void setFontColor(Color fontColor) {
        defaultStyle.foreground = fontColor;
    }

    public RecordManager getRecordManager() {
        return rm;
    }

    /**
     * ?
     * 
     * @param f
     *          User
     * @param directly
     *          ?
     * @param removeSelf
     *          ??
     */
    public void deleteFriend(User f, boolean directly, boolean removeSelf) {
        if (directly && f != null) {
            Group g = f.group;
            g.removeUser(f);
            if (g.users.size() == 0)
                blindHelper.collapseGroup(g);
            blindHelper.refreshGroup(g);
        } else {
            // ??
            deleteFriendFromServer(f.qq, true, removeSelf, null);
        }
    }

    /**
    * ??
    * 
    * @param qqNum
    *          ?QQ?0?
    * @param doDelete
    *          ??????QQ??????
    * @param removeSelf
    *          ??
    * @param g
    *          ??null
    */
    public void deleteFriendFromServer(int qqNum, boolean doDelete, boolean removeSelf, Group g) {
        // ???
        if (doDelete)
            client.deleteFriend(qqNum);
        if (removeSelf)
            client.removeSelfFrom(qqNum);
        if (g != null)
            deleteToMap.put(qqNum, g);
    }

    public Label getSystemMenuButton() {
        return btnSysMenu;
    }

    public Label getSMSButton() {
        return btnSMS;
    }

    /**
     * ?
     * @param e
     */
    public void handleRuntimeError(QQEvent e) {
        // ???
        stopWaitingPanelAnimation();
        setVisible(false);
        // 
        getClient().getUser().setStatus(QQ.QQ_STATUS_OFFLINE);
        logout();
        // ?
        CrashDialog dialog = new CrashDialog(this, ((ErrorPacket) e.getSource()).errorMessage);
        dialog.open();
    }

    /**
     * ??
     * 
     * @param e
     */
    public void restartLogin(QQEvent e) {
        // ???
        stopWaitingPanelAnimation();
        setVisible(false);
        // 
        getClient().getUser().setStatus(QQ.QQ_STATUS_OFFLINE);
        logout();
        // ??
        if (e != null) {
            String msg = "";
            if (e.getSource() instanceof LoginReplyPacket)
                msg = ((LoginReplyPacket) e.getSource()).replyMessage;
            else if (e.getSource() instanceof ErrorPacket)
                msg = ((ErrorPacket) e.getSource()).errorMessage;
            MessageDialog.openError(getShell(), message_box_login_fail_title,
                    (msg == null || msg.trim().equals("")) ? error_login_fail : msg);
        }
        relaunchLoginDialog();
    }

    /**
     * ??
     */
    public void relaunchLoginDialog() {
        // ??
        LoginDialog login = new LoginDialog(getShell(), true);
        if (login.open()) {
            // QQ
            QQUser me = new QQUser(login.getQQ(), login.getMd5Password());
            if (login.isLoginHidden()) {
                me.setLoginMode(QQ.QQ_LOGIN_MODE_HIDDEN);
                me.setStatus(QQ.QQ_STATUS_HIDDEN);
            } else {
                me.setLoginMode(QQ.QQ_LOGIN_MODE_NORMAL);
                me.setStatus(QQ.QQ_STATUS_ONLINE);
            }
            // 
            getClient().setUser(me);
            // ?
            LumaQQ.initUserFilePath(me);
            // client???
            setClient(getClient());
            // ?
            if (login.isUseNetworkSetting())
                LumaQQ.syncLoginOption(login.getLogins().getNetwork(), optionHelper);
            // relayout
            getBlind().layout();
            // ?
            setVisible(true);
            setWaitingPanelHint(hint_login);
            resumeWaitingPanelAnimation();
            checkLogin(false, false);
        } else
            close();
    }

    /**
     * ??
     */
    public void changeUser() {
        LoginDialog login = new LoginDialog(shell, true);
        if (login.open()) {
            int qqNum = login.getQQ();
            if (myModel.qq != qqNum) {
                // ??
                switchPanel(PANEL_WAITING);
                setWaitingPanelHint(hint_login);
                resumeWaitingPanelAnimation();
                // 
                client.logout();
                uiHelper.setTrayIconByStatus();
                // QQ
                QQUser me = new QQUser(qqNum, login.getMd5Password());
                if (login.isLoginHidden()) {
                    me.setLoginMode(QQ.QQ_LOGIN_MODE_HIDDEN);
                    me.setStatus(QQ.QQ_STATUS_HIDDEN);
                } else {
                    me.setLoginMode(QQ.QQ_LOGIN_MODE_NORMAL);
                    me.setStatus(QQ.QQ_STATUS_ONLINE);
                }
                // QQ
                client.setUser(me);
                // ?
                LumaQQ.initUserFilePath(me);
                // client
                setClient(this.client);
                // ?
                if (login.isUseNetworkSetting())
                    LumaQQ.syncLoginOption(login.getLogins().getNetwork(), optionHelper);
                // traytooltip
                if (item != null)
                    item.setToolTipText("LumaQQ " + String.valueOf(myModel.qq));
                // 
                checkLogin(false, false);
            }
        }
    }

    public ClusterCustomFaceReceiver getFaceReceiver() {
        return faceReceiver;
    }

    /**
     * @param f
     */
    public void addOnline(User f) {
        currentOnlines.add(f);
    }

    public Label getSystemMessageButton() {
        return btnSysMsg;
    }

    public TrayItem getTrayItem() {
        return item;
    }

    public Ring getStatusRing() {
        return statusRing;
    }

    /**
     * @return Returns the optionHelper.
     */
    public OptionHelper getOptionHelper() {
        return optionHelper;
    }

    public ClusterCategoryTool getClusterCategoryUtility() {
        return ccu;
    }

    /**
     * ????
     */
    public void onMessageKey() {
        if (mq.hasNext()) {
            populateMessage();
        } else {
            menuHelper.setStatusMenuVisible(false);
            if (shell.getMinimized()) {
                shell.setLocation(shellLocation);
                shell.setMinimized(false);
                shell.setVisible(true);
            } else if (isAutoDockEnabled() && isDocking())
                pend();
        }
    }

    public IHotkeyListener getMessageKeyListener() {
        return messageKeyListener;
    }

    public Listener getFalseMessageKeyListener() {
        return falseMessageKeyListener;
    }

    public void off() {
        ((AutoDockManager) getShell().getData(AutoDockManager.DOCK_MANAGER)).off();
    }

    private boolean isAutoDockEnabled() {
        return getShell().getData(AutoDockManager.DOCK_MANAGER) != null;
    }

    /**
     * ?????
     * 
     * @return
     */
    private boolean isDocking() {
        return ((AutoDockManager) getShell().getData(AutoDockManager.DOCK_MANAGER)).isDocking();
    }

    /**
     * ???
     * 
     * @return
     */
    private boolean isPending() {
        return ((AutoDockManager) getShell().getData(AutoDockManager.DOCK_MANAGER)).isPending();
    }

    /**
     * ??dockpending
     * 
     * @return
     */
    public boolean isOff() {
        AutoDockManager manager = (AutoDockManager) getShell().getData(AutoDockManager.DOCK_MANAGER);
        if (manager == null)
            return true;
        else
            return manager.isOff();
    }

    /**
     * 
     */
    private void onSMS() {
        ReceiveIMPacket packet = (ReceiveIMPacket) mq.getSMS();
        if (packet == null)
            shellLauncher.openSMSWindow();
        else {
            if (packet.sms.sender == 10000) {
                /*
                 * ?
                 */
                String mobile = packet.sms.senderName;
                SMSWindow window = shellLauncher.openSMSWindow(mobile);
                window.putSMS(packet);
                packet = (ReceiveIMPacket) mq.getSMS(mobile);
                while (packet != null) {
                    window.putSMS(packet);
                    packet = (ReceiveIMPacket) mq.getSMS(mobile);
                }
            } else {
                /*
                 * QQ?
                 */
                User f = ModelRegistry.getUser(packet.sms.sender);
                if (f == null) {
                    f = new User();
                    f.qq = packet.sms.sender;
                }
                SMSWindow window = shellLauncher.openSMSWindow(f);
                window.putSMS(packet);
                packet = (ReceiveIMPacket) mq.getSMS(f.qq);
                while (packet != null) {
                    window.putSMS(packet);
                    packet = (ReceiveIMPacket) mq.getSMS(f.qq);
                }
            }
            uiHelper.stopBlinkSMSIcon();
            uiHelper.resetTrayImageEffect();
        }

    }

    public TimerHelper getTimerHelper() {
        return timerHelper;
    }

    public void setTimerHelper(TimerHelper timerHelper) {
        this.timerHelper = timerHelper;
    }

    /**
     * @return the currentPanel
     */
    public int getCurrentPanel() {
        return currentPanel;
    }

    /**
     * @return the jobQueue
     */
    public IExecutor getDiskJobQueue() {
        return diskJobQueue;
    }

    /**
     * @return the diskManager
     */
    public DiskManager getDiskManager() {
        return diskManager;
    }

    /**
     * @return the longTimeJobQueue
     */
    public IExecutor getIMJobQueue() {
        return imJobQueue;
    }

    /**
     * ?
     * 
     * @param event
     */
    private void onDiskViewerSelection(SelectionChangedEvent event) {
        IStructuredSelection s = (event == null) ? null : (IStructuredSelection) event.getSelection();
        if (s == null || s.isEmpty()) {
            if (menuHelper != null)
                menuHelper.setDiskMenuData(null);
            setDiskOpHint(NLS.bind(disk_hint_capacity, FileTool.getSizeString(diskManager.getUnused()),
                    FileTool.getSizeString(diskManager.getCapacity())));
            tiRefresh.setEnabled(false);
            tiDownload.setEnabled(false);
            tiUpload.setEnabled(false);
        } else {
            Object obj = s.getFirstElement();
            if (menuHelper != null)
                menuHelper.setDiskMenuData(obj);
            if (obj instanceof File) {
                File file = (File) obj;
                setDiskOpHint(NLS.bind(disk_hint_file, FileTool.getSizeString(file.size),
                        DateTool.format(file.modifiedTime)));
            } else
                setDiskOpHint(NLS.bind(disk_hint_capacity, FileTool.getSizeString(diskManager.getUnused()),
                        FileTool.getSizeString(diskManager.getCapacity())));

            if (obj instanceof Integer)
                tiRefresh.setEnabled(true);
            else if (obj instanceof Directory) {
                Directory dir = (Directory) obj;
                switch (dir.id) {
                case QQ.QQ_DISK_DIR_MY_FAVORITE:
                case QQ.QQ_DISK_DIR_MY_ALBUM:
                    tiRefresh.setEnabled(true);
                    break;
                default:
                    tiRefresh.setEnabled(false);
                    break;
                }
            } else
                tiRefresh.setEnabled(false);

            tiDownload.setEnabled(obj instanceof File);
            tiUpload.setEnabled(obj instanceof Directory && diskManager.isChildCreatable((Directory) obj)
                    || obj instanceof Integer && (Integer) obj == DiskContentProvider.MY_DISK);
        }
    }

    public TreeViewer getDiskViewer() {
        return diskViewer;
    }
}